Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/contributing-to-gittensory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ pasted JSON block will not work in Codex). You'll use these tools in Phases 1 an
- **Run the pre-start checks** via MCP: `gittensory_check_before_start` (is it claimed / a duplicate
cluster / already solved?) and, if linking an issue, `gittensory_validate_linked_issue`.
- **Stay in scope.** The gate's `wantedPaths` are `src/`, `packages/`, `test/`, `migrations/`,
`scripts/`, `.github/workflows/`, `wrangler.jsonc`, `apps/gittensory-ui/`. Avoid `blockedPaths`
`scripts/`, `review-enrichment/`, `.github/workflows/`, `wrangler.jsonc`, `apps/gittensory-ui/`. Avoid `blockedPaths`
(`site/`, `CNAME`, `**/lovable/**`). Keep the PR narrow — one coherent change.

---
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/contributing-to-gittensory/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ Implications for you:

`.gittensory.yml` (the public config you can predict against) sets the gate *modes* (`linkedIssue:
advisory`, `duplicates: block`, `readiness: advisory/60`, AI review off) and the focus manifest
(`wantedPaths`: `src/ packages/ test/ migrations/ scripts/ .github/workflows/ wrangler.jsonc
apps/gittensory-ui/`; `blockedPaths`: `site/ CNAME **/lovable/**`; `linkedIssuePolicy: preferred`;
(`wantedPaths`: `src/ packages/ test/ migrations/ scripts/ review-enrichment/ .github/workflows/
wrangler.jsonc apps/gittensory-ui/`; `blockedPaths`: `site/ CNAME **/lovable/**`; `linkedIssuePolicy: preferred`;
`testExpectations: npm run test:ci`). But the **modes are inputs to the disposition above** — the
engine still auto-merges the clean case and auto-closes the adverse case. The MCP `predict_gate` uses
the public config + safe defaults; a clean prediction is necessary but not sufficient (it can't see
Expand Down
27 changes: 26 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
backend: ${{ steps.filter.outputs.backend }}
ui: ${{ steps.filter.outputs.ui }}
mcp: ${{ steps.filter.outputs.mcp }}
rees: ${{ steps.filter.outputs.rees }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand Down Expand Up @@ -65,6 +66,9 @@ jobs:
- 'packages/gittensory-mcp/**'
- 'scripts/check-mcp-package.mjs'
- 'package-lock.json'
rees:
- 'review-enrichment/**'
- '.github/workflows/ci.yml'

# Fast-failing checks first: workflow lint + typecheck.
lint:
Expand Down Expand Up @@ -305,6 +309,27 @@ jobs:
- name: MCP package check
run: npm run test:mcp-pack

# Standalone Railway review-enrichment service.
rees:
name: rees
needs: changes
if: ${{ github.event_name == 'push' || needs.changes.outputs.rees == 'true' }}
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) && 'ubuntu-latest' || 'self-hosted' }}
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: review-enrichment/package-lock.json
- name: REES build, source-map validation, and tests
run: npm run rees:test

# UI checks: each split into its own step for legible failures.
ui:
name: ui
Expand Down Expand Up @@ -373,7 +398,7 @@ jobs:
# Path-filtered jobs report "skipped", which is treated as success.
validate:
name: validate
needs: [changes, lint, test, coverage-upload, workers, mcp, ui, security]
needs: [changes, lint, test, coverage-upload, workers, mcp, rees, ui, security]
if: ${{ always() }}
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) && 'ubuntu-latest' || 'self-hosted' }}
timeout-minutes: 2
Expand Down
1 change: 1 addition & 0 deletions .gittensory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ wantedPaths:
- test/
- migrations/
- scripts/
- review-enrichment/
- .github/workflows/
- wrangler.jsonc
- apps/gittensory-ui/
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"drizzle:generate": "drizzle-kit generate",
"build:mcp": "npm --workspace @jsonbored/gittensory-mcp run build",
"test:mcp-pack": "node scripts/check-mcp-package.mjs",
"rees:install": "npm ci --prefix review-enrichment --prefer-offline --no-audit --no-fund",
"rees:test": "npm run rees:install && npm --prefix review-enrichment test",
"rees:validate-sourcemaps": "npm --prefix review-enrichment run validate:sourcemaps",
"db:migrations:check": "node scripts/check-migrations.mjs",
"actionlint": "node scripts/actionlint.mjs",
"ui:dev": "npm run ui:preview",
Expand Down Expand Up @@ -53,7 +56,7 @@
"test:smoke:production": "node scripts/smoke-production.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci && npm run changelog:check:mcp",
"test:watch": "vitest",
Expand Down
12 changes: 7 additions & 5 deletions review-enrichment/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build && npm prune --omit=dev
COPY scripts ./scripts
RUN npm run build && npm run validate:sourcemaps && npm prune --omit=dev

