adding-mod-parsers

Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)

181 stars

Best use case

adding-mod-parsers is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)

Teams using adding-mod-parsers should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/adding-mod-parsers/SKILL.md --create-dirs "https://raw.githubusercontent.com/majiayu000/claude-skill-registry/main/skills/data/adding-mod-parsers/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/adding-mod-parsers/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How adding-mod-parsers Compares

Feature / Agentadding-mod-parsersStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Adding Mod Parsers

## Overview

The mod parser converts raw mod strings (e.g., `"+10% all stats"`) into typed `Mod` objects used by the calculation engine. It uses a **template-based** system for pattern matching.

## When to Use

- Adding support for new mod string patterns
- Extending existing mod types to handle new variants
- Adding new mod types to the engine

## Project File Locations

| Purpose | File Path |
|---------|-----------|
| Mod type definitions | `src/tli/mod.ts` |
| Parser templates | `src/tli/mod-parser/templates.ts` |
| Enum registrations | `src/tli/mod-parser/enums.ts` |
| Calculation handlers | `src/tli/calcs/offense.ts` |
| Tests | `src/tli/mod-parser.test.ts` |

## Implementation Checklist

### 1. Check if Mod Type Exists

Look in `src/tli/mod.ts` under `ModDefinitions`. If the mod type doesn't exist, add it:

```typescript
interface ModDefinitions {
  // ... existing types ...
  NewModType: { value: number; someField: string };
}
```

### 2. Add Template in `templates.ts`

Templates use a DSL for pattern matching. **Do not add comments to templates.ts** - the template string itself is self-documenting.

```typescript
t("{value:dec%} all stats").output("StatPct", (c) => ({
  value: c.value,
  statModType: "all" as const,
})),
t("{value:dec%} {statModType:StatWord}")
  .enum("StatWord", StatWordMapping)
  .output("StatPct", (c) => ({ value: c.value, statModType: c.statModType })),
t("{value:dec%} [additional] [{modType:DmgModType}] damage").output("DmgPct", (c) => ({
  value: c.value,
  dmgModType: c.modType ?? "global",
  addn: c.additional !== undefined,
})),
t("{value:dec%} attack and cast speed").outputMany([
  spec("AspdPct", (c) => ({ value: c.value, addn: false })),
  spec("CspdPct", (c) => ({ value: c.value, addn: false })),
]),
```

**Template capture types:**

| Type | Matches | Example Input → Output |
|------|---------|------------------------|
| `{name:int}` | Unsigned integer | `"5"` → `5` |
| `{name:dec}` | Unsigned decimal | `"21.5"` → `21.5` |
| `{name:int%}` | Unsigned integer percent | `"30%"` → `30` |
| `{name:dec%}` | Unsigned decimal percent | `"96%"` → `96` |
| `{name:+int}` | **Signed** integer (requires `+` or `-`) | `"+5"` → `5`, `"-3"` → `-3` |
| `{name:+dec}` | **Signed** decimal (requires `+` or `-`) | `"+21.5"` → `21.5` |
| `{name:+int%}` | **Signed** integer percent | `"+30%"` → `30`, `"-15%"` → `-15` |
| `{name:+dec%}` | **Signed** decimal percent | `"+96%"` → `96` |
| `{name:EnumType}` | Enum lookup | `{dmgType:DmgChunkType}` |

**Signed vs Unsigned Types:**
- Use **unsigned** (`dec%`, `int`) when input does NOT start with `+` or `-` (e.g., `"8% additional damage applied to Life"`)
- Use **signed** (`+dec%`, `+int`) when input STARTS with `+` or `-` (e.g., `"+25% additional damage"`)
- Signed types will NOT match unsigned inputs, and vice versa

**Optional syntax:**
- `[additional]` - Optional literal, sets `c.additional?: true`
- `[{modType:DmgModType}]` - Optional capture, sets `c.modType?: DmgModType`
- `{(effect|damage)}` - Alternation (regex-style)

### 3. Add Enum Mapping (if needed)

If you need custom word → value mapping, add to `enums.ts`:

```typescript
export const StatWordMapping: Record<string, string> = {
  strength: "str",
  dexterity: "dex",
  intelligence: "int",
};

registerEnum("StatWord", ["strength", "dexterity", "intelligence"]);
```

### 4. Add Handler in `offense.ts` (if new mod type)

If you added a new mod type, add handling in `calculateOffense()` or relevant helper:

```typescript
case "NewModType": {
  break;
}
```

For existing mod types with new variants (like adding `statModType: "all"`), update existing handlers to also filter for the new variant:

```typescript
const flat = sumByValue(
  statMods.filter((m) => m.statModType === statType || m.statModType === "all"),
);
```

### 5. Add Tests

Add test cases in `src/tli/mod_parser.test.ts`:

```typescript
test("parse percentage all stats", () => {
  const result = parseMod("+10% all stats");
  expect(result).toEqual([
    {
      type: "StatPct",
      statModType: "all",
      value: 10,
    },
  ]);
});
```

### 6. Verify

```bash
pnpm test src/tli/mod_parser.test.ts
pnpm typecheck
pnpm check
```

## Template Ordering

**IMPORTANT:** More specific patterns must come before generic ones in `allParsers` array.

```typescript
// Good: specific before generic
t("{value:dec%} all stats").output(...),           // Specific
t("{value:dec%} {statModType:StatWord}").output(...), // Generic

// Bad: generic would match first and fail on "all stats"
```

## Examples

### Simple Value Parser (Signed)

**Input:** `"+10% all stats"` (starts with `+`)

```typescript
t("{value:+dec%} all stats").output("StatPct", (c) => ({
  value: c.value,
  statModType: "all" as const,
})),
```

### Simple Value Parser (Unsigned)

