Skip to content

Latest commit

 

History

History
259 lines (223 loc) · 14.7 KB

File metadata and controls

259 lines (223 loc) · 14.7 KB

ACKS Module Toolchain — Canonical Conventions

This document records the decided answers for the ACKS module family so no future session re-derives them differently. When something here changes, change it here first, then propagate with bin/sync-toolchain.mjs.

1. Repo anatomy

acks-<feature>/
  module.json              Foundry manifest — the real version number lives here
  scripts/                 ESM runtime code; entry point scripts/module.mjs
  templates/               Handlebars templates (may be nested)
  styles/                  CSS shipped to Foundry
  lang/en.json             Flat i18n keys, prefixed "<MODULE-ID-UPPERCASED>."
  packs/_source/<pack>/    JSON pack sources — committed
  packs/<pack>/            Compiled LevelDB packs — committed AND shipped
  ruledata/                (optional) runtime-fetched JSON rules — ships in zip
  tools/                   Dev harness — NOT shipped
    build-packs.mjs        SYNCED harness (needs tools/pack-data.mjs)
    pack-data.mjs          Module-owned document content; exports `packs` map
    validate.mjs           SYNCED validator
    test-logic.mjs         (optional) module-owned pure-logic tests
  docs/MODEL.md            Design doc (reuse → extend → enhance → invent)
  (rules extracts are NOT in the repo — see "Rules extracts" below)
  .github/workflows/release.yml   SYNCED release workflow
  .gitignore .gitattributes       SYNCED
  LICENSE                  SYNCED proprietary license + disclaimer (see docs/LICENSING.md)
  CLAUDE.md                RENDERED from skeleton per repo
  .claude/settings.json    SYNCED permission allowlist
  package.json             MERGED (scripts/devDeps/engines enforced)
  package-lock.json        Committed (CI uses `npm ci`)

2. Git conventions

  • Default branch: main. (acks-formation is a legacy master; renaming requires a remote default-branch change on GitHub — do it deliberately.)
  • Tags: v<semver> and the tag MUST equal module.json version (CI enforces this).
  • Compiled LevelDB packs are committed. They ship in the zip and make git-clone dev installs work without a build step.
  • .gitattributes is mandatory. core.autocrlf=true is common on Windows; without the binary markers git rewrites line endings in LevelDB files on checkout. CURRENT is plain text ("MANIFEST-00000N\n") that LevelDB parses strictly — a CRLF checkout breaks the pack on a fresh clone. Never remove those lines.
  • Never ignore packs/**/*.log — LevelDB stores live data in .log files. The canonical .gitignore carries a comment to that effect.
  • Lockfiles are committed for reproducible CI installs.
  • After a local npm run build:packs, if packs/_source shows no diff, the LevelDB churn is timestamp noise: git restore packs/ && git clean -fd packs/. Commit rebuilt packs only when sources actually changed.

3. module.json conventions

  • id == repo name == npm package name; title = ACKS II — <Feature>.
  • version: plain semver X.Y.Z. Bump before tagging.
  • compatibility: minimum: "14", verified: "14.364" (raise verified as tested; existing modules keep their historical minimums until retested).
  • relationships.systems: acks, minimum: "14".
  • Every relationships.requires entry carries a human reason and a compatibility.minimum. Standard deps when needed: lib-wrapper (>=1.12.0) for safe method wrapping, socketlib (>=1.1.0) for GM-routed writes.
  • manifest/download: https://github.com/NocTempre/<id>/releases/latest/download/module.json|zip.
  • Declare a pack in packs only once it has content (the build harness skips empty packs; CI fails on declared-but-missing packs).
  • languages: at least enlang/en.json.

4. Release pipeline

The release procedure lives once, in this repo: .github/workflows/release-module.yml (a reusable workflow_call workflow). Each module carries only a thin synced release.yml caller that references it @main — so procedure changes here propagate to every module automatically, with no sync step. (Pin the reference to a tag/SHA only if stability ever matters more than propagation.)

Trigger: push tag v*. Steps: tag/version match check → npm cibuild:packsvalidatetest (each --if-present) → declared-pack existence check → ship-by-default zip → publish module.json + module.zip to the GitHub release. A manual "Run workflow" on a module performs a dry run: full build + validation, no tag check, no publish.

A second synced workflow, toolchain-check.yml, runs on every module push/PR: it checks out this template and fails CI if any canonical file drifted from canon — hand-edits can't survive unnoticed.

