@arjunkhera/atlas 0.3.40 → 0.3.42

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.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: fresh-agent-test
3
+ description: >
4
+ The fresh-agent test that ends onboarding a repo (target design flow 1, step 9): a fresh
5
+ agent does three tasks from the repo's files alone, and the judge crew picks them before
6
+ the run and grades them after. Trigger when onboarding reaches its last step, when the
7
+ owner asks to "run the fresh-agent test" or "judge the onboarding", or when a stage done
8
+ line names the test.
9
+ ---
10
+
11
+ # The fresh-agent test
12
+
13
+ A fresh agent does three tasks from a repo's files alone. If it passes all
14
+ three, the files are enough for an agent with no memory. You, the lead,
15
+ wrote or changed those files, so you never pick a task and never grade one.
16
+ The `atlas:judge` crew does both, each time fresh. `atlas judge` does the
17
+ exact work: the seal, the brief, the run and the check.
18
+
19
+ Design: `docs/design-docs/fresh-agent-judge/` in the Atlas repo.
20
+
21
+ ## Before you start
22
+
23
+ 1. Name the item that the test proves, such as the stage or the onboarding
24
+ item. Call it the test item.
25
+ 2. Make a run folder outside the repo, such as a folder in the session's
26
+ scratch space.
27
+ 3. Read the commit to test: `git rev-parse HEAD` on the branch or the main
28
+ branch that holds the onboarding. Every run uses this commit.
29
+ 4. A run needs a sign-in that an empty home folder can use. A cloud
30
+ session has one. On a Mac, an empty home folder has no login. Set
31
+ `ANTHROPIC_API_KEY` for the run, or run the test from a cloud session.
32
+
33
+ ## Steps
34
+
35
+ 1. **Pick.** Send `atlas:judge` with the job "pick". Give it the repo
36
+ folder, the commit and the run folder. Add the onboarding pull request
37
+ when there is one. Add the task files of earlier tests of this repo.
38
+ Give it nothing else: no hint, no list of what you changed.
39
+ 2. **Seal.** Run `atlas judge seal <run folder>/tasks.md`. Fix nothing
40
+ yourself. On a shape error, send a new pick run with the error lines.
41
+ 3. **Record the seal.** Before any run, call `item_comment` on the test
42
+ item. Put in the seal line, the three runs it picked, and the whole of
43
+ `tasks.md`. When the file is too long for one comment, add a second
44
+ comment with the rest.
45
+ 4. **Run.** For each of the three picked runs, run
46
+ `atlas judge run <run folder>/tasks.md <id> --out <run folder> --repo <repo folder>`.
47
+ Run them one at a time. The command sends the brief itself, so never
48
+ send a task to an agent by another way.
49
+ 5. **Grade.** Send a new `atlas:judge` with the job "grade" and the run
50
+ folder. Never send the pick run's crew again.
51
+ 6. **Check.** Run
52
+ `atlas judge check --tasks <run folder>/tasks.md --verdict <run folder>/verdict.md --seal <hash from step 3> --runs <run folder>`.
53
+ Copy the hash from the tracker comment, not from the file.
54
+ 7. **Record the result.** Call `item_comment` on the test item. Put in the
55
+ output of the check and the whole of `verdict.md`. Link both comments
56
+ from the onboarding page or the pull request body.
57
+
58
+ `check` exits 0 when all three pass, 1 when one fails, and 2 when the
59
+ verdict does not count. On exit 2, send a new grade run with the error
60
+ lines. Never edit `verdict.md` yourself.
61
+
62
+ ## When a task fails
63
+
64
+ 1. A failed task is a gap in the files. List it on the onboarding page.
65
+ Partial readiness still goes ahead (target design, flow 1).
66
+ 2. Fix the files. Then run a new test from step 1. The new pick gets the
67
+ earlier task files, so it picks new tasks. Never rerun the same tasks
68
+ after a fix: that teaches to the test.
69
+
70
+ ## When a run breaks
71
+
72
+ 1. A run that did not finish exits 1 and keeps its folder. You may run that
73
+ one task once more, into a new `--out` folder. Say so in the result
74
+ comment.
75
+ 2. When the test stops for good, call `item_comment` on the test item with
76
+ "abandoned" and the reason. Delete the run folder.
77
+
78
+ ## Never
79
+
80
+ 1. Never pick, grade or edit a task, a report or a verdict.
81
+ 2. Never give a crew or a run a hint about the files.
82
+ 3. Never count a test whose check exits 2.
@@ -56,6 +56,7 @@ say that when you hand over a page.
56
56
  | Review a design or a diff | The three reviewer crews, as sdlc-task says |