**Input:** `"8% additional damage applied to Life"` (no sign)

```typescript
t("{value:dec%} additional damage applied to life").output("DmgPct", (c) => ({
  value: c.value,
  dmgModType: "global" as const,
  addn: true,
})),
```

### Parser with Condition (Signed)

**Input:** `"+40% damage if you have Blocked recently"`

```typescript
t("{value:+dec%} damage if you have blocked recently").output("DmgPct", (c) => ({
  value: c.value,
  dmgModType: "global" as const,
  addn: false,
  cond: "has_blocked_recently" as const,
})),
```

### Parser with Per-Stackable (Signed in "deals" position)

**Input:** `"Deals +1% additional damage to an enemy for every 2 points of Frostbite Rating the enemy has"`

Note: The `+` appears AFTER "deals", so use `{value:+dec%}`:

```typescript
t("deals {value:+dec%} additional damage to an enemy for every {amt:int} points of frostbite rating the enemy has")
  .output("DmgPct", (c) => ({
    value: c.value,
    dmgModType: "global" as const,
    addn: true,
    per: { stackable: "frostbite_rating" as const, amt: c.amt },
  })),
```

### Multi-Output Parser (Signed)

**Input:** `"+6% attack and cast speed"`

```typescript
t("{value:+dec%} [additional] attack and cast speed").outputMany([
  spec("AspdPct", (c) => ({ value: c.value, addn: c.additional !== undefined })),
  spec("CspdPct", (c) => ({ value: c.value, addn: c.additional !== undefined })),
]),
```

### Flat Stat Parser (Signed)

**Input:** `"+166 Max Mana"`

```typescript
t("{value:+dec} max mana").output("MaxMana", (c) => ({ value: c.value })),
```

### No-Op Parser (Recognized but produces no mods)

**Input:** `"Energy Shield starts to Charge when Blocking"`

Use `outputNone()` when a mod string should be recognized (not flagged as unparsed) but has no effect on calculations:

```typescript
t("energy shield starts to charge when blocking").outputNone(),
```

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Using `dec%` for input with `+` prefix | Use `+dec%` for inputs like `"+25% damage"` |
| Using `+dec%` for input without sign | Use `dec%` for inputs like `"8% damage applied to life"` |
| Template doesn't match input case | Templates are matched case-insensitively; input is normalized to lowercase |
| Missing `as const` on string literals | Add `as const` for type narrowing: `statModType: "all" as const` |
| Handler doesn't account for new variant | Update `offense.ts` to handle new values (e.g., `statModType === "all"`) |
| Generic template before specific | Move specific templates earlier in `allParsers` array |

## Data Flow

```
Raw string: "+10% all stats"
    ↓ normalize (lowercase, trim)
"10% all stats"
    ↓ template matching (allParsers)
{ type: "StatPct", value: 10, statModType: "all" }
    ↓ calculateStats() in offense.ts
Applied to str, dex, int calculations
```

Related Skills

adding-tweets

181
from majiayu000/claude-skill-registry

Add tweets to the Second Brain. Use when the user provides a Twitter/X URL and pasted tweet content, asking to "add a tweet", "save this tweet", or "capture this tweet".

adding-todos

181
from majiayu000/claude-skill-registry

Use this skill to capture an idea, task, or issue that surfaces during a Kata session as a structured todo for later work. This skill creates markdown todo files in the .planning/todos/pending directory with relevant metadata and content extracted from the conversation. Triggers include "add todo", "capture todo", "new todo", and "create todo".

adding-stacks

181
from majiayu000/claude-skill-registry

Use when adding a new framework/stack to create-faster CLI tool - addresses copy-first mentality, incomplete implementations, and missing dependencies

adding-phases

181
from majiayu000/claude-skill-registry

Use this skill to add planned work discovered during execution to the end of the current milestone in the roadmap. This skill appends sequential phases to the current milestone's phase list, automatically calculating the next phase number. Triggers include "add phase", "append phase", "new phase", and "create phase". This skill updates ROADMAP.md and STATE.md accordingly.

adding-persistent-event

181
from majiayu000/claude-skill-registry

Adds a new type of event that gets persisted to the event log. Use this when adding new kinds of write operations to the system or when adding new events to existing code.

adding-notes

181
from majiayu000/claude-skill-registry

Add new notes to the Second Brain knowledge base. Use when the user provides a resource (URL, book, podcast, article, GitHub repo, Reddit thread) and asks to "add a note", "create a note", "save this", "add to my notes", "take notes on", or "capture this".

/mnt/data2/nhlstats/.github/skills/adding-new-sport/SKILL.md

181
from majiayu000/claude-skill-registry

```markdown

adding-new-metric

181
from majiayu000/claude-skill-registry

Guides systematic implementation of new sustainability metrics in OSS Sustain Guard using the plugin-based metric system. Use when adding metric functions to evaluate project health aspects like issue responsiveness, test coverage, or security response time.

adding-new-ai-format

181
from majiayu000/claude-skill-registry

Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing

adding-nango-provider-support

181
from majiayu000/claude-skill-registry

Use when adding support for a new Nango provider - configures provider in providers.yaml, creates documentation (main page, setup guide, connect guide), and updates docs.json following established patterns

adding-models

181
from majiayu000/claude-skill-registry

Guide for adding new LLM models to Letta Code. Use when the user wants to add support for a new model, needs to know valid model handles, or wants to update the model configuration. Covers models.json configuration, CI test matrix, and handle validation.

adding-milestones

181
from majiayu000/claude-skill-registry

Use this skill when adding a milestone to an existing project, starting a new milestone cycle, creating the first milestone after project init, or defining what's next after completing work. Triggers include "add milestone", "new milestone", "start milestone", "create milestone", "first milestone", "next milestone", and "milestone cycle".