Skip to content

release: 2026-03-06 #2#1792

Merged
OAGr merged 58 commits intoproductionfrom
main
Mar 6, 2026
Merged

release: 2026-03-06 #2#1792
OAGr merged 58 commits intoproductionfrom
main

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Mar 6, 2026

Release 2026-03-06

54 commits since last release.

Warning

Production has 1 commits not on main (hotfixes or merge commits).
Review carefully to ensure these won't be overwritten.

Features

Fixes

Refactoring

Documentation

Infrastructure

Other


Full diff

OAGr and others added 30 commits March 4, 2026 18:54
Adds a CI guard that fails immediately if a PR targets production
from any branch other than main. Combined with the branch protection
rule (requiring CI to pass) this prevents the production/main drift
that required manual reconciliation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…b-shadowban-check (#1706)

Root causes:
- snapshot-retention: used WIKI_SERVER_API_KEY (project-scoped) to call
  DELETE endpoints that require `content` scope — resulting in 403 errors.
  Fixed by using LONGTERMWIKI_CONTENT_KEY (content-scoped) with fallback to
  LONGTERMWIKI_SERVER_API_KEY (legacy superkey that grants all scopes).

- github-shadowban-check: hardcoded "quri-bot" which does not exist on GitHub.
  Every run returned 404 → interpreted as "banned" → success: false.
  Fixed by defaulting to empty usernames and reading from
  TASK_GITHUB_SHADOWBAN_CHECK_USERNAMES env var (comma-separated).

Closes #1616

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#1703)

The integrity check in fetchIntegritySummary() was counting edit_logs
and citation_quotes with page_id_int IS NULL as dangling references.
But records from before the Phase D / Phase 4a migration have
page_id_old (text) populated and page_id_int NULL — these are
migration artifacts, not data corruption.

Fix: only flag records where BOTH page_id_old AND page_id_int are NULL
(truly orphaned). This eliminates ~54 false positives from the System
Health dashboard (E927), restoring trust in its integrity counts.

Closes #1615

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1708)

The citation dot overlays were completely dark in production because
buildCitationQuotesBundle() still read from the old citationQuotes table
while all new data is written to statement_citations/statementPageReferences.

- Add GET /api/statements/citation-dots/all endpoint that joins
  statementPageReferences → statements → statementCitations → resources
  and returns data grouped by page slug, mapping statement verdicts to
  AccuracyVerdict values (verified→accurate, disputed→inaccurate, etc.)
- Add buildStatementCitationDots() in build-data.mjs that fetches from
  the new endpoint and stores results as database.statementCitationDots
- Add statementCitationDots field to DatabaseShape and getStatementCitationDots()
  getter in data/index.ts
- Add getStatementCitationQuotes(pageId, referenceMap) in citation-data.ts
  that bridges footnoteResourceId strings to numeric footnote numbers via
  the referenceMap produced by renderMdxPage()
- Merge statement quotes with legacy quotes in page.tsx; statement data
  takes precedence for any footnote it covers, legacy fills the rest

