@arjunkhera/atlas 0.3.45 → 0.3.48
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 +6 -4
- package/door/lib/install.mjs +47 -5
- package/door/lib/owner-only.mjs +778 -0
- package/door/lib/pull-request.mjs +3 -2
- package/door/lib/tooling.mjs +34 -10
- package/door/lib/upkeep.mjs +47 -6
- package/door/templates/pull_request_template.md +25 -0
- package/owner-only.yaml +238 -0
- package/package.json +3 -2
- package/shape/check.mjs +44 -3
- package/shape/releases.json +5 -0
- package/skills/lead/SKILL.md +23 -1
- package/work/lib/child-env.mjs +6 -0
- package/work/mcp.mjs +16 -8
package/door/cli.mjs
CHANGED
|
@@ -44,7 +44,7 @@ const ATLAS_VERSION = packagePackageVersion(PACKAGE_ROOT);
|
|
|
44
44
|
|
|
45
45
|
const HELP = `atlas — the door into a repo's Atlas files
|
|
46
46
|
|
|
47
|
-
atlas tooling write --root <repo> write
|
|
47
|
+
atlas tooling write --root <repo> write the Atlas files into a repo
|
|
48
48
|
atlas tooling pr --root <repo> the same, as one pull request
|
|
49
49
|
--replace-unknown also replace a copy that is in no release
|
|
50
50
|
--workflow rewrite only the workflow, for a repo whose check is current
|
|
@@ -140,8 +140,10 @@ const HELP = `atlas — the door into a repo's Atlas files
|
|
|
140
140
|
compare the judge's verdict with the sealed tasks and the runs; exit 0 only when all pass
|
|
141
141
|
|
|
142
142
|
atlas install install Atlas for every folder on this Mac
|
|
143
|
+
--cloud in a cloud setup script: skip the checks that need a started session
|
|
143
144
|
atlas upgrade [--version <v>] check, show and install a newer release
|
|
144
145
|
atlas doctor is Atlas installed and working here
|
|
146
|
+
--cloud before the first cloud session: skip the checks that need a started session
|
|
145
147
|
|
|
146
148
|
Atlas ${ATLAS_VERSION}. Check version ${CHECK_VERSION}; it reads spec ${Object.keys(SPECS).join(', ')}.
|
|
147
149
|
A person merges every file the tooling writer writes.
|
|
@@ -169,9 +171,9 @@ export const FLAGS = Object.freeze({
|
|
|
169
171
|
upkeep: { root: 'optional', event: 'value', json: 'switch' },
|
|
170
172
|
gate: { repo: 'value', root: 'optional', 'dry-run': 'switch', off: 'switch', on: 'switch' },
|
|
171
173
|
judge: { tasks: 'value', verdict: 'value', seal: 'value', runs: 'value', out: 'value', repo: 'value', budget: 'value', model: 'value' },
|
|
172
|
-
install: { local: 'switch', from: 'value', 'skip-global': 'switch', yes: 'switch' },
|
|
174
|
+
install: { local: 'switch', from: 'value', 'skip-global': 'switch', yes: 'switch', cloud: 'switch' },
|
|
173
175
|
upgrade: { version: 'optional', yes: 'switch' },
|
|
174
|
-
doctor: {},
|
|
176
|
+
doctor: { cloud: 'switch' },
|
|
175
177
|
});
|
|
176
178
|
|
|
177
179
|
// Every command takes --help.
|
|
@@ -230,7 +232,7 @@ function toolingCommand(chosen) {
|
|
|
230
232
|
for (const row of result.rows) line(` ${row.state.padEnd(10)} ${row.path} sha256 ${row.sha256.slice(0, 12)}…`);
|
|
231
233
|
line('');
|
|
232
234
|
line(result.changed
|
|
233
|
-
? '
|
|
235
|
+
? 'The files come from the package. No hand step, and no edit by hand afterwards.'
|
|
234
236
|
: 'Nothing changed. Both files already match the package.');
|
|
235
237
|
return 0;
|
|
236
238
|
} catch (error) {
|
package/door/lib/install.mjs
CHANGED
|
@@ -140,9 +140,12 @@ export async function install({ packageRoot, options = {}, line = console.log, e
|
|
|
140
140
|
const installArgs = ['plugin', 'install', PLUGIN_ID, '--scope', 'user'];
|
|
141
141
|
exec('claude', installArgs);
|
|
142
142
|
line('');
|
|
143
|
-
|
|
143
|
+
// A cloud setup script runs before any session: the key comes from the
|
|
144
|
+
// environment variable, and no tools server has started yet (item 92cfbba3).
|
|
145
|
+
if (options.cloud) line('Installed. In a cloud session the key comes from the environment, and the lead checks the version at its first reply.');
|
|
146
|
+
else for (const text of connectionSteps(env)) line(text);
|
|
144
147
|
line('');
|
|
145
|
-
return doctor({ packageRoot, options, line, env, exec });
|
|
148
|
+
return doctor({ packageRoot, options: { cloud: Boolean(options.cloud) }, line, env, exec });
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
// ---------------------------------------------------------------- upgrade
|
|
@@ -151,13 +154,15 @@ export async function install({ packageRoot, options = {}, line = console.log, e
|
|
|
151
154
|
export const blobSha = (bytes) => createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
|
|
152
155
|
export const sha512Integrity = (bytes) => `sha512-${createHash('sha512').update(bytes).digest('base64')}`;
|
|
153
156
|
|
|
154
|
-
function filesUnder(root) {
|
|
157
|
+
function filesUnder(root, skip = () => false) {
|
|
155
158
|
const out = new Map();
|
|
156
159
|
const walk = (dir) => {
|
|
157
160
|
for (const entry of readdirSync(dir)) {
|
|
158
161
|
const full = join(dir, entry);
|
|
162
|
+
const path = relative(root, full).split('\\').join('/');
|
|
163
|
+
if (skip(path)) continue;
|
|
159
164
|
if (statSync(full).isDirectory()) walk(full);
|
|
160
|
-
else out.set(
|
|
165
|
+
else out.set(path, readFileSync(full));
|
|
161
166
|
}
|
|
162
167
|
};
|
|
163
168
|
walk(root);
|
|
@@ -201,6 +206,36 @@ export function matchTree({ tree, files }) {
|
|
|
201
206
|
return problems;
|
|
202
207
|
}
|
|
203
208
|
|
|
209
|
+
// Claude Code writes `.in_use/` into a plugin copy to mark it in use. It is
|
|
210
|
+
// not part of the package, so the proof of a loaded copy skips it.
|
|
211
|
+
const COPY_MARKER = /^\.in_use(\/|$)/;
|
|
212
|
+
const firstLines = (error, count = 3) => String(error?.stderr || error?.message || error).trim().split('\n').slice(0, count).join(' ');
|
|
213
|
+
|
|
214
|
+
// Is the copy at packageRoot the copy that a master release run built? The
|
|
215
|
+
// same proof as the upgrade, run on files already loaded. It reads only.
|
|
216
|
+
// The proof runs from the copy it checks, so it catches stale, broken or
|
|
217
|
+
// drifted files, not a package made to lie (security review of atlas#187).
|
|
218
|
+
// "unproven" is the upgrade's refusal: no master run built this version.
|
|
219
|
+
// "not checked" is never a pass: the caller says the reason.
|
|
220
|
+
export function proveCopy({ packageRoot, exec = defaultExec }) {
|
|
221
|
+
try {
|
|
222
|
+
const { version } = packageJson(packageRoot);
|
|
223
|
+
const repo = repoOf(packageRoot);
|
|
224
|
+
const gh = (args) => JSON.parse(exec('gh', args));
|
|
225
|
+
let run;
|
|
226
|
+
try { run = findReleaseRun({ gh, repo, version }); } catch (error) { return { state: 'not checked', reason: `GitHub did not answer: ${firstLines(error)}` }; }
|
|
227
|
+
if (!run) return { state: 'unproven', reason: `no successful release run on a commit of master built ${version}. It may have been published from a branch.` };
|
|
228
|
+
let tree;
|
|
229
|
+
try { tree = gh(['api', '--method', 'GET', `repos/${repo}/git/trees/${run.head_sha}?recursive=1`]).tree; } catch (error) { return { state: 'not checked', reason: `GitHub did not give the file tree: ${firstLines(error)}` }; }
|
|
230
|
+
const files = filesUnder(packageRoot, (path) => COPY_MARKER.test(path));
|
|
231
|
+
const problems = matchTree({ tree, files });
|
|
232
|
+
const found = { run: run.id, commit: run.head_sha };
|
|
233
|
+
return problems.length ? { state: 'differs', ...found, problems: problems.slice(0, 20) } : { state: 'match', ...found, files: files.size };
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return { state: 'not checked', reason: `the proof stopped: ${firstLines(error)}` };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
204
239
|
export async function upgrade({ packageRoot, options = {}, line = console.log, env = process.env, exec = defaultExec, ask = defaultAsk }) {
|
|
205
240
|
const { name, version: installed } = packageJson(packageRoot);
|
|
206
241
|
const repo = repoOf(packageRoot);
|
|
@@ -276,11 +311,18 @@ export function doctor({ packageRoot, options = {}, line = console.log, env = pr
|
|
|
276
311
|
}
|
|
277
312
|
|
|
278
313
|
// Off the cloud, the key lives only in the connection file. The cloud passes it as a variable.
|
|
279
|
-
if (env.CLAUDE_CODE_REMOTE !== 'true') {
|
|
314
|
+
if (env.CLAUDE_CODE_REMOTE !== 'true' && !options.cloud) {
|
|
280
315
|
const connection = readConnection({ HOME: env.HOME });
|
|
281
316
|
check('connection', connection.state === 'ok', connection.state === 'ok' ? `the Engram connection file is in place; the key expires ${connection.expires}` : connection.message);
|
|
282
317
|
}
|
|
283
318
|
|
|
319
|
+
// A cloud setup runs before the first session, so the three checks of a
|
|
320
|
+
// started tools server cannot pass yet. The lead checks at its first reply.
|
|
321
|
+
if (options.cloud) {
|
|
322
|
+
for (const row of rows) line(`${row.pass ? 'PASS' : 'FAIL'} ${row.name.padEnd(12)} ${row.detail}`);
|
|
323
|
+
line('SKIP tools server, engram and gh: no session has started yet');
|
|
324
|
+
return rows.every((row) => row.pass) ? 0 : 1;
|
|
325
|
+
}
|
|
284
326
|
let health = null;
|
|
285
327
|
try { health = JSON.parse(readFileSync(join(env.HOME, '.atlas', 'health.json'), 'utf8')); } catch { /* never started */ }
|
|
286
328
|
check('tools server', Boolean(health), health ? `last started ${health.started_at}, Atlas ${health.package_version}` : 'the Atlas tools server has not started yet. Open a Claude Code session, then run "atlas doctor" again.');
|