@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.
- package/.claude-plugin/plugin.json +1 -1
- package/door/cli.mjs +12 -3
- package/door/lib/design.mjs +50 -1
- package/package.json +1 -1
- package/skills/lead/SKILL.md +1 -1
- package/skills/sdlc-task/SKILL.md +4 -3
- package/skills/sdlc-task/design/README.md +11 -1
- package/skills/sdlc-task/templates/design-hub.html +1 -1
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
|
|
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') {
|
package/door/lib/design.mjs
CHANGED
|
@@ -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
package/skills/lead/SKILL.md
CHANGED
|
@@ -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
|
|
72
|
-
with `design.yaml
|
|
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
|
|
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
|
-
|
|
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/
|
|
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>
|