@geonosis/cli 1.0.0

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,773 @@
1
+ // src/argv.ts
2
+ var parseArgv = (argv, valued) => {
3
+ const flags = {};
4
+ const positional = [];
5
+ const switches = /* @__PURE__ */ new Set();
6
+ for (let at = 0; at < argv.length; at += 1) {
7
+ const token = argv[at] ?? "";
8
+ if (!token.startsWith("--")) {
9
+ positional.push(token);
10
+ continue;
11
+ }
12
+ const equals = token.indexOf("=");
13
+ if (equals !== -1) {
14
+ flags[token.slice(0, equals)] = token.slice(equals + 1);
15
+ continue;
16
+ }
17
+ if (!valued.has(token)) {
18
+ switches.add(token);
19
+ continue;
20
+ }
21
+ const value = argv[at + 1];
22
+ if (value === void 0) throw new Error(`${token} needs a value`);
23
+ flags[token] = value;
24
+ at += 1;
25
+ }
26
+ return { flags, positional, switches };
27
+ };
28
+ var VALUED = {
29
+ discover: /* @__PURE__ */ new Set(["--config"]),
30
+ init: /* @__PURE__ */ new Set(["--brand", "--presets", "--runtime"]),
31
+ sync: /* @__PURE__ */ new Set(["--into", "--target"])
32
+ };
33
+ var list = (value) => value === void 0 ? [] : value.split(",").map((one) => one.trim()).filter((one) => one !== "");
34
+ var inputFor = (id, argv, cwd) => {
35
+ if (id === "skills") return { cwd, verb: argv[0] ?? "list" };
36
+ const valued = VALUED[id];
37
+ if (valued === void 0) return { args: [...argv], cwd };
38
+ const { flags, positional, switches } = parseArgv(argv, valued);
39
+ if (id === "init") {
40
+ return {
41
+ ...flags["--brand"] === void 0 ? {} : { brand: flags["--brand"] },
42
+ cwd,
43
+ presets: list(flags["--presets"]),
44
+ ...flags["--runtime"] === void 0 ? {} : { runtime: flags["--runtime"] }
45
+ };
46
+ }
47
+ if (id === "sync") {
48
+ return {
49
+ check: switches.has("--check"),
50
+ cwd,
51
+ ...flags["--into"] === void 0 ? {} : { into: flags["--into"] },
52
+ ...flags["--target"] === void 0 ? {} : { target: flags["--target"] },
53
+ write: switches.has("--write") || !switches.has("--check")
54
+ };
55
+ }
56
+ return {
57
+ ...flags["--config"] === void 0 ? {} : { config: flags["--config"] },
58
+ cwd,
59
+ ...positional.length === 0 ? {} : { paths: positional }
60
+ };
61
+ };
62
+
63
+ // src/types.ts
64
+ var resultOf = (exitCode, output) => ({
65
+ exitCode,
66
+ ok: exitCode === 0,
67
+ output
68
+ });
69
+ var refused = (output) => resultOf(2, output);
70
+
71
+ // src/discover.ts
72
+ import { spawnSync } from "child_process";
73
+ import { existsSync } from "fs";
74
+ import { join } from "path";
75
+ import { normaliseFindings, resolveOxlint, ruleIdOf } from "@geonosis/lint-parity";
76
+ var SITES = 3;
77
+ var siteOf = (line) => line.split(":").slice(0, 2).join(":");
78
+ var candidatesOf = (findings) => {
79
+ const byRule = /* @__PURE__ */ new Map();
80
+ for (const line of findings) {
81
+ const rule = ruleIdOf(line);
82
+ byRule.set(rule, [...byRule.get(rule) ?? [], siteOf(line)]);
83
+ }
84
+ return [...byRule.entries()].map(([rule, sites]) => ({ count: sites.length, rule, sites: sites.slice(0, SITES) })).toSorted((a, b) => b.count - a.count || a.rule.localeCompare(b.rule));
85
+ };
86
+ var formatCandidates = (candidates) => candidates.length === 0 ? "discover \u2014 no finding in this tree, so there is no shape to propose.\n" : [
87
+ `discover \u2014 ${candidates.length} rule(s) firing, loudest first. Candidates for a fixShape; nothing was written.`,
88
+ "",
89
+ ...candidates.flatMap((one) => [
90
+ `${String(one.count).padStart(6)} ${one.rule}`,
91
+ ...one.sites.map((site) => ` ${site}`)
92
+ ]),
93
+ ""
94
+ ].join("\n");
95
+ var CONFIG = ".oxlintrc.json";
96
+ var runDiscover = async ({
97
+ config,
98
+ cwd,
99
+ paths
100
+ }) => {
101
+ const named = config ?? CONFIG;
102
+ if (!existsSync(join(cwd, named))) {
103
+ return refused(`geonosis discover: no ${named} in ${cwd} \u2014 there is no config to read with.
104
+ `);
105
+ }
106
+ const done = spawnSync(
107
+ resolveOxlint(cwd),
108
+ ["--format=unix", "--config", named, ...paths ?? ["."]],
109
+ { cwd, encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } }
110
+ );
111
+ if (done.error !== void 0) {
112
+ return refused(`geonosis discover: could not run oxlint: ${done.error.message}
113
+ `);
114
+ }
115
+ const output = `${done.stdout ?? ""}${done.stderr ?? ""}`;
116
+ return resultOf(0, formatCandidates(candidatesOf(normaliseFindings(output, cwd))));
117
+ };
118
+
119
+ // src/options.ts
120
+ import { rules } from "@geonosis/oxlint-plugin-biological-architecture";
121
+ var MISSING = /is enabled but its `([^`]+)` option is/;
122
+ var optionsNeededBy = () => {
123
+ const needed = {};
124
+ for (const [name, rule] of Object.entries(rules)) {
125
+ try {
126
+ rule.create({ options: [] });
127
+ } catch (error) {
128
+ const option = MISSING.exec(String(error.message))?.[1];
129
+ if (option !== void 0) needed[name] = option;
130
+ }
131
+ }
132
+ return needed;
133
+ };
134
+
135
+ // src/init.ts
136
+ import { existsSync as existsSync2, writeFileSync } from "fs";
137
+ import { join as join2 } from "path";
138
+ import { PRESETS } from "@geonosis/oxlint-plugin-biological-architecture";
139
+ var PREFIX = "biological-architecture/";
140
+ var SCHEMA = "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json";
141
+ var LOCKFILES = [
142
+ ["bun", "bun.lock"],
143
+ ["bun", "bun.lockb"],
144
+ ["pnpm", "pnpm-lock.yaml"]
145
+ ];
146
+ var runtimeOf = (cwd) => LOCKFILES.find(([, file]) => existsSync2(join2(cwd, file)))?.[0];
147
+ var execOf = (runtime) => runtime === "bun" ? "bunx" : "npx";
148
+ var scriptOf = (runtime, name) => runtime === "bun" ? `bun run ${name}` : `pnpm ${name}`;
149
+ var tiersOf = (runtime) => ({
150
+ fast: [scriptOf(runtime, "typecheck"), scriptOf(runtime, "lint"), scriptOf(runtime, "test")],
151
+ full: [scriptOf(runtime, "build"), "fast", scriptOf(runtime, "ratchet")]
152
+ });
153
+ var testCounter = (runtime) => runtime === "bun" ? { command: "bun test", counter: "testFailures" } : {
154
+ command: "npx vitest run --reporter=json --outputFile={report}",
155
+ counter: "testFailures",
156
+ report: "vitest-json"
157
+ };
158
+ var countersFor = (runtime) => {
159
+ const exec = execOf(runtime);
160
+ return [
161
+ {
162
+ command: `${exec} oxlint --format=unix --config .oxlintrc.json .`,
163
+ counter: "oxlintErrors",
164
+ expectFormat: "unix"
165
+ },
166
+ {
167
+ command: `${exec} oxlint --format=unix --config .oxlintrc.json .`,
168
+ counter: "oxlintWarnings",
169
+ expectFormat: "unix"
170
+ },
171
+ { command: `${exec} tsc --noEmit`, counter: "typecheckErrors" },
172
+ {
173
+ command: `${exec} oxfmt --config .oxfmtrc.json --list-different .`,
174
+ counter: "unformattedFiles"
175
+ },
176
+ testCounter(runtime),
177
+ { counter: "lawLineCount", path: "CLAUDE.md" },
178
+ { command: "true", counter: "disabledCiJobs" },
179
+ { command: "true", counter: "packagesWithoutTypecheck" }
180
+ ];
181
+ };
182
+ var keyOf = (entry) => typeof entry.key === "string" ? entry.key : String(entry.counter);
183
+ var rulesFor = (presets, answers = {}) => {
184
+ const needed = optionsNeededBy();
185
+ const wanted = new Set(
186
+ presets.flatMap((name) => Object.keys(PRESETS[name].rules))
187
+ );
188
+ const rules2 = {};
189
+ const unanswered = [];
190
+ for (const id of [...wanted].toSorted()) {
191
+ const bare = id.startsWith(PREFIX) ? id.slice(PREFIX.length) : id;
192
+ const option = needed[bare];
193
+ if (option === void 0) {
194
+ rules2[id] = "error";
195
+ continue;
196
+ }
197
+ const answered = answers[bare];
198
+ if (answered === void 0) unanswered.push([bare, option]);
199
+ else rules2[id] = answered;
200
+ }
201
+ return { rules: rules2, unanswered };
202
+ };
203
+ var oxlintConfig = (rules2) => ({
204
+ $schema: SCHEMA,
205
+ categories: { correctness: "error", suspicious: "error" },
206
+ ignorePatterns: ["node_modules", "dist", "coverage"],
207
+ jsPlugins: ["@geonosis/oxlint-plugin-biological-architecture"],
208
+ plugins: ["typescript", "unicorn"],
209
+ rules: rules2
210
+ });
211
+ var json = (value) => `${JSON.stringify(value, void 0, 2)}
212
+ `;
213
+ var refusal = (output) => resultOf(1, output);
214
+ var runInit = async ({
215
+ brand,
216
+ cwd,
217
+ presets,
218
+ runtime
219
+ }) => {
220
+ const known = Object.keys(PRESETS).toSorted();
221
+ const unknown = presets.filter((name) => !known.includes(name));
222
+ if (presets.length === 0 || unknown.length > 0) {
223
+ return refusal(
224
+ `geonosis init: ${presets.length === 0 ? "name at least one preset" : `no preset called ${unknown.join(", ")}`} \u2014 the kit ships ${known.join(", ")}.
225
+ `
226
+ );
227
+ }
228
+ const chosen = runtime ?? runtimeOf(cwd);
229
+ if (chosen === void 0) {
230
+ return refusal(
231
+ `geonosis init: no lockfile in ${cwd} says which package manager this repo uses \u2014 pass --runtime pnpm or --runtime bun.
232
+ `
233
+ );
234
+ }
235
+ const { rules: rules2, unanswered } = rulesFor(
236
+ presets,
237
+ brand === void 0 ? {} : { "no-brand-names": ["error", { brands: [brand] }] }
238
+ );
239
+ const counters = countersFor(chosen);
240
+ const files = {
241
+ "geonosis.json": json({ verify: tiersOf(chosen) }),
242
+ ".oxlintrc.json": json(oxlintConfig(rules2)),
243
+ "geonosis.ratchet.json": json({ baseline: "gate-baseline.json", counters }),
244
+ "gate-baseline.json": json(Object.fromEntries(counters.map((one) => [keyOf(one), 0])))
245
+ };
246
+ const already = Object.keys(files).filter((file) => existsSync2(join2(cwd, file)));
247
+ if (already.length > 0) {
248
+ return refusal(
249
+ `geonosis init: ${already.join(", ")} already exists in ${cwd} \u2014 nothing was written. Move what is there, or edit it by hand.
250
+ `
251
+ );
252
+ }
253
+ for (const [file, body] of Object.entries(files)) writeFileSync(join2(cwd, file), body);
254
+ return resultOf(
255
+ 0,
256
+ [
257
+ `geonosis init \u2014 ${chosen}, ${Object.keys(rules2).length} rule(s) enabled from ${presets.join(" + ")}`,
258
+ "",
259
+ ...Object.keys(files).map((file) => ` wrote ${file}`),
260
+ "",
261
+ ...unanswered.length === 0 ? [] : [
262
+ ` ${unanswered.length} rule(s) were NOT enabled: each needs an option only this repo can answer.`,
263
+ ...unanswered.map(([rule, option]) => ` ${rule} \u2014 needs \`${option}\``),
264
+ ""
265
+ ],
266
+ " gate-baseline.json starts every counter at 0. Run `geonosis ratchet`: a number above zero",
267
+ " today is this repo\u2019s existing debt, and recording it once \u2014 in the commit that adopts the",
268
+ " gate, with the reason \u2014 is the only time a baseline goes up.",
269
+ ""
270
+ ].join("\n")
271
+ );
272
+ };
273
+
274
+ // src/skills.ts
275
+ import { createHash } from "crypto";
276
+ import { existsSync as existsSync3, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
277
+ import { join as join3, relative, sep } from "path";
278
+ var LOCK_FILE = "skills-lock.json";
279
+ var SKILL_FILE = "SKILL.md";
280
+ var ROOTS = ["skills", "plugin/skills", ".claude/skills"];
281
+ var OWNER_EXECUTE = 64;
282
+ var posix = (path) => path.split(sep).join("/");
283
+ var hashOf = (bytes) => createHash("sha256").update(new Uint8Array(bytes)).digest("hex");
284
+ var rootsIn = (cwd) => ROOTS.filter((root) => existsSync3(join3(cwd, root)) && statSync(join3(cwd, root)).isDirectory());
285
+ var skillsIn = (cwd) => {
286
+ const found = [];
287
+ for (const root of rootsIn(cwd)) {
288
+ for (const entry of readdirSync(join3(cwd, root), { withFileTypes: true })) {
289
+ if (!entry.isDirectory()) continue;
290
+ const dir = join3(cwd, root, entry.name);
291
+ const skill = join3(dir, SKILL_FILE);
292
+ if (!existsSync3(skill)) continue;
293
+ found.push({
294
+ dir,
295
+ hash: hashOf(readFileSync(skill)),
296
+ name: entry.name,
297
+ skillPath: posix(relative(cwd, skill))
298
+ });
299
+ }
300
+ }
301
+ return found.toSorted((a, b) => a.name.localeCompare(b.name));
302
+ };
303
+ var readLock = (cwd) => {
304
+ const path = join3(cwd, LOCK_FILE);
305
+ if (!existsSync3(path)) return void 0;
306
+ return JSON.parse(readFileSync(path, "utf8"));
307
+ };
308
+ var executablesIn = (dir, cwd) => {
309
+ const found = [];
310
+ const walk = (at) => {
311
+ for (const entry of readdirSync(at, { withFileTypes: true })) {
312
+ const path = join3(at, entry.name);
313
+ if (entry.isDirectory()) {
314
+ walk(path);
315
+ continue;
316
+ }
317
+ if (entry.isFile() && (statSync(path).mode & OWNER_EXECUTE) !== 0) {
318
+ found.push(posix(relative(cwd, path)));
319
+ }
320
+ }
321
+ };
322
+ walk(dir);
323
+ return found.toSorted();
324
+ };
325
+ var entryFor = (skill, previous) => ({
326
+ computedHash: skill.hash,
327
+ skillPath: skill.skillPath,
328
+ // A skill fetched from github stays a github pin: relocking must not quietly downgrade the
329
+ // provenance of every entry to "whatever is on this disk right now".
330
+ source: previous?.source ?? skill.skillPath.replace(`/${SKILL_FILE}`, ""),
331
+ sourceType: previous?.sourceType ?? "local"
332
+ });
333
+ var lockFor = (cwd) => {
334
+ const previous = readLock(cwd);
335
+ return {
336
+ skills: Object.fromEntries(
337
+ skillsIn(cwd).map((skill) => [skill.name, entryFor(skill, previous?.skills[skill.name])])
338
+ ),
339
+ version: 1
340
+ };
341
+ };
342
+ var listing = (cwd) => {
343
+ const skills = skillsIn(cwd);
344
+ return skills.length === 0 ? `geonosis skills: no ${SKILL_FILE} under ${ROOTS.join(", ")} in ${cwd}.
345
+ ` : `${skills.map((one) => `${one.hash.slice(0, 12)} ${one.name} ${one.skillPath}`).join("\n")}
346
+ ${skills.length} skill(s)
347
+ `;
348
+ };
349
+ var audit = (cwd) => {
350
+ const lock = readLock(cwd);
351
+ if (lock === void 0) {
352
+ return refused(
353
+ `geonosis skills audit: no ${LOCK_FILE} in ${cwd} \u2014 run \`skills lock\` first.
354
+ `
355
+ );
356
+ }
357
+ const found = skillsIn(cwd);
358
+ const complaints = [];
359
+ for (const skill of found) {
360
+ const pinned = lock.skills[skill.name];
361
+ if (pinned === void 0) {
362
+ complaints.push(`${skill.name}: on disk and in no lock \u2014 it was never pinned`);
363
+ } else if (pinned.computedHash !== skill.hash) {
364
+ complaints.push(
365
+ `${skill.name}: ${skill.skillPath} hashes ${skill.hash.slice(0, 12)}, the lock pins ${pinned.computedHash.slice(0, 12)}`
366
+ );
367
+ }
368
+ for (const file of executablesIn(skill.dir, cwd)) {
369
+ complaints.push(
370
+ `${skill.name}: ${file} is executable \u2014 a lock over ${SKILL_FILE} is blind to it`
371
+ );
372
+ }
373
+ }
374
+ for (const name of Object.keys(lock.skills).toSorted()) {
375
+ if (!found.some((one) => one.name === name)) {
376
+ complaints.push(`${name}: pinned by the lock and not on disk`);
377
+ }
378
+ }
379
+ return complaints.length === 0 ? resultOf(0, `skills audit PASS \u2014 ${found.length} skill(s), every hash as pinned.
380
+ `) : resultOf(
381
+ 1,
382
+ `${complaints.map((one) => ` ${one}`).join("\n")}
383
+ skills audit FAIL \u2014 ${complaints.length} finding(s).
384
+ `
385
+ );
386
+ };
387
+ var runSkills = async ({ cwd, verb }) => {
388
+ if (verb === "list") return resultOf(0, listing(cwd));
389
+ if (verb === "lock") {
390
+ const lock = lockFor(cwd);
391
+ writeFileSync2(join3(cwd, LOCK_FILE), `${JSON.stringify(lock, void 0, 2)}
392
+ `);
393
+ return resultOf(
394
+ 0,
395
+ `wrote ${LOCK_FILE} \u2014 ${Object.keys(lock.skills).length} skill(s) pinned by content hash.
396
+ `
397
+ );
398
+ }
399
+ if (verb === "audit") return audit(cwd);
400
+ return refused(`geonosis skills: "${verb}" is not one of list, lock, audit.
401
+ `);
402
+ };
403
+
404
+ // src/spawn.ts
405
+ import { spawnSync as spawnSync2 } from "child_process";
406
+ import { readFileSync as readFileSync2 } from "fs";
407
+ import { createRequire } from "module";
408
+ import { join as join4 } from "path";
409
+ import { fileURLToPath } from "url";
410
+ import { packageDirOf } from "@geonosis/doctor";
411
+ var OUTPUT_LIMIT = 8 * 1024 * 1024;
412
+ var entryOf = (from, name) => createRequire(join4(from, "noop.js")).resolve(name);
413
+ var binPath = (from, ref) => {
414
+ const dir = packageDirOf(entryOf(from, ref.package), ref.package);
415
+ const manifest = JSON.parse(readFileSync2(join4(dir, "package.json"), "utf8"));
416
+ const declared = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[ref.bin];
417
+ if (declared === void 0) {
418
+ throw new Error(`${ref.package} declares no bin called "${ref.bin}"`);
419
+ }
420
+ return join4(dir, declared);
421
+ };
422
+ var SELF = fileURLToPath(new URL("..", import.meta.url));
423
+ var versionOf = (from, name) => {
424
+ try {
425
+ const dir = packageDirOf(entryOf(from, name), name);
426
+ const manifest = JSON.parse(readFileSync2(join4(dir, "package.json"), "utf8"));
427
+ return manifest.version ?? "an unknown version";
428
+ } catch {
429
+ return "an unknown version";
430
+ }
431
+ };
432
+ var findBin = (cwd, ref) => {
433
+ try {
434
+ return { note: "", path: binPath(cwd, ref) };
435
+ } catch {
436
+ }
437
+ const path = binPath(SELF, ref);
438
+ return {
439
+ note: `geonosis: ${ref.package} is not installed in ${cwd}; running the copy that came with @geonosis/cli (${versionOf(SELF, ref.package)}).
440
+ `,
441
+ path
442
+ };
443
+ };
444
+ var runBin = async (cwd, ref, args) => {
445
+ let found;
446
+ try {
447
+ found = findBin(cwd, ref);
448
+ } catch (error) {
449
+ return refused(
450
+ `geonosis: ${ref.package} is installed neither in ${cwd} nor beside this CLI, so "${ref.bin}" could not be run \u2014 ${error.message}
451
+ `
452
+ );
453
+ }
454
+ const done = spawnSync2(process.execPath, [found.path, ...args], {
455
+ cwd,
456
+ encoding: "utf8",
457
+ maxBuffer: OUTPUT_LIMIT
458
+ });
459
+ if (done.error !== void 0) {
460
+ return refused(`geonosis: could not run ${found.path}: ${done.error.message}
461
+ `);
462
+ }
463
+ return resultOf(done.status ?? 2, `${found.note}${done.stdout ?? ""}${done.stderr ?? ""}`);
464
+ };
465
+
466
+ // src/sync.ts
467
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
468
+ import { dirname, join as join5 } from "path";
469
+ var LAW_FILE = "CLAUDE.md";
470
+ var AGENTS_FILE = "AGENTS.md";
471
+ var CURSOR_RULES = ".cursor/rules";
472
+ var GENERATED = "Generated by `geonosis sync` \u2014 edit CLAUDE.md and the skills, never this file.";
473
+ var FRONTMATTER = /^---\n([\s\S]*?)\n---\n?/;
474
+ var fieldOf = (block, name) => new RegExp(`^${name}:\\s*(.+)$`, "m").exec(block)?.[1]?.trim() ?? "";
475
+ var frontOf = (source) => {
476
+ const found = FRONTMATTER.exec(source);
477
+ if (found?.[1] === void 0) return { body: source.trim(), description: "" };
478
+ return {
479
+ body: source.slice(found[0].length).trim(),
480
+ description: fieldOf(found[1], "description")
481
+ };
482
+ };
483
+ var generate = (cwd) => {
484
+ const law = readFileSync3(join5(cwd, LAW_FILE), "utf8").trimEnd();
485
+ const skills = skillsIn(cwd).map((skill) => ({
486
+ ...skill,
487
+ ...frontOf(readFileSync3(join5(skill.dir, "SKILL.md"), "utf8"))
488
+ }));
489
+ const files = {
490
+ [AGENTS_FILE]: [
491
+ `<!-- ${GENERATED} -->`,
492
+ "",
493
+ law,
494
+ "",
495
+ "## Skills",
496
+ "",
497
+ ...skills.length === 0 ? ["This repo ships no skills."] : skills.map((one) => `- **${one.name}** \u2014 ${one.description} (\`${one.skillPath}\`)`),
498
+ ""
499
+ ].join("\n")
500
+ };
501
+ files[`${CURSOR_RULES}/law.mdc`] = [
502
+ "---",
503
+ `description: ${LAW_FILE}, the law of this repo`,
504
+ "globs:",
505
+ "alwaysApply: true",
506
+ "---",
507
+ "",
508
+ `<!-- ${GENERATED} -->`,
509
+ "",
510
+ law,
511
+ ""
512
+ ].join("\n");
513
+ for (const skill of skills) {
514
+ files[`${CURSOR_RULES}/${skill.name}.mdc`] = [
515
+ "---",
516
+ `description: ${skill.description}`,
517
+ "globs:",
518
+ "alwaysApply: false",
519
+ "---",
520
+ "",
521
+ `<!-- ${GENERATED} -->`,
522
+ "",
523
+ skill.body,
524
+ ""
525
+ ].join("\n");
526
+ }
527
+ return files;
528
+ };
529
+ var ledgerArgs = ({ check, into, target, write }) => [
530
+ "sync",
531
+ "--target",
532
+ target ?? "",
533
+ ...write === true && into !== void 0 ? ["--write", into] : [],
534
+ ...check === true && into !== void 0 ? ["--check", into] : []
535
+ ];
536
+ var runSync = async (input) => {
537
+ if (input.target !== void 0) {
538
+ return runBin(
539
+ input.cwd,
540
+ { bin: "geonosis-ledger", package: "@geonosis/ledger" },
541
+ ledgerArgs(input)
542
+ );
543
+ }
544
+ if (!existsSync4(join5(input.cwd, LAW_FILE))) {
545
+ return refused(
546
+ `geonosis sync: no ${LAW_FILE} in ${input.cwd} \u2014 there is no law to generate the other tools' files from.
547
+ `
548
+ );
549
+ }
550
+ const files = generate(input.cwd);
551
+ if (input.check === true) {
552
+ const stale = Object.entries(files).filter(([file, body]) => {
553
+ const path = join5(input.cwd, file);
554
+ return !existsSync4(path) || readFileSync3(path, "utf8") !== body;
555
+ });
556
+ return stale.length === 0 ? resultOf(0, `sync check PASS \u2014 ${Object.keys(files).length} file(s) match the law.
557
+ `) : resultOf(
558
+ 1,
559
+ `${stale.map(([file]) => ` ${file}: not what the law generates`).join("\n")}
560
+ sync check FAIL \u2014 run \`geonosis sync --write\`.
561
+ `
562
+ );
563
+ }
564
+ for (const [file, body] of Object.entries(files)) {
565
+ const path = join5(input.cwd, file);
566
+ mkdirSync(dirname(path), { recursive: true });
567
+ writeFileSync3(path, body);
568
+ }
569
+ return resultOf(
570
+ 0,
571
+ `${Object.keys(files).map((file) => ` wrote ${file}`).join("\n")}
572
+ sync \u2014 ${Object.keys(files).length} file(s) generated from ${LAW_FILE}.
573
+ `
574
+ );
575
+ };
576
+
577
+ // src/registry.ts
578
+ import { z } from "zod";
579
+ var passthroughInput = z.object({
580
+ args: z.array(z.string()).default([]),
581
+ cwd: z.string()
582
+ });
583
+ var WRAPPED = [
584
+ {
585
+ bin: "geonosis-ratchet",
586
+ description: "Measure every counter in geonosis.ratchet.json against the baseline; fail on growth, rewrite the baseline down on a shrink.",
587
+ id: "ratchet",
588
+ kind: "write",
589
+ package: "@geonosis/ratchet"
590
+ },
591
+ {
592
+ bin: "geonosis-doctor",
593
+ description: "Ask the four questions a version bump is not finished without: loaded, exercised, baseline, runner.",
594
+ id: "doctor",
595
+ kind: "read",
596
+ package: "@geonosis/doctor"
597
+ },
598
+ {
599
+ bin: "geonosis-verify",
600
+ description: "Run a tier of gates declared in geonosis.json and write the gate report a Stop hook and CI read.",
601
+ id: "verify",
602
+ kind: "write",
603
+ package: "@geonosis/verify"
604
+ },
605
+ {
606
+ bin: "geonosis-lint-parity",
607
+ description: "Diff two oxlint configs over the same paths, or over a reach corpus \u2014 the adoption proof and the release check.",
608
+ id: "lint-parity",
609
+ kind: "read",
610
+ package: "@geonosis/lint-parity"
611
+ },
612
+ {
613
+ bin: "geonosis-verify-arch",
614
+ description: "Run the whole-graph architecture scans a per-file linter cannot: uniqueness, reachability, tier direction.",
615
+ id: "verify-arch",
616
+ kind: "read",
617
+ package: "@geonosis/verify-arch"
618
+ },
619
+ {
620
+ bin: "geonosis-visual-diff",
621
+ description: "Compare two PNGs pixel for pixel, or check a frame against a recorded baseline.",
622
+ id: "visual-diff",
623
+ kind: "write",
624
+ package: "@geonosis/visual-diff"
625
+ },
626
+ {
627
+ bin: "geonosis-ledger",
628
+ description: "The tick ledger: plans, ticks, proofs, decisions, the fallbacks journal, the handoff block, the edges table.",
629
+ id: "ledger",
630
+ kind: "write",
631
+ package: "@geonosis/ledger"
632
+ },
633
+ {
634
+ bin: "geonosis-walk",
635
+ description: "Drive a browser over a repo\u2019s URLs with the probes it declared, and write the walk report.",
636
+ id: "walk",
637
+ kind: "read",
638
+ package: "@geonosis/walk"
639
+ },
640
+ {
641
+ bin: "geonosis-review",
642
+ description: "Apply or check a review decision against the acceptance criteria a plan states \u2014 decision in, verdict out.",
643
+ id: "review",
644
+ kind: "write",
645
+ package: "@geonosis/review"
646
+ },
647
+ {
648
+ bin: "geonosis-testbed",
649
+ description: "Run a suite against the adapters a repo declares \u2014 installed only where the testbed is a dependency.",
650
+ id: "testbed",
651
+ kind: "read",
652
+ package: "@geonosis/testbed"
653
+ },
654
+ {
655
+ bin: "geonosis-observability",
656
+ description: "Plant an error and prove it reached the sink \u2014 a write, because with a real sink it sends to a third party.",
657
+ id: "observability",
658
+ kind: "write",
659
+ package: "@geonosis/observability"
660
+ },
661
+ {
662
+ bin: "geonosis-ledger",
663
+ description: "The digest: six lines, always the same six, whatever the size of the repo.",
664
+ id: "status",
665
+ kind: "read",
666
+ lead: ["status"],
667
+ package: "@geonosis/ledger"
668
+ }
669
+ ];
670
+ var wrap = (one) => ({
671
+ description: one.description,
672
+ id: one.id,
673
+ inputSchema: passthroughInput,
674
+ kind: one.kind,
675
+ run: async (input) => {
676
+ const { args, cwd } = passthroughInput.parse(input);
677
+ return runBin(cwd, { bin: one.bin, package: one.package }, [...one.lead ?? [], ...args]);
678
+ }
679
+ });
680
+ var initInput = z.object({
681
+ brand: z.string().optional(),
682
+ cwd: z.string(),
683
+ presets: z.array(z.string()),
684
+ runtime: z.enum(["bun", "pnpm"]).optional()
685
+ });
686
+ var syncInput = z.object({
687
+ check: z.boolean().optional(),
688
+ cwd: z.string(),
689
+ into: z.string().optional(),
690
+ target: z.enum(["layer-walls", "restricted-imports", "turbo"]).optional(),
691
+ write: z.boolean().optional()
692
+ });
693
+ var discoverInput = z.object({
694
+ config: z.string().optional(),
695
+ cwd: z.string(),
696
+ paths: z.array(z.string()).optional()
697
+ });
698
+ var skillsInput = z.object({ cwd: z.string(), verb: z.string() });
699
+ var OWN = [
700
+ {
701
+ description: "Write the four config files an existing repo needs \u2014 geonosis.json, .oxlintrc.json, geonosis.ratchet.json, gate-baseline.json \u2014 refusing to overwrite any of them.",
702
+ id: "init",
703
+ inputSchema: initInput,
704
+ kind: "write",
705
+ run: async (input) => runInit(initInput.parse(input))
706
+ },
707
+ {
708
+ description: "Generate AGENTS.md and the Cursor rules from CLAUDE.md and this repo\u2019s skills, or hand a --target to the ledger\u2019s edges table.",
709
+ id: "sync",
710
+ inputSchema: syncInput,
711
+ kind: "write",
712
+ run: async (input) => runSync(syncInput.parse(input))
713
+ },
714
+ {
715
+ description: "Read the findings this repo\u2019s own oxlint config produces into fixShape candidates: rule, count, first three sites. Writes nothing.",
716
+ id: "discover",
717
+ inputSchema: discoverInput,
718
+ kind: "read",
719
+ run: async (input) => runDiscover(discoverInput.parse(input))
720
+ },
721
+ {
722
+ description: "list, lock or audit the skills this repo ships \u2014 pinned by content hash, and failed on drift or on an executable file inside a skill directory.",
723
+ id: "skills",
724
+ inputSchema: skillsInput,
725
+ kind: "write",
726
+ run: async (input) => runSkills(skillsInput.parse(input))
727
+ }
728
+ ];
729
+ var COMMANDS = [...WRAPPED.map(wrap), ...OWN].toSorted(
730
+ (a, b) => a.id.localeCompare(b.id)
731
+ );
732
+ var commandById = (id) => {
733
+ const found = COMMANDS.find((one) => one.id === id);
734
+ if (found === void 0) {
735
+ throw new Error(
736
+ `no command called "${id}" \u2014 the registry has ${COMMANDS.map((one) => one.id).join(", ")}`
737
+ );
738
+ }
739
+ return found;
740
+ };
741
+
742
+ export {
743
+ parseArgv,
744
+ inputFor,
745
+ resultOf,
746
+ refused,
747
+ candidatesOf,
748
+ formatCandidates,
749
+ runDiscover,
750
+ optionsNeededBy,
751
+ runtimeOf,
752
+ countersFor,
753
+ rulesFor,
754
+ runInit,
755
+ LOCK_FILE,
756
+ SKILL_FILE,
757
+ hashOf,
758
+ rootsIn,
759
+ skillsIn,
760
+ executablesIn,
761
+ lockFor,
762
+ runSkills,
763
+ findBin,
764
+ runBin,
765
+ LAW_FILE,
766
+ AGENTS_FILE,
767
+ CURSOR_RULES,
768
+ generate,
769
+ runSync,
770
+ WRAPPED,
771
+ COMMANDS,
772
+ commandById
773
+ };