diff --git a/CHANGELOG.md b/CHANGELOG.md index b231732af..ffa6eead5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- 루트 `README.md`를 구매자/운영자 표면으로 재작성한다. 제품 정의, 단독 실행, 호스트 호출(HTTP API·`contracts/orchestrator-gateway.json`), 운영 설정을 앞에 두고 naruon·곁(gyeot) 허브-리프 호출을 지원 경로로 문서화한다. CloudAgent/OpenCode 절차, PR 스택, exact-head CI, writer/agent 경계는 `CONTRIBUTING.md`와 `docs/development/contributor-and-agent-procedure.md`로 옮기며 삭제하지 않는다. `test/readme-customer-facing.test.ts`가 README에 봇 매뉴얼 잔여가 남지 않는지 고정한다. - 비리뷰 LLM 작업인 `hourly-product-development`를 리뷰와 동일한 `contextual-orchestrator` 게이트웨이 계약(`NOEMA_LLM_API_URL` `/v1`, 모델 별칭 `contextual-orchestrator`, 전용 `NOEMA_LLM_API_KEY`)으로 전환한다. Llama Nemotron → Nemotron Super → DeepSeek 순차 NIM 후보 폴백과 `NVIDIA_NIM_API_KEY` 직접 호출을 제거하고, 공유 `scripts/verify-orchestrator-gateway.mjs`가 `/healthz` 신원과 직접 공급자 호스트를 실패-폐쇄한다. 리뷰어의 `NOEMA_FALLBACK_*` / PydanticAI `FallbackModel` 순차 폴백도 제거해 남은 설정은 실패-폐쇄한다. 동일 계약을 `contracts/orchestrator-gateway.json`으로 공개해 `ContextualWisdomLab/naruon` 판단·결정 에이전트가 1급 소비자로 재사용할 수 있게 한다. naruon 배선은 별도 저장소 PR이다. 상위 공급자 키는 오케스트레이터 KV에 남기며 OIDC 토큰 중개·App 신원·3-runner 샌드박스 경계는 유지한다. - 검증된 active-orphan 워크플로 하나를 운영자가 호출할 수 있는 `operations:workflow-registry-disable` 경로를 추가한다. 저장소와 워크플로 ID를 `NOEMA_MAINTAINER_TOKEN_PATH` 위임 토큰 파일 읽기 전에 검사하고, 신선한 전체 레지스트리 감사·즉시 live refresh·프로세스 로컬 plan·보호된 main/워크플로 재검증·사후 전체 감사 봉투(`schema_version` 1, `PASS`/`FAIL`, `remaining_failure_codes`, `remaining_active_orphan_ids`)를 통과한 뒤에만 영수증을 유지한다. 성공 종료와 `post_audit_status: FAIL`은 해당 ID만 `disabled_manually`가 되었고 레지스트리는 아직 더럽을 수 있음을 뜻하므로, 운영자는 영수증의 `remaining_active_orphan_ids`로 다음 단일 호출을 이어간다. 배치 비활성화·자가 수리 워크플로·거버넌스 완화는 추가하지 않으며 호출 계약은 doctoring에 기록한다. - 읽기 전용 `operations:runner-assignment` audit를 추가해 exact workflow run/source head에 대한 runner assignment를 완전 pagination으로 진단하고, 신선한 unassigned queue는 bounded grace 이후 실패-폐쇄한다. 이 증빙은 runner assignment와 required Check/CI, formal review, merge, release, deployment authority를 분리하며 assigned runner 이후 workflow failure를 성공으로 승격하지 않는다. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5c6287310 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to Noema + +This file is for maintainers, contributors, and coding agents. Buyers and +operators should start at [`README.md`](./README.md). + +## Development + +```bash +npm install +npm test +npm run typecheck +npm run security:scan +``` + +There is no lint script. `typecheck` and tests are the code gates. Coverage is +scoped to `src/**/*.ts` and CI enforces 100% statements, branches, functions, +and lines. + +Update `CHANGELOG.md` (`## Unreleased`) with every behavior change. Docs in +`docs/` and the changelog are largely Korean; code, comments, and this file +are English. + +## Where procedure lives + +| Topic | Document | +| --- | --- | +| Customer/operator product surface | [`README.md`](./README.md) | +| Cross-agent security, secrets, and LLM gateway rules | [`AGENTS.md`](./AGENTS.md) | +| Bot, writer, and hourly-loop procedure | [`docs/development/contributor-and-agent-procedure.md`](./docs/development/contributor-and-agent-procedure.md) | +| Hourly product-development operations | [`docs/operations/hourly-product-development.md`](./docs/operations/hourly-product-development.md) | +| Hourly commercial-readiness / SHA-bound merge | [`docs/hourly-commercial-readiness-loop.md`](./docs/hourly-commercial-readiness-loop.md) | +| Review sandbox | [`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md) | + +Do not put CloudAgent, OpenCode session, PR-stacking, exact-head CI, or +do-not-merge procedure back into `README.md`. That file must stay +customer/operator facing. + +## Relocated hourly product-development summary + +`hourly-product-development.yml` runs a proposal-only coding session through +the same `contextual-orchestrator` gateway contract as review +(`NOEMA_LLM_API_URL`, `NOEMA_LLM_MODEL`, dedicated `NOEMA_LLM_API_KEY`) when +the PR queue is empty. It does not iterate a model-candidate list. It cannot +review, merge, release, or deploy; the existing hourly commercial-readiness +loop retains exact-head governance and SHA-bound merge authority. + +Full runner isolation, OpenCode pin, publication lease, and credential split: +[`docs/operations/hourly-product-development.md`](./docs/operations/hourly-product-development.md). +Doctoring (APA 7th, existing verified sources only): +[`docs/doctoring/hourly-nim-opencode-development.md`](./docs/doctoring/hourly-nim-opencode-development.md). diff --git a/README.md b/README.md index 3d1169604..2e96a5fad 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,182 @@ # Noema -Noema is ContextualWisdomLab's multi-purpose GitHub App bot: an OIDC token -broker, an independent LLM pull request reviewer, hourly product development, -and a first-class agent contract for `ContextualWisdomLab/naruon` judgments -and decisions. naruon wiring is a separate repository pull request; this -repository publishes the reusable gateway contract. - -It runs as a Cloudflare Worker on the Free tier: - -- GitHub Actions requests a GitHub OIDC token with audience `cwl-noema-review`. -- Noema verifies the OIDC issuer, audience, organization owner, and trusted central workflow identity. -- Noema exchanges the verified OIDC token for a GitHub App installation token scoped to the target repository. -- The central `.github` workflow uses that installation token to submit an LLM review verdict from a GitHub App identity separate from OpenCode Agent. -- The Project #1 review-bot roadmap keeps untrusted code/data analysis in a separate sandbox; see `docs/noema-agent-sandbox-plan.md`. - -The LLM call itself is configured in the central workflow with: - -- `NOEMA_LLM_API_URL` — the HTTPS `contextual-orchestrator` base URL ending in - `/v1` -- `NOEMA_LLM_MODEL` — normally the gateway routing alias - `contextual-orchestrator` -- `NOEMA_LLM_API_KEY` — a dedicated gateway inference token, never an upstream - provider key - -The same contract is reusable by naruon. The secret-free machine-readable -copy is [`contracts/orchestrator-gateway.json`](./contracts/orchestrator-gateway.json); -see [Orchestrator gateway consumer contract](./docs/orchestrator-gateway-consumer-contract.md). +Noema is a ContextualWisdomLab leaf product: a GitHub App credential broker +and an independent LLM pull-request reviewer. It runs on its own, and a host +calls it through a published HTTP API and a secret-free LLM gateway contract. +That hub-and-leaf call is the supported MSA path — **따로 또 같이** — not a +reason to merge repositories. + +It deploys as a Cloudflare Worker (Free tier) with two jobs: + +1. **Token exchange.** GitHub Actions presents an OIDC JWT (audience + `cwl-noema-review`). Noema verifies issuer, audience, organization owner, + and the exact trusted central workflow identity, then returns a + repository-scoped GitHub App installation token + (`pull_requests: write`, `contents: read`, `checks: read`). +2. **Review.** The default-branch + [`central-review`](./.github/workflows/central-review.yml) runtime accepts a + `noema-review` dispatch and publishes an App-authored verdict. Untrusted + analysis stays in a separate sandbox; see + [`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md). + +Every Noema LLM job — production review, hourly product development, and +host-side judgments — calls `ContextualWisdomLab/contextual-orchestrator`. +Upstream provider keys stay in the orchestrator credential KV. Noema does not +walk a sequential model list or fall back to a direct provider. + +## Composition hubs + +Leaf products stay independently deployable. Composition hubs call them as +published dependencies. Do not fold Noema into a hub repo. + +| Hub | Role | How it calls Noema | +| --- | --- | --- | +| [`naruon`](https://github.com/ContextualWisdomLab/naruon) | Judgments and decisions | First-class consumer of the published orchestrator gateway contract. Naruon wiring is a separate repository pull request. | +| [`gyeot` (곁)](https://github.com/ContextualWisdomLab/gyeot) | On-device wellness composition hub | Call Noema through the HTTP API and/or the same published contract when a host needs token exchange or the LLM gateway. | + +The machine-readable LLM contract is +[`contracts/orchestrator-gateway.json`](./contracts/orchestrator-gateway.json). +Narrative: [Orchestrator gateway consumer contract](./docs/orchestrator-gateway-consumer-contract.md). Print it with `node scripts/verify-orchestrator-gateway.mjs --print-contract`. -The product repository also owns the default-branch-only -[`central-review`](./.github/workflows/central-review.yml) runtime. It accepts a -`noema-review` `repository_dispatch` event containing `target_repository`, -`pr_number`, and the exact `pr_head_sha`; branch-selected manual workflow code -cannot receive the App key. The runtime waits up to 90 minutes for non-OpenCode -checks, initializes and explores CodeGraph at the exact target head, permits a -single provider request to run for 90 minutes, and delegates upstream failover -to `contextual-orchestrator` before publishing an App-authored review. Before -sending the manifest it rejects known direct-provider URLs and verifies the -unauthenticated `/healthz` service identity. It rejects target symlinks, strips -credentials from the CodeGraph subprocess, and revalidates the live head -immediately before publication. - -The production cutover is intentionally separate from the code change because -it creates organization variables and a secret. Follow the -[contextual-orchestrator reviewer cutover runbook](./docs/contextual-orchestrator-reviewer-cutover.md); -do not reuse `OPENAI_API_KEY` as Noema's gateway token. - -Example dispatch (bind the SHA from a fresh PR read, never from stale local -state): +Host LLM settings (never an upstream provider key): -```bash -gh api repos/ContextualWisdomLab/noema/dispatches -X POST --input - <<'JSON' -{"event_type":"noema-review","client_payload":{"target_repository":"ContextualWisdomLab/example","pr_number":1,"pr_head_sha":"0123456789abcdef0123456789abcdef01234567"}} -JSON -``` +| Name | Meaning | +| --- | --- | +| `NOEMA_LLM_API_URL` | HTTPS OpenAI-compatible base ending in `/v1` | +| `NOEMA_LLM_MODEL` | Routing alias, normally `contextual-orchestrator` | +| `NOEMA_LLM_API_KEY` | Dedicated gateway inference token | -## Required GitHub App permissions +`GET /healthz` must return +`{"status":"ok","service":"contextual-orchestrator"}`. Known direct-provider +hosts are rejected. Leftover `NOEMA_FALLBACK_*` settings fail closed. -Repository permissions: +## Run it alone -- Pull requests: Read and write -- Checks: Read-only -- Contents: Read-only +Requires Node.js 22+ (CI uses Node 24). This package is private; there is no +published npm library. The Worker and the HTTP contract are the product. -Install the app on `ContextualWisdomLab/.github` and target repositories that use the central required workflow. +```bash +npm install +npm test +npm run typecheck +npm run dev +``` -## Worker secrets +`npm run dev` starts a local Worker. Provision secrets on the Worker binding +(the KV-equivalent), not `process.env` in `src/`: -```powershell +```bash wrangler secret put GITHUB_APP_ID wrangler secret put GITHUB_APP_PRIVATE_KEY_PEM +# optional: pin a single installation instead of discovering by repository +wrangler secret put GITHUB_APP_INSTALLATION_ID ``` -Optional: +Deploy: -```powershell -wrangler secret put GITHUB_APP_INSTALLATION_ID +```bash +npm run deploy ``` -Runtime guardrail: +Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` (or the customer +central workflow) to the deployed `/exchange` URL. Production cutover that +creates organization variables or secrets is a separate operator step; see +[contextual-orchestrator reviewer cutover](./docs/contextual-orchestrator-reviewer-cutover.md). +Do not reuse `OPENAI_API_KEY` as Noema's gateway token. -- `NOEMA_RATE_LIMIT_PER_MINUTE` defaults to `60`. `/exchange` first applies a SQLite-backed Durable Object fixed-window limit coordinated across Worker isolates, then retains the original isolate-local limiter as defense in depth. Missing or malformed distributed decisions fail closed; see [Distributed rate limiting](./docs/distributed-rate-limiting.md). -- `NOEMA_OIDC_JWKS_CACHE_TTL_SECONDS` defaults to `300`; `NOEMA_INSTALLATION_CACHE_TTL_SECONDS` defaults to `600` to reduce repeated external lookups on hot paths. -- `/exchange` accepts only `POST` (`Allow: POST` on 405), returns standard Bearer challenges on 401, bounds untrusted trace/client headers before reflection or rate-limit keying, validates `target_repository` as a string before GitHub token creation, refreshes OIDC JWKS when a new signing `kid` appears, returns no-store/nosniff response headers, and keeps issued/inbound tokens out of operational logs. +## How a host calls it -## Deploy +The public HTTP surface is documented in [`openapi.json`](./openapi.json) and +[API 명세](./docs/api-spec.md). Every JSON response is +`{ ok: true, data, trace_id }` or +`{ ok: false, error_code, message, details, trace_id }`. -```powershell -npm install -npm run deploy +| Method | Path | Purpose | +| --- | --- | --- | +| `GET` | `/health` | Liveness only. Does not prove credential-exchange readiness. | +| `GET` / `HEAD` | `/ready` | Unauthenticated runtime readiness. Incomplete config returns `503 ERR_SERVICE_NOT_READY` without reflecting secrets. | +| `POST` | `/exchange` | Exchange a GitHub Actions OIDC bearer for a short-lived installation token. | + +Example liveness check: + +```bash +curl -sS "$NOEMA_BASE_URL/health" +``` + +Example exchange (OIDC bearer from GitHub Actions; JSON body optional, max +8,192 UTF-8 bytes): + +```bash +curl -sS -X POST "$NOEMA_EXCHANGE_URL" \ + -H "authorization: Bearer $ACTIONS_ID_TOKEN" \ + -H "content-type: application/json" \ + -d '{"target_repository":"ContextualWisdomLab/example"}' ``` -Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deployed `/exchange` URL. +`target_repository` must be a string `owner/name` in the allowed organization. +`/exchange` accepts only `POST` (`Allow: POST` on 405), returns Bearer +challenges on 401 (`invalid_request` vs `invalid_token`), and includes +`Cache-Control: no-store`, `Pragma: no-cache`, `X-Content-Type-Options: nosniff`, +`X-Trace-Id`, and `X-Latency-Ms`. Issued and inbound tokens must not appear in +logs. + +A host that only needs the LLM gateway copies +`contracts/orchestrator-gateway.json` (or calls +`node scripts/verify-orchestrator-gateway.mjs --print-contract`) and uses the +same `NOEMA_LLM_*` settings. Do not copy Noema's OIDC broker, GitHub App +identities, or sandbox/runner isolation into the host. + +The Python reviewer package (`reviewer/`) is the judgement plane. It consumes a +bounded PR manifest and can publish a `ReviewVerdict`. See +[`reviewer/README.md`](./reviewer/README.md). + +## Operator-facing review dispatch + +`central-review` is default-branch-only. Bind the SHA from a fresh PR read: + +```bash +gh api repos/ContextualWisdomLab/noema/dispatches -X POST --input - <<'JSON' +{"event_type":"noema-review","client_payload":{"target_repository":"ContextualWisdomLab/example","pr_number":1,"pr_head_sha":"0123456789abcdef0123456789abcdef01234567"}} +JSON +``` + +Runtime isolation, wait policy, and sandbox controls live in +[`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md) and +[`docs/development/contributor-and-agent-procedure.md`](./docs/development/contributor-and-agent-procedure.md). + +## Required GitHub App permissions + +Repository permissions: + +- Pull requests: Read and write +- Checks: Read-only +- Contents: Read-only + +Install the app on `ContextualWisdomLab/.github` and on target repositories +that use the central required workflow. + +## Operator configuration + +Public Worker vars (defaults in `wrangler.toml`): + +| Variable | Default | Role | +| --- | --- | --- | +| `ALLOWED_ISSUER` | `https://token.actions.githubusercontent.com` | GitHub Actions OIDC issuer | +| `ALLOWED_AUDIENCE` | `cwl-noema-review` | OIDC audience | +| `ALLOWED_REPOSITORY_OWNER` | `ContextualWisdomLab` | Allowed org | +| `ALLOWED_WORKFLOW_REPOSITORY` | `ContextualWisdomLab/.github` | Trusted workflow repo | +| `ALLOWED_WORKFLOW_REF_PREFIX` | `…/noema-review.yml@refs/heads/main` | Exact trusted workflow ref (name kept; matching is exact, not prefix) | +| `GITHUB_API_BASE` | `https://api.github.com` | GitHub Cloud API origin | +| `NOEMA_RATE_LIMIT_PER_MINUTE` | `60` | `/exchange` fixed-window budget | +| `NOEMA_OIDC_JWKS_CACHE_TTL_SECONDS` | `300` | OIDC JWKS cache | +| `NOEMA_INSTALLATION_CACHE_TTL_SECONDS` | `600` | Installation-id cache | + +`/exchange` first applies a SQLite-backed Durable Object fixed-window limit +across Worker isolates, then keeps the isolate-local limiter as defense in +depth. Missing or malformed distributed decisions fail closed; see +[Distributed rate limiting](./docs/distributed-rate-limiting.md). +OIDC `jti` values are consumed once by a Durable Object replay guard. ## 판매/운영 패키지 @@ -101,6 +186,7 @@ Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deplo - [온보딩 가이드](./docs/onboarding.md) - [운영 Runbook](./docs/runbook.md) - [Distributed Rate Limiting](./docs/distributed-rate-limiting.md) +- [Runtime readiness](./docs/runtime-readiness.md) - [Hourly Commercial-Readiness Loop](./docs/hourly-commercial-readiness-loop.md) - [Hourly Orchestrator Product Development](./docs/operations/hourly-product-development.md) - [SLA/지원 정책](./docs/sla-and-support.md) @@ -120,8 +206,7 @@ Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deplo - [Buyer Due Diligence Index](./docs/buyer-due-diligence-index.md) - [Transfer Readiness Plan](./docs/transfer-readiness-plan.md) - [Library Boundary Decision](./docs/library-boundary-decision.md) - -`hourly-product-development.yml` runs a proposal-only OpenCode session through the same `contextual-orchestrator` gateway contract as review (`NOEMA_LLM_API_URL`, `NOEMA_LLM_MODEL`, dedicated `NOEMA_LLM_API_KEY`) when the PR queue is empty. It does not iterate a model-candidate list. It cannot review, merge, release, or deploy; the existing hourly commercial-readiness loop retains exact-head governance and SHA-bound merge authority. +- [Contributing](./CONTRIBUTING.md) ## KPI 계산 diff --git a/docs/development/contributor-and-agent-procedure.md b/docs/development/contributor-and-agent-procedure.md new file mode 100644 index 000000000..1c6fa27d4 --- /dev/null +++ b/docs/development/contributor-and-agent-procedure.md @@ -0,0 +1,114 @@ +# Contributor and agent procedure + +Internal procedure for writers, coding agents, and maintainers. This is not +the customer README. Product facts for buyers and operators stay in +[`README.md`](../../README.md). + +## Writer and agent boundaries + +- `README.md` is customer/operator facing. Do not turn it into a bot work + manual (PR stacking, exact-head CI tips, do-not-merge checklists, CloudAgent + or OpenCode session steps, hourly-loop instructions). +- Cross-agent security and LLM-gateway rules live in [`AGENTS.md`](../../AGENTS.md). +- Secrets reach `src/` only through the typed Worker `Env` binding + (`wrangler secret put`). Do not introduce `process.env` / `os.getenv` secret + reads in `src/`. +- Do not sequentially try the next model or agent. The orchestrator selects + min-cost / max-performance. Do not configure a direct-provider fallback. +- Do not treat cancelled OpenCode or Strix bodies as paper or standard grounds. + Reuse existing verified APA 7th citations in `docs/doctoring/`; do not invent + papers or treat drafts as final. +- Do not merge this leaf into a composition hub. Hub-and-leaf calls through the + published API and `contracts/orchestrator-gateway.json` are the supported + path. Naruon and gyeot (곁) are intended composition hubs; keep those links. + +## Central-review runtime (relocated from README) + +The product repository owns the default-branch-only +[`central-review`](../../.github/workflows/central-review.yml) runtime. It +accepts a `noema-review` `repository_dispatch` event containing +`target_repository`, `pr_number`, and the exact `pr_head_sha`; branch-selected +manual workflow code cannot receive the App key. The runtime waits up to 90 +minutes for non-OpenCode checks, initializes and explores CodeGraph at the +exact target head, permits a single provider request to run for 90 minutes, and +delegates upstream failover to `contextual-orchestrator` before publishing an +App-authored review. Before sending the manifest it rejects known +direct-provider URLs and verifies the unauthenticated `/healthz` service +identity. It rejects target symlinks, strips credentials from the CodeGraph +subprocess, and revalidates the live head immediately before publication. + +The production cutover is intentionally separate from the code change because +it creates organization variables and a secret. Follow the +[contextual-orchestrator reviewer cutover runbook](../contextual-orchestrator-reviewer-cutover.md); +do not reuse `OPENAI_API_KEY` as Noema's gateway token. + +Example dispatch (bind the SHA from a fresh PR read, never from stale local +state): + +```bash +gh api repos/ContextualWisdomLab/noema/dispatches -X POST --input - <<'JSON' +{"event_type":"noema-review","client_payload":{"target_repository":"ContextualWisdomLab/example","pr_number":1,"pr_head_sha":"0123456789abcdef0123456789abcdef01234567"}} +JSON +``` + +Sandbox and evidence-collection isolation: +[`docs/noema-agent-sandbox-plan.md`](../noema-agent-sandbox-plan.md). + +## Hourly loops + +### Product development (proposal only) + +`.github/workflows/hourly-product-development.yml` runs a proposal-only +OpenCode session through the same `contextual-orchestrator` gateway contract as +review (`NOEMA_LLM_API_URL`, `NOEMA_LLM_MODEL`, dedicated `NOEMA_LLM_API_KEY`) +when the PR queue is empty. It does not iterate a model-candidate list. It +cannot review, merge, release, or deploy; the existing hourly +commercial-readiness loop retains exact-head governance and SHA-bound merge +authority. + +Operator narrative: +[`docs/operations/hourly-product-development.md`](../operations/hourly-product-development.md). +Prerequisites: +[`docs/operations/hourly-product-development-prerequisites.md`](../operations/hourly-product-development-prerequisites.md). + +### Commercial readiness (merge authority) + +`.github/workflows/hourly-commercial-readiness.yml` inspects open pull requests +and, only when current-head evidence is complete, performs a SHA-bound squash +merge. It does not checkout or execute PR-branch code. + +Operator narrative: +[`docs/hourly-commercial-readiness-loop.md`](../hourly-commercial-readiness-loop.md). + +Do not treat a green predecessor head, a synthetic merge ref, or a queued check +as acceptance. Exact-head CI and review evidence are recorded in those +operations docs, not in `README.md`. + +## Pull-request stacking and merge tips + +These tips are for agents and maintainers. They are not buyer documentation. + +- Keep stacked PRs in dependency order. A feature-base stacked PR may have no + Security Scan run; absence is non-passing evidence, not scanner success. + After the predecessor integrates, retarget onto an eligible protected base + (`main`, `master`, or `develop`) and require a fresh terminal-success + Security Scan on the unchanged exact head before merge. +- Do not merge from this procedure document. Human or existing + commercial-readiness authority decides merge. +- Do not mark a PR Ready when citations are unverified, when the change is + stacked on an unmerged parent, or when `README.md` has bot-manual leftover. +- A failing `trivy-fs` is a real finding. Remediate by bumping the vulnerable + npm dependency. Do not weaken the gate. See [`AGENTS.md`](../../AGENTS.md). + +## CloudAgent / OpenCode procedure + +OpenCode is a coding agent used by `hourly-product-development.yml` only. It is +not a customer interface and not a paper or standard. Pin, budget, runner +split, and publication lease are in +[`docs/operations/hourly-product-development.md`](../operations/hourly-product-development.md). +Do not clone an OpenCode sidecar into naruon or gyeot. Do not copy OpenCode +bot model-candidate lists. + +Cloud Agent / Cursor Cloud runs follow the same README rule: land +customer-visible product facts in `README.md`, and keep run procedure here or +in `AGENTS.md`. diff --git a/test/readme-customer-facing.test.ts b/test/readme-customer-facing.test.ts new file mode 100644 index 000000000..f89e71582 --- /dev/null +++ b/test/readme-customer-facing.test.ts @@ -0,0 +1,79 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +function readText(path: string): string { + return readFileSync(path, "utf8"); +} + +describe("README stays customer/operator facing", () => { + const readme = readText("README.md"); + const contributing = readText("CONTRIBUTING.md"); + const procedure = readText("docs/development/contributor-and-agent-procedure.md"); + + it("leads with product, standalone run, host call, and operator config", () => { + for (const requiredText of [ + "leaf product", + "따로 또 같이", + "naruon", + "gyeot", + "/health", + "/ready", + "/exchange", + "contracts/orchestrator-gateway.json", + "openapi.json", + "npm run dev", + "wrangler secret put", + "GITHUB_APP_ID", + "NOEMA_LLM_API_URL", + "NOEMA_LLM_API_KEY", + "contextual-orchestrator", + "hourly-product-development", + "NOEMA_RATE_LIMIT_PER_MINUTE", + ]) { + expect(readme).toContain(requiredText); + } + }); + + it("does not keep bot-manual leftover in README", () => { + for (const leftover of [ + "CloudAgent", + "OpenCode 1.17.13", + "OpenCode session", + "OpenCode Agent", + "PR stacking", + "stacked PR", + "do-not-merge", + "Do not merge", + "writer/agent", + "writer and agent", + "exact-head CI", + "Do not fabricate", + "proposal-only OpenCode", + ]) { + expect(readme, `README must not contain bot-manual leftover: ${leftover}`).not.toContain( + leftover, + ); + } + }); + + it("relocates contributor and agent procedure to internal docs", () => { + expect(contributing).toContain("docs/development/contributor-and-agent-procedure.md"); + expect(contributing).toContain("hourly-product-development.yml"); + expect(contributing).toContain("exact-head"); + expect(contributing).toContain("Do not put CloudAgent"); + + for (const requiredText of [ + "Writer and agent boundaries", + "central-review", + "pr_head_sha", + "OpenCode session", + "exact-head CI", + "PR stacking", + "Do not merge", + "CloudAgent", + "hourly-product-development.yml", + ]) { + expect(procedure).toContain(requiredText); + } + }); +});