Closes #1598

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntion (#1711)

Adds both a _ignoreCommandNote JSON key and an inline bash comment to
explain that Vercel's ignoreCommand uses inverted exit codes
(exit 1 = BUILD, exit 0 = SKIP), which is the opposite of Unix convention.
This was gotten wrong 4 times across PRs #1586, #1611, #1630.

Closes #1659

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#1697)

- Add `.finite()` to all numeric fields in CreateStatementBody,
  PatchStatementBody, BatchScoreBody, CoverageScoreBody, and
  QualityDimensionsSchema — rejects Infinity/-Infinity (NaN was
  already rejected by Zod's number type)
- Add `.min(1)` to all optional nullish string fields in
  CreateStatementBody and PatchStatementBody — rejects empty strings
  (`""`) that would store junk data in the DB
- Add `.min(1)` to ListQuery entityId/propertyId filter params to
  prevent empty-string filter queries
- Export CreateStatementBody and PatchStatementBody for unit testing
- Add 40 new tests in statements-boundary-validation.test.ts covering
  all three failure modes from issue #1647

Root cause fix for the 5-PR junk-data chain (#1606, #1607, #1608,
#1620, #1623): Infinity values and empty strings could previously be
stored in the statements table because the Zod schemas lacked
`.finite()` and `.min(1)` constraints on optional fields.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…y fixed (#1713)

* docs: add comments to Vercel ignoreCommand explaining exit code convention

Adds both a _ignoreCommandNote JSON key and an inline bash comment to
explain that Vercel's ignoreCommand uses inverted exit codes
(exit 1 = BUILD, exit 0 = SKIP), which is the opposite of Unix convention.
This was gotten wrong 4 times across PRs #1586, #1611, #1630.

Closes #1659

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(sessions): add fix-chain tracking to link fix PRs to the PRs they fixed

Implements GitHub issue #1669. Agents can now record when a PR is fixing
regressions from a previous PR, enabling fix rate computation and traceability.

**Database changes:**
- Add `pr_outcome` column to `agent_sessions` (merged | merged_with_revisions |
  reverted | closed_without_merge)
- Add `fixes_pr_url` column to `agent_sessions` to store the URL of the PR
  being fixed

**API changes (wiki-server):**
- Export `PR_OUTCOMES` constant and `PrOutcome` type from `api-types.ts`
- Update `UpdateAgentSessionSchema` to accept `prOutcome` and `fixesPrUrl`
- Update PATCH /:id route to handle the new fields
- Add GET /stats endpoint returning `{ total, fixSessions, fixRate }`

**CLI changes (crux):**
- `crux issues done <N> --fixes-pr=<URL>` records the fix-chain relationship
- `crux issues done <N> --outcome=<outcome>` records PR outcome
- When `--fixes-pr` is used, posts a comment on the original PR noting the fix
- Session is updated with `fixesPrUrl` and `prOutcome` via wiki-server

**Dashboard changes:**
- Agent Sessions table shows "Fixes" column (orange link to the PR being fixed)
- Agent Sessions table shows "Outcome" column with color-coded badges
- Dashboard header shows fix session count and fix rate when > 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1712)

Addresses the silent CI failure mode observed March 2-4, 2026: when
main branch CI stops producing results entirely (e.g. YAML syntax
errors), the existing checkActions() doesn't alert because it only
checks the most-recent completed run — if there are no runs, nothing
fires.

New check (crux health --check=ci-main):
- Queries GitHub API for ci.yml runs on main branch in last 24h
- Alerts if: 0 runs triggered (workflow may be broken or no pushes)
- Alerts if: runs exist but 0 succeeded (all failed/cancelled)
- Passes if: at least one successful run or a run is in-progress
- Integrated into the twice-daily wellness-check.yml workflow via the
  existing --report --auto-issue pipeline, which creates/updates/
  closes GitHub issues automatically

Also fixes a gap where health/**/*.ts and health/**/*.test.ts were not
included in crux/tsconfig.json or crux/vitest.config.ts, meaning the
existing pr-quality.test.ts and wellness-report.test.ts were also being
skipped by the test runner.

Closes #1651

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ws (#1707)

Replaces the monolithic wellness-check.yml with three focused workflow files:
- server-api-health.yml: Server & DB health + API smoke tests
- frontend-data-health.yml: Frontend availability + data freshness
- ci-pr-health.yml: GitHub Actions health + job queue + PR quality + label cleanup

Each workflow runs independently, uses the same twice-daily schedule, and
manages the wellness GitHub issue via --auto-issue. All 7 checks from the
original are preserved with identical logic (no changes to crux/health/).

Updates .claude/audits.yaml to reference the new workflow file names.
Updates comments in crux/health/ and crux/commands/ to reference new files.

Closes #1667

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(statements): add property upsert endpoint and seed-properties command

- POST /properties/upsert: batch create/update properties in wiki-server
- crux statements seed-properties: seeds 25 properties for 5 missing
  categories (governance, technical, research, products, people)
- These categories are defined as coverage targets but had zero properties,
  causing the improve pipeline to generate nothing for them

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-parsing): handle JSON arrays in LLM response parsing

parseJsonFromLlm looked for the first `{` brace, finding the first
object inside an array and returning only that single object. This
broke the improve pipeline which expects a JSON array.

Now checks for `[` first — if the array bracket comes before any
object brace, it extracts the complete array. This fixes the improve
pipeline generating 0 statements despite valid LLM output.

Also improved batch insert error messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#1699)

* feat: add session-sweep groundskeeper task to auto-expire stale agent sessions

Adds a new `session-sweep` task to the groundskeeper that runs every 4 hours
and automatically marks stale active sessions (not updated in 4+ hours) as
completed. Also removes the `claude-working` label from any closed GitHub
issues that were linked to swept sessions, fixing phantom active sessions and
stale labels (#1664).

Changes:
- apps/groundskeeper/src/tasks/session-sweep.ts: new task calling
  POST /api/agent-sessions/sweep and cleaning up claude-working labels
- apps/groundskeeper/src/config.ts: adds sessionSweep task config with
  TASK_SESSION_SWEEP_ENABLED and TASK_SESSION_SWEEP_SCHEDULE env vars
- apps/groundskeeper/src/index.ts: registers session-sweep task on startup
- apps/wiki-server/src/routes/agent-sessions.ts: sweep endpoint now returns
  issueNumber so groundskeeper can clean up labels
- Test files: update Config mocks in all existing tests to include sessionSweep
- apps/groundskeeper/src/tasks/session-sweep.test.ts: 10 unit tests covering
  sweep endpoint call, label cleanup, deduplication, error handling

Closes #1664

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve E1007 numericId conflict between statement dashboards

statement-quality-dashboard and statement-scores-dashboard both had
numericId E1007, causing build-data to fail with a conflict error.
Reassign statement-quality-dashboard to E1008 (the next unused ID).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove stale computeDiffHash tests after hash check was removed

The c4ebc2e commit removed the diff-hash feature from validate-review-marker.ts
but left the test file importing the now-non-existent computeDiffHash function.
Remove the stale describe block to fix the TypeScript compilation error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sessions): add numeric costCents and durationMinutes fields for aggregation

Closes #1666

- Add DB migration 0060 adding cost_cents (INTEGER) and duration_minutes (REAL) to sessions table
- Add costCents and durationMinutes fields to CreateSessionSchema Zod validation
- Add parseCostCents() and parseDurationMinutes() exported functions to sessions route that parse free-text strings like "~$2.50" → 250 and "~45 minutes" → 45.0
- Auto-parse numeric values on write if not explicitly provided; explicit values take precedence over auto-parsed
- Update sessionValues(), mapSessionRow(), and sessionConflictSet to handle new fields
- Add 24 new tests: 10 parser unit tests, 14 integration tests
- Update agent-sessions dashboard to show aggregate cost when data available
- Existing free-text cost/duration fields unchanged for backwards compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: extract shared fetchAllPaginated helper for API pagination

Replace 4 duplicate pagination implementations with a single shared
helper in apps/web/src/lib/fetch-paginated.ts. Key improvements:
- Fails on intermediate page errors instead of silently returning partial data
- Fetches remaining pages in parallel after first page determines total
- Configurable page size, deadline, timeout, and extra query params
- Console warning when multi-page fetching triggers

Refactored consumers:
- statements-content.tsx (internal dashboard)
- property-explorer-content.tsx (internal dashboard)
- statements/page.tsx (statements explorer)
- claims-data.ts (claims pages)

Closes #1650

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…aggregation (#1715)

* feat(sessions): add numeric costCents and durationMinutes fields for aggregation

Closes #1666

- Add DB migration 0060 adding cost_cents (INTEGER) and duration_minutes (REAL) to sessions table
- Add costCents and durationMinutes fields to CreateSessionSchema Zod validation
- Add parseCostCents() and parseDurationMinutes() exported functions to sessions route that parse free-text strings like "~$2.50" → 250 and "~45 minutes" → 45.0
- Auto-parse numeric values on write if not explicitly provided; explicit values take precedence over auto-parsed
- Update sessionValues(), mapSessionRow(), and sessionConflictSet to handle new fields
- Add 24 new tests: 10 parser unit tests, 14 integration tests
- Update agent-sessions dashboard to show aggregate cost when data available
- Existing free-text cost/duration fields unchanged for backwards compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sessions): correct migration 0062 journal timestamp to be strictly increasing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents (#1716)

* feat(statements): add property upsert endpoint and seed-properties command

- POST /properties/upsert: batch create/update properties in wiki-server
- crux statements seed-properties: seeds 25 properties for 5 missing
  categories (governance, technical, research, products, people)
- These categories are defined as coverage targets but had zero properties,
  causing the improve pipeline to generate nothing for them

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-parsing): handle JSON arrays in LLM response parsing

parseJsonFromLlm looked for the first `{` brace, finding the first
object inside an array and returning only that single object. This
broke the improve pipeline which expects a JSON array.

Now checks for `[` first — if the array bracket comes before any
object brace, it extracts the complete array. This fixes the improve
pipeline generating 0 statements despite valid LLM output.

Also improved batch insert error messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): resolve quality mode bugs found during testing

- Fix by-entity response format: use [...structured, ...attributed]
  instead of .statements (quality mode crashed immediately)
- Re-export analyzeGaps() from gaps.ts (lost during main merge)
- Add normalizeValueDate() to convert partial dates (2024, 2024-06)
  to valid ISO dates — PostgreSQL date columns reject partial strings
- Validate propertyId against property vocabulary before insert
  (FK constraint rejects IDs the LLM invents)
- Include actual API error message in quality mode rejection reasons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): add classify mode and propertyId PATCH support

- Add --mode=classify to improve pipeline: batch-classifies uncategorized
  statements by assigning propertyId via LLM (uses Haiku, ~$0.008/entity)
- Add propertyId to PATCH endpoint schema (PatchStatementBody + handler)
- Add propertyId to PatchStatementInput client type
- Update CLI help to show --mode=quality|classify

Tested on Anthropic entity: classified 36/36 uncategorized statements,
structure dimension score improved 0.560 → 0.683.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add classify mode to statements help text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): show 0 instead of ? for empty classify remaining count

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): score active-only, validate entity, expand property vocab

