Files
arma-modlist-tools/CLAUDE.md
revernomad17 595544e94f add CLAUDE.md for Claude Code context
Covers: common commands, package vs CLI architecture, data flow,
group naming convention, server index structure, junction/symlink
rules (critical: never shutil.rmtree), check_names two-pass
classification, Python 3.9 from __future__ requirement, and
test suite structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 17:33:16 +07:00

4.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Common Commands

# Run all tests (no network required)
python test_suite.py

# Check Python version and dependencies
python check_deps.py

# Full pipeline (parse → compare → fetch → link)
python run.py

# Parse + compare only (no download, no linking)
python run.py --skip-fetch --skip-link

# Diagnose mod folder name / steam_id issues
python check_names.py
python check_names.py --fix --fix-ids

There is no build step, linter config, or package install beyond pip install -r requirements.txt.

Architecture

Package vs CLI layer

arma_modlist_tools/ is a pure library — no I/O side effects, no sys.exit, no print. All CLI scripts (run.py, fetch_mods.py, link_mods.py, etc.) sit at the project root and call into the package. New functionality goes in the package first, then a CLI script wraps it.

Data flow

modlist_html/*.html
    └─ parser.parse_modlist_dir()
           └─ compare.compare_presets()
                  └─ comparison.json  ←─ source of truth for groups + mod identity
                         ├─ fetcher.build_server_index()  ←─ Caddy JSON API
                         │      └─ fetcher.find_mod_folder()  (steam_id first, name fallback)
                         │             └─ downloads/{group}/@ModName/
                         │                    └─ linker.link_group()
                         │                           └─ arma_dir/@ModName  (junction/symlink)
                         └─ reporter.build_missing_report()  →  missing_report.json

Group naming convention

  • "shared" — mods present in all compared presets
  • "<preset_name>" — mods unique to one preset (key from comparison["unique"])

This group label is stored in missing_report.json per-mod so sync_missing.py knows where to place newly available mods without re-reading comparison.json.

Server index structure

build_server_index() returns:

{
    "by_steam_id": {"450814997": "https://server/@cba_a3/"},  # primary lookup
    "by_name":     {"cbaa3":     "https://server/@cba_a3/"},  # normalized fallback
    "folders":     [...]                                       # raw Caddy listing
}

_normalize_name strips @, lowercases, removes all non-alphanumeric: "@CBA_A3""cbaa3". Used in both the index builder and every lookup.

Detection: os.path.islink() returns False for Windows junctions. Always use _is_junction() from linker.py, which checks st_file_attributes & 0x400 (FILE_ATTRIBUTE_REPARSE_POINT) on Windows.

Removal: Use os.rmdir() on Windows and os.unlink() on Linux. Never shutil.rmtree() — it follows the junction and deletes the target mod files.

Creation: cmd /c mklink /J <link> <target> on Windows, os.symlink() on Linux.

check_names.py classification (two-pass)

Pass 1 collects raw (server_name, local_steam_id) for every disk folder. Pass 2 builds ok_disk_names — the set of disk names that already match the server exactly. Any MISMATCH whose proposed server name is in ok_disk_names is reclassified as ID_COLLISION (the local meta.cpp has a wrong publishedid that belongs to a different mod). This prevents false rename suggestions caused by shared/duplicate steam IDs on the server.

--fix-ids corrects meta.cpp using steam IDs from comparison.json (sourced from Steam Workshop URLs in the HTML presets) as the authoritative source.

Python Version Compatibility

Minimum is Python 3.9. All files that use X | Y union type annotations must have from __future__ import annotations as the first import. Without it, the | syntax raises TypeError at runtime on Python < 3.10. Every module in arma_modlist_tools/ already has it; any new CLI script you add must include it too.

Test Suite

test_suite.py uses a custom harness (no pytest/unittest dependency). Structure:

group("section name")   # prints header
test("description", callable)  # runs fn, catches exceptions, tracks pass/fail
skip("description", "reason")  # marks skipped

Tests that exercise the linker use tempfile.TemporaryDirectory() — never the real arma_dir. Tests that would require network calls mock list_mod_files with unittest.mock.patch.

Key Files Not in Git

  • config.json — credentials + paths (copy from config.template.json)
  • downloads/ — downloaded mod files, can be several GB
  • modlist_json/ — generated JSON output

The .html preset files in modlist_html/ are tracked as example inputs.