- Do not install, enable, or recommend the Runlayer Cursor marketplace plugin (MCP governance hooks block browser/MCP tools without tenant login).
- Prefer taking full initiative: run commands, start services, and verify behavior in this environment instead of telling the user to perform manual setup or testing steps themselves; resolve technical and architecture choices from repo evidence rather than asking the user to pick options.
- Default Trask synthesis to free LLM providers (
TRASK_LLM_PROFILE=free,openrouter/free); do not assumeOPENAI_API_KEY, local LLM, or paid models unless explicitly requested. - After any change to Trask/Holocron runtime code (
packages/trask,packages/trask-http,apps/holocron-web,apps/trask-http-server,scripts/trask_web_research.py,infra/trask-indexer,infra/trask-retrieve-worker), rebuild and restart the live stack before browser or e2e validation — never assume a priortrask_live_stacksession is still running or serving new code.
docs/solutions/— documented fixes and tooling decisions (YAML frontmatter:module,tags,problem_type). Search before implementing or debugging in Trask, Holocron, Pazaak, or ingest paths.- PazaakWorld gameplay (authoritative matches, RPCs, realtime): local/dev uses Nakama (
infra/nakama,@openkotor/pazaak-nakamaruntime). Point the client withVITE_PAZAAK_BACKEND=nakama(or setVITE_NAKAMA_HOST) and keepVITE_LEGACY_HTTP_ORIGIN=http://localhost:4001when you still need the bot for OAuth token exchange and/api/trask/*. Older Cloudflare Worker + Durable Object paths remain in-repo for reference but are not the primary gameplay backend for the Nakama cutover. - PazaakWorld HTTP API failover (non-Nakama paths):
@openkotor/platformcreateBrowserApiClientwalks comma-separatedVITE_API_BASESin order and retries the next origin on network errors or 5xx (4xx does not hop).VITE_LEGACY_HTTP_ORIGINorigins are prepended (deduped) beforeVITE_API_BASES, so Pages can setVITE_LEGACY_HTTP_ORIGINto a public bot andPAZAAK_API_BASESto a Cloudflare Worker URL for bot-first → Worker fallback. Worker lives ininfra/pazaak-matchmaking-worker; CI runswrangler deploy --dry-runin.github/workflows/pazaak-matchmaking-worker.ymlwithout secrets; live deploy needsCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDrepository secrets. - PazaakWorld on GitHub Pages (static client only):
OpenKotOR/community-botsis published athttps://openkotor.github.io/community-bots/; CI and Vite should useBASE=/community-bots/(andimport.meta.env.BASE_URLin app code). Treat legacy/bots/org URLs as mismatched with the renamed repo unless a separate redirect orbotsPages repo exists. - For KotOR-authentic color theming in PazaakWorld (including fixing mismatched labels like “KotOR classic”), reference OpenKotOR ModSync’s K1 and TSL theme definitions rather than inventing standalone palettes.
- Trask citation modules (PR #33–#88): Root verify scripts use workspace package imports (
@openkotor/trask,trask-config,config,retrieval) — seedocs/solutions/tooling-decisions/trask-root-script-package-imports-2026-05-24.md.citation-markers.ts,research-answer-split.ts,query-anchor.ts,grounded-evidence.ts(compose),discord-reply-format.ts(brief embed). Offline preflight:pnpm trask:gate(onepnpm build, import smoke,trask:config-drift, full measure with skip-check, then:ci);pnpm holocron:e2e,pnpm verify:trask-cli, andpnpm verify:trask-discordruntrask:gatebefore live steps. CI offline after build:pnpm trask:gate:ci. Indexed import-smoke (no LLM/token):pnpm trask:verify-import-smoke:ci; package import smoke viapnpm trask:smoke-imports:ci(inside gate). Stackpnpm trask:smoke:stack-bootstrap. Live QA bootstrap:docs/solutions/tooling-decisions/trask-qa-stack-bootstrap-2026-05-24.md. composite_score floor 165. After golden/policy edits, gate already includes drift — standalonepnpm trask:config-driftremains for quick checks. Import smoke also gates AE3 stderr trace failure classes (scripts/lib/trask_research_trace_assert.mjs; grep patterns indocs/trask-ops.md). Seedocs/solutions/tooling-decisions/trask-citation-stack-closeout-2026-05-24.mdandtrask-citation-module-architecture-2026-05-24.md. - Trask / Holocron (RAG contract): See
docs/brainstorms/trask-self-hosted-research-pipeline-requirements.mdfor product policy. Pipeline: Crawl4AI → embed/Chroma on the indexer host (infra/trask-indexer,bash scripts/bootstrap_trask_indexer.sh) →POST /retrieveonly via Cloudflare Worker (infra/trask-retrieve-worker, Wrangler deploy +.github/workflows/trask-retrieve-worker.yml) →scripts/trask_web_research.py(passages JSON; no direct Chroma unlessTRASK_WEB_RESEARCH_LOCAL_CHROMA=1) → grounded compose in@openkotor/trask(TRASK_GROUNDED_COMPOSEon;TRASK_RESEARCH_COMPOSE_MODE=grounded; DDG off by default). UI:apps/holocron-web; API:apps/trask-http-server(/api/trask/*, port 4010). Local stack:bash scripts/trask_live_stack.sh(indexer 8790 + Worker 8787 + Holocron 4010). SetTRASK_INDEXER_BASE_URL=http://127.0.0.1:8787(Worker), not raw Chroma. Public:TRASK_API_BASE→infra/trask-worker(TRASK_BUILTIN_API=0) → livetrask-http-server; retrieve Worker URL in production secrets. Discord RAG:scripts/trask_discord_sync.py; bot sync whenTRASK_DISCORD_SYNC_INTERVAL_MS> 0. Research budget: default 30s global cap (TRASK_RESEARCH_BUDGET_MS); each provider attempt uses remaining budget so Cloudflare retrieve and extractive fallback still run. - Discord archive two-repo pipeline (DCE scrape ↔ Trask index): Recurring append-only export runs in the DiscordChatExporter fork (
~/Downloads/DiscordChatExporter, branchfeat/recurring-cli-scrape; GUI zip~/Downloads/DiscordChatExporter.linux-x64/). Trask Chroma indexing runs here viadata/trask/discord-export-targets.json+python scripts/trask_discord_sync.py(orbash scripts/trask_discord_sync_after_scrape.sh). Authoritative bridge runbook:docs/knowledgebase/50-execution/discordchat-exporter-trask-bridge-runbook.md. Layouts: indexer acceptsmanifest.json+containers/(export_discord_server.py) or DCE flat* [channel_id].jsonatoutput_dir. Targets stayenabled: falseuntil pilot enablement checklist passes. Decision record:docs/solutions/tooling-decisions/discordchat-exporter-trask-index-bridge-2026-06-04.md. - Workspace checkout may symlink to
/run/media/brunner56/MyBook/Workspaces/community-bots; prefer that path for authoritative reads/writes when both it and~/Workspaces/community-botsexist.
- Runtime: Node.js ≥24, pnpm 10.11.0 (via corepack)
- Monorepo: pnpm workspaces —
apps/*,packages/*,infra/* - Build:
pnpm build(runstsc -b tsconfig.workspace.json) - Type check:
pnpm check - Lint:
pnpm --filter pazaak-world lint(only pazaak-world has eslint config currently) - Tests:
pnpm test(shorthand fornode --test packages/*/dist/*.test.js apps/*/dist/*.test.js, Node.js built-in runner; build first).pnpm test:watchfor interactive watch mode.
After pnpm install, you must run pnpm rebuild esbuild once per clean install because esbuild's postinstall is blocked by the onlyBuiltDependencies allow-list in pnpm-workspace.yaml. Without the rebuild, the pazaak-nakama bundle step will fail (esbuild binary missing). This does not affect the main TypeScript workspace build or the Vite-based dev servers.
Standard scripts from package.json:
pnpm dev:pazaak-world— Vite dev server for the card game SPA (port 5173)pnpm dev:holocron-web— Vite dev server for the KOTOR knowledge base (port 5174 if 5173 is taken)pnpm dev:hk86-web— static Discord bots hub pagepnpm dev:trask-http— REST + static serve for holocron-webpnpm dev:pazaak— Discord Pazaak Bot (needsPAZAAK_DISCORD_BOT_TOKEN)pnpm dev:trask— Discord Trask Bot (needsTRASK_DISCORD_BOT_TOKEN)pnpm dev:hk— Discord HK Bot (needsHK_DISCORD_BOT_TOKEN)
- The pazaak-world app renders a dark/space-themed animated background on load; without a backend (bot or Nakama), the main menu card content may not render — this is expected when no API is reachable.
trask-httptests have 2 pre-existing flaky failures around temp-file rename race conditions under/tmp; these are not environment issues.- holocron-web uses TypeScript ~5.7 while the rest of the workspace uses ~6.0; Vite handles each independently.
- The
package-lock.jsonat root is legacy/stale; pnpm is the authoritative package manager.
Requirement: Do not claim Holocron search/research is working until you have fully verified it in a real browser — all five canonical research queries must complete with substantive answers and sources. Use Playwright (pnpm holocron:e2e) as the mandatory automated gate; use the Cursor browser MCP for an extra manual pass when it is available. A single happy-path click or CLI-only check is not sufficient.
Holocron e2e is functional only (no mocked /api/trask routes, no UI regression fixtures). Happy-path Playwright: holocron-research.spec.ts (five expert queries + reload; playwright.config.ts). Failure-path: holocron-research-failure.spec.ts via pnpm holocron:e2e:playwright:failure (playwright.failure.config.ts, unreachable indexer). When Cursor browser MCP is available, run the same five queries on :4010 in addition to Playwright — not instead of it.
- Ensure
trask-http-serverserves built Holocron on http://127.0.0.1:4010 (TRASK_WEB_ALLOW_ANONYMOUS=1). - Start the Worker retrieve path (not raw Chroma):
bash scripts/trask_live_stack.shor indexer on 8790 +pnpm --dir infra/trask-retrieve-worker devon 8787 withTRASK_INDEXER_BASE_URL=http://127.0.0.1:8787for Holocron/trask-http. Seed:bash scripts/bootstrap_trask_indexer.sh,bash scripts/trask_index_seed_for_qa.sh(CI does this before e2e). - Run the full Playwright suite (all five searches — not a subset):
pnpm exec playwright install chromium --with-deps # once per machine
pnpm holocron:e2e
# server already listening:
HOLOCRON_REUSE_SERVER=1 pnpm holocron:e2e- When the Cursor browser MCP is available, you must run all five canonical queries in the web UI (not a subset). Use a fresh
?thread=<uuid>per query so history does not bleed across tests. Workflow:browser_navigate→browser_lock→ fill Question input → Submit question (only after the button is enabled) → wait until Thinking clears and an assistant message plus citation links appear →browser_unlockwhen finished. Report pass/fail per query in your completion message. Playwright alone does not satisfy this step when browser MCP works. - If the task touches deployed/public Holocron behavior, also verify
https://openkotor.github.io/community-bots/qa-webui/?thread=<fresh-uuid>in a real browser after deploy (seedocs/knowledgebase/30-product-ux/holocron-web-trask-client.mdanddocs/knowledgebase/50-execution/validation-ladder.md§8). KeepTRASK_API_BASEon the Trask worker withTRASK_BUILTIN_API=0and a live research backend, then confirm at least one public query returns multiplehttps://sources before reporting success.
Pass criteria (each of the five queries): question input enabled → submit → user message visible → assistant answer substantive and on-topic → no stuck Thinking (≤ ~200s) → Sources panel or visible https:// citations (≥2) on approved hosts.
Canonical five queries — browser e2e uses expert phrasing from data/trask/eval/verification-queries.json (holocron-research.spec.ts); CLI/fixtures use data/trask/eval/golden-queries.json:
- What is TSLPatcher used for in KOTOR modding?
- How do I troubleshoot KOTOR widescreen resolution issues on PC?
- What is MDLOps used for in the KOTOR toolchain?
- Where are Knights of the Old Republic save files stored on Windows?
- What does the reone project provide for Odyssey engine work?
Do not reuse an “earlier” stack. Stale trask-http-server, Worker, indexer, Playwright, or Vite processes serve old JS/Python, cause ERR_CONNECTION_REFUSED, or point Holocron at the wrong API origin.
Trigger a full restart when you change anything in: packages/trask/**, packages/trask-http/**, packages/persistence/** (Trask query records), apps/holocron-web/**, apps/trask-http-server/**, scripts/trask_web_research.py, infra/trask-indexer/**, infra/trask-retrieve-worker/**, or after pnpm build that rebuilds those packages.
Standard path (indexer + Worker + Holocron on one command):
pnpm build # when TypeScript in trask / trask-http / trask-http-server changed
bash scripts/trask_live_stack.shtrask_live_stack.sh kills 4010 / 8787 / 8790, rebuilds Holocron static assets, starts Chroma indexer → retrieve Worker → trask-http-server with TRASK_INDEXER_BASE_URL=http://127.0.0.1:8787.
Verify before UI or browser MCP (agents must run this themselves):
curl -sf http://127.0.0.1:4010/ >/dev/null && echo "Holocron OK"
curl -sf http://127.0.0.1:8787/health
curl -sf http://127.0.0.1:8790/healthOpen http://127.0.0.1:4010 (same origin as /api/trask). Do not validate research on Vite :5174 alone unless VITE_TRASK_API_BASE points at :4010.
Playwright-only shortcut (e2e job already starts its own server; still restart if you edited Holocron/trask code in the same session):
fuser -k 4010/tcp 5173/tcp 5174/tcp 4174/tcp 7860/tcp 2>/dev/null || true
pkill -f 'holocron-e2e-live-server' 2>/dev/null || true
pkill -f 'trask-http-server' 2>/dev/null || true
pkill -f 'playwright.*holocron' 2>/dev/null || true
node scripts/holocron-e2e-live-build.mjs
TRASK_WEB_ALLOW_ANONYMOUS=1 bash scripts/holocron-e2e-live-server.shFor live research validation, prefer trask_live_stack.sh so the Worker retrieve path (8787) matches production; the Playwright shortcut does not start the Worker unless you add it separately.
| Variable | Purpose |
|---|---|
TRASK_WEB_ALLOW_ANONYMOUS=1 |
Anonymous Holocron sessions on standalone trask-http-server |
OPENROUTER_API_KEY / LITELLM_PROXY_URL / OPENCODE_LLM_PROXY_URL |
LLM synthesis — free default (TRASK_LLM_PROFILE=free, openrouter/free); paid via OPENAI_API_KEY or TRASK_LLM_PROFILE=paid (see docs/trask-research-backends.md) |
INGEST_STATE_DIR |
Defaults to data/ingest-worker for local knowledge chunks |
TRASK_WEB_RESEARCH_PYTHON |
Optional; defaults to .venv-trask-research/bin/python when present |
TRASK_INDEXER_BASE_URL |
Cloudflare retrieve Worker (http://127.0.0.1:8787 local dev); proxies to Chroma indexer on 8790. Do not point clients at Chroma directly. |
trask-http-server must use persistQueries: true for anonymous users so Holocron’s 202 + thread poll path completes (configured in apps/trask-http-server/src/main.ts).
- Both are required when validating Holocron changes and browser MCP is available: Playwright (
pnpm holocron:e2e) and all five queries via Cursor browser onhttp://127.0.0.1:4010. - Do not claim “browser works” or Holocron is done after Playwright or CLI only — the user expects explicit confirmation of all five UI queries when MCP is available.
- Lock order:
browser_navigate(or confirm tab on :4010) →browser_lock→ interactions →browser_unlock. - Do not treat Vite preview on :4174 with route mocks as validation of live research.
- For public outage recovery, do not stop at localhost or a local static preview; repair the public API origin and re-test the deployed Pages URL.
pnpm verify:trask-clipnpm verify:trask-cli runs scripts/verify_trask_cli_qa.mjs (golden queries). pnpm verify:trask-cli:ci / pnpm trask:verify-import-smoke:ci use --import-smoke (no LLM). pnpm verify:trask-discord runs live Discord-format checks via the research wizard.
Public Pages spot-check (post-deploy): pnpm holocron:public-gate — one Playwright query on https://openkotor.github.io/community-bots/qa-webui/; writes docs/evidence/holocron-public-pages-gate-latest.md (requires live VITE_TRASK_API_BASE / worker backend).
One-shot QA surfaces (Discord + Holocron Playwright + browser gate): pnpm trask:qa:surfaces — runs scripts/trask_qa_surfaces.sh (offline Discord harness on :4012, then live stack on :4010 for happy Playwright + browser gate, failure-path Playwright last). Optional public Pages spot-check: TRASK_QA_PUBLIC=1 pnpm trask:qa:surfaces. Live Discord bot: pnpm verify:trask-discord.
Live browser gate (stack on :4010): pnpm holocron:browser-gate — five expert queries via Playwright against an existing trask-http-server; writes docs/evidence/holocron-browser-gate-latest.md (no webServer boot).
Playwright (offline Discord + live Holocron): pnpm trask:e2e:discord:playwright — static harness on :4012 (scripts/discord-ask-e2e-webserver.mjs, e2e/trask-discord-ask.spec.mjs); mirrors import-smoke embed contract in a real browser (no discord.com, no LLM). pnpm trask:e2e:playwright runs Discord harness then pnpm holocron:e2e:playwright (six happy-path tests). pnpm holocron:e2e:playwright:failure — unreachable indexer on :4011 (playwright.failure.config.ts; live stack on :4010 can stay up). pnpm trask:e2e:playwright:full — Discord + Holocron happy + failure. pnpm trask:public-api:check — worker /healthz + ask smoke (TRASK_API_BASE required). CI runs Discord Playwright in a container job; Holocron job runs happy then failure specs.
After code changes to answer formatting, citation alignment, or grounded-evidence.ts, run:
pnpm trask:smoke-imports # build + workspace package import smoke
pnpm trask:smoke-imports:ci # smoke only (after build; TRASK_SKIP_BUILD=1)
pnpm trask:gate:ci # CI offline after build: smoke + config-drift + optimize-measure:ci
pnpm trask:gate # one build, smoke, config-drift, full measure (skip-check), :ci (floor 165)
pnpm trask:verify-import-smoke:ci # Discord + CLI import-smoke (needs indexer+Worker)
pnpm trask:optimize-measure # faithfulness + discord stress + all Trask citation unit suites + check
pnpm trask:optimize-measure:ci # CI-equivalent gate after build — see docs/solutions/tooling-decisions/trask-citation-module-architecture-2026-05-24.md
pnpm trask:faithfulness-eval # faithfulness fixtures onlytrask:faithfulness-eval replays committed golden fixtures under data/trask-eval/fixtures/ (no live web research). It does not replace Holocron e2e for end-to-end research validation. pnpm holocron:e2e runs pnpm trask:gate then test:e2e:all (happy-path + failure-path Playwright; webServer uses scripts/holocron-e2e-webserver.mjs, bootstraps indexer+Worker on happy path, HOLOCRON_E2E_FAILURE_MODE=1 on failure path). pnpm verify:trask-discord and pnpm verify:trask-cli preflight with trask:gate and auto-bootstrap the indexed stack before live queries. CI runs pnpm build, early pnpm trask:gate:ci, indexer+Worker bootstrap, pnpm trask:verify-import-smoke:ci, then pnpm holocron:e2e:playwright with TRASK_SKIP_BUILD=1 and HOLOCRON_E2E_SKIP_STACK_BOOTSTRAP=1, then a final pnpm trask:gate:ci. Product policy strings live under data/trask/; pnpm trask:gate includes trask:config-drift — use standalone pnpm trask:config-drift only for a quick drift-only check after edits.
Do not claim Discord /ask is fixed until live checks pass. Run pnpm verify:trask-discord (full expert set). Offline Playwright gate: pnpm trask:e2e:discord:playwright (five golden import-smoke embeds on :4012). Holocron requires pnpm holocron:e2e and, when browser MCP works, all five UI queries on :4010. CI runs offline Discord Playwright in Build; live verify:trask-discord still needs a bot token locally.
- Worker retrieve at
TRASK_INDEXER_BASE_URL=http://127.0.0.1:8787—pnpm verify:trask-discordauto-bootstraps indexer+Worker when unhealthy (same as CLI/Holocron e2e); manual path:bash scripts/trask_live_stack.shwith QA seedbash scripts/bootstrap_trask_indexer.sh,bash scripts/trask_index_seed_for_qa.sh. - Restart Trask bot after
@openkotor/traskchanges: kill oldtrask-bot/dist/main.js, rebuild (pnpm build), start withTRASK_INDEXER_BASE_URL+TRASK_WEB_RESEARCH_PYTHON+.envtoken. - Run the full Discord live gate (golden + expert queries — not a subset):
pnpm verify:trask-discord
# optional: post each passing embed to #discord-bot-testing for visual proof
pnpm verify:trask-discord:post- When Cursor browser MCP is available, open the user’s Discord tab (
#discord-bot-testing), run/askfor at least three diverse queries (tooling, technical, expert phrasing), and confirm embeds match pass criteria below. CLI/API-only proof is insufficient when browser works.
Pass criteria (each query): embed description ≤5 non-empty lines · on-topic only (no catalog dump of unrelated tools) · ≥2 distinct inline [n](https://…) citations (Discord jump links count when indexed) · no Sources heading or numbered source list in the embed · no Answer for: prefix · no duplicated topic label (TSLPatcher TSLPatcher). pnpm verify:trask-discord also HEAD-checks cited URLs unless --skip-url-check.
Research logging: Python scripts/trask_web_research.py logs to stderr via trask.research (TRASK_RESEARCH_LOG_LEVEL=INFO default, DEBUG or -v for verbose). Node forwards lines through setTraskResearchLogSink (wired in trask-bot); set TRASK_RESEARCH_LOG_VERBOSE=1 to surface DEBUG in bot logs.
Canonical queries include all goldenQueriesForSurface("discord") plus expert variants in scripts/verify_trask_discord_live.mjs. Contract: docs/knowledgebase/10-architecture-runtime/trask-discord-slash-contract.md.