@theholocron/cli 5.0.0-alpha.20 → 5.0.0-alpha.200

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
@@ -17,33 +17,101 @@ holocron --help
17
17
  ## Interactive mode
18
18
 
19
19
  Run `holocron` with no command, a parent command with no subcommand
20
- (`holocron auth`, `holocron skills`, `holocron upgrade`), or a leaf command
21
- missing a required positional (`holocron deploy`, `holocron secret set`), and
22
- you get a prompt instead of a `--help` dead end:
20
+ (`holocron auth`, `holocron repo`, `holocron repo upgrade`, …), or a leaf command
21
+ missing a required positional (`holocron deploy`, `holocron secrets set`), and
22
+ you get a prompt instead of a `--help` dead end.
23
+
24
+ The menu mirrors the command tree: each level shows only the next word, and a
25
+ group (marked `›`) opens its own submenu, as deep as the commands nest. Nothing
26
+ is shown as a `<command> <subcommand>` path — `config show` is `config ›` then
27
+ `show`. Every submenu has a `← back` item, and a command that is also a group
28
+ (`repo sync`, `deploy`) has a `(run it)` item at the top of its submenu. Typing
29
+ searches the whole tree — `upgrade` at the top finds the `repo` group — with
30
+ name matches ranked above description matches.
31
+
32
+ ```console
33
+ $ holocron
34
+ ? What would you like to do? › repo
35
+ ❯ repo ›
36
+ ? repo — choose a subcommand: › upgrade
37
+ ? repo upgrade — choose a subcommand: › node
38
+ ? Target Node.js major version: › 24
39
+ ```
23
40
 
41
+ For a command that declares options (so far: `package publish` — more are
42
+ rolled out one command group at a time), it also offers those: any required
43
+ option is prompted like a positional, and everything else goes through one
44
+ "Set any options?" checkbox, prompting only what you picked:
45
+
46
+ ```console
47
+ ? Set any options? (space to select, enter to skip) › ◉ npm dist-tag ◯ One-time password (2FA)
48
+ ? npm dist-tag (blank = latest): › beta
24
49
  ```
25
- $ holocron
26
- ? What would you like to do? › dep
27
- ────────────────────────────────────────────────
28
- ❯ deploy Trigger a deployment via the configured `deployment` capability
29
50
 
30
- ? Branch to deploy: › main
51
+ `package publish` specifically has three workflows that don't mix — a
52
+ steady-state publish, a one-shot `--initial` bootstrap, and a `--sync-trust`
53
+ maintenance run — so it asks which one first, then only offers that
54
+ workflow's own options; nothing from a different workflow is ever shown:
55
+
56
+ ```console
57
+ ? What would you like to do? › Initial publish (--initial)
58
+ ? Set any options? (space to select, enter to skip) › ◉ One-time password (2FA)
59
+ ...dry-run output...
60
+ ? Dry run complete — proceed with the real run? › yes
61
+ ? One-time password (2FA), if npm requires it for this publish: › ***
31
62
  ```
32
63
 
64
+ Each workflow's label ends with its real flag in parentheses when it has
65
+ one, and a longer caveat ("use only for a package's very first publish")
66
+ shows as helper text under the highlighted choice rather than bloating the
67
+ label itself.
68
+
69
+ A secret option (`--otp`) is masked as you type and delivered to the spawned
70
+ command through its environment, never its argv — `ps` and shell history
71
+ both expose a child's argv, never its env. A time-sensitive one like `--otp`
72
+ is checked off early but not actually typed until right before the real run
73
+ — after the dry-run preview and the "proceed?" question, never before —
74
+ since a dry run never checks it anyway and a code typed any earlier risks
75
+ going stale while something else is still being answered.
76
+
77
+ Whether an option is _checkbox-optional vs. always included_ and _collected
78
+ upfront vs. collected last_ are independent choices. `package publish`'s
79
+ `--otp` is always collected last, but whether it's checkbox-optional depends
80
+ on `HOLOCRON_NPM_ENFORCE_2FA`: unset (the default), it's opt-in like any other
81
+ checkbox pick; set in your own shell profile (never checked into the repo),
82
+ it's always included — for an operator whose own npm account enforces 2FA
83
+ on every publish, so the box doesn't need re-checking every single run.
84
+ Setting it has no effect on anyone else publishing from the same repo.
85
+
33
86
  The picked command spawns as a normal `holocron <command> …` invocation —