57
57
  | Prove a pull request against its definition of done | The `atlas:verifier` crew |
58
58
  | Render a page for the owner | The `atlas:artifact-renderer` crew |
59
+ | Run the fresh-agent test at the end of onboarding: pick, run and grade three tasks | The `atlas:fresh-agent-test` skill, which sends the `atlas:judge` crew |
59
60
  | Onboard an area to tests, review scenario words, write or convert a test, sort a red test, plant faults | The `atlas:tests` skill |
60
61
 
61
62
  A crew reads and reports. It never holds the Atlas tools.
@@ -104,6 +105,7 @@ set. A test keeps the two lists equal.
104
105
  | `atlas design` | `folder` to learn where designs live; `check` before you show a design; `build` to make its page |
105
106
  | `atlas view` | `build --product <id>` makes the project view page of one product, when the owner asks for it or after an Atlas release changes it. Publish it with only the grant the command prints |
106
107
  | `atlas upkeep` | `check` before you ask for a merge: the docs index, dead doc links, and the tracker item a pull request names. The daily upkeep run calls it on the main branch |
108
+ | `atlas judge` | In the fresh-agent test only, as the `atlas:fresh-agent-test` skill says. `seal` after the pick, `brief` to read a sealed ask, `run` for each sealed task, `check` after the grade |
107
109
 
108
110
  A person merges every file that `atlas tooling` writes.
109
111
  A person merges every file that `atlas tests write` writes.
@@ -358,6 +358,31 @@ not counted, and the test passes.
358
358
  After the product is ready, a lost connection, a server exit or a time-out is
359
359
  `fail`. `blocked` is for a guard refusal, a start-up fault and the first-call check.
360
360
 
361
+ ### A test in Python
362
+
363
+ Use the Python library when the runner of the area is Python. Its header,
364
+ `test/atlas/contract.py`, holds a worked example.
365
+
366
+ 1. Name the file `test/scenarios/<name>_test.py`. List a Python runner in
367
+ `runner` of `tests.yaml`. `runner` may be a list, one runner for each
368
+ language.
369
+ 2. Before the import, set `sys.dont_write_bytecode = True`. Put
370
+ `test/atlas` on `sys.path`, and import `scenario` from `contract`.
371
+ Run Python with `-B`: the check refuses bytecode in the test folder.
372
+ Never run it with `-O`, which removes every assert.
373
+ 3. Decorate the body: `@scenario("../../scenarios/<name>.md", adapter=adapter)`.
374
+ 4. Write each check as a `with` block that holds an assert statement:
375
+ `with run.check("<scenario>/e1#<fingerprint>"):`. Use `run.gate` for a gate.
376
+ To keep a short proof, set `proof.value` in `with ... as proof:`.
377
+ 5. Use `run.not_exercised` and `run.wait_for`, in snake case.
378
+ 6. Give the driver of each way in through the adapter (`define_adapter`).
379
+ Build its child environment with `child_env`.
380
+
381
+ The library reads the scenario through node, so the runner needs node and
382
+ python3. It does not yet start an environment, give run secrets, repeat a
383
+ run, ask a judge or ship drivers. Each of these ends `blocked`. Write such a
384
+ scenario with `contract.mjs`.
385
+
361
386
  ### A model in the test
362
387
 
