@arjunkhera/atlas 0.3.15 → 0.3.16

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, for any repository.",
5
5
  "author": {
6
6
  "name": "Arjun Khera"
package/door/cli.mjs CHANGED
@@ -23,7 +23,7 @@ import { scan, toText, verdict, CHECK_VERSION, SPECS } from '../shape/check.mjs'
23
23
  import { packagePackageVersion } from './lib/releases.mjs';
24
24
  import { install, upgrade, doctor } from './lib/install.mjs';
25
25
  import { checkPaths, LIMITS } from './lib/ste.mjs';
26
- import { checkDesignFolder } from './lib/design.mjs';
26
+ import { checkDesignFolder, designFolderOf, repoRootFrom } from './lib/design.mjs';
27
27
  import { writeDesignPage, readTracker } from './lib/design-build.mjs';
28
28
  import { loadPrivateTerms } from './lib/privacy.mjs';
29
29
  import { checkCommand as testsCheck, writeCommand as testsWrite } from './lib/tests.mjs';
@@ -52,6 +52,8 @@ const HELP = `atlas — the door into a repo's Atlas files
52
52
  --share also run the privacy filter, quotes included
53
53
  --terms <file> private terms; default ~/.config/atlas/private-terms.txt
54
54
  atlas design check <folder> … check a design folder: each needed part, no state line, every code in the Key
55
+ atlas design folder print where this repo keeps its designs: design.folder in atlas.yaml, or docs/design-docs
56
+ --root <repo> the repo; default the nearest folder up from here that holds atlas.yaml
55
57
  atlas design build <folder> build the design's page from its folder
56
58
  --tracker <file> the tracker data, as JSON, from the lead
57
59
  --out <file> the page; default docs/artifacts/<folder name>.html
@@ -117,7 +119,7 @@ export const FLAGS = Object.freeze({
117
119
  'kind-drift': { root: 'optional', 'record-kind': 'value', registry: 'value', repo: 'value' },
118
120
  check: { root: 'optional', repo: 'value', 'fail-on': 'value' },
119
121
  ste: { share: 'switch', terms: 'value' },
120
- design: { tracker: 'value', out: 'value', draft: 'switch' },
122
+ design: { tracker: 'value', out: 'value', draft: 'switch', root: 'optional' },
121
123
  tests: { root: 'optional', halves: 'value', evidence: 'value', 'guards-from': 'value', tests: 'value', 'dry-run': 'switch', run: 'value', own: 'value', base: 'value', covers: 'switch', findings: 'value', repo: 'value', max: 'value', bot: 'value', since: 'value', out: 'value', text: 'switch', json: 'switch' },
122
124
  install: { local: 'switch', from: 'value', 'skip-global': 'switch', yes: 'switch' },
123
125
  upgrade: { version: 'optional', yes: 'switch' },
@@ -258,7 +260,14 @@ function steCommand(chosen) {
258
260
  function designCommand(chosen) {
259
261
  const what = chosen._[1];
260
262
  const folders = chosen._.slice(2);
261
- if (what !== 'check' && what !== 'build') throw new Error('use atlas design check <folder> or atlas design build <folder>');
263
+ if (what === 'folder') {
264
+ if (folders.length || chosen.tracker !== undefined || chosen.out !== undefined || chosen.draft) throw new Error('atlas design folder takes only --root.');
265
+ const where = designFolderOf(chosen.root === undefined || chosen.root === true ? repoRootFrom(process.cwd()) : resolve(chosen.root));
266
+ line(where.folder);
267
+ return 0;
268
+ }
269
+ if (chosen.root !== undefined) throw new Error('--root works only with atlas design folder.');
270
+ if (what !== 'check' && what !== 'build') throw new Error('use atlas design check <folder>, atlas design build <folder> or atlas design folder');
262
271
  if (!folders.length) throw new Error(`give a design folder: atlas design ${what} <folder>`);
263
272
  if (what === 'check' && (chosen.tracker !== undefined || chosen.out !== undefined || chosen.draft)) throw new Error('--tracker, --out and --draft work only with atlas design build.');
264
273
  if (what === 'build') {
@@ -10,7 +10,7 @@
10
10
  import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
11
11
  import { join, dirname, resolve, basename } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { parseYaml } from '../../shape/check.mjs';
13
+ import { parseYaml, insideRepo } from '../../shape/check.mjs';
14
14
 
15
15
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
16
16
  export const PARTS_FILE = join(PACKAGE_ROOT, 'skills', 'sdlc-task', 'design', 'parts.yaml');
@@ -227,3 +227,52 @@ export function checkDesign(design, registry = loadRegistry()) {
227
227
  export function checkDesignFolder(folder, registry) {
228
228
  return checkDesign(readDesign(folder), registry);
229
229
  }
230
+
231
+ // Where a repo keeps its designs. A repo sets the folder in atlas.yaml, as
232
+ // `design: { folder: docs/design }`. Without the key, the folder is
233
+ // docs/design-docs, so a repo that never sets it does not change. The shape
234
+ // check does not read the key: a check change makes every repo's copy behind.
235
+ // The path is relative to the repo root, plain (no `.`, `..` or `~`), and
236
+ // never in a folder that a tool owns or a crew may not read.
237
+ export const DEFAULT_DESIGN_FOLDER = 'docs/design-docs';
238
+ export const CLOSED_FOLDERS = Object.freeze(['.git', '.github', '.claude', '.atlas', 'node_modules']);
239
+
240
+ export function designFolderOf(root) {
241
+ const fallback = { folder: DEFAULT_DESIGN_FOLDER, path: join(root, DEFAULT_DESIGN_FOLDER), from: 'default' };
242
+ const facts = join(root, 'atlas.yaml');
243
+ const data = existsSync(facts) ? parseYaml(readFileSync(facts, 'utf8')) : null;
244
+ if (!data || typeof data !== 'object' || !Object.hasOwn(data, 'design')) return fallback;
245
+ const design = data.design;
246
+ if (!design || typeof design !== 'object' || Array.isArray(design)) throw new Error('atlas.yaml: design must be a map with one key, such as "design: { folder: docs/design }". Remove design to keep docs/design-docs.');
247
+ const unknown = Object.keys(design).filter((key) => key !== 'folder');
248
+ if (unknown.length) throw new Error(`atlas.yaml: design has the unknown key(s) ${unknown.join(', ')}. The one key is folder.`);
249
+ const raw = design.folder;
250
+ if (typeof raw !== 'string' || !raw.trim()) throw new Error('atlas.yaml: design.folder must be a path, such as docs/design.');
251
+ const steps = raw.trim().replace(/\/+$/, '').split('/');
252
+ if (raw.trim().startsWith('/') || /^[A-Za-z]:/.test(raw.trim()) || steps.some((step) => step === '' || step === '.' || step === '..' || step.includes('\\')) || steps[0].startsWith('~')) {
253
+ throw new Error(`atlas.yaml: design.folder "${raw}" must be a plain path inside the repo, relative to its root, such as docs/design.`);
254
+ }
255
+ if (CLOSED_FOLDERS.includes(steps[0])) throw new Error(`atlas.yaml: design.folder "${raw}" is in ${steps[0]}/, which a tool owns or a crew may not read. Pick a folder such as docs/design.`);
256
+ const folder = steps.join('/');
257
+ return { folder, path: join(root, folder), from: 'atlas.yaml' };
258
+ }
259
+
260
+ // The repo root for a command run with no --root: the nearest folder, from
261
+ // here up, that holds atlas.yaml. The search stops at the git root. With no
262
+ // atlas.yaml on the way, it is the folder the command ran in.
263
+ export function repoRootFrom(start) {
264
+ for (let dir = resolve(start); ; dir = dirname(dir)) {
265
+ if (existsSync(join(dir, 'atlas.yaml'))) return dir;
266
+ if (existsSync(join(dir, '.git')) || dirname(dir) === dir) return resolve(start);
267
+ }
268
+ }
269
+
270
+ // Each design in the repo: a folder under the design folder that holds
271
+ // design.yaml. A missing design folder holds no designs.
272
+ export function designFoldersIn(root) {
273
+ const { path } = designFolderOf(root);
274
+ if (!existsSync(path) || !statSync(path).isDirectory()) return [];
275
+ return readdirSync(path, { withFileTypes: true })
276
+ .filter((entry) => entry.isDirectory() && existsSync(join(path, entry.name, DESIGN_FILE)))
277
+ .map((entry) => join(path, entry.name));
278
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arjunkhera/atlas",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, as a Claude Code plugin for any repository.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -101,7 +101,7 @@ set. A test keeps the two lists equal.
101
101
  | `atlas code-read` | To resolve the citations of a code digest |
102
102
  | `atlas ste` | Before each publish; `--share` before a page goes to anyone else |
103
103
  | `atlas tests` | `write` puts the test kit in an area. `check` runs before a merge. `proof` prints the proof table of one run |
104
- | `atlas design` | `check` before you show a design; `build` to make its page |
104
+ | `atlas design` | `folder` to learn where designs live; `check` before you show a design; `build` to make its page |
105
105
 
106
106
  A person merges every file that `atlas tooling` writes.
107
107
  A person merges every file that `atlas tests write` writes.
@@ -68,8 +68,9 @@ with hashes retire.)
68
68
  commit to it. Local sessions use `feature/`, `fix/` or `chore/`; cloud
69
69
  sessions use `claude/`. The pull request targets that branch.
70
70
  4. **Design** (standard and above): follow [`design/README.md`](design/README.md).
71
- Pick the kinds, and say why. Make the folder `docs/design-docs/<slug>/`
72
- with `design.yaml`, then write the Summary, Your words and Goals first.
71
+ Pick the kinds, and say why. Make the folder `<design folder>/<slug>/`
72
+ with `design.yaml` (`atlas design folder` prints the design folder),
73
+ then write the Summary, Your words and Goals first.
73
74
  Read the template for each part in `design/parts/`. List the folder in
74
75
  `docs/index.md` in the commit that first lands it. Hotfix tier skips the design: the definition of done lives on
75
76
  the work item, and a resume anchor covers pauses.
@@ -123,7 +124,7 @@ A lock is the owner's approval of a short design (target design, flow 4).
123
124
  not.
124
125
  4. Call `item_lock` with the definition of done as `scope.text`. Set
125
126
  `scope.design_doc` to a link to the design at the approved commit, such
126
- as `https://github.com/<owner>/<repo>/tree/<sha>/docs/design-docs/<slug>`.
127
+ as `https://github.com/<owner>/<repo>/tree/<sha>/<design folder>/<slug>`.
127
128
  The verb keeps a fingerprint of that text for
128
129
  the tools. No person reads or writes a hash, and no frozen text is copied
129
130
  into the doc.
@@ -126,8 +126,18 @@ change as work moves went to the tracker.
126
126
 
127
127
  ## The folder
128
128
 
129
+ Each design is one folder in the repo's design folder. `atlas design folder`
130
+ prints that folder. A repo sets it in `atlas.yaml`:
131
+
132
+ ```yaml
133
+ design:
134
+ folder: docs/design
135
+ ```
136
+
137
+ Without the key, the design folder is `docs/design-docs`.
138
+
129
139
  ```text
130
- docs/design-docs/<slug>/
140
+ <design folder>/<slug>/
131
141
  design.yaml title, item, product, kinds, shared, look
132
142
  summary.md ---
133
143
  part: summary
@@ -37,7 +37,7 @@ a { color: var(--accent); }
37
37
  <div class="wrap"><table>
38
38
  <thead><tr><th>Changed</th><th>Product</th><th>Design</th><th>State</th><th>Source</th></tr></thead>
39
39
  <tbody>
40
- <tr><td class="when">2026-10-07</td><td class="product">atlas</td><td><a href="https://claude.ai/artifact/EXAMPLE">Design docs as walkthroughs, in one place</a></td><td><span class="state build">Building</span></td><td><a href="https://github.com/ACCOUNT/REPO/blob/master/docs/design-docs/EXAMPLE.md">design doc</a></td></tr>
40
+ <tr><td class="when">2026-10-07</td><td class="product">atlas</td><td><a href="https://claude.ai/artifact/EXAMPLE">Design docs as walkthroughs, in one place</a></td><td><span class="state build">Building</span></td><td><a href="https://github.com/ACCOUNT/REPO/tree/master/DESIGN-FOLDER/EXAMPLE">design doc</a></td></tr>
41
41
  </tbody>
42
42
  </table></div>
43
43
  </main></body></html>