34
87
  `--token`, `--org`, `--cwd`, and `--dry-run` on the original invocation carry
35
88
  through. A non-interactive shell (CI, a script, a pipe) gets the old
36
89
  hard-failure message instead of hanging on a prompt.
37
90
 
91
+ For a command whose handler honours `--dry-run` (most of them — everything
92
+ except `auth *` and `config show`), the menu always previews it first — a
93
+ dry run doesn't mutate anything, so there's nothing to ask permission for —
94
+ then asks the one real decision:
95
+
96
+ ```console
97
+ ...dry-run output...
98
+ ? Dry run complete — proceed with the real run? › yes
99
+ ```
100
+
101
+ A preview that reports nothing would actually change (e.g. `clone` finding
102
+ every repo already cloned) skips that question too — there'd be nothing for
103
+ the real run to do either. `--skip-dry-run` opts out of the preview
104
+ entirely, straight to the real run, for when you already know what you want.
105
+
38
106
  ## Execution contexts
39
107
 
40
108
  Every command is tagged with how much of a repo it needs:
41
109
 
42
- | Context | Needs | Examples |
43
- | ------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
44
- | `global` | just the CLI binary | `version`, `clone`, `new`, `upgrade node`, `auth set` / `check`, `plugin create`, `skills …` |
45
- | `repo-aware` | `./holocron.config` in cwd, no plugins | `run`, `ci`, `config show`, `sync-readme` |
46
- | `workspace` | the `@theholocron/holocron-plugin-*` packages resolvable | `doctor`, `setup`, `sync`, `secrets sync`, `deploy` |
110
+ | Context | Needs | Examples |
111
+ | ------------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `global` | the CLI binary alone | `version`, `clone`, `new repo` / `plugin`, `repo upgrade …`, `auth set` / `check`, `package …`, `run lint commit-msg`, `skills …` |
113
+ | `repo-aware` | `./holocron.config`, walking up from cwd, no plugins | `run`, `run ci`, `config show`, `repo sync readme`, `deploy on-release` |
114
+ | `workspace` | the `@theholocron/holocron-plugin-*` packages resolvable | `doctor`, `repo setup`, `repo sync`, `secrets …`, `deploy`, `sync github` |
47
115
 
48
116
  A `workspace` command run from a bare global install (no plugins next to the
49
117
  CLI) fails with one actionable line — install the plugins as devDependencies or
@@ -51,6 +119,21 @@ use `pnpm exec holocron <cmd>`. `auth check` degrades to "token present,
51
119
  verification skipped" instead. `holocron --help` prints the grouping; see
