# `.list` format spec for modpack files

## Purpose
A `.list` file is a human-editable, ordered list of RimWorld mod references.

It is designed to be:

- easy to read and edit manually
- stable under UI edits
- easy to parse line by line
- tolerant of malformed lines without destroying them

A `.list` file is **not** the place for resolved metadata, dependency graphs, or lock data.

---

## File rules

- Encoding: `UTF-8`
- Preserve existing line endings on write
- Preserve unknown and malformed lines as-is
- Preserve untouched lines exactly as they originally appeared

---

## Supported line types

Each line must be treated as exactly one of these:

1. **Blank line**
2. **Alias directive line**
3. **Comment line**
4. **Entry line**
5. **Unknown line**

### Blank line
A line containing only whitespace.

Examples:

```text
<empty line>

```

### Alias directive line
A line whose first non-whitespace token is `@alias`.

General shape:

```text
[leading whitespace]@alias[required whitespace]<alias>
```

Examples:

```text
@alias core
@alias libraries
  @alias gameplay
```

Rules:

- alias directives declare a stable human-authored alias for the `.list` file
- the alias value must be a single non-whitespace token
- a file should contain at most one alias directive
- when present, the alias should normally appear near the top of the file
- malformed alias directive lines must be preserved as unknown lines

### Comment line
A line whose first non-whitespace character is `#`.

Examples:

```text
# Libraries
    # CE-related stuff
```

These comments are positional. They are not machine-semantic.

### Entry line
A mod entry with optional disabled marker and optional trailing comment.

General shape:

```text
[leading whitespace][!][optional whitespace]<reference>[optional whitespace][# trailing comment]
```

Examples:

```text
packageid:Ludeon.RimWorld
packageid:Ludeon.RimWorld.Biotech
steam:2009463077
steam:2009463077  # Harmony
!steam:2009463077
!local:MyFork  # disabled for testing
```

### Unknown line
Any non-blank, non-comment line that does not parse as a valid entry.

Unknown lines must not be deleted or auto-corrected.

Examples:

```text
banana
steam::
???
```

---

## Supported reference kinds

Entry lines must contain exactly one reference of one of these forms:

### Steam Workshop mod

```text
steam:2009463077
```

### Package ID reference

```text
packageid:brrainz.harmony
packageid:Ludeon.RimWorld
packageid:Ludeon.RimWorld.Royalty
packageid:Ludeon.RimWorld.Ideology
packageid:Ludeon.RimWorld.Biotech
packageid:Ludeon.RimWorld.Anomaly
```

### Local mod reference

```text
local:MyFork
local:MyDevMod
```

---

## Disabled entries

A disabled entry starts with `!`.

Examples:

```text
!steam:2009463077
!packageid:brrainz.harmony
!local:MyFork
```

Disabled status is first-class syntax and must not be represented by commenting out the line.

This is valid:

```text
!steam:2009463077  # disabled
```

This is just a comment, not a disabled entry:

```text
# steam:2009463077
```

---

## Comments

### Standalone comments
A standalone comment line begins with `#` after optional leading whitespace.

Example:

```text
# Essential libraries
```

### Trailing comments
An entry line may include a trailing comment introduced by `#`.

Example:

```text
steam:2009463077  # Harmony
```

Trailing comments belong to the entry and move with it if the entry is moved.

Standalone comment lines belong to their position unless explicitly moved.

---

## Whitespace handling

The parser should be permissive:

- allow leading whitespace before alias directive lines
- allow leading whitespace before comment lines and entry lines
- allow optional whitespace after `!`
- allow optional whitespace before trailing `# comment`

Examples that should parse as entries:

```text
steam:2009463077
    steam:2009463077
!steam:2009463077
!   steam:2009463077
```

Examples that should parse as alias directives:

```text
@alias core
  @alias libraries
```

When creating new lines, the writer should prefer canonical formatting.

Recommended canonical output for new alias directives:

