Some checks failed
tests / Unit tests (Node 22) (push) Failing after 2m13s
Expands the unit test suite from 320 to 380 tests (+60) and adds a
Gitea Actions CI workflow. Closes all six follow-up recommendations
from the test-architecture validation report.
New tests (tests/unit/):
- ollamaClient.test.ts — Ollama SDK wrapper, options passthrough
- litellmClient.test.ts — OpenAI SDK wrapper, model fallback
- personaLoader.test.ts — Zod validation + cache invalidation
- foundryReward.test.ts — Tool plugin: lookup, errors, partial grants
- xpAwarder.test.ts — Bulk XP awards + per-player skip reasons
- redisErrorPath.test.ts — Singleton error handler does not crash
- messageRouterRunLLMTurn.test.ts — 18 cases for the runtime heart:
narrative-only path, tool dispatch, filter correction, retry loop
guard, missed-skill-check heuristic, typing indicator interval,
LLM error fallback, archive on resolve.
Coverage (line %):
- harness/litellmClient.ts 0 → 100
- harness/ollamaClient.ts 0 → 100
- harness/tools/foundryReward.ts 0 → 100
- session/xpAwarder.ts 0 → 100
- persona/loader.ts 0 → 100
- db/redis.ts 0 → 100
- bot/handlers/messageRouter.ts 0 → 39.86 (runLLMTurn now covered)
Tooling:
- package.json: + test:coverage, test:watch scripts
- devDep: @vitest/coverage-v8@^3.1.0
- tests/README.md: conventions, anti-patterns, template map
- .gitignore: exclude coverage/
- .gitea/workflows/test.yml: Node 22, npm cache, tsc --noEmit gate
Documentation (from earlier /bmad-document-project run, now committed):
- docs/index.md
- docs/project-overview.md
- docs/architecture.md
- docs/deployment-guide.md
- docs/api-contracts.md
- docs/data-models.md
- docs/source-tree-analysis.md
- docs/component-inventory.md
- docs/development-guide.md
- _bmad-output/test-artifacts/automate-validation-report.md
Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
10 KiB
Source Tree Analysis
Annotated directory tree for the Mardonar Encounter Engine. Generated 2026-06-19.
Top level
mardonar-npcs/
├── src/ # TypeScript source (compiled to dist/)
├── specs/ # Encounter YAML files (loaded by /encounter start)
├── tests/ # Vitest unit + integration suites
├── Docs/ # Pre-existing project documentation (encounter engine overview, build plan, epics, stories, UX designs)
├── lore/ # Game-world reference material
├── data/ # Runtime tally + per-encounter summaries (volume-mounted in prod)
├── scripts/ # Top-level utility scripts (only deploy-commands.ts lives here; the rest are under src/scripts)
├── docs/ # Generated by bmad-document-project (this folder)
├── node_modules/ # npm dependencies (gitignored)
├── dist/ # tsc output (gitignored)
├── Dockerfile # Multi-stage Node 22 alpine build
├── docker-compose.dev.yml # Local Redis + Neo4j orchestration
├── package.json
├── tsconfig.json # NodeNext ESM, strict, rootDir=src
├── vitest.config.ts # v8 coverage
├── .env / .env.example # Zod-validated env config
├── persona.yaml # @Zalram Cloudwalker persona
├── prd.md # Active PRD: Dynamic Goal Registration
└── README.md
src/ — TypeScript source
src/bot/ — Discord I/O layer
| Path | Role |
|---|---|
index.ts |
Entry point. Wires the discord.js Client, registers slash commands, dispatches interactionCreate and messageCreate to handlers. |
commands/ |
One file per slash command. Each exports data (SlashCommandBuilder) and execute(interaction, client). |
commands/dndname.ts |
/dndname set|show|clear — character name registration. |
commands/encounter.ts |
/encounter start|status|end|generate|spec|random|stats|audit — encounter session lifecycle. |
commands/character.ts |
/character register|show|view|admin — character profile + Foundry link modals. |
commands/roll.ts |
/roll — manual dice roll. |
commands/actions.ts |
/actions — in-character action shortcuts. |
commands/xp.ts |
/xp award — XP grant to a character. |
commands/encounters.ts |
/encounters — search/list encounters via GraphMCP. Includes select menu + search modal interactions. |
commands/turn.ts |
/turn — turn management. |
embeds/ |
Discord embed builders. Pure functions taking typed args. |
embeds/playerGate.ts |
"Please register your character name" embed. |
embeds/skillCheck.ts |
Suspense embed → dice embed with roll buttons. |
embeds/resolution.ts |
Encounter complete embed. |
embeds/encounterDiscovery.ts |
Encounter search result embeds. |
embeds/loreAnswer.ts |
@mention lore response embed. |
handlers/ |
Event handlers and sidecar logic. The runtime heart of the bot. |
handlers/messageRouter.ts |
Core encounter-thread message pipeline: gates, debounce, LLM call, tool dispatch. |
handlers/mentionHandler.ts |
@Zalram persona replies (uses persona/loader.ts). |
handlers/rollHandler.ts |
Button + modal submit skill-check roll resolution. |
handlers/generationQueue.ts |
Debounce + LLM turn scheduling (500ms coalesce). |
handlers/queueCap.ts |
Burst cap: drops messages if too many arrived before the last LLM response. |
handlers/reactionManager.ts |
👀 reaction lifecycle: scheduled → processing → complete. |
handlers/responseFilter.ts |
Post-LLM response scrubbing (catches fabricated rolls, echoed system tags, empty responses). |
lib/welcomeDM.ts |
Welcome DM utility. |
src/harness/ — LLM orchestration
| Path | Role |
|---|---|
promptBuilder.ts |
System prompt assembly. XML-sectioned: narrator, tone, sportsmanship, NPCs, players, setting, resolved context, skill checks, hidden goals, tool contract. |
contextAssembler.ts |
Builds the LLM message list: system + pinned history + trimmed sliding history. |
llmClient.ts |
Entry point. Routes to LiteLLM (primary) with Ollama fallback. |
litellmClient.ts |
OpenAI-compatible HTTP client for LiteLLM proxy. |
ollamaClient.ts |
Native ollama npm + direct HTTP fallback path. |
toolParser.ts |
Extracts tool_call blocks from LLM response. Three fallback patterns. |
toolRegistry.ts |
Plugin registry. getActiveTools(spec.tools) returns per-encounter active set. |
toolDispatcher.ts |
Validates tool name against active set, dispatches to plugin handler, logs result. |
tools/ |
Tool plugin implementations. Each module calls registerTool() at load. |
tools/index.ts |
Side-effect imports — add new tool files here. |
tools/skillCheckEmit.ts |
Posts dice-roll embed; blocks input until resolved. Pulls player modifier from Foundry. |
tools/encounterResolve.ts |
Marks encounter complete, writes summary, archives thread. |
tools/contextRecall.ts |
Retrieves canonical session facts from resolvedContext. |
tools/goalRegister.ts |
Adds new goals mid-encounter (per prd.md). |
tools/foundryLookup.ts |
Live character data from VTT relay. |
tools/foundryReward.ts |
XP / item grant to a character via VTT. |
src/session/ — Redis-backed state
| Path | Role |
|---|---|
playerRegistry.ts |
(guildId, discordId) → Player (DnD name). |
characterRegistry.ts |
Character profile: DnD name, pronouns, characterClass, race, level, backstory, Foundry actor UUID. |
sessionManager.ts |
threadId → SessionState. Pinned + sliding history trim by token budget. |
encounterLog.ts |
Filesystem tally + summary writer (one .txt per encounter in data/summaries/). |
xpAwarder.ts |
XP grant via VTT relay. |
src/graphmcp/ — GraphMCP JSON-RPC client
| Path | Role |
|---|---|
client.ts |
6 RPC calls + NPC memory formatter. |
ingest.ts |
Publishes encounter messages to Redis stream raw.messages. |
loreResolver.ts |
/encounter generate helper. |
vocabularyResolver.ts |
Resolves spec randomizable: entries (vocabulary or graphmcp source). |
src/vtt/ — Foundry VTT integration
| Path | Role |
|---|---|
foundryClient.ts |
HTTP client. Live actor data + formatters. |
relaySession.ts |
RSA-OAEP encrypted handshake + headless Foundry session spin-up when relay is down. |
Other src/ modules
| Path | Role |
|---|---|
db/redis.ts |
ioredis singleton (lazyConnect, maxRetriesPerRequest: 3). |
spec/loader.ts |
YAML loader + Zod schema (EncounterSpecSchema). |
persona/loader.ts |
persona.yaml loader for @mention. |
lib/logger.ts |
pino wrapper. |
config.ts |
Zod env schema + parsed config singleton. |
scripts/deploy-commands.ts |
Slash command registration via Discord REST v10. |
types/index.ts |
Shared interfaces + CONTEXT_BUDGET constant. |
specs/ — Encounter YAML files
Loaded by /encounter start <spec-name>. specs/SPEC_FORMAT.md documents the schema. Current set:
market-thief.yaml— the original "low-stakes warm-up" example used in the READMEcog-claw-debt.yamlmawfang-pursuit.yamlsilt-leak.yamlstormscar-pilgrim.yamlvelvet-auction.yamlwhispering-stone.yaml
Each spec declares: encounterId, title, tone, setting, openingNarrative, npcs[] (with optional nameKey and memoryKey), goals (primary + secondary), sportsmanshipRules, skillChecks (grouped by suffix _dc/_skill/_note), randomizable[] (vocabulary or graphmcp queries with fallbacks), tools[], and optional dmNotes.
tests/ — Vitest suites
tests/
├── fixtures/spec.ts # shared spec fixture
├── unit/ # 21 unit test files (no external services)
│ ├── promptBuilder.test.ts
│ ├── contextAssembler.test.ts
│ ├── toolParser.test.ts
│ ├── toolDispatcher.test.ts
│ ├── sessionManager.test.ts
│ ├── playerRegistry.test.ts
│ ├── characterRegistry.test.ts
│ ├── specLoader.test.ts
│ ├── rollHandler.test.ts
│ ├── rollDetection.test.ts
│ ├── responseFilter.test.ts
│ ├── queueCap.test.ts
│ ├── generationQueue.test.ts
│ ├── reactionManager.test.ts
│ ├── encounterLog.test.ts
│ ├── encounterDiscoveryEmbed.test.ts
│ ├── loreAnswerEmbed.test.ts
│ ├── skillCheckEmbed.test.ts
│ ├── graphmcpClient.test.ts
│ ├── foundryClientRetry.test.ts
│ ├── foundryClientFormatters.test.ts
│ ├── goalRegister.test.ts
│ └── relaySession.test.ts
└── integration/
└── phase1.test.ts # requires running Docker services
Docs/ — Pre-existing project documentation (historical)
| Path | Role |
|---|---|
mardonar-encounter-engine.md |
Out of date — describes a Go bot with embedded MCP layer. Treat as historical. The current docs/architecture.md supersedes it. |
mardonar-build-plan.md |
Phased build plan with packages and test guidance. |
epics.md |
Epic list. |
stories/ |
Story specs (1.1, 1.2, 2.1, 3.1, 4.1). |
ux-designs/ux-mardonar-2026-05-30/ |
UX session artifacts: EXPERIENCE.md, DESIGN.md, .decision-log.md. |
Critical entry points
| What you want to change | Start here |
|---|---|
| Add a slash command | src/bot/commands/, then src/scripts/deploy-commands.ts, then run npm run deploy-commands |
| Add a tool the LLM can call | src/harness/tools/<name>.ts, register in src/harness/tools/index.ts |
| Change system prompt structure | src/harness/promptBuilder.ts |
| Change context window budget | src/types/index.ts → CONTEXT_BUDGET |
| Add an encounter | specs/<name>.yaml (see specs/SPEC_FORMAT.md) |
| Change message pipeline | src/bot/handlers/messageRouter.ts |
| Change LLM client | src/harness/llmClient.ts (router), litellmClient.ts / ollamaClient.ts (implementations) |
| Add a Foundry VTT feature | src/vtt/, then add a tool in src/harness/tools/ |
| Add a GraphMCP-backed feature | src/graphmcp/client.ts, then add a tool |