- Score command now filters to active statements with non-empty text,
  fixing inflated counts and artificially low quality scores
- Improve pipeline now validates entity exists before spending LLM budget
- Classify rejection messages now distinguish null (no match) from
  invalid property IDs (vocabulary gap)
- Added 6 new properties: executive-compensation, operating-expenses,
  net-income, net-assets, risk-assessment, award

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): move coverage-scores routes before /:id wildcard

The GET /:id wildcard route was intercepting /coverage-scores and
/coverage-scores/all requests, causing "Statement ID must be a
positive integer" errors. Moved these routes before the wildcard
so they match first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): auto-classify before gap-filling in improve pipeline

When runSinglePass detects uncategorized statements, it automatically
runs classify (cheap Haiku call) before analyzing gaps. This ensures
accurate coverage numbers and prevents wasting Sonnet budget on
categories that are already covered after classification.

Tested on redwood-research: 15 uncategorized statements classified,
coverage went from 0.330 → 1.000 in 2 passes (was 7+ without classify).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ol (#1719)

* feat: add structured JSONL logging and periodic reflection to PR Patrol

- Log every PR fix result and cycle summary to ~/.cache/pr-patrol/runs.jsonl
  with structured fields (pr_num, issues, result, elapsed, cycle_number)
- Every 10 cycles, spawn a Claude reflection session that analyzes the last
  100 log entries for recurring patterns and optionally files a GitHub issue
- Reflection results logged separately to ~/.cache/pr-patrol/reflections.jsonl
- Fix summary truncation bug (tail -c 1500 | head -c 500 took a random
  middle slice, now uses tail -c 500 for the actual last 500 chars)
- Configurable via PR_PATROL_REFLECTION_INTERVAL env var (default: 10)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review feedback

- Validate REFLECTION_INTERVAL is a positive integer to prevent modulo-by-zero
  arithmetic errors in the reflection loop
- Filter empty strings from jq split(",") to avoid spurious [""] in JSONL logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…rds (#1624)

* feat(security): add SafeExternalLink component to centralize URL safety guards

- Create apps/web/src/lib/url-utils.ts with isSafeUrl() utility
- Create apps/web/src/components/ui/safe-external-link.tsx wrapping isSafeUrl
  validation + target=_blank rel=noopener noreferrer in one component
- Migrate all 4 files using the duplicated isSafeUrl+<a> pattern:
  CitationOverlay, FootnoteTooltip, InlineCitationCards, citation-detail
- Remove 3 copy-pasted isSafeUrl definitions and 1 getDomain duplicate
- Re-export isSafeUrl from CitationOverlay for backward compatibility
- Update citation-data.test.ts to import from url-utils directly

Closes #1621

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: remove dead isSafeUrl re-export, consolidate getDomain in citation-detail

- Remove dead re-export of isSafeUrl from CitationOverlay (no consumers remain)
- Replace local getDomain copy in citation-detail.tsx with import from resource-utils

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove duplicate isSafeUrl/getDomain imports in FootnoteTooltip

Leftover stale import line from merge caused TS2300 duplicate identifier
errors. Remove the redundant `import { getDomain, isSafeUrl } from
"./resource-utils"` since both are already imported individually above.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: show clear warning when AUTOMATION_PAUSED skips workflows

All 15 workflows that check AUTOMATION_PAUSED now:
- Show [PAUSED] in the GitHub Actions run name
- Run a paused-notice job with a ::warning annotation explaining
  how to resume

Also adds CLI commands:
- `pnpm crux ci pause-automation` — set AUTOMATION_PAUSED
- `pnpm crux ci unpause-automation` — delete AUTOMATION_PAUSED

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract paused-notice job into reusable workflow

Replace the 7-line inline paused-notice job block duplicated across
15 workflow files with a single reusable workflow (_paused-notice.yml).
Each workflow now calls it with a 2-line `uses:` reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* rename: pause-automation → pause-actions, unpause → resume-actions

Use GitHub's own terminology ("Actions") and more natural "resume"
instead of "unpause".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…1696)

* feat: add data quality assertions to statements extraction pipeline

Adds a new `validate-quality.ts` module with three core exports:
- `validateExtractedStatements()` — checks post-LLM extraction output
- `validateCreateStatementBatch()` — checks pre-DB-write batch
- `printQualityReport()` — color-coded CLI reporter

Assertion checks:
- ZERO_STATEMENTS: no statements from a page with ≥2 sections
- EMPTY_STATEMENT_TEXT: blank statement text strings
- NON_FINITE_NUMERIC: NaN or Infinity in numeric values
- ALL_ZERO_NUMERIC: all-zero batch when ≥3 numeric values (suggests extraction failure)

In dry-run mode, violations are warnings (logged but not blocking).
In apply mode (--apply flag), violations abort before any DB writes.

Integration points:
- `extract.ts`: validates after FK sanitization, before dry-run/apply split
- `improve.ts`: validates accepted statements before `createStatementBatch()`

Closes #1653

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore E1008 numericId for statement-quality-dashboard (conflict with E1007)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address CodeRabbit review feedback on data quality assertions

- Add validateCreateStatementBatch call in runQualityPass (improve.ts)
  before createStatementBatch to close the quality gate bypass
- Add DUPLICATE_TUPLE detection in validateCreateStatementBatch for
  items with the same (subjectEntityId, propertyId, valueDate) key
- Add tests for printQualityReport dry-run and apply-mode CLI branches
- Add tests for DUPLICATE_TUPLE detection and non-duplicate cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents agents from creating new bash scripts when TypeScript
in crux/ is the better choice. Exceptions: git hooks, Claude Code
hooks, and CI glue where Node.js isn't available.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(statements): add property upsert endpoint and seed-properties command

- POST /properties/upsert: batch create/update properties in wiki-server
- crux statements seed-properties: seeds 25 properties for 5 missing
  categories (governance, technical, research, products, people)
- These categories are defined as coverage targets but had zero properties,
  causing the improve pipeline to generate nothing for them

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(json-parsing): handle JSON arrays in LLM response parsing

parseJsonFromLlm looked for the first `{` brace, finding the first
object inside an array and returning only that single object. This
broke the improve pipeline which expects a JSON array.

Now checks for `[` first — if the array bracket comes before any
object brace, it extracts the complete array. This fixes the improve
pipeline generating 0 statements despite valid LLM output.

Also improved batch insert error messages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): resolve quality mode bugs found during testing

