Skip to content

Lexicon Onboarding Skill

This page is written to be handed to an AI agent on its own. Point yours at this URL and it has everything needed to take a lexicon from nothing to published, without reading the rest of the authoring section first.

Every step is either a command to run or a check to make. Where a step needs depth, it links out. Do the steps in order: later ones assume earlier ones.

A lexicon is a plugin that teaches chant about one operational area. It lives in lexicons/<name>/, is published as @intentius/chant-lexicon-<name>, and is versioned in lockstep with core.

  1. Scaffold

    Terminal window
    npx tsx packages/core/src/cli/main.ts init lexicon <name>
    npm install

    Verify it typechecks before writing anything:

    Terminal window
    npx tsc --noEmit -p lexicons/<name>/tsconfig.build.json

    Detail: Scaffold a Lexicon.

  2. Implement the plugin

    At minimum a serializer and the generate lifecycle. Depending on the platform you may also want observation, live export, lint rules, and post-synth checks.

    Detail: Implement Generate, Create a Serializer, Write Lint Rules.

  3. Create an example workspace

    npm workspace resolution needs at least one dependent per package, or import("@intentius/chant-lexicon-<name>") fails from packages/core. Create lexicons/<name>/examples/basic-deployment/ with a package.json depending on both @intentius/chant and your lexicon, then add lexicons/<name>/examples/* to the root workspaces array.

    Full package.json shape: CI & Distribution.

  4. Wire it into the monorepo

    Terminal window
    npx tsx packages/core/src/cli/main.ts dev onboard <name>
    npm install

    This patches the root package.json, .github/workflows/chant.yml, the publish.yml test-job prepack line, and both smoke Dockerfiles. It is idempotent.

    You do not add a publish step. scripts/publish-packages.sh enumerates every non-private workspace package, so a new lexicon is published the moment it exists.

  5. Verify locally

    Terminal window
    npx vitest run lexicons/<name> # your lexicon
    just check # build, lint, full suite, lexicon contract

    just check runs the completeness contract, which gates on the tracked allowlist in scripts/check-lexicons.ts. An untracked failure exits 1.

  6. Create the npm trusted-publisher record

    Do this before the first release. On npmjs.com, open @intentius/chant-lexicon-<name>, then Settings, Trusted Publisher:

    FieldValue
    Organization / repositoryINTENTIUS/chant
    Workflow filenamepublish.yml
    Environmentleave empty

    The environment field matters. The publish job declares no environment:, so any value here fails to match the OIDC claims and the record silently does nothing.

    The record is per package. It is not inherited from the organization and not shared with the other lexicons, so a new one is always needed.

    A brand-new package name has to exist on the registry before npm will let you attach a record, so for a first-ever publish the order is: publish once by hand, create the record, and let every release after that go through CI.

  7. Release

    Terminal window
    just release patch # every package, in lockstep
    just release-lexicon <name> patch # this lexicon only

    Never hand-roll the tag. The workflow triggers on chant-v* and lexicon-*-v* only; a bare v0.34.0 tag matches nothing and the release silently never happens.

  8. Confirm it actually published

    Terminal window
    npm view @intentius/chant-lexicon-<name> version
    npm view @intentius/chant-lexicon-<name> dist.attestations

    A version with no attestation did not go out through trusted publishing, which means step 6 is incomplete even if the version number looks right.

scripts/publish-packages.sh never stops at the first failure. Every package gets its attempt, the summary table names the ones that failed, and the run exits non-zero at the end. Re-running is safe: a package already at its version is skipped, so a partial release is recovered by dispatching the workflow again.

SymptomCauseFix
ENEEDAUTH on your package while others publishNo trusted-publisher recordStep 6
registry REFUSED the OIDC exchange: HTTP 404The registry saying the same thing outrightStep 6
registry ACCEPTED the OIDC exchange but publish still failsThe record is fineRead the npm error; the auth path is not the problem
EOTP on the NPM_TOKEN fallbackThe secret is a publish or granular token, so npm wants a one-time passwordReplace it with an npm automation token
Your package sits a version behind the othersAn earlier run failed on itFix its auth, then re-dispatch publish

npm’s OIDC helper is written never to throw. When the token exchange fails it logs at verbose level and returns undefined, so npm publish falls through to ordinary auth and reports a bare ENEEDAUTH. That one error covers both “no record at all” and “a record that does not match this workflow”, and it reads exactly like having no credentials.

scripts/publish-packages.sh replays the exchange and prints the registry’s own status and message, so read that line instead of inferring. A successful exchange answers 201, not 200.

Because the record is matched against the workflow filename, a probe run from any workflow other than publish.yml returns 404 for every package and proves nothing.