Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d52b918
api: don't persist a Gemini parse failure as five 0/10 observations
Sep 30, 2026
8d2d7e6
api: stop 500ing on non-numeric limits and malformed job ids
Sep 30, 2026
789e0b0
api: reject oversize wiki insights with a 400 instead of a raw 500
Sep 30, 2026
3377e63
api: cap prompt text sent to Gemini at 64K characters
Sep 30, 2026
b3546d2
api: stop sending the raw team token to Langfuse
Sep 30, 2026
0e3e0f9
deps: npm audit fix (non-breaking) — next, hono, ws, protobufjs, shar…
Sep 30, 2026
22c3503
docs: fix the MCP install steps, document the token trust model
Sep 30, 2026
c12fc13
docs: drop stale Railway/Claude-model references in sub-READMEs
Sep 30, 2026
bdcb986
api: server-minted team secrets; team id is no longer the credential
Sep 30, 2026
fa1a9da
mcp: init registers or joins a team; normalised team ids; secret out …
Sep 30, 2026
f04b712
clients: treat the team token as a secret; dashboard stops exposing it
Sep 30, 2026
a13c102
docs+compose: document team secrets, the join flow and legacy upgrade
Sep 30, 2026
702cfe7
api: Postgres integration tests — every route, two tenants, in CI
Sep 30, 2026
242a51f
clients: per-install anonymous user id instead of a shared 'demo' (op…
Sep 30, 2026
3cf5baa
fence team-authored content wherever it reaches an LLM
Sep 30, 2026
a844abb
api: stricter library promotion — unrounded mean, dimension floor, se…
Sep 30, 2026
23d5c51
chore: normalise package.json line endings (CR-CR-LF → LF)
Sep 30, 2026
5b8a84c
tooling: ESLint flat config + lint/audit/docker CI jobs; Next 16; 0 a…
Sep 30, 2026
5326098
api: scoring eval harness — golden prompts, N runs, spread report
Sep 30, 2026
6dc8879
mcp: rich bootstrap no longer uploads gitignored files
Sep 30, 2026
66ea7e3
api: 500s return a request id, not the raw error
Sep 30, 2026
ef9c0f3
browser-ext: flag the public demo team in the popup; never paint a se…
Sep 30, 2026
8a144fa
api: in-process rate limits (429 + Retry-After) and request body caps
Sep 30, 2026
288272c
api: survive idle-connection drops, recover orphaned jobs, drain on S…
Sep 30, 2026
4d35274
clients: handle 429 — coaching fails open and says why
Sep 30, 2026
9e6d8eb
test(api): integration suite must never reach the real Gemini API
Sep 30, 2026
ff5903f
api: drop cached team context on wiki writes; remove raw NUL bytes
Sep 30, 2026
80aa5fa
review: fence also neutralises lookalike team_content tags
Sep 30, 2026
3d59d58
review: rate limiter keys on the proxy-appended XFF entry, IPv6 per /…
Sep 30, 2026
723ac3d
review: a teammate's init after a legacy upgrade joins, not forks; su…
Sep 30, 2026
825cf7d
review: no literal invisible characters in the fence source and tests
Sep 30, 2026
5e6c4d0
review: browser extension makes every API call from its service worke…
Sep 30, 2026
91c0cbb
review: coach output fences a teammate's library example as untrusted…
Sep 30, 2026
587513c
review: rate limits are all-or-none per request, and env limits are p…
Sep 30, 2026
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
72 changes: 57 additions & 15 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,58 @@ POSTGRES_PORT=5432
# client's API URL too (see SELFHOSTING.md).
PORT=3000

# Any unrecognised X-Team-Token spawns its own team row. The right default for
# a single-tenant self-host — teammates cloning the repo land in the same team
# with no admin step. Set to false to require teams be registered explicitly.
TRAILHEAD_AUTO_CREATE_TEAMS=true
# Team credentials — see SELFHOSTING.md → "Security model". Each repo's team
# is registered by the MCP CLI's `init` (POST /teams) and gets a server-minted
# secret stored in the repo's gitignored .trailhead-team file.
#
# Accept pre-2026-09-30 tokens (repo_…, derived from the git remote URL) for
# teams that have not been upgraded. Deprecated; set false once every team has
# run `init --upgrade-legacy`.
TRAILHEAD_ACCEPT_LEGACY_TOKENS=true
# With legacy tokens accepted, an unknown X-Team-Token creates a legacy team.
# Unauthenticated tenant creation — leave false unless this is a throwaway demo.
TRAILHEAD_AUTO_CREATE_TEAMS=false
# When set, registering a team (POST /teams) requires this value in
# X-Admin-Token (`init --admin-token`). Leave empty for open registration while
# the API is bound to 127.0.0.1; set it before exposing the API on a network.
TRAILHEAD_ADMIN_TOKEN=

# How /coach prompts join the team library. Both modes require the exact
# average >= 7.0, no dimension < 5, and an independent re-score that agrees.
# auto — then they join automatically (default)
# review — then they wait for a teammate: GET /prompts/pending,
# POST /prompts/:id/review {"approve": true|false}
TRAILHEAD_PROMOTION_MODE=auto

# Scorer sampling overrides (defaults: 0.2 and -1 = dynamic thinking). Scores
# vary run to run; measure a change with the eval harness first
# (apps/api/eval/README.md) rather than setting these blind.
# TRAILHEAD_SCORE_TEMPERATURE=0.2
# TRAILHEAD_SCORE_THINKING_BUDGET=-1

# Rate limits (in-process token buckets; "N/W" with s|m|h, or "off"). Over the
# limit the API returns 429 + Retry-After and clients send the prompt uncoached.
# Per replica — see SELFHOSTING.md → Security model → Rate limits.
# TRAILHEAD_RL_REGISTER_PER_IP=10/1h
# TRAILHEAD_RL_LLM_PER_TEAM=120/1m
# TRAILHEAD_RL_LLM_PER_IP=120/1m
# TRAILHEAD_RL_BOOTSTRAP_PER_TEAM=6/1h
# TRAILHEAD_RATE_LIMIT=on
# Behind your own reverse proxy: key per-IP limits on X-Forwarded-For.
TRAILHEAD_TRUST_PROXY=false

# 500 responses carry only a request_id that matches the server log. Set true
# while debugging locally to also return the raw error message.
TRAILHEAD_EXPOSE_ERRORS=false

# Safety catch on DELETE /team/data for the seeded demo team. Set true only if
# you really want `trailhead-mcp reset` to be able to wipe it.
TRAILHEAD_ALLOW_DEMO_RESET=false

# --- Team token --------------------------------------------------------------
# The demo team's token, and the fallback the clients ship with. Real teams get
# a token derived from their git remote by `npx trailhead-mcp init` — this is
# only the demo/seed value.
# --- Demo team -----------------------------------------------------------------
# The seeded demo team's public secret, and the fallback the clients ship with.
# Documentation only — the API does not read it. Real teams get their own
# secret from the MCP CLI's `init` (node apps/mcp-server/bin/cli.mjs init).
TEAM_TOKEN=trailhead_demo_acme_2026

# --- Langfuse (optional) -----------------------------------------------------
Expand All @@ -74,15 +113,18 @@ LANGFUSE_BASEURL=https://cloud.langfuse.com
# =============================================================================

# --- MCP server (apps/mcp-server) --------------------------------------------
# Base URL of your API. `npx trailhead-mcp init` writes this into the generated
# Base URL of your API. The MCP CLI's `init` writes this into the generated
# MCP config. Unset -> the CLIs warn and fall back to http://localhost:3000.
# TRAILHEAD_API_URL=http://localhost:3000
# Per-repo team token, normally auto-derived from the git remote.
# Team secret. Normally read from the repo's .trailhead-team (written by init);
# set this only to override it.
# TRAILHEAD_TEAM_TOKEN=

# --- Dashboard (apps/dashboard) ----------------------------------------------
# Baked in at build time (NEXT_PUBLIC_*), so a deployed dashboard must set it
# before `next build`. Unset -> http://localhost:3000, and the Teams page says
# so explicitly rather than failing silently.
# NEXT_PUBLIC_API_URL=http://localhost:3000
# NEXT_PUBLIC_TEAM_TOKEN=trailhead_demo_acme_2026
# Server-side, read at runtime; the dashboard never sends the secret to the
# browser (client components go through its read-only /api/trailhead proxy).
# Unset -> http://localhost:3000 and the public demo team. The old
# NEXT_PUBLIC_API_URL / NEXT_PUBLIC_TEAM_TOKEN names still work as fallbacks
# (deprecated: NEXT_PUBLIC_* values can be inlined into client bundles).
# TRAILHEAD_API_URL=http://localhost:3000
# TRAILHEAD_TEAM_TOKEN=trailhead_sk_...
79 changes: 79 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,82 @@ jobs:
# without showing red here first. Builds the same way Vercel does.
- name: build dashboard
run: npm --workspace=apps/dashboard run build

lint:
name: eslint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: install
run: npm ci
# Flat config at the repo root (eslint.config.mjs): typescript-eslint
# everywhere, eslint-config-next for apps/dashboard.
- name: lint
run: npm run lint

audit:
name: npm audit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
# Reads package-lock.json against the advisory DB; no install needed.
# Fails on any high/critical advisory in the full tree (dev included:
# the extensions ship what their build tools produce).
- name: audit
run: npm audit --audit-level=high

docker:
name: docker image builds
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
# The self-hosting quick start is `docker compose up`; make sure the
# compose file still parses and the API image still builds from it.
- name: compose config
run: GEMINI_API_KEY=ci-placeholder docker compose config --quiet
- name: build api image
run: docker build -f apps/api/Dockerfile -t trailhead-api:ci .

integration:
name: api integration tests (Postgres)
runs-on: ubuntu-latest
timeout-minutes: 10
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: trailhead
POSTGRES_PASSWORD: trailhead
# The suite wipes its database and refuses names not ending in _it/_test.
POSTGRES_DB: trailhead_it
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U trailhead -d trailhead_it"
--health-interval 5s
--health-timeout 5s
--health-retries 12
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
- name: install
run: npm ci
# Every route, two tenants, real Postgres; Gemini is stubbed in-process,
# so no API key or network is involved.
- name: integration tests
env:
TRAILHEAD_IT_DATABASE_URL: postgresql://trailhead:trailhead@localhost:5432/trailhead_it
run: npm --workspace=apps/api run test:integration
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ apps/browser-ext/build/

# VS Code extension build artifacts
apps/vscode-ext/*.vsix
apps/api/eval/results/
Loading
Loading