FROM node:22-slim AS runtime
# Least privilege: run as a non-root user.
RUN useradd --create-home --uid 10001 rees
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
COPY --from=build --chown=rees:rees /app/dist ./dist
COPY --from=build --chown=rees:rees /app/package.json ./package.json
USER rees
ENV PORT=8080
EXPOSE 8080
# Provide at runtime (NOT baked into the image): REES_SHARED_SECRET (shared bearer with the engine).
CMD ["node", "dist/server.js"]
# Provide at runtime (NOT baked into the image): REES_SHARED_SECRET (shared bearer with the engine),
# plus optional Sentry vars. The uploader no-ops unless Sentry auth/org/project/release are configured.
CMD ["sh", "-c", "node dist/upload-sourcemaps.js && find dist -type f -name '*.map' -delete && node dist/server.js"]
42 changes: 42 additions & 0 deletions review-enrichment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,45 @@ Separate service from the engine. Set **Root Directory = `review-enrichment`** s
`railway.json` + `Dockerfile`. Set `REES_SHARED_SECRET` (same value the engine holds) as a service variable — never
commit it. The engine reaches the service over Railway **private networking** (`<service>.railway.internal`); no public
domain is required.

## Sentry releases and source maps

REES supports optional Sentry error reporting and source-map upload for Railway deployments. The Docker image builds
`dist/*.js.map` with embedded `sourcesContent`, then the runtime startup command injects Sentry debug ids, uploads the
exact post-injection `dist/` files, records a deploy, removes source maps from the running filesystem, and starts
`dist/server.js`.

Set these Railway service variables:

| Variable | Purpose |
| --------------------------- | ----------------------------------------------------------------------- |
| `SENTRY_DSN` | Enables REES error capture. Unset means the SDK is a no-op. |
| `SENTRY_AUTH_TOKEN` | Allows the runtime uploader to create releases and upload source maps. |
| `SENTRY_ORG` | Sentry organization slug. |
| `SENTRY_PROJECT` | Sentry project slug. |
| `SENTRY_ENVIRONMENT` | Optional; defaults to Railway's environment name, then `production`. |
| `SENTRY_TRACES_SAMPLE_RATE` | Optional; defaults to `0`, so errors report without tracing. |
| `SENTRY_RELEASE` | Optional override. Only set it when that exact REES bundle is uploaded. |
| `SENTRY_REPOSITORY` | Optional; defaults to `JSONbored/gittensory` for commit association. |
| `REES_SENTRY_UPLOAD_STRICT` | Optional. Set `true` to fail startup if source-map upload fails. |

By default the release id is `gittensory-rees@<RAILWAY_GIT_COMMIT_SHA>`, using Railway's Git metadata. The Sentry
GitHub code mapping should be:

| Sentry field | Value |
| ----------------- | ------------------- |
| Stack Trace Root | `/app` |
| Source Code Root | `review-enrichment` |
| Branch | `main` |

Do **not** pass `SENTRY_AUTH_TOKEN` as a Docker build arg. Railway deploys this service from Git, and Docker build args
can leak through image metadata. Keeping the upload at runtime means Sentry sees the same `dist/` files that the service
executes, without exposing source maps over HTTP.

If Sentry still shows frames such as `/app/dist/server.js`, check:

1. The event's `release` is `gittensory-rees@<same Railway commit sha>` or your exact `SENTRY_RELEASE` override.
2. The Sentry release has an artifact bundle uploaded for the REES project.
3. Railway has `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, and `SENTRY_PROJECT` set on the REES service.
4. The Sentry code mapping is `/app` → `review-enrichment` on branch `main`.
5. `npm --prefix review-enrichment run validate:sourcemaps` passes locally.
Loading
Loading