Files
zalbot/docs/source-tree-analysis.md
Kaysser Kayyali e2c92e854f
Some checks failed
tests / Unit tests (Node 22) (push) Failing after 2m13s
Add unit tests for LLM clients, persona loader, and XP/Foundry rewards
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>
2026-06-19 05:59:13 +00:00

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 README
  • cog-claw-debt.yaml
  • mawfang-pursuit.yaml
  • silt-leak.yaml
  • stormscar-pilgrim.yaml
  • velvet-auction.yaml
  • whispering-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.tsCONTEXT_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