Development Setup
Show the whole prompt
Read https://intentius.io/chant/contributing/development/.
Set up the chant repository on this machine: run `npm install` and `just regen`, then `just check` and `npx tsx scripts/typecheck.ts`.
Show me the output of each, and the first failure if one fails. Change no file.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/contributing/development/.Set up the chant repository on this machine: run `npm install` and `just regen`, then `just check` and `npx tsx scripts/typecheck.ts`.Show me the output of each, and the first failure if one fails. Change no file.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Prerequisites
Section titled “Prerequisites”- Node.js 24+ — runtime. The repo
.nvmrcpins Node 24, which is what CI tests against. - tsx — TypeScript execution (
npm install -g tsx) - Git — for version control
- just — task runner (optional, but recommended)
Clone and Install
Section titled “Clone and Install”git clone https://github.com/intentius/chant.gitcd chantnpm installJustfile Commands
Section titled “Justfile Commands”The project uses just for common tasks. Run just with no arguments to see all available commands:
just # list all commandsjust install # install all dependencies (including docs)just build # type-check packages/core on TypeScript 7just test # run all testsjust test-e2e-affected # run the e2e tests your change reachesjust lint # run eslint on packages/just check # run build + lint + testjust docs # start the docs dev serverjust bench # run performance benchmarksjust smoke # build and run the Docker smoke testjust docs-build # build the unified documentation siteRun Tests
Section titled “Run Tests”npx vitest run # all testsnpx vitest run packages/core # core package onlynpx vitest run lexicons/aws # AWS lexicon onlynpx vitest run lexicons/k8s # K8s lexicon onlyThe suite is two vitest projects. unit is every test file except *.e2e.test.ts, and each of its tests has a 5 second budget: a test that runs longer fails with a message naming the budget. e2e is every *.e2e.test.ts, the tests that spawn the CLI, run tsc over fixtures, build every example in the corpus, boot containers or release whole projects, all on the machine running the suite. They are not the cloud deployments on the E2E Testing page. Pull request CI runs only the unit tests. They run in four shards beside the typechecks and lint. The e2e tests run in release-suites.yml on main every three hours and when someone starts it, and a release only tags a commit they passed.
npx vitest run --project unit # unit tests onlynpx vitest run --project e2e # end-to-end tests onlyA unit test over the budget either gets faster or moves into a *.e2e.test.ts file, and so does a test that runs the CLI or tsc over whole projects, or builds the example corpus. CHANT_UNIT_TEST_BUDGET_MS sets a different budget for a local run, and 0 turns it off.
Before you merge
Section titled “Before you merge”Pull requests don’t run the e2e tests, so run the ones your change reaches yourself:
just test-e2e-affected # against origin/mainjust test-e2e-affected main --list # print the files without running themIt runs every changed *.e2e.test.ts, the e2e files in the same directory as a changed file, and the e2e files vitest’s import graph relates to the diff (--no-related leaves those out). When the change touches examples/, a lexicon’s examples/ or src/serializer*, or docs/, it also runs the three example gates: root-examples-gate, sandbox-execution-boundary and fold-differential. The last line is a one-line result to paste into the pull request. Unlike just test-e2e, it doesn’t wait for a person to type run, so coding agents run it too.
Lexicon artifacts on a fresh clone
Section titled “Lexicon artifacts on a fresh clone”Lexicon tests import each lexicon’s generated barrel (src/generated/) and bundled dist/meta.json. Neither is committed. just test builds whatever is missing before running vitest (about a minute the first time, nothing afterwards). If you run npx vitest run directly on a fresh clone, the run stops before any test with a message naming the lexicons that need generating. Build them with either of these:
just regen # every lexiconnpm run generate -w lexicons/aws && npm run bundle -w lexicons/aws # one lexiconSet CHANT_SKIP_ARTIFACT_CHECK=1 to bypass the check.
Kubernetes roundtrip tests
Section titled “Kubernetes roundtrip tests”The K8s lexicon has additional integration tests that run against real manifests from kubernetes/examples:
cd lexicons/k8s
# Parse-only roundtrip (parse → generate)just import-samples --skip-clone --verbose
# Full roundtrip (parse → generate → import → serialize → compare)just full-roundtrip --skip-clone --verbose
# k3d cluster validation (requires k3d + kubectl + Docker)just k3d-validate --verboseTest Patterns
Section titled “Test Patterns”- Unit tests live next to source files or in
__tests__/directories - The
packages/test-utils/directory (@intentius/chant-test-utils) provides shared test fixtures, assertions, and the example test harness
Example Tests
Section titled “Example Tests”Example tests are centralized — each lexicon has a single examples.test.ts that auto-discovers all its examples. Example directories contain only source code (no .test.ts files), so users who copy examples get clean projects.
Per-lexicon examples live at lexicons/<name>/examples/<example>/src/ and are tested by lexicons/<name>/examples/examples.test.ts.
Cross-lexicon examples (e.g. AWS + K8s, GitLab + AWS) live at examples/<name>/src/ and are tested by examples/examples.e2e.test.ts.
Both use the shared harness from @intentius/chant-test-utils/example-harness:
import { describeAllExamples } from "@intentius/chant-test-utils/example-harness";import { mySerializer } from "@intentius/chant-lexicon-my-lexicon";
describeAllExamples({ lexicon: "my-lexicon", serializer: mySerializer, outputKey: "my-lexicon", examplesDir: import.meta.dirname,}, { // Per-example overrides (optional) "docs-snippets": { skipLint: true }, "my-example": { checks: (output) => { expect(output).toContain("expected"); }, },});Adding a new example: create a subdirectory with src/ and a package.json — the centralized examples.test.ts auto-discovers it with no test file changes needed. Add an override entry only if you need custom assertions or to skip lint/build.
Type Checking
Section titled “Type Checking”scripts/tsc7.sh --noEmit --declaration false --declarationMap false -p packages/core/tsconfig.jsonnpx tsx scripts/typecheck.ts # tests, scripts and examples, against a baselineTwo TypeScript versions are installed:
| What | Version | How it is called |
|---|---|---|
Typechecks, and every package and lexicon .d.ts build | 7.0 | scripts/tsc7.sh, which runs the root alias typescript-native by path |
Code that imports typescript: core (fold, lint, codegen), lexicon lint rules, eslint’s parser | 5.9 | import ts from "typescript" |
TypeScript 7’s typescript package has no JS compiler API, so typescript stays on 5.9. Both packages ship a tsc bin. node_modules/.bin/tsc is 5.9, so a plain npx tsc still runs 5.9. scripts/tsc7.test.ts fails if any of these changes.
On TypeScript 7, a --noEmit check with declaration: true is slower than on 5.9, because it still computes declaration diagnostics. Pass --declaration false --declarationMap false to noEmit checks. Declaration errors are still caught by each package’s build.
Project Layout
Section titled “Project Layout”chant/├── packages/│ ├── core/ @intentius/chant — type system, pipeline, CLI, and codegen infrastructure│ │ └── src/│ │ ├── runtime.ts createResource/createProperty factories│ │ ├── codegen/│ │ │ ├── naming.ts NamingStrategy (collision-free class names)│ │ │ ├── generate.ts generatePipeline (schema → artifacts)│ │ │ ├── package.ts packagePipeline (artifacts → BundleSpec)│ │ │ └── fetch.ts fetchWithCache, extractFromZip│ │ └── lsp/│ │ └── lexicon-providers.ts LexiconIndex, completions, hover│ └── test-utils/ shared test fixtures and assertions├── lexicons/│ ├── aws/ @intentius/chant-lexicon-aws — AWS CloudFormation lexicon│ ├── gitlab/ @intentius/chant-lexicon-gitlab — GitLab CI/CD lexicon│ └── k8s/ @intentius/chant-lexicon-k8s — Kubernetes lexicon├── docs/ Astro Starlight documentation site└── package.jsonCore codegen infrastructure
Section titled “Core codegen infrastructure”The packages/core/src/codegen/ directory contains reusable infrastructure for lexicon code generation pipelines. Lexicons supply provider-specific callbacks and data tables; core handles orchestration, logging, warning collection, stats counting, and artifact writing. The AWS lexicon (lexicons/aws/) demonstrates this pattern — its codegen files are thin wrappers around core pipelines.
Docs Classification
Section titled “Docs Classification”Every page under docs/src/content/docs/ carries a diataxis frontmatter field with one of four values, following Diátaxis:
| Value | The page is for | Sidebar group |
|---|---|---|
tutorial | learning by doing, start to finish | Tutorials |
how-to | getting one task done | How-to guides |
reference | looking something up | Reference |
explanation | understanding why | Explanation |
One mode per page. A page that needs two gets split. The sidebar in docs/astro.config.mjs is grouped by the same four values, and just docs-check fails when a page is missing the field, is in no sidebar group, or sits in a group that does not match its field. index and whats-new are the only exempt pages.
Lexicon docs sites use the same four values in lexicons/<name>/docs/pages/*.mdx; the docs pipeline builds their sidebar from the field.
What’s Next
Section titled “What’s Next”- Architecture Overview — understand how the pieces fit together
- Core Type System — Declarable, Resource, Property types