- Fix by-entity response format: use [...structured, ...attributed]
  instead of .statements (quality mode crashed immediately)
- Re-export analyzeGaps() from gaps.ts (lost during main merge)
- Add normalizeValueDate() to convert partial dates (2024, 2024-06)
  to valid ISO dates — PostgreSQL date columns reject partial strings
- Validate propertyId against property vocabulary before insert
  (FK constraint rejects IDs the LLM invents)
- Include actual API error message in quality mode rejection reasons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): add classify mode and propertyId PATCH support

- Add --mode=classify to improve pipeline: batch-classifies uncategorized
  statements by assigning propertyId via LLM (uses Haiku, ~$0.008/entity)
- Add propertyId to PATCH endpoint schema (PatchStatementBody + handler)
- Add propertyId to PatchStatementInput client type
- Update CLI help to show --mode=quality|classify

Tested on Anthropic entity: classified 36/36 uncategorized statements,
structure dimension score improved 0.560 → 0.683.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add classify mode to statements help text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): show 0 instead of ? for empty classify remaining count

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): score active-only, validate entity, expand property vocab

- Score command now filters to active statements with non-empty text,
  fixing inflated counts and artificially low quality scores
- Improve pipeline now validates entity exists before spending LLM budget
- Classify rejection messages now distinguish null (no match) from
  invalid property IDs (vocabulary gap)
- Added 6 new properties: executive-compensation, operating-expenses,
  net-income, net-assets, risk-assessment, award

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(statements): move coverage-scores routes before /:id wildcard

The GET /:id wildcard route was intercepting /coverage-scores and
/coverage-scores/all requests, causing "Statement ID must be a
positive integer" errors. Moved these routes before the wildcard
so they match first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): auto-classify before gap-filling in improve pipeline

When runSinglePass detects uncategorized statements, it automatically
runs classify (cheap Haiku call) before analyzing gaps. This ensures
accurate coverage numbers and prevents wasting Sonnet budget on
categories that are already covered after classification.

Tested on redwood-research: 15 uncategorized statements classified,
coverage went from 0.330 → 1.000 in 2 passes (was 7+ without classify).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): add entity ideation + ontology skills + statement reassignment

Add `crux statements ideate` command for analyzing entity statements and
suggesting sub-entity splits via LLM. Add `subjectEntityId` to PATCH
endpoint to enable statement reassignment between entities. Add
`includeChildren` query parameter for roll-up queries across related entities.

Create three Claude Code skills for deep ontology reasoning:
- /ontology-review — analyze entity structure and suggest splits
- /entity-deep-dive — comprehensive single-entity quality review
- /knowledge-gap — identify missing topics in the knowledge base

Applied to Anthropic: created claude entity, moved 16 statements to it,
reassigned 14 statements to constitutional-ai, responsible-scaling-policies,
sleeper-agents, deceptive-alignment, and long-term-benefit-trust. Updated
Anthropic's relatedEntries with 7 missing relationships.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use valid relationship types in Anthropic relatedEntries

Replace policy-of, product-of, governance with valid enum values
(composed-of, related) to pass schema validation gate check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(statements): add coverage targets for approach, concept, policy, risk, project types

Enable the improve pipeline to work on non-organization entity types.
Previously only organization, person, and model had coverage targets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…test (#1730)

- crux/statements/improve.ts: duplicate function declaration of
  normalizeValueDate was introduced in #1723 causing esbuild transform
  failure; remove the second copy (identical to the first)
- crux/statements/coverage-targets.test.ts: 'concept' was added to
  TARGETS in #1723; update the "unknown entity type" tests to use
  'widget' which truly has no coverage targets

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: increase auto-update timeout and align scheduled count default

The auto-update job has been hitting the 60-minute job timeout for 3
consecutive days (run IDs 22705771773, 22660285330, 22618059745).

Root cause: each page improvement takes ~10-12 minutes, setup/install/
build-data takes ~18 minutes, so 5 pages * 12 min + 18 min overhead =
~78 minutes — well over the 60-minute limit.

Two changes:
1. Increase `timeout-minutes` from 60 to 120 to give the job enough
   headroom to complete all pages.
2. Align the scheduled run default count (`|| '5'`) with the
   `workflow_dispatch` UI default (which was already '3'). This reduces
   the scheduled wall time to ~54 min (3 pages * 12 min + 18 min),
   comfortably within the new 120-minute limit.

Closes #1725

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use >| (force-overwrite) for /tmp writes to prevent zsh noclobber stale reads

In push-and-ensure-green.md, Option B showed `cat > /tmp/pr-body.md` which silently
fails under zsh noclobber if the file already exists, causing the agent to read
stale file content. Replace with `cat >| /tmp/pr-body.md` and add a warning comment.

pr-review-guidelines.md already correctly documents this issue as "BAD" pattern.

Closes #1701

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1729)

The auto-update job has been hitting the 60-minute job timeout for 3
consecutive days (run IDs 22705771773, 22660285330, 22618059745).

Root cause: each page improvement takes ~10-12 minutes, setup/install/
build-data takes ~18 minutes, so 5 pages * 12 min + 18 min overhead =
~78 minutes — well over the 60-minute limit.

Two changes:
1. Increase `timeout-minutes` from 60 to 120 to give the job enough
   headroom to complete all pages.
2. Align the scheduled run default count (`|| '5'`) with the
   `workflow_dispatch` UI default (which was already '3'). This reduces
   the scheduled wall time to ~54 min (3 pages * 12 min + 18 min),
   comfortably within the new 120-minute limit.

