Skip to content

fix(magic): normalize spell-database keys so kebab-case lookups resolve#53

Merged
Mnehmos merged 3 commits intomainfrom
fix/41-spell-catalog
Apr 20, 2026
Merged

fix(magic): normalize spell-database keys so kebab-case lookups resolve#53
Mnehmos merged 3 commits intomainfrom
fix/41-spell-catalog

Conversation

@Mnehmos
Copy link
Copy Markdown
Owner

@Mnehmos Mnehmos commented Apr 18, 2026

Summary

The spell catalog already contained Fire Bolt, Magic Missile, Sacred Flame, etc., but getSpell("fire-bolt") returned undefined. Root cause: the map was keyed by name.toLowerCase() ("fire bolt") and getSpell did the same normalization, so kebab-case ids never matched. Surfaced as Unknown spell: <name> for almost every common spell.

Fix

  • normalizeSpellKey collapses whitespace/hyphens/underscores and lowercases.
  • Each spell is registered under both its spaced name and its kebab-case id.
  • getSpell / spellExists use the same normalizer, so "Fire Bolt" / "fire-bolt" / "fire_bolt" / "FIREBOLT" / "firebolt" all resolve to the same record.

Test plan

  • New regression suite (tests/engine/magic/spell-database.test.ts) covers the eight spells the live test couldn't find.
  • spellcasting.test.ts: 71 passed, 3 skipped — no regressions.
  • Full suite: 1899 passed, 6 skipped, 0 failed.

Closes #41

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Spell lookups accept varied key formats (spacing, hyphens, underscores, casing) and resolve to the same spell.
    • Additional spells added to the database.
  • Bug Fixes

    • Spell listings and class/level queries return deduplicated results so each spell appears once.
    • Spell count now reflects unique spells only, preventing alias-inflated totals.
  • Tests

    • Added tests for key normalization, existence checks, iteration deduplication, and accurate spell count.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

This change canonicalizes spell lookup keys (lowercase, remove spaces/hyphens/underscores), indexes each spell under normalized name and id, makes lookups delimiter-insensitive, deduplicates iteration results, and updates SPELL_COUNT to reflect unique Spell objects.

Changes

Cohort / File(s) Summary
Spell lookup normalization
src/engine/magic/spell-database.ts
Added normalizeSpellKey(); registerSpell() indexes spells under normalized spell.name and spell.id; getSpell() and spellExists() perform normalized lookups; iteration helpers (getSpellsByLevel, getSpellsForClass) dedupe using unique Spell objects; SPELL_COUNT now counts unique spells.
Key normalization tests
tests/engine/magic/spell-database.test.ts
New Jest tests covering multiple key variants (case, kebab, spaces, concatenated, underscores) for getSpell()/spellExists(), negative lookup, SPELL_COUNT equals unique spells, and that getSpellsByLevel / getSpellsForClass return no duplicate spell ids.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hop through names both near and far,
I squash the dashes, spaces, every bar.
One true spell, two keys no more,
I count them once and then I soar. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses the core requirement from #41 (spell normalization for kebab-case lookups) and adds three requested spells (scorching-ray, healing-word, web), but does not fully complete the acceptance criteria. Add the remaining five spells (magic-missile, fire-bolt, eldritch-blast, sacred-flame, shield, hold-person, spiritual-weapon) and implement improved error messaging with available spells or search hints.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: implementing spell-database key normalization to enable kebab-case lookups, which directly addresses the root cause identified in issue #41.
Out of Scope Changes check ✅ Passed All changes directly support the normalization feature and issue #41 requirements; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/41-spell-catalog

Comment @coderabbitai help to get the list of available commands and usage tips.

Spells were registered under name.toLowerCase() (e.g. "fire bolt") while
callers passed kebab-case ids ("fire-bolt"). getSpell did the same
normalization on the read side, so every kebab-case lookup returned
undefined and surfaced as "Unknown spell" — even though the spell was
in the database.

- Introduce normalizeSpellKey: strips whitespace/hyphens/underscores and
  lowercases, accepting "Fire Bolt" / "fire-bolt" / "fire_bolt" /
  "FIREBOLT" / "firebolt" interchangeably.
- registerSpell indexes each spell under both the spaced name and the
  kebab-case id so any sensible call resolves.
- getSpell and spellExists use the same normalizer.
- SPELL_COUNT switches to deduped Set count.
- Add focused regression tests covering the live-test failure cases:
  fire-bolt, magic-missile, sacred-flame, eldritch-blast, cure-wounds,
  hold-person, spiritual-weapon, bless.