The zip includes everything except an explicit dev-only denylist (.git*, .github, .claude, CLAUDE.md, node_modules, tools, packs/_source, package*.json). Rationale: the old allowlist zip silently dropped new runtime dirs (henchmen's ruledata/ had to be patched in by hand). New runtime content now ships automatically; add to the denylist only for new dev-only paths.

Release procedure (also encoded in the acks-release skill):

  1. Bump module.json version (+ changelog if present).
  2. npm run build:packs — commit pack changes only if _source changed.
  3. npm run validate (and npm test where present).
  4. Commit, git tag v<version>, push branch + tag.
  5. Confirm publication with BOUNDED checks (never gh run watch — it hangs through GitHub API outages, which 2026-07-16 stranded several agents): poll gh release view v<version> --json assets ~30s apart, cap ~5 min, in a background/Monitor wait rather than foreground. On API 5xx, stop and report — the pushed tag completes on its own.
  6. Verify: curl -sm 15 -L https://github.com/NocTempre/<id>/releases/latest/download/module.json reports the new version (needs the repo public; while private use gh release view — unauthenticated manifest fetches 404).

5. Dev harness

  • Node >= 20 (CI pins 20). devDependencies exactly: @foundryvtt/foundryvtt-cli ^1.0.0, classic-level ^3.0.0, handlebars ^4.7.9. (The core system uses foundryvtt-cli v3; upgrading the modules is a deliberate, together-only change.)
  • tools/pack-data.mjs is the module-owned content file. Contract: export const packs = { "<pack-name>": () => [docs] } (arrays also accepted). Large data may live in sibling files (e.g. monsters' bestiary-data.mjs) re-exported through the map. Documents carry 16-char alphanumeric _id and a _key ending in that _id.
  • tools/validate.mjs (synced) checks: JS syntax of scripts/** + tools/**, Handlebars compilation of templates/**, JSON validity of module.json / package.json / lang/*.json / ruledata/** / packs/_source/**, pack _id/_key invariants, module.json invariants (semver, paths exist, manifest URL shape), and that every i18n key referenced in code exists in lang/en.json (dynamic-suffix tolerant).
  • Optional tools/test-logic.mjs (module-owned): pure-logic regression tests that mock minimal Foundry globals and import the real scripts (see acks-equipment). Wire as "test" in package.json; CI runs it --if-present.
  • Optional tools/validate-extra.mjs (module-owned): an extra check that must run as PART of validation (not just npm test). The canonical validate.mjs auto-runs it last if present and fails on its non-zero exit, so scripts.validate stays exactly node tools/validate.mjs (canon) while the module still gets its own gate. acks-content uses it to run its IP-safety register lint (tools/lint-register.mjs) — chaining the lint into scripts.validate directly would drift from canon and fail toolchain-check.
  • Foundry dev install: junction, not copy: New-Item -ItemType Junction -Path "$env:LOCALAPPDATA\FoundryVTT\Data\modules\<id>" -Target "C:\Proj\<id>"
  • Local live testing: check the local rules reference directory for C:\Proj\acks-rules\TEST_ENVIRONMENT.md. If it exists, it defines this machine's Foundry test server (URL, world, users, drive-it-by-API notes) — use it for live verification. If it does not exist, skip live testing and rely on validate + build:packs (+ test where present). The file is machine-specific and LOCAL-ONLY: every developer defines their own (or none); never commit ports, world ids, user names, or passwords to any repo.

5b. Module key & namespacing (enforced by validate.mjs §7)

Anything a module puts into a shared registry carries its key, so nothing collides and any identifier in a bug report greps straight back to its owner. One form per registry — no legacy alternatives (the 2026-07-15 migration renamed every pre-existing deviation rather than grandfathering it):

  • JS registriesglobalThis exposures, custom hook names (Hooks.call/callAll), Handlebars helpers — start with the camelCase namespace: the module id camelCased (acks-influenceacksInfluence, hook acksInfluenceRollComplete, helper acksMonstersHas). Derived from the id, never declared. Hyphens can't appear in JS identifiers, hence camelCase here and kebab elsewhere. Firing another acks-* module's hook is a warning, not a failure — deliberate cross-module calls are legitimate.
  • CSS classes (top-level selectors) start with the module id as-is (kebab): .acks-formation-skill-tab.
  • lang keys start with <ID-UPPERCASED>.; Foundry-owned roots (TYPES.*) are allowlisted in the validator — extend that allowlist in the template only.
  • Pack document _ids (top-level) start with the short key declared in module.jsonflags.<id>.idPrefix — mandatory for any module with packs. It exists only because ids are exactly 16 chars (the camel namespace wouldn't leave room). Keep it short; scaffolding derives a default (acks + word initials) overridable with --key.
  • Settings, document flags, and socketlib channels are already scoped by the Foundry APIs — no check needed.

Declared short keys (must stay unique across the family): equipment acksEq, formation acksfm, henchmen acksHm, influence acksInfl, monsters acksm.

Consumers of another module's hooks use the literal hook name (grep-ability is the point); the firing module also publishes its hook names on its API object (e.g. globalThis.acksInfluence.hooks).

5c. Licensing & disclaimers

Every module is an ACKS II App under the ACKS II App License. Two rules are non-negotiable and enforced by canon: (1) the code is proprietary — no open-source/Open-Game/compatibility license may ever be applied to it (nothing that lets others take the registered code); (2) the mandatory disclaimers (unofficial, registration number, required books, free, trademark) are stated clearly in each README. The canonical LICENSE (a COPY file, CI-enforced) carries the code terms; the per-module registration number and required publications live in the README "## License" section. Full rules and rationale: docs/LICENSING.md.

6. Design doctrine

Reuse → extend → enhance → invent (from acks-monsters, adopted family-wide): reuse core system documents and fields; extend only with genuinely new data in flags["<module-id>"]; enhance with alternate sheets/wrappers (libWrapper) rather than replacements; invent nothing the system already provides. docs/MODEL.md in each module records how that module applies this.

Rules extracts are LOCAL-ONLY. The canonical extract of the relevant ACKS II rules for each module lives at C:\Proj\acks-rules\<module-id>\ (never in a repo — licensed book text; the public repos got indexed online, so on 2026-07-16 the extracts were purged from all five modules' git history and the repos went private). Sessions cite the local extract instead of re-reading PDFs. The canonical .gitignore carries armor patterns against accidental re-adds; anything shipped in-repo (ruledata/, compendium text) must stay paraphrase-with-citation, never verbatim book text.

7. Claude infrastructure

  • Each module carries a rendered CLAUDE.md (layout, commands, conventions, pointer here) and a synced .claude/settings.json allowlist covering the routine dev loop (npm, node harness, git incl. commit/tag/push, gh run, GitHub API reads). settings.local.json stays gitignored for personal grants.
  • Shared skills live here in .claude/skills/ and are installed to ~/.claude/skills/ by sync-toolchain.mjs --install-skills, so they work from any working directory (sessions usually run from foundryvtt-acks-core with the modules as additional dirs — note that in that setup only the core repo's settings govern permissions; the per-module settings.json pays off in standalone sessions).
  • The core repo keeps its Claude context in an untracked CLAUDE.md (via .git/info/exclude) to avoid polluting the AutarchLLC fork.

8. Known deviations (as of 2026-07-15, post-rollout)

All five modules are on canon (sync --check reports zero drift). Remaining deliberate deviations:

Repo Deviation Path back to canon
acks-formation master branch; pack data inline in a custom 21 KB build-packs.mjs rename branch on GitHub; extract data to pack-data.mjs, then sync harness
acks-influence pack data inline in custom build-packs.mjs; compatibility.minimum still 13 extract data; raise minimum when retested on 14

Pack-data _stats guidance learned during rollout: henchmen and equipment use fixed timestamps (rebuilds are byte-identical); formation/influence/monsters still stamp Date.now() at import, so every rebuild churns packs/_source. Prefer fixed timestamps in new pack data — pin the stamp to the value already committed and the rebuild is a no-op, so a dirty packs/_source then genuinely means the content changed. With Date.now() data, compare diffs excluding createdTime/modifiedTime before deciding whether to commit a rebuild.

9. Consistency automation (implemented 2026-07-15)

Three layers keep the family consistent, by mechanism rather than discipline:

  1. Reusable release workflow (release-module.yml@main) — CI logic propagates to all modules automatically; nothing to sync.
  2. CI drift check (toolchain-check.yml, synced) — canonical files (validators, dotfiles, Claude infra) are enforced: a module push with hand-edited or stale canon fails CI until sync-toolchain --apply runs.
  3. Template CI (ci.yml here) — every template change scaffolds a module from the skeleton and runs the canonical build + validate, so canon itself can't break silently.

Possible later: publish the harness as a git-dependency npm package (acks-tools) with bin entries, replacing the vendored tools/*.mjs.