Contributing to DivineSWE
Building Platform Packages
DivineSWE supports four platforms. The canonical source targets claude-code; packages for other platforms are generated from it.
Generate a Single Platform
npm run generate -- --platform copilot-cliOutput lands in the mapping-selected directory: committed copilot/ or
codex/ for marketplace packages, and dist/<platform>/ for other targets.
Generate All Platforms
npm run generate:allSee docs/platform-portability.md for the full generator architecture, platform mapping schema, and instructions for adding new platform targets.
Installing Platform Bundles into a Target Project
The install-platform.mjs script automates the generate → verify → stage → promote pipeline for any installable platform. By default, platforms install globally (e.g. ~/.config/opencode/); use --per-project to opt out.
npm run install:copilot-cli # Install copilot-cli bundle globallynpm run install:all # Install all installable platforms globallynpm run install:interactive # Interactive platform selection and target confirmationTo install into the current project directory instead of the global default:
node scripts/install-platform.mjs install --platform copilot-cli --per-project --yesTo overwrite existing files:
node scripts/install-platform.mjs install --platform copilot-cli --force --yesTo install to a specific directory:
node scripts/install-platform.mjs install --platform copilot-cli --target /path/to/project --yesTo install all platforms at once:
node scripts/install-platform.mjs install --all --yesopencode is not a file-copy install target. It is delivered as an npm plugin (the
divineswenpm package). The installer rejects--platform opencode.
See docs/platform-portability.md for the full flag reference, three-state file model, atomicity guarantees, and marker-based target resolution.
Publishing the npm Package (Maintainers)
The unscoped divineswe package is published manually:
npm run generate:all # regenerate dist/opencode/ (also runs automatically via prepack)npm pack --dry-run # inspect the tarball file list before shippingnpm publish # publish the divineswe package to npmprepack runs npm run generate:all on both npm pack and npm publish, so the shipped dist/opencode/ payload is always freshly generated from the canonical source.
Adding a New Platform
- Create
platforms/<new-platform>.jsonfollowing the schema. - Optionally create platform-specific hooks in
hooks/<new-platform>/. - Optionally create skill variants as
skills/<skill>/SKILL.<new-platform>.md. - Run
node scripts/generate-platform.mjs --platform <new-platform>to test. - Add integration tests in
tests/integration/platform-<new-platform>.test.ts.
Version Bumps
When updating the version number, all three files must be updated together:
package.json— canonical version source.claude-plugin/plugin.json— plugin manifest versionpackage-lock.json— runnpm installafter updatingpackage.jsonto sync
Tests in tests/plugin/plugin-manifest-adversarial.test.ts enforce that these three files agree. A stale package-lock.json is the most common drift — always regenerate it after a version bump.
Version bump release checklist
Beyond the three-file sync above, a version bump touches two more places that do not derive the version automatically:
EXPECTED-literal gotcha: a version bump must ALSO update the hardcodedEXPECTEDliteral and theshould_report_<ver>test names intests/structural/book71-pipeline-adversarial.test.ts— that guard does NOT derive the version frompackage.json, so a bump that skips it leaves a stale-version assertion silently passing against the wrong string.- copilot-content-commit gotcha (Book 79): a prophecy that runs
npm run generate:allmust COMMIT the regeneratedcopilot/CONTENT (agents/skills/scripts, not just the version fields) plus bothmarketplace.jsonversions, becausetests/integration/four-platform-determinism.test.tscompares the committedcopilot/against a fresh regen — a partial (version-only) commit leaves stale content and FAILS determinism on a clean checkout. Mason’s selective-add for a version-bump prophecy must include any regeneratedcopilot/content plus any Loki-Break sibling adversarial test files — both have been left behind by an over-narrow add.
Git Practices
- Use
git mvfor file renames to preserve history - Do not include Co-Authored-By lines in commits
- Commit message format:
Book N: <summary>
Test Conventions
- Runner:
npx vitest run(vitest, NOT pytest) - Test naming:
should_<behavior>_when_<condition>with underscores - Tests read AGENT.md/SKILL.md content and assert regex patterns against it
- Generated-artifact tests must assert REAL loadability — TOML parseability, shell-command validity, hook command-path existence — NOT just token/regex presence
See docs/testing-guide.md for the full idioms: real-loadability corollaries, doc-as-contract guards, the boot-smoke contract, and the doc-relocation test idiom.
Website (site/)
The site/ subtree is a self-contained Astro + Starlight static site (cinematic landing + /docs) served from the root of its own subdomain (https://divineswe.aidragon.io, Astro base: '/', root-relative links). It has its own vitest runner — run site tests via cd site && npm run test, never the repo-root npx vitest run (the root config excludes site/). site/ is excluded from the npm tarball and invisible to plugin loading; whole-repo structural test walkers must keep 'site' in their SKIP sets so recursive copies never traverse site/node_modules.
- Docs sync:
site/scripts/sync-docs.mjspublishes an allow-list of repodocs/stems plus rootCHANGELOG.md(H1 → title frontmatter; rewrites relative.mdcross-links so nothing 404s or leaks internal-doc names; the allow-list gates files, NOT link contents). The sync console count derives fromALLOW_LIST.length + 1— never a literal. - Sidebar: two groups — Initiation (Overview / Quick Start / Installation / Workflows) and The Scriptures (Architecture / Agents / Skills / Configuration / State Model / Testing Guide / Vows — Consumer Contract / Contributing / Changelog). Every sidebar
slugmust resolve to an allow-list-derived synced route (Overview points atdocs/overview, notdocs). - Drift guards: install commands in synced
quickstart.md/installation.mdstay byte-exact to README;site/src/content/docs/index.mdis a frozenlanding-adversarialsource-scan placeholder — never edit it; thesite/tests/build-smoke.test.tsALLOW_LIST_SLUGScanary is a separate hardcoded list — when the sync allow-list grows, update it too. - Reference docs are de-booked: architecture, configuration, state-model, and testing-guide are evergreen prose with no numbered
Book Ndev-log tags (CHANGELOG exempt; guardtests/structural/book78-debook.test.ts). - Build & deploy: the build-smoke is env-gated on
DIVINESWE_SITE_E2E. Deployment is via.gitlab-ci.yml(build → SFTP-mirrorsite/distvialftpusing maskedSFTP_HOST/SFTP_USER/SFTP_PASSWORD/SFTP_REMOTE_DIRCI variables). The gitlab.com remote is the source, decoupled from the deploy origin; gitlab.com source links are preserved across the site. - Gotcha:
astro devskips the sync-docs prebuild — run the sync manually when previewing docs changes.
The Offering Acceptance Policy (Book 88)
An Offering is a screened, instance-discovered finding that a downstream
DivineSWE instance hands back to the maintainers — either a proposed law (in
laws-register format) or a script-opportunity sighting. Instances stage and
screen Offerings locally via /divineswe:offering (scripts/offering.mjs) and
deliver them by an explicit human action — a prefilled GitLab new-issue URL, or
Service Desk email for account-less contributors. There is no telemetry, no
phone-home, and no instance-side auto-submission: the human always sends.
This section governs what happens after an Offering arrives — how a
maintainer triages it and canonizes the accepted ones.
Dedup authority is maintainer-side
The instance-side lint runs an advisory self-dedup against its own local
audit log only — it cannot see the upstream backlog (the docs are not shipped,
and the tool has no network reach). Maintainer-side PR/issue review is the
authoritative dedup. Only a maintainer, reading the incoming Offering against
docs/honor-system-rules.md (the laws register) and
docs/script-opportunities.md (its stable SO-NNN
namespace), decides whether an Offering is a genuinely new finding, a refinement
of an existing entry, or a duplicate to close. No automated check — upstream or
downstream — is ever that decision.
Label taxonomy
Triage every incoming Offering with exactly one disposition label and one kind label.
Kind — where the Offering wants to land:
| Label | Meaning | Canonical home when accepted |
|---|---|---|
offering:law | A proposed law / honor-system rule. | docs/honor-system-rules.md (the laws register) |
offering:opportunity | A script-opportunity sighting (agent hand-executing an exact procedure). | docs/script-opportunities.md, a new SO-NNN entry |
Disposition — the triage verdict:
| Label | Meaning |
|---|---|
offering:accepted | New, in-scope, and worth canonizing — proceed to the canonization step. |
offering:refined | Overlaps an existing entry; fold the new signal into that entry rather than adding a duplicate. |
offering:duplicate | Already covered by an existing law or SO-NNN; close with a pointer to the canonical entry. |
offering:declined | Out of scope or contrary to a Denied decision (e.g. telemetry, auto-submission); close with the rationale. |
offering:deferred | Valid but parked behind a volume/again trigger (e.g. a shipped upstream snapshot); record the trigger. |
Canonization
An offering:accepted finding is canonized into its canonical home:
offering:law→ add the rule todocs/honor-system-rules.mdin laws-register format, naming the enforcement mechanism that gives it teeth (the register does not trust prose to guard itself), and wire the guarding test/hook the rule claims.offering:opportunity→ append a new, allocation-orderedSO-NNNentry todocs/script-opportunities.md, classified against the three-gate decision rule (MECHANICAL / HYBRID / JUDGMENT) whose canonical home isskills/avatar-forge/axioms.md. IDs are opaque, never reused, never renumbered — theSO-NNNnamespace is the dedup anchor for future Offerings.
An offering:refined finding is merged into the existing law or SO-NNN entry;
offering:duplicate / offering:declined / offering:deferred Offerings are
closed with a rationale (and, for deferrals, the recorded re-trigger).