Closes #41

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/engine/magic/spell-database.ts (1)

395-413: ⚠️ Potential issue | 🟡 Minor

Haste is level 3, not level 5.

The spell is placed under the "5TH LEVEL SPELLS" section comment but level: 3 is correct per D&D 5e SRD. Either move this spell registration to the 3rd-level section (after Counterspell) or update the section comment. Same applies to Fly immediately below.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/magic/spell-database.ts` around lines 395 - 413, The Haste
registration (registerSpell with id: 'haste') is correctly set to level: 3 but
resides under a "5TH LEVEL SPELLS" section; move the registerSpell block for id
'haste' (and likewise the subsequent Fly registration) into the 3rd-level spell
section (e.g., after Counterspell) or update the surrounding section comment to
accurately reflect 3rd-level spells so the section header matches the
spell.level values; ensure you update only the comment/position and not the
level field on the registerSpell entries.
🧹 Nitpick comments (1)
tests/engine/magic/spell-database.test.ts (1)

38-41: Consider adding edge-case tests for malformed input.

The unknown-spell test is good, but consider adding tests for edge cases like empty strings or whitespace-only input to ensure normalizeSpellKey handles them gracefully without throwing.

💡 Optional edge-case tests
it('handles empty and whitespace-only input gracefully', () => {
    expect(getSpell('')).toBeUndefined();
    expect(getSpell('   ')).toBeUndefined();
    expect(spellExists('')).toBe(false);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/engine/magic/spell-database.test.ts` around lines 38 - 41, Add tests
for malformed inputs (empty string and whitespace-only) to ensure getSpell and
spellExists behave gracefully and that normalizeSpellKey doesn't throw;
specifically, add assertions like expect(getSpell('')).toBeUndefined(),
expect(getSpell('   ')).toBeUndefined(), and
expect(spellExists('')).toBe(false). If normalizeSpellKey currently throws or
returns unexpected values for blank/whitespace input, update it to trim input
and handle empty result (e.g., return a safe normal form or undefined lookup) so
these new tests pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 29-30: SPELL_DATABASE is inserting each spell twice
(normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id)), causing
duplicates when iterating via SPELL_DATABASE.values(); update the iteration
helpers getSpellsByLevel and getSpellsForClass to dedupe results by tracking
seen spell IDs (e.g., create a local Set seen and skip any spell whose spell.id
is already in seen) before pushing into the results, ensuring SPELL_COUNT can
remain as-is and normalizeSpellKey/SPELL_DATABASE inserts need not be changed.

---

Outside diff comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 395-413: The Haste registration (registerSpell with id: 'haste')
is correctly set to level: 3 but resides under a "5TH LEVEL SPELLS" section;
move the registerSpell block for id 'haste' (and likewise the subsequent Fly
registration) into the 3rd-level spell section (e.g., after Counterspell) or
update the surrounding section comment to accurately reflect 3rd-level spells so
the section header matches the spell.level values; ensure you update only the
comment/position and not the level field on the registerSpell entries.

---

Nitpick comments:
In `@tests/engine/magic/spell-database.test.ts`:
- Around line 38-41: Add tests for malformed inputs (empty string and
whitespace-only) to ensure getSpell and spellExists behave gracefully and that
normalizeSpellKey doesn't throw; specifically, add assertions like
expect(getSpell('')).toBeUndefined(), expect(getSpell('   ')).toBeUndefined(),
and expect(spellExists('')).toBe(false). If normalizeSpellKey currently throws
or returns unexpected values for blank/whitespace input, update it to trim input
and handle empty result (e.g., return a safe normal form or undefined lookup) so
these new tests pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c63508e7-5907-4dd6-a18b-635007c01751

📥 Commits

Reviewing files that changed from the base of the PR and between 2d9ddb9 and 87db880.

📒 Files selected for processing (2)
  • src/engine/magic/spell-database.ts
  • tests/engine/magic/spell-database.test.ts

Comment thread src/engine/magic/spell-database.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87db8807d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

};
SPELL_DATABASE.set(spell.name.toLowerCase(), spell);
SPELL_DATABASE.set(normalizeSpellKey(spell.name), spell);
SPELL_DATABASE.set(normalizeSpellKey(spell.id), spell);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deduplicate results when adding secondary spell keys

Adding a second Map entry for each spell can cause duplicate results from getSpellsByLevel/getSpellsForClass, because both functions iterate SPELL_DATABASE.values() without deduplication. This appears when normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id) differ (for example, punctuation differences such as apostrophes), so one logical spell can be emitted multiple times and skew selection/count logic in callers.