```text
@alias core
```

Recommended canonical output for new entries:

```text
steam:2009463077
steam:2009463077  # Harmony
!steam:2009463077  # disabled
```

---

## Grouping semantics

Grouping is defined by file boundaries, not by syntax inside a file.

Comments may be used by humans as visual separators, but they do not define subgroups.

Example:

```text
# UI
steam:1
steam:2

# Combat
steam:3
```

The `# UI` and `# Combat` lines are comments only.

---

## Examples

### Valid file

```text
@alias core
# Core game
packageid:Ludeon.RimWorld
packageid:Ludeon.RimWorld.Royalty
packageid:Ludeon.RimWorld.Ideology
packageid:Ludeon.RimWorld.Biotech

# Libraries
steam:2009463077  # Harmony
steam:818773962   # HugsLib
steam:2606448745  # XML Extensions

# Local development
local:MyCombatFork
!local:WeirdTestMod  # disabled for now
```

### File with unknown lines

```text
# Stuff to investigate
steam:2009463077  # Harmony
banana
steam::
??? mystery entry
```

The last three non-comment lines must be preserved as unknown lines.

---

## Parsing requirements

The parser must produce one node per line.

Recommended node categories:

- `blank`
- `alias`
- `comment`
- `entry`
- `unknown`

For alias directive nodes, the parser should extract:

- raw line text
- leading whitespace
- alias value

For entry nodes, the parser should extract:

- raw line text
- leading whitespace
- disabled flag
- raw reference text
- parsed reference kind
- parsed reference value
- spacing before trailing comment
- trailing comment text, if present

---

## Editing requirements

When modifying files through the UI or automation:

- preserve untouched lines exactly
- preserve alias directives
- preserve blank lines
- preserve standalone comments
- preserve trailing comments on moved entries
- preserve unknown lines unchanged
- modify the smallest possible amount of text

Examples:

### Toggle entry
Convert:

```text
steam:2009463077  # Harmony
```

to:

```text
!steam:2009463077  # Harmony
```

without altering the comment.

### Move entry
Move the whole entry line, including its trailing comment.

### Insert entry
New entries may be written in canonical form.

### Delete entry
Remove only the selected entry line unless the user explicitly selected surrounding comments too.

---

## Resolution model

A `.list` entry expresses **user intent**, not resolved truth.

Examples:

- `steam:2009463077`
- `packageid:brrainz.harmony`
- `packageid:Ludeon.RimWorld.Biotech`
- `local:MyFork`

Resolution to canonical mod identity belongs in a separate lock/resolution file, not in `.list`.

---

## Non-goals

The `.list` format should not include:

- nested structures
- includes/imports
- dependency rules
- version pins
- performance metadata
- semantic section headers
- rich machine-readable annotations in comments

---

## Suggested agent guidance

Tell the agent to follow these rules:

1. Parse line-by-line.
2. Never rewrite the whole file just to make formatting prettier.
3. Preserve untouched lines byte-for-byte.
4. Treat malformed lines as unknown, not fatal.
5. Use canonical formatting only for newly created lines.
6. Keep alias directives as first-class syntax, not comments.
7. Do not infer semantic grouping from comments.
8. Do not comment out entries to disable them; use `!`.

---

## Compact machine-oriented summary

```text
.list file:
- UTF-8 text
- ordered, line-based
- line types: blank | alias | comment | entry | unknown

alias directive syntax:
- optional leading whitespace
- "@alias"
- required whitespace
- alias token

entry syntax:
- optional leading whitespace
- optional "!"
- optional whitespace
- reference:
  - steam:<workshop_id>
  - packageid:<package_id>
  - local:<name>
- optional whitespace
- optional trailing comment starting with "#"

rules:
- preserve untouched lines exactly
- preserve unknown lines unchanged
- standalone comments are positional
- trailing comments belong to the entry
- disabled entries use "!"
- grouping is by file, not by in-file comments
```
