The public documentation site lives in docs-site/ and is built with Astro + Starlight. English is
served at the site root, with Korean under /ko, Simplified Chinese under /zh-cn, Traditional Chinese under /zh-tw, Russian under /ru, and Japanese under /ja. docs-site/astro.config.mjs is the locale source of truth.
Manual navigation is defined in docs-site/astro.config.mjs. When adding a public page, update the
sidebar and either add localized copies or intentionally accept Starlight fallback behavior.
.github/workflows/deploy-docs.yml publishes the docs to:
https://opencodex.me/
The workflow runs on main pushes touching docs-site/** or the workflow itself, builds
docs-site, uploads the artifact, and deploys with GitHub Pages.
[Decision Log]
- 목적과 의도: Serve the public documentation from the memorable first-party
opencodex.medomain. - 기존 구현 및 제약 조건: The project Pages site was built for
lidge-jun.github.io/opencodex, so Astro emitted a/opencodexbase path that returns 404 under a root custom domain. - 검토한 주요 대안: Keep the GitHub project URL as canonical; redirect the custom domain through Cloudflare; configure the custom domain directly on GitHub Pages and build for the domain root.
- 선택한 방식: Keep GitHub Actions Pages hosting, configure
opencodex.meas the repository custom domain, publish root-relative assets and routes, and retain the default GitHub URL only as GitHub's automatic redirect. - 다른 대안 대신 이 방식을 선택한 이유: Direct Pages hosting preserves the existing deployment and HTTPS lifecycle without adding a second proxy or redirect service.
- 장점, 단점 및 영향: Public links and canonical metadata become stable and branded. DNS and the Pages custom-domain setting are now deployment dependencies, and old hardcoded
/opencodexlinks must not be reintroduced.
Local validation:
cd docs-site
bun install --frozen-lockfile
bun run build[Decision Log]
- 목적과 의도: Prevent a failed npm replacement from making the Task Scheduler wrapper retry missing package files forever.
- 기존 구현 및 제약 조건: The wrapper deliberately restarts a proxy after runtime crashes, but an absent baked Bun or CLI path cannot recover inside that process. Current updater preflight and stop-first behavior reduce replacement risk but do not provide a transactional restore of npm's package tree and global launchers.
- 검토한 주요 대안: Keep unconditional five-second retries, add a generic crash ceiling, restore npm directories in-place, or classify only proven missing executable paths as terminal.
- 선택한 방식: Check the baked Bun and CLI paths before every spawn; log one actionable incomplete-install message and exit with code 3 when either is absent. Preserve the existing retry loop for a child that actually launched and then failed.
- 다른 대안 대신 이 방식을 선택한 이유: A generic retry ceiling can stop a service after unrelated intermittent crashes, while copying a package directory without matching npm shims, ownership, and lock guarantees is not a safe rollback.
- 장점, 단점 및 영향: File-less package skeletons no longer produce unbounded service logs or restart churn. The wrapper still recovers ordinary proxy crashes, but repairing an incomplete npm install remains an explicit reinstall plus
ocx service repairoperation until a verified staged-update design exists.
| Workflow | Trigger | Purpose |
|---|---|---|
.github/workflows/ci.yml |
pull_request to main/dev, push to main/preview/dev, or manual dispatch when runtime/package paths change |
Cross-platform runtime/package quality gate. Linux runs the suite as four parallel shards (test 1/4–4/4) plus a consolidated gates job; macOS runs the full suite. Windows runs the full suite only on a push to main/preview or a manual dispatch — it is the shipping boundary, not the pull-request lane, because it was last to finish in every sampled run at roughly three times the Linux median. The aggregate ci job asserts platform-windows actually succeeded on those boundary events rather than accepting a skip. npm-global-smoke always remains GitHub-hosted because it mutates the global package prefix. |
.github/workflows/release.yml |
Manual dispatch only | npm publish/dry-run workflow. It requires the exact GITHUB_SHA to have a successful Cross-platform CI run before publish or dry-run. |
.github/workflows/deploy-docs.yml |
push to main touching docs-site/** or the workflow, or manual dispatch |
Build and publish the Astro/Starlight docs site to GitHub Pages. |
.github/workflows/service-lifecycle.yml |
pull_request to main/dev and push, both filtered on the service path set (src/service.ts, src/cli.ts, src/cli/index.ts, src/lib/bun-runtime.ts, package.json, bun.lock, the workflow), or manual dispatch |
Service-lifecycle smoke on three platforms: Linux systemd, macOS launchd, and Windows Scheduled Tasks. Each installs, verifies, stops via ocx stop, and uninstalls. The path list is kept in sync with the release.yml service-gate regex. |
.github/workflows/enforce-pr-target.yml |
pull_request_target (opened, reopened, edited, labeled, unlabeled, ready_for_review, synchronize) plus default-branch status events filtered to successful CodeRabbit statuses |
The enforce-target gate: rejects pull requests whose head ancestry sits on the main tip while far behind dev, rejects empty or malformed descriptions, requires a GUI screenshot when the title/body mentions gui (immediately waivable with the maintainer-controlled gui-screenshot-waived label; legacy maintainer comments remain compatibility evidence on later PR events), keeps contributor PRs in draft until a four-box readiness checklist is complete, verifies the CI / latest-dev / Codex+CodeRabbit-findings claims (review threads plus current-head CodeRabbit review-body findings outside the diff range), and adds a review-ready status label at the ready moment. CodeRabbit status SHAs must resolve to exactly one open current-head PR before writes. Stacked child PRs targeting another open PR's head skip the wrong-base gate. |
.github/workflows/enforce-issue-quality.yml |
issues (opened, edited, reopened), issue_comment (created, edited), or manual dispatch with an issue number |
Issue-template compliance gate. |
.github/workflows/issue-quality-tests.yml |
pull_request and push filtered on the issue/PR automation scripts, templates, and their workflows |
Tests the issue and PR automation scripts themselves, so the gates cannot rot silently. |
.github/workflows/issue-triage.yml |
issues (opened) |
Duplicate detection and triage labeling for new issues. |
.github/workflows/pr-labeler.yml |
pull_request_target (opened, edited, synchronize, labeled, unlabeled) |
Type and path labeling plus title sync; labeled/unlabeled let a human override enqueue a fresher run in the per-PR concurrency group. |
.github/workflows/react-doctor.yml |
pull_request (opened, synchronize, reopened, ready_for_review) and push to main; no path filter |
React-focused static review. Findings fail the job; write-scoped outputs stay disabled, a contract pinned by tests/ci-workflows.test.ts. |
.github/workflows/stale-needs-info.yml |
schedule only (daily 06:15 UTC); deliberately no manual dispatch |
Closes issues left in needs-info past the grace period. Manual dispatch is omitted so a branch-selected run cannot execute that branch's body with issue write scope. |
pull_request_target, issues, and schedule workflows always load from the repository default
branch, not from dev. Landing a change to one of them on dev does not change live behavior until
it is promoted, so those files follow the promotion model rather than ordinary integration.
The Windows selector is an operational stability control, not a security boundary. A pull request
controls the pull_request workflow body and can rewrite an event-name check, repository variable,
or selector output. Because this is a public user-owned repository and runner groups are unavailable,
the repository setting Fork pull request workflows from outside collaborators: Require approval
for all outside collaborators (all_external_contributors) must remain enabled before any self-
hosted runner is registered. Maintainers must inspect workflow changes before approving an external
run. If that setting cannot be verified, unset OCX_SELF_HOSTED_WINDOWS and deregister the runner;
the workflow then fails back to windows-latest rather than exposing a persistent maintainer host.
Docs-only changes intentionally route through the docs workflow instead of the runtime CI gate. If a
docs change also edits runtime/package/release files, run the relevant local runtime checks before
push and let ci.yml provide the Linux/Windows confirmation. Service-related changes
(src/service.ts, src/cli/index.ts, and the rest of the service path set) additionally trigger the
service-lifecycle.yml smoke test on all three platforms.
The root READMEs are the concise product entrypoint. They should explain what opencodex does, how to
install/start it, where Codex state is touched, and where the full docs live. Deep implementation
invariants belong in structure/, not the README.
docs/ contains investigations and diagnostic notes. Do not treat it as the current public user
manual. When an investigation graduates into a maintained invariant, summarize it here under
structure/ and link public workflows from docs-site/.
AGENTS.md and MAINTAINERS.md are authoritative; this section
exists so the repository-shape source of truth does not omit the shape of its own history.
devis the single integration branch and the target for ordinary pull requests.mainmoves only by maintainer-controlled promotion;previewcarries thex.y.z-preview.*train. One documented exception: a stacked child PR may target another open PR's head branch as a review workflow, and is retargeted todevonce the parent lands or closes.- Bun-native TypeScript on
devis the only runtime line. The former Go native-runtime experiment is retired and archived, and nogo/tree is tracked in this repository; a localgo/directory is untracked leftovers. If native code returns, the expectation is an incremental module landing ondev, not a second full-runtime branch. devlog/is a tracked directory in this repository — no submodule, no private mirror. Open units live indevlog/_plan/, closed units indevlog/_fin/, and external parity references indevlog/_chase/(the reference clones themselves are gitignored).- The runtime does not consume
devlog/, so a contributor who ignores it still builds and runs. Repository checks do read it deliberately:privacy:scanscans it, andtests/repo-hygiene.test.tsenforces the mechanical guards — no tracked160000gitlink anywhere, devlog Markdown tracked as ordinary blobs, no.gitmodules, and no open plan carrying an unresolved security verdict on a security-boundary topic. Some unit-scoped release gate scripts resolve their evidence directory fromdevlog/_planor_finas well. - Security work in progress does not go in any tracked directory. Scratch space only; only the published outcome — the fix, its regression test, the release note, the advisory once public — reaches the repository.
MAINTAINERS.md is the source of truth for current project roles and the review and merge policy.
.github/CODEOWNERS declares default reviewers and repeats ownership for authentication, repository
automation, release, and governance paths where an explicit security review is required. GitHub
repository settings remain the source of truth for actual account permissions and protected-branch
enforcement.
[Decision Log]
- 목적과 의도: Make project ownership and review authority discoverable without exposing credentials or treating a documentation file as an access-control mechanism.
- 기존 구현 및 제약 조건: Contribution and security docs referred to maintainers generically, while the repository had no maintainer roster or CODEOWNERS policy. GitHub permissions can change independently of the source tree.
- 검토한 주요 대안: Keep the roster only in GitHub settings; introduce a larger standalone governance charter; list raw GitHub permission levels in the repository.
- 선택한 방식: Add a concise maintainer roster and merge policy, use CODEOWNERS for review routing, and keep actual permission state authoritative in GitHub settings.
- 다른 대안 대신 이 방식을 선택한 이유: A two-maintainer project needs clear ownership and sensitive-path review rules but does not yet need a separate governance framework.
- 장점, 단점 및 영향: Contributors can identify reviewers and merge expectations directly from the repository. The roster must be updated when responsibilities change, and CODEOWNERS still requires branch-protection configuration to enforce approvals.
The source runs on Bun, but the published package does not require a user-installed Bun.
package.json bin points at bin/ocx.mjs (a Node shim), and the Bun runtime ships as the bun
npm dependency (esbuild-style: a tiny main package plus platform-specific @oven/bun-*
optionalDependencies, finalized by the dependency's own postinstall: node install.js).
Invariants:
bin/ocx.mjsresolves the bundled binary viarequire.resolve("bun/package.json")and a size gate (>= 1 MB) that rejects the ~450-byte placeholder stub left by--ignore-scripts/pnpm; it then lazy-runsinstall.jsand execssrc/cli/index.tsunder Bun, propagating exit code and signal.package.jsoncarries"trustedDependencies": ["bun"]sobun installruns the dependency's postinstall, and"engines": { "node": ">=18" }(Bun is no longer a user prerequisite).- The plain-Node launcher owns
OPENCODEX_BUN_PATHselection before Bun can load project dotenv and stamps the chosen source/path pair.src/service.tsandsrc/codex/shim.tsbake that already- selected executable (normally the bundled binary, stable under the npm global prefix) into launchd/systemd/Task Scheduler and the Codex autostart shim. Bun-side code never re-selects a durable executable from the post-dotenv environment. - Public docs (root READMEs +
docs-siteinstallation pages, all locales) state Node 18+ as the only prerequisite. Do not reintroduce "install Bun first" / "bun must be on PATH" guidance for npm users.
Package release is npm-focused. package.json exposes opencodex and ocx, prepublishOnly runs
typecheck and GUI build, and scripts/release.ts now runs local typecheck, bun test --isolate tests, and
bun run privacy:scan before the version bump, commit/push, Cross-platform CI wait, and GitHub
Release workflow dispatch. Docs publishing is separate from npm release publishing.
Release notes are rendered OpenAI-Codex-style by scripts/release-notes.ts render inside
.github/workflows/release.yml: ## New Features / ## Bug Fixes / ## Documentation /
## Chores / ## Other Changes sections with prefix-free, scope-grouped summary bullets
(- Providers: Add X; Add Y (#1, #2)), followed by a ## Changelog section listing every PR
as - #N <title> @author; when a comparison baseline exists, that section also includes a
compare link. Carried preview changelogs and the since-preview delta feed the same renderer,
so stable notes are the aggregate of their preview train. The raw commit dump is
intentionally gone — non-PR commits stay reachable via the Full Changelog compare link when
that link is available.
The deterministic renderer produces the structure but not curated prose. Maintainers who want the OpenAI-style grouped summaries can run the optional local polish step against the rendered body (needs an OpenAI-compatible API key):
bun scripts/release-notes.ts render ... --out notes.md
bun scripts/release-notes.ts polish --in notes.md --out notes.mdpolish rewrites only the category sections, keeps the machine-rendered Changelog verbatim,
and fails closed when the rewrite drops, invents, or re-heads any PR reference. It is never
called from CI — there is no LLM credential on the runner — so the workflow ships the
deterministic body whenever the maintainer skips it.
Every npm release version must map cleanly across four surfaces:
| Surface | Required state |
|---|---|
package.json |
version equals the release workflow version input. |
| npm registry | @bitkyc08/opencodex@<version> does not exist before publish, then exists after publish with the requested dist-tag. |
| Git tag | v<version> does not exist before publish, then points at the exact release commit. |
| GitHub Release | v<version> does not exist before publish, then is created from the exact release commit. |
The release must fail before npm publish if npm, the Git tag, or the GitHub Release already has the
requested version. This prevents partial releases where npm is published but GitHub Release creation
fails afterward.
Do not force-move public version tags by default. If release metadata is already inconsistent, treat the version as consumed and publish the next unused patch version instead. Only rewrite a public tag after an explicit human decision that the public history rewrite is acceptable.
Manual preflight checks when debugging a release:
npm view @bitkyc08/opencodex@<version> version
git ls-remote origin refs/tags/v<version>
gh release view v<version>If any of these commands reports an existing artifact for the requested version, stop before
publishing. For a non-destructive recovery, choose the next unused patch version and release that
version through scripts/release.ts.
.github/workflows/ci.yml is the ordinary quality gate for runtime/package changes. Linux runs
the suite in four shards with a separate gates job, macOS runs it whole, and Windows runs whole
but only at the shipping boundary (push to main/preview, or manual dispatch). Each lane runs:
bun install --frozen-lockfile
bun x tsc --noEmit
bun test --isolate tests
bun run privacy:scan
bun build scripts/release.ts --target=bun --outdir=.tmp/ci-release-script-check
cd gui && bun install --frozen-lockfile && bun run lint && bun run build
bun run src/cli/index.ts helpand the Node-only global-install smoke path:
npm install
npm run build:gui
npm pack --json > pack.json
npm install -g ./bitkyc08-opencodex-*.tgz
ocx helpThe CI intentionally does not build docs, run coverage, or perform remote Ubuntu/RDP smoke tests. Those stay outside the default gate until a concrete regression justifies the extra runtime.
The Release workflow remains manual and publish-focused. Before any dry-run or publish step, it
checks that the exact release commit (GITHUB_SHA) already has a successful Cross-platform CI run.
This keeps release runs short and makes release a deployment of a verified commit rather than a
second CI pipeline.