Useful? React with 👍 / 👎.

Reviewers (PR #53) flagged that registering each spell under both
spell.name and spell.id keys causes SPELL_DATABASE.values() to yield
duplicates. getSpellsByLevel() and getSpellsForClass() iterated those
values directly, returning each spell twice.

Adds a uniqueSpells() helper that wraps the iteration in a Set, and
routes both helpers (and getCantrips by extension) through it.
Regression tests assert no duplicates in level/class queries.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/engine/magic/spell-database.ts (1)

29-30: Add collision protection when writing normalized keys.

SPELL_DATABASE.set(...) currently overwrites silently if two different spells normalize to the same key. Guarding this in registerSpell will prevent hard-to-debug data loss later.

Suggested hardening
 function registerSpell(input: SpellInput): void {
     const spell: Spell = {
         ...input,
         ritual: input.ritual ?? false
     };
-    SPELL_DATABASE.set(normalizeSpellKey(spell.name), spell);
-    SPELL_DATABASE.set(normalizeSpellKey(spell.id), spell);
+    const keys = [normalizeSpellKey(spell.name), normalizeSpellKey(spell.id)];
+    for (const key of keys) {
+        const existing = SPELL_DATABASE.get(key);
+        if (existing && existing.id !== spell.id) {
+            throw new Error(`Spell key collision for "${key}": "${existing.id}" vs "${spell.id}"`);
+        }
+        SPELL_DATABASE.set(key, spell);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/magic/spell-database.ts` around lines 29 - 30, SPELL_DATABASE.set
calls using normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id) can
silently overwrite an existing entry; modify the registerSpell logic so before
calling SPELL_DATABASE.set you check SPELL_DATABASE.has(key) and compare the
existing entry's id (or reference) to the incoming spell, and if they differ
either throw an error or log and skip the set to avoid collision; apply this
check for both normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id) and
include clear diagnostic text mentioning the colliding key and the two spell
ids.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 587-595: The spell lookup uses normalizeSpellKey for reads but the
database population is inserting keys that aren't normalized, so kebab-case IDs
like "scorching-ray", "healing-word", and "web" never get registered; fix the
database population to store normalized keys by calling normalizeSpellKey when
inserting into SPELL_DATABASE (wherever SPELL_DATABASE.set or the initial
registration/population occurs) so that SPELL_DATABASE keys match the
normalization used by get (normalizeSpellKey(name)) and spellExists(name).

---

Nitpick comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 29-30: SPELL_DATABASE.set calls using
normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id) can silently
overwrite an existing entry; modify the registerSpell logic so before calling
SPELL_DATABASE.set you check SPELL_DATABASE.has(key) and compare the existing
entry's id (or reference) to the incoming spell, and if they differ either throw
an error or log and skip the set to avoid collision; apply this check for both
normalizeSpellKey(spell.name) and normalizeSpellKey(spell.id) and include clear
diagnostic text mentioning the colliding key and the two spell ids.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8c9bdfe-bf9d-4134-b0f1-15029c9754fb

📥 Commits

Reviewing files that changed from the base of the PR and between 87db880 and c9ee31d.

📒 Files selected for processing (2)
  • src/engine/magic/spell-database.ts
  • tests/engine/magic/spell-database.test.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/engine/magic/spell-database.test.ts

Comment thread src/engine/magic/spell-database.ts
… list

Reviewers (PR #53) flagged that the issue-#41 acceptance criteria
listed 10 baseline spells and three were still missing:
- Scorching Ray (L2 evocation, sorcerer/wizard, 6d6 fire +2d6/upcast)
- Healing Word (L1 evocation, bard/cleric/druid, bonus action 1d4)
- Web (L2 conjuration, sorcerer/wizard, 20ft cube concentration restrain)

All three are now registered with the same dual-key normalization the
rest of the catalog uses. Lookup tests extended to cover them via both
canonical name and kebab-case id.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/engine/magic/spell-database.ts (1)

756-758: SPELL_COUNT can drift from the exported map.

This value is frozen at module load, but SPELL_DATABASE is exported as a mutable Map. Any later registration or test mutation makes the count stale. A helper like getSpellCount() would keep this invariant accurate.

Suggested direction
-// Export total unique-spell count for tests (each spell may be indexed under
-// multiple keys, so dedupe before counting).
-export const SPELL_COUNT = new Set(SPELL_DATABASE.values()).size;
+// Compute total unique-spell count on demand so it stays aligned with the map.
+export function getSpellCount(): number {
+    return uniqueSpells().length;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/magic/spell-database.ts` around lines 756 - 758, SPELL_COUNT is
computed once at module load and can drift because SPELL_DATABASE is a mutable
Map; replace the exported constant with a runtime getter (e.g., export function
getSpellCount() or an exported getter named SPELL_COUNT) that computes and
returns new Set(SPELL_DATABASE.values()).size on each call so the count always
reflects the current SPELL_DATABASE contents; update any tests/imports to call
the function/getter instead of using the frozen constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 242-313: Add cast-path tests in spellcasting.test.ts for the three
new spells: for 'healing-word' (id: healing-word) write a test that uses the
existing castSpell helper to target a creature and assert HP increases by 1d4 +
spellcasting modifier (and that upcasting (slot >1) increases healing by +1d4
per slot via the upcastBonus); for 'scorching-ray' (id: scorching-ray) write a
test that casts the spell and asserts it produces three separate attack/damage
instances of 2d6 fire each (and that upcasting grants extra ray(s) per
upcastBonus); and for 'web' (id: web) write a test that casts on an area and
asserts affected creatures must make a Dexterity save and failing results in the
RESTRAINED condition being applied (and concentration/duration behavior as
appropriate). Use the same test helpers and assertions used by existing spell
tests (e.g., castSpell, getSpellById, checking resulting HP/damage events and
conditions) to mirror style and sizing.

---

Nitpick comments:
In `@src/engine/magic/spell-database.ts`:
- Around line 756-758: SPELL_COUNT is computed once at module load and can drift
because SPELL_DATABASE is a mutable Map; replace the exported constant with a
runtime getter (e.g., export function getSpellCount() or an exported getter
named SPELL_COUNT) that computes and returns new
Set(SPELL_DATABASE.values()).size on each call so the count always reflects the
current SPELL_DATABASE contents; update any tests/imports to call the
function/getter instead of using the frozen constant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49459343-b1b9-47c6-847a-d4a91a2320d5

📥 Commits

Reviewing files that changed from the base of the PR and between c9ee31d and 41a3c95.

📒 Files selected for processing (2)
  • src/engine/magic/spell-database.ts
  • tests/engine/magic/spell-database.test.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/engine/magic/spell-database.test.ts

Comment on lines +242 to +313
registerSpell({
id: 'healing-word',
name: 'Healing Word',
level: 1,
school: 'evocation',
castingTime: 'bonus_action',
range: 60,
components: { verbal: true, somatic: false, material: false },
duration: 'Instantaneous',
concentration: false,
description: 'A creature of your choice within range regains 1d4 + your spellcasting ability modifier.',
higherLevels: 'Healing increases by 1d4 for each slot level above 1st.',
classes: ['bard', 'cleric', 'druid'],
targetType: 'creature',
effects: [{
type: 'healing',
dice: '1d4',
upcastBonus: { dice: '1d4', perLevel: 1 }
}],
autoHit: false
});

// ============================================================================
// 2ND LEVEL SPELLS
// ============================================================================

registerSpell({
id: 'scorching-ray',
name: 'Scorching Ray',
level: 2,
school: 'evocation',
castingTime: 'action',
range: 120,
components: { verbal: true, somatic: true, material: false },
duration: 'Instantaneous',
concentration: false,
description: 'You create three rays of fire and hurl them at targets within range. Each ray deals 2d6 fire on a hit.',
higherLevels: 'You create one additional ray for each slot level above 2nd.',
classes: ['sorcerer', 'wizard'],
targetType: 'creature',
effects: [{
type: 'damage',
dice: '6d6',
damageType: 'fire',
saveType: 'none',
upcastBonus: { dice: '2d6', perLevel: 1 }
}],
autoHit: false
});

registerSpell({
id: 'web',
name: 'Web',
level: 2,
school: 'conjuration',
castingTime: 'action',
range: 60,
components: { verbal: true, somatic: true, material: true, materialDescription: 'a bit of spiderweb' },
duration: 'Concentration, up to 1 hour',
concentration: true,
description: 'You conjure a mass of thick, sticky webbing in a 20-foot cube. Creatures starting their turn in the webs or entering them on their turn must make a Dexterity saving throw or become restrained.',
classes: ['sorcerer', 'wizard'],
targetType: 'area',
areaOfEffect: { shape: 'cube', size: 20 },
effects: [{
type: 'debuff',
saveType: 'dexterity',
saveEffect: 'none',
conditions: ['RESTRAINED']
}],
autoHit: false
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for spell in healing-word scorching-ray web; do
  echo "=== $spell ==="
  rg -n -C2 --glob 'tests/**/*.ts' "\"$spell\"|'$spell'" tests || true
done

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 1487


🏁 Script executed:

# Search for cast or casting related tests
find tests -name '*.test.ts' -o -name '*.spec.ts' | head -20

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 766


🏁 Script executed:

# Search for spell-casting or cast-related test patterns
rg -l 'cast|spell.*effect|apply.*spell' tests/ --type ts | head -20

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 547


🏁 Script executed:

# Search for these specific spell references in all test files, not just by name
rg -n 'healing-word|scorching-ray' tests/ --type ts -B2 -A5

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 899


🏁 Script executed:

# Check test file structure - how many test files exist
find tests -name '*.test.ts' -o -name '*.spec.ts' | wc -l

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 70


🏁 Script executed:

# Check the contents of spellcasting.test.ts
cat -n tests/server/spellcasting.test.ts | head -100

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 4488


🏁 Script executed:

# Search specifically in spellcasting.test.ts for references to our three spells
rg -n 'healing-word|scorching-ray|web' tests/server/spellcasting.test.ts -B3 -A3

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 49


🏁 Script executed:

# Check what spells are actually tested in spellcasting.test.ts
rg -n "id:.*'|spell.*id" tests/server/spellcasting.test.ts | head -30

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 2970


🏁 Script executed:

# Search all test files for these spell IDs in cast/casting contexts
rg -n 'spellId.*healing-word|spellId.*scorching-ray|spellId.*web|"healing-word"|"scorching-ray"' tests/ --type ts -B2 -A2

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 49


🏁 Script executed:

# Check the full spell-database.test.ts to understand what's actually being tested
wc -l tests/engine/magic/spell-database.test.ts

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 111


🏁 Script executed:

# Get the context around the spell lookup tests in spell-database.test.ts
sed -n '20,60p' tests/engine/magic/spell-database.test.ts

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 2071


🏁 Script executed:

# Search for cast-related test descriptions across all test files
rg -n "it.*cast|describe.*cast|it.*spell.*effect|it.*healing|it.*damage" tests/ --type ts | grep -i "healing-word\|scorching-ray\|web" || echo "No cast tests found for these spells"

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 170


🏁 Script executed:

# Check if there are any references to issue `#41` in the codebase
rg -n "issue.*41|#41" . --type ts --type md -B2 -A2

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 1144


🏁 Script executed:

# View the entire spell-database.test.ts file to see all tests
cat -n tests/engine/magic/spell-database.test.ts

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 3725


🏁 Script executed:

# Search for any cast action tests that might use spell IDs generically
rg -n "castSpell|executeSpell|executeAction.*spell|spellAction" tests/ --type ts | head -20

Repository: Mnehmos/mnehmos.rpg.mcp

Length of output: 2523


Add cast-path tests for the three newly registered spells.

The three spells—healing-word, scorching-ray, and web—currently appear only in the lookup/dedup regression test (spell-database.test.ts), confirming they can be resolved by name and kebab-id variants. However, they lack spell-casting tests that verify their effects and mechanics (as other spells like Magic Missile, Fireball, and Cure Wounds have in spellcasting.test.ts). Add at least one cast-oriented test per spell to validate healing amounts, damage rolls, and condition applications.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/magic/spell-database.ts` around lines 242 - 313, Add cast-path
tests in spellcasting.test.ts for the three new spells: for 'healing-word' (id:
healing-word) write a test that uses the existing castSpell helper to target a
creature and assert HP increases by 1d4 + spellcasting modifier (and that
upcasting (slot >1) increases healing by +1d4 per slot via the upcastBonus); for
'scorching-ray' (id: scorching-ray) write a test that casts the spell and
asserts it produces three separate attack/damage instances of 2d6 fire each (and
that upcasting grants extra ray(s) per upcastBonus); and for 'web' (id: web)
write a test that casts on an area and asserts affected creatures must make a
Dexterity save and failing results in the RESTRAINED condition being applied
(and concentration/duration behavior as appropriate). Use the same test helpers
and assertions used by existing spell tests (e.g., castSpell, getSpellById,
checking resulting HP/damage events and conditions) to mirror style and sizing.

@Mnehmos Mnehmos merged commit fdca349 into main Apr 20, 2026
4 checks passed
@Mnehmos Mnehmos deleted the fix/41-spell-catalog branch April 20, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spell catalog missing common D&D spells (magic-missile, fire-bolt, scorching-ray, sacred-flame, web)

1 participant