52
120
  [Execution Contexts](https://theholocron.github.io/holocron/execution-contexts).
53
121
 
122
+ ### Viewing the resolved config
123
+
124
+ `holocron config show` prints the resolved config. On a TTY it opens a foldable
125
+ tree by default; piped or redirected, it always emits plain, valid JSON
126
+ (`holocron config show | jq`).
127
+
128
+ ```console
129
+ holocron config show # TTY: foldable tree (↑↓ move, →/⏎ expand, ← collapse, E/C all, q quit)
130
+ holocron config show --depth 2 # static coloured tree, anything nested deeper than 2 levels collapsed
131
+ holocron config show --no-interactive # static coloured tree, fully expanded
132
+ ```
133
+
134
+ Passing `--interactive` explicitly needs a TTY on stdin and stdout and fails
135
+ with a one-line message otherwise; the default falls back to static output. It has no extra dependencies — nothing to install.
136
+
54
137
  ## Config file
55
138
 
56
139
  Holocron reads `holocron.config.{json,js,ts}` from the project root
@@ -103,18 +186,18 @@ A minimal config — for repos with a `package.json` and a GitHub remote
103
186
 
104
187
  ### repo options
105
188
 
106
- Additional `repo` fields recognised by `holocron setup`:
189
+ Additional `repo` fields recognised by `holocron repo setup`:
107
190
 
108
- | Field | Type | Description |
109
- | ----------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
110
- | `repo.teams` | `Array<string \| { slug, permission }>` | GitHub teams granted repo access. String shorthand defaults to `push` (Write). `holocron setup` also writes `.github/CODEOWNERS` for teams with `push`/`maintain`/`admin`. |
111
- | `repo.topics` | `string[]` | GitHub topics set on the repository. |
112
- | `repo.protection` | `"balanced" \| "strict" \| "none"` | Branch-protection preset applied by `holocron setup`. For `"strict"`, the required status checks are derived from the task manifest — see below. |
113
- | `repo.properties` | `RepoProperties` | Org-level custom property values synced to the GitHub dashboard. |
191
+ | Field | Type | Description |
192
+ | ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
193
+ | `repo.teams` | `Array<string \| { slug, permission }>` | GitHub teams granted repo access. String shorthand defaults to `push` (Write). `holocron repo setup` also writes `.github/CODEOWNERS` for teams with `push`/`maintain`/`admin`. |
194
+ | `repo.topics` | `string[]` | GitHub topics set on the repository. |
195
+ | `repo.protection` | `"balanced" \| "strict" \| "none"` | Branch-protection preset applied by `holocron repo setup`. For `"strict"`, the required status checks are derived from the task manifest — see below. |
196
+ | `repo.properties` | `RepoProperties` | Org-level custom property values synced to the GitHub dashboard. |
114
197
 
115
198
  ### Custom properties synced to GitHub
116
199
 
117
- `holocron setup` and `holocron sync` both call `syncProperties()` with two
200
+ `holocron repo setup` and `holocron repo sync` both call `syncProperties()` with two
118
201
  kinds of fields — always one-way (`holocron.config.ts` → resolved →
119
202
  properties; properties are never a second editable source of truth):
120
203
 
@@ -160,7 +243,7 @@ posts, instead of reporting pass/fail.
160
243
 
161
244
  ### Required status checks (`protection: "strict"`)
162
245
 
163
- `holocron setup` builds the branch-protection required-check list from the
246
+ `holocron repo setup` builds the branch-protection required-check list from the
164
247
  manifest, not a hand-maintained array:
165
248
 
166
249
  - `"DCO"` (hard-prepended), then
@@ -186,12 +269,32 @@ The generated ruleset also sets `require_extra_approval_for_unattributed_changes
186
269
  false` — with `required_approving_review_count: 0` and a single maintainer, the
187
270
  GitHub default (`true`) is an unsatisfiable block on any PR with an
188
271
  unverified/unattributed commit. Repository-admin bypass still covers the real
189
- risk. And the `codecov.yml` `holocron setup` writes carries `if_not_found:
272
+ risk. And the `codecov.yml` `holocron repo setup` writes carries `if_not_found:
190
273
  success` on every status default, so an infra-only PR (dep bump, config,
191
274
  lockfile — no coverable change) passes instead of hanging on a `codecov/*`
192
- required check that never posts. A re-run of `holocron setup` backfills both into
275
+ required check that never posts. A re-run of `holocron repo setup` backfills both into
193
276
  an existing repo.
194
277
 
278
+ ### Default branch (`repo.defaultBranch`)
279
+
280
+ ```ts
281
+ export default defineConfig({
282
+ repo: { protection: "strict", defaultBranch: "alpha" },
283
+ providers: { source: "github" },
284
+ });
285
+ ```
286
+
287
+ `holocron repo setup` syncs GitHub's repo-level default branch (`HEAD`) — what a
288
+ fresh clone checks out, what `gh pr create`/the compare UI target by default,
289
+ and what Sentinel's `validateConfig()` reads (it only ever reads config from
290
+ the repo's default branch, never a PR ref — a deliberate security boundary).
291
+ Omit this field to leave GitHub's current setting untouched; most repos never
292
+ need it. It exists for a repo using a `main`/`alpha` prerelease-channel split
293
+ where active development happens on a non-`main` branch: set this to that
294
+ branch so Sentinel validates against what's actually being developed, then
295
+ change it back once that branch merges to `main` for a stable cut and stops
296
+ being the active branch.
297
+
195
298
  ### Git hooks
196
299
 
197
300
  ```ts
@@ -202,7 +305,7 @@ export default defineConfig({
202
305
  });
203
306
  ```
204
307
 
205
- `holocron setup` writes `.husky/pre-push` (runs `holocron ci` before every
308
+ `holocron repo setup` writes `.husky/pre-push` (runs `holocron run ci` before every
206
309
  push) and `.husky/pre-commit` (runs `gitleaks protect --staged` + `lint-staged`
207
310
  before every commit — scoped to staged changes, not the whole history, so a
208
311
  past false positive can't permanently block future commits) and sets
@@ -213,7 +316,7 @@ setup --hooks` / `--no-hooks` override per run. Bypass one push with `git push
213
316
 
214
317
  ### Task scripts
215
318
 
216
- `holocron sync` reconciles `package.json#scripts` from the `tasks` manifest
319
+ `holocron repo sync` reconciles `package.json#scripts` from the `tasks` manifest
217
320
  (merge, never clobber — only the managed keys are touched):
218
321
 
219
322
  ```ts
@@ -248,8 +351,8 @@ export default defineConfig({
248
351
  `@theholocron/astromech`'s `loadTasksConfig` merges it over
249
352
  `holocron.config`'s `tasks` — **task arrays concatenate**
250
353
  (`holocron.config` first), the dedicated file **wins on scalars**,
251
- `extraRequiredChecks` concatenate. `holocron run` / `holocron ci` /
252
- `holocron setup` / `holocron sync` all read the merged result, so the
354
+ `extraRequiredChecks` concatenate. `holocron run` / `holocron run ci` /
355
+ `holocron repo setup` / `holocron repo sync` all read the merged result, so the
253
356
  split is transparent. Useful for keeping the capability/provider config
254
357
  and the "what this repo runs" manifest in separate files (this repo does).
255
358
 
@@ -276,10 +379,27 @@ job execute the same thing — at a turbo root both are
276
379
  `turbo run test -- --coverage` (the registry's org-default flags flow through
277
380
  turbo's `--`).
278
381
 
279
- ### `holocron ci`
382
+ ### `holocron deploy on-release`
280
383
 
281
384
  ```bash
282
- holocron ci [--all] [--filter <pkg>] [--dry-run]
385
+ holocron deploy on-release [--channel <alpha|main>] [--from <commit>] [--to <commit>] [--dry-run]
386
+ ```
387
+
388
+ Deploys every workspace package whose own `holocron.config` declares
389
+ `{ name: "delivery.deploy", with: { on: "release" } }` (holocron#930), when the
390
+ release (`--channel`: its prerelease identifier, or `main`/empty for a stable
391
+ release) is on the task's `channel` (default `main`, the stable release) and changes the package or one of its
392
+ `workspace:*` dependencies between `--from` (the previous release's commit,
393
+ omitted when there is none) and `--to` (default `HEAD`). It waits for npm to
394
+ serve the release's new dependency versions, then runs `pnpm --filter <pkg>
395
+ delivery.deploy`. A package that doesn't apply is skipped; a deploy that
396
+ doesn't succeed exits non-zero. The shared `delivery.publish` workflow runs it as its own
397
+ `deploy` job after the release job (opt in with `deploy-on-release: true`).
398
+
399
+ ### `holocron run ci`
400
+
401
+ ```bash
402
+ holocron run ci [--all] [--filter <pkg>] [--dry-run]
283
403
  ```
284
404
 
285
405
  Runs the merge-gating checks locally, in CI order, exiting non-zero on the
@@ -293,6 +413,46 @@ Order comes from the `CI_ORDER` constant in `@theholocron/astromech`
293
413
  expands into its sub-jobs (each under its own `audit / …` check context)
294
414
  unless the repo ships its own `"audit"` script.
295
415
 
416
+ ### `holocron deploy`
417
+
418
+ `holocron deploy <branch>` (a Git-linked deployment) and `holocron deploy
419
+ --files <dir>` (a directory of build output, no linked repo) both trigger a
420
+ deployment through the configured `deployment` capability, then **wait for
421
+ it to finish**. The command reports success, exit code 0, only when the
422
+ deployment reaches `ready`. A deployment that ends `error` or `cancelled`,
423
+ or is still building after 10 minutes, is a failure: the exit code is
424
+ non-zero and the output includes the provider's own reason
425
+ (`DeploymentRecord.errorMessage`, e.g. Vercel's `Command "npm install"
426
+ exited with 1`). Providers build asynchronously, so accepting a deployment
427
+ isn't the same as it going live (holocron#911).
428
+
429
+ Provider plugins support this through the `Deployment` capability's
430
+ existing `getDeployment(id)`. They should set `errorMessage` on the
431
+ returned `DeploymentRecord` whenever the provider exposes one.
432
+
433
+ ### `holocron monitoring check <url>`
434
+
435
+ Runs every configured `monitoring` check against `<url>` instead of each
436
+ check's own configured `url` — no vendor API call, only a plain HTTP
437
+ request evaluated locally against the exact same `assertions` already in
438
+ `holocron.config.ts`. Built for CI: the `delivery.previewCheck` task runs
439
+ this against a PR's Vercel preview deployment (triggered by
440
+ `deployment_status`, not `push`/`pull_request`), reusing the production
441
+ check definitions to catch a breaking change before merge instead of
442
+ after (holocron#1010).
443
+
444
+ ```ts
445
+ export default defineConfig({
446
+ tasks: ["delivery.previewCheck"],
447
+ providers: {
448
+ monitoring: ["checkly", { accountId: "...", checks: [/* ... */] }],
449
+ },
450
+ });
451
+ ```
452
+
453
+ Exits non-zero if any check fails — the job's own pass/fail is what
454
+ posts as the GitHub check, no separate reporting step needed.
455
+
296
456
  ### Lint parity
297
457
 
298
458
  The `lint` task takes an optional `linters` array — one list that drives
@@ -310,7 +470,7 @@ export default defineConfig({
310
470
 
311
471
  ### Skills installer
312
472
 
313
- `holocron setup` can install shared skills from `@theholocron/skills` into the local repo:
473
+ `holocron repo setup` can install shared skills from `@theholocron/skills` into the local repo:
314
474
 
315
475
  ```ts
