diff --git a/.agents/skills/verify-template/SKILL.md b/.agents/skills/verify-template/SKILL.md index 7833a97..32a33a7 100644 --- a/.agents/skills/verify-template/SKILL.md +++ b/.agents/skills/verify-template/SKILL.md @@ -1,11 +1,11 @@ --- name: verify-template -description: "Drive this AntelopeJS template the way a new user does: scaffold a project with `ajs project init` from an offline interfaces manifest, install it, run its own `pnpm run checks`, run `ajs project dev --watch` and hit its welcome page, endpoint or ready line. Use to prove a change to the template works before merge, to verify a PR, or to run the release canary." +description: "Drive this AntelopeJS template the way a new user does: scaffold a project with `ajs project init`, or a module with `ajs module init`, from an offline interfaces manifest, install it, run its own `pnpm run checks`, run `ajs project dev --watch` and hit its welcome page, endpoint or ready line. Use to prove a change to the template works before merge, to verify a PR, or to run the release canary." --- # Verify template -The user of a template is someone running `ajs project init --template`: they get a copy of the repository without `.agents/` and `.claude/skills/` and expect it to install, pass its checks and run. Every deterministic step is an `antelopejs-verify` command: you choose which journeys to run and judge what they show. Never replace a command with a shell recipe. When a journey you need does not exist, add it to `verify.config.ts`, name it in its feature file, then run it. +The user of a template is someone running `ajs project init --template`, or `ajs module init --template` for a module template: they get a copy of the repository without `.agents/` and `.claude/skills/` and expect it to install, pass its checks and run. Every deterministic step is an `antelopejs-verify` command: you choose which journeys to run and judge what they show. Never replace a command with a shell recipe. When a journey you need does not exist, add it to `verify.config.ts`, name it in its feature file, then run it. `antelopejs-sync` writes this file, `package.json` and `tsconfig.json` here, and keeps them in sync. Everything else in `.agents/skills/verify-template/` is the repository's own layer, which sync never touches. @@ -15,7 +15,7 @@ The user of a template is someone running `ajs project init --template`: they ge node_modules/.bin/antelopejs-verify new --base origin/main --title "" ``` -`new` prints the run folder; pass it as `` below. `new` copies the working tree, uncommitted changes included, into a local git repository, and lists it in an offline interfaces manifest (`templates`, or `projectTemplates` when `template.project` is set) under `template.name`. Journeys point `ajs config set git` at it, so no step reaches GitHub. Every `services` entry of `verify.config.ts` then starts in its own Docker container on a free loopback port, as for a module. Every journey gets its own `HOME` and scratch folder, `CI=1`, `AJS_NO_UPDATE_CHECK=1`, `NO_COLOR=1` and a free port from the kernel, so two runs never share an instance. Run it from the repository root after installing dependencies. +`new` prints the run folder; pass it as `` below. `new` copies the working tree, uncommitted changes included, into a local git repository, and lists it in an offline interfaces manifest (`templates`, or `projectTemplates` when `template.project` is set) under `template.name`. `context.scaffold` creates from it with `ajs project init`, or with `ajs module init` when `template.module` is set. Journeys point `ajs config set git` at it, so no step reaches GitHub. Every `services` entry of `verify.config.ts` then starts in its own Docker container on a free loopback port, as for a module. Every journey gets its own `HOME` and scratch folder, `CI=1`, `AJS_NO_UPDATE_CHECK=1`, `NO_COLOR=1` and a free port from the kernel, so two runs never share an instance. Run it from the repository root after installing dependencies. ## Doctor @@ -47,6 +47,9 @@ The pack ships these journeys for this kind; a feature file names them next to t - `scaffold-dev`: `ajs project init --template --pm pnpm --git-init`, `pnpm install --frozen-lockfile`, then `ajs project dev --watch` with `PORT` set until `template.probe` answers: a path and status on that port, or a `log` line followed by `settleMs` of still running. Ctrl+C must exit 130. - `scaffold-checks`: the same scaffold and install, then the project's own `pnpm run checks`. +- For a module template (`template.module`), both run `ajs module init --template --pm pnpm --git-init` in place of `ajs project init`, and `scaffold-dev` runs `pnpm run build` before `ajs project dev --watch --project playground`. + +Both take their project from `context.prepared("scaffolded", ...)`, so the run scaffolds and installs it once. A repository journey that prepares `scaffolded` shares that build, so its build must be the same `context.scaffold` then `pnpm install --frozen-lockfile --prefer-offline`. ## Evidence @@ -58,21 +61,24 @@ Each step writes `/evidence////` with `command.t node_modules/.bin/antelopejs-verify clean ``` -Each journey deletes its scratch folder and `HOME`, its install included, when it ends: passed, failed or interrupted. Only its evidence stays. `clean` stops what the run started, by recorded PID and container name, and removes everything but `evidence/` and `run.json`, the folders `context.prepared` kept included. Run it after every run, failed ones included. +Each journey deletes its scratch folder and `HOME`, its install included, when it ends: passed, failed or interrupted. Only its evidence stays. `clean` stops what the run started, by recorded PID and container name, and removes everything but `evidence/` and `run.json`, the folders `context.prepared` kept included. Run it after every run, failed ones included. It cleans only a run this checkout created, the folder `new` printed: it refuses the shared runs folder, a repository's folder of runs and another checkout's run, which belong to other sessions. Never delete under the runs folder yourself, with `rm` or a glob over run names. ## The repository's own layer - `verify.config.ts` default-exports a `VerifyConfig` from `@antelopejs/tooling-configs/verify`: - `doors`: one entry per door kind (`data`, `format`, `contract`, `outbound`, `exposure`, `publish`), each `null` when it cannot apply or `{ globs, funnels, why }`. - - `journeys`: the repository's journeys by id, each `{ title, entryPoint, run(context) }`, using the check helpers the same import exports. A check states what the product must do; an environment that went away is never a failing check. When a journey fails, the harness reruns each service's readiness command, up to four times over about 3.5 s, and looks at every `ajs project dev` the journey started, its own readiness probe included. The journey ends NOT VERIFIED, not FAIL, when a service still fails every rerun, an app was killed by SIGKILL, SIGTERM, SIGHUP or SIGINT that the harness did not send, or the app the harness started failed to bind the port the harness gave it (`EADDRINUSE` naming that port). An app that exits on its own with an exit code, one that crashes by another signal such as SIGABRT or SIGSEGV, and one that logs `EADDRINUSE` for any other port are the product's failure. A change that takes its own service down still reads as an outage: the service stays down, so the journey is NOT VERIFIED. For anything else the journey depends on, such as a process it started with `context.command` or an outside endpoint, probe it when a check fails and call `context.notVerified(reason)` when it no longer answers. - - `context.prepared(key, build)` does the preparation many journeys repeat once per run, such as scaffolding and installing a project, and returns the journey's own copy at `/`. The first journey that asks for `key` runs `build(folder)` on the empty folder with its usual steps, after a step `prepared ` that marks them as the build and says whether it was kept. A build is kept until `clean` only when it returns, records at least one check, passes every check, and leaves only files, directories and symlinks in the folder. Every later journey of the run, also in a later `journey` command, gets a fresh copy and one step, `prepared `, that names the journey that built it and holds that build's evidence under `build/`. A build that is not kept is built again by the next journey, and rerunning the journey that built builds again. An absolute symlink into the folder points into each copy; other symlinks keep their target. `build` writes only inside the folder it is given: a write elsewhere, such as a sibling under `` or the repository, stays with the journey that built. Two concurrent `journey` commands that reach the same key at once both build, and the first to finish is kept. The commands `build` runs get a `HOME` of their own that is deleted when it ends: redo after `prepared` anything a later step needs in `HOME`, such as `ajs config set git`. Pass the same `build` for a key in every journey. + - `journeys`: the repository's journeys by id, each `{ title, entryPoint, run(context) }`, using the check helpers the same import exports. A check states what the product must do; an environment that went away is never a failing check. When a journey fails, the harness reruns each service's readiness command, up to four times over about 3.5 s, and looks at every `ajs project dev` the journey started, its own readiness probe included. The journey ends NOT VERIFIED, not FAIL, when a service still fails every rerun, an app was killed by SIGKILL, SIGTERM, SIGHUP or SIGINT that the harness did not send, or the app the harness started failed to bind the port the harness gave it (`EADDRINUSE` naming that port), or the first failing step is an install, image pull or image build whose failed requests went only to fixed public registry names that also fail a fresh DNS and TCP probe at verdict time. A registry that answers that probe makes the failure the product's, whatever the output says. An app that exits on its own with an exit code, one that crashes by another signal such as SIGABRT or SIGSEGV, and one that logs `EADDRINUSE` for any other port are the product's failure. A change that takes its own service down still reads as an outage: the service stays down, so the journey is NOT VERIFIED. For anything else the journey depends on, such as a process it started with `context.command` or an outside endpoint, probe it when a check fails and call `context.notVerified(reason)` when it no longer answers. + + - `context.prepared(key, build)` does the preparation many journeys repeat once per run, such as scaffolding and installing a project, and returns the journey's own copy at `/`. The first journey that asks for `key` runs `build(folder)` on the empty folder with its usual steps, after a step `prepared ` that marks them as the build and says whether it was kept. A build is kept until `clean` only when it returns, records at least one check, passes every check, and leaves only files, directories and symlinks in the folder. Every later journey of the run, also in a later `journey` command, gets a fresh copy and one step, `prepared `, that names the journey that built it and holds that build's evidence under `build/`. A build that is not kept is built again by the next journey, and rerunning the journey that built builds again. An absolute symlink into the folder points into each copy; other symlinks keep their target. A file with a hard link outside the folder, such as a package file pnpm links from its store, is linked into each copy rather than copied: `context.edit` gives it a new inode, and a journey that writes it another way replaces it instead of writing it in place. `build` writes only inside the folder it is given: a write elsewhere, such as a sibling under `` or the repository, stays with the journey that built. Two concurrent `journey` commands that reach the same key at once both build, and the first to finish is kept. A project `build` creates with `scaffold` starts each copy as `scaffold` starts one: a copy with no installed module gets a module cache an earlier passing journey kept, and a passing journey keeps the registry modules installed into its copy. The commands `build` runs get a `HOME` of their own that is deleted when it ends: redo after `prepared` anything a later step needs in `HOME`, such as `ajs config set git`. Pass the same `build` for a key in every journey. - `env`, `setup` (commands replacing the build), `userFacing` and `shared` (globs replacing the kind's default; `shared` lists the files whose change runs every feature), and `formal` (formal checks: `name`, `covers`, `command`, `violation` with an optional `states` group for the counterexample length, and optionally `behaviour` and `baseCommand`). - `template.name`: the listing name users pass to `--template`. - `template.project`: list it under `projectTemplates`. +- `template.module`: a module template, listed under `templates` and created with `ajs module init`; it excludes `template.project`. - `services`: image, port, readiness command and `reset`, as for a module. `new` starts them in Docker on free ports; a journey reads them with `context.service(name)` and `context.native(...)` and passes their endpoints to the project it starts through the `env` of `context.dev`. +- `context.dev(name, project, { command })`: a template that is not an `ajs` project starts with its own command, such as `["node", "ace", "serve"]`, in place of `ajs project dev`. The harness records its PID, so `context.stop` and `clean` stop it, and `template.probe` decides when it is ready. Leave `scaffold-dev` and `scaffold-checks` out of its features: both run `ajs`. - `template.probe`: `path`, `status` and `body` for an HTTP welcome page or endpoint, or `log` and `settleMs` for a template that serves nothing. - `features/README.md`: the index of the features; `map` does not read it as a feature. -- `features/.md`: frontmatter lists `covers:` (source globs), `docs:` (pages a verdict judges) and `journeys:`, then the sections Sub-features, How to get to it (user POV), Driving it with antelopejs-verify, and Gotchas. `features/smoke.md` covers nothing and runs on every verdict; here it runs `scaffold-dev` and `scaffold-checks`. +- `features/.md`: frontmatter lists `covers:` (source globs), `docs:` (pages a verdict judges) and `journeys:`, then the sections Sub-features, How to get to it (user POV), Driving it with antelopejs-verify, and Gotchas. `features/smoke.md` covers nothing and runs on every verdict; here it runs `scaffold-dev` and `scaffold-checks`, or, for a template that is not an `ajs` project, its own journeys that start it with `context.dev(name, project, { command })`. ```bash node_modules/.bin/antelopejs-verify map @@ -126,7 +132,7 @@ The skill is not Trusted yet. It becomes Trusted when it passes a shadow run of ## Gotchas -- `ajs project init` leaves out `.agents/` and `.claude/skills/`, so the scaffolded project carries neither this skill nor anything else under `.agents/`. Never expect either folder there. +- `ajs project init` and `ajs module init` leave out `.agents/` and `.claude/skills/`, so the scaffolded project or module carries neither this skill nor anything else under `.agents/`. Never expect either folder there. - Every file a user gets is user-facing here, tests included; only `.github/` and `.claude/` outside `.claude/skills/` are not. - Runs live under the system temp folder (`TMPDIR`), and the shared caches under `~/.cache/antelopejs-verify`. `new` deletes every run no command touched for 7 days, unless a process it recorded is still alive. - A repository that outgrows this skill runs `antelopejs-sync --verify custom` and keeps its own `.agents/skills/verify-/`. diff --git a/AGENTS.md b/AGENTS.md index c9f2df2..ae6452b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ An AntelopeJS module built from the `template-module-typescript` template. `ajs ## Checks -Finish every change with `pnpm run checks`: typecheck, lint, format, knip, `pnpm test`, size, ratchet and sync, printing only what failed. `guardrails.ratchet.json` is empty, so every lint finding fails `pnpm run lint`. Its numbers only go down: never add an entry to it. The pre-commit hook formats, lints, size-checks and ratchets the files you stage, and the pre-push hook runs `typecheck`. +Finish every change with `pnpm run checks`: typecheck, lint, format, knip, `pnpm test`, size, suppressions, sync and the lockfile checks, printing only what failed. `guardrails.suppressions.json` records no suppression, so every lint finding fails `pnpm run lint`. The pre-commit hook formats, lints, size-checks and ratchets the files you stage, and the pre-push hook runs `typecheck`. The scripts, hooks and CI expect pnpm (`packageManager` in `package.json`); a module created with another `--pm` must switch to pnpm. `pnpm.onlyBuiltDependencies` is empty, so no dependency runs install scripts; this stops lefthook's own `postinstall` from installing hooks, so only `prepare` installs them, and only when this directory is the repository root. `ajs module init` installs dependencies before `git init`, so run `pnpm install` once in the new repository to install the hooks, then make the first commit, whose hook formats the `package.json` the CLI rewrote. @@ -78,6 +78,7 @@ Verify a boundary against the imports a developer or agent would plausibly write | String-keyed object tables are read through own keys only | `antelopejs/no-inherited-key-lookup` | | A type guard checks each member it narrows to; keys with `Object.hasOwn` | `antelopejs/no-presence-only-type-guard` | | A type guard checks the nested elements and values it claims | review; no tool | +| `Object.assign` writes no `unknown`, `any` or mistyped value over a field | `antelopejs/no-unsafe-object-assign` | | Every file type-aware lint reads is listed by a tsconfig | `antelopejs/require-tsconfig-include` | | Params ≤ 5, function ≤ 120 lines, file ≤ 500 lines, depth ≤ 4, complexity ≤ 20 | `eslint/max-*`, `eslint/complexity` | | Cleanup preserves declarations, runtime exports and DMS auto-imports | `antelopejs-api-diff ` | diff --git a/README.md b/README.md index d3b5647..647fc13 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,12 @@ pnpm dev `pnpm dev` runs the playground project in watch mode (`ajs project run -w -p playground`). It builds both modules, and the playground prints `Hello world` through the `Log` interface. -| Command | What it does | -| ----------------- | ---------------------------------------------------------------------------------------- | -| `pnpm dev` | Runs the playground in watch mode | -| `pnpm build` | Builds the module into `dist/` | -| `pnpm test` | Builds the module, starts it, and runs the `test/*.test.ts` files with `ajs module test` | -| `pnpm run checks` | Runs typecheck, lint, format, knip, the tests, size, ratchet and sync checks | +| Command | What it does | +| ----------------- | ------------------------------------------------------------------------------------------- | +| `pnpm dev` | Runs the playground in watch mode | +| `pnpm build` | Builds the module into `dist/` | +| `pnpm test` | Builds the module, starts it, and runs the `test/*.test.ts` files with `ajs module test` | +| `pnpm run checks` | Runs typecheck, lint, format, knip, the tests, size, suppressions, sync and lockfile checks | ## Make it yours diff --git a/guardrails.suppressions.json b/guardrails.suppressions.json index 6936c18..40a7d3a 100644 --- a/guardrails.suppressions.json +++ b/guardrails.suppressions.json @@ -2,5 +2,7 @@ "rules": {}, "tsExpectError": 0, "tsIgnore": 0, - "safety": 0 + "safety": 0, + "config": {}, + "agentFiles": true } diff --git a/package.json b/package.json index 65245b9..42f73f7 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@antelopejs/core": "^1.21.1", - "@antelopejs/tooling-configs": "^0.4.0", + "@antelopejs/tooling-configs": "^0.5.0", "@types/mocha": "^10.0.10", "@types/node": "^22.19.15", "knip": "^6.34.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ca20c9..b120539 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,8 +16,8 @@ importers: specifier: ^1.21.1 version: 1.21.1 '@antelopejs/tooling-configs': - specifier: ^0.4.0 - version: 0.4.0(knip@6.43.0)(oxfmt@0.66.0)(oxlint@1.81.0(oxlint-tsgolint@7.0.2005)) + specifier: ^0.5.0 + version: 0.5.0(knip@6.43.0)(oxfmt@0.66.0)(oxlint@1.81.0(oxlint-tsgolint@7.0.2005)) '@types/mocha': specifier: ^10.0.10 version: 10.0.10 @@ -56,8 +56,8 @@ packages: '@antelopejs/interface-core@0.4.0': resolution: {integrity: sha512-IPX6dtt3KHknclN4mLOYeRYL2Odz1Pts+dtaZul62NghsBUX/0OFHD4Qhoaf+Beva4hjInbaF918ajr4Z5HVWQ==} - '@antelopejs/tooling-configs@0.4.0': - resolution: {integrity: sha512-qmi9CD5OZkWgIFGYZTQ63eyFBMEqGfY8967smEKyQVM0WW1n7kybqeG0ycB0wP+zgT4/LHehF6h7byLvTMBE1A==} + '@antelopejs/tooling-configs@0.5.0': + resolution: {integrity: sha512-vTZyx0FpSzArjSdb119i+CTGPsYe903/D/JY+0KVUgBjdw/2+LTRt58NUinDtvnf9H18uAlF2xwWJXsSPTv/6A==} engines: {node: '>=22.18.0'} hasBin: true peerDependencies: @@ -1328,7 +1328,7 @@ snapshots: dependencies: reflect-metadata: 0.2.2 - '@antelopejs/tooling-configs@0.4.0(knip@6.43.0)(oxfmt@0.66.0)(oxlint@1.81.0(oxlint-tsgolint@7.0.2005))': + '@antelopejs/tooling-configs@0.5.0(knip@6.43.0)(oxfmt@0.66.0)(oxlint@1.81.0(oxlint-tsgolint@7.0.2005))': dependencies: '@oxlint/plugins': 1.81.0 dts-bundle-generator: 9.5.1