diff --git a/AGENTS.md b/AGENTS.md index c17d90e976..f616781960 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -927,209 +927,67 @@ review sessions on someone else's PR still function normally. ## Automated review coverage (owner decision, 2026-08-22) -CodeRabbit's included allowance is exhausted and the organisation has reached its usage spending -cap, so it declines most reviews with "Review limit reached" — measured on PRs #2113, #2252, #2255, -#2256, #2263 and #2278 between 2026-08-18 and 2026-08-22. The owner has decided to **leave the cap -as it is and accept that CodeRabbit review is intermittent**, rather than raise it (`#CCZ4HB`). - -**Read `docs/decisions/ccz4hb-review-coverage.md` before acting on this.** A separate analysis -written the same day measured the cause and it is not primarily billing: CodeRabbit's included -reviews refill at one per hour (24/day) while this repo merged 25.4 PRs/day on average over the -preceding month. It also found that 285 of the last 1,190 merged PRs (24%) changed only -documentation and 190 (16%) changed only the repo's own record-keeping files — roughly six review -credits a day spent on files a code-review bot has nothing to say about. Two levers therefore remain -open at **no cost and no loss of safety**, and the owner's decision above does not foreclose them: -configure CodeRabbit to skip documentation-only PRs, and stop opening PRs whose only content is a -bookkeeping record. Do those before anyone proposes raising the cap again. - -What the decision means in practice, and what it does not mean: - -- The ChatGPT Codex connector reviewed PR #2278 on 2026-08-22 in the same run CodeRabbit skipped, so - as of that date automated review is reduced rather than absent. Do not treat that as permanent — - the connector reported its own usage limit on PR #2113 on 2026-08-18, so it can lapse too. -- Draft PRs are skipped by CodeRabbit outright, so a PR that stays in draft gets nothing from it - even when allowance is available. Undrafting mid-CI also cancels the in-flight run. -- **Do not weaken, skip, or relax any required check to compensate.** The required gates are now - carrying more of the load, not less, and the whole point of accepting reduced bot review is that - the deterministic checks stay strict. -- Clinical-risk and RAG-surface diffs still require their PR-body preflight sections in full; those - are enforced by `scripts/pr-policy.mjs` and are unaffected by review-bot availability. - -Reducing PR churn (below) remains the cheapest way to get more value from the allowance that exists. +CodeRabbit's included allowance is capped and review is intermittent (`#CCZ4HB`). The decision and root-cause analysis are documented in `docs/decisions/ccz4hb-review-coverage.md`. + +- Draft PRs are skipped by CodeRabbit outright; undrafting mid-CI cancels the in-flight run. +- **Do not weaken, skip, or relax any required check to compensate.** Required gates carry the deterministic safety net and must stay strict. +- Clinical-risk and RAG-surface diffs still require their PR-body preflight sections in full (`scripts/pr-policy.mjs`). +- Reduce PR churn by bundling low-risk append-only paperwork with product PRs (see below). ## PR bundling (reduce one-task-one-PR churn) -Every `newtask`/`handoff` cycle mints a dedicated `claude/` branch and PR, so a -single docs/ledger-append line pays the same required-CI bill as a large change: -`static-pr`, `pr-required`, and whatever path-scoped job the diff happens to trigger. -Measured 2026-07-30 (PR #1406, sampling the last 500 CI workflow runs, ~3 days of PR -traffic): 437 PR-triggered runs, ~40% cancelled mid-run (mostly superseded by a newer -push before Production UI finished), roughly 12 Production-UI-hours burned on runs that -never completed. More PRs also means more `docs/branch-review-ledger.md` rows and more -of the anti-churn re-syncing described above. - -Before opening a new branch, check whether the task can ride an **already-open PR you -still own** or be bundled with **other currently-queued low-risk work** instead of -minting a new one. If the target PR's CI is already running, either wait for it to settle -before pushing the addition or assemble every commit before that PR's first push — -`.github/workflows/ci.yml` cancels in-progress runs for pull requests, so a push mid-run -cancels and restarts CI rather than saving an invocation, reproducing the exact cancellation waste -this rule exists to cut (reproduced 2026-07-30 pushing a second commit to PR #1406: the -in-flight `static-pr` run was cancelled, failing `pr-required` on the now-stale head). -A settle-then-push addition also lands after this repo's one automatic Codex review may -already have run against the earlier head — in practice the connector re-reviews each new -push (observed on this same PR), but if it doesn't, request a fresh review explicitly -before merging rather than assuming the addition was covered. **If the target PR has -auto-merge armed, an ordinary fast-forward push is still safe to bundle onto** — GitHub -re-validates required checks against the new head before merging. Per-PR auto-merge state is -user-owned: automation must not disable or re-enable it, and a force-push or base/target change -while armed still hard-blocks with no override — that is the actual race, not an additive -commit. `guard-push.mjs` enforces the force-push block for every locally pushed PR branch when -authenticated `gh` is available; agent -policy remains the backstop in environments where local hooks or `gh` are unavailable. +Before opening a new branch, check whether the task can ride an **already-open PR you still own** or be bundled with **other currently-queued low-risk work** instead of minting a new one. If the target PR's CI is already running, wait for it to settle before pushing the addition or assemble every commit before that PR's first push (pushes mid-run cancel and restart CI). + +**If the target PR has auto-merge armed, an ordinary fast-forward push is still safe to bundle onto** — GitHub re-validates required checks against the new head before merging. Per-PR auto-merge state is user-owned: automation must not disable or re-enable it, and a force-push or base/target change while armed still hard-blocks with no override. `guard-push.mjs` enforces the force-push block for locally pushed PR branches when authenticated `gh` is available; agent policy remains the backstop. + Bundle only when every item being combined is: -- **Independently low-risk, checked two ways — neither is exhaustive alone.** - First, `scripts/pr-policy.mjs` / `classifyPullRequestFiles` must return - `clinicalRisk: false`, `operationalRisk: false` (dependency manifests, lockfiles, - `.github/workflows/**`, build/test-runner config), and no RAG-ranking-surface path. - Second, the diff must not touch anything in this repo's own broader "PR risk - detection" list below (auth, privacy, migrations/RLS, clinical/RAG/retrieval, - **background jobs/workers/queue processing**, payment/billing, public API contracts, - production config/deployment, file upload/download, provider/paid-API calls) — - that list catches real high-risk paths the narrower classifier doesn't flag at all - (e.g. `worker/**` trips neither `clinicalRisk` nor `operationalRisk`, but ingestion - workers are exactly the kind of change this exclusion exists for). When a path's risk - category is genuinely unclear under either check, default to its own PR rather than - extending the exclusion list further — the two checks together are a floor, not a - closed enumeration. -- **Still its own committed, separately revertible commit while the PR is open** — - bundling means one PR with multiple commits, never one squashed diff; `git revert ` - must undo any one item without touching the others before merge. That guarantee ends at - merge: this repo's normal squash-merge folds every commit into one on `main`, and once - the feature branch is deleted those original SHAs are unreachable. Reverting a single - bundled item after merge means reverting the relevant hunks of the squash commit by - hand, not `git revert ` on an item's original commit — keep an item out of the - bundle if it might need its own durable post-merge revert. -- **Listed as its own bullet** in the PR body's Summary, not blended into one narrative - — a reviewer (and `pr-policy.mjs`) still needs to find each item's own - governance/RAG-impact statement if it needs one. -- **Not already mid-edit** in another open PR or session — check local context first - (`docs/branch-review-ledger.md`, `git branch`/`git log`). Confirming against the live - open-PR list means a GitHub API read: only do that with this session's already- - authorized GitHub access, or ask before querying GitHub, per "API and provider - confirmation boundary" — do not treat it as a silent, unconditional prerequisite that - blocks starting ordinary work. - -**Best candidates:** small same-scope documentation or low-risk follow-up changes. Review records -and issue requests now use independent immutable files, so they should travel with their owning -product PR instead of receiving a dedicated ledger-only branch. +- **Independently low-risk, checked two ways:** + 1. `scripts/pr-policy.mjs` / `classifyPullRequestFiles` must return `clinicalRisk: false`, `operationalRisk: false`, and no RAG-ranking-surface path. + 2. The diff must not touch anything in this repo's broader "PR risk detection" list (auth, privacy, migrations/RLS, clinical/RAG/retrieval, background jobs/workers/queue processing, payment/billing, public API contracts, production config/deployment, file upload/download, provider/paid-API calls). +- **Committed as its own separately revertible commit** while the PR is open (one PR with multiple commits, not one squashed diff). +- **Listed as its own bullet** in the PR body's Summary. +- **Not already mid-edit** in another open PR or session (check local context / review ledger first). + +**Best candidates:** small same-scope documentation, immutable review records (`docs/branch-review-records/`), or queued issue requests (`docs/outstanding-issues-inbox/`). **Never bundle:** -- A change needing its own `RAG impact:` line together with one that doesn't. +- A change needing its own `RAG impact:` line together with one that does not. - A change needing `## Clinical Governance Preflight` together with unrelated chores. -- Anything explicitly scoped "1 PR per work order" by its own tracking doc (e.g. the - maturity backlog in `docs/maturity-backlog-workorders.md`, ledger `#086`) — those are - deliberately isolated for staged rollout and review. +- Anything explicitly scoped "1 PR per work order" by its own tracking doc (e.g. `docs/maturity-backlog-workorders.md`). -Bundling saves PR/CI-invocation count, not verification rigor — every bundled item still -gets the smallest correct gate run against it before it joins the PR. +Bundling saves PR/CI-invocation count, not verification rigor — every bundled item still gets the smallest correct gate run against it before joining the PR. ## Anti-conflict and CI-speed operating procedure -Goal: fewer false merge conflicts, less cancelled CI, and faster feedback — without -weakening required gates, flake policy, provider boundaries, or clinical/RAG safeguards. -Do not touch unrelated active PRs unless the user explicitly asks (`Run PR`, sync, or a -named PR). Future process only. +Goal: fewer false merge conflicts, less cancelled CI, and faster feedback — without weakening required gates, flake policy, provider boundaries, or clinical/RAG safeguards. Do not touch unrelated active PRs unless the user explicitly asks (`Run PR`, sync, or a named PR). ### Prevent conflicts before they start -- Prefer fewer, shorter-lived PRs. Bundle independently low-risk append-only docs/ledger - chores (see "## PR bundling") instead of one PR per line. -- Start from a fresh `origin/main` worktree/branch (`newtask`); do not pile new work onto a - stale head that already shares hot files with the open queue. -- The legacy `docs/branch-review-ledger.md` and `docs/outstanding-issues.md` are **serial-only**: - normal PRs must not add rows there. `npm run ledger:append` creates an immutable review record; - `npm run issues:add|update|queue|done` creates one immutable inbox request (`queue` corrects a - recommended-execution-queue row; see ledger `#M6JNR8`). One fresh-base, - cross-worktree-locked `npm run issues:reconcile` operation applies landed requests to the - canonical issue ledger. `check:ledger-write-discipline` rejects direct table-row edits, - changed request records, deleted requests, and a canonical issue diff that does not exactly - equal its recorded reconciliation transaction. The legacy review table's merge attribute remains - unspecified: GitHub cannot rely on local drivers. -- Before calling GitHub `DIRTY`/`CONFLICTING` a real conflict, run - `git merge-tree --write-tree origin/main `. Clean tree + behind = sync; dirty tree = - real conflict. +- Prefer fewer, shorter-lived PRs. Bundle independently low-risk append-only docs/ledger chores (see "## PR bundling") instead of one PR per line. +- Start from a fresh `origin/main` worktree/branch (`newtask`); do not pile new work onto a stale head that already shares hot files with the open queue. +- The legacy `docs/branch-review-ledger.md` and `docs/outstanding-issues.md` are **serial-only**: normal PRs must not add rows there. `npm run ledger:append` creates an immutable review record; `npm run issues:add|update|queue|done` creates one immutable inbox request (`queue` corrects a recommended-execution-queue row; see ledger `#M6JNR8`). One fresh-base, cross-worktree-locked `npm run issues:reconcile` operation applies landed requests to the canonical issue ledger. `check:ledger-write-discipline` rejects direct table-row edits, changed request records, deleted requests, and a canonical issue diff that does not exactly equal its recorded reconciliation transaction. +- Before calling GitHub `DIRTY`/`CONFLICTING` a real conflict, run `git merge-tree --write-tree origin/main `. Clean tree + behind = sync; dirty tree = real conflict. ### Speed CI without skipping quality -- Assemble every commit for a head before the first push, or wait for the current PR CI run - to settle before pushing again. Apply the same settle-first rule to branch syncs: for a - behind-but-clean PR with required CI in flight, wait, then perform at most one late - `update-branch` / `git merge origin/main` after review and fix work is assembled. - Cancel-in-progress should remain enabled **for pull requests**, where a newer head genuinely - supersedes the work in flight, but every superseding push or sync cancels Production UI - mid-flight (~40% of recent PR CI runs were cancellations). It is deliberately NOT enabled for - base-branch pushes: a merged commit cannot be superseded, so cancelling there destroyed the - only verification `main` received (23 of the last 30 main pushes cancelled, measured - 2026-08-18). Do not "simplify" that exemption back to a blanket `true` — it is pinned by - `tests/ci-cache-safety.test.ts`. -- For Run PR sweeps and normal readiness pushes — never an explicit bare PR publication — run - `npm run format` **and commit the result**, then `npm run verify:pr-local` (or the smallest - gate that covers the change). Format is in `static-pr` but not in `verify:cheap`; an - uncommitted format leaves CI red on the pushed blob. Whole-tree Prettier, not a single edited - file. -- If a PR has auto-merge armed, its auto-merge state is user-owned and automation must not disable - or re-enable it. Ordinary fast-forward pushes, `update-branch`/merge-main-in syncs, and bundled - additions may proceed — GitHub re-validates required checks against the new head before merging, - so an additive push cannot slip past that. A force-push, history rewrite, or base/target change - while armed still hard-blocks with no override; wait for the user to change that state first. -- Missing CI checks are not a green pass. `pull_request` workflows do not run when GitHub - cannot build `refs/pull//merge`. The `PR mergeability` check uses trusted - `pull_request_target` events and refreshes unchanged PR heads after protected-base - pushes; it fails explicitly on `mergeable_state: dirty`. Behind-but-clean heads still use - `npm run sync:pr-branches` / `:apply` with a human `gh` identity — never bot - `update-branch`. -- At the first PR snapshot, collect unresolved review threads as well as check state. Triage and - repair clear actionable threads before waiting for the long CI tail, so a late bot finding does - not turn one otherwise-green run into a full second run. Reply before resolving; leave ambiguous - or product-sensitive threads open for the owner. -- Babysit dormant: observe a fresh CI run only at a meaningful stage boundary, then at most once - every five minutes for no longer than 30 minutes per run. If it remains queued or in progress at - that limit, record the run URL as deferred and continue the sweep. Prefer a terminal-event wait - over repeated log reads, never stream logs or poll minute-by-minute, and re-read the exact - head/base only when a check settles or immediately before a final audit. -- For a sweep that may need local repair, prepare one isolated, exact-lock worktree before the first - local gate with `node scripts/setup-codex-worktree.mjs`. It reuses only a complete byte-identical - install or performs the locked install once; do not create partial dependency junctions that make - local lint/typecheck appear unavailable. -- When the repository merge queue is enabled, treat queue state as read-only during a Run PR sweep. - Report active validation capacity and failed or conflicting entries, but do not configure queue - concurrency/grouping or add, remove, or re-queue entries without separate explicit user - authorization. -- When `gh pr checks` cannot read check runs with the current token, query the Actions runs for the - exact head SHA instead; do not report CI as unverifiable until that read-only fallback has also - failed. -- Treat outstanding-issue IDs as display locators, not proof that work landed. Verify completion - from distinctive content and its recorded check on the exact target ref; PR state, row ID, and row - title are insufficient, especially after squash merges or concurrent renumbering. Queue changes only - through `npm run issues:add|update|done`; each PR adds its own inbox file. Run - `npm run issues:reconcile` from one deliberately serialized ledger branch after the relevant PRs land; - it alone edits `docs/outstanding-issues.md` and allocates IDs. Never use GitHub's Update branch button - on a PR touching the canonical ledger. -- Keep Playwright blocking tests at zero retries. Quarantine only after three reproductions - on the same SHA via `tests/flake-ledger.json` (`@quarantine`, not `@critical`, ≤30-day - expiry). Do not weaken tap targets to `min-h-11` to chase generic a11y guidance — that - reintroduces a known `ui-smoke` flake. +- Assemble every commit for a head before the first push, or wait for the current PR CI run to settle before pushing again. Apply the same settle-first rule to branch syncs: for a behind-but-clean PR with required CI in flight, wait, then perform at most one late `update-branch` / `git merge origin/main` after review and fix work is assembled. Cancel-in-progress remains enabled for pull requests (pushes mid-run cancel Production UI), but is deliberately disabled for base-branch pushes (`tests/ci-cache-safety.test.ts`). +- For Run PR sweeps and normal readiness pushes — never an explicit bare PR publication — run `npm run format` **and commit the result**, then `npm run verify:pr-local` (or the smallest gate that covers the change). Format is in `static-pr` but not in `verify:cheap`; an uncommitted format leaves CI red on the pushed blob. Whole-tree Prettier, not a single edited file. +- If a PR has auto-merge armed, its auto-merge state is user-owned and automation must not disable or re-enable it. Ordinary fast-forward pushes, `update-branch`/merge-main-in syncs, and bundled additions may proceed — GitHub re-validates required checks against the new head before merging, so an additive push cannot slip past that. A force-push, history rewrite, or base/target change while armed still hard-blocks with no override; wait for the user to change that state first. +- Missing CI checks are not a green pass. The `PR mergeability` check uses trusted `pull_request_target` events and refreshes unchanged PR heads after protected-base pushes; it fails explicitly on `mergeable_state: dirty`. Behind-but-clean heads use `npm run sync:pr-branches` / `:apply` with human `gh` auth — never bot `update-branch`. +- Triage and repair actionable review threads early; reply before resolving (``). Leave ambiguous or product-sensitive threads open for the owner. +- Babysit dormant: observe fresh CI only at meaningful stage boundaries (at most once every 5 min, ≤30 min per run). If queued/running at limit, record run URL as deferred and continue sweep. +- For sweeps needing local repair, prepare one isolated, exact-lock worktree via `node scripts/setup-codex-worktree.mjs`. +- Treat merge queue state as read-only. Fall back to Actions runs for exact head SHA if `gh pr checks` cannot read check runs. +- Treat outstanding-issue IDs as display locators, not proof that work landed. Queue changes only through `npm run issues:add|update|done`; reconcile via `npm run issues:reconcile` from a dedicated branch after PRs land. +- Keep Playwright blocking tests at zero retries; quarantine via `tests/flake-ledger.json` only after three reproductions on the same SHA. ### Operator sync (explicit only) -- Leave active PRs alone unless the user asks. Report-only inventory: - `npm run sync:pr-branches`. Apply only with confirmation and human/operator `gh` auth: - `npm run sync:pr-branches:apply`. +- Leave active PRs alone unless requested. Report: `npm run sync:pr-branches`. Apply with confirmation and human/operator auth: `npm run sync:pr-branches:apply`. @@ -1173,41 +1031,13 @@ Run the matching planner command in `docs/productivity-workflows.md` without sid ## Outstanding-work memory (`/issues`) -`docs/outstanding-issues.md` is the single universal, durable, cross-session ledger for every -outstanding **task**, **recommendation**, and **issue** in this repo. It owns evidence, resolution -history, recommended order, acuity, capability, timing, effort, approvals, verification, and stop -rules. Chat context resets; this file does not, so anything worth remembering belongs there. -Detailed runbooks such as `docs/operator-backlog.md` may support a task but must not become a second -status ledger. Update the universal ledger when work completes, is dropped, becomes stale, or is -materially re-scoped. Never restore completed, duplicate, speculative, superseded, or rejected work -to the recommended queue. - -- When the user types `/issues`, invoke the `issues` skill (`.claude/skills/issues/SKILL.md`): run - `npm run issues:report -- --json` to read the cached `origin/main` ledger with an explicit stale-state warning; - refresh that ref first only with provider authorization. State the recommended queue in order, then summarize other open - items by priority. A plain `/issues` is read-only — it mutates and commits nothing. -- `/issues add|done|update|capture …` queue immutable request files under - `docs/outstanding-issues-inbox/`; ordinary branches never edit the canonical ledger. Commit a - request only when explicitly asked, and never push unless requested or already handing off. - One deliberately serialized fresh-base branch later runs `npm run issues:reconcile`, which alone - edits the canonical ledger. That transaction is the whole deliverable: the legacy `ISSUES-LIST.html` - visual register was retired by `#338` on 2026-08-18, so there is no second artifact to refresh. -- Proactively offer to `capture` unresolved follow-ups, deferrals, and known risks into the ledger - before a session's context is lost — that is what keeps it a memory rather than a stale list. -- **An open row is not evidence that nobody is building it.** Some rows do carry a progress marker in - their prose (`IN PROGRESS`, `IMPLEMENTED in PR #1766`), but there is no structured status field and - no atomic claim: a marker is written by whoever did the work, usually after the fact, and nothing - requires or checks one. Absence of a marker therefore means nothing. - So before _acting_ on a queued item (not before reading the list back), check the open PRs for the - route, component, or surface it touches — a duplicate PR will rarely quote the ledger id. Skipping - this shipped the same conversion twice on 2026-08-09: PR #1766, and PR #1767 closed as a duplicate, - four hours apart, leaving two divergent shapes to adjudicate. It is one GitHub read and falls under - the provider-confirmation boundary above; if GitHub is unreachable, warn and continue rather than - blocking an offline session. Tracked as `#292`. -- A `SessionStart` hook (`.claude/hooks/issues-surface.sh`, wired in `.claude/settings.json`) - auto-surfaces the recommended queue plus open-item counts at the start of every session and, on a - context reset (`compact`/`resume`/`clear`), nudges a `/issues capture`. It is read-only — it never - writes the ledger. `/issues` is still the way to read the full list or mutate it. +`docs/outstanding-issues.md` is the universal durable cross-session ledger for tasks, recommendations, and issues. Update it when work completes, is dropped, or is materially re-scoped. Never restore completed, duplicate, speculative, or rejected work to the recommended queue. + +- When the user types `/issues`, invoke the `issues` skill (`.claude/skills/issues/SKILL.md`): run `npm run issues:report -- --json` to read the cached `origin/main` ledger (read-only; mutates and commits nothing). +- `/issues add|done|update|queue …` queue immutable request files under `docs/outstanding-issues-inbox/`. Ordinary branches never edit the canonical ledger. One deliberately serialized fresh-base branch runs `npm run issues:reconcile` after PRs land. +- Proactively offer to capture unresolved follow-ups, deferrals, and known risks into the ledger before session context is lost. +- Before acting on a queued item, check open PRs for overlapping routes or components to avoid duplicate concurrent work (`#292`). +- The `SessionStart` hook (`.claude/hooks/issues-surface.sh`, wired in `.claude/settings.json`) auto-surfaces the recommended queue plus open-item counts at session start (read-only). ## Codex GitHub review behavior @@ -1303,7 +1133,6 @@ Automatic Codex review is review-only by default. This repository includes `.git - The workflow may request one automatic repair pass per pull request lifetime. Later heads require an explicit human request. - Only trust a pull-request deduplication marker when it was posted by the trigger-token account (the same identity that posts the request), resolved at runtime rather than hard-coded. - Permission failures while reading or creating pull-request comments must fail the workflow visibly, not return a successful soft-skip. -- Grant `pull-requests: write` only to the narrow marker-driven thread-resolution job; the request job runs with read-only repository contents and relies on the trigger token's own scope, and neither job approves reviews or alters code. - The workflow must not run Codex directly with API credentials. - P0 and P1 findings should always be fixed. - P2 and lower findings should be fixed only when clear, scoped, low-risk, and testable; otherwise explain the decision and resolve or mark ready for human resolution. @@ -1314,119 +1143,22 @@ Automatic Codex review is review-only by default. This repository includes `.git ## Codex Cloud environment -Codex Cloud uses an isolated Linux container and does not inherit desktop files, -credentials, OAuth sessions, MCP authentication, local services, or uncommitted work. -Use `docs/codex-cloud.md` as the environment contract: +Codex Cloud uses an isolated Linux container and does not inherit desktop credentials, local services, or uncommitted work. Full environment specification and runbooks live in `docs/codex-cloud.md`. - Configure setup as `bash scripts/setup-codex-cloud.sh && bash scripts/install-codex-cloud-command-shims.sh`. - Configure maintenance as `bash scripts/maintain-codex-cloud.sh && bash scripts/install-codex-cloud-command-shims.sh`. -- Default to `CODEX_CLOUD_ACCESS_PROFILE=offline` for ordinary and protected RAG work. - Use `connected` only when the user explicitly authorizes the required provider access. -- When MCP tools are already callable in a Cloud session and the task needs them, use the host - plugin/connector inventory. The production Supabase target is limited to prompted, read-only - `docs` and `development` metadata tools; do not enable database, SQL, row, or log tools. - Write-capable Figma, Railway, and Sentry tools still require explicit confirmation. Paid API - canaries (`eval:rag`, `eval:retrieval:quality`, `eval:quality`, `verify:release`, - `test:live`, `check:supabase-project`) still need explicit confirmation. Project - `.codex/config.toml` keeps Desktop/CLI MCP entries `enabled = false` in git (`check:codex-cloud` - fails if any tracked entry is enabled). Opt in locally via `$CODEX_HOME/config.toml` (preferred) - or a never-committed project-file edit, then `codex mcp login railway`. Cloud setup never writes - Railway or Supabase MCP registrations to `$CODEX_HOME`. Hosted ChatGPT/Codex requires an - installed, workspace-authorized, OAuth-authenticated app, and a fresh task must prove the callable - inventory with read-only identity calls. Root `.mcp.json` is a static cross-client template, not - hosted runtime proof. -- Cloud has no Windows task-start script. Report that exact fact, then perform equivalent - read-only identity, branch, status, worktree, and Git-operation checks. Proceed only in a - clean disposable checkout on a task-specific non-protected branch. -- Cloud mirrors the tracked repository toolchain, not Windows files, `.env.local`, - desktop plugins, browser sessions, OAuth sessions, user-global skills, or uncommitted - work. Keep required workflows in tracked instructions, scripts, tests, and repo-local - skills. -- Repository setup cannot grant GitHub installation permissions, workspace RBAC, network - policy, or provider credentials. Treat those as product/account settings and verify them - separately without printing secret values. -- In a fresh Cloud task, run `bash scripts/check-codex-cloud-raw-env.sh` before sourcing a - profile or entering a login shell. It must report only provider variable names and presence, - never values. Treat exit `1` / `FAIL`+`STOP` as a hard stop for any unexpected inherited name. - Only exit `2` / `FAIL-KNOWN` for `OPENAI_BASE_URL` alone may use the restricted - profile-and-shim continue path; do not generalize that allowance. Exit `2` is still a failed raw - boundary — future automation must not treat non-1 as success or as a blind retry. That name can - redirect OpenAI-bound traffic, so never invoke OpenAI clients from the raw parent or any binary - that bypasses the profile/`node`/`npm`/`npx` scrub. Then run - `npm run check:codex-cloud` directly; it must report the static-and-environment PASS line. Run - `npm run check:codex-cloud -- --runtime` with `CODEX_CLOUD_EXPECTED_BASE_SHA` set to the - intended merge/base commit when the checkout has only a task HEAD. Setup and maintenance may - report freshness as unverified so provisioning remains repairable, but explicit acceptance must - not pass an arbitrary HEAD. The command shims load the generated profile for normal `node`, - `npm`, and `npx` work. Also run `npm run check:runtime` and - `npm run check:installed-lock-parity` before trusting a new or reset environment. A skipped - browser install is not full browser readiness. Output is limited to approved mode values, - presence booleans, full Git commit identities, and MCP server/command/environment-variable - names; never print credential values. -- Do not add OpenAI, Supabase, Railway, GitHub, database, or user credentials as ordinary - Cloud environment variables. Codex Cloud secrets are setup-only and unavailable to the - agent phase unless the platform explicitly exposes a secret to the named task phase; do not - copy them into files to bypass that boundary. -- Provider-backed checks, hosted CI mutations, deployment, production data access, and - Git publishing still require the explicit authorization defined above. -- The ordinary offline Cloud profile intentionally cannot perform authenticated production or - live-provider checks. `check:production-readiness` reports this as a provider capability gap. -- Authenticated live tests run through the manual - `.github/workflows/authenticated-live-tests.yml` GitHub Actions workflow, its explicit - dispatch confirmation, and the `Database / production` environment, never by exposing - credentials to the Codex Cloud agent shell. -- The active hosted workspace is **Personal Pro**. Use Railway's installed official ChatGPT app - with browser OAuth and **Allow read actions**; Personal Pro does not provide the dedicated-group - RBAC or per-tool action disabling assumed by Enterprise/Edu instructions. Prove Railway with the - callable tool inventory and a read-only identity/project-list call. Repository setup and local - MCP config cannot activate it, and the Codex Cloud connector page currently offers no Railway - connector. Use the documented split control plane: Codex Cloud for code and its native GitHub - connector, ChatGPT web for Railway and project-scoped read-only Supabase. Every provider change - still requires explicit approval. Enterprise/Edu custom-app controls are a future governance - option, not the current workspace classification. - CLI token auth is a separate operator capability: it requires a separately installed Railway CLI - and a dedicated - `RAILWAY_API_TOKEN`, and must never substitute `RAILWAY_TOKEN` or expose either token to an - ordinary agent shell. GitHub connector access, GitHub CLI authentication, the credential-free - `origin` URL, and shell Git authentication are separate capabilities. -- For an explicitly authorised GitHub task, use the authenticated GitHub connector/MCP - tools as the default remote control plane. Use them for repository, PR, issue, review - thread, and Actions work, including inline-thread replies/resolution, Actions - run/job/log/artifact inspection, and approved branch, file, or PR mutations. Missing - `gh`, shell GitHub credentials, or direct shell network access is not a loss of this - capability. The intended connection is `BigSimmo` with repository write access. - Reserve administrator access for separately approved operations. -- In Codex Cloud, use native Push, the authenticated GitHub connector, or GitHub's UI for branch - publication and cleanup. `CODEX_CLOUD_GITHUB_PAT` is excluded from every Cloud agent shell. - The helper `bash scripts/delete-codex-cloud-branch-with-pat.sh ` is - operator-only outside Codex Cloud; it must reject `CODEX_CLOUD=1`, validate the exact - non-protected ref and credential-free origin, and never print the token. If the native or - connector path is unavailable, report the platform limit rather than copying a PAT into a - profile, remote URL, cached file, or agent environment. -- Confirm the exact repository and PR/thread/job before a write, and verify the connector - result before treating the write as successful. A repository cannot sanitize a variable - already inherited by the top-level task process; the tracked shims protect normal - `node`/`npm`/`npx` commands. Report raw-parent exposure as a Codex Cloud launcher defect - rather than weakening the provider-variable contract. -- Cloud browser proof is Playwright/Chromium, Firefox, or WebKit container evidence, not - physical iPhone Safari/PWA acceptance. +- Default to `CODEX_CLOUD_ACCESS_PROFILE=offline` for ordinary/RAG work; use `connected` only with explicit provider authorization. +- Personal Pro split control plane: Codex Cloud for code and GitHub connector; ChatGPT web for Railway and read-only Supabase metadata. +- Acceptance: run `bash scripts/check-codex-cloud-raw-env.sh`, `npm run check:codex-cloud`, and `npm run check:codex-cloud -- --runtime` (with `CODEX_CLOUD_EXPECTED_BASE_SHA`). +- Do not expose provider secrets (OpenAI, Supabase, Railway, GitHub PATs) in Cloud agent shells or committed config. +- Authenticated live tests run via `.github/workflows/authenticated-live-tests.yml` with manual dispatch, never from Cloud agent shells. +- Branch deletion helper `bash scripts/delete-codex-cloud-branch-with-pat.sh` is operator-only outside Cloud. ## Cursor Cloud specific instructions (not Codex Cloud) -Durable notes for Cloud Agents. Standard commands live in `README.md` and `package.json`; only non-obvious caveats are captured here. - -- Context7 peer-library docs habit (and the Next 16 local-docs carve-out) lives in `docs/agents-guide.md`. Project MCP is local `@upstash/context7-mcp@3.2.5` with `CONTEXT7_API_KEY` from env/Secrets. If the host-injected Context7 MCP returns quota exceeded, use `npx ctx7 library|docs …` with the same secret — do not invent peer APIs from training data. -- Runtime: the app hard-requires Node >=24.15.0 <25 / npm 11.x (`engine-strict`; the preinstall and runtime gates enforce the minor floor, while `scripts/dev-free-port.mjs` rejects other majors). A compatible Node 24 is installed via nvm and symlinked into `/usr/local/cargo/bin` (first entry in `PATH`) so `node`/`npm` resolve to it in every shell. If a shell ever resolves `/exec-daemon/node` (v22) instead, prepend the installed nvm Node 24 bin to `PATH` (for example `"$HOME/.nvm/versions/node/v24.18.1/bin"`; run `ls "$HOME/.nvm/versions/node"` to confirm the exact patch version). -- Live vs demo mode: the app auto-detects. When the Supabase + OpenAI env vars below are present (set them as Cloud Agent **Secrets** so they inject into `.env.local`/`process.env`), `isDemoMode()` (`src/lib/env.ts`) is false and the app runs against the live `Clinical KB Database` project (~2000 indexed docs) with OpenAI answer generation. When they are absent, dev auto-falls back to demo mode using the synthetic corpus in `src/lib/demo-data.ts` / `public/demo-documents/`. Required for live mode: `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_PROJECT_REF`, `SUPABASE_PROJECT_NAME`, `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` (`sb_publishable_…`), `SUPABASE_SERVICE_ROLE_KEY` (accepts the `sb_secret_…` secret key), `OPENAI_API_KEY`. Keep `RAG_PROVIDER_MODE=auto` so OpenAI is used with graceful source-only fallback. `E2E_USER_EMAIL`/`E2E_USER_PASSWORD` power CI env-check and Playwright. -- Live-mode caveat: `RAG_PROVIDER_MODE=auto` attempts OpenAI (fast → strong route); if generation fails the built-in quality gates it silently degrades to a deterministic "Source-only" answer that still cites real documents — this is expected, not a failure. The header sign-in UI exposes magic-link + OAuth only (no password field), but the `/api/answer` + retrieval flow works server-side without a browser session. -- What still won't run in this VM even with secrets: `npm run worker` also needs the Python OCR stack (`worker/python/requirements.txt`) and heavy parsing deps; Supabase edge functions need Deno v2.x + deployment. `verify:release` additionally runs governance/eval gates. Treat missing-secret failures of `check:supabase-project`/`verify:release` in demo mode as expected, not regressions. -- Dev server: `npm run dev` selects a stable per-project localhost port (e.g. `4461`), binds `0.0.0.0`, and prints the exact URL. Never assume port 3000/3001/3002. `npm run ensure` starts/verifies it in the background. -- Verification without secrets: `npm run lint`, `npm run typecheck`, and `npm run test` (vitest) all pass offline. `npm run verify:cheap` also runs runtime, GitHub Actions pin, CI-scope, and sitemap checks. `npm run verify:pr-local` adds format, conditional build/client-bundle scanning, and RAG fixture/manifest validation without repeating unit tests; browser, Docker/Supabase, audit, and provider checks remain separate. See `docs/testing.md` for lock, live-test, Playwright, and flake-ledger rules. If `check:installed-lock-parity` or `check:playwright-browser-revision` reports Playwright/image drift (`#255`), do not force a mismatched Chromium path — delegate browser proof to CI Production UI (see `docs/testing.md` § Testing speed playbook). -- For GitHub-related work authorised in this session, prefer the connected GitHub - connector/MCP tools first for PR, issue, comment, review-thread, and Actions tasks they - support (including run/job/log/artifact inspection and review-thread replies/resolution). - A missing `gh` CLI is not a blocker for connector-supported work; never add a PAT as a - workaround. The intended connection is `BigSimmo` with repository write access. - Reserve administrator access for separately approved operations. Verify the exact target and connector result before any write. Ordinary - authorised shell `git` branch publication remains allowed; use shell `gh` only for a - genuine connector gap and only when the task permits it. +Durable notes for Cursor Cloud agents (see `docs/agents-guide.md` and `docs/testing.md` for full reference): + +- Context7 peer-library docs habit and Next 16 local docs live in `docs/agents-guide.md`. +- Requires Node >=24.15.0 <25 / npm 11.x (installed via nvm, symlinked to `/usr/local/cargo/bin`). +- Offline verification: `npm run lint`, `npm run typecheck`, `npm run test`, `npm run verify:cheap`, and `npm run verify:pr-local` all pass without secrets. +- For authorized GitHub work, use the connected GitHub connector/MCP tools as primary interface (`BigSimmo` write access). diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json index 0e7ba7fed8..a7dbe78770 100644 --- a/data/repo-awareness-snapshot.json +++ b/data/repo-awareness-snapshot.json @@ -1,8 +1,8 @@ { "version": "repo-awareness-snapshot-v1", "captured_revision": { - "sha": "d9a2e0619cd30539b5dc4a433da0d97cc8f618ed", - "committed_at": "2026-08-27T10:05:30+08:00" + "sha": "de2b9b35bb84c96ced4a6eb09e6d6c99bc2de0bc", + "committed_at": "2026-08-27T10:18:22+08:00" }, "routes": { "modes": [ @@ -3813,6 +3813,14 @@ "outcome": "FIXED", "checks": "merge-main, review-replies, thread-resolve, snapshot-regen" }, + { + "date": "2026-08-27", + "ref": "codex/tooling-hooks-ci-invariants", + "head": "dba86f029eed2c6d8a4c6c7594840c60d5d004a2", + "scope": "PR #2397 babysit: merge main, conflict resolution, review thread fixes", + "outcome": "mergeable; snapshot regenerated; 5 premature inbox done requests removed; dev-drive-cache fails closed; Medication pin /medications", + "checks": "vitest session-start-hook+sidebar-production 18/18; check:dev-drive-cache; check:outstanding-issues-snapshot" + }, { "date": "2026-08-26", "ref": "claude/dev-hub-handoff-accuracy (PR #2382)", @@ -24839,8 +24847,8 @@ } ], "counts": { - "records": 2629, - "refs": 1606 + "records": 2630, + "refs": 1607 } } } diff --git a/docs/agents-guide.md b/docs/agents-guide.md index 1b5d1fd8d0..0f9bbdb616 100644 --- a/docs/agents-guide.md +++ b/docs/agents-guide.md @@ -102,7 +102,15 @@ To prevent dual competing responders from answering the same PR review comment ( 1. **Authoritative responder**: The repository GitHub Action (`.github/workflows/codex-autofix-review-comments.yml`) is the primary automated resolver for Codex PR review comments. It includes explicit governance safeguards: - Trusted-bot login gating (`chatgpt-codex-connector[bot]`). - Per-PR deduplication marker (``). - - Three-cycle head-SHA cap per PR lifetime to prevent runaway repair loops. - - Respect for `skip-codex-review` labels. -2. **App-level watcher throttling**: Interactive desktop/client app watchers ("Autofix pull requests") must be disabled or stand down on pull requests where repository workflows run. Do not instruct an interactive agent session to concurrently fix a review comment that is already queued or being addressed by the repository workflow. -3. **Deduplication markers**: Automated fixers must inspect review threads for existing disposition markers (``) and active commit history before initiating new edits or pushing duplicate commits. + - Single automatic repair pass per PR lifetime to prevent runaway repair loops. + - Respect for `skip-codex-review` labels and explicit opt-ins via `codex-review`. + - Hard hold: clinical-decision surfaces (`data/**`, `src/data/**`, `src/lib/mha-act-sections.ts`, `src/lib/form-catalog.ts`, `src/lib/form-ranker.ts`, `src/components/forms/**`, `src/lib/rag/**`, and named ranking surfaces) are never automatically repaired. +2. **Bot ownership boundaries and watcher throttling**: + - **Repository Codex auto-fixer**: Owns unattended repair of actionable Codex review comments on open PRs passing risk routing. + - **App-level / Client watchers**: Interactive desktop or client app watchers ("Autofix pull requests") must stand down and not compete on repository pull requests. Do not instruct an interactive agent session to concurrently fix a review comment that is already queued or being addressed by the repository workflow. + - **CodeRabbit**: Advisory only (`commit_status: false`), intermittent/capped, skipped on draft PRs. Never generates fix commits or competes for PR mutation. + - **Interactive human / agent sessions**: When asked to fix comments or running a `Run PR` sweep, always check if an auto-fixer has already replied or pushed fixes (``). Never create competing commits on the same review finding. +3. **Review comment lifecycle and disposition markers**: + - For every fixed or fully dispositioned thread, start the thread reply with ``. + - On the next line, include `` for code fixes or `` for no-code dispositions. + - Threads requiring human judgment, architectural decisions, or touching clinical holds must be left open with an explanatory reply instead of using the resolved marker. diff --git a/docs/branch-review-records/3d772ae226f7524d949fe2a22a1d5f62ebac498b6cdcf20a53f1f44bc4e08659.record.md b/docs/branch-review-records/3d772ae226f7524d949fe2a22a1d5f62ebac498b6cdcf20a53f1f44bc4e08659.record.md new file mode 100644 index 0000000000..1df9e25272 --- /dev/null +++ b/docs/branch-review-records/3d772ae226f7524d949fe2a22a1d5f62ebac498b6cdcf20a53f1f44bc4e08659.record.md @@ -0,0 +1 @@ +| 2026-08-27 | codex/tooling-hooks-ci-invariants | dba86f029eed2c6d8a4c6c7594840c60d5d004a2 | PR #2397 babysit: merge main, conflict resolution, review thread fixes | mergeable; snapshot regenerated; 5 premature inbox done requests removed; dev-drive-cache fails closed; Medication pin /medications | vitest session-start-hook+sidebar-production 18/18; check:dev-drive-cache; check:outstanding-issues-snapshot | diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 53894bdfc6..01f4ab8420 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -1,6 +1,6 @@ # Scripts index -Curated map of `scripts/` (275 files) and the `package.json` script surface (279 entries), +Curated map of `scripts/` (276 files) and the `package.json` script surface (280 entries), grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run ` referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above. diff --git a/docs/testing.md b/docs/testing.md index 8e572d75ab..e0dec2f9dd 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -14,21 +14,33 @@ Ordinary Vitest and Playwright runs remove OpenAI, Supabase, database, and E2E c **Provider-backed boundary:** `test:live`, `eval:quality`, `eval:retrieval:quality`, `verify:release`, `check:supabase-project`, and other OpenAI/Supabase/hosted workflows need **explicit user approval** before agents run them (see root `AGENTS.md`). Prefer offline gates (`verify:cheap`, `verify:pr-local`, `eval:rag:offline`) unless that approval is in the task. -### Windows process-spawn diagnostic +### Windows process-spawn diagnostic (#VV83VA) -Before investigating a slow `git push`, `gh`, or pre-push guard on a Windows workstation, measure an unrelated local process spawn. In PowerShell: +Before investigating a slow `git push`, `gh`, or pre-push guard on a Windows workstation, measure an unrelated local process spawn. + +In PowerShell: ```powershell Measure-Command { node --version } +# or evaluating inline execution: +Measure-Command { node -e "console.log(process.version)" } ``` From `cmd.exe`, invoke the same measurement without relying on shell aliases: ```cmd powershell -NoProfile -Command "Measure-Command { node --version }" +powershell -NoProfile -Command "Measure-Command { node -e 'console.log(process.version)' }" +``` + +In Bash / WSL: + +```bash +time node --version +time node -e "console.log(process.version)" ``` -Subsecond completion is healthy; if this simple command takes multiple seconds, treat it as host process-spawn starvation rather than a repository or GitHub CLI fault. Close stale Codex and terminal sessions, then retry; reboot the workstation if the condition persists. Do not change Windows Defender, add security exclusions, or otherwise alter Windows security settings as part of this diagnosis. +Subsecond completion (<0.2s) is healthy; if this simple command takes multiple seconds (measured up to 17s on process-starved hosts vs 0.08s after reboot), treat it as host process-spawn starvation rather than a repository or GitHub CLI fault. Close stale Codex, Node, and terminal sessions, then retry; reboot the workstation if the condition persists. Do not change Windows Defender, add security exclusions, or otherwise alter Windows security settings as part of this diagnosis. ## Risk-based selection diff --git a/package.json b/package.json index fd5b809ba7..cfde824c56 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "check:gitleaks-pinned": "node scripts/run-gitleaks-pinned.mjs --self-test", "check:ci-triage": "node scripts/ci-triage.mjs --self-test", "check:gate-manifest": "node scripts/check-gate-manifest.mjs", + "check:dev-drive-cache": "node scripts/check-dev-drive-cache.mjs --self-test && node scripts/check-dev-drive-cache.mjs", "check:branch-review-ledger": "node scripts/check-branch-review-ledger.mjs --self-test && node scripts/branch-review-ledger.mjs --self-test && node scripts/check-branch-review-ledger.mjs", "check:outstanding-issues": "node scripts/check-outstanding-issues.mjs --self-test && node scripts/outstanding-issues.mjs --self-test && node scripts/ledger-inbox.mjs --self-test && node scripts/ledger-inbox.mjs check && node scripts/check-outstanding-issues.mjs && npm run check:outstanding-issues-snapshot", "snapshot:issues": "node scripts/generate-outstanding-issues-snapshot.mjs", diff --git a/scripts/check-dev-drive-cache.mjs b/scripts/check-dev-drive-cache.mjs new file mode 100644 index 0000000000..7d54be0e93 --- /dev/null +++ b/scripts/check-dev-drive-cache.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node +/** + * check-dev-drive-cache.mjs — Verify npm package cache registration in Windows Dev Drive trusted cache (#6SMMB4). + * + * On Windows workstations hosting worktrees on a Dev Drive (e.g. D:, ReFS), + * verifies whether `npm config get cache` resolves to a path on a Dev Drive volume + * and whether that cache directory is trusted by Microsoft Defender. + */ +import { execFileSync, spawnSync } from "node:child_process"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +export function resolveNpmCache(exec = execFileSync) { + try { + const isWin = process.platform === "win32"; + const stdout = isWin + ? exec("cmd.exe", ["/c", "npm", "config", "get", "cache"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }) + : exec("npm", ["config", "get", "cache"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }); + return stdout.trim(); + } catch { + return process.env.npm_config_cache || ""; + } +} + +export function inspectDevDriveTrust(cachePath, { platform = process.platform, exec = spawnSync } = {}) { + if (platform !== "win32") { + return { + status: "skipped", + reason: "Dev Drive trust verification is specific to Windows workstations.", + cachePath, + }; + } + + if (!cachePath) { + return { + status: "warning", + reason: "Could not resolve npm cache directory path.", + cachePath: "", + }; + } + + const drive = path.parse(path.resolve(cachePath)).root.replace(/[\/\\]$/, ""); + const queryResult = exec("fsutil", ["devdrv", "query", drive], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); + + const output = `${queryResult.stdout ?? ""}\n${queryResult.stderr ?? ""}`.trim(); + + if (queryResult.status !== 0) { + if (output.includes("Error 5") || output.includes("Access is denied")) { + return { + status: "info", + reason: `fsutil devdrv query requires elevation (Error 5). From an elevated administrator prompt run: fsutil devdrv trust "${cachePath}"`, + cachePath, + drive, + elevated: false, + }; + } + return { + status: "warning", + reason: `Could not query Dev Drive status for volume ${drive}: ${output || "unknown error"}`, + cachePath, + drive, + }; + } + + const isTrusted = /trusted/i.test(output) && !/not trusted/i.test(output); + return { + status: isTrusted ? "ok" : "untrusted", + reason: isTrusted + ? `Dev Drive on ${drive} is trusted.` + : `Dev Drive on ${drive} is not registered as trusted. Run elevated: fsutil devdrv trust "${cachePath}"`, + cachePath, + drive, + elevated: true, + rawOutput: output, + }; +} + +export function exitCodeForDevDriveResult(result) { + if (result.status === "ok") return 0; + if (result.status === "skipped" || result.status === "info") return 0; + return 1; +} + +export function runCheck({ log = console.log, warn = console.warn, inspect = inspectDevDriveTrust } = {}) { + const cachePath = resolveNpmCache(); + const result = inspect(cachePath); + + if (result.status === "ok") { + log(`[devdrv-cache] OK: npm cache (${result.cachePath}) on ${result.drive} is registered in trusted Dev Drive.`); + return 0; + } + if (result.status === "skipped" || result.status === "info") { + log(`[devdrv-cache] ${result.reason}`); + return 0; + } + warn(`[devdrv-cache] ${result.status === "untrusted" ? "FAIL" : "WARN"}: ${result.reason}`); + return exitCodeForDevDriveResult(result); +} + +function selfTest() { + const linuxResult = inspectDevDriveTrust("/home/user/.npm", { platform: "linux" }); + if (linuxResult.status !== "skipped") throw new Error("Linux platform should be skipped"); + + const deniedExec = () => ({ status: 1, stdout: "", stderr: "Failed to open the volume. Error 5: Access is denied." }); + const deniedResult = inspectDevDriveTrust("D:\\.npm-cache", { platform: "win32", exec: deniedExec }); + if (deniedResult.status !== "info" || deniedResult.elevated !== false) { + throw new Error("Access denied should return info status with non-elevated flag"); + } + + const trustedExec = () => ({ + status: 0, + stdout: "This is a Developer Volume (Dev Drive). Volume is trusted.", + stderr: "", + }); + const trustedResult = inspectDevDriveTrust("D:\\.npm-cache", { platform: "win32", exec: trustedExec }); + if (trustedResult.status !== "ok" || trustedResult.elevated !== true) { + throw new Error("Trusted output should return ok status"); + } + + const untrustedExec = () => ({ + status: 0, + stdout: "This is a Developer Volume (Dev Drive). Volume is not trusted.", + stderr: "", + }); + const untrustedResult = inspectDevDriveTrust("D:\\.npm-cache", { platform: "win32", exec: untrustedExec }); + if (untrustedResult.status !== "untrusted") { + throw new Error("Untrusted output should return untrusted status"); + } + if (exitCodeForDevDriveResult(untrustedResult) !== 1) { + throw new Error("Untrusted result should map to exit code 1"); + } + if (exitCodeForDevDriveResult({ status: "warning", reason: "missing cache" }) !== 1) { + throw new Error("Warning result should map to exit code 1"); + } + + console.log("[devdrv-cache] self-test passed."); + return 0; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + if (process.argv.includes("--self-test")) { + process.exit(selfTest()); + } + process.exit(runCheck()); +} diff --git a/tests/guard-push.test.ts b/tests/guard-push.test.ts index c55621532b..49990e553c 100644 --- a/tests/guard-push.test.ts +++ b/tests/guard-push.test.ts @@ -125,7 +125,7 @@ describe("auto-merge verdict", () => { }); }); -describe("force-push detection", () => { +describe("force-push detection", { timeout: 60_000 }, () => { it("does not flag a fast-forward push", () => { const { root, git } = gitFixture(); writeFileSync(join(root, "one.md"), "one\n"); @@ -195,7 +195,7 @@ describe("drift verdict", () => { }); }); -describe("push-range parsing", () => { +describe("push-range parsing", { timeout: 60_000 }, () => { it("parses a new-branch push (zero remote sha)", () => { const ranges = parsePushRanges(`refs/heads/x abc123 refs/heads/x ${ZERO}\n`); expect(ranges).toHaveLength(1); diff --git a/tests/hosted-migration-role-guard.test.ts b/tests/hosted-migration-role-guard.test.ts index 3a6fc9b2a9..bed49122bc 100644 --- a/tests/hosted-migration-role-guard.test.ts +++ b/tests/hosted-migration-role-guard.test.ts @@ -71,7 +71,7 @@ function syntheticRepository({ }; } -describe("hosted migration-role guard", () => { +describe("hosted migration-role guard", { timeout: 60_000 }, () => { it("accepts the current repository state", () => { const result = inspectMigrationRoleRepository(); diff --git a/tests/session-start-hook.test.ts b/tests/session-start-hook.test.ts index b4fd543700..89811dd940 100644 --- a/tests/session-start-hook.test.ts +++ b/tests/session-start-hook.test.ts @@ -251,6 +251,20 @@ describe("precompact observability hook", () => { ); } }); + + it("survives malformed payloads and missing git directory without error", () => { + const emptyDir = mkdtempSync(join(tmpdir(), "precompact-empty-")); + scratchRoots.push(emptyDir); + + const result = spawnSync(bashCommand, [sourcePrecompactHook.replace(/\\/g, "/")], { + cwd: emptyDir, + encoding: "utf8", + input: "not json at all { malformed [", + }); + expect(result.status, `hook exited ${result.status}: ${result.stderr}`).toBe(0); + expect(result.stdout).toBe(""); + expect(result.stderr).toBe(""); + }); }); /**