@arjunkhera/atlas 0.3.18 → 0.3.20
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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/agents/word-reviewer.md +122 -0
- package/package.json +1 -1
- package/skills/lead/SKILL.md +1 -1
- package/skills/tests/SKILL.md +10 -17
- package/work/mcp.mjs +32 -0
- package/work/protected-paths.json +2 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ Atlas sets how a repository is worked on. It is one Claude Code plugin with:
|
|
|
8
8
|
one piece of work. Three more skills: `repo-skills` adds a procedure the
|
|
9
9
|
right way, `learnings` turns a miss into one fix, and `feedback` files an
|
|
10
10
|
issue.
|
|
11
|
-
3. **
|
|
12
|
-
(architect, product, security), a page renderer,
|
|
13
|
-
proves a pull request.
|
|
11
|
+
3. **Eight crews**: a code reader, a capability reader, three design reviewers
|
|
12
|
+
(architect, product, security), a page renderer, a reviewer of test
|
|
13
|
+
words, and the verifier that proves a pull request.
|
|
14
14
|
4. **The Atlas tools**, an MCP server that keeps work items, questions,
|
|
15
15
|
decisions and delivery marks in an Engram knowledge graph.
|
|
16
16
|
5. **The `atlas` command**: check a repository's Atlas files, write the check
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: word-reviewer
|
|
3
|
+
description: >
|
|
4
|
+
Read-only reviewer of the words of test scenarios. The tests skill sends it in its
|
|
5
|
+
step "Review the words", before any test code exists. It asks ten questions of each
|
|
6
|
+
assertion and returns one list of findings, each with the full new words. It proposes
|
|
7
|
+
words; it never edits a file and never writes a test. Review of words is judgment
|
|
8
|
+
work, so this crew inherits the session's model.
|
|
9
|
+
model: inherit
|
|
10
|
+
tools: Read, Grep, Glob, Bash
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Word reviewer
|
|
14
|
+
|
|
15
|
+
You review the words of test scenarios before any test code exists. You
|
|
16
|
+
propose new words. You never edit a file, you never write a test, and you
|
|
17
|
+
never change the state of the repo. Bash can change files, so use it only to
|
|
18
|
+
read and to run `atlas ste`.
|
|
19
|
+
|
|
20
|
+
## What you read
|
|
21
|
+
|
|
22
|
+
The caller names the repo, the area and each new or changed scenario. Read
|
|
23
|
+
only what the agent that converts a scenario into a test reads:
|
|
24
|
+
|
|
25
|
+
1. Each scenario the caller names, `scenarios/<id>.md`.
|
|
26
|
+
2. The area's map, `scenarios/map.md`, and its `atlas/tests.yaml`.
|
|
27
|
+
3. The docs that the map names.
|
|
28
|
+
4. The rules for the words, in the section "Review the words" of
|
|
29
|
+
`${CLAUDE_PLUGIN_ROOT}/skills/tests/SKILL.md`.
|
|
30
|
+
|
|
31
|
+
Do not read the product source. When a question needs the source, say so in
|
|
32
|
+
the finding, and do not guess the answer.
|
|
33
|
+
|
|
34
|
+
## The ten questions
|
|
35
|
+
|
|
36
|
+
Ask each question of each assertion. Use the problem name exactly as it is
|
|
37
|
+
written here.
|
|
38
|
+
|
|
39
|
+
| Problem | The question |
|
|
40
|
+
|---|---|
|
|
41
|
+
| two-readings | Can two careful agents write different tests from it? |
|
|
42
|
+
| not-exact | Does an "exact" assertion have one right answer? |
|
|
43
|
+
| many-claims | Does it hold more than one outcome? |
|
|
44
|
+
| duplicate | Does another assertion check the same thing? |
|
|
45
|
+
| weak | How could the product pass it while the claim is false? |
|
|
46
|
+
| hidden-precondition | Does it need a state that `Start with` does not name? |
|
|
47
|
+
| missing-data | Does it need a value that the scenario does not give? |
|
|
48
|
+
| shared-data | Can another run change the data it reads? |
|
|
49
|
+
| empty-when | Does a "when" assertion name a step that makes it true? |
|
|
50
|
+
| loose-not | Does a "not" assertion pass on a blank or broken answer? |
|
|
51
|
+
|
|
52
|
+
## The new words
|
|
53
|
+
|
|
54
|
+
1. Write the full new words of each assertion that you change, not a hint.
|
|
55
|
+
2. Keep every claim of the old words. Add no new requirement.
|
|
56
|
+
3. When you split an assertion, the first part keeps its id. Give each other
|
|
57
|
+
part an id that the scenario does not use yet.
|
|
58
|
+
4. Keep each new assertion to one outcome, and mark it `exact` or `judged`.
|
|
59
|
+
5. When the right words need a fact that only the owner knows, write the
|
|
60
|
+
question in the finding. Do not invent the fact.
|
|
61
|
+
|
|
62
|
+
## The `atlas ste` check
|
|
63
|
+
|
|
64
|
+
Run `atlas ste <file>` on each scenario file. Report each finding. Do not
|
|
65
|
+
fix the scenario: the writer of the words fixes it.
|
|
66
|
+
|
|
67
|
+
## Output: one list, no preamble
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
## Word review: <area>, <scenario ids>
|
|
71
|
+
|
|
72
|
+
### Findings
|
|
73
|
+
| Scenario | Assertion | Problem | Why, in one sentence | New words |
|
|
74
|
+
|---|---|---|---|---|
|
|
75
|
+
| <id> | <e-id> | <problem name> | <why> | <full new words> |
|
|
76
|
+
|
|
77
|
+
### Count for each problem
|
|
78
|
+
- <problem name>: <n>
|
|
79
|
+
|
|
80
|
+
### atlas ste
|
|
81
|
+
- <scenario file>: <n> findings. <each finding, one line>
|
|
82
|
+
|
|
83
|
+
### Questions for the owner
|
|
84
|
+
- <question>, or "none"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When an assertion has no problem, leave it out of the findings. When no
|
|
88
|
+
assertion has a problem, write "no findings" under Findings, and still give
|
|
89
|
+
the `atlas ste` result.
|
|
90
|
+
|
|
91
|
+
Worked example, from a meal planner scenario:
|
|
92
|
+
|
|
93
|
+
1. Input: "e3 exact: the planner refuses the save, and the week does not
|
|
94
|
+
change."
|
|
95
|
+
2. Process: the line holds a refusal and an unchanged week, so it has two
|
|
96
|
+
outcomes.
|
|
97
|
+
3. Output: one finding.
|
|
98
|
+
|
|
99
|
+
| Scenario | Assertion | Problem | Why, in one sentence | New words |
|
|
100
|
+
|---|---|---|---|---|
|
|
101
|
+
| week-conflict | e3 | many-claims | The line holds a refusal and an unchanged week. | e3 exact: the error is `breakfast_leftover_forbidden`. e4 exact: the week's `revision` is the same as before step 2. |
|
|
102
|
+
|
|
103
|
+
## Never-read and never-quote paths (rules in `${CLAUDE_PLUGIN_ROOT}/work/protected-paths.json`)
|
|
104
|
+
|
|
105
|
+
These deny rules bind every sub-agent that reads a repo.
|
|
106
|
+
|
|
107
|
+
**Never read**, in any repo, by any tool (`Read`, `Grep`, `Glob`, `Bash`):
|
|
108
|
+
`deploy/`, `.env*`, `.testenv/`, `.claude/`, `.stack/`, `.atlas/`,
|
|
109
|
+
`.mcp.json`, `.sdlc/`, `.dev.vars*`. Do not open them, do not grep them, do not list
|
|
110
|
+
their contents, do not cat them from a shell. If a search hits one of these
|
|
111
|
+
paths, drop the hit and say so in your review.
|
|
112
|
+
|
|
113
|
+
**Never quote** from these paths. You may name a file and line, but never
|
|
114
|
+
paste its contents into your review: `fixtures`,
|
|
115
|
+
`test/*.integration.test.ts`, `docs/references`, `docs/exec-plans`.
|
|
116
|
+
|
|
117
|
+
**Never quote a secret.** A line that looks like a token, key, password or
|
|
118
|
+
private key is cited by path and line only. The patterns are in
|
|
119
|
+
`${CLAUDE_PLUGIN_ROOT}/work/protected-paths.json`.
|
|
120
|
+
|
|
121
|
+
**Never hold a write door.** You never receive the `atlas-work` verb server
|
|
122
|
+
or the knowledge-graph MCP server. If a briefing hands you one, refuse and say so.
|
package/package.json
CHANGED
package/skills/lead/SKILL.md
CHANGED
|
@@ -92,7 +92,7 @@ set. A test keeps the two lists equal.
|
|
|
92
92
|
| Command | When to call it |
|
|
93
93
|
|---|---|
|
|
94
94
|
| `atlas install` | Once on a new Mac, when the owner asks for Atlas there |
|
|
95
|
-
| `atlas upgrade` | When the owner
|
|
95
|
+
| `atlas upgrade` | When the owner asks to upgrade Atlas. Call the `atlas_upgrade` tool instead: the Bash sandbox blocks this command. Run the command by hand only where that tool is missing |
|
|
96
96
|
| `atlas doctor` | When Atlas seems missing or broken in a session |
|
|
97
97
|
| `atlas check` | Before you ask for a merge in a repo that has the shape check |
|
|
98
98
|
| `atlas status` | To learn whether a repo's check is current or behind |
|
package/skills/tests/SKILL.md
CHANGED
|
@@ -211,7 +211,7 @@ route tests that people keep, stop. Tell the owner that path 1 comes later.
|
|
|
211
211
|
7. Put two personas on one secret only with `same-identity` on both.
|
|
212
212
|
8. Draft `scenarios/map.md` from the code digest and the docs of the repo.
|
|
213
213
|
9. Write two or three first scenarios. Start with the smallest.
|
|
214
|
-
10. Review the words (
|
|
214
|
+
10. Review the words (the section below).
|
|
215
215
|
11. Convert each scenario into a test.
|
|
216
216
|
12. Run `atlas tests check --root <repo> --halves static,lint`, then the tests,
|
|
217
217
|
then `atlas tests check --root <repo> --halves run`.
|
|
@@ -249,23 +249,16 @@ Use these steps when you write or change an assertion.
|
|
|
249
249
|
|
|
250
250
|
## Review the words
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
It proposes words. It never edits. It asks ten questions of each assertion:
|
|
252
|
+
Review each new or changed scenario before any code exists.
|
|
254
253
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
| missing-data | Does it need a value that the scenario does not give? |
|
|
264
|
-
| shared-data | Can another run change the data it reads? |
|
|
265
|
-
| empty-when | Does a "when" assertion name a step that makes it true? |
|
|
266
|
-
| loose-not | Does a "not" assertion pass on a blank or broken answer? |
|
|
267
|
-
|
|
268
|
-
Then run `atlas ste` on the scenario. The owner reads these pages.
|
|
254
|
+
1. Send the `atlas:word-reviewer` crew. Give it the repo, the area and the
|
|
255
|
+
ids of the new or changed scenarios. It asks ten questions of each
|
|
256
|
+
assertion, and it runs `atlas ste` on each scenario. It proposes words
|
|
257
|
+
and never edits.
|
|
258
|
+
2. Read its list. Change the words where a finding is right. When you keep
|
|
259
|
+
the old words, say why in the design.
|
|
260
|
+
3. Ask the owner each question in its list, one at a time.
|
|
261
|
+
4. Run `atlas ste` on each changed scenario. The owner reads these pages.
|
|
269
262
|
|
|
270
263
|
Rules for the words:
|
|
271
264
|
|
package/work/mcp.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { COST_MEANING, DECISION_KINDS } from './lib/questions.mjs';
|
|
|
25
25
|
import { FATAL_CONCLUSIONS } from './lib/delivery.mjs';
|
|
26
26
|
import { createMcpServer } from './lib/mcp-stdio.mjs';
|
|
27
27
|
import { childEnvironment } from './lib/child-env.mjs';
|
|
28
|
+
import { upgrade } from '../door/lib/install.mjs';
|
|
28
29
|
export { childEnvironment };
|
|
29
30
|
|
|
30
31
|
const ATLAS_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
@@ -72,6 +73,7 @@ export const TOOLS = [
|
|
|
72
73
|
{ name: 'where_are_we', description: 'Answer "where are we with X" in six parts: live support and known limits from the code read at question time, then in progress, delivered and proposed from the items, then the current priority decision. Every row carries a citation: a path and a line, an entity id, a pull request, or a decision date. The code half comes from the sub-agent digests, which are refused when they cite a never-read path or quote a never-quote path, and every string is scrubbed by pattern. Computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', required: ['product', 'capability', 'code'], properties: { product: S('Product registry id.'), capability: S('The capability the question is about, the X.'), code: { type: 'array', description: 'One digest per repo, from the sub-agents briefed by code_read_brief. At least one is required: an answer with no code read says nothing about what the code does today.', items: CODE_DIGEST }, items: { type: 'array', description: 'Item ids the lead judges to answer this question.', items: { type: 'string' } }, match: { type: 'array', description: 'Terms to match against item titles, summaries and tags. Give items, match, or both.', items: { type: 'string' } }, movement_repos: { type: 'array', description: "Narrow the movement read to these registered repo ids. Default: every repo of the product. The digests' own repos are always read.", items: { type: 'string' } }, since: S('ISO date. Adds what changed since then.') }, additionalProperties: false } },
|
|
73
74
|
{ name: 'built_pending_possible', description: 'Answer "what are we building, what is built, what is pending and what can be done": delivered items by date, active items, paused items with their reason, proposed items with their age, and the decisions that set the order. Item records, delivery marks and decisions only; no code read and no GitHub read. Computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', properties: { product: S('Limit the view to one product id.'), since: S('ISO date. Limits built and the decisions to what came after it.') }, additionalProperties: false } },
|
|
74
75
|
{ name: 'design_index', description: 'List every design: each work item with a design-doc link, in any lifecycle, newest change first, with its product, its state for the hub ("Waiting on the owner", "Building", "Paused", "Shipped", "Dropped"), its design docs and its pages. Also returns design_hub_url, the one hub page. When: before the lead rebuilds the "Design docs" hub, after each design publish, lock and done. Item records only; computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', properties: { product: S('Limit the list to one product id.') }, additionalProperties: false } },
|
|
76
|
+
{ name: 'atlas_upgrade', description: 'Move Atlas on this Mac to the newest release: the same steps as the "atlas upgrade --yes" command. It checks that a master release run built the release, installs it and updates the plugin. When: the owner asks to upgrade Atlas. Use this tool and not the command: the Bash sandbox blocks the writes to ~/.npm, ~/.atlas and ~/.claude/plugins, and this server runs outside it. With check true it only reports the loaded and the newest version. Scope: this Mac, no tracker write. Owner present only. Not in a Claude cloud session: a new cloud session installs the newest release.', inputSchema: { type: 'object', properties: { check: { type: 'boolean', description: 'True: report the versions and install nothing.' } }, additionalProperties: false } },
|
|
75
77
|
];
|
|
76
78
|
|
|
77
79
|
// The Engram connection: the address and the key, in one setting, separated by
|
|
@@ -233,6 +235,34 @@ function writeHealth(env, health) {
|
|
|
233
235
|
try { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, `${JSON.stringify(health, null, 2)}\n`, { mode: 0o600 }); } catch { /* health is advice, never a reason to stop */ }
|
|
234
236
|
}
|
|
235
237
|
|
|
238
|
+
// The atlas_upgrade tool (owner, 12 October 2026: "Atlas upgrade tool"). An
|
|
239
|
+
// agent's Bash runs in a sandbox that cannot write to ~/.npm, ~/.atlas or
|
|
240
|
+
// ~/.claude/plugins, so `atlas upgrade` fails there. This server runs outside
|
|
241
|
+
// that sandbox. The tool runs the same upgrade steps as the command, with the
|
|
242
|
+
// short child environment: never the Engram key.
|
|
243
|
+
const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
244
|
+
const ATLAS_VERSION_OF = (root) => JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
245
|
+
function childExec(env) {
|
|
246
|
+
return (command, args) => execFileSync(command, args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, timeout: 600_000, stdio: ['pipe', 'pipe', 'pipe'], env: childEnvironment(env) });
|
|
247
|
+
}
|
|
248
|
+
export async function runUpgrade({ args = {}, env = process.env, ownerPresent, packageRoot = PACKAGE_ROOT, exec = childExec(env), upgradeFn = upgrade } = {}) {
|
|
249
|
+
const refuse = (message) => { const error = new Error(message); error.name = 'VerbError'; throw error; };
|
|
250
|
+
if (!ownerPresent) refuse('atlas_upgrade needs the owner-present flag on the server host (ATLAS_WORK_OWNER_PRESENT=1)');
|
|
251
|
+
if (inClaudeCloud(env)) refuse('atlas_upgrade does not run in a Claude cloud session. A new cloud session installs the newest release.');
|
|
252
|
+
const loaded = ATLAS_VERSION_OF(packageRoot);
|
|
253
|
+
if (args.check) {
|
|
254
|
+
let newest;
|
|
255
|
+
try { newest = exec('npm', ['view', '@arjunkhera/atlas', 'version']).trim(); } catch (error) { refuse(`npm could not read the newest version: ${String(error.stderr || error.message).trim().split('\n').slice(0, 3).join(' ')}`); }
|
|
256
|
+
return { loaded, newest, behind: newest !== loaded };
|
|
257
|
+
}
|
|
258
|
+
const lines = [];
|
|
259
|
+
let code;
|
|
260
|
+
try { code = await upgradeFn({ packageRoot, options: { yes: true }, line: (text) => lines.push(text), env, exec }); } catch (error) {
|
|
261
|
+
refuse(`the upgrade stopped: ${String(error.stderr || error.message).trim().split('\n').slice(0, 6).join(' ')}`);
|
|
262
|
+
}
|
|
263
|
+
return { ok: code === 0, loaded, output: lines, next: code === 0 ? 'Restart the Claude Code sessions on this Mac to load the new release.' : 'Nothing was installed. Read the output.' };
|
|
264
|
+
}
|
|
265
|
+
|
|
236
266
|
export function buildServer({ env = process.env, input, output, readGitHub, readLive, log, allowLoopbackHttp = false } = {}) {
|
|
237
267
|
const hostile = hostileVariables(env);
|
|
238
268
|
if (hostile.length) throw new Error(`refusing to start: ${hostile.join(', ')} ${hostile.length === 1 ? 'is' : 'are'} set, and could send the Engram key elsewhere or turn certificate checks off. Unset ${hostile.length === 1 ? 'it' : 'them'}.`);
|
|
@@ -256,6 +286,8 @@ export function buildServer({ env = process.env, input, output, readGitHub, read
|
|
|
256
286
|
? `Atlas work verbs for the owner's session. Session ${session}; owner present: ${ownerPresent ? 'yes' : 'no: the owner-only tools refuse'}. Call registry_list first to learn the product and repo ids, needs_me to see the open questions, and in_flight to see the active work. Every write needs a request_id. The server stamps writer.session and the bound client; never pass writer, session, actor or role. Unknown product or repo ids are refused. Sub-agents must not receive this server.`
|
|
257
287
|
: `Atlas work verbs. They cannot reach Engram yet: ${connection.message}`;
|
|
258
288
|
const server = createMcpServer({ name: 'atlas-work', version: MANIFEST_VERSION, instructions, tools: TOOLS, input, output, log, call: (name, args, boundTo) => {
|
|
289
|
+
// The upgrade needs no Engram connection: an old key must not block a new release.
|
|
290
|
+
if (name === 'atlas_upgrade') return runUpgrade({ args, env, ownerPresent });
|
|
259
291
|
if (!verbs) throw new Error(connection.message);
|
|
260
292
|
// The writer names the one client this server is bound to (D6 R3): stamped by the server, never by the caller.
|
|
261
293
|
if (boundTo && boundTo.session !== bound) { bound = boundTo.session; verbs = verbsFor(bound); }
|