@crvouga/atlas 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog: @crvouga/atlas
2
2
 
3
- ## 0.6.0 (2026-10-15)
3
+ ## 0.6.1 (2026-10-15)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - make consumer integration reproducible (#9) ([bfd4e0c](https://github.com/crvouga/atlas/commit/bfd4e0c1c804d1f11fce3299e609af86c50e4732))
8
+
9
+ ## 0.6.0 (2026-10-14)
4
10
 
5
11
  ### Features
6
12
 
package/README.md CHANGED
@@ -40,8 +40,19 @@ backends, and tells you in one command whether the product still does what it wa
40
40
  > [using Atlas from another project](docs/dogfooding.md) and [reporting issues](docs/reporting-issues.md).
41
41
  > Releases are automatic: see [releasing](https://github.com/crvouga/atlas/blob/main/docs/releasing.md).
42
42
 
43
+ ## Given this repo link to integrate Atlas?
44
+
45
+ Start with **[the agent integration playbook](docs/agents.md#given-only-this-repository-link)** in your own
46
+ project. It covers choosing an integration, installing with your package manager, finding docs for your installed
47
+ version, running without a server, connecting real suites, and proving completion. **[Verify Atlas locally](docs/quickstart.md)**
48
+ is a copyable walkthrough with expected JSON and exit codes; it needs no credentials or existing test suite.
49
+ The machine-readable index is [llms.txt](llms.txt), and [llms-full.txt](llms-full.txt) contains the guides in one file.
50
+
43
51
  ## Run this repository's example
44
52
 
53
+ For contributors working in an Atlas checkout with dependencies installed (`bun install --frozen-lockfile`).
54
+ Consumer projects install the npm package instead; they do not need to clone or build this repository.
55
+
45
56
  Use two terminals:
46
57
 
47
58
  ```sh
@@ -64,20 +75,21 @@ Atlas is built to be integrated and operated by agents, with nothing to click an
64
75
  - **`atlas init`** detects a project's test runners, specs and CI and prints a plan (`--format json`) of the exact commands to
65
76
  run each suite and send its results; `--write` creates the CI workflow and an `AGENTS.md` section. Running it twice
66
77
  changes nothing.
67
- - **Every command prints JSON on request** (`--format json`), every document has a published JSON Schema, exit codes mean the
68
- same everywhere (`0` worked, `1` the answer is no, `2` the command was wrong), and every operation is safe to repeat.
78
+ - **Init and report commands print JSON on request** (`--format json`) with published JSON Schemas and consistent
79
+ exit codes (`0` worked, `1` the answer is no, `2` the command was wrong). Reapplying init or publishing identical evidence is safe.
69
80
  - **The server describes itself:** `GET /api/openapi.json` (OpenAPI 3.1) and `GET /llms.txt` (a guide for an agent given only
70
81
  the address) need no token; errors are RFC 9457 problem documents that say what to do.
71
82
  - **Documentation is generated from the code beside it:** `llms.txt` / `llms-full.txt` (also in the package), the TypeScript
72
83
  reference in [`docs/reference/`](docs/reference/server.md), the CLI and HTTP references, and the settings tables; tests fail when
73
84
  any of it drifts.
74
85
 
75
- Start with **[Atlas for AI agents](docs/agents.md)**.
86
+ Start with **[Atlas for AI agents](docs/agents.md)**. A [copyable integration brief](docs/agents.md#an-integration-brief-you-can-give-an-agent)
87
+ lets a user hand an agent this repository URL and a concrete definition of done.
76
88
 
77
89
  ## Integrate in five minutes
78
90
 
79
91
  ```sh
80
- bun add -d @crvouga/atlas # or npm install -D / pnpm add -D; runs on Bun and Node 22.18+
92
+ bun add -d @crvouga/atlas # or npm install -D / pnpm add -D
81
93
 
82
94
  # 1. Each suite publishes a layer of what it ran: a JUnit or CTRF file, an Atlas run, or a checks file.
83
95
  bunx atlas report publish --store atlas-reports --producer web-e2e --junit out.xml --specs ./specs --environment desktop-web
@@ -105,7 +117,11 @@ bunx atlas report status --store all --specs ./specs --require-environments desk
105
117
  ## One package
106
118
 
107
119
  Atlas is a single npm package. Each part is its own import path, so a bundler keeps only what you
108
- import. It runs on Bun and on Node 22.18 or later, with any package manager:
120
+ import. Use your project's package manager:
121
+
122
+ <!-- generated:runtime-requirements -->
123
+ Supported runtimes (from `package.json`): **node `>=22.18.0`** or **bun `>=1.2.0`**. Node consumers do not need Bun unless they use the Bun WebView driver.
124
+ <!-- /generated:runtime-requirements -->
109
125
 
110
126
  ```sh
111
127
  bun add -d @crvouga/atlas
@@ -19,6 +19,7 @@ import { InitPlanSchema } from '../../schema/src/cli-results.js';
19
19
  import { collectSpecFiles, specKindOf } from './directory-outline.js';
20
20
  import { httpStore } from './http-store.js';
21
21
  import { serverToken } from './store-url.js';
22
+ import { PACKAGE_MANAGERS } from './package-managers.js';
22
23
  const BEGIN = '<!-- atlas:begin -->';
23
24
  const END = '<!-- atlas:end -->';
24
25
  function defaultGit(args, cwd) {
@@ -50,9 +51,6 @@ function packageManagerOf(directory, pkg) {
50
51
  const declared = typeof pkg?.packageManager === 'string' ? pkg.packageManager.split('@')[0] : null;
51
52
  return declared === 'bun' || declared === 'pnpm' || declared === 'yarn' || declared === 'npm' ? declared : pkg ? 'npm' : null;
52
53
  }
53
- /** How the package manager runs a package's binary without installing it: `bunx`, `pnpm dlx`, `yarn dlx`, `npx`. */
54
- const EXEC = { bun: 'bunx', pnpm: 'pnpm dlx', yarn: 'yarn dlx', npm: 'npx' };
55
- const INSTALL_DEV = { bun: 'bun add -d', pnpm: 'pnpm add -D', yarn: 'yarn add -D', npm: 'npm install -D' };
56
54
  /** The spec files of a project by kind, and the folder to pass as `--specs`. */
57
55
  async function specsOf(directory) {
58
56
  const candidates = ['specs', 'spec', 'features', 'docs/specs', 'test/features', 'tests/features'];
@@ -118,7 +116,7 @@ function readdirSafe(directory) {
118
116
  /** The suite for a detected runner: how to run it so it writes a JUnit file (or the run manifest), and how to publish it. */
119
117
  function suiteOf(detected, context) {
120
118
  const specs = context.specs ? ` --specs ${context.specs}` : '';
121
- const publishCommand = (producer, input, environment) => `${context.exec} @crvouga/atlas report publish --producer ${producer} ${input}${specs}${environment ? ` --environment ${environment}` : ''}`;
119
+ const publishCommand = (producer, input, environment) => `${context.exec} atlas report publish --producer ${producer} ${input}${specs}${environment ? ` --environment ${environment}` : ''}`;
122
120
  const make = (id, output, run, environment, prerequisites = [], format = 'junit', stream = null) => ({
123
121
  id, runner: detected.runner, evidence: detected.evidence, run, output, format, environment,
124
122
  publish: publishCommand(id, format === 'manifest' ? `--manifest ${output} --upload-run` : `--${format} ${output}`, environment), stream, prerequisites
@@ -136,7 +134,7 @@ function suiteOf(detected, context) {
136
134
  case 'bun-test': return make('bun-test', out('bun'), `bun test --reporter=junit --reporter-outfile=${out('bun')}`, null);
137
135
  case 'pytest': return make('pytest', out('pytest'), `pytest --junitxml=${out('pytest')}`, null);
138
136
  case 'go-test': return make('go-test', out('go'), `go test -v ./... 2>&1 | go-junit-report > ${out('go')}`, null, ['go install github.com/jstemmer/go-junit-report/v2@latest']);
139
- default: return make('statecharts', 'atlas-runs/latest/manifest.json', `${context.exec} @crvouga/atlas run --mode fast`, null, [], 'manifest');
137
+ default: return make('statecharts', 'atlas-runs/latest/manifest.json', `${context.exec} atlas run --mode fast`, null, [], 'manifest');
140
138
  }
141
139
  }
142
140
  /** A suite that does not set its own exit-code handling must still publish: a failure is evidence. */
@@ -144,7 +142,7 @@ function githubWorkflow(suites, context) {
144
142
  const setup = context.manager === 'bun'
145
143
  ? ' - uses: oven-sh/setup-bun@v2\n - run: bun install --frozen-lockfile\n'
146
144
  : ` - uses: actions/setup-node@v4\n with: { node-version: 22 }\n - run: ${context.manager === 'pnpm' ? 'corepack enable && pnpm install --frozen-lockfile' : context.manager === 'yarn' ? 'corepack enable && yarn install --immutable' : 'npm ci'}\n`;
147
- const exec = EXEC[context.manager ?? 'npm'];
145
+ const exec = PACKAGE_MANAGERS[context.manager ?? 'npm'].exec;
148
146
  const jobs = suites.map(suite => ` ${suite.id.replace(/[^a-z0-9]+/g, '-')}:
149
147
  runs-on: ubuntu-latest
150
148
  steps:
@@ -157,13 +155,15 @@ ${setup} # A failing suite must still publish: a failure is evidence. The j
157
155
  `).join('\n');
158
156
  return `# Sends every suite's results to the project's Atlas server and gates on the combined report.
159
157
  # Generated by \`atlas init\`; safe to edit. Needs the repository variable ATLAS_URL (https://atlas.example.com) and the
160
- # secrets ATLAS_PUBLISH_TOKEN (a \`publish\` token) and ATLAS_READ_TOKEN (a \`read\` token) of that server.
158
+ # variable ATLAS_PROJECT (a stable project slug), and secrets ATLAS_PUBLISH_TOKEN (a \`publish\` token) and
159
+ # ATLAS_READ_TOKEN (a \`read\` token) of that server.
161
160
  name: atlas
162
161
  on: [push, pull_request]
163
162
  permissions:
164
163
  contents: read
165
164
  env:
166
165
  ATLAS_STORE: \${{ vars.ATLAS_URL }}
166
+ ATLAS_PROJECT: \${{ vars.ATLAS_PROJECT }}
167
167
  ATLAS_TOKEN: \${{ secrets.ATLAS_PUBLISH_TOKEN }}
168
168
  jobs:
169
169
  ${jobs}
@@ -175,26 +175,27 @@ ${jobs}
175
175
  ATLAS_TOKEN: \${{ secrets.ATLAS_READ_TOKEN }}
176
176
  steps:
177
177
  - uses: actions/checkout@v4
178
- - uses: oven-sh/setup-bun@v2
178
+ ${setup.trimEnd()}
179
179
  # This branch laid over ${context.defaultBranch}: what this run did not re-run is inherited from ${context.defaultBranch}'s latest
180
180
  # results, and a laptop's work in progress or another branch's results cannot turn this red.
181
- - run: bunx @crvouga/atlas report status --branch "\${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" --branch ${context.defaultBranch} --ignore-modified${context.specs ? ' --require-all-specs' : ''}
181
+ - run: ${exec} atlas report status --branch "\${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" --branch ${context.defaultBranch} --ignore-modified${context.specs ? ' --require-all-specs' : ''}
182
182
  `;
183
183
  }
184
184
  /** The `AGENTS.md` section: what Atlas is in this project and the commands to use, replaced between its markers. */
185
- function agentsSection(plan) {
185
+ function agentsSection(plan, exec) {
186
186
  const url = plan.server.url ?? '<the project\'s Atlas server>';
187
187
  const lines = [
188
188
  BEGIN,
189
189
  '## Atlas: test evidence for this project',
190
190
  '',
191
191
  `This project sends the results of its test suites to an [Atlas](https://github.com/crvouga/atlas) server (${url}), which shows everyone one combined, live report. A check is identified by a stable id; the newest completed result of each check wins; unknown is never a pass. Set \`ATLAS_STORE=${url}\` and \`ATLAS_TOKEN\` (a \`publish\` token for sending, a \`read\` token for asking).`,
192
+ 'Set `ATLAS_PROJECT` to the same stable project slug locally and in CI; this selects the project on a shared node. Use the installed `@crvouga/atlas` and the project\'s lockfile. Integration guide: https://github.com/crvouga/atlas/blob/main/docs/agents.md (version-matched copy: `node_modules/@crvouga/atlas/docs/agents.md`).',
192
193
  '',
193
194
  '- **Machine-readable API:** `' + `${url}/api/openapi.json` + '` (OpenAPI 3.1). **Guide for agents:** `' + `${url}/llms.txt` + '`.',
194
195
  '- **After you change behaviour,** run the suite, then send its results (publish even when it fails: a failure is evidence).',
195
196
  ...plan.suites.flatMap(suite => ['', ` - **${suite.id}**: \`${suite.run}\``, ` then \`${suite.publish}\`` + (suite.stream ? ` (or stream: \`${suite.stream}\`)` : '')]),
196
197
  '',
197
- `- **Is it passing?** \`bunx @crvouga/atlas report status --branch ${plan.git.defaultBranch} --ignore-modified --format json\` (exit 0 passes, 1 does not, 2 means the command was wrong). The failing checks are in \`failing\`.`,
198
+ `- **Is it passing?** \`${exec} atlas report status --branch ${plan.git.defaultBranch} --ignore-modified --format json\` (exit 0 passes, 1 does not, 2 means the command was wrong). The failing checks are in \`failing\`.`,
198
199
  plan.specs.directory ? `- **Specs** live in \`${plan.specs.directory}/\` (${Object.entries(plan.specs.kinds).map(([kind, count]) => `${count} ${kind}`).join(', ')}). Results are laid over them by path.` : '- No spec files were found. Pass `--spec <path>` or `--specs <dir>` to `publish` to link checks to specs.',
199
200
  '- Keep each check\'s `id` stable across runs: that is what lets a new result replace an old one.',
200
201
  '- Evidence (screenshots, videos, traces, logs) rides on checks: https://github.com/crvouga/atlas/blob/main/docs/extending.md',
@@ -228,13 +229,14 @@ export async function planInit(options) {
228
229
  const env = options.env ?? {};
229
230
  const git = options.git ?? defaultGit;
230
231
  const pkg = readJson(path.join(directory, 'package.json'));
232
+ const dependencies = { ...pkg?.dependencies, ...pkg?.devDependencies };
231
233
  const manager = packageManagerOf(directory, pkg);
232
234
  const specs = await specsOf(directory);
233
235
  const configured = env.ATLAS_STORE?.split(/[\n,]+/).map(item => item.trim()).find(item => /^https?:\/\//i.test(item));
234
236
  const serverUrl = (options.server ?? configured ?? null)?.replace(/\/+$/, '') ?? null;
235
237
  const warnings = [];
236
- const exec = EXEC[manager ?? 'npm'];
237
- const context = { exec, install: INSTALL_DEV[manager ?? 'npm'], specs: specs.directory, server: serverUrl };
238
+ const { exec, installDev } = PACKAGE_MANAGERS[manager ?? 'npm'];
239
+ const context = { exec, install: installDev, specs: specs.directory, server: serverUrl };
238
240
  const suites = runnersOf(directory, pkg).map(found => suiteOf(found, context));
239
241
  if (!suites.length)
240
242
  warnings.push('No test runner was recognised (looked for vitest, jest, playwright, cypress, cucumber, mocha, codeceptjs, node --test, bun test, pytest, go test and Atlas statecharts). Add one suite by hand: run it so it writes a JUnit XML or CTRF file, then `atlas report publish --producer <name> --junit <file>`.');
@@ -252,7 +254,7 @@ export async function planInit(options) {
252
254
  warnings.push(`${ci} detected: no workflow is generated for it. In its jobs run each suite, then its publish command (with ATLAS_STORE and ATLAS_TOKEN set from secrets), then the gate command from AGENTS.md.`);
253
255
  const base = { suites, specs, server: { url: serverUrl, tokenEnv: 'ATLAS_TOKEN' }, git: { branch, defaultBranch } };
254
256
  const wanted = [
255
- { path: 'AGENTS.md', purpose: 'Tells every agent working in this repository how to send and read test evidence.', next: current => withSection(current, agentsSection(base)) }
257
+ { path: 'AGENTS.md', purpose: 'Tells every agent working in this repository how to send and read test evidence.', next: current => withSection(current, agentsSection(base, exec)) }
256
258
  ];
257
259
  if (ci === 'github-actions' && suites.length)
258
260
  wanted.push({ path: '.github/workflows/atlas.yml', purpose: 'Runs each suite in CI, sends its results to the server even when it fails, and gates on the combined report.', next: () => githubWorkflow(suites, { manager, defaultBranch, specs: specs.directory }) });
@@ -291,10 +293,11 @@ export async function planInit(options) {
291
293
  else if (options.verify)
292
294
  warnings.push('--verify needs a server: pass --server or set ATLAS_STORE.');
293
295
  const nextSteps = [
296
+ ...(dependencies['@crvouga/atlas'] ? [] : [`Install Atlas in this project before running the planned commands: ${context.install} @crvouga/atlas. Commit the manifest and lockfile so local runs and CI use the same version.`]),
294
297
  ...(serverUrl ? [] : ['Decide which server to send results to (run one with `atlas serve`), then run this again with --server <url>.']),
295
298
  ...(options.write ? [] : ['Review the plan, then run again with --write to create the files in `files`.']),
296
299
  ...suites.flatMap(suite => suite.prerequisites.map(command => `Install what ${suite.id} needs: ${command}`)),
297
- ...(serverUrl ? [`Set ATLAS_STORE=${serverUrl} and ATLAS_TOKEN (a publish token) in CI and on each machine that should send results.`, 'Run `atlas report doctor` to prove the server can be written to and read.'] : []),
300
+ ...(serverUrl ? [`Set ATLAS_STORE=${serverUrl}, ATLAS_PROJECT (the same stable project slug) and ATLAS_TOKEN (a publish token) in CI and on each machine that should send results.`, 'Run `atlas report doctor` to prove the server can be written to and read.'] : []),
298
301
  ...suites.map(suite => `Prove ${suite.id}: run \`${suite.run}\`, then \`${suite.publish}\`, then open ${serverUrl ?? 'the server'} and find its results.`)
299
302
  ];
300
303
  const ok = verification ? verification.reachable && verification.canPublish && verification.error === null : true;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @module Consumer package-manager commands
3
+ *
4
+ * One source for commands emitted by init, the running server's agent guide, and generated documentation.
5
+ * Execute the installed CLI so a consumer keeps the versions selected by its own manifest and lockfile.
6
+ */
7
+ /** Supported consumer package managers, their development install command and installed-binary executor. */
8
+ export declare const PACKAGE_MANAGERS: {
9
+ readonly bun: {
10
+ readonly label: "Bun";
11
+ readonly installDev: "bun add -d";
12
+ readonly exec: "bunx";
13
+ };
14
+ readonly npm: {
15
+ readonly label: "npm";
16
+ readonly installDev: "npm install -D";
17
+ readonly exec: "npm exec --";
18
+ };
19
+ readonly pnpm: {
20
+ readonly label: "pnpm";
21
+ readonly installDev: "pnpm add -D";
22
+ readonly exec: "pnpm exec";
23
+ };
24
+ readonly yarn: {
25
+ readonly label: "Yarn";
26
+ readonly installDev: "yarn add -D";
27
+ readonly exec: "yarn exec";
28
+ };
29
+ };
30
+ /**
31
+ * Markdown installation table for consumer and server agent documentation.
32
+ * @example
33
+ * const markdown = packageManagerTable(); // the same commands `atlas init` emits
34
+ */
35
+ export declare function packageManagerTable(): string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @module Consumer package-manager commands
3
+ *
4
+ * One source for commands emitted by init, the running server's agent guide, and generated documentation.
5
+ * Execute the installed CLI so a consumer keeps the versions selected by its own manifest and lockfile.
6
+ */
7
+ /** Supported consumer package managers, their development install command and installed-binary executor. */
8
+ export const PACKAGE_MANAGERS = {
9
+ bun: { label: 'Bun', installDev: 'bun add -d', exec: 'bunx' },
10
+ npm: { label: 'npm', installDev: 'npm install -D', exec: 'npm exec --' },
11
+ pnpm: { label: 'pnpm', installDev: 'pnpm add -D', exec: 'pnpm exec' },
12
+ yarn: { label: 'Yarn', installDev: 'yarn add -D', exec: 'yarn exec' }
13
+ };
14
+ /**
15
+ * Markdown installation table for consumer and server agent documentation.
16
+ * @example
17
+ * const markdown = packageManagerTable(); // the same commands `atlas init` emits
18
+ */
19
+ export function packageManagerTable() {
20
+ return ['| Package manager | Install | Run the installed CLI |', '| --- | --- | --- |',
21
+ ...Object.values(PACKAGE_MANAGERS).map(manager => `| ${manager.label} | \`${manager.installDev} @crvouga/atlas\` | \`${manager.exec} atlas\` |`)
22
+ ].join('\n');
23
+ }
@@ -147,10 +147,10 @@ export declare const InitPlanSchema: z.ZodObject<{
147
147
  project: z.ZodObject<{
148
148
  name: z.ZodNullable<z.ZodString>;
149
149
  packageManager: z.ZodNullable<z.ZodEnum<{
150
- bun: "bun";
150
+ npm: "npm";
151
151
  pnpm: "pnpm";
152
+ bun: "bun";
152
153
  yarn: "yarn";
153
- npm: "npm";
154
154
  }>>;
155
155
  languages: z.ZodArray<z.ZodString>;
156
156
  }, z.core.$loose>;
@@ -208,3 +208,234 @@ export declare const InitPlanSchema: z.ZodObject<{
208
208
  nextSteps: z.ZodArray<z.ZodString>;
209
209
  warnings: z.ZodArray<z.ZodString>;
210
210
  }, z.core.$loose>;
211
+ /**
212
+ * JSON command contracts, shared by schema publication, agent documentation and executable command checks.
213
+ * A new result needs an entry here and a runtime scenario in the documentation contract tests.
214
+ */
215
+ export declare const CLI_RESULTS: {
216
+ readonly init: {
217
+ readonly command: "atlas init";
218
+ readonly schemaName: "init-plan";
219
+ readonly schema: z.ZodObject<{
220
+ ok: z.ZodBoolean;
221
+ directory: z.ZodString;
222
+ project: z.ZodObject<{
223
+ name: z.ZodNullable<z.ZodString>;
224
+ packageManager: z.ZodNullable<z.ZodEnum<{
225
+ npm: "npm";
226
+ pnpm: "pnpm";
227
+ bun: "bun";
228
+ yarn: "yarn";
229
+ }>>;
230
+ languages: z.ZodArray<z.ZodString>;
231
+ }, z.core.$loose>;
232
+ git: z.ZodObject<{
233
+ branch: z.ZodNullable<z.ZodString>;
234
+ defaultBranch: z.ZodString;
235
+ }, z.core.$loose>;
236
+ ci: z.ZodEnum<{
237
+ none: "none";
238
+ "github-actions": "github-actions";
239
+ "gitlab-ci": "gitlab-ci";
240
+ circleci: "circleci";
241
+ }>;
242
+ server: z.ZodObject<{
243
+ url: z.ZodNullable<z.ZodString>;
244
+ tokenEnv: z.ZodLiteral<"ATLAS_TOKEN">;
245
+ }, z.core.$loose>;
246
+ specs: z.ZodObject<{
247
+ directory: z.ZodNullable<z.ZodString>;
248
+ kinds: z.ZodRecord<z.ZodString, z.ZodNumber>;
249
+ }, z.core.$loose>;
250
+ suites: z.ZodArray<z.ZodObject<{
251
+ id: z.ZodString;
252
+ runner: z.ZodString;
253
+ evidence: z.ZodString;
254
+ run: z.ZodString;
255
+ output: z.ZodString;
256
+ format: z.ZodEnum<{
257
+ junit: "junit";
258
+ ctrf: "ctrf";
259
+ manifest: "manifest";
260
+ }>;
261
+ environment: z.ZodNullable<z.ZodString>;
262
+ publish: z.ZodString;
263
+ stream: z.ZodNullable<z.ZodString>;
264
+ prerequisites: z.ZodArray<z.ZodString>;
265
+ }, z.core.$loose>>;
266
+ files: z.ZodArray<z.ZodObject<{
267
+ path: z.ZodString;
268
+ action: z.ZodEnum<{
269
+ create: "create";
270
+ update: "update";
271
+ unchanged: "unchanged";
272
+ }>;
273
+ purpose: z.ZodString;
274
+ content: z.ZodString;
275
+ }, z.core.$loose>>;
276
+ written: z.ZodArray<z.ZodString>;
277
+ verification: z.ZodNullable<z.ZodObject<{
278
+ reachable: z.ZodBoolean;
279
+ role: z.ZodNullable<z.ZodString>;
280
+ canPublish: z.ZodBoolean;
281
+ error: z.ZodNullable<z.ZodString>;
282
+ }, z.core.$loose>>;
283
+ nextSteps: z.ZodArray<z.ZodString>;
284
+ warnings: z.ZodArray<z.ZodString>;
285
+ }, z.core.$loose>;
286
+ };
287
+ readonly publish: {
288
+ readonly command: "atlas report publish";
289
+ readonly schemaName: "publish-result";
290
+ readonly schema: z.ZodObject<{
291
+ ok: z.ZodLiteral<true>;
292
+ store: z.ZodString;
293
+ layer: z.ZodObject<{
294
+ id: z.ZodString;
295
+ producer: z.ZodString;
296
+ environment: z.ZodNullable<z.ZodString>;
297
+ checks: z.ZodNumber;
298
+ passed: z.ZodNumber;
299
+ failed: z.ZodNumber;
300
+ notRun: z.ZodNumber;
301
+ linkedToSpec: z.ZodNumber;
302
+ freshness: z.ZodOptional<z.ZodEnum<{
303
+ unknown: "unknown";
304
+ modified: "modified";
305
+ }>>;
306
+ attachmentFiles: z.ZodNumber;
307
+ attachmentBytes: z.ZodNumber;
308
+ }, z.core.$loose>;
309
+ specSet: z.ZodOptional<z.ZodObject<{
310
+ id: z.ZodString;
311
+ files: z.ZodNumber;
312
+ branch: z.ZodNullable<z.ZodString>;
313
+ }, z.core.$loose>>;
314
+ run: z.ZodOptional<z.ZodObject<{
315
+ id: z.ZodString;
316
+ files: z.ZodNumber;
317
+ bytes: z.ZodNumber;
318
+ alreadyPublished: z.ZodBoolean;
319
+ }, z.core.$loose>>;
320
+ warnings: z.ZodArray<z.ZodString>;
321
+ }, z.core.$loose>;
322
+ };
323
+ readonly stream: {
324
+ readonly command: "atlas report stream";
325
+ readonly schemaName: "stream-result";
326
+ readonly schema: z.ZodObject<{
327
+ ok: z.ZodBoolean;
328
+ producer: z.ZodString;
329
+ environment: z.ZodNullable<z.ZodString>;
330
+ layers: z.ZodNumber;
331
+ checks: z.ZodNumber;
332
+ lost: z.ZodNumber;
333
+ warnings: z.ZodArray<z.ZodString>;
334
+ }, z.core.$loose>;
335
+ };
336
+ readonly merge: {
337
+ readonly command: "atlas report merge";
338
+ readonly schemaName: "merge-result";
339
+ readonly schema: z.ZodObject<{
340
+ ok: z.ZodLiteral<true>;
341
+ into: z.ZodString;
342
+ copied: z.ZodNumber;
343
+ skipped: z.ZodNumber;
344
+ ignored: z.ZodNumber;
345
+ }, z.core.$loose>;
346
+ };
347
+ readonly doctor: {
348
+ readonly command: "atlas report doctor";
349
+ readonly schemaName: "doctor-result";
350
+ readonly schema: z.ZodObject<{
351
+ ok: z.ZodBoolean;
352
+ stores: z.ZodArray<z.ZodObject<{
353
+ id: z.ZodString;
354
+ checks: z.ZodArray<z.ZodObject<{
355
+ name: z.ZodString;
356
+ ok: z.ZodBoolean;
357
+ detail: z.ZodString;
358
+ fix: z.ZodOptional<z.ZodString>;
359
+ }, z.core.$loose>>;
360
+ }, z.core.$loose>>;
361
+ }, z.core.$loose>;
362
+ };
363
+ readonly status: {
364
+ readonly command: "atlas report status";
365
+ readonly schemaName: "report-status";
366
+ readonly schema: z.ZodObject<{
367
+ schemaVersion: z.ZodLiteral<1>;
368
+ ok: z.ZodBoolean;
369
+ reasons: z.ZodArray<z.ZodString>;
370
+ checks: z.ZodObject<{
371
+ passed: z.ZodNumber;
372
+ failed: z.ZodNumber;
373
+ running: z.ZodNumber;
374
+ 'not-run': z.ZodNumber;
375
+ total: z.ZodNumber;
376
+ }, z.core.$strip>;
377
+ byEnvironment: z.ZodRecord<z.ZodString, z.ZodObject<{
378
+ passed: z.ZodNumber;
379
+ failed: z.ZodNumber;
380
+ running: z.ZodNumber;
381
+ 'not-run': z.ZodNumber;
382
+ total: z.ZodNumber;
383
+ }, z.core.$strip>>;
384
+ layers: z.ZodNumber;
385
+ backends: z.ZodArray<z.ZodObject<{
386
+ id: z.ZodString;
387
+ label: z.ZodString;
388
+ available: z.ZodBoolean;
389
+ }, z.core.$strip>>;
390
+ notices: z.ZodArray<z.ZodString>;
391
+ specsWithoutEvidence: z.ZodArray<z.ZodString>;
392
+ specs: z.ZodArray<z.ZodObject<{
393
+ path: z.ZodNullable<z.ZodString>;
394
+ title: z.ZodString;
395
+ kind: z.ZodString;
396
+ checks: z.ZodObject<{
397
+ passed: z.ZodNumber;
398
+ failed: z.ZodNumber;
399
+ running: z.ZodNumber;
400
+ 'not-run': z.ZodNumber;
401
+ total: z.ZodNumber;
402
+ }, z.core.$strip>;
403
+ byEnvironment: z.ZodRecord<z.ZodString, z.ZodObject<{
404
+ passed: z.ZodNumber;
405
+ failed: z.ZodNumber;
406
+ running: z.ZodNumber;
407
+ 'not-run': z.ZodNumber;
408
+ total: z.ZodNumber;
409
+ }, z.core.$strip>>;
410
+ }, z.core.$loose>>;
411
+ failing: z.ZodArray<z.ZodObject<{
412
+ spec: z.ZodNullable<z.ZodString>;
413
+ check: z.ZodString;
414
+ title: z.ZodString;
415
+ status: z.ZodEnum<{
416
+ failed: "failed";
417
+ running: "running";
418
+ }>;
419
+ environment: z.ZodNullable<z.ZodString>;
420
+ producer: z.ZodString;
421
+ branch: z.ZodNullable<z.ZodString>;
422
+ sha: z.ZodNullable<z.ZodString>;
423
+ layer: z.ZodString;
424
+ backend: z.ZodString;
425
+ runUrl: z.ZodNullable<z.ZodString>;
426
+ detail: z.ZodString;
427
+ }, z.core.$loose>>;
428
+ }, z.core.$loose>;
429
+ };
430
+ readonly error: {
431
+ readonly command: "a command that was wrong (standard error)";
432
+ readonly schemaName: "command-error";
433
+ readonly schema: z.ZodObject<{
434
+ ok: z.ZodLiteral<false>;
435
+ error: z.ZodObject<{
436
+ message: z.ZodString;
437
+ code: z.ZodLiteral<"usage">;
438
+ }, z.core.$loose>;
439
+ }, z.core.$loose>;
440
+ };
441
+ };
@@ -13,6 +13,7 @@
13
13
  * @see docs/agents.md
14
14
  */
15
15
  import { z } from 'zod/v4';
16
+ import { ReportStatusSchema } from './layer.js';
16
17
  /** What was written for one layer by `atlas report publish`. */
17
18
  export const PublishedLayerSchema = z.looseObject({
18
19
  id: z.string().describe('The layer id: 64 hex digits, the SHA-256 of its content. Publishing the same input again gives the same id'),
@@ -122,3 +123,16 @@ export const InitPlanSchema = z.looseObject({
122
123
  nextSteps: z.array(z.string()).describe('What to do next, in order, as plain instructions an agent can follow'),
123
124
  warnings: z.array(z.string())
124
125
  });
126
+ /**
127
+ * JSON command contracts, shared by schema publication, agent documentation and executable command checks.
128
+ * A new result needs an entry here and a runtime scenario in the documentation contract tests.
129
+ */
130
+ export const CLI_RESULTS = {
131
+ init: { command: 'atlas init', schemaName: 'init-plan', schema: InitPlanSchema },
132
+ publish: { command: 'atlas report publish', schemaName: 'publish-result', schema: PublishResultSchema },
133
+ stream: { command: 'atlas report stream', schemaName: 'stream-result', schema: StreamResultSchema },
134
+ merge: { command: 'atlas report merge', schemaName: 'merge-result', schema: MergeResultSchema },
135
+ doctor: { command: 'atlas report doctor', schemaName: 'doctor-result', schema: DoctorResultSchema },
136
+ status: { command: 'atlas report status', schemaName: 'report-status', schema: ReportStatusSchema },
137
+ error: { command: 'a command that was wrong (standard error)', schemaName: 'command-error', schema: CommandErrorSchema }
138
+ };
@@ -6,6 +6,7 @@
6
6
  * address, whether it needs a token), so what it says is true of the server that says it, and it points at the
7
7
  * machine-readable description (`/api/openapi.json`) rather than repeating it.
8
8
  */
9
+ import { packageManagerTable } from '../../reports/src/package-managers.js';
9
10
  /** The guide for one server, Markdown. Stable in structure: an agent may read it by its headings. */
10
11
  export function llmsTxt(context) {
11
12
  const { url } = context;
@@ -28,27 +29,35 @@ ${context.open
28
29
 
29
30
  ## Integrate a project (run in the project's repository)
30
31
 
32
+ Install \`@crvouga/atlas\` with the project's existing package manager first. The commands below use Bun;
33
+ substitute the executor for your package manager. Node also runs the CLI; Bun is optional. Read the installed
34
+ \`docs/agents.md\` for the supported runtime versions and full integration playbook.
35
+
36
+ ${packageManagerTable()}
37
+
31
38
  \`\`\`sh
39
+ bun add -d @crvouga/atlas
32
40
  export ATLAS_STORE=${url}${context.open ? '' : '\nexport ATLAS_TOKEN=<a publish token>'}
33
- bunx @crvouga/atlas init --server ${url} # detects test runners and specs, prints a plan (add --json for a machine-readable one)
34
- bunx @crvouga/atlas init --server ${url} --write # writes the CI workflow and the AGENTS.md section the plan names
35
- bunx @crvouga/atlas report doctor # proves the server can be written to and read
41
+ export ATLAS_PROJECT=checkout-service # choose the same stable slug locally and in CI
42
+ bunx atlas init --server ${url} # detects test runners and specs, prints a plan (add --json for a machine-readable one)
43
+ bunx atlas init --server ${url} --write # writes the CI workflow and the AGENTS.md section the plan names
44
+ bunx atlas report doctor # proves the server can be written to and read
36
45
  \`\`\`
37
46
 
38
47
  After each suite, send its results (a JUnit or CTRF file, or JSON lines as checks finish):
39
48
 
40
49
  \`\`\`sh
41
- bunx @crvouga/atlas report publish --producer <suite> --junit out.xml --specs ./specs --environment <env>
42
- my-suite --json | bunx @crvouga/atlas report stream --producer <suite> --environment <env>
50
+ bunx atlas report publish --producer <suite> --junit out.xml --specs ./specs --environment <env>
51
+ my-suite --json | bunx atlas report stream --producer <suite> --environment <env>
43
52
  \`\`\`
44
53
 
45
- Or with no tooling at all: \`POST ${url}/api/checks\` with \`{"producer":"smoke","checks":[{"id":"login","title":"can log in","status":"passed"}]}\`.
54
+ Or with no tooling at all: \`POST ${url}/api/checks?project=<slug>\` with \`{"producer":"smoke","checks":[{"id":"login","title":"can log in","status":"passed"}]}\`.
46
55
 
47
56
  ## Ask whether it passes
48
57
 
49
58
  \`\`\`sh
50
- bunx @crvouga/atlas report status --format json --branch main --ignore-modified # exit 0 pass, 1 does not, 2 wrong command
51
- curl ${context.open ? '' : '-H "authorization: Bearer $ATLAS_TOKEN" '}${url}/api/status # the same verdict; header X-Atlas-Ok: true|false
59
+ bunx atlas report status --format json --branch main --ignore-modified # exit 0 pass, 1 does not, 2 wrong command
60
+ curl ${context.open ? '' : '-H "authorization: Bearer $ATLAS_TOKEN" '}"${url}/api/status?project=$ATLAS_PROJECT" # the same verdict; header X-Atlas-Ok: true|false
52
61
  \`\`\`
53
62
 
54
63
  ## Rules worth knowing