factorio-mod-workshop 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ # The workspace holds all mod-specific and personal data.
2
+ # It is meant to be a separate (usually private) git repository.
3
+ /workspace/
4
+ # Throwaway workspace for exercising commands during development. Never point
5
+ # fmw at a real workspace to test: judgements written there are attributed to
6
+ # the maintainer.
7
+ /dev-workspace/
8
+
9
+ # Stray virtualenvs (`uv venv <name>` typo-ed as a command argument).
10
+ enable/
11
+ venv/
12
+
13
+ __pycache__/
14
+ *.py[cod]
15
+ .venv/
16
+ dist/
17
+ build/
18
+ *.egg-info/
19
+ .DS_Store
@@ -0,0 +1,117 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for AI agents working in this repository.
4
+
5
+ ## What this is
6
+
7
+ `fmw` (Factorio Mod Workshop) is a CLI toolkit that turns Factorio mod portal activity into structured, diffable data, so that maintainers and AI agents can track bug reports, feature ideas and development plans across many mods without re-reading HTML every time.
8
+
9
+ Two audiences, and the split matters:
10
+
11
+ - **This repository is the tool.** Generic, shareable, open source. No personal data, **no local filesystem paths**, no mod-specific content.
12
+ - **The workspace is the data.** Registry, cached discussions, curated tracker items. Gitignored here, meant to be its own git repository. Located via `--workspace`, then `$FMW_WORKSPACE`, then `./workspace`.
13
+
14
+ Keep that boundary. If you are about to write a path like `/Users/...` or a specific mod's bug into a file under this repo, it belongs in the workspace instead.
15
+
16
+ ## Layout
17
+
18
+ ```
19
+ fmw/
20
+ config.py workspace resolution, bundled templates, mods.yaml / config.yaml access
21
+ templates/ workspace scaffold copied by `fmw init`, read via importlib.resources
22
+ portal.py mod portal API + discussion scraping + title marker parsing
23
+ store.py cache read/write, change detection, changelog.jsonl
24
+ tracker.py curated Markdown items with YAML frontmatter
25
+ discover.py scan local repos for info.json, merge into the registry
26
+ cli.py argparse command line, every command supports --json
27
+ docs/ workspace format, portal behaviour, agent playbooks
28
+ ```
29
+
30
+ ## Working on the code
31
+
32
+ - Python 3.10+, `uv venv && uv pip install -e .`. Dependencies stay minimal: `httpx`, `beautifulsoup4`, `PyYAML`. Do not add more without a good reason.
33
+ - **Support both install modes.** `uv tool install` / `uvx` (package copied into a uv-managed environment) and `uv pip install -e .` from a checkout. Bundled data must therefore be package data, and nothing may assume a repository is present. `fmw launcher` refuses to run from an ephemeral `uvx` environment, since the path it would embed is deleted by `uv cache prune`.
34
+ - **Never locate files by walking up from `__file__`.** Bundled data lives inside the package and is read with `importlib.resources` (`config.template_dir()`), which behaves identically under editable installs, wheels and zipapps. Path arithmetic like `parents[2]` silently breaks when the layout or the install mode changes, and the breakage surfaces far from its cause.
35
+ - **No implicit workspace.** It comes from `--workspace` or `$FMW_WORKSPACE`, never a guessed default. A workspace is real data; picking the wrong one silently is worse than refusing to run. Both failure modes name the fix in the error text.
36
+ - Never let a loop over discovered files report success when it found nothing. `fmw init` used to print "Workspace ready" over an empty directory for exactly that reason.
37
+ - Every command must support `--json`. Human-readable output is the default; structured output is what makes agents effective.
38
+ - Network access happens **only** in `portal.py`, and only in `fmw sync`, `fmw discover` and `fmw thread --refresh`. Everything else reads the cache. Keep it that way so agents can explore for free.
39
+ - Be polite to the portal: one request at a time, ~0.4 s apart, descriptive User-Agent, retry 5xx with backoff. Never parallelise scraping.
40
+ - The tool is **read-only** with respect to the portal. There is no write API and posting replies is the maintainer's job.
41
+ - Machine-specific data (`config.yaml`, `paths.yaml`) is separated from portable data (registry, tracker, cache) so a workspace can be a shared git repository. Never put a local path into `mods.yaml`.
42
+ - `discover.build_registry` merges by field category: `MANUAL_FIELDS` are the maintainer's, `LOCAL_FIELDS` always come from the scan, `PORTAL_FIELDS` are written only when a lookup happened. Adding a field means putting it in the right group; treating a portal field as always-present makes `--no-portal` silently erase it.
43
+ - Distinguish "not checked" from "checked and false". `on_portal` is absent until a lookup happens, `false` only on a 404; a network failure warns and leaves the old value. Recording a guess as a fact is how a transient outage turns into "all your mods are unpublished".
44
+ - Derive rather than store anything that can change on the portal without a content change (title markers are the example), so a stale cache does not produce stale conclusions.
45
+
46
+ ## Portal facts worth remembering
47
+
48
+ - Mod metadata has a real API: `https://mods.factorio.com/api/mods/<name>/full`, no auth. Discussions do not - they are HTML only.
49
+ - **Discussion list pagination is `/discussion/page/N`, a path segment.** A `?page=N` query parameter is silently ignored and returns page 1 again with HTTP 200, which is easy to mistake for the list being truncated at 50. Tag filters are not paginated (`/discussion/b/page/2` is a 404), so do not build the full list from them. `Portal.thread_list` walks pages until one is empty.
50
+ - Exact timestamps live in `title=` attributes; visible text is only "8 hours ago". Always read the attribute.
51
+ - Maintainers mark resolved threads by prefixing the title, e.g. `[FIXED]`, `[DONE]`, `[EXPLAINED]`; a trailing `?` means unsure. This is the only thread status the portal carries.
52
+
53
+ Details and selectors: `docs/portal-notes.md`.
54
+
55
+ ## Working on mods, not the tool
56
+
57
+ Run `fmw help workflow` for the full loop. The short version:
58
+
59
+ 1. `fmw sync` to find what is new, `fmw inbox` to see what needs a decision.
60
+ 2. `fmw thread <url>` to read a discussion in full before concluding anything.
61
+ 3. Record findings as tracker items and **always** link the portal thread via `sources: [portal:<id>]` - that is what marks it handled, and what groups several reports of one problem into a single item.
62
+ 4. `fmw mods --json` gives each mod's local checkout path; correlate reported versions against the mod's `changelog.txt` before assuming a bug is still live.
63
+
64
+ ## Testing
65
+
66
+ **Never exercise commands against a real workspace until asked explicitly by user.** `fmw mark`, `fmw track new` and the bulk sweeps write judgements that are indistinguishable from the maintainer's own, in files that carry no authorship. A `--duplicate-of` run to check a flag becomes a claim that two reports are the same problem, made without reading either.
67
+
68
+ Use `dev-workspace/` in this repository (gitignored, created with `fmw init`). Seed it by copying `mods.yaml`, `paths.yaml` and `cache/` from a real workspace - all three are regenerable, and copying avoids re-fetching from the portal. Point at it with `--workspace ./dev-workspace` or `FMW_WORKSPACE`.
69
+
70
+ If test data does reach a real workspace, say so plainly and remove it; a workspace is typically not under git, so there is nothing to revert to.
71
+
72
+ ## Three layers, three questions
73
+
74
+ `cache/` is what the portal said (disposable). `threads/<mod>.yaml` is your judgement about a *conversation*. `tracker/` is what is being done about a *problem*. Keep them apart: a network sync writes only to `cache/`, so it can never touch a judgement.
75
+
76
+ Tickets are for **code changes only**, `kind: error` or `feature`. A thread that needs an answer, is a duplicate, or is simply wrong gets `fmw mark`, not a ticket. Opening a ticket per thread is the failure mode this layering exists to prevent - there are 151 threads here, of which maybe 15 describe work.
77
+
78
+ Ticket ids are random 6-hex, unique across all mods, and never change; references use the id alone so retitling is free. `mods:` is a list because several of these mods share code.
79
+
80
+ **Reporter-supplied data is a hint; maintainer-supplied data is authoritative.** Never branch on the portal's thread tag - users mislabel constantly. Title markers like `[FIXED]` or `(fixed)` are written by the maintainer and can be trusted; they are matched in either bracket style at either end of the title.
81
+
82
+ ## Version mismatches: ask, do not infer
83
+
84
+ When `fmw mods` flags a mod with `~`, its local `info.json` version differs from the published one. **Do not infer why, and do not "fix" it.** Unreleased work, an out-of-date checkout and a deliberate local edit are indistinguishable from the numbers, and maintainers edit the local version for reasons the tool cannot see. Stop and ask what the state means before changing anything in that checkout.
85
+
86
+ ## The reply obligation
87
+
88
+ A ticket reaching `status: released` makes every thread in its `sources:` owe a reply, derived rather than stored so it cannot be forgotten. Only `fmw mark --responded` clears it, recording the ticket id in the thread's `announced` list. Do not clear it on the user's behalf without them having actually replied.
89
+
90
+ ## The testing handover
91
+
92
+ An agent can write a fix but cannot verify one - that needs a human running Factorio, and each run is expensive for them. So a finished-but-unverified fix gets `status: needs-testing` plus a filled-in `## Test plan` section, never `status: fixed`. `fmw testplan` batches every such item into a single document so one game session clears many fixes. Respect that boundary: claiming a fix is verified when nobody has launched the game is the one failure mode this workflow exists to prevent.
93
+
94
+ `maintained` is `active` / `frozen` / `unknown`, chosen because YAML 1.1 turns bare `yes`/`no` into booleans - that silently broke every comparison against the field, so a hand-edited `maintained: no` excluded nothing. Prefer vocabularies YAML cannot coerce for any hand-edited enum.
95
+
96
+ ## Field vocabularies
97
+
98
+ `kind`, `status`, `maintained`, `triage`, `class`, source prefixes and title markers all have fixed value sets, documented in `fmw/help.py` under the `workspace` topic together with what enforces each one. When you add a value, update that topic - it is the only place a user can see the vocabulary, and several fields are preserved but read by nothing, which the listing is what keeps visible.
99
+
100
+ ## Where documentation lives
101
+
102
+ One rule decides where a sentence goes: **can someone who ran `uv tool install` and never cloned this repo need it?** If yes it ships in the package; if it is only needed to *change* fmw, it lives in the repository.
103
+
104
+ - **`fmw/help.py`** - all user-facing documentation. `COMMANDS` entries become each subparser's argparse description, so `fmw <cmd> --help` is the single place a command is explained. `TOPICS` covers what spans commands: `workspace` (file formats, every field value) and `workflow` (the loop and the mistakes it prevents). **Adding or changing a command means updating its entry**; a command with no entry gets a bare flag listing. `fmw help --all` prints the lot in one call, which is what an agent should read rather than probing commands one at a time.
105
+ - **`README.md`** - what the tool is and why, plus install. A pitch, not a reference. Resist adding command detail; it belongs in `--help`, where it is offline and cannot drift from the code.
106
+ - **`docs/`** - developer material only: how the portal behaves and why the scraper is shaped as it is. Nothing a user of the tool needs.
107
+ - **`CLAUDE.md`** - conventions for agents changing this repo.
108
+
109
+ Do not restate the same thing in two of these. The workspace templates going stale, and playbooks duplicating `--help`, were both this failure.
110
+
111
+ ## Platforms
112
+
113
+ Developed on macOS, can be used on Linux, with Windows implemented but untested. Keep it that way deliberately: use `pathlib` rather than string paths, `os.name == "nt"` for the few real branches (the launcher writes `sh` vs `.cmd`, and uv's tool and bin directories differ), and never hardcode a shell. `_path_hint()` reads `$SHELL` so instructions match what the user actually runs.
114
+
115
+ ## Documentation style
116
+
117
+ Do not hard-wrap Markdown prose. Write each paragraph as a single long line and let the editor wrap it; mixing manual newlines with soft wrapping renders badly at different widths.