316
476
  export default defineConfig({
@@ -371,18 +531,62 @@ export default acmeConfig;
371
531
 
372
532
  ```
373
533
 
534
+ ## Notifications — server as code
535
+
536
+ A `notifications` provider can declare a whole server in `holocron.config.ts`
537
+ (`layout`: categories, channels, roles, access, server settings, welcome screen,
538
+ onboarding, the bot application) and the CLI applies and checks it. The
539
+ commands and the config are provider-neutral; Discord is the first provider.
540
+
541
+ | Command | What it does |
542
+ | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
543
+ | `holocron notifications setup` | Applies the layout; looks everything up by name first, so a re-run changes nothing that matches |
544
+ | `holocron notifications channel add <name>` | Creates one channel (optionally with a webhook whose URL is stored as a secret) |
545
+ | `holocron notifications link` | Links your account on the provider to your GitHub account by signing in with the provider (OAuth, nothing secret kept) |
546
+ | `holocron notifications sync-roles` | Makes who holds a role match a GitHub team, through the links file (`--keep` only adds; `--provider` picks one) |
547
+ | `holocron doctor notifications [--built-only]` | Read-only, step by step: does the server match the layout, and do the secrets exist |
548
+
549
+ Config shape added for role sync and sign-in (all under the provider's `layout`):
550
+
551
+ | Field | Meaning |
552
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
553
+ | `roles[].team` | A GitHub team slug. The role follows that team: given to linked members, taken from linked people who left. Never touches holders nobody has linked. |
554
+ | `links.hostedUrl` | A hosted sign-in service (Sentinel). `link` asks `<hostedUrl>/api/link` and uses it when it answers `{ "service": "link" }`; otherwise it signs in locally. |
555
+ | `links.localPort` | The port of the local sign-in listener (default `53682`, bound to `127.0.0.1` only). |
556
+ | `links.repo` / `links.path` | Where the links file lives (default `<org>/.github-private` and `notifications/<provider>-links.json`). |
557
+ | `application.intents.members` | Switches the Server Members Intent on or off (needed to list who holds a role). |
558
+ | `application.redirectUris` | Redirect URIs that must be registered. Check-only: the API cannot set them, so `setup` and the doctor say what to add in the developer portal. |
559
+
560
+ The links file is `{ "version": 1, "links": [{ "github": "<id>", "login": "<login>", "member": "<id>" }] }`: two public ids per person.
561
+ It is read and written through the `source` capability with the `github.user-sync` token (fine-grained, **only** the
562
+ private config repo, Contents read and write), so no other token needs Contents on other repositories.
563
+
564
+ Capability surface behind it (all optional, so providers without the concept omit them):
565
+
566
+ - `Source.teamMembers(teamSlug, org?)`, `Source.readFile(path, repo?)` (returns the `version` a later write must present),
567
+ `Source.writeFile(path, content, { message, version?, repo? })` (a changed `version` is refused: a real conflict check).
568
+ - `Notifications.roleMembers(role)`, `addRoleMember(role, memberId)`, `removeRoleMember(role, memberId)`.
569
+ - `Notifications.signIn()` returning `{ authorizeUrl, complete }`: the provider-neutral shape of "prove which account you own".
570
+ - `Notifications.ensureApplication` returns `{ updated, todo? }`; each `todo` is a step only a person can do (a portal setting
571
+ the API ignores) and is reported as a failure until done.
572
+
573
+ Provider credentials for Discord: `discord.holonet` is the **bot token** (Bot → Reset Token), `discord.holonet.oauth` is the
574
+ **OAuth client secret** (OAuth2 → Reset Secret). They are different values; the API can create or read neither.
575
+
576
+ See the [command pages](https://theholocron.dev/holocron/commands/notifications-setup/) for each command's options.
577
+
374
578
  ## Upgrading
375
579
 
376
- - **`holocron upgrade node <to>`** — patches every Node.js version pin
580
+ - **`holocron repo upgrade node <to>`** — patches every Node.js version pin
377
581
  (`engines.node`, `.nvmrc`, `.node-version`, `.github/workflows/*`,
378
582
  `Dockerfile`, `.tool-versions`) to a new major.
379
- - **`holocron upgrade deps`** — bumps every `@theholocron/*` pin in
583
+ - **`holocron repo upgrade deps`** — bumps every `@theholocron/*` pin in
380
584
  `pnpm-workspace.yaml` (`catalog:` and every named `catalogs.*`) to its latest
381
585
  published version, then migrates `holocron.config.ts` from the 7.x preset
382
586
  shape (`const { repo, workflows } = node()` + `repo.requiredChecks` +
383
587
  `workflows:`) to the current composed-preset API (`const preset = node()` +
384
588
  `...preset` + `tasks:` + `extraRequiredChecks:`). Mechanical transforms only —
385
- review the result, then `pnpm install && holocron ci`. `--pins-only` skips
589
+ review the result, then `pnpm install && holocron run ci`. `--pins-only` skips
386
590
  the config migration; `--dry-run` prints without writing.
387
591
 
388
592
  ## Auth — fine-grained tokens
@@ -397,11 +601,12 @@ holocron auth set github.issues ghp_xxx # issue management
397
601
  holocron auth set github.sync ghp_xxx # sync-github workflow templates
398
602
  holocron auth set github.release ghp_xxx # semantic-release
399
603
  holocron auth set github.admin ghp_xxx # setup, secrets, environments
604
+ holocron auth set github.user-sync ghp_xxx # the files that link GitHub accounts to other services' members
400
605
  ```
401
606
 
402
607
  The resolution chain per capability is:
403
608
 
404
- ```
609
+ ```text
405
610
  --token flag → HOLOCRON_<FEATURE>_TOKEN env var → keyring("github.<feature>")
406
611
  ```
407
612
 
@@ -418,16 +623,21 @@ holocron auth list # show all stored providers
418
623
  ## Logging
419
624
 
420
625
  Operational output goes through `@theholocron/observability/logger` — separate
421
- from the user-facing `print` surface. Global flags:
626
+ from the user-facing `print` surface. Local/interactive runs are quiet by
627
+ default: no colorized or raw structured console output, regardless of the
628
+ resolved level (Axiom shipping, when configured, is unaffected). CI is
629
+ untouched — its existing stdout output still appears. Global flags:
422
630
 
423
631
  ```sh
424
- holocron doctor --verbose # log level → debug (full structured output)
425
- holocron doctor --quiet # log level → error (suppress info + warn)
632
+ holocron doctor --log debug # show console output at this level (opt back in)
633
+ holocron doctor --verbose # log level → debug, and shows console output
634
+ holocron doctor --quiet # log level → error, and shows console output
426
635
  holocron doctor --debug # print "Run ID: <uuid>" at command end for Axiom lookup
427
636
  ```
428
637
 
429
- Level resolution (highest priority first): `--verbose` / `--quiet` →
430
- `HOLOCRON_LOG_LEVEL` → `log.level` in `holocron.config` → `"info"`.
638
+ Level resolution (highest priority first): `--log <level>` → `--verbose` /
639
+ `--quiet` → `HOLOCRON_LOG_LEVEL` → `log.level` in `holocron.config` →
640
+ `"info"`.
431
641
 
432
642
  ```ts
433
643
  export default defineConfig({
@@ -439,7 +649,16 @@ Axiom shipping activates when a token (`HOLOCRON_AXIOM_TOKEN` / `AXIOM_TOKEN`
439
649
  → OS keyring `axiom.<org>` → `axiom`) **and** a dataset
440
650
  (`HOLOCRON_AXIOM_DATASET` / `AXIOM_DATASET` → `log.axiom.dataset` in
441
651
  `holocron.config`) both resolve; env vars win. The token is never read from a
442
- config file. See the [logging guide](https://docs.theholocron.dev/holocron/logging/).
652
+ config file.
653
+
654
+ The dataset side of that resolution is **execution-context-aware** (ADR-0012):
655
+ a `global`-context command (`clone`, `new`, `auth set`, `upgrade node`, `plugin
656
+ create`, …) always ships to holocron's own dataset regardless of `$PWD` — it
657
+ isn't "about" any repo, so routing it through whichever repo's `.envrc`
658
+ happens to be active would be wrong. `repo-aware` / `workspace` commands
659
+ (`doctor`, `deploy`, `sync`, …) keep the env-var/config chain above, so a
660
+ per-repo `HOLOCRON_AXIOM_DATASET` — e.g. via a `.envrc` + `direnv` — separates
661
+ each consuming repo's logs. See the [logging guide](https://docs.theholocron.dev/holocron/logging/).
443
662
 
444
663
  ### Telemetry
445
664
 
@@ -468,38 +687,69 @@ export default defineConfig({
468
687
 
469
688
  Each SDK sits behind an interface from `@theholocron/observability` —
470
689
  `ErrorSink` (Sentry) / `AnalyticsSink` (PostHog); the adapters, and the only
471
- `@sentry/node` / `posthog-node` imports, live in that package. `telemetry.ts`
690
+ `@sentry/node` / `posthog-node` imports, live in that package. `observability/telemetry.ts`
472
691
  holds the orchestration + the Holocron-specific credential resolution
473
- (`telemetry/resolve.ts`). See the
692
+ (`observability/resolve-telemetry.ts`). See the
474
693
  [telemetry guide](https://docs.theholocron.dev/holocron/telemetry/).
475
694
 
695
+ ### ESLint bundle options
696
+
697
+ Most repos need nothing here — `@theholocron/eslint-config`'s `library()`
698
+ bundle resolves with zero committed file (Bucket A: `eslint --config
699
+ <shared path>`, no per-repo `eslint.config.ts` needed). A package with a
700
+ genuine, deliberate exception — e.g. Web Crypto globals a Node-targeted
701
+ package still needs, exempted via `library()`'s own `browserPackages`
702
+ option in that package's own `eslint.config.ts` — should also declare the
703
+ same paths in `holocron.config`'s `eslint.browserPackages`:
704
+
705
+ ```ts
706
+ export default defineConfig({
707
+ eslint: {
708
+ browserPackages: ["packages/github-client/src/app-auth"],
709
+ },
710
+ });
711
+ ```
712
+
713
+ This doesn't replace the package's own `eslint.config.ts` (local/CI linting
714
+ still resolves that file directly) — it's the signal Sentinel's centralized
715
+ static-analysis check (holocron#849/#858) reads instead, since that check
716
+ never reads a PR's own committed config files. Without it, Sentinel flags a
717
+ false-positive `eslint-plugin-n` node-builtins finding on exactly the files
718
+ the local override exists to exempt.
719
+
476
720
  ## What's in here
477
721
 
478
- - `src/capabilities/` — the 14 capability interfaces that providers
479
- implement
722
+ - `src/plugin/capabilities.ts` — the 18 capability interfaces that
723
+ providers implement
480
724
  - `src/config.ts` — config schema, `defineConfig`, `resolveConfig`,
481
725
  `CapabilityConfigPackage`
482
726
  - `src/config/load-config.ts` — `loadConfig` — reads `holocron.config.*`
483
727
  (file discovery via [`@theholocron/datapad`](../datapad))
484
728
  - `src/define-config.ts` — `defineConfig` typed pass-through
485
- - `src/logger.ts` — CLI-side `@theholocron/observability/logger` wiring
729
+ - `src/observability/logger.ts` — CLI-side `@theholocron/observability/logger` wiring
486
730
  (`buildCliLogger`, `resolveLogLevel`)
487
731
  - `src/loader.ts` — `PluginLoader` — dynamic-imports plugins, resolves
488
732
  capability config packages, builds the capability registry
489
733
  - `src/cli.ts` — yargs entry, dispatches subcommands. `holocron run` / `ci` are
490
734
  delegated to [`@theholocron/astromech`](../astromech) (`createAstromech`).
491
- `holocron sync` / `holocron setup` write each repo's `.github/workflows/*.yml`
492
- thin callers from `astromech.thinCallers()`; `holocron sync-github` pushes the
735
+ `holocron repo sync` / `holocron repo setup` write each repo's `.github/workflows/*.yml`
736
+ thin callers from `astromech.thinCallers()`; `holocron sync github` pushes the
493
737
  reusable `workflow_call` implementations + composite actions from
494
738
  `astromech.reusableTemplates()` to `theholocron/.github` (a pure sync target —
495
739
  never hand-edit its `.github/workflows/*`).
496
- - `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
497
- `secrets sync`, `publish`, `bump-versions`, `sync-github`, `upgrade node`,
498
- `upgrade deps`, `plugin create`, `auth`
499
- - `src/interactive-menu.ts` — the interactive fallback: `COMMAND_REGISTRY`,
500
- `launchMenu` (Layers 1–2, picks + spawns a fresh `holocron <command>`),
501
- `promptForPositionals` (Layer 3, called inline from each leaf command's
502
- `cli.ts` handler)
740
+ - `src/commands/` — `repo setup`, `repo sync`, `doctor`, `deploy`,
741
+ `monitoring check`, `secrets set`, `secrets sync`, `package publish`,
742
+ `package bump-versions`, `sync github`, `repo upgrade node`,
743
+ `repo upgrade deps`, `new plugin`, `auth`, `notifications setup`,
744
+ `notifications channel add`, `notifications link`,
745
+ `notifications sync-roles`, `doctor notifications`
746
+ - `src/interactive-menu/` — the interactive fallback: `command-registry`
747
+ (`COMMAND_REGISTRY`, each entry's `positionals`/`options`), `launch-menu`
748
+ (Layers 1–2, picks + spawns a fresh `holocron <command>`), `child-argv`
749
+ (builds the spawned argv + secret env), `menu-items`, `run-menu-entries`.
750
+ The generic prompting pieces it calls — `promptForPositionals`,
751
+ `promptForOptions` (Layer 3) — live in `src/ui/`, which has no knowledge
752
+ of commands.
503
753
 
504
754
  ## Status
505
755