363
388
  A model makes a test vary and costs money. Three parts of the kit handle it.
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ // The bridge for a contract library in another language (design section 8.1).
3
+ // A library in Python, say, must read a scenario exactly as the link check
4
+ // reads it: the same parser, the same fingerprints, the same tests.yaml check
5
+ // and the same guard comparison. So it does not parse the words itself. It
6
+ // runs this file and reads one JSON object:
7
+ //
8
+ // node bridge.mjs load <scenario.md>
9
+ // -> { root, file, environment, evidenceDir, guardRefusal, tests, envSpec, shippedDrivers, wayGuardParts,
10
+ // scenario: { id, title, file, sha256, method, fields, through, ways, repeat,
11
+ // assertions: [{ id, fullId, kind, gate, ways, fp, names, line }],
12
+ // tables: { T1: { name, kind, header, rows, fingerprint, cases } } } }
13
+ //
14
+ // `cases` is every case of a combination table (every combination, or a set
15
+ // that holds every pair), so the other language need not make them again.
16
+ // A fault in the scenario or in tests.yaml prints { error } and exits 1.
17
+ // The environment and the folders come from the same variables as
18
+ // contract.mjs: ATLAS_ENV, CI, ATLAS_EVIDENCE_DIR and ATLAS_GUARDS_FROM.
19
+ import { existsSync, readFileSync } from 'node:fs';
20
+ import { dirname, join, resolve } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { readTestsYaml, checkTestsYaml, compareGuardParts, resolveEnvironments, GUARD_PARTS } from './tests-yaml.mjs';
23
+ import { SHIPPED_DRIVERS } from './drivers/index.mjs';
24
+ import { readScenario, addFingerprints, combinations, pairs } from './scenario.mjs';
25
+
26
+ function findRoot(file) {
27
+ let dir = dirname(file);
28
+ for (;;) {
29
+ if (existsSync(join(dir, 'atlas', 'tests.yaml'))) return dir;
30
+ const up = dirname(dir);
31
+ if (up === dir) throw new Error(`no atlas/tests.yaml above ${file}`);
32
+ dir = up;
33
+ }
34
+ }
35
+
36
+ export function loadForLibrary(path, env = process.env) {
37
+ const file = resolve(String(path));
38
+ const root = findRoot(file);
39
+ const yaml = readTestsYaml(join(root, 'atlas', 'tests.yaml'));
40
+ const findings = checkTestsYaml(yaml);
41
+ if (findings.length) throw new Error(`atlas/tests.yaml: ${findings.map((f) => `line ${f.line}: ${f.message}`).join('; ')}`);
42
+ const sc = readScenario(file);
43
+ if (sc.problems.length) throw new Error(`${path}: ${sc.problems.map((p) => `line ${p.line}: ${p.message}`).join('; ')}`);
44
+ const tests = yaml.value;
45
+ addFingerprints(sc, tests);
46
+ // The same rule as contract.mjs: a branch whose guard parts differ from the main branch copy is refused.
47
+ let guardRefusal = null;
48
+ if (env.ATLAS_GUARDS_FROM) {
49
+ try {
50
+ const changes = compareGuardParts(readFileSync(env.ATLAS_GUARDS_FROM, 'utf8'), yaml.value);
51
+ if (changes.length) guardRefusal = `the guard parts of atlas/tests.yaml differ from the main branch copy (${changes.map((c) => c.part).join(', ')}); the run is refused. A change to a guard part comes to the owner`;
52
+ } catch (error) {
53
+ guardRefusal = `ATLAS_GUARDS_FROM names a file that cannot be read as tests.yaml (${error.code ?? error.message}); the run is refused`;
54
+ }
55
+ }
56
+ const environment = env.ATLAS_ENV ?? (env.CI ? 'ci' : 'local');
57
+ const evidenceDir = env.ATLAS_EVIDENCE_DIR ?? join(root, 'test', 'evidence');
58
+ const tables = {};
59
+ for (const [name, t] of sc.tables) {
60
+ const cases = t.kind === 'combination' ? (t.cover === 'pair' ? pairs(t.dimensions) : combinations(t.dimensions)) : null;
61
+ tables[name] = { name, kind: t.kind, header: t.header ?? null, rows: t.rows ?? [], fingerprint: t.fingerprint, cases };
62
+ }
63
+ return {
64
+ root, file, environment, evidenceDir, guardRefusal, tests,
65
+ // What a library in another language must refuse until it has its own form of the guard.
66
+ shippedDrivers: SHIPPED_DRIVERS,
67
+ wayGuardParts: GUARD_PARTS.filter((p) => p.startsWith('ways-in.*.')).map((p) => p.slice('ways-in.*.'.length)),
68
+ envSpec: resolveEnvironments(tests.environments ?? {})[environment] ?? null,
69
+ scenario: {
70
+ id: sc.id, title: sc.title, file: sc.file, sha256: sc.sha256, method: sc.method,
71
+ names: [...new Set([...sc.start, ...sc.steps, ...sc.assertions].flatMap((x) => x.names ?? []))],
72
+ fields: sc.fields, through: sc.through, ways: sc.ways ?? sc.through, repeat: sc.repeat ?? null,
73
+ assertions: sc.assertions.map((a) => ({ id: a.id, fullId: a.fullId, kind: a.kind, gate: Boolean(a.gate), ways: a.ways ?? sc.through, fp: a.fp, names: a.names ?? [], line: a.line })),
74
+ tables,
75
+ },
76
+ };
77
+ }
78
+
79
+ export function main(argv, write = (text) => process.stdout.write(text)) {
80
+ const [verb, path] = argv;
81
+ if (verb !== 'load' || !path) { process.stderr.write('bridge: use "bridge.mjs load <scenario.md>"\n'); return 2; }
82
+ try {
83
+ write(`${JSON.stringify(loadForLibrary(path))}\n`);
84
+ return 0;
85
+ } catch (error) {
86
+ write(`${JSON.stringify({ error: error.message })}\n`);
87
+ return 1;
88
+ }
89
+ }
90
+
91
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = main(process.argv.slice(2));