Closes #1725

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: allow merge commits to not invalidate review-done marker

Add onlyMergeCommitsSince() helper to validate-review-marker.ts that
uses --first-parent git log to check whether all commits between the
reviewed SHA and HEAD are merge commits. If they are, the marker is
still considered valid — merging main into a feature branch to resolve
conflicts should not require a re-review.

Non-merge commits (new code authored on the branch) still invalidate
the marker as before. Also handles the edge case where the marker SHA
is not an ancestor of HEAD (rebase/force-push) by failing closed.

Closes #1695

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update tests to call onlyMergeCommitsSince() directly and add cwd param

- Add optional `cwd` parameter to onlyMergeCommitsSince() so tests can
  inject a temp repo directory instead of always running against PROJECT_ROOT
- Rewrite onlyMergeCommitsSince tests to import and call the function
  directly, asserting its return value (true/false) rather than testing
  raw git commands separately
- Fix tautological expect(sha).toBe(sha) assertion — now calls the
  function with sha === sha to verify the early-exit path returns true
- Add a new test case: returns false when markerSha is not an ancestor
  of headSha (rebase/force-push scenario)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1705)

- Replace hard-coded /api/statements?limit=500 in statements-content.tsx
  with a paginating fetchAllStatementsPaginated() helper that fetches all
  pages (each up to 500 rows) — prevents dashboard table truncation and
  wrong entity-count stats when total exceeds 500
- Replace hard-coded /api/statements?limit=200 in property-explorer-content.tsx
  with the same paginating approach — prevents wrong entity-to-property
  coverage counts when total exceeds 200
- Raise /insights endpoint in sessions.ts from hard-coded .limit(500) to
  INSIGHTS_LIMIT=5000 with a console.warn when the limit is hit — prevents
  silent insight truncation; a warning flags when pagination is needed

Both web fixes log a console.warn when multi-page fetching is required
so operators can see when data volumes are growing.

Closes #1650

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Detailed analysis of Anthropic statements pages identifying 32 issues
across data quality, UX, missing features, and architecture. Includes
design mockups and a prioritized 5-phase fix roadmap.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Snapshot component:
- Replace alarming "conflicting properties" with "multiple open values"
- Remove amber background highlighting and warning triangle icons
- Change "(also: $14B)" to "(prev: $14B [2026-02])" with dates
- Sort previous values by date descending
- Improve qualifier label formatting (strip prefix, replace hyphens)

PATCH endpoint:
- Add qualifierKey to PatchStatementBody schema and handler
- Previously PATCH silently ignored qualifierKey updates

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add actionlint gate check for GitHub Actions workflow YAML

Closes #1646

- Add crux/validate/validate-actions-yaml.ts: runs actionlint on all
  .github/workflows/*.yml files, gracefully skips when actionlint is
  not installed (local dev), blocking when installed.
- Register the check in validate-gate.ts as a blocking parallel step.
- Add Install actionlint + actionlint blocking step to .github/workflows/ci.yml.
- Fix pre-existing injection risk in ci.yml (github.head_ref used directly
  in shell — actionlint [expression] finding). Replaced with env var HEAD_REF.
- shellcheck style/info suggestions (SC*:info, SC*:style) are excluded via
  -shellcheck= flag so only real actionlint structural errors block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use official download-actionlint script to fix gzip install failure

The previous install command used /releases/latest/download/ redirect URLs
piped directly to tar, which fails with "gzip: stdin: not in gzip format"
because GitHub's redirect responses are not handled correctly in piped curl.

Replace with the official download-actionlint script which handles platform
detection and the redirect properly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use gh release download for actionlint install (curl URL returns 404)

The download-actionlint script URL returned a 404 page and the
/releases/latest/download/ redirect returned non-gzip HTML. Using
`gh release download` which handles GitHub API auth and redirects
reliably.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use import.meta.url for main script detection in validate-actions-yaml

Replace process.argv[1]?.includes('validate-actions-yaml') with the more
robust ESM-idiomatic check process.argv[1] === fileURLToPath(import.meta.url).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the full standalone /statements/entity/[entityId] page with a
redirect to /wiki/[numericId]/statements, consolidating two different
views of the same data into a single canonical URL (the wiki tab).

This addresses discussion #1736 item 2.1. Old links and internal
references to /statements/entity/... will transparently redirect.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Record verification results for post-merge items and ongoing audits:
- PR #1630: Vercel production-only builds — verified
- PR #1637: Audits system end-to-end — verified
- Ongoing: ci-gate-catches-regressions, audits-system-adoption,
  vercel-production-only — all passing

Also reviewed PR #1707 (wellness-check split), #1710 (AUTOMATION_PAUSED),
#1708 (citation dots), #1704 (issue-responder disabled), #1712 (CI alerting)
— all confirmed working in production.

https://claude.ai/code/session_012gfpYecbR2ZdkHAiobzBj3

Co-authored-by: Claude <noreply@anthropic.com>
OAGr and others added 18 commits March 5, 2026 10:28
- Standalone page: replace misleading "Total" (included retracted) with
  "Active"; rename labels to "Structured"/"Attributed"; "With Citations"
  now counts only active statements
- Wiki tab: replace redundant "Active Cited" with "Retracted" count;
  rename "Active Structured/Attributed" to "Structured/Attributed"
- Add brief intro text on both pages explaining what structured vs
  attributed statements are
- Add "rose" color option to StatCard component

Addresses discussion #1736 items 2.7 and 2.10.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
)

The internal Statements dashboard (E1002) and Property Explorer dashboard
(E1004) were redundant with the richer public pages at /statements and
/statements/properties. The public pages have better filtering, navigation,
and visual design.

- Sidebar now links directly to /statements and /statements/properties
- Internal redirects updated to point to public pages
- Removed unused content components, table components, and MDX stubs
- Cleaned up mdx-components.tsx registrations
- Updated wiki-nav test to reflect new linking pattern

https://claude.ai/code/session_014ajohoZ9vfQ7zskBvScfpR

Co-authored-by: Claude <noreply@anthropic.com>
…ic (#1751)

- Both server and client csvEscape now quote cells starting with =, +, -, @, \t, \r
  to defend against CSV injection attacks in Excel/Google Sheets
- Sanitize entityId in Content-Disposition headers to prevent header injection
- Client statementToFlatValue now handles valueNumeric (matching server formatValue)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: improve statements page UX (discussion #1736)

- Extract formatPeriod to shared statement-display.ts with human-readable
  dates (Mar 2026 instead of 2026-03)
- Hide Verdict column when >90% of statements are unverified
- Hide inactive/retracted statements by default on standalone entity page
- Hide empty categories, collapse uncategorized by default
- Fix qualifier display leaking raw text into values (suppress non-prefix
  qualifiers like "per-share-tender")
- Show qualifier context in Property column as "(qualifier)"
- Add qualifierKey to PATCH schema (from PR #1737)
- Soften conflict display: "prev:" with dates instead of "also:" with
  amber warnings
- Label sidebar category counts as "(all entities)"

Closes #1736

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove duplicate qualifierKey field in PatchStatementBody schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ead of rejecting batch (#1760)

The facts sync endpoint rejected the entire batch of 144 facts when any
subject value (like "industry-average") didn't match a real entity. This
has been blocking the Sync Entities & Facts workflow since the subject
validation was added in b40dbf9.

Now warns and nulls out unresolved subjects so the rest of the batch
syncs successfully.

Closes #1759

https://claude.ai/code/session_01GxwyPf2Q2xcCFuka8rGpR2

Co-authored-by: Claude <noreply@anthropic.com>
…c.) (#1753)

Extends PR Patrol to surface unresolved bot review comments as actionable
issues, using the existing single GraphQL call (no extra API request).

Changes:
- Extend PR_QUERY to fetch reviewThreads (resolved/outdated status,
  path, line, comment body/author) alongside status checks
- Add extractBotComments() — filters to unresolved, non-outdated threads
  from known bot logins (coderabbitai, dependabot, renovate, github-actions)
- Add two new PrIssueType values: bot-review-major (score 55) and
  bot-review-nitpick (score 15), classified by ACTIONABLE_SEVERITY_RE
- Inject bot comment bodies into the fix prompt with file/line context,
  truncated at 2000 chars each to keep prompts manageable
- Update pr-patrol.md skill doc to reflect the new issue type

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create shared CommandOptions type in crux/lib/command-types.ts and
  update 14 command files to use it instead of duplicated declarations
- Centralize wiki-server timeout constants in crux/lib/config.ts,
  replacing duplicated values in client.ts and sync-common.ts
- Fix crux/README.md: remove duplicate edit-log entry, add 27 missing
  domain references, update page count from ~625 to ~660
- Fix --no-limit flag in issues create (kebab-case wasn't recognized
  after crux.mjs converts to camelCase)

Closes #1754
Closes #1756
Closes #1757

https://claude.ai/code/session_01RK5QvhtkMzMqoxFNEHLE1j

Co-authored-by: Claude <noreply@anthropic.com>
Resolves conflicts between main and origin/production to fix PR #1763.

Conflict resolutions:
- .github/workflows/ci.yml: kept HEAD (env var approach for shell safety)
- apps/web/vercel.json: kept HEAD (includes github.enabled:false from #1739)
- apps/wiki-server/src/routes/statements.ts: kept HEAD (qualifierKey schema,
  /export endpoint); removed duplicate qualifierKey line (merge artifact)
- Deleted property-explorer-content.tsx + statements-content.tsx (consolidated in #1749)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ft (#1782)

* fix: derive test mock types from Drizzle schema to prevent column name drift

5 wiki-server test files had hardcoded column name strings in their in-memory
store type definitions. Every schema migration that renames a column broke
these mocks, causing cascading CI failures (PR #1609 had 30+ failures from
this exact problem).

Fix: replace hardcoded field definitions with TypeScript types derived from
the Drizzle schema using \`typeof table.\$inferSelect\`. TypeScript now catches
column renames at compile time — if a migration renames a field, the type
error surfaces immediately rather than as a runtime test failure.

Key changes per file:
- hallucination-risk.test.ts: HrsRow = typeof hallucinationRiskSnapshots.\$inferSelect
- edit-logs.test.ts: EditLogRow = typeof editLogs.\$inferSelect
- citations.test.ts: QuoteRow/SnapshotRow/ContentRow from respective schema tables
- auto-update-runs.test.ts: RunRow/ResultRow from autoUpdateRuns/autoUpdateResults
- auto-update-news.test.ts: RunRow/NewsRow from autoUpdateRuns/autoUpdateNewsItems

All stores now use camelCase Drizzle field names (matching \$inferSelect) with
toSqlRow() helpers to produce the snake_case objects the dispatch functions
need to return for the mock SQL layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): remove unused rest variable in auto-update-news dispatch

The destructured `rest` variable was never used after the PR migration
to camelCase stores. The `newsToSqlRow()` call now directly receives `r`
instead of `{ ...r }` (same result), and the redundant destructuring
is removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude was hitting the 30-turn limit during daily maintenance sweeps,
causing `claude-code-action` to fail with `error_max_turns`. Observed
in runs 22649707498 (2026-03-04) and 22708103847 (2026-03-05).

Raise --max-turns 30 → 60 and timeout-minutes 30 → 60 to give the
maintenance sweep enough budget to complete daily tasks.

Root cause investigation for issue #1745:
- server-health-monitor.yml: historical push-triggered failures from
  when YAML had syntax errors (fixed in #1587). No scheduled runs exist
  yet because the weekly Monday schedule hasn't fired since the YAML
  was last fixed (2026-03-05, next Monday is 2026-03-09).
- auto-update.yml: timeout-minutes: 60 caused 60-min cancellations.
  Already fixed in main via PR #1728 (timeout-minutes: 120).
- scheduled-maintenance.yml: max-turns: 30 too low, fixed here.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… drift detection (#1781)

* feat(ci): add PR size warning (non-test additions) and Drizzle schema drift detection

PR size warning (#1685):
- Update pr-size-check.yml to filter out test files, wiki content,
  snapshots, and lockfiles before counting additions
- Now checks non-test infrastructure additions (threshold: 500) instead
  of total additions+deletions, matching the issue's intent
- Adds git checkout step with fetch-depth: 0 to enable per-file diff
- Comment now shows both non-test and total additions for context

Drizzle schema drift detection (#1686):
- Add crux/validate/validate-schema-drift.ts with two modes:
  - PR mode (BASE_SHA/HEAD_SHA): warns if schema.ts changed without
    a new migration file being added or modified
  - Local/structural mode: runs drizzle-kit check to compare schema.ts
    against migration journal snapshots (no DB required)
- Add "Drizzle schema drift detection (advisory)" step to ci.yml,
  runs on pull_request events with continue-on-error: true
- Catches the class of bug from PR #1570 (migration 0052 journal desync)

Both checks use ::warning:: annotations — advisory only, never blocking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): fix shallow-clone false-negative in schema drift check and remove dead pr API call

Three bugs found during code review:

1. **False-negative in schema drift detection (ci.yml)**: The default
   checkout is shallow (depth=1). `git diff BASE...HEAD` (3-dot) requires
   finding the merge-base of both SHAs via ancestor traversal, which fails
   silently with a shallow clone. The `getChangedFiles()` try/catch was
   returning `[]` on error, so the check reported "schema.ts not modified"
   (false-negative) for most real PRs where the base branch had advanced
   since branching.

   Fix: `git fetch --unshallow` before the validator step so the merge-base
   is always reachable. Falls back to `--depth=100` if already unshallow
   (e.g., already fetched with depth=0 earlier in the job).

2. **Silent git failure → misleading message**: `getChangedFiles()` returned
   `[]` on error, which led runPrDriftCheck to print "schema.ts not modified
   in this PR" even when git had failed entirely.

   Fix: Return `null` on error (distinct from `[]` = no files changed) and
   emit a `::warning::` annotation explaining that the check was skipped.

3. **Dead API call in pr-size-check.yml**: The `const { data: pr }` GitHub
   API fetch was left over from the old implementation (which used
   `pr.additions`, `pr.deletions`, `pr.changed_files`). The new code
   computes all those values from `git diff --numstat` and never reads `pr`.
   Removed the unnecessary API round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…on (#1786)

* fix(ci): increase scheduled-maintenance max-turns and timeout

Previous run (2026-03-05T07:59:11Z) failed with error_max_turns after
31 turns — Claude ran out with max-turns=30 on a weekly sweep. Also
increase timeout-minutes from 30 to 60 to give sufficient wall-clock
headroom for weekly/monthly cadences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(pr-patrol): add main branch CI monitoring and PR overlap detection

- Add check_main_branch() that runs at the top of each cycle with
  highest priority — if main CI is red, it dispatches Claude to
  diagnose and fix before processing any PRs
- Add detect_pr_overlaps() that compares changed files across open
  PRs and posts warning comments when PRs touch the same files
- Remove claude-assistant.yml (effectively dead — 0 successful runs
  in visible history, all triggers skip)
- Remove ci-autofix.yml (reliably fails with 10-turn limit, redundant
  with pr-patrol's existing CI failure handling with 40 turns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(pr-patrol): port main branch CI + overlap detection to TypeScript, delete bash script

Replaces the bash implementation with TypeScript in crux/pr-patrol/index.ts:
- checkMainBranch(): queries GitHub CI workflow runs on main, returns red/green
- fixMainBranch(): spawns Claude to diagnose and fix main branch CI failures
- detectPrOverlaps(): fetches changed files for open PRs, posts overlap warnings
- Generalized cooldown functions to accept string keys (for 'main-branch', 'overlap-A-B')
- Integrated both features into runCheckCycle() with main branch as step 0
- Deleted scripts/pr-patrol.sh (1028 lines) — TS version is now canonical
- Updated CLAUDE.md to remove "pending migration to TS" note
- Updated .claude/commands/pr-patrol.md daemon mode references

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#1784)

* feat(statements): add CRUD CLI commands and markdown-based ontology draft workflow

Phase 0b: CRUD commands for statements management:
- `crux statements list <entity>` — formatted table with property/value/date
- `crux statements create <entity>` — auto-validates property, generates text
- `crux statements update <id>` — PATCH any field (property, status, text, date)
- `crux statements retract <id>` — single or batch retract with --all flag
- `crux statements properties` — list all properties grouped by category

Phase 1: Ontology review workflow:
- `crux statements draft <entity>` — generates .claude/ontology-drafts/<entity>.md
  with coverage tables, gap analysis, unclassified statements, and action items
- `crux statements apply-draft <entity>` — parses checked actions (RETRACT,
  CLASSIFY, NEW_PROPERTY, CREATE) and executes them against wiki-server

Also expands PatchStatementInput to match server's full PATCH schema (variety,
statementText, qualifierKey, validStart/End, attributedTo).

Epic: #1765

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: prefix unused parseDraft parameter with underscore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…dings (#1780)

* feat(ci): add CodeRabbit security gate to block merges on critical findings

Adds a new GitHub Actions workflow that checks for unresolved Critical or
Major CodeRabbit review threads before a PR can merge. If any blocking
findings exist, the check fails until either:
- The thread is resolved in the GitHub review interface, OR
- A human adds the `coderabbit-security-ok` label after acknowledging

The gate prevents bots and claude-* actors from self-bypassing via label.

Addresses issue #1649, which identified 3 unresolved security findings
from CodeRabbit (XSS, global delete risk, missing schema validation) that
were self-merged without human review. All 3 bugs were already fixed:
- XSS (PR #1613): StatementSourcesTable.tsx uses isSafeUrl() at line 111
- Global delete (PR #1623): cleanup endpoint uses z.string().min(1) for entityId
- Schema validation (PR #1634): QualityDimensionsSchema with .strict() exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security-gate): harden bot actor detection and add GraphQL pagination

Two security fixes for the CodeRabbit security gate:

1. **Broaden bot actor detection in isDisallowedActor**
   - Previous: only blocked `github-actions[bot]`, `dependabot[bot]`, and `claude-*`
   - Any other bot (renovate[bot], snyk-bot, GitHub Apps, coderabbitai[bot]) could
     self-apply the `coderabbit-security-ok` label to bypass the gate
   - Fix: accept the full actor object and check `actor.type === 'Bot'` first
     (GitHub sets this for ALL bot accounts and GitHub Apps), with a belt-and-suspenders
     `[bot]` suffix check and the explicit list as final fallbacks
   - Call site updated to pass `mostRecent.actor` instead of just `.actor.login`

2. **Paginate GraphQL reviewThreads query**
   - Previous: `reviewThreads(first: 100)` with no pagination
   - PRs with >100 review threads would silently miss findings in threads 101+
   - Fix: add `pageInfo { hasNextPage endCursor }` and loop with cursor pagination
   - Collects all threads before filtering for blocking severity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security-gate): accept both coderabbitai and coderabbitai[bot] logins

CodeRabbit may appear as either login depending on whether the GraphQL or
REST API is used. Accept both to prevent the gate silently passing when
the login format doesn't match.

Addresses CodeRabbit Critical finding on PR #1780.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): increase scheduled-maintenance max-turns and timeout

Previous run (2026-03-05T07:59:11Z) failed with error_max_turns after
31 turns — Claude ran out with max-turns=30 on a weekly sweep. Also
increase timeout-minutes from 30 to 60 to give sufficient wall-clock
headroom for weekly/monthly cadences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): integrate Reviewdog for inline PR annotations

Add Reviewdog to CI pipeline to post inline annotations on PR diffs for
validation findings. This makes it much faster to locate issues vs
digging through CI logs.

Changes:
- Add crux validate to-rdjsonl converter (crux → Reviewdog rdjsonl format)
- Add reviewdog setup + crux validation annotations step (70+ rules)
- Add actionlint reviewdog annotations step
- Both annotation steps are advisory (continue-on-error, filter-mode=added)
- Add job permissions for Checks API (checks: write)
- 5 tests for the converter covering severity mapping, paths, edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add post-merge audit for reviewdog annotations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use null check for line 0, update audit PR number

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): add pipefail to reviewdog steps, remove stderr suppression

Surfaces broken annotation plumbing as a visible (orange) step failure
instead of silently producing no annotations. continue-on-error keeps
it advisory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1771)

* feat(ci): test Drizzle migrations against throwaway Postgres in CI

Adds a migrate-test job that spins up a throwaway Postgres 16 container
and runs all Drizzle migrations against it on every PR. Catches broken
migrations before they reach production.

Previously, migration failures were only discovered after deploying to
production. The job is independent (no needs: dependency) so it runs
in parallel with the main CI job.

Closes #1652

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add timeout-minutes to migrate-test job

Without a timeout, the job defaults to the GitHub Actions maximum of 6
hours. A stuck migration (e.g., waiting for a DDL lock that never
arrives) would block the CI queue for the full 6 hours. 15 minutes is
well above the expected runtime (~2–3 min for 63 migrations on a fresh
container) but provides a safety net.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(migration): remove FK to wiki_pages(integer_id) — column added via manual migration only

integer_id on wiki_pages was applied via phase4a-manual-migration.sql (outside Drizzle),
so it does not exist in a fresh CI database. The migrate-test CI job was failing with
'column integer_id does not exist'. Store page_id_int as plain INTEGER with a comment.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): increase scheduled-maintenance max-turns and timeout

Previous run (2026-03-05T07:59:11Z) failed with error_max_turns after
31 turns — Claude ran out with max-turns=30 on a weekly sweep. Also
increase timeout-minutes from 30 to 60 to give sufficient wall-clock
headroom for weekly/monthly cadences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(pr-patrol): add auto-merge for PRs labeled ready-to-merge

Extend PR Patrol's detect-score-fix cycle with a merge phase that
automatically squash-merges PRs labeled `ready-to-merge` when clean:
CI green, no conflicts, no unresolved threads, no unchecked items.

Key changes:
- Refactor detectAllPrIssues into fetchOpenPrs + detectAllPrIssuesFromNodes
  so both fix and merge phases share a single GraphQL fetch
- Add checkMergeEligibility (pure, exported) and findMergeCandidates
- Merge at most 1 PR per cycle (lets CI re-run on updated main)
- Add `crux pr-patrol merge-status` subcommand for live status
- 23 unit tests covering eligibility checks and edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(pr-patrol): also block merge on CANCELLED CI conclusion

Caught during code review — a CheckRun with conclusion: 'CANCELLED'
was not detected by either the failure or pending checks, allowing
a PR with a cancelled CI check to be auto-merged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…1790)

Salvages valuable content from closed PR #1764:
- Add 15 Claude model entities (E1027-E1041) to data/entities/ai-models.yaml
- Simplify current-snapshot.tsx: always show verdict column, amber conflict text
- Add 'part-of' and 'created-by' to schema relationship enum

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add language identifiers to unlabeled code fences in command docs
- Fix qualifier parsing to preserve segments after first colon
- Filter includeChildren to only composed-of relationships (not all related)
- Move budget check after LLM analysis (was always 0 before)
- Combine JSON output into single object in --json --apply mode

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
longterm-wiki-agent3 Error Error Mar 6, 2026 2:53am

OAGr and others added 3 commits March 5, 2026 18:37
* fix(ci): increase scheduled-maintenance max-turns and timeout

Previous run (2026-03-05T07:59:11Z) failed with error_max_turns after
31 turns — Claude ran out with max-turns=30 on a weekly sweep. Also
increase timeout-minutes from 30 to 60 to give sufficient wall-clock
headroom for weekly/monthly cadences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(release): prevent squash merges on production branch

Squash-merging release PRs (main → production) creates orphan commits
that cause conflicts on every subsequent release. Added:
- GitHub branch ruleset enforcing merge commits only for production
- CI notice reminding to use merge commits on release PRs
- CLI warning in `crux release create` output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ci): increase scheduled-maintenance max-turns and timeout

Previous run (2026-03-05T07:59:11Z) failed with error_max_turns after
31 turns — Claude ran out with max-turns=30 on a weekly sweep. Also
increase timeout-minutes from 30 to 60 to give sufficient wall-clock
headroom for weekly/monthly cadences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(pr): draft PRs by default, auto-undraft in pr-patrol

- `crux pr create` now creates draft PRs by default (use --no-draft to override)
- New `crux pr ready` command validates eligibility (CI green, no conflicts,
  no unresolved threads, no unchecked items) before converting draft to ready
- PR Patrol auto-undrafts draft PRs labeled `ready-to-merge` when all other
  eligibility checks pass, then merges in the same cycle
- PR Patrol skips draft PRs from the fix queue (no point fixing drafts)
- Added `isDraft` to GraphQL PR query and `GqlPrNode` interface
- `is-draft` added as a new `MergeBlockReason`
- 4 new tests covering draft eligibility checks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review findings — is-draft self-block, REST fallback, failed undraft tracking

- Filter out 'is-draft' from block reasons in `crux pr ready` (it's the
  whole point of the command, not a blocker)
- Replace REST-then-GraphQL fallback with direct GraphQL for undrafting
  (REST API doesn't support undrafting, so the REST call always fails)
- Track which PRs were successfully undrafted before attempting merge
  (prevents merge attempts on PRs where undraft API call failed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(pr): address CodeRabbit review feedback

- Validate --pr option: return error on malformed values instead of
  silently falling back to branch autodetection
- Keep ci-pending as a blocker for `crux pr ready` to match PR Patrol
  auto-undraft behavior (only is-draft is filtered out)
- Simulate undraft in dry-run mode so merge phase also reports what
  would happen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* origin/production:
  release: 2026-03-05 #3 (#1763)

# Conflicts:
#	.claude/audits.yaml
#	.github/workflows/ci.yml
#	apps/web/src/app/wiki/[id]/statements/current-snapshot.tsx
#	crux/commands/pr-patrol.ts
#	crux/pr-patrol/index.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Production release PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant