@geonosis/cli 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -32,7 +32,7 @@ the version skew `geonosis-doctor` exists to catch.
32
32
 
33
33
  | Command | What it does |
34
34
  |---|---|
35
- | `geonosis init --presets a,b [--brand x] [--runtime pnpm\|bun]` | Writes `geonosis.json`, `.oxlintrc.json`, `geonosis.ratchet.json` and `gate-baseline.json` into an EXISTING repo. It refuses to overwrite any of them and when one exists, it writes none of the others. |
35
+ | `geonosis init --presets a,b [--brand x] [--runtime pnpm\|bun]` | Writes `geonosis.json`, `.oxlintrc.json`, `geonosis.ratchet.json` and `gate-baseline.json` into an EXISTING repo each one that is absent; each one that exists is kept byte for byte and named (`kept … already yours`), never overwritten and never merged into. An existing repo is exactly the one that already has some of them. `init --help` prints the syntax; a flag it does not have is refused by name. |
36
36
  | `geonosis sync [--check] [--target turbo\|layer-walls\|restricted-imports --into <file>]` | Generates `AGENTS.md` and `.cursor/rules/*.mdc` from `CLAUDE.md` and this repo's skills — one law, every tool (D-002). Deterministic, so `--check` is a gate. With `--target` it hands the edges table to `geonosis-ledger sync`, exit code and all. |
37
37
  | `geonosis discover [--config <file>] [paths…]` | Reads the findings this repo's own oxlint config already produces into `fixShape` candidates — rule, count, first three sites. It writes nothing: the scored agent never writes the scoreboard. |
38
38
  | `geonosis skills list\|lock\|audit` | `skills-lock.json` by content hash, in during.day's shape (`source`, `sourceType`, `skillPath`, `computedHash`). `audit` fails on drift, on a skill nobody pinned, and on an **executable file inside a skill directory** — the Vercel incident class a markdown hash cannot see. |
package/bin/geonosis.mjs CHANGED
@@ -1,4 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  // Committed, so `pnpm install` can link the bin on a fresh clone — before `pnpm build` has
3
3
  // produced dist/. A bin that only exists after a build is a bin that is missing when you need it.
4
- import '../dist/geonosis-cli.js'
4
+ //
5
+ // In the repo, `src/` sits beside `dist/`, and a dist older than src answered for a fix it did not
6
+ // carry once (#62). The published package ships no src, so there the check is skipped.
7
+ import { existsSync, readdirSync, statSync } from 'node:fs'
8
+ import { dirname, join } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url))
12
+ const newest = (dir) =>
13
+ existsSync(dir)
14
+ ? readdirSync(dir, { withFileTypes: true }).reduce((most, entry) => {
15
+ const at = join(dir, entry.name)
16
+ return Math.max(most, entry.isDirectory() ? newest(at) : statSync(at).mtimeMs)
17
+ }, 0)
18
+ : 0
19
+ const src = join(here, '..', 'src')
20
+ if (existsSync(src) && newest(src) > newest(join(here, '..', 'dist'))) {
21
+ process.stderr.write(
22
+ 'geonosis: dist is older than src — run pnpm build before trusting this bin.\n',
23
+ )
24
+ process.exit(2)
25
+ }
26
+
27
+ await import('../dist/geonosis-cli.js')
@@ -37,11 +37,13 @@ var inputFor = (id, argv, cwd) => {
37
37
  if (valued === void 0) return { args: [...argv], cwd };
38
38
  const { flags, positional, switches } = parseArgv(argv, valued);
39
39
  if (id === "init") {
40
+ const unknownFlags = [...switches, ...Object.keys(flags)].filter((one) => !valued.has(one));
40
41
  return {
41
42
  ...flags["--brand"] === void 0 ? {} : { brand: flags["--brand"] },
42
43
  cwd,
43
44
  presets: list(flags["--presets"]),
44
- ...flags["--runtime"] === void 0 ? {} : { runtime: flags["--runtime"] }
45
+ ...flags["--runtime"] === void 0 ? {} : { runtime: flags["--runtime"] },
46
+ ...unknownFlags.length === 0 ? {} : { unknownFlags }
45
47
  };
46
48
  }
47
49
  if (id === "sync") {
@@ -133,8 +135,8 @@ var optionsNeededBy = () => {
133
135
  };
134
136
 
135
137
  // src/init.ts
136
- import { existsSync as existsSync2, writeFileSync } from "fs";
137
- import { join as join2 } from "path";
138
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
139
+ import { dirname, join as join2 } from "path";
138
140
  import { PRESETS } from "@geonosis/oxlint-plugin-biological-architecture";
139
141
  var PREFIX = "biological-architecture/";
140
142
  var SCHEMA = "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json";
@@ -211,17 +213,36 @@ var oxlintConfig = (rules2) => ({
211
213
  var json = (value) => `${JSON.stringify(value, void 0, 2)}
212
214
  `;
213
215
  var refusal = (output) => resultOf(1, output);
216
+ var baselineNamedBy = (cwd) => {
217
+ const at = join2(cwd, "geonosis.ratchet.json");
218
+ if (!existsSync2(at)) return "gate-baseline.json";
219
+ try {
220
+ const parsed = JSON.parse(readFileSync(at, "utf8"));
221
+ return typeof parsed.baseline === "string" && parsed.baseline !== "" ? parsed.baseline : "gate-baseline.json";
222
+ } catch {
223
+ return "gate-baseline.json";
224
+ }
225
+ };
226
+ var INIT_USAGE = "geonosis init --presets <a,b,c> [--brand <name>] [--runtime pnpm|bun] \u2014 writes geonosis.json, .oxlintrc.json, geonosis.ratchet.json and gate-baseline.json where they are absent; a file that exists is kept, never overwritten.";
214
227
  var runInit = async ({
215
228
  brand,
216
229
  cwd,
217
230
  presets,
218
- runtime
231
+ runtime,
232
+ unknownFlags = []
219
233
  }) => {
220
234
  const known = Object.keys(PRESETS).toSorted();
235
+ if (unknownFlags.length > 0) {
236
+ return refusal(
237
+ `geonosis init: ${unknownFlags.join(", ")} is not a flag of this command \u2014 ${INIT_USAGE}
238
+ `
239
+ );
240
+ }
221
241
  const unknown = presets.filter((name) => !known.includes(name));
222
242
  if (presets.length === 0 || unknown.length > 0) {
223
243
  return refusal(
224
244
  `geonosis init: ${presets.length === 0 ? "name at least one preset" : `no preset called ${unknown.join(", ")}`} \u2014 the kit ships ${known.join(", ")}.
245
+ ${INIT_USAGE}
225
246
  `
226
247
  );
227
248
  }
@@ -237,26 +258,33 @@ var runInit = async ({
237
258
  brand === void 0 ? {} : { "no-brand-names": ["error", { brands: [brand] }] }
238
259
  );
239
260
  const counters = countersFor(chosen);
261
+ const baselinePath = baselineNamedBy(cwd);
240
262
  const files = {
241
263
  "geonosis.json": json({ verify: tiersOf(chosen) }),
242
264
  ".oxlintrc.json": json(oxlintConfig(rules2)),
243
265
  "geonosis.ratchet.json": json({ baseline: "gate-baseline.json", counters }),
244
- "gate-baseline.json": json(Object.fromEntries(counters.map((one) => [keyOf(one), 0])))
266
+ [baselinePath]: json(Object.fromEntries(counters.map((one) => [keyOf(one), 0])))
245
267
  };
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.
268
+ const kept = Object.keys(files).filter((file) => existsSync2(join2(cwd, file)));
269
+ const wrote = Object.keys(files).filter((file) => !kept.includes(file));
270
+ for (const file of wrote) {
271
+ mkdirSync(dirname(join2(cwd, file)), { recursive: true });
272
+ writeFileSync(join2(cwd, file), files[file] ?? "");
273
+ }
274
+ if (wrote.length === 0) {
275
+ return resultOf(
276
+ 0,
277
+ `geonosis init \u2014 nothing to write: ${kept.join(", ")} already exist in ${cwd} and are kept as they are.
250
278
  `
251
279
  );
252
280
  }
253
- for (const [file, body] of Object.entries(files)) writeFileSync(join2(cwd, file), body);
254
281
  return resultOf(
255
282
  0,
256
283
  [
257
284
  `geonosis init \u2014 ${chosen}, ${Object.keys(rules2).length} rule(s) enabled from ${presets.join(" + ")}`,
258
285
  "",
259
- ...Object.keys(files).map((file) => ` wrote ${file}`),
286
+ ...wrote.map((file) => ` wrote ${file}`),
287
+ ...kept.map((file) => ` kept ${file} (already yours \u2014 not touched)`),
260
288
  "",
261
289
  ...unanswered.length === 0 ? [] : [
262
290
  ` ${unanswered.length} rule(s) were NOT enabled: each needs an option only this repo can answer.`,
@@ -273,7 +301,7 @@ var runInit = async ({
273
301
 
274
302
  // src/skills.ts
275
303
  import { createHash } from "crypto";
276
- import { existsSync as existsSync3, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
304
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
277
305
  import { join as join3, relative, sep } from "path";
278
306
  var LOCK_FILE = "skills-lock.json";
279
307
  var SKILL_FILE = "SKILL.md";
@@ -292,7 +320,7 @@ var skillsIn = (cwd) => {
292
320
  if (!existsSync3(skill)) continue;
293
321
  found.push({
294
322
  dir,
295
- hash: hashOf(readFileSync(skill)),
323
+ hash: hashOf(readFileSync2(skill)),
296
324
  name: entry.name,
297
325
  skillPath: posix(relative(cwd, skill))
298
326
  });
@@ -303,7 +331,7 @@ var skillsIn = (cwd) => {
303
331
  var readLock = (cwd) => {
304
332
  const path = join3(cwd, LOCK_FILE);
305
333
  if (!existsSync3(path)) return void 0;
306
- return JSON.parse(readFileSync(path, "utf8"));
334
+ return JSON.parse(readFileSync2(path, "utf8"));
307
335
  };
308
336
  var executablesIn = (dir, cwd) => {
309
337
  const found = [];
@@ -403,7 +431,7 @@ var runSkills = async ({ cwd, verb }) => {
403
431
 
404
432
  // src/spawn.ts
405
433
  import { spawnSync as spawnSync2 } from "child_process";
406
- import { readFileSync as readFileSync2 } from "fs";
434
+ import { readFileSync as readFileSync3 } from "fs";
407
435
  import { createRequire } from "module";
408
436
  import { join as join4 } from "path";
409
437
  import { fileURLToPath } from "url";
@@ -412,7 +440,7 @@ var OUTPUT_LIMIT = 8 * 1024 * 1024;
412
440
  var entryOf = (from, name) => createRequire(join4(from, "noop.js")).resolve(name);
413
441
  var binPath = (from, ref) => {
414
442
  const dir = packageDirOf(entryOf(from, ref.package), ref.package);
415
- const manifest = JSON.parse(readFileSync2(join4(dir, "package.json"), "utf8"));
443
+ const manifest = JSON.parse(readFileSync3(join4(dir, "package.json"), "utf8"));
416
444
  const declared = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[ref.bin];
417
445
  if (declared === void 0) {
418
446
  throw new Error(`${ref.package} declares no bin called "${ref.bin}"`);
@@ -423,7 +451,7 @@ var SELF = fileURLToPath(new URL("..", import.meta.url));
423
451
  var versionOf = (from, name) => {
424
452
  try {
425
453
  const dir = packageDirOf(entryOf(from, name), name);
426
- const manifest = JSON.parse(readFileSync2(join4(dir, "package.json"), "utf8"));
454
+ const manifest = JSON.parse(readFileSync3(join4(dir, "package.json"), "utf8"));
427
455
  return manifest.version ?? "an unknown version";
428
456
  } catch {
429
457
  return "an unknown version";
@@ -464,8 +492,8 @@ var runBin = async (cwd, ref, args) => {
464
492
  };
465
493
 
466
494
  // 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";
495
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
496
+ import { dirname as dirname2, join as join5 } from "path";
469
497
  var LAW_FILE = "CLAUDE.md";
470
498
  var AGENTS_FILE = "AGENTS.md";
471
499
  var CURSOR_RULES = ".cursor/rules";
@@ -481,10 +509,10 @@ var frontOf = (source) => {
481
509
  };
482
510
  };
483
511
  var generate = (cwd) => {
484
- const law = readFileSync3(join5(cwd, LAW_FILE), "utf8").trimEnd();
512
+ const law = readFileSync4(join5(cwd, LAW_FILE), "utf8").trimEnd();
485
513
  const skills = skillsIn(cwd).map((skill) => ({
486
514
  ...skill,
487
- ...frontOf(readFileSync3(join5(skill.dir, "SKILL.md"), "utf8"))
515
+ ...frontOf(readFileSync4(join5(skill.dir, "SKILL.md"), "utf8"))
488
516
  }));
489
517
  const files = {
490
518
  [AGENTS_FILE]: [
@@ -551,7 +579,7 @@ var runSync = async (input) => {
551
579
  if (input.check === true) {
552
580
  const stale = Object.entries(files).filter(([file, body]) => {
553
581
  const path = join5(input.cwd, file);
554
- return !existsSync4(path) || readFileSync3(path, "utf8") !== body;
582
+ return !existsSync4(path) || readFileSync4(path, "utf8") !== body;
555
583
  });
556
584
  return stale.length === 0 ? resultOf(0, `sync check PASS \u2014 ${Object.keys(files).length} file(s) match the law.
557
585
  `) : resultOf(
@@ -563,7 +591,7 @@ sync check FAIL \u2014 run \`geonosis sync --write\`.
563
591
  }
564
592
  for (const [file, body] of Object.entries(files)) {
565
593
  const path = join5(input.cwd, file);
566
- mkdirSync(dirname(path), { recursive: true });
594
+ mkdirSync2(dirname2(path), { recursive: true });
567
595
  writeFileSync3(path, body);
568
596
  }
569
597
  return resultOf(
@@ -590,7 +618,7 @@ var WRAPPED = [
590
618
  },
591
619
  {
592
620
  bin: "geonosis-doctor",
593
- description: "Ask the four questions a version bump is not finished without: loaded, exercised, baseline, runner.",
621
+ description: "Ask the seven questions a version bump is not finished without: loaded, exercised, baseline, runner, drift, observability, deployed.",
594
622
  id: "doctor",
595
623
  kind: "read",
596
624
  package: "@geonosis/doctor"
@@ -637,6 +665,13 @@ var WRAPPED = [
637
665
  kind: "read",
638
666
  package: "@geonosis/walk"
639
667
  },
668
+ {
669
+ bin: "geonosis-release",
670
+ description: "Refuse a migration the serving version cannot survive, validate the release contract, prove a smoke named the version that answered it, and read declared against deployed.",
671
+ id: "release",
672
+ kind: "read",
673
+ package: "@geonosis/release"
674
+ },
640
675
  {
641
676
  bin: "geonosis-review",
642
677
  description: "Apply or check a review decision against the acceptance criteria a plan states \u2014 decision in, verdict out.",
@@ -681,7 +716,8 @@ var initInput = z.object({
681
716
  brand: z.string().optional(),
682
717
  cwd: z.string(),
683
718
  presets: z.array(z.string()),
684
- runtime: z.enum(["bun", "pnpm"]).optional()
719
+ runtime: z.enum(["bun", "pnpm"]).optional(),
720
+ unknownFlags: z.array(z.string()).optional()
685
721
  });
686
722
  var syncInput = z.object({
687
723
  check: z.boolean().optional(),
@@ -702,7 +738,8 @@ var OWN = [
702
738
  id: "init",
703
739
  inputSchema: initInput,
704
740
  kind: "write",
705
- run: async (input) => runInit(initInput.parse(input))
741
+ run: async (input) => runInit(initInput.parse(input)),
742
+ usage: INIT_USAGE
706
743
  },
707
744
  {
708
745
  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.",
@@ -2,7 +2,7 @@ import {
2
2
  COMMANDS,
3
3
  commandById,
4
4
  inputFor
5
- } from "./chunk-U3TBZWD3.js";
5
+ } from "./chunk-RKOVR7WT.js";
6
6
 
7
7
  // src/geonosis-cli.ts
8
8
  var REFUSED = 2;
@@ -46,6 +46,11 @@ var main = async () => {
46
46
  `);
47
47
  return REFUSED;
48
48
  }
49
+ if (command.usage !== void 0 && rest.slice(1).some((one) => one === "--help" || one === "-h")) {
50
+ process.stdout.write(`${command.usage}
51
+ `);
52
+ return 0;
53
+ }
49
54
  const result = await command.run(inputFor(id, rest.slice(1), process.cwd()));
50
55
  process.stdout.write(
51
56
  json ? `${JSON.stringify({ command: id, exitCode: result.exitCode, ok: result.ok, output: result.output }, void 0, 2)}
package/dist/index.d.ts CHANGED
@@ -44,6 +44,8 @@ type CommandKind = 'read' | 'write';
44
44
  */
45
45
  type Command = {
46
46
  description: string;
47
+ /** The command's own `--help`: one line of syntax the door prints and refusals quote. */
48
+ usage?: string;
47
49
  id: string;
48
50
  inputSchema: ZodType;
49
51
  kind: CommandKind;
@@ -83,6 +85,8 @@ type InitInput = {
83
85
  cwd: string;
84
86
  presets: string[];
85
87
  runtime?: Runtime;
88
+ /** Flags the door did not recognise — refused by name rather than read as "no preset". */
89
+ unknownFlags?: string[];
86
90
  };
87
91
  declare const runtimeOf: (cwd: string) => Runtime | undefined;
88
92
  declare const countersFor: (runtime: Runtime) => Record<string, unknown>[];
@@ -99,12 +103,7 @@ type Rules = {
99
103
  * listed — `optionsNeededBy` asks the rules themselves.
100
104
  */
101
105
  declare const rulesFor: (presets: string[], answers?: Record<string, unknown[]>) => Rules;
102
- /**
103
- * The four files an existing repo needs before any gate can run, written once and never over the
104
- * top of anything. A tool that overwrote a config would be a tool nobody dares run twice, and the
105
- * one thing worse than a repo without a baseline is a repo whose baseline was silently replaced.
106
- */
107
- declare const runInit: ({ brand, cwd, presets, runtime, }: InitInput) => Promise<CommandResult>;
106
+ declare const runInit: ({ brand, cwd, presets, runtime, unknownFlags, }: InitInput) => Promise<CommandResult>;
108
107
 
109
108
  /**
110
109
  * Which rules refuse to run without options, MEASURED by asking them.
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  runSync,
30
30
  runtimeOf,
31
31
  skillsIn
32
- } from "./chunk-U3TBZWD3.js";
32
+ } from "./chunk-RKOVR7WT.js";
33
33
  export {
34
34
  AGENTS_FILE,
35
35
  COMMANDS,
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@geonosis/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
+ "types": "./dist/index.d.ts",
4
5
  "description": "One declarative command registry over every geonosis bin — the CLI door onto it, plus init, sync, discover and skills.",
5
6
  "keywords": [
6
7
  "cli",
@@ -24,7 +25,10 @@
24
25
  "geonosis": "bin/geonosis.mjs"
25
26
  },
26
27
  "exports": {
27
- ".": "./dist/index.js"
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ }
28
32
  },
29
33
  "files": [
30
34
  "bin",
@@ -32,18 +36,19 @@
32
36
  ],
33
37
  "dependencies": {
34
38
  "zod": "^4.0.0",
35
- "@geonosis/doctor": "1.0.0",
36
- "@geonosis/lint-parity": "1.0.0",
37
- "@geonosis/observability": "1.0.0",
38
- "@geonosis/ledger": "1.0.0",
39
- "@geonosis/oxlint-plugin-biological-architecture": "1.0.0",
40
- "@geonosis/testbed": "1.0.0",
41
- "@geonosis/verify": "1.0.0",
42
- "@geonosis/verify-arch": "1.0.0",
43
- "@geonosis/ratchet": "1.0.0",
44
- "@geonosis/review": "1.0.0",
45
- "@geonosis/visual-diff": "1.0.0",
46
- "@geonosis/walk": "1.0.0"
39
+ "@geonosis/lint-parity": "1.1.0",
40
+ "@geonosis/observability": "1.1.0",
41
+ "@geonosis/doctor": "1.1.0",
42
+ "@geonosis/oxlint-plugin-biological-architecture": "1.1.0",
43
+ "@geonosis/ledger": "1.1.0",
44
+ "@geonosis/ratchet": "1.1.0",
45
+ "@geonosis/verify": "1.1.0",
46
+ "@geonosis/testbed": "1.1.0",
47
+ "@geonosis/release": "1.1.0",
48
+ "@geonosis/review": "1.1.0",
49
+ "@geonosis/visual-diff": "1.1.0",
50
+ "@geonosis/verify-arch": "1.1.0",
51
+ "@geonosis/walk": "1.1.0"
47
52
  },
48
53
  "peerDependencies": {
49
54
  "oxlint": ">=1.77"