@hublo/sentinel 0.1.0-alpha.11 → 0.1.0-alpha.12

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
@@ -23,9 +23,9 @@ A large monorepo accumulates:
23
23
 
24
24
  - **One source of truth for config** — every project just `extends @hublo/sentinel/...`; the actual rules live in one versioned place. Change a rule once, everyone gets it on the next version bump.
25
25
  - **One source of truth for tooling dependencies** — a project depends on `@hublo/sentinel`, not on a scattered pile of eslint / vitest / plugin devDeps. Bump one version and the whole toolchain moves, atomically, tested in isolation first.
26
- - **`--update` = adopt, refresh, _and_ migrate** — the same command generates the stubs the first time (**adopt**), regenerates them after a change like a runner swap (**refresh**), and is run module by module to roll out gradually (**migrate**).
27
- - **Move one module at a time** — installed per module, so you migrate at your pace; a module can adopt sentinel while its neighbour keeps the old setup. No big-bang.
28
- - **Swap tools without touching projects** — change eslint → biome (or benchmark them) in one place; `--update` regenerates the stubs.
26
+ - **`--init` sets a module up** — the one command generates the stubs the first time (**adopt**), regenerates them after a change like a runner swap (**refresh**), and applies the workspace prep the module needs. Run it module by module to roll out gradually. (`--migrate`, for changing an already-initialized setup, is a reserved future verb.)
27
+ - **Move one module at a time** — installed per module, so you adopt at your pace; a module can adopt sentinel while its neighbour keeps the old setup. No big-bang.
28
+ - **Swap tools without touching projects** — change eslint → biome (or benchmark them) in one place; `--init` regenerates the stubs.
29
29
  - **No silent drift** — the guard keeps every project's config converged on the source of truth.
30
30
 
31
31
  ### Before → after
@@ -35,7 +35,7 @@ A large monorepo accumulates:
35
35
  | **Config** | ~116 eslint + ~528 tsconfig files with real, drifting content | thin stubs that `extends` a versioned preset; rules in one place |
36
36
  | **Tooling deps** | ~165 devDeps at the root, shared by all | one `@hublo/sentinel` per module; the toolchain rides its version |
37
37
  | **Upgrade a tool** | big-bang: every project at once, untested in isolation | bump one version, tested in sentinel first, atomic |
38
- | **Swap a tool** | edit config in every project | swap an adapter + `--update`; zero project churn |
38
+ | **Swap a tool** | edit config in every project | swap an adapter + `--init`; zero project churn |
39
39
  | **A rule change** | edit many configs, hope they stay consistent | change once; the drift guard enforces it |
40
40
  | **Migration** | all-or-nothing | module by module, at your pace |
41
41
 
@@ -43,13 +43,13 @@ A large monorepo accumulates:
43
43
 
44
44
  sentinel writes **standard config files** into a project (each just `extends` a sentinel preset) and runs the checks. Your editor and the tools read those **normal files natively**, they never call sentinel at runtime, so nothing is coupled to it or brittle.
45
45
 
46
- > **Shipped today:** only the **TypeScript** tool, so `--update` writes the `tsconfig` stub, and `--run`/`--report`/`--status` work for `--typescript`. The `eslint.config.js` / `--lint` / `--test` snippets below illustrate the end state; those subpaths (`@hublo/sentinel/lint/*`, …) land with their tool ticket.
46
+ > **Shipped today:** only the **TypeScript** tool, so `--init` writes the `tsconfig` stub, and `--run`/`--report`/`--status` work for `--typescript`. The `eslint.config.js` / `--lint` / `--test` snippets below illustrate the end state; those subpaths (`@hublo/sentinel/lint/*`, …) land with their tool ticket.
47
47
 
48
48
  **Step 1 — put a module on sentinel** (once per module, by a dev; the files are committed):
49
49
 
50
50
  ```bash
51
51
  pnpm add -D @hublo/sentinel
52
- sentinel --update # run from the app dir; writes eslint.config.js, tsconfig, ... then you commit them
52
+ sentinel --init # run from the app dir; writes eslint.config.js, tsconfig, ... then you commit them
53
53
  ```
54
54
 
55
55
  Those files are tiny, they just point at a sentinel preset. What gets committed:
@@ -95,7 +95,7 @@ And the app's `package.json` scripts route every check through the one CLI (run
95
95
  **Step 3 — evolve the toolchain, in one central place:**
96
96
 
97
97
  - a **rule change** → bump the `@hublo/sentinel` version; the stubs already point at it, so there is **nothing to regenerate**;
98
- - a **structural change** (new tool, new preset, runner swap) → run `sentinel --update` once to refresh the stubs (sentinel tells you when this is needed).
98
+ - a **structural change** (new tool, new preset, runner swap) → run `sentinel --init` once to refresh the stubs (sentinel tells you when this is needed).
99
99
 
100
100
  **Step 4 — stay converged:** a drift guard in CI flags any module whose config quietly diverged from the shared source.
101
101
 
@@ -131,7 +131,7 @@ Every check is described by three layers:
131
131
  A run is `target × runner × flavour`, e.g. `sentinel --run --lint --runner=eslint` from the `host-admin` dir.
132
132
 
133
133
  - `--runner` is an **optional override on a central default**. `sentinel --lint` uses the configured default runner, so swapping a tool globally is a one-place change; `--runner=biome` overrides for a single run (great for benchmarking eslint vs biome vs oxlint, and for gradual migration).
134
- - The **flavour is detected** from the module's dependencies (deterministic: a framework dep → its flavour, else `node`), and **`--flavour` overrides it**. Detection can be wrong where deps are hoisted at the repo root (it returns `node`), so pass `--flavour` for `--update` (that is where the preset is chosen). `--run`/`--report` don't depend on it, they run the tool on the committed config.
134
+ - The **flavour is detected** from the module's dependencies (deterministic: a framework dep → its flavour, else `node`), and **`--flavour` overrides it**. Detection can be wrong where deps are hoisted at the repo root (it returns `node`), so pass `--flavour` for `--init` (that is where the preset is chosen). `--run`/`--report` don't depend on it, they run the tool on the committed config.
135
135
 
136
136
  ### Adapters & the engine (ports & adapters)
137
137
 
@@ -144,12 +144,12 @@ A run is `target × runner × flavour`, e.g. `sentinel --run --lint --runner=esl
144
144
  `sentinel` does not reimplement tools. The contract:
145
145
 
146
146
  - **`appliesTo(flavour)`** — which flavours this adapter handles (resolution filters on it, so a React-only adapter is never picked for Nest)
147
- - **`plan(flavour)`** — PURE: returns a declarative `UpdatePlan` of file operations (used by `--update`). The adapter never touches the disk; the engine applies the plan.
147
+ - **`plan(flavour)`** — PURE: returns a declarative `UpdatePlan` of file operations (used by `--init`). The adapter never touches the disk; the engine applies the plan.
148
148
  - **`run(ctx)`** — invoke the tool's bin against the project (used by `--run`)
149
149
  - **`inspect(flavour)`** — the adapter's resolved base config (used by `--inspect`)
150
150
  - **`report(ctx)`** — metrics (used by `--report`)
151
151
 
152
- **`--update` is a declarative plan, not file-writing inside the adapter.** The adapter describes intent as operations; the engine executes them:
152
+ **`--init` is a declarative plan, not file-writing inside the adapter.** The adapter describes intent as operations; the engine executes them:
153
153
 
154
154
  - `write { path, contents }` — a file the adapter fully owns (the thin stub)
155
155
  - `merge-json { path, value }` — pin the keys sentinel owns while **preserving** a project's own (this is how a tsconfig's `paths`/`include` survive)
@@ -169,7 +169,7 @@ flowchart LR
169
169
  D --> R["registry.resolve<br/>(target, flavour, runner)"]
170
170
  R --> A["adapter<br/>eslint / tsc / vitest / ..."]
171
171
  A -->|"--run"| Run["tool binary on the project"]
172
- A -->|"--update"| Upd["declarative plan → engine writes"]
172
+ A -->|"--init"| Upd["declarative plan → engine writes"]
173
173
  A -->|"--inspect"| Ins["resolved config"]
174
174
  A -->|"--report"| Rep["metrics"]
175
175
  ```
@@ -179,7 +179,7 @@ flowchart LR
179
179
  ```mermaid
180
180
  flowchart TB
181
181
  S["@hublo/sentinel<br/>rules = one source of truth"]
182
- S -->|"--update generates"| Stub["thin stub per module<br/>(extends sentinel)"]
182
+ S -->|"--init generates"| Stub["thin stub per module<br/>(extends sentinel)"]
183
183
  Stub --> IDE["editor: live lint / type / format"]
184
184
  Stub --> NX["nx: target inference"]
185
185
  S -->|"--run injects config"| CI["CLI / CI: run tool on target"]
@@ -191,7 +191,7 @@ flowchart TB
191
191
  The rules live in `sentinel`. Each module keeps a **thin, generated stub** per tool, a few lines that `extends`/re-export the sentinel preset:
192
192
 
193
193
  - **Rules in sentinel** — one source of truth, versioned.
194
- - **Thin stubs per module** — the stub is what keeps the **editor working** (VS Code discovers config by file, real-time lint/type/format stay live) and what **nx** uses to infer targets. Stubs are **generated by `sentinel --update`**, never hand-written; swapping a runner regenerates them.
194
+ - **Thin stubs per module** — the stub is what keeps the **editor working** (VS Code discovers config by file, real-time lint/type/format stay live) and what **nx** uses to infer targets. Stubs are **generated by `sentinel --init`**, never hand-written; swapping a runner regenerates them.
195
195
  - **Drift guard** — sentinel validates that each stub is _only_ the sanctioned `extends`, with nothing added or overridden. Unsanctioned drift is flagged in CI; a genuine exception must be **declared in an allowlist** (visible, reviewed), never silent.
196
196
  - **Per-module install** — `@hublo/sentinel` is added per module, so adoption is **gradual** (migrate lot by lot; a module can adopt sentinel while its neighbour still uses the old config). Root configs are removed only once the **last** module has migrated.
197
197
  - **Runner binaries** (`eslint`, `typescript`, `vite`, `vitest`, …) are **peer dependencies** so they install once and stay resolvable by the editor + nx, while sentinel still dictates their versions.
@@ -264,7 +264,7 @@ VERBS --run execute the target's tool
264
264
  --inspect show the resolved configuration (incl. deferred rules)
265
265
  --report metrics and health
266
266
  --status adoption + conformity (coverage + drift), read from configs
267
- --update generate/apply the config stubs (writes; one module only)
267
+ --init generate/apply the config stubs (writes; one module only)
268
268
 
269
269
  TYPES --lint --format --typescript --build --test
270
270
  --static-analysis --runtime-analysis --arch
@@ -278,7 +278,7 @@ OPTIONS --module <name> from the root: scope to one module
278
278
  --runner <tool> override the default runner
279
279
  --ci from the root: affected only; non-zero exit on failure
280
280
  --fix auto-fix where applicable
281
- --dry-run preview a --update without writing
281
+ --dry-run preview a --init without writing
282
282
  --json machine-readable output (report / inspect / status / --dry-run)
283
283
 
284
284
  EXAMPLES sentinel --run --typescript # in a module → that module
@@ -286,11 +286,11 @@ EXAMPLES sentinel --run --typescript # in a module →
286
286
  sentinel --report # from root → all types, all modules
287
287
  sentinel --status --typescript # from root → adoption coverage
288
288
  sentinel --status --ci # from root → fail CI on drift
289
- sentinel --update --typescript --flavour react # write stubs for the current module
289
+ sentinel --init --typescript --flavour react # write stubs for the current module
290
290
  ```
291
291
 
292
292
  `--run`/`--inspect`/`--report`/`--status` share one context rule (developer from a
293
- module, or from the root for a name / affected / all); `--update` writes, so it targets
293
+ module, or from the root for a name / affected / all); `--init` writes, so it targets
294
294
  one module only (adopting every module at once is refused, adopt gradually).
295
295
 
296
296
  **`--status` — adoption + conformity.** A cheap, workspace-wide read (no tool run, no
@@ -320,7 +320,7 @@ src/
320
320
  settings.ts # tunables (workspace-root marker, ...)
321
321
  registry.ts # register + flavour-aware resolve
322
322
  dispatch.ts # verb → adapter method
323
- apply-plan.ts # the engine's filesystem port (applies --update operations)
323
+ apply-plan.ts # the engine's filesystem port (applies --init operations)
324
324
  roles/<config-role>/ # lint, format, typescript, build, test
325
325
  adapters/<runner>/ # one adapter per tool (implements the contract)
326
326
  flavours/<stack>/ # config presets per stack (react, nest, svelte, ...)
@@ -361,7 +361,7 @@ nx is a **task runner**: it just runs the target's script. So `project.json` bar
361
361
  This scaffold is the foundation; each tool is added one at a time on top of it:
362
362
 
363
363
  1. **Foundation** — repo, exports, CLI skeleton, adapter contract, changesets, CI/release. **✅ shipped**
364
- 2. **TypeScript** (`--typescript`) — runner `tsc` (later `tsgo`): presets react/nest/node, `--run`/`--inspect`/`--report`/`--update`, the composable grid, phased (non-breaking) strictness. **✅ shipped in `0.1.0-alpha.x`**
364
+ 2. **TypeScript** (`--typescript`) — runner `tsc` (later `tsgo`): presets react/nest/node, `--run`/`--inspect`/`--report`/`--init`, the composable grid, phased (non-breaking) strictness. **✅ shipped in `0.1.0-alpha.x`**
365
365
  3. **Lint** (`--lint`) — benchmark `eslint` vs `biome` vs `oxlint`.
366
366
  4. **Build** (`--build`) — `vite`.
367
367
  5. **Test** (`--test`) — `vitest`, plus a11y / w3c setups.
@@ -5,12 +5,13 @@ import {
5
5
  dispatch,
6
6
  palette,
7
7
  readNxProjectName,
8
+ readOwnPackage,
8
9
  readOwnVersion,
9
10
  readProjectPackageJson,
10
11
  registerAdapters,
11
12
  resolve,
12
13
  resolveBin
13
- } from "../chunk-6N2CLPWE.js";
14
+ } from "../chunk-6ZTM3PCI.js";
14
15
 
15
16
  // bin/sentinel.ts
16
17
  import { program } from "commander";
@@ -106,7 +107,7 @@ function resolveContext(cwd2, opts2) {
106
107
  }
107
108
 
108
109
  // src/core/domain.ts
109
- var VERBS = ["run", "inspect", "update", "report", "status"];
110
+ var VERBS = ["run", "inspect", "init", "migrate", "report", "status"];
110
111
  var TARGETS = [
111
112
  "lint",
112
113
  "format",
@@ -331,6 +332,73 @@ function renderStatusSummary(items, p) {
331
332
  return ` ${p.strong("coverage:")} ${c.adopted}/${c.total} adopted ${p.dim("\xB7")} ${c.conformant}/${c.adopted} conformant${drift}`;
332
333
  }
333
334
 
335
+ // src/core/workspace-prep.ts
336
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
337
+ import { dirname, join as join3 } from "path";
338
+ var OVERRIDE_KEY = "i18next>typescript";
339
+ var NATIVE_TS_ALIAS = "@typescript/native";
340
+ var WORKSPACE_YAML = "pnpm-workspace.yaml";
341
+ var RELEASE_AGE_KEY = "minimumReleaseAgeExclude";
342
+ var LIST_ITEM = /^(\s*)-\s+(.*?)\s*$/;
343
+ function findWorkspaceRoot(startDir) {
344
+ let dir = startDir;
345
+ for (; ; ) {
346
+ if (existsSync2(join3(dir, WORKSPACE_ROOT_MARKER))) return dir;
347
+ const parent = dirname(dir);
348
+ if (parent === dir) return void 0;
349
+ dir = parent;
350
+ }
351
+ }
352
+ function declaredNativeTs(pkg) {
353
+ const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS];
354
+ if (!spec) return void 0;
355
+ const version = spec.slice(spec.lastIndexOf("@") + 1).replace(/^[\^~>=<\s]+/, "");
356
+ return version || void 0;
357
+ }
358
+ function ensureI18nextSingleton(root, dryRun) {
359
+ const pkgPath = join3(root, "package.json");
360
+ if (!existsSync2(pkgPath)) return void 0;
361
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
362
+ const want = declaredNativeTs(pkg);
363
+ if (!want) return void 0;
364
+ const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY];
365
+ if (have === want) return void 0;
366
+ if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`;
367
+ pkg.pnpm ??= {};
368
+ pkg.pnpm.overrides ??= {};
369
+ pkg.pnpm.overrides[OVERRIDE_KEY] = want;
370
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
371
+ return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`;
372
+ }
373
+ function ensureReleaseAgeAllowList(root, dryRun) {
374
+ const yamlPath = join3(root, WORKSPACE_YAML);
375
+ if (!existsSync2(yamlPath)) return void 0;
376
+ const own = readOwnPackage().name;
377
+ const lines = readFileSync2(yamlPath, "utf8").split("\n");
378
+ const keyIdx = lines.findIndex((line) => line.replace(/\s+$/, "") === `${RELEASE_AGE_KEY}:`);
379
+ if (keyIdx === -1) return void 0;
380
+ let lastItemIdx = keyIdx;
381
+ let indent = " ";
382
+ for (let i = keyIdx + 1; i < lines.length; i++) {
383
+ const match = lines[i]?.match(LIST_ITEM);
384
+ if (!match) break;
385
+ indent = match[1] ?? indent;
386
+ lastItemIdx = i;
387
+ if ((match[2] ?? "").replace(/^['"]|['"]$/g, "") === own) return void 0;
388
+ }
389
+ if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`;
390
+ lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`);
391
+ writeFileSync(yamlPath, lines.join("\n"));
392
+ return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`;
393
+ }
394
+ function ensureWorkspacePrep(opts2) {
395
+ const dryRun = Boolean(opts2.dryRun);
396
+ return [
397
+ ensureI18nextSingleton(opts2.root, dryRun),
398
+ ensureReleaseAgeAllowList(opts2.root, dryRun)
399
+ ].filter((message) => message !== void 0);
400
+ }
401
+
334
402
  // src/shared/node-version.ts
335
403
  var MIN_NODE = "20.12.0";
336
404
  function parts(version) {
@@ -360,16 +428,16 @@ program.name("sentinel").description("One CLI that guards code health: presets,
360
428
  "before",
361
429
  [
362
430
  "A check composes: verb + type + location.",
363
- " verb what to do: --run --inspect --report --status --update",
431
+ " verb what to do: --run --inspect --report --status --init (--migrate: planned)",
364
432
  " type which check: --lint --typescript ... (omit = all types; or --all)",
365
433
  " where run from a MODULE dir \u2192 that module; from the ROOT \u2192 --module <name>,",
366
- " --ci (affected), or all modules. --update targets one module only.",
434
+ " --ci (affected), or all modules. --init targets one module only.",
367
435
  ""
368
436
  ].join("\n")
369
- ).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--update", "generate/apply the config stubs").option("--report", "metrics and health report").option("--status", "adoption + conformity across modules (coverage + drift)").option("--lint", "linting").option("--format", "formatting").option("--typescript", "type checking").option("--build", "build").option("--test", "tests").option("--static-analysis", "cycles, complexity, duplication, centrality").option("--runtime-analysis", "bundle, Lighthouse, web vitals").option("--arch", "architecture boundaries").option("--all", "every target").option(
437
+ ).option("--run", "execute the target tool").option("--inspect", "show the resolved configuration").option("--init", "set up a module: write its config stubs + the workspace prep it needs").option("--migrate", "planned: change an already-initialized setup (not available yet)").option("--report", "metrics and health report").option("--status", "adoption + conformity across modules (coverage + drift)").option("--lint", "linting").option("--format", "formatting").option("--typescript", "type checking").option("--build", "build").option("--test", "tests").option("--static-analysis", "cycles, complexity, duplication, centrality").option("--runtime-analysis", "bundle, Lighthouse, web vitals").option("--arch", "architecture boundaries").option("--all", "every target").option(
370
438
  "--module <name>",
371
439
  "from the workspace root: scope to one module (omit = all; inside a module dir, drop this)"
372
- ).option("--flavour <name>", `override the detected stack preset (${FLAVOURS.join(", ")})`).option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--update)").option("--json", "machine-readable JSON output (report/inspect/--dry-run)").option(
440
+ ).option("--flavour <name>", `override the detected stack preset (${FLAVOURS.join(", ")})`).option("--runner <tool>", "override the default runner (e.g. eslint, biome)").option("--ci", "CI mode: from the root, only the affected modules; non-zero exit on failure").option("--fix", "auto-fix where applicable").option("--dry-run", "preview the changes without writing (--init)").option("--json", "machine-readable JSON output (report/inspect/--dry-run)").option(
373
441
  "--max-diagnostics <n>",
374
442
  "cap the diagnostics embedded per module in --report (0 = no cap)",
375
443
  "100"
@@ -382,7 +450,7 @@ program.name("sentinel").description("One CLI that guards code health: presets,
382
450
  " sentinel --report --typescript --module bff-admin # from root \u2192 one module",
383
451
  " sentinel --report # from root \u2192 all types, all modules",
384
452
  " sentinel --report --ci # from root \u2192 affected only",
385
- " sentinel --update --typescript --flavour react # write stubs for the current module"
453
+ " sentinel --init --typescript --flavour react # set up the current module + workspace"
386
454
  ].join("\n")
387
455
  ).addHelpText("after", () => {
388
456
  const available = availableTargets();
@@ -436,8 +504,8 @@ if (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {
436
504
  `--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`
437
505
  );
438
506
  }
439
- if (opts.dryRun && verb !== "update") {
440
- program.error("--dry-run only applies to --update (the read verbs never write).");
507
+ if (opts.dryRun && verb !== "init") {
508
+ program.error("--dry-run only applies to --init (the read verbs never write).");
441
509
  }
442
510
  async function runVerb() {
443
511
  const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) });
@@ -491,12 +559,12 @@ async function runVerb() {
491
559
  }
492
560
  return verb === "run" || opts.ci ? worstCode : 0;
493
561
  }
494
- async function runUpdate() {
562
+ async function runInit() {
495
563
  const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: false });
496
564
  const [module] = modules;
497
565
  if (scope === "all" || scope === "affected" || !module) {
498
566
  program.error(
499
- "--update writes files: target one module (run from its directory, or pass --module). Adopting every module at once is intentionally not allowed \u2014 adopt gradually."
567
+ "--init writes files: target one module (run from its directory, or pass --module). Adopting every module at once is intentionally not allowed \u2014 adopt gradually."
500
568
  );
501
569
  }
502
570
  const available = availableTargets();
@@ -529,10 +597,27 @@ sentinel (${type}): ${asMessage(err)}
529
597
  worst = Math.max(worst, 1);
530
598
  }
531
599
  }
600
+ if (worst === 0) {
601
+ const root = findWorkspaceRoot(module.root);
602
+ if (root) {
603
+ const changes = ensureWorkspacePrep({ root, dryRun: Boolean(opts.dryRun) });
604
+ const prefix = opts.dryRun ? " dry run: " : " ";
605
+ for (const change of changes) process.stderr.write(`${prefix}${change}
606
+ `);
607
+ if (changes.length > 0 && !opts.dryRun) {
608
+ process.stderr.write(
609
+ palette(process.stderr).dim(" run `pnpm install` to apply the workspace changes\n")
610
+ );
611
+ }
612
+ }
613
+ }
532
614
  return worst;
533
615
  }
534
616
  async function main() {
535
- const runSelectedVerb = verb === "update" ? runUpdate : runVerb;
617
+ if (verb === "migrate") {
618
+ program.error("--migrate is planned and not available yet; use --init to set a module up.");
619
+ }
620
+ const runSelectedVerb = verb === "init" ? runInit : runVerb;
536
621
  const exitCode = await runSelectedVerb();
537
622
  process.exit(exitCode);
538
623
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../bin/sentinel.ts","../../src/core/context.ts","../../src/core/discover-modules.ts","../../src/core/settings.ts","../../src/core/domain.ts","../../src/core/orchestrate.ts","../../src/core/render.ts","../../src/shared/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * sentinel CLI: a command composes verb × type × location.\n *\n * Exactly one verb (--run / --inspect / --report / --status / --update); a type (--typescript …)\n * or ALL when none is named; and a location resolved by `resolveContext` (a module\n * directory, or the workspace root with --module / --ci / all). The CLI is generic: it\n * resolves each target's adapter (honouring --runner) and dispatches; tools arrive as\n * adapters in later tickets. The flavour is detected from a module's dependencies\n * (deterministic, `node` default) and can be overridden with --flavour (recommended\n * for --update).\n */\nimport { program } from 'commander'\n\nimport { registerAdapters } from '../src/adapters.js'\nimport { resolveContext } from '../src/core/context.js'\nimport { FLAVOURS, TARGETS, VERBS, type Flavour, type Target } from '../src/core/domain.js'\nimport { dispatch } from '../src/core/dispatch.js'\nimport { analyse, generateSummaries } from '../src/core/orchestrate.js'\nimport { availableTargets } from '../src/core/registry.js'\nimport {\n renderStatusRow,\n renderStatusSummary,\n renderSummary,\n statusCoverage,\n} from '../src/core/render.js'\nimport { palette } from '../src/shared/color.js'\nimport { checkNodeVersion } from '../src/shared/node-version.js'\nimport { readOwnVersion } from '../src/shared/package-json.js'\n\n// Fail fast with a clear message on an unsupported Node (sentinel's coloured output needs\n// `styleText`'s stream option, Node 22.13+), instead of a cryptic crash deep in the CLI.\nconst nodeCheck = checkNodeVersion(process.versions.node)\nif (!nodeCheck.ok) {\n process.stderr.write(`${nodeCheck.message}\\n`)\n process.exit(1)\n}\n\n// Register every tool adapter up front, BEFORE parsing, so `--help` can reflect what is\n// actually wired (availableTargets) rather than a hardcoded list that pretends all targets\n// are ready. Registration has no dependency on the parsed options.\nregisterAdapters()\n\nprogram\n .name('sentinel')\n .description('One CLI that guards code health: presets, analysis, and arch checks.')\n .version(readOwnVersion())\n .configureHelp({ sortOptions: false })\n // On any parse error (unknown flag, missing value): suggest the closest flag and\n // point at --help, so a typo like `--lnt` gets \"did you mean --lint?\".\n .showSuggestionAfterError(true)\n .showHelpAfterError('(run \"sentinel --help\" for usage)')\n .addHelpText(\n 'before',\n [\n 'A check composes: verb + type + location.',\n ' verb what to do: --run --inspect --report --status --update',\n ' type which check: --lint --typescript ... (omit = all types; or --all)',\n ' where run from a MODULE dir → that module; from the ROOT → --module <name>,',\n ' --ci (affected), or all modules. --update targets one module only.',\n '',\n ].join('\\n'),\n )\n // verbs (pick one)\n .option('--run', 'execute the target tool')\n .option('--inspect', 'show the resolved configuration')\n .option('--update', 'generate/apply the config stubs')\n .option('--report', 'metrics and health report')\n .option('--status', 'adoption + conformity across modules (coverage + drift)')\n // targets (pick one, or --all)\n .option('--lint', 'linting')\n .option('--format', 'formatting')\n .option('--typescript', 'type checking')\n .option('--build', 'build')\n .option('--test', 'tests')\n .option('--static-analysis', 'cycles, complexity, duplication, centrality')\n .option('--runtime-analysis', 'bundle, Lighthouse, web vitals')\n .option('--arch', 'architecture boundaries')\n .option('--all', 'every target')\n // modifiers\n .option(\n '--module <name>',\n 'from the workspace root: scope to one module (omit = all; inside a module dir, drop this)',\n )\n .option('--flavour <name>', `override the detected stack preset (${FLAVOURS.join(', ')})`)\n .option('--runner <tool>', 'override the default runner (e.g. eslint, biome)')\n .option('--ci', 'CI mode: from the root, only the affected modules; non-zero exit on failure')\n .option('--fix', 'auto-fix where applicable')\n .option('--dry-run', 'preview the changes without writing (--update)')\n .option('--json', 'machine-readable JSON output (report/inspect/--dry-run)')\n .option(\n '--max-diagnostics <n>',\n 'cap the diagnostics embedded per module in --report (0 = no cap)',\n '100',\n )\n .addHelpText(\n 'after',\n [\n '',\n 'Examples:',\n ' sentinel --run --typescript # in a module → that module',\n ' sentinel --report --typescript --module bff-admin # from root → one module',\n ' sentinel --report # from root → all types, all modules',\n ' sentinel --report --ci # from root → affected only',\n ' sentinel --update --typescript --flavour react # write stubs for the current module',\n ].join('\\n'),\n )\n // Availability, derived from the registry so it never lies: only wired targets are\n // \"available now\"; the rest are honestly marked as planned.\n .addHelpText('after', () => {\n const available = availableTargets()\n const planned = TARGETS.filter((t) => !available.includes(t))\n return [\n '',\n `Available now: ${available.length ? available.map((t) => `--${t}`).join(', ') : '(none yet)'}`,\n planned.length\n ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(', ')}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .parse()\n\nconst opts = program.opts()\n\n// commander camelCases hyphenated flags (--static-analysis -> staticAnalysis).\nfunction toCamel(flag: string): string {\n return flag.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n}\n\n/** Exactly one of `keys` must be flagged; return it, else fail with guidance. */\nfunction pickOne<T extends string>(kind: string, keys: readonly T[]): T {\n const chosen = keys.filter((k) => opts[toCamel(k)])\n if (chosen.length !== 1) {\n const supported = keys.map((k) => `--${k}`).join(', ')\n program.error(\n chosen.length === 0\n ? `Missing a ${kind}. Supported: ${supported}.`\n : `Pick exactly one ${kind}: got ${chosen.map((k) => `--${k}`).join(', ')}.`,\n )\n }\n return chosen[0] as T\n}\n\n/**\n * The explicit `--flavour`, VALIDATED, or undefined when not given. When undefined,\n * the flavour is DETECTED from the module's dependencies (see `detect-framework.ts`);\n * this override is recommended for `--update`, where hoisted deps can make detection\n * fall back to `node`. An unknown value is a hard error (typo caught), not a silent guess.\n */\nfunction parseFlavour(value: unknown): Flavour | undefined {\n if (value === undefined) return undefined\n if (typeof value !== 'string' || !FLAVOURS.includes(value as Flavour)) {\n return program.error(\n `sentinel: unknown --flavour ${JSON.stringify(value)}. Supported: ${FLAVOURS.join(', ')}.`,\n )\n }\n return value as Flavour\n}\n\nfunction asMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nconst verb = pickOne('verb', VERBS)\nconst cwd = process.cwd()\nconst flavour = parseFlavour(opts.flavour)\n\n// Targets (the \"type\" axis): a named one, or ALL when none is given. Uniform for every\n// verb — run / inspect / report / update. `resolveContext` owns the \"location\" axis.\nconst namedTargets = TARGETS.filter((t) => opts[toCamel(t)])\nif (opts.all && namedTargets.length > 0) {\n program.error(\n `--all runs every target; drop the specific one(s): ${namedTargets.map((t) => `--${t}`).join(', ')}.`,\n )\n}\nconst targets: Target[] = opts.all || namedTargets.length === 0 ? [...TARGETS] : namedTargets\n// Whether the user named the target(s) themselves. When true, a target with no adapter is\n// a usage error (they asked for something sentinel cannot do). When false (`--all` / none),\n// an unsupported target is just an expected skip, shown but not failed.\nconst targetsExplicit = !(opts.all || namedTargets.length === 0)\n\n// --max-diagnostics: how many structured diagnostics --report embeds per module (0 = all).\n// `Number` (not parseInt) so a fractional value like `3.9` is rejected, not silently floored.\nconst maxDiagnostics = Number(opts.maxDiagnostics)\nif (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {\n program.error(\n `--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`,\n )\n}\n\n// --dry-run only previews a write.\nif (opts.dryRun && verb !== 'update') {\n program.error('--dry-run only applies to --update (the read verbs never write).')\n}\n\n/**\n * run / inspect / report: resolve the module context (cwd module, `--module`, all, or\n * `--ci` affected), then execute verb × targets across it and print.\n */\nasync function runVerb(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) })\n const out = palette(process.stdout)\n const err = palette(process.stderr)\n\n // `--run` streams the tool's own output to stdout, so a JSON envelope would be mixed in;\n // `--json` is only honoured for report/inspect. Say so instead of silently ignoring it.\n if (opts.json && verb === 'run') {\n process.stderr.write(\n err.warn(\n 'note: --json is ignored for --run (it streams the tool output); use --report --json for a machine envelope.',\n ) + '\\n',\n )\n }\n\n const started = Date.now()\n const { results, worstCode } = await analyse({\n verb: verb as 'run' | 'report' | 'inspect' | 'status',\n targets,\n modules,\n runner: opts.runner,\n flavour,\n targetsExplicit,\n maxDiagnostics,\n ci: Boolean(opts.ci),\n fix: Boolean(opts.fix),\n onProgress: (done, total, name) =>\n process.stderr.write(err.dim(` [${done}/${total}] ${name}\\n`)),\n })\n\n // Shape each outcome into a summary row, then print it colored. `run` rows carry no\n // metrics (bare head line); `status` has its own 3-state rows + a coverage footer;\n // `--json` bypasses the human rendering entirely.\n const summary = generateSummaries(results, targets)\n if (opts.json && verb !== 'run') {\n const payload =\n verb === 'status' ? { ...summary, coverage: statusCoverage(summary.results) } : summary\n process.stdout.write(JSON.stringify(payload, null, 2) + '\\n')\n } else if (verb === 'status') {\n for (const item of summary.results) process.stdout.write(renderStatusRow(item, out) + '\\n')\n process.stdout.write(renderStatusSummary(summary.results, out) + '\\n')\n } else {\n for (const item of summary.results) {\n process.stdout.write(renderSummary(item, out) + '\\n')\n }\n }\n process.stderr.write(\n err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms\\n`),\n )\n\n // An explicitly requested target with no adapter is a usage error: say so clearly and\n // exit non-zero, so a human or agent never mistakes \"nothing ran\" for a success.\n if (targetsExplicit && summary.skipped.length > 0) {\n const avail = availableTargets()\n process.stderr.write(\n err.fail(\n `sentinel: target(s) not available yet: ${summary.skipped.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${avail.length ? avail.map((t) => `--${t}`).join(', ') : '(none yet)'}.`,\n ) + '\\n',\n )\n return 1\n }\n\n // `run` always reports failures via its exit code; report/inspect only under --ci.\n return verb === 'run' || opts.ci ? worstCode : 0\n}\n\n/**\n * update: a WRITE. Same context rule, but it must resolve to exactly ONE module (the\n * current one, or `--module`). Adopting every module at once would be a big-bang, so\n * the implicit \"all\" is deliberately refused; adopt gradually.\n */\nasync function runUpdate(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: false })\n const [module] = modules\n if (scope === 'all' || scope === 'affected' || !module) {\n program.error(\n '--update writes files: target one module (run from its directory, or pass --module). ' +\n 'Adopting every module at once is intentionally not allowed — adopt gradually.',\n )\n }\n // Same policy as the read verbs: a target the user NAMED but that has no adapter is a\n // usage error; targets swept in by --all / no target are just skipped, never flooded as\n // \"No adapter…\" errors that would fail an otherwise-successful update.\n const available = availableTargets()\n if (targetsExplicit) {\n const unwired = targets.filter((type) => !available.includes(type))\n if (unwired.length > 0) {\n program.error(\n `target(s) not available yet: ${unwired.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${available.map((t) => `--${t}`).join(', ') || '(none yet)'}.`,\n )\n }\n }\n const toRun = targets.filter((type) => available.includes(type))\n\n let worst = 0\n for (const type of toRun) {\n try {\n const code = await dispatch({\n verb,\n target: type,\n runner: opts.runner,\n cwd: module.root,\n flavour,\n dryRun: Boolean(opts.dryRun),\n json: Boolean(opts.json),\n })\n worst = Math.max(worst, code)\n } catch (err) {\n // A genuine adapter failure (not an unwired target, those are filtered above).\n process.stderr.write(`\\nsentinel (${type}): ${asMessage(err)}\\n`)\n worst = Math.max(worst, 1)\n }\n }\n return worst\n}\n\n/**\n * Entry point. `--update` writes the config stubs; the read verbs\n * (run / inspect / report / status) resolve a location and execute.\n * Exit with the command's own status code, or 1 on an unexpected error.\n */\nasync function main(): Promise<void> {\n const runSelectedVerb = verb === 'update' ? runUpdate : runVerb\n const exitCode = await runSelectedVerb()\n process.exit(exitCode)\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`\\nsentinel: ${asMessage(error)}\\n`)\n process.exit(1)\n})\n","/**\n * Context resolution: the composable \"location\" axis of a command.\n *\n * One rule for every read verb (run / inspect / report), so a developer works the way\n * they already work with nx, from their module or from the root:\n * - in a MODULE dir (has package.json/project.json, and is NOT the workspace root):\n * the context is THAT module; `--module` is redundant there and is rejected.\n * - at the workspace ROOT: `--module X` scopes to X, `--ci` scopes to the affected\n * set, and otherwise it is every module (the whole-repo status).\n * Update (a write) reuses this but forbids the implicit \"all\" (see the CLI).\n */\nimport { existsSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { readNxProjectName } from '../shared/package-json.js'\nimport { discoverModules, type ModuleRef } from './discover-modules.js'\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\n/** A directory is a module if it carries one of these (and is not the root). */\nconst MODULE_MARKERS = ['package.json', 'project.json'] as const\n\n/** How the context was resolved, for messages and the \"all\"-guard on update. */\nexport type ContextScope = 'cwd-module' | 'named-module' | 'affected' | 'all'\n\nexport interface ResolvedContext {\n modules: ModuleRef[]\n scope: ContextScope\n}\n\nfunction isModuleDir(cwd: string): boolean {\n return MODULE_MARKERS.some((marker) => existsSync(join(cwd, marker)))\n}\n\n/**\n * Resolve which modules a command targets from where it runs + its flags. Throws with\n * an actionable message when the combination is contradictory (e.g. `--module` from\n * inside a module) or the directory is neither a module nor the workspace root.\n */\nexport function resolveContext(\n cwd: string,\n opts: { module?: string; ci?: boolean },\n): ResolvedContext {\n const atRoot = existsSync(join(cwd, WORKSPACE_ROOT_MARKER))\n\n // In a module directory: the module IS the current one.\n if (!atRoot && isModuleDir(cwd)) {\n if (opts.module) {\n throw new Error(\n 'You are in a module directory: drop --module (the context is the current module).',\n )\n }\n if (opts.ci) {\n throw new Error('--ci selects the affected set from the workspace root; run it there.')\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return { modules: [{ name, root: cwd }], scope: 'cwd-module' }\n }\n\n // At the workspace root: --module (one), --ci (affected), or every module.\n if (atRoot) {\n if (opts.module) {\n const found = discoverModules(cwd).find((module) => module.name === opts.module)\n if (!found) throw new Error(`module \"${opts.module}\" not found in the workspace.`)\n return { modules: [found], scope: 'named-module' }\n }\n if (opts.ci) return { modules: discoverModules(cwd, { affected: true }), scope: 'affected' }\n return { modules: discoverModules(cwd), scope: 'all' }\n }\n\n throw new Error(\n `Run sentinel from a module directory or the workspace root ` +\n `(found neither ${MODULE_MARKERS.join('/')} nor ${WORKSPACE_ROOT_MARKER} here).`,\n )\n}\n","/**\n * Module discovery via nx. `--report`/`--inspect` without a `--module` analyse\n * every project; `--ci` narrows to the affected ones. We ask nx (its cached graph)\n * for the authoritative names + roots in a couple of calls, not one per project,\n * so it stays fast on the real monorepo.\n */\nimport { execFileSync } from 'node:child_process'\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { resolveBin } from '../shared/resolve-bin.js'\n\n/** A discovered module: its nx project name and absolute root. */\nexport interface ModuleRef {\n name: string\n root: string\n}\n\nfunction runNx(cwd: string, args: string[]): string {\n const nx = resolveBin(cwd, 'nx') ?? 'nx'\n try {\n // Disable the daemon for deterministic, CI-friendly one-shot invocations.\n return execFileSync(nx, args, {\n cwd,\n encoding: 'utf8',\n env: { ...process.env, NX_DAEMON: 'false' },\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `sentinel: could not run nx (${message}). Is nx installed in this workspace, and are you at its root?`,\n { cause: error },\n )\n }\n}\n\n/** Every module (name -> absolute root) from the nx project graph, in one call. */\nfunction readGraph(cwd: string): ModuleRef[] {\n const dir = mkdtempSync(join(tmpdir(), 'sentinel-nx-'))\n const file = join(dir, 'graph.json')\n try {\n runNx(cwd, ['graph', '--file', file])\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as {\n graph?: { nodes?: Record<string, { data: { root: string } }> }\n }\n const nodes = parsed.graph?.nodes\n if (!nodes || typeof nodes !== 'object') {\n throw new Error(\n 'sentinel: unexpected nx graph output (no graph.nodes); the installed nx version may be incompatible.',\n )\n }\n return Object.entries(nodes).map(([name, node]) => ({\n name,\n root: join(cwd, node.data.root),\n }))\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nexport function discoverModules(cwd: string, options: { affected?: boolean } = {}): ModuleRef[] {\n const modules = readGraph(cwd)\n if (!options.affected) return modules\n const affected = new Set(\n JSON.parse(runNx(cwd, ['show', 'projects', '--affected', '--json'])) as string[],\n )\n return modules.filter((module) => affected.has(module.name))\n}\n","/**\n * Tunable settings: the knobs you would actually change. The fixed vocabulary and\n * its types live in `domain.ts`.\n *\n * Note: the flavour is DETECTED from a module's dependencies (see\n * `detect-framework.ts`), deterministically, `node` when no framework dep is present.\n * `--flavour` overrides the detection (recommended for `--update`, since hoisted deps\n * can make detection return `node`). The detection table lives in `detect-framework.ts`.\n */\n\n/** A file that marks a workspace root; sentinel refuses to operate there. */\nexport const WORKSPACE_ROOT_MARKER = 'nx.json'\n","/**\n * The domain vocabulary: the fixed sets of verbs, targets, and flavours, and the\n * types derived from them. This is the model, and the extension point: adding a\n * verb / target / flavour is a one-line edit to a list here, and because the types\n * are DERIVED (`(typeof LIST)[number]`), the compiler forces every switch/handler\n * to cover the new member.\n *\n * Tunable behaviour (defaults, detection signals, marker filenames) lives in\n * `settings.ts`, not here.\n */\n\n/** Verbs: what to do. Each maps to an adapter method in dispatch. */\nexport const VERBS = ['run', 'inspect', 'update', 'report', 'status'] as const\nexport type Verb = (typeof VERBS)[number]\n\n/** Targets: the kind of check. The CLI `--<target>` flags map 1:1 to these. */\nexport const TARGETS = [\n 'lint',\n 'format',\n 'typescript',\n 'build',\n 'test',\n 'static-analysis',\n 'runtime-analysis',\n 'arch',\n] as const\nexport type Target = (typeof TARGETS)[number]\n\n/** Flavours: the stack preset a project resolves to (strict by default). */\nexport const FLAVOURS = ['react', 'nest', 'svelte', 'node'] as const\nexport type Flavour = (typeof FLAVOURS)[number]\n\n/**\n * The outcome of one module × target check. `unsupported` is a distinct third state\n * (a requested target has no adapter yet): it must never read as a pass, and an\n * explicitly-requested unsupported target is a usage error (non-zero exit), while an\n * unsupported target swept in by `--all` is just skipped and shown.\n */\nexport type ResultStatus = 'ok' | 'failed' | 'unsupported'\n","/**\n * Orchestration for `--report`/`--inspect`: analyse a set of modules across a set\n * of targets, then shape the outcomes into one versioned, parseable summary.\n *\n * The engine resolves each module's flavour and adapter and calls the per-module\n * method; a target with no adapter yet is simply skipped. `generateSummaries`\n * builds the aggregate by reusing `generateSummary` for each result (one factory,\n * no duplicated shaping between the single- and multi-module paths).\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { ModuleRef } from './discover-modules.js'\nimport type { Flavour, ResultStatus, Target } from './domain.js'\nimport { availableTargets, resolve } from './registry.js'\nimport type { RunContext } from './types.js'\n\n/** One project × target outcome. */\nexport interface AnalyseResult {\n project: string\n target: Target\n flavour: Flavour\n ok: boolean\n /**\n * The three-state outcome. `ok` stays for back-compat (`status === 'ok'`), but\n * `status` is the source of truth: `unsupported` (no adapter yet) must never read\n * as a pass.\n */\n status: ResultStatus\n /** `--report`: metrics (errors, implicit-any); `--inspect`: the resolved config. */\n data: unknown\n}\n\nexport interface AnalyseParams {\n verb: 'run' | 'report' | 'inspect' | 'status'\n targets: readonly Target[]\n modules: readonly ModuleRef[]\n runner?: string\n /** Explicit `--flavour`, when declared; otherwise detected per module from deps. */\n flavour?: Flavour\n /**\n * Whether the targets were named by the user (vs swept in by `--all` / no target). When\n * true, a target with no adapter yields a visible per-module `unsupported` row; when\n * false, it is skipped silently per module (the envelope's `skipped` still lists it once,\n * so `--all` does not flood the output with N×M unsupported rows).\n */\n targetsExplicit: boolean\n ci: boolean\n fix: boolean\n /** Cap on structured diagnostics per module in `--report` (0 = no cap). */\n maxDiagnostics?: number\n /** Called after each module, for progress display. */\n onProgress?: (done: number, total: number, moduleName: string) => void\n}\n\n/** Run the analyse across modules × targets. Returns the results + worst exit code. */\nexport async function analyse(\n params: AnalyseParams,\n): Promise<{ results: AnalyseResult[]; worstCode: number }> {\n const results: AnalyseResult[] = []\n let worstCode = 0\n let done = 0\n\n for (const module of params.modules) {\n // Explicit --flavour wins; otherwise detect it from the module's deps and, when the\n // module looks mixed (e.g. a Sails backend that also pulls React), warn and point at\n // --flavour rather than choosing silently.\n let flavour: Flavour\n if (params.flavour) {\n flavour = params.flavour\n } else {\n const detection = describeFramework(readProjectPackageJson(module.root))\n flavour = detection.flavour\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: ${module.name}: flavour is ambiguous, detected \"${flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to be explicit.`,\n ) + '\\n',\n )\n }\n }\n const ctx: RunContext = {\n module: module.name,\n cwd: module.root,\n flavour,\n ci: params.ci,\n fix: params.fix,\n maxDiagnostics: params.maxDiagnostics,\n }\n for (const target of params.targets) {\n let adapter\n try {\n adapter = resolve(target, flavour, params.runner)\n } catch (error) {\n // No adapter for this target yet. When the user named it, emit a visible per-module\n // `unsupported` row so it never reads as a pass. When it was swept in by `--all`,\n // skip silently per module: the envelope's `skipped` still lists it once, so `--all`\n // does not flood the output with one unsupported row per module × unwired target.\n if (params.targetsExplicit) {\n const reason = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'unsupported',\n data: { reason },\n })\n }\n continue\n }\n // Isolate every check: one module/target that throws must not abort the whole\n // sweep. A failure becomes a `ok:false` row (with the error) so `--report --all`\n // still returns a complete picture across a large workspace.\n try {\n if (params.verb === 'run') {\n // Label the run so multi-module output (root/--all) is readable; the tool\n // streams its own output (stdio inherit) between headers.\n if (params.modules.length > 1) {\n const err = palette(process.stderr)\n process.stderr.write(\n `\\n ${err.accent('▶')} ${err.strong(module.name)} ${err.dim(`(${flavour})`)} ${target}\\n`,\n )\n }\n const result = await adapter.run(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'report') {\n const result = await adapter.report(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: result.metrics ?? {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'inspect') {\n const config = await adapter.inspect(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: true,\n status: 'ok',\n data: config,\n })\n } else {\n // status: adoption + conformity, read from the committed config (no tool run).\n // A drifted adopted module is a conformance failure; a non-adopted one is not.\n const status = await adapter.status(ctx)\n const ok = !status.adopted || status.conformant\n results.push({\n project: module.name,\n target,\n flavour,\n ok,\n status: ok ? 'ok' : 'failed',\n data: status,\n })\n if (!ok) worstCode = Math.max(worstCode, 1)\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'failed',\n data: { error: message },\n })\n worstCode = Math.max(worstCode, 1)\n }\n }\n params.onProgress?.((done += 1), params.modules.length, module.name)\n }\n\n return { results, worstCode }\n}\n\n/** The shaped summary for ONE result (the factory unit). */\nexport function generateSummary(result: AnalyseResult): Record<string, unknown> {\n const { project, target, flavour, ok, status, data } = result\n const details =\n data && typeof data === 'object' ? (data as Record<string, unknown>) : { value: data }\n return { project, target, flavour, ok, status, ...details }\n}\n\n/**\n * The aggregate, versioned envelope for MANY results, built from `generateSummary`.\n * `executed` / `skipped` split the REQUESTED targets by whether an adapter is wired, so an\n * agent sees what actually ran vs what was unsupported at the top level without scanning\n * every row (and independently of `--all` suppressing per-module unsupported rows).\n * schemaVersion is 2 since these fields are new to the shape.\n */\nexport function generateSummaries(\n results: readonly AnalyseResult[],\n requestedTargets: readonly Target[],\n): {\n schemaVersion: number\n executed: Target[]\n skipped: Target[]\n results: Record<string, unknown>[]\n} {\n const available = new Set(availableTargets())\n return {\n schemaVersion: 2,\n executed: requestedTargets.filter((t) => available.has(t)),\n skipped: requestedTargets.filter((t) => !available.has(t)),\n results: results.map(generateSummary),\n }\n}\n","/**\n * Human rendering of one summary row (`--run`/`--report`/`--inspect`), colored via a\n * `Palette`. Pure and stream-agnostic: it returns the text, the CLI writes it. `--json`\n * bypasses this entirely (machines get the raw envelope).\n *\n * The goal is legibility: the pass/fail mark and the module name read at a glance, the\n * error counts stand out when non-zero, and the phased `deferred` rules become their own\n * indented block instead of an inline JSON blob.\n */\nimport type { Palette } from '../shared/color.js'\n\n/** Keys carried by the row head (or redundant with it), never repeated in the details. */\nconst HEAD_KEYS = new Set([\n 'project',\n 'module',\n 'target',\n 'flavour',\n 'ok',\n 'status',\n 'reason',\n // The structured diagnostics are for `--json` consumers; the human row stays the concise\n // `errors=N` line rather than dumping every diagnostic inline.\n 'diagnostics',\n 'diagnosticsTruncated',\n])\n\n/** A deferred (phased) rule, as `--inspect` reports it. */\ninterface DeferredRule {\n rule: string\n phase: number\n reason: string\n}\n\nfunction isDeferredRules(value: unknown): value is DeferredRule[] {\n return (\n Array.isArray(value) &&\n value.every((entry) => typeof entry === 'object' && entry !== null && 'rule' in entry)\n )\n}\n\n/** Color a detail value: error counts are the signal, so red when >0, green at 0. */\nfunction renderValue(key: string, value: unknown, p: Palette): string {\n if ((key === 'errors' || key === 'implicitAny') && typeof value === 'number') {\n return value > 0 ? p.fail(String(value)) : p.ok(String(value))\n }\n // implicitAny is `deferred` when the rule is off (phase 1): honest, not a fake 0.\n if (key === 'implicitAny' && value === 'deferred') return p.warn('deferred')\n return typeof value === 'string' ? value : JSON.stringify(value)\n}\n\n/**\n * `unsupported` is a third state (a requested target with no adapter yet): a distinct dim\n * marker, never a ✓ (would read as a pass) nor a ✗ (would read as a failure of the code).\n * Shared by every row renderer (summary AND status) so the state can never render as a\n * legitimate outcome on one path and not the other.\n */\nfunction renderUnsupportedRow(item: Record<string, unknown>, p: Palette): string {\n const reason = typeof item.reason === 'string' ? ` ${p.dim(`(${item.reason})`)}` : ''\n const head = ` ${p.dim('·')} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n return `${head} ${p.dim('—')} ${p.warn('unsupported')}${reason}`\n}\n\n/**\n * One summary row → colored lines: a head line (`✓ name (flavour) target — key=value …`)\n * plus, when present, an indented `deferred` block listing the phased rules.\n */\nexport function renderSummary(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const ok = item.ok === true\n const mark = ok ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n // Split details: the phased `deferred` rules get their own block; everything else is\n // rendered inline as dim `key=value` pairs (the head keys are dropped as redundant).\n const entries = Object.entries(item).filter(([key]) => !HEAD_KEYS.has(key))\n const deferred = entries.find(([key]) => key === 'deferred')?.[1]\n const inline = entries\n .filter(([key]) => key !== 'deferred')\n .map(([key, value]) => `${p.dim(`${key}=`)}${renderValue(key, value, p)}`)\n .join(' ')\n\n const lines = [inline ? `${head} ${p.dim('—')} ${inline}` : head]\n\n if (isDeferredRules(deferred) && deferred.length > 0) {\n lines.push(` ${p.warn('deferred (phase 1, non-breaking):')}`)\n for (const { rule, phase, reason } of deferred) {\n lines.push(` ${p.warn('•')} ${p.strong(rule)} ${p.dim(`— phase ${phase}: ${reason}`)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Workspace adoption + conformity totals, for the `--status` footer / JSON. */\nexport interface Coverage {\n total: number\n adopted: number\n conformant: number\n drifted: number\n}\n\nexport function statusCoverage(items: readonly Record<string, unknown>[]): Coverage {\n // Only real status rows count toward coverage; an `unsupported` target (a requested\n // target with no adapter) must not inflate the denominator (`total`).\n const relevant = items.filter((i) => i.status !== 'unsupported')\n const adopted = relevant.filter((i) => i.adopted === true)\n const conformant = adopted.filter((i) => i.conformant === true)\n return {\n total: relevant.length,\n adopted: adopted.length,\n conformant: conformant.length,\n drifted: adopted.length - conformant.length,\n }\n}\n\n/** Short preset name for display (`@hublo/sentinel/tsconfig/nest` -> `nest`). */\nfunction presetShort(preset: unknown): string {\n return typeof preset === 'string' ? preset.replace('@hublo/sentinel/tsconfig/', '') : '?'\n}\n\n/**\n * One `--status` row, three states: not-adopted (dim `·`), adopted + conformant (green\n * `✓`), adopted + drifted (red `✗`, with the drifted keys named).\n */\nexport function renderStatusRow(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const adopted = item.adopted === true\n const conformant = item.conformant === true\n const mark = !adopted ? p.dim('·') : conformant ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n if (!adopted) return `${head} ${p.dim('— not adopted')}`\n if (conformant) {\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)}) conformant`)}`\n }\n const drift = Array.isArray(item.drift) ? item.drift.join(', ') : ''\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)})`)} ${p.fail(`drift: ${drift}`)}`\n}\n\n/** The `--status` footer: `coverage: X/N adopted · K/X conformant`. */\nexport function renderStatusSummary(items: readonly Record<string, unknown>[], p: Palette): string {\n const c = statusCoverage(items)\n const drift = c.drifted > 0 ? p.fail(` · ${c.drifted} drifted`) : ''\n return ` ${p.strong('coverage:')} ${c.adopted}/${c.total} adopted ${p.dim('·')} ${c.conformant}/${c.adopted} conformant${drift}`\n}\n","/**\n * Node version guard. sentinel colours output with `util.styleText`, added in Node 20.12, so\n * an older runtime (the tester ran serviceapp on Node 10) crashes cryptically. We check up\n * front and fail with a clear, actionable message instead. Kept as a pure function so it is\n * unit-testable without spawning Node. The `{ stream }` option we pass is newer (22.13) but\n * older 20.x ignore it gracefully, so 20.12 is the real floor (and matches CI on Node 20).\n */\n\n/** The minimum Node this CLI supports (the `util.styleText` floor). */\nexport const MIN_NODE = '20.12.0'\n\n/** Parse `24.15.0` (or `v24.15.0`) into `[major, minor, patch]`; missing parts are 0. */\nfunction parts(version: string): [number, number, number] {\n const [major = 0, minor = 0, patch = 0] = version\n .replace(/^v/, '')\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n return [major, minor, patch]\n}\n\n/**\n * Whether `current` (e.g. `process.versions.node`) meets `min`. Compares major, then minor,\n * then patch. Returns `{ ok }` and, when not, a clear message telling the user their version,\n * the required one, and how to switch.\n */\nexport function checkNodeVersion(\n current: string,\n min: string = MIN_NODE,\n): { ok: boolean; message?: string } {\n const [cMajor, cMinor, cPatch] = parts(current)\n const [mMajor, mMinor, mPatch] = parts(min)\n const ok =\n cMajor > mMajor ||\n (cMajor === mMajor && cMinor > mMinor) ||\n (cMajor === mMajor && cMinor === mMinor && cPatch >= mPatch)\n if (ok) return { ok: true }\n return {\n ok: false,\n message:\n `sentinel requires Node >= ${min}, but you are on ${current}. ` +\n `Switch with fnm/nvm (e.g. \\`fnm use ${mMajor}\\`) and re-run.`,\n }\n}\n"],"mappings":"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","names":["join","cwd","cwd","join","opts","flavour"]}
1
+ {"version":3,"sources":["../../bin/sentinel.ts","../../src/core/context.ts","../../src/core/discover-modules.ts","../../src/core/settings.ts","../../src/core/domain.ts","../../src/core/orchestrate.ts","../../src/core/render.ts","../../src/core/workspace-prep.ts","../../src/shared/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * sentinel CLI: a command composes verb × type × location.\n *\n * Exactly one verb (--run / --inspect / --report / --status / --init); a type (--typescript …)\n * or ALL when none is named; and a location resolved by `resolveContext` (a module\n * directory, or the workspace root with --module / --ci / all). The CLI is generic: it\n * resolves each target's adapter (honouring --runner) and dispatches; tools arrive as\n * adapters in later tickets. The flavour is detected from a module's dependencies\n * (deterministic, `node` default) and can be overridden with --flavour (recommended\n * for --init).\n */\nimport { program } from 'commander'\n\nimport { registerAdapters } from '../src/adapters.js'\nimport { resolveContext } from '../src/core/context.js'\nimport { FLAVOURS, TARGETS, VERBS, type Flavour, type Target } from '../src/core/domain.js'\nimport { dispatch } from '../src/core/dispatch.js'\nimport { analyse, generateSummaries } from '../src/core/orchestrate.js'\nimport { availableTargets } from '../src/core/registry.js'\nimport {\n renderStatusRow,\n renderStatusSummary,\n renderSummary,\n statusCoverage,\n} from '../src/core/render.js'\nimport { ensureWorkspacePrep, findWorkspaceRoot } from '../src/core/workspace-prep.js'\nimport { palette } from '../src/shared/color.js'\nimport { checkNodeVersion } from '../src/shared/node-version.js'\nimport { readOwnVersion } from '../src/shared/package-json.js'\n\n// Fail fast with a clear message on an unsupported Node (sentinel's coloured output needs\n// `styleText`'s stream option, Node 22.13+), instead of a cryptic crash deep in the CLI.\nconst nodeCheck = checkNodeVersion(process.versions.node)\nif (!nodeCheck.ok) {\n process.stderr.write(`${nodeCheck.message}\\n`)\n process.exit(1)\n}\n\n// Register every tool adapter up front, BEFORE parsing, so `--help` can reflect what is\n// actually wired (availableTargets) rather than a hardcoded list that pretends all targets\n// are ready. Registration has no dependency on the parsed options.\nregisterAdapters()\n\nprogram\n .name('sentinel')\n .description('One CLI that guards code health: presets, analysis, and arch checks.')\n .version(readOwnVersion())\n .configureHelp({ sortOptions: false })\n // On any parse error (unknown flag, missing value): suggest the closest flag and\n // point at --help, so a typo like `--lnt` gets \"did you mean --lint?\".\n .showSuggestionAfterError(true)\n .showHelpAfterError('(run \"sentinel --help\" for usage)')\n .addHelpText(\n 'before',\n [\n 'A check composes: verb + type + location.',\n ' verb what to do: --run --inspect --report --status --init (--migrate: planned)',\n ' type which check: --lint --typescript ... (omit = all types; or --all)',\n ' where run from a MODULE dir → that module; from the ROOT → --module <name>,',\n ' --ci (affected), or all modules. --init targets one module only.',\n '',\n ].join('\\n'),\n )\n // verbs (pick one)\n .option('--run', 'execute the target tool')\n .option('--inspect', 'show the resolved configuration')\n .option('--init', 'set up a module: write its config stubs + the workspace prep it needs')\n .option('--migrate', 'planned: change an already-initialized setup (not available yet)')\n .option('--report', 'metrics and health report')\n .option('--status', 'adoption + conformity across modules (coverage + drift)')\n // targets (pick one, or --all)\n .option('--lint', 'linting')\n .option('--format', 'formatting')\n .option('--typescript', 'type checking')\n .option('--build', 'build')\n .option('--test', 'tests')\n .option('--static-analysis', 'cycles, complexity, duplication, centrality')\n .option('--runtime-analysis', 'bundle, Lighthouse, web vitals')\n .option('--arch', 'architecture boundaries')\n .option('--all', 'every target')\n // modifiers\n .option(\n '--module <name>',\n 'from the workspace root: scope to one module (omit = all; inside a module dir, drop this)',\n )\n .option('--flavour <name>', `override the detected stack preset (${FLAVOURS.join(', ')})`)\n .option('--runner <tool>', 'override the default runner (e.g. eslint, biome)')\n .option('--ci', 'CI mode: from the root, only the affected modules; non-zero exit on failure')\n .option('--fix', 'auto-fix where applicable')\n .option('--dry-run', 'preview the changes without writing (--init)')\n .option('--json', 'machine-readable JSON output (report/inspect/--dry-run)')\n .option(\n '--max-diagnostics <n>',\n 'cap the diagnostics embedded per module in --report (0 = no cap)',\n '100',\n )\n .addHelpText(\n 'after',\n [\n '',\n 'Examples:',\n ' sentinel --run --typescript # in a module → that module',\n ' sentinel --report --typescript --module bff-admin # from root → one module',\n ' sentinel --report # from root → all types, all modules',\n ' sentinel --report --ci # from root → affected only',\n ' sentinel --init --typescript --flavour react # set up the current module + workspace',\n ].join('\\n'),\n )\n // Availability, derived from the registry so it never lies: only wired targets are\n // \"available now\"; the rest are honestly marked as planned.\n .addHelpText('after', () => {\n const available = availableTargets()\n const planned = TARGETS.filter((t) => !available.includes(t))\n return [\n '',\n `Available now: ${available.length ? available.map((t) => `--${t}`).join(', ') : '(none yet)'}`,\n planned.length\n ? `Planned (ship in later tickets): ${planned.map((t) => `--${t}`).join(', ')}`\n : '',\n ]\n .filter(Boolean)\n .join('\\n')\n })\n .parse()\n\nconst opts = program.opts()\n\n// commander camelCases hyphenated flags (--static-analysis -> staticAnalysis).\nfunction toCamel(flag: string): string {\n return flag.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n}\n\n/** Exactly one of `keys` must be flagged; return it, else fail with guidance. */\nfunction pickOne<T extends string>(kind: string, keys: readonly T[]): T {\n const chosen = keys.filter((k) => opts[toCamel(k)])\n if (chosen.length !== 1) {\n const supported = keys.map((k) => `--${k}`).join(', ')\n program.error(\n chosen.length === 0\n ? `Missing a ${kind}. Supported: ${supported}.`\n : `Pick exactly one ${kind}: got ${chosen.map((k) => `--${k}`).join(', ')}.`,\n )\n }\n return chosen[0] as T\n}\n\n/**\n * The explicit `--flavour`, VALIDATED, or undefined when not given. When undefined,\n * the flavour is DETECTED from the module's dependencies (see `detect-framework.ts`);\n * this override is recommended for `--init`, where hoisted deps can make detection\n * fall back to `node`. An unknown value is a hard error (typo caught), not a silent guess.\n */\nfunction parseFlavour(value: unknown): Flavour | undefined {\n if (value === undefined) return undefined\n if (typeof value !== 'string' || !FLAVOURS.includes(value as Flavour)) {\n return program.error(\n `sentinel: unknown --flavour ${JSON.stringify(value)}. Supported: ${FLAVOURS.join(', ')}.`,\n )\n }\n return value as Flavour\n}\n\nfunction asMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nconst verb = pickOne('verb', VERBS)\nconst cwd = process.cwd()\nconst flavour = parseFlavour(opts.flavour)\n\n// Targets (the \"type\" axis): a named one, or ALL when none is given. Uniform for every\n// verb — run / inspect / report / update. `resolveContext` owns the \"location\" axis.\nconst namedTargets = TARGETS.filter((t) => opts[toCamel(t)])\nif (opts.all && namedTargets.length > 0) {\n program.error(\n `--all runs every target; drop the specific one(s): ${namedTargets.map((t) => `--${t}`).join(', ')}.`,\n )\n}\nconst targets: Target[] = opts.all || namedTargets.length === 0 ? [...TARGETS] : namedTargets\n// Whether the user named the target(s) themselves. When true, a target with no adapter is\n// a usage error (they asked for something sentinel cannot do). When false (`--all` / none),\n// an unsupported target is just an expected skip, shown but not failed.\nconst targetsExplicit = !(opts.all || namedTargets.length === 0)\n\n// --max-diagnostics: how many structured diagnostics --report embeds per module (0 = all).\n// `Number` (not parseInt) so a fractional value like `3.9` is rejected, not silently floored.\nconst maxDiagnostics = Number(opts.maxDiagnostics)\nif (!Number.isInteger(maxDiagnostics) || maxDiagnostics < 0) {\n program.error(\n `--max-diagnostics must be a non-negative integer (0 = no cap); got ${JSON.stringify(opts.maxDiagnostics)}.`,\n )\n}\n\n// --dry-run only previews a write.\nif (opts.dryRun && verb !== 'init') {\n program.error('--dry-run only applies to --init (the read verbs never write).')\n}\n\n/**\n * run / inspect / report: resolve the module context (cwd module, `--module`, all, or\n * `--ci` affected), then execute verb × targets across it and print.\n */\nasync function runVerb(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: Boolean(opts.ci) })\n const out = palette(process.stdout)\n const err = palette(process.stderr)\n\n // `--run` streams the tool's own output to stdout, so a JSON envelope would be mixed in;\n // `--json` is only honoured for report/inspect. Say so instead of silently ignoring it.\n if (opts.json && verb === 'run') {\n process.stderr.write(\n err.warn(\n 'note: --json is ignored for --run (it streams the tool output); use --report --json for a machine envelope.',\n ) + '\\n',\n )\n }\n\n const started = Date.now()\n const { results, worstCode } = await analyse({\n verb: verb as 'run' | 'report' | 'inspect' | 'status',\n targets,\n modules,\n runner: opts.runner,\n flavour,\n targetsExplicit,\n maxDiagnostics,\n ci: Boolean(opts.ci),\n fix: Boolean(opts.fix),\n onProgress: (done, total, name) =>\n process.stderr.write(err.dim(` [${done}/${total}] ${name}\\n`)),\n })\n\n // Shape each outcome into a summary row, then print it colored. `run` rows carry no\n // metrics (bare head line); `status` has its own 3-state rows + a coverage footer;\n // `--json` bypasses the human rendering entirely.\n const summary = generateSummaries(results, targets)\n if (opts.json && verb !== 'run') {\n const payload =\n verb === 'status' ? { ...summary, coverage: statusCoverage(summary.results) } : summary\n process.stdout.write(JSON.stringify(payload, null, 2) + '\\n')\n } else if (verb === 'status') {\n for (const item of summary.results) process.stdout.write(renderStatusRow(item, out) + '\\n')\n process.stdout.write(renderStatusSummary(summary.results, out) + '\\n')\n } else {\n for (const item of summary.results) {\n process.stdout.write(renderSummary(item, out) + '\\n')\n }\n }\n process.stderr.write(\n err.dim(` ${modules.length} module(s) [${scope}] in ${Date.now() - started}ms\\n`),\n )\n\n // An explicitly requested target with no adapter is a usage error: say so clearly and\n // exit non-zero, so a human or agent never mistakes \"nothing ran\" for a success.\n if (targetsExplicit && summary.skipped.length > 0) {\n const avail = availableTargets()\n process.stderr.write(\n err.fail(\n `sentinel: target(s) not available yet: ${summary.skipped.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${avail.length ? avail.map((t) => `--${t}`).join(', ') : '(none yet)'}.`,\n ) + '\\n',\n )\n return 1\n }\n\n // `run` always reports failures via its exit code; report/inspect only under --ci.\n return verb === 'run' || opts.ci ? worstCode : 0\n}\n\n/**\n * init: a WRITE. Same context rule, but it must resolve to exactly ONE module (the\n * current one, or `--module`). Adopting every module at once would be a big-bang, so\n * the implicit \"all\" is deliberately refused; adopt gradually. After writing the module's\n * files it applies the workspace-level prep that module needs to install and resolve (the\n * one place a write reaches the root); see `workspace-prep`.\n */\nasync function runInit(): Promise<number> {\n const { modules, scope } = resolveContext(cwd, { module: opts.module, ci: false })\n const [module] = modules\n if (scope === 'all' || scope === 'affected' || !module) {\n program.error(\n '--init writes files: target one module (run from its directory, or pass --module). ' +\n 'Adopting every module at once is intentionally not allowed — adopt gradually.',\n )\n }\n // Same policy as the read verbs: a target the user NAMED but that has no adapter is a\n // usage error; targets swept in by --all / no target are just skipped, never flooded as\n // \"No adapter…\" errors that would fail an otherwise-successful update.\n const available = availableTargets()\n if (targetsExplicit) {\n const unwired = targets.filter((type) => !available.includes(type))\n if (unwired.length > 0) {\n program.error(\n `target(s) not available yet: ${unwired.map((t) => `--${t}`).join(', ')}. ` +\n `Available now: ${available.map((t) => `--${t}`).join(', ') || '(none yet)'}.`,\n )\n }\n }\n const toRun = targets.filter((type) => available.includes(type))\n\n let worst = 0\n for (const type of toRun) {\n try {\n const code = await dispatch({\n verb,\n target: type,\n runner: opts.runner,\n cwd: module.root,\n flavour,\n dryRun: Boolean(opts.dryRun),\n json: Boolean(opts.json),\n })\n worst = Math.max(worst, code)\n } catch (err) {\n // A genuine adapter failure (not an unwired target, those are filtered above).\n process.stderr.write(`\\nsentinel (${type}): ${asMessage(err)}\\n`)\n worst = Math.max(worst, 1)\n }\n }\n\n // After a clean write, apply the workspace-level prep the module needs to install and\n // resolve. Skipped when the write failed, so we never prep a half-done init. Honoured\n // under --dry-run as a preview (nothing written).\n if (worst === 0) {\n const root = findWorkspaceRoot(module.root)\n if (root) {\n const changes = ensureWorkspacePrep({ root, dryRun: Boolean(opts.dryRun) })\n const prefix = opts.dryRun ? ' dry run: ' : ' '\n for (const change of changes) process.stderr.write(`${prefix}${change}\\n`)\n if (changes.length > 0 && !opts.dryRun) {\n process.stderr.write(\n palette(process.stderr).dim(' run `pnpm install` to apply the workspace changes\\n'),\n )\n }\n }\n }\n return worst\n}\n\n/**\n * Entry point. `--init` writes the config stubs + workspace prep; the read verbs\n * (run / inspect / report / status) resolve a location and execute. `--migrate` is\n * planned and refused for now. Exit with the command's own status code, or 1 on an\n * unexpected error.\n */\nasync function main(): Promise<void> {\n if (verb === 'migrate') {\n program.error('--migrate is planned and not available yet; use --init to set a module up.')\n }\n const runSelectedVerb = verb === 'init' ? runInit : runVerb\n const exitCode = await runSelectedVerb()\n process.exit(exitCode)\n}\n\nmain().catch((error: unknown) => {\n process.stderr.write(`\\nsentinel: ${asMessage(error)}\\n`)\n process.exit(1)\n})\n","/**\n * Context resolution: the composable \"location\" axis of a command.\n *\n * One rule for every read verb (run / inspect / report), so a developer works the way\n * they already work with nx, from their module or from the root:\n * - in a MODULE dir (has package.json/project.json, and is NOT the workspace root):\n * the context is THAT module; `--module` is redundant there and is rejected.\n * - at the workspace ROOT: `--module X` scopes to X, `--ci` scopes to the affected\n * set, and otherwise it is every module (the whole-repo status).\n * Update (a write) reuses this but forbids the implicit \"all\" (see the CLI).\n */\nimport { existsSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { readNxProjectName } from '../shared/package-json.js'\nimport { discoverModules, type ModuleRef } from './discover-modules.js'\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\n/** A directory is a module if it carries one of these (and is not the root). */\nconst MODULE_MARKERS = ['package.json', 'project.json'] as const\n\n/** How the context was resolved, for messages and the \"all\"-guard on update. */\nexport type ContextScope = 'cwd-module' | 'named-module' | 'affected' | 'all'\n\nexport interface ResolvedContext {\n modules: ModuleRef[]\n scope: ContextScope\n}\n\nfunction isModuleDir(cwd: string): boolean {\n return MODULE_MARKERS.some((marker) => existsSync(join(cwd, marker)))\n}\n\n/**\n * Resolve which modules a command targets from where it runs + its flags. Throws with\n * an actionable message when the combination is contradictory (e.g. `--module` from\n * inside a module) or the directory is neither a module nor the workspace root.\n */\nexport function resolveContext(\n cwd: string,\n opts: { module?: string; ci?: boolean },\n): ResolvedContext {\n const atRoot = existsSync(join(cwd, WORKSPACE_ROOT_MARKER))\n\n // In a module directory: the module IS the current one.\n if (!atRoot && isModuleDir(cwd)) {\n if (opts.module) {\n throw new Error(\n 'You are in a module directory: drop --module (the context is the current module).',\n )\n }\n if (opts.ci) {\n throw new Error('--ci selects the affected set from the workspace root; run it there.')\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return { modules: [{ name, root: cwd }], scope: 'cwd-module' }\n }\n\n // At the workspace root: --module (one), --ci (affected), or every module.\n if (atRoot) {\n if (opts.module) {\n const found = discoverModules(cwd).find((module) => module.name === opts.module)\n if (!found) throw new Error(`module \"${opts.module}\" not found in the workspace.`)\n return { modules: [found], scope: 'named-module' }\n }\n if (opts.ci) return { modules: discoverModules(cwd, { affected: true }), scope: 'affected' }\n return { modules: discoverModules(cwd), scope: 'all' }\n }\n\n throw new Error(\n `Run sentinel from a module directory or the workspace root ` +\n `(found neither ${MODULE_MARKERS.join('/')} nor ${WORKSPACE_ROOT_MARKER} here).`,\n )\n}\n","/**\n * Module discovery via nx. `--report`/`--inspect` without a `--module` analyse\n * every project; `--ci` narrows to the affected ones. We ask nx (its cached graph)\n * for the authoritative names + roots in a couple of calls, not one per project,\n * so it stays fast on the real monorepo.\n */\nimport { execFileSync } from 'node:child_process'\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { resolveBin } from '../shared/resolve-bin.js'\n\n/** A discovered module: its nx project name and absolute root. */\nexport interface ModuleRef {\n name: string\n root: string\n}\n\nfunction runNx(cwd: string, args: string[]): string {\n const nx = resolveBin(cwd, 'nx') ?? 'nx'\n try {\n // Disable the daemon for deterministic, CI-friendly one-shot invocations.\n return execFileSync(nx, args, {\n cwd,\n encoding: 'utf8',\n env: { ...process.env, NX_DAEMON: 'false' },\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new Error(\n `sentinel: could not run nx (${message}). Is nx installed in this workspace, and are you at its root?`,\n { cause: error },\n )\n }\n}\n\n/** Every module (name -> absolute root) from the nx project graph, in one call. */\nfunction readGraph(cwd: string): ModuleRef[] {\n const dir = mkdtempSync(join(tmpdir(), 'sentinel-nx-'))\n const file = join(dir, 'graph.json')\n try {\n runNx(cwd, ['graph', '--file', file])\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as {\n graph?: { nodes?: Record<string, { data: { root: string } }> }\n }\n const nodes = parsed.graph?.nodes\n if (!nodes || typeof nodes !== 'object') {\n throw new Error(\n 'sentinel: unexpected nx graph output (no graph.nodes); the installed nx version may be incompatible.',\n )\n }\n return Object.entries(nodes).map(([name, node]) => ({\n name,\n root: join(cwd, node.data.root),\n }))\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nexport function discoverModules(cwd: string, options: { affected?: boolean } = {}): ModuleRef[] {\n const modules = readGraph(cwd)\n if (!options.affected) return modules\n const affected = new Set(\n JSON.parse(runNx(cwd, ['show', 'projects', '--affected', '--json'])) as string[],\n )\n return modules.filter((module) => affected.has(module.name))\n}\n","/**\n * Tunable settings: the knobs you would actually change. The fixed vocabulary and\n * its types live in `domain.ts`.\n *\n * Note: the flavour is DETECTED from a module's dependencies (see\n * `detect-framework.ts`), deterministically, `node` when no framework dep is present.\n * `--flavour` overrides the detection (recommended for `--init`, since hoisted deps\n * can make detection return `node`). The detection table lives in `detect-framework.ts`.\n */\n\n/** A file that marks a workspace root; sentinel refuses to operate there. */\nexport const WORKSPACE_ROOT_MARKER = 'nx.json'\n","/**\n * The domain vocabulary: the fixed sets of verbs, targets, and flavours, and the\n * types derived from them. This is the model, and the extension point: adding a\n * verb / target / flavour is a one-line edit to a list here, and because the types\n * are DERIVED (`(typeof LIST)[number]`), the compiler forces every switch/handler\n * to cover the new member.\n *\n * Tunable behaviour (defaults, detection signals, marker filenames) lives in\n * `settings.ts`, not here.\n */\n\n/**\n * Verbs: what to do. `init` writes a module's config + the workspace prep it needs (the\n * one-time setup); the read verbs (run/inspect/report/status) execute or introspect.\n * `migrate` is reserved for changing an already-initialized setup and is not wired yet\n * (the CLI lists it as planned and refuses it).\n */\nexport const VERBS = ['run', 'inspect', 'init', 'migrate', 'report', 'status'] as const\nexport type Verb = (typeof VERBS)[number]\n\n/** Targets: the kind of check. The CLI `--<target>` flags map 1:1 to these. */\nexport const TARGETS = [\n 'lint',\n 'format',\n 'typescript',\n 'build',\n 'test',\n 'static-analysis',\n 'runtime-analysis',\n 'arch',\n] as const\nexport type Target = (typeof TARGETS)[number]\n\n/** Flavours: the stack preset a project resolves to (strict by default). */\nexport const FLAVOURS = ['react', 'nest', 'svelte', 'node'] as const\nexport type Flavour = (typeof FLAVOURS)[number]\n\n/**\n * The outcome of one module × target check. `unsupported` is a distinct third state\n * (a requested target has no adapter yet): it must never read as a pass, and an\n * explicitly-requested unsupported target is a usage error (non-zero exit), while an\n * unsupported target swept in by `--all` is just skipped and shown.\n */\nexport type ResultStatus = 'ok' | 'failed' | 'unsupported'\n","/**\n * Orchestration for `--report`/`--inspect`: analyse a set of modules across a set\n * of targets, then shape the outcomes into one versioned, parseable summary.\n *\n * The engine resolves each module's flavour and adapter and calls the per-module\n * method; a target with no adapter yet is simply skipped. `generateSummaries`\n * builds the aggregate by reusing `generateSummary` for each result (one factory,\n * no duplicated shaping between the single- and multi-module paths).\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { ModuleRef } from './discover-modules.js'\nimport type { Flavour, ResultStatus, Target } from './domain.js'\nimport { availableTargets, resolve } from './registry.js'\nimport type { RunContext } from './types.js'\n\n/** One project × target outcome. */\nexport interface AnalyseResult {\n project: string\n target: Target\n flavour: Flavour\n ok: boolean\n /**\n * The three-state outcome. `ok` stays for back-compat (`status === 'ok'`), but\n * `status` is the source of truth: `unsupported` (no adapter yet) must never read\n * as a pass.\n */\n status: ResultStatus\n /** `--report`: metrics (errors, implicit-any); `--inspect`: the resolved config. */\n data: unknown\n}\n\nexport interface AnalyseParams {\n verb: 'run' | 'report' | 'inspect' | 'status'\n targets: readonly Target[]\n modules: readonly ModuleRef[]\n runner?: string\n /** Explicit `--flavour`, when declared; otherwise detected per module from deps. */\n flavour?: Flavour\n /**\n * Whether the targets were named by the user (vs swept in by `--all` / no target). When\n * true, a target with no adapter yields a visible per-module `unsupported` row; when\n * false, it is skipped silently per module (the envelope's `skipped` still lists it once,\n * so `--all` does not flood the output with N×M unsupported rows).\n */\n targetsExplicit: boolean\n ci: boolean\n fix: boolean\n /** Cap on structured diagnostics per module in `--report` (0 = no cap). */\n maxDiagnostics?: number\n /** Called after each module, for progress display. */\n onProgress?: (done: number, total: number, moduleName: string) => void\n}\n\n/** Run the analyse across modules × targets. Returns the results + worst exit code. */\nexport async function analyse(\n params: AnalyseParams,\n): Promise<{ results: AnalyseResult[]; worstCode: number }> {\n const results: AnalyseResult[] = []\n let worstCode = 0\n let done = 0\n\n for (const module of params.modules) {\n // Explicit --flavour wins; otherwise detect it from the module's deps and, when the\n // module looks mixed (e.g. a Sails backend that also pulls React), warn and point at\n // --flavour rather than choosing silently.\n let flavour: Flavour\n if (params.flavour) {\n flavour = params.flavour\n } else {\n const detection = describeFramework(readProjectPackageJson(module.root))\n flavour = detection.flavour\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: ${module.name}: flavour is ambiguous, detected \"${flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to be explicit.`,\n ) + '\\n',\n )\n }\n }\n const ctx: RunContext = {\n module: module.name,\n cwd: module.root,\n flavour,\n ci: params.ci,\n fix: params.fix,\n maxDiagnostics: params.maxDiagnostics,\n }\n for (const target of params.targets) {\n let adapter\n try {\n adapter = resolve(target, flavour, params.runner)\n } catch (error) {\n // No adapter for this target yet. When the user named it, emit a visible per-module\n // `unsupported` row so it never reads as a pass. When it was swept in by `--all`,\n // skip silently per module: the envelope's `skipped` still lists it once, so `--all`\n // does not flood the output with one unsupported row per module × unwired target.\n if (params.targetsExplicit) {\n const reason = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'unsupported',\n data: { reason },\n })\n }\n continue\n }\n // Isolate every check: one module/target that throws must not abort the whole\n // sweep. A failure becomes a `ok:false` row (with the error) so `--report --all`\n // still returns a complete picture across a large workspace.\n try {\n if (params.verb === 'run') {\n // Label the run so multi-module output (root/--all) is readable; the tool\n // streams its own output (stdio inherit) between headers.\n if (params.modules.length > 1) {\n const err = palette(process.stderr)\n process.stderr.write(\n `\\n ${err.accent('▶')} ${err.strong(module.name)} ${err.dim(`(${flavour})`)} ${target}\\n`,\n )\n }\n const result = await adapter.run(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'report') {\n const result = await adapter.report(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: result.ok,\n status: result.ok ? 'ok' : 'failed',\n data: result.metrics ?? {},\n })\n worstCode = Math.max(worstCode, result.code)\n } else if (params.verb === 'inspect') {\n const config = await adapter.inspect(ctx)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: true,\n status: 'ok',\n data: config,\n })\n } else {\n // status: adoption + conformity, read from the committed config (no tool run).\n // A drifted adopted module is a conformance failure; a non-adopted one is not.\n const status = await adapter.status(ctx)\n const ok = !status.adopted || status.conformant\n results.push({\n project: module.name,\n target,\n flavour,\n ok,\n status: ok ? 'ok' : 'failed',\n data: status,\n })\n if (!ok) worstCode = Math.max(worstCode, 1)\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push({\n project: module.name,\n target,\n flavour,\n ok: false,\n status: 'failed',\n data: { error: message },\n })\n worstCode = Math.max(worstCode, 1)\n }\n }\n params.onProgress?.((done += 1), params.modules.length, module.name)\n }\n\n return { results, worstCode }\n}\n\n/** The shaped summary for ONE result (the factory unit). */\nexport function generateSummary(result: AnalyseResult): Record<string, unknown> {\n const { project, target, flavour, ok, status, data } = result\n const details =\n data && typeof data === 'object' ? (data as Record<string, unknown>) : { value: data }\n return { project, target, flavour, ok, status, ...details }\n}\n\n/**\n * The aggregate, versioned envelope for MANY results, built from `generateSummary`.\n * `executed` / `skipped` split the REQUESTED targets by whether an adapter is wired, so an\n * agent sees what actually ran vs what was unsupported at the top level without scanning\n * every row (and independently of `--all` suppressing per-module unsupported rows).\n * schemaVersion is 2 since these fields are new to the shape.\n */\nexport function generateSummaries(\n results: readonly AnalyseResult[],\n requestedTargets: readonly Target[],\n): {\n schemaVersion: number\n executed: Target[]\n skipped: Target[]\n results: Record<string, unknown>[]\n} {\n const available = new Set(availableTargets())\n return {\n schemaVersion: 2,\n executed: requestedTargets.filter((t) => available.has(t)),\n skipped: requestedTargets.filter((t) => !available.has(t)),\n results: results.map(generateSummary),\n }\n}\n","/**\n * Human rendering of one summary row (`--run`/`--report`/`--inspect`), colored via a\n * `Palette`. Pure and stream-agnostic: it returns the text, the CLI writes it. `--json`\n * bypasses this entirely (machines get the raw envelope).\n *\n * The goal is legibility: the pass/fail mark and the module name read at a glance, the\n * error counts stand out when non-zero, and the phased `deferred` rules become their own\n * indented block instead of an inline JSON blob.\n */\nimport type { Palette } from '../shared/color.js'\n\n/** Keys carried by the row head (or redundant with it), never repeated in the details. */\nconst HEAD_KEYS = new Set([\n 'project',\n 'module',\n 'target',\n 'flavour',\n 'ok',\n 'status',\n 'reason',\n // The structured diagnostics are for `--json` consumers; the human row stays the concise\n // `errors=N` line rather than dumping every diagnostic inline.\n 'diagnostics',\n 'diagnosticsTruncated',\n])\n\n/** A deferred (phased) rule, as `--inspect` reports it. */\ninterface DeferredRule {\n rule: string\n phase: number\n reason: string\n}\n\nfunction isDeferredRules(value: unknown): value is DeferredRule[] {\n return (\n Array.isArray(value) &&\n value.every((entry) => typeof entry === 'object' && entry !== null && 'rule' in entry)\n )\n}\n\n/** Color a detail value: error counts are the signal, so red when >0, green at 0. */\nfunction renderValue(key: string, value: unknown, p: Palette): string {\n if ((key === 'errors' || key === 'implicitAny') && typeof value === 'number') {\n return value > 0 ? p.fail(String(value)) : p.ok(String(value))\n }\n // implicitAny is `deferred` when the rule is off (phase 1): honest, not a fake 0.\n if (key === 'implicitAny' && value === 'deferred') return p.warn('deferred')\n return typeof value === 'string' ? value : JSON.stringify(value)\n}\n\n/**\n * `unsupported` is a third state (a requested target with no adapter yet): a distinct dim\n * marker, never a ✓ (would read as a pass) nor a ✗ (would read as a failure of the code).\n * Shared by every row renderer (summary AND status) so the state can never render as a\n * legitimate outcome on one path and not the other.\n */\nfunction renderUnsupportedRow(item: Record<string, unknown>, p: Palette): string {\n const reason = typeof item.reason === 'string' ? ` ${p.dim(`(${item.reason})`)}` : ''\n const head = ` ${p.dim('·')} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n return `${head} ${p.dim('—')} ${p.warn('unsupported')}${reason}`\n}\n\n/**\n * One summary row → colored lines: a head line (`✓ name (flavour) target — key=value …`)\n * plus, when present, an indented `deferred` block listing the phased rules.\n */\nexport function renderSummary(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const ok = item.ok === true\n const mark = ok ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n // Split details: the phased `deferred` rules get their own block; everything else is\n // rendered inline as dim `key=value` pairs (the head keys are dropped as redundant).\n const entries = Object.entries(item).filter(([key]) => !HEAD_KEYS.has(key))\n const deferred = entries.find(([key]) => key === 'deferred')?.[1]\n const inline = entries\n .filter(([key]) => key !== 'deferred')\n .map(([key, value]) => `${p.dim(`${key}=`)}${renderValue(key, value, p)}`)\n .join(' ')\n\n const lines = [inline ? `${head} ${p.dim('—')} ${inline}` : head]\n\n if (isDeferredRules(deferred) && deferred.length > 0) {\n lines.push(` ${p.warn('deferred (phase 1, non-breaking):')}`)\n for (const { rule, phase, reason } of deferred) {\n lines.push(` ${p.warn('•')} ${p.strong(rule)} ${p.dim(`— phase ${phase}: ${reason}`)}`)\n }\n }\n return lines.join('\\n')\n}\n\n/** Workspace adoption + conformity totals, for the `--status` footer / JSON. */\nexport interface Coverage {\n total: number\n adopted: number\n conformant: number\n drifted: number\n}\n\nexport function statusCoverage(items: readonly Record<string, unknown>[]): Coverage {\n // Only real status rows count toward coverage; an `unsupported` target (a requested\n // target with no adapter) must not inflate the denominator (`total`).\n const relevant = items.filter((i) => i.status !== 'unsupported')\n const adopted = relevant.filter((i) => i.adopted === true)\n const conformant = adopted.filter((i) => i.conformant === true)\n return {\n total: relevant.length,\n adopted: adopted.length,\n conformant: conformant.length,\n drifted: adopted.length - conformant.length,\n }\n}\n\n/** Short preset name for display (`@hublo/sentinel/tsconfig/nest` -> `nest`). */\nfunction presetShort(preset: unknown): string {\n return typeof preset === 'string' ? preset.replace('@hublo/sentinel/tsconfig/', '') : '?'\n}\n\n/**\n * One `--status` row, three states: not-adopted (dim `·`), adopted + conformant (green\n * `✓`), adopted + drifted (red `✗`, with the drifted keys named).\n */\nexport function renderStatusRow(item: Record<string, unknown>, p: Palette): string {\n if (item.status === 'unsupported') return renderUnsupportedRow(item, p)\n\n const adopted = item.adopted === true\n const conformant = item.conformant === true\n const mark = !adopted ? p.dim('·') : conformant ? p.ok('✓') : p.fail('✗')\n const head = ` ${mark} ${p.strong(String(item.project))} ${p.dim(`(${item.flavour})`)} ${item.target}`\n\n if (!adopted) return `${head} ${p.dim('— not adopted')}`\n if (conformant) {\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)}) conformant`)}`\n }\n const drift = Array.isArray(item.drift) ? item.drift.join(', ') : ''\n return `${head} ${p.dim('—')} ${p.ok('adopted')} ${p.dim(`(${presetShort(item.preset)})`)} ${p.fail(`drift: ${drift}`)}`\n}\n\n/** The `--status` footer: `coverage: X/N adopted · K/X conformant`. */\nexport function renderStatusSummary(items: readonly Record<string, unknown>[], p: Palette): string {\n const c = statusCoverage(items)\n const drift = c.drifted > 0 ? p.fail(` · ${c.drifted} drifted`) : ''\n return ` ${p.strong('coverage:')} ${c.adopted}/${c.total} adopted ${p.dim('·')} ${c.conformant}/${c.adopted} conformant${drift}`\n}\n","/**\n * Workspace-level preparation that `--init` applies once, at the workspace root, so an\n * adopted module actually installs and resolves cleanly. `--init` writes a module's own\n * files; this is the small, deliberate exception where it also touches the root, because\n * \"set this module up\" is not honest if the workspace it lives in cannot then install or\n * resolve it.\n *\n * Kept intentionally narrow (YAGNI): it applies the specific fixes a workspace's OWN\n * config shows it needs, and is a no-op everywhere else. It is NOT a speculative dedupe\n * engine, when a second, different case appears we generalize from real cases instead of\n * predicting them. The two fixes today:\n *\n * - i18next singleton override. If the root declares a second TypeScript via the\n * `@typescript/native` alias (a repo mid-migration running two TypeScripts), adopting a\n * module re-resolves pnpm and i18next, which carries an optional type-only `typescript`\n * peer, forks into two peer-keyed variants. That forks react-i18next into two React\n * contexts and breaks translation-singleton tests. Pinning i18next's `typescript` peer\n * to the declared TS collapses it to one. Read from the root's own declaration, so it\n * tracks the version and disappears (no-op) once the migration removes the second TS.\n *\n * - release-age allow-list. If the root uses pnpm's `minimumReleaseAgeExclude` gate, a\n * freshly published sentinel is younger than the gate and will not install; allow-list\n * it. Only touched when the gate is actually configured.\n *\n * Both edits are idempotent and defensive: absent the trigger (no second TypeScript, no\n * release-age gate) each is skipped, so the step is safe on any workspace.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport { readOwnPackage } from '../shared/package-json.js'\nimport { WORKSPACE_ROOT_MARKER } from './settings.js'\n\nconst OVERRIDE_KEY = 'i18next>typescript'\nconst NATIVE_TS_ALIAS = '@typescript/native' // `npm:typescript@^7.x`: a workspace's second, real TS\nconst WORKSPACE_YAML = 'pnpm-workspace.yaml'\nconst RELEASE_AGE_KEY = 'minimumReleaseAgeExclude'\nconst LIST_ITEM = /^(\\s*)-\\s+(.*?)\\s*$/\n\ninterface RootPackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n pnpm?: { overrides?: Record<string, string> }\n}\n\n/** Walk up from a module directory to the workspace root (the dir holding the marker). */\nexport function findWorkspaceRoot(startDir: string): string | undefined {\n let dir = startDir\n for (;;) {\n if (existsSync(join(dir, WORKSPACE_ROOT_MARKER))) return dir\n const parent = dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n\n/**\n * The TypeScript version to pin i18next's peer to, read from the root's own\n * `@typescript/native` alias (`npm:typescript@^7.0.2` -> `7.0.2`). Returns undefined when\n * the alias is absent (no second TypeScript, so no fork to fix).\n */\nfunction declaredNativeTs(pkg: RootPackageJson): string | undefined {\n const spec = pkg.dependencies?.[NATIVE_TS_ALIAS] ?? pkg.devDependencies?.[NATIVE_TS_ALIAS]\n if (!spec) return undefined\n const version = spec.slice(spec.lastIndexOf('@') + 1).replace(/^[\\^~>=<\\s]+/, '')\n return version || undefined\n}\n\n/**\n * Ensure the i18next singleton override matches the declared second TypeScript. No-op when\n * the workspace runs a single TypeScript (no `@typescript/native`). Returns a message when\n * it changed (or would change, under dryRun), else undefined.\n */\nfunction ensureI18nextSingleton(root: string, dryRun: boolean): string | undefined {\n const pkgPath = join(root, 'package.json')\n if (!existsSync(pkgPath)) return undefined\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as RootPackageJson\n const want = declaredNativeTs(pkg)\n if (!want) return undefined // single TypeScript: nothing forks\n\n const have = pkg.pnpm?.overrides?.[OVERRIDE_KEY]\n if (have === want) return undefined\n if (dryRun) return `would pin ${OVERRIDE_KEY} to ${want} (i18next singleton)`\n\n pkg.pnpm ??= {}\n pkg.pnpm.overrides ??= {}\n pkg.pnpm.overrides[OVERRIDE_KEY] = want\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n return `pinned ${OVERRIDE_KEY} to ${want} in package.json (i18next singleton)`\n}\n\n/**\n * Ensure sentinel is allow-listed under `minimumReleaseAgeExclude` so a fresh publish\n * installs. No-op when the workspace has no such gate. Targeted text edit so comments and\n * formatting are preserved. Returns a message when it changed (or would), else undefined.\n */\nfunction ensureReleaseAgeAllowList(root: string, dryRun: boolean): string | undefined {\n const yamlPath = join(root, WORKSPACE_YAML)\n if (!existsSync(yamlPath)) return undefined\n const own = readOwnPackage().name\n const lines = readFileSync(yamlPath, 'utf8').split('\\n')\n const keyIdx = lines.findIndex((line) => line.replace(/\\s+$/, '') === `${RELEASE_AGE_KEY}:`)\n if (keyIdx === -1) return undefined // no release-age gate in use\n\n let lastItemIdx = keyIdx\n let indent = ' '\n for (let i = keyIdx + 1; i < lines.length; i++) {\n const match = lines[i]?.match(LIST_ITEM)\n if (!match) break\n indent = match[1] ?? indent\n lastItemIdx = i\n if ((match[2] ?? '').replace(/^['\"]|['\"]$/g, '') === own) return undefined // already listed\n }\n if (dryRun) return `would allow-list ${own} under ${RELEASE_AGE_KEY}`\n\n lines.splice(lastItemIdx + 1, 0, `${indent}- '${own}'`)\n writeFileSync(yamlPath, lines.join('\\n'))\n return `allow-listed ${own} under ${RELEASE_AGE_KEY} in ${WORKSPACE_YAML}`\n}\n\n/**\n * Apply every workspace-level fix the given root needs. Returns the human-readable\n * messages for what changed (empty when nothing did). Under dryRun nothing is written and\n * the messages describe what would change.\n */\nexport function ensureWorkspacePrep(opts: { root: string; dryRun?: boolean }): string[] {\n const dryRun = Boolean(opts.dryRun)\n return [\n ensureI18nextSingleton(opts.root, dryRun),\n ensureReleaseAgeAllowList(opts.root, dryRun),\n ].filter((message): message is string => message !== undefined)\n}\n","/**\n * Node version guard. sentinel colours output with `util.styleText`, added in Node 20.12, so\n * an older runtime (the tester ran serviceapp on Node 10) crashes cryptically. We check up\n * front and fail with a clear, actionable message instead. Kept as a pure function so it is\n * unit-testable without spawning Node. The `{ stream }` option we pass is newer (22.13) but\n * older 20.x ignore it gracefully, so 20.12 is the real floor (and matches CI on Node 20).\n */\n\n/** The minimum Node this CLI supports (the `util.styleText` floor). */\nexport const MIN_NODE = '20.12.0'\n\n/** Parse `24.15.0` (or `v24.15.0`) into `[major, minor, patch]`; missing parts are 0. */\nfunction parts(version: string): [number, number, number] {\n const [major = 0, minor = 0, patch = 0] = version\n .replace(/^v/, '')\n .split('.')\n .map((n) => Number.parseInt(n, 10) || 0)\n return [major, minor, patch]\n}\n\n/**\n * Whether `current` (e.g. `process.versions.node`) meets `min`. Compares major, then minor,\n * then patch. Returns `{ ok }` and, when not, a clear message telling the user their version,\n * the required one, and how to switch.\n */\nexport function checkNodeVersion(\n current: string,\n min: string = MIN_NODE,\n): { ok: boolean; message?: string } {\n const [cMajor, cMinor, cPatch] = parts(current)\n const [mMajor, mMinor, mPatch] = parts(min)\n const ok =\n cMajor > mMajor ||\n (cMajor === mMajor && cMinor > mMinor) ||\n (cMajor === mMajor && cMinor === mMinor && cPatch >= mPatch)\n if (ok) return { ok: true }\n return {\n ok: false,\n message:\n `sentinel requires Node >= ${min}, but you are on ${current}. ` +\n `Switch with fnm/nvm (e.g. \\`fnm use ${mMajor}\\`) and re-run.`,\n }\n}\n"],"mappings":"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","names":["join","cwd","cwd","join","opts","flavour","existsSync","readFileSync","join","existsSync","join","readFileSync","opts"]}
@@ -287,7 +287,7 @@ var TscAdapter = class extends BaseAdapter {
287
287
  /**
288
288
  * The tsc adapter drives type-checking for any flavour: `--run`/`--report`/
289
289
  * `--inspect` just execute tsc against the module's existing config, which is
290
- * meaningful regardless of flavour. `--update` is the exception, it only WRITES a
290
+ * meaningful regardless of flavour. `--init` is the exception, it only WRITES a
291
291
  * preset for flavours that ship one (gated inside `plan`), so svelte is not
292
292
  * clobbered with a non-existent preset.
293
293
  */
@@ -295,7 +295,7 @@ var TscAdapter = class extends BaseAdapter {
295
295
  return true;
296
296
  }
297
297
  /**
298
- * Plan `--update`: make the module extend the sentinel preset with a THIN,
298
+ * Plan `--init`: make the module extend the sentinel preset with a THIN,
299
299
  * conformant stub, and route type-checking through the CLI. The engine applies
300
300
  * the ops; ensuring the `@hublo/sentinel` dependency is an adoption step
301
301
  * (`pnpm add`), not a file write.
@@ -739,8 +739,8 @@ function previewPlan(opts, plan) {
739
739
  return 0;
740
740
  }
741
741
  async function dispatch(opts) {
742
- if (opts.verb !== "update") {
743
- throw new Error(`dispatch handles --update only; --${opts.verb} routes through analyse()`);
742
+ if (opts.verb !== "init") {
743
+ throw new Error(`dispatch handles --init only; --${opts.verb} routes through analyse()`);
744
744
  }
745
745
  const adapter = resolve(opts.target, opts.flavour, opts.runner);
746
746
  const flavour = resolveFlavour(opts);
@@ -765,6 +765,7 @@ export {
765
765
  resolve,
766
766
  BaseAdapter,
767
767
  palette,
768
+ readOwnPackage,
768
769
  readOwnVersion,
769
770
  readProjectPackageJson,
770
771
  readNxProjectName,
@@ -774,4 +775,4 @@ export {
774
775
  detectFramework,
775
776
  dispatch
776
777
  };
777
- //# sourceMappingURL=chunk-6N2CLPWE.js.map
778
+ //# sourceMappingURL=chunk-6ZTM3PCI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/registry.ts","../src/core/base-adapter.ts","../src/shared/color.ts","../src/shared/package-json.ts","../src/roles/typescript/adapters/tsc/tsc.adapter.ts","../src/shared/jsonc.ts","../src/shared/resolve-bin.ts","../src/roles/typescript/config-policy.ts","../src/roles/typescript/phased-rules.ts","../src/roles/typescript/presets.ts","../src/roles/typescript/read-adoption.ts","../src/roles/typescript/resolve-tsconfig-target.ts","../src/roles/typescript/register.ts","../src/adapters.ts","../src/core/detect-framework.ts","../src/shared/text.ts","../src/core/apply-plan.ts","../src/shared/deep-merge.ts","../src/core/dispatch.ts"],"sourcesContent":["/**\n * Adapter registry. Tool branches register their adapters here; the CLI resolves\n * an adapter by (target, flavour, runner). A default runner per target keeps the\n * common invocation tool-agnostic (`sentinel --lint`), while `--runner` overrides\n * it. Resolution is flavour-aware: candidates are filtered by `appliesTo(flavour)`,\n * so a React-only adapter is never picked for a Nest project, and two adapters can\n * share a (target, runner) if they specialise different flavours.\n */\nimport type { Flavour, Target } from './domain.js'\nimport type { Adapter } from './types.js'\n\nconst adapters: Adapter[] = []\n\n/** Default runner per target, so `--runner` stays optional. */\nconst defaultRunner: Partial<Record<Target, string>> = {\n // Filled in by tool branches, e.g. lint: 'eslint', typescript: 'tsc'.\n}\n\n/**\n * Register a tool adapter (called from each role's registration, wired into the\n * bootstrap in `src/adapters.ts`). No (target, runner) uniqueness guard here: two\n * adapters may share one for different flavours; a genuine clash (same target,\n * runner AND flavour) is caught at resolve time, where the flavour is known.\n */\nexport function register(adapter: Adapter): void {\n adapters.push(adapter)\n}\n\n/** Set the default runner for a target. */\nexport function setDefaultRunner(target: Target, runner: string): void {\n defaultRunner[target] = runner\n}\n\n/** All registered adapters (for `--all`, listing, and reports). */\nexport function all(): readonly Adapter[] {\n return adapters\n}\n\n/**\n * The targets that actually have an adapter wired today (distinct, in registration\n * order). The source of truth for \"what can sentinel really do right now\" — used to\n * flag a requested-but-unwired target and to show availability in `--help`, instead\n * of a hardcoded list that pretends every target is ready.\n */\nexport function availableTargets(): Target[] {\n return [...new Set(adapters.map((a) => a.target))]\n}\n\n/**\n * Resolve one adapter by target, honouring an explicit `--runner` or the target's\n * default. When a `flavour` is known it also filters by `appliesTo`, so a\n * React-only adapter is never picked for Nest; when it is absent (sentinel does\n * not detect it), every adapter for the target is a candidate. Throws with an\n * actionable message for each failure mode: no adapter for the target (yet), none\n * that handles the flavour, an ambiguous choice, an unknown runner, or two\n * adapters claiming the same (target, runner, flavour).\n */\nexport function resolve(target: Target, flavour?: Flavour, runner?: string): Adapter {\n const forTarget = adapters.filter((a) => a.target === target)\n if (forTarget.length === 0) {\n throw new Error(\n `No adapter registered for target \"${target}\" yet (it ships in a later ticket).`,\n )\n }\n\n const candidates = flavour ? forTarget.filter((a) => a.appliesTo(flavour)) : forTarget\n if (candidates.length === 0) {\n throw new Error(`No adapter for target \"${target}\" handles flavour \"${flavour}\".`)\n }\n\n const wanted = runner ?? defaultRunner[target]\n const available = candidates.map((a) => a.runner).join(', ')\n\n // No runner asked for and no default: only unambiguous when there is exactly one.\n if (!wanted) {\n const [first, ...rest] = candidates\n if (first && rest.length === 0) return first\n throw new Error(\n `Multiple runners for target \"${target}\" (${available}); pass --runner or set a default.`,\n )\n }\n\n const matching = candidates.filter((a) => a.runner === wanted)\n if (matching.length === 0) {\n throw new Error(\n `No runner \"${wanted}\" for target \"${target}\" (flavour \"${flavour}\"). Available: ${available}.`,\n )\n }\n if (matching.length > 1) {\n throw new Error(\n `Ambiguous: ${matching.length} adapters claim target \"${target}\", runner \"${wanted}\", flavour \"${flavour}\".`,\n )\n }\n return matching[0] as Adapter\n}\n","/**\n * `BaseAdapter`: an optional convenience base class for adapters. This is the\n * runtime part of the contract (the types live in `types.ts`); it provides\n * default `inspect`/`report` that throw \"not implemented yet\", so an adapter can\n * extend it and implement only what it needs.\n */\nimport type { Flavour, Target } from './domain.js'\nimport type {\n Adapter,\n AdapterResult,\n RunContext,\n StatusReport,\n UpdateContext,\n UpdatePlan,\n} from './types.js'\n\nexport abstract class BaseAdapter implements Adapter {\n abstract readonly target: Target\n abstract readonly runner: string\n abstract appliesTo(flavour: Flavour): boolean\n abstract plan(context: UpdateContext): UpdatePlan | Promise<UpdatePlan>\n abstract run(ctx: RunContext): Promise<AdapterResult>\n\n inspect(_ctx: RunContext): Promise<unknown> {\n throw new Error(`${this.runner}: --inspect not implemented yet`)\n }\n\n report(_ctx: RunContext): Promise<AdapterResult> {\n throw new Error(`${this.runner}: --report not implemented yet`)\n }\n\n status(_ctx: RunContext): Promise<StatusReport> {\n throw new Error(`${this.runner}: --status not implemented yet`)\n }\n}\n","/**\n * Terminal color, on top of Node's built-in `util.styleText` (no dependency).\n *\n * Passing `{ stream }` lets styleText itself decide whether to emit escape codes: it\n * returns the text UNCHANGED when the stream is not a TTY, when `NO_COLOR` is set, or\n * when the terminal has no color support. So the same code stays readable on a terminal\n * and clean in pipes, files, and CI logs, we never hand-roll a TTY check.\n */\nimport { styleText } from 'node:util'\n\ntype Format = Parameters<typeof styleText>[0]\n\n/** The semantic styles, each a `string -> string` painter. */\nexport interface Palette {\n /** A passing result. */ ok: (text: string) => string\n /** A failing result. */ fail: (text: string) => string\n /** A caution / deferred / deprecation notice. */ warn: (text: string) => string\n /** Emphasis (a name, a heading). */ strong: (text: string) => string\n /** Secondary, low-salience text (keys, timings, scope). */ dim: (text: string) => string\n /** A structural marker (a run header). */ accent: (text: string) => string\n}\n\n/**\n * Bind the semantic styles to ONE stream: `palette(process.stdout)` for the result\n * summary, `palette(process.stderr)` for notices/progress, so each is colored only when\n * that specific stream supports it.\n */\nexport function palette(stream: NodeJS.WriteStream): Palette {\n const paint = (format: Format, text: string): string => styleText(format, text, { stream })\n return {\n ok: (text) => paint(['green', 'bold'], text),\n fail: (text) => paint(['red', 'bold'], text),\n warn: (text) => paint('yellow', text),\n strong: (text) => paint('bold', text),\n dim: (text) => paint('dim', text),\n accent: (text) => paint('cyan', text),\n }\n}\n\n/**\n * An identity palette (no escapes), for pure rendering in tests and for any caller that\n * wants the plain text regardless of the stream.\n */\nexport const plainPalette: Palette = {\n ok: (text) => text,\n fail: (text) => text,\n warn: (text) => text,\n strong: (text) => text,\n dim: (text) => text,\n accent: (text) => text,\n}\n","/**\n * package.json helpers, shared by the CLI and the engine.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * sentinel's OWN version, for `--version`. We must NOT use\n * `process.env.npm_package_version`, that is the version of whatever script\n * invoked us (an app's package.json, or nothing). Instead walk up from this\n * module to the nearest package.json, which is always sentinel's own, whether\n * running from source or the bundled dist.\n */\n/**\n * sentinel's OWN name + version, read by walking up from this module to the nearest\n * package.json. Used both for `--version` and to pin the exact dependency `--init`\n * writes into an adopted module, so a module always references the version that adopted it.\n */\nexport function readOwnPackage(): { name: string; version: string } {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string; version?: string }\n if (typeof pkg.version === 'string') {\n return { name: pkg.name ?? '@hublo/sentinel', version: pkg.version }\n }\n } catch {\n // Malformed package.json; keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return { name: '@hublo/sentinel', version: '0.0.0' } // filesystem root\n dir = parent\n }\n}\n\nexport function readOwnVersion(): string {\n return readOwnPackage().version\n}\n\n/**\n * Read a module's package.json for framework detection; tolerant of a missing or\n * malformed file (returns `{}`, so detection falls through to the `node` default).\n */\nexport function readProjectPackageJson(dir: string): {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n} {\n const path = join(dir, 'package.json')\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Record<string, never>\n } catch {\n process.stderr.write(`sentinel: could not parse ${path}; ignoring for detection.\\n`)\n return {}\n }\n}\n\n/**\n * The nx project name for a module, read from its `project.json`, or undefined if\n * there is none/unreadable. Used when `--init` has to scaffold a `package.json`\n * for a module that only has a `project.json` (common for nx apps/services): the\n * scaffolded file borrows this name so it is a valid, uniquely-named workspace\n * package. Reusable by any tool that scaffolds, not just TypeScript.\n */\nexport function readNxProjectName(dir: string): string | undefined {\n const path = join(dir, 'project.json')\n if (!existsSync(path)) return undefined\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as { name?: unknown }\n return typeof parsed.name === 'string' ? parsed.name : undefined\n } catch {\n return undefined\n }\n}\n","/**\n * TypeScript adapter (runner: `tsc`). Maps the verbs onto tsc for one module:\n * - plan (--init) → thin conformant tsconfig stub extending the preset\n * - run (--run) → `tsc -b` (build mode), the module's own tsc\n * - inspect (--inspect) → the module's resolved config (preset, target, flavour)\n * - report (--report) → type-error + implicit-any counts (the conformance signal)\n * - status (--status) → adoption + conformity, read from the committed config\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { BaseAdapter } from '../../../../core/base-adapter.js'\nimport type { Flavour, Target } from '../../../../core/domain.js'\nimport { palette } from '../../../../shared/color.js'\nimport type {\n AdapterResult,\n FileOperation,\n RunContext,\n StatusReport,\n UpdateContext,\n UpdatePlan,\n} from '../../../../core/types.js'\nimport { parseJsonc } from '../../../../shared/jsonc.js'\nimport { readNxProjectName, readOwnPackage } from '../../../../shared/package-json.js'\nimport { resolveBin } from '../../../../shared/resolve-bin.js'\nimport { presetOwnedKeys } from '../../config-policy.js'\nimport { DEFERRED_RULES, deferredRuleNames } from '../../phased-rules.js'\nimport { hasShippedPreset } from '../../presets.js'\nimport { readTsconfigAdoption } from '../../read-adoption.js'\nimport { resolveTsconfigTarget } from '../../resolve-tsconfig-target.js'\n\nconst TYPECHECK_SCRIPT = { typecheck: 'sentinel --run --typescript' }\n\n/**\n * `--init` writes the exact-version `@hublo/sentinel` devDependency into the module (so\n * the `extends` and the `typecheck` script resolve). The one remaining step is to fetch it,\n * so the plan says to run `pnpm install`, and adoption stays a one-command, zero-manual move.\n */\nconst INSTALL_NOTE =\n 'run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve'\n\n/** Default cap on embedded diagnostics per module (the full count stays in `errors`). */\nconst DEFAULT_MAX_DIAGNOSTICS = 100\n\n/** One structured tsc diagnostic, parsed from `tsc -b` output. */\nexport interface TscDiagnostic {\n file: string\n line: number\n col: number\n /** The TS error code, e.g. `TS7006`. */\n code: string\n message: string\n}\n\n/** `path/to/file.ts(12,5): error TS7006: Parameter 'x' implicitly has an 'any' type.` */\nconst DIAGNOSTIC_RE = /^(.+?)\\((\\d+),(\\d+)\\): error (TS\\d+): (.+)$/\n\n/**\n * Parse structured diagnostics out of `tsc` output so `--report` carries the detail (file,\n * line, code, message), not just a count. Location-less errors (e.g. a config-level\n * TS18003) do not match and are left to the `errors` count, which stays the authoritative\n * total, so a report never quietly loses one.\n */\nexport function parseDiagnostics(output: string): TscDiagnostic[] {\n const diagnostics: TscDiagnostic[] = []\n for (const raw of output.split('\\n')) {\n const m = DIAGNOSTIC_RE.exec(raw.trim())\n if (m) {\n diagnostics.push({\n file: m[1] as string,\n line: Number(m[2]),\n col: Number(m[3]),\n code: m[4] as string,\n message: m[5] as string,\n })\n }\n }\n return diagnostics\n}\n\n/**\n * Phase-1 notice, generated from the single source of truth (`DEFERRED_RULES`). First\n * adoption is a NON-BREAKING lateral move; the deferred rules are enabled centrally in\n * a later wave. Surfaced on every run so the reduced strictness is visible in logs,\n * never a silent gap; `--inspect` lists the full set.\n */\nconst PHASED_STRICTNESS_WARNING =\n `sentinel typescript: phase 1 (non-breaking) — deferred: ${deferredRuleNames().join(', ')}` +\n '. Enabled centrally in a later wave; run `sentinel --inspect --typescript` for the list.'\n\n/**\n * Append `preset` to a tsconfig's `extends`, as a TS 5.0 array, WITHOUT dropping\n * what is already there (the base config the module extends). Accepts the current\n * `extends` as a string, an array, or absent; returns the composed chain with the\n * preset last (so its options win) and never duplicated, so a re-run is a no-op.\n */\nfunction composeExtends(current: unknown, preset: string): string[] {\n const chain =\n typeof current === 'string'\n ? [current]\n : Array.isArray(current)\n ? current.filter((entry): entry is string => typeof entry === 'string')\n : []\n return chain.includes(preset) ? chain : [...chain, preset]\n}\n\nexport class TscAdapter extends BaseAdapter {\n readonly target: Target = 'typescript'\n readonly runner = 'tsc'\n\n /**\n * The tsc adapter drives type-checking for any flavour: `--run`/`--report`/\n * `--inspect` just execute tsc against the module's existing config, which is\n * meaningful regardless of flavour. `--init` is the exception, it only WRITES a\n * preset for flavours that ship one (gated inside `plan`), so svelte is not\n * clobbered with a non-existent preset.\n */\n appliesTo(_flavour: Flavour): boolean {\n return true\n }\n\n /**\n * Plan `--init`: make the module extend the sentinel preset with a THIN,\n * conformant stub, and route type-checking through the CLI. The engine applies\n * the ops; ensuring the `@hublo/sentinel` dependency is an adoption step\n * (`pnpm add`), not a file write.\n *\n * Per resolved case:\n * - extends-base: append the preset to the `extends` chain (keep the base for\n * the monorepo's paths/structure) + strip preset-owned `compilerOptions`\n * (drift), keeping the project's own paths/include (the allowlist).\n * - none: create a fresh thin `tsconfig.json`.\n * - other-chain (svelte): skip, its config extends a different base.\n */\n plan(context: UpdateContext): UpdatePlan {\n // Only write a preset for a flavour that actually ships one. A declared-but-\n // unshipped flavour (e.g. svelte) is skipped, never pointed at a preset that\n // does not exist, which would break the module's typecheck.\n if (!hasShippedPreset(context.flavour)) {\n return {\n operations: [],\n notes: [`skipped: no TypeScript preset for flavour \"${context.flavour}\" yet`],\n }\n }\n const target = resolveTsconfigTarget(context.cwd)\n const preset = `@hublo/sentinel/tsconfig/${context.flavour}`\n const addScript = this.packageJsonOperation(context.cwd)\n\n if (target.reason === 'other-chain') {\n return {\n operations: [],\n notes: [`skipped: ${target.path} extends a non-base config; handled separately`],\n }\n }\n\n if (target.reason === 'none') {\n const contents = JSON.stringify({ extends: preset, include: ['src'] }, null, 2) + '\\n'\n return {\n operations: [{ kind: 'write', path: target.path, contents }, addScript],\n notes: [`created ${target.path} (no tsconfig found)`, INSTALL_NOTE],\n }\n }\n\n // extends-base: KEEP the base (it carries the monorepo's paths/types/structure,\n // not just tooling) and APPEND the preset to the extends chain (TS 5.0 array\n // extends). Later wins, so the preset's tooling overrides the base while the\n // base's config survives. Then strip the module's own preset-owned options so the\n // preset wins over stale local copies.\n const existing = parseJsonc<{ extends?: unknown; compilerOptions?: Record<string, unknown> }>(\n readFileSync(join(context.cwd, target.path), 'utf8'),\n target.path,\n )\n const extendsChain = composeExtends(existing.extends, preset)\n const drift = presetOwnedKeys(existing.compilerOptions)\n const operations: FileOperation[] = [\n { kind: 'merge-json', path: target.path, value: { extends: extendsChain } },\n ]\n const notes: string[] = []\n if (drift.length > 0) {\n operations.push({\n kind: 'remove-json-keys',\n path: target.path,\n keys: drift.map((key) => ['compilerOptions', key]),\n })\n notes.push(`stripped preset-owned compilerOptions: ${drift.join(', ')}`)\n }\n operations.push(addScript)\n notes.push(INSTALL_NOTE)\n return { operations, notes }\n }\n\n /**\n * The op that routes type-checking through the CLI. If the module already has a\n * `package.json`, merge the script in and leave the rest untouched. If it does\n * NOT (common for nx apps/services that carry only a `project.json`), scaffold a\n * minimal, workspace-valid one, its nx name + `private: true`, so pnpm accepts it\n * and it can then receive the `@hublo/sentinel` devDep (added via `pnpm add` at\n * adoption, never written here, so the lockfile stays authoritative).\n */\n private packageJsonOperation(cwd: string): FileOperation {\n // Write the exact-version devDependency too, so adoption needs no manual `pnpm add`:\n // `--init` then `pnpm install` is enough. `merge-json` preserves everything else and\n // makes a re-run idempotent (same version -> no change).\n const own = readOwnPackage()\n const devDependencies = { [own.name]: own.version }\n if (existsSync(join(cwd, 'package.json'))) {\n return {\n kind: 'merge-json',\n path: 'package.json',\n value: { scripts: TYPECHECK_SCRIPT, devDependencies },\n }\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return {\n kind: 'merge-json',\n path: 'package.json',\n value: { name, private: true, scripts: TYPECHECK_SCRIPT, devDependencies },\n }\n }\n\n /**\n * Type-check the module with `tsc -b` (build mode) on its solution config, the\n * way the monorepo itself does. Build mode walks the config's `references`, so a\n * references-only solution (Pattern A: app + spec) is actually checked instead of\n * passing vacuously; it also only caches SUCCESSFUL builds, so errors are always\n * re-reported. Uses the module's own tsc. Nothing to check is a pass.\n */\n async run(ctx: RunContext): Promise<AdapterResult> {\n const config = this.typecheckTarget(ctx.cwd)\n if (!config) {\n process.stderr.write('sentinel typescript(tsc): no tsconfig to check\\n')\n return { ok: true, code: 0 }\n }\n process.stderr.write(`${palette(process.stderr).warn(PHASED_STRICTNESS_WARNING)}\\n`)\n const tsc = resolveBin(ctx.cwd, 'tsc') ?? 'tsc'\n const result = spawnSync(tsc, ['-b', config], { cwd: ctx.cwd, stdio: 'inherit' })\n if (result.error) {\n process.stderr.write(\n `sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?\\n`,\n )\n return { ok: false, code: 1 }\n }\n const code = result.status ?? 1\n return { ok: code === 0, code }\n }\n\n /**\n * The config to type-check with `tsc -b`. Prefer the module's root `tsconfig.json`\n * (the solution the monorepo builds; `tsc -b` follows its `references` to cover\n * app + spec), else the base-extending file, else null when there is nothing to\n * check.\n */\n private typecheckTarget(cwd: string): string | null {\n if (existsSync(join(cwd, 'tsconfig.json'))) return 'tsconfig.json'\n const target = resolveTsconfigTarget(cwd)\n return target.reason === 'none' ? null : target.path\n }\n\n /**\n * The module's resolved TypeScript config: which preset, which file, how, and the\n * phased-strictness state (`deferred` rules that are off in phase 1). This is the\n * \"list what's deferred\" query, `sentinel --inspect --typescript`.\n */\n async inspect(ctx: RunContext): Promise<unknown> {\n const target = resolveTsconfigTarget(ctx.cwd)\n // The preset is READ from the module's committed `extends` chain, not derived from\n // the (possibly hoisted-fallback) flavour: a nest module reports `.../nest`, not\n // `.../node`, so `--inspect` reflects what the file actually uses.\n const { preset, adopted } = readTsconfigAdoption(ctx.cwd)\n return {\n module: ctx.module,\n target: 'typescript',\n flavour: ctx.flavour,\n configFile: target.path,\n configState: target.reason,\n preset,\n adopted,\n phase: 1,\n deferred: DEFERRED_RULES,\n }\n }\n\n /**\n * Adoption + conformity from the committed tsconfig, for `--status`. No tsc run and\n * no flavour guessing: reads the actual `extends` chain, so a workspace-wide scan is\n * a cheap coverage + drift dashboard (adopted? which preset? drifted?).\n */\n async status(ctx: RunContext): Promise<StatusReport> {\n const { adopted, preset, conformant, drift } = readTsconfigAdoption(ctx.cwd)\n return { adopted, preset, conformant, drift }\n }\n\n /**\n * Report conformance for the module: `tsc -b` (build mode, so app + spec are\n * covered) and count total type errors plus the implicit-`any` family (TS70xx:\n * 7006/7031/7053/… ), the signal that drives the noImplicitAny migration. No\n * tsconfig is a clean, empty report.\n */\n async report(ctx: RunContext): Promise<AdapterResult> {\n const config = this.typecheckTarget(ctx.cwd)\n if (!config) {\n return { ok: true, code: 0, metrics: { errors: 0, implicitAny: 'deferred', diagnostics: [] } }\n }\n const tsc = resolveBin(ctx.cwd, 'tsc') ?? 'tsc'\n const result = spawnSync(tsc, ['-b', config], { cwd: ctx.cwd, encoding: 'utf8' })\n if (result.error) {\n process.stderr.write(\n `sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?\\n`,\n )\n return { ok: false, code: 1, metrics: { error: 'tsc not available' } }\n }\n const output = `${result.stdout ?? ''}${result.stderr ?? ''}`\n const errors = (output.match(/error TS\\d+/g) ?? []).length\n // Embed the structured diagnostics so `--report --json` carries the detail (no need to\n // re-run `--run`). Cap to keep the envelope bounded; `errors` remains the full count and\n // `diagnosticsTruncated` flags when the cap dropped some, so nothing is hidden silently.\n const parsed = parseDiagnostics(output)\n const cap =\n ctx.maxDiagnostics === 0 ? Infinity : (ctx.maxDiagnostics ?? DEFAULT_MAX_DIAGNOSTICS)\n const diagnostics = Number.isFinite(cap) ? parsed.slice(0, cap) : parsed\n return {\n ok: errors === 0,\n code: result.status ?? 0,\n metrics: {\n errors,\n implicitAny: this.implicitAnyMetric(ctx.cwd, tsc, config, output),\n diagnostics,\n diagnosticsTruncated: diagnostics.length < parsed.length,\n },\n }\n }\n\n /**\n * The `implicitAny` report metric, honestly. Implicit-`any` violations (TS70xx) are\n * only *visible* to tsc when `noImplicitAny` is ON. In phase 1 the rule is DEFERRED\n * (off), so counting TS70xx from the committed-config run is structurally always 0 —\n * a misleading \"no implicit-any\" when the rule simply was not applied. So: report the\n * real count only when the rule is on, otherwise `'deferred'` (never a fake `0`). The\n * remaining debt while deferred is a separate, opt-in probe (a later `--migration`).\n */\n private implicitAnyMetric(\n cwd: string,\n tsc: string,\n config: string,\n mainOutput: string,\n ): number | 'deferred' {\n return this.noImplicitAnyEnabled(cwd, tsc, config, mainOutput)\n ? (mainOutput.match(/error TS70\\d\\d/g) ?? []).length\n : 'deferred'\n }\n\n /**\n * Whether `noImplicitAny` is effectively ON in the module's resolved config. Read from\n * `tsc --showConfig` (an explicit value wins; otherwise `strict` implies it). If\n * `--showConfig` is unavailable, fall back to the run's own evidence: implicit-`any`\n * errors in the output mean the rule must be on.\n */\n private noImplicitAnyEnabled(\n cwd: string,\n tsc: string,\n config: string,\n mainOutput: string,\n ): boolean {\n const shown = spawnSync(tsc, ['-p', config, '--showConfig'], { cwd, encoding: 'utf8' })\n if (shown.status === 0 && shown.stdout) {\n try {\n const co = parseJsonc<{ compilerOptions?: { noImplicitAny?: boolean; strict?: boolean } }>(\n shown.stdout,\n 'tsconfig(--showConfig)',\n ).compilerOptions\n if (co) return co.noImplicitAny ?? co.strict === true\n } catch {\n // malformed --showConfig output: fall through to the evidence-based check\n }\n }\n return /error TS70\\d\\d/.test(mainOutput)\n }\n}\n","/**\n * JSONC (JSON with comments + trailing commas) helpers. tsconfig files are JSONC,\n * so reading them with plain `JSON.parse` throws on real projects (e.g.\n * host-admin's tsconfig.app.json has comments). Backed by jsonc-parser (the VS\n * Code library), which also underpins content-preserving edits (added with the\n * `--init` merge).\n */\nimport { parse, printParseErrorCode, type ParseError } from 'jsonc-parser'\n\n/**\n * Parse JSONC text into a value. Throws with a clear message listing the parse\n * errors, so a malformed config fails loudly rather than silently mis-reading.\n */\nexport function parseJsonc<T = unknown>(text: string, source = 'config'): T {\n const errors: ParseError[] = []\n const value = parse(text, errors, { allowTrailingComma: true }) as T\n if (errors.length > 0) {\n const details = errors.map((error) => printParseErrorCode(error.error)).join(', ')\n throw new Error(`${source}: malformed JSONC (${details}).`)\n }\n return value\n}\n","/**\n * Find a tool binary the way node/npm would: walk up from a directory looking for\n * `node_modules/.bin/<name>`. Used so `--run` invokes the MODULE's own tool version\n * (its `tsc`), not sentinel's. Returns undefined if not found (caller falls back to\n * the name on PATH).\n */\nimport { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport function resolveBin(fromDir: string, name: string): string | undefined {\n let dir = fromDir\n for (;;) {\n const candidate = join(dir, 'node_modules', '.bin', name)\n if (existsSync(candidate)) return candidate\n const parent = dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n","/**\n * The allowlist: which tsconfig `compilerOptions` a module may keep locally. Only\n * genuinely project-specific settings, everything else is owned by the sentinel\n * preset and stripped by `--init`, so every migrated module is conformant from\n * the start. A sanctioned exception would be added here (visible + reviewed).\n */\nexport const PERMITTED_COMPILER_OPTIONS: readonly string[] = [\n 'paths',\n 'baseUrl',\n 'rootDir',\n 'outDir',\n 'tsBuildInfoFile',\n]\n\n/**\n * The `compilerOptions` keys the preset owns: present in the project but not in the\n * allowlist. These are the drift `--init` strips.\n */\nexport function presetOwnedKeys(compilerOptions: Record<string, unknown> | undefined): string[] {\n if (!compilerOptions) return []\n return Object.keys(compilerOptions).filter((key) => !PERMITTED_COMPILER_OPTIONS.includes(key))\n}\n","/**\n * Phased TypeScript strictness, the single source of truth.\n *\n * These are the rules sentinel DEFERS on first adoption so it stays non-breaking, then\n * enables centrally in a later wave. The preset deliberately does NOT set them (so a\n * repo base that relaxes a rule keeps it off), they are announced by the run/report\n * warning, and reported by `--inspect` so a developer, or a CI conformance check, can\n * list exactly what is deferred. Each `rule` here MUST stay absent from every preset\n * (asserted in the preset tests), so this list and the presets can never drift.\n *\n * The paired other half lives in the consuming repo: the base config marks the matching\n * relaxations `@deprecated` (the levers to remove). deferred (here, coming) ⇄\n * @deprecated (there, going).\n */\nexport interface PhasedRule {\n /** The tsconfig compilerOption that is deferred. */\n rule: string\n /** The migration wave that will enable it. */\n phase: number\n /** Why it is deferred (what enabling it will surface). */\n reason: string\n}\n\nexport const DEFERRED_RULES: readonly PhasedRule[] = [\n { rule: 'noImplicitAny', phase: 2, reason: 'the implicit-any migration (TS70xx)' },\n { rule: 'noUnusedLocals', phase: 2, reason: 'unused-local cleanup (TS6133)' },\n { rule: 'noUnusedParameters', phase: 2, reason: 'unused-parameter cleanup (TS6133)' },\n] as const\n\n/** The deferred rule names, e.g. for a one-line warning. */\nexport function deferredRuleNames(): string[] {\n return DEFERRED_RULES.map((entry) => entry.rule)\n}\n","/**\n * The flavours whose TypeScript preset actually ships: one `flavours/<flavour>.ts`\n * source, flattened to `dist/tsconfig/<flavour>.json` by `scripts/build-presets.ts`.\n *\n * Single source of truth, shared by the build script and the adapter's `appliesTo`,\n * so a flavour is only ever offered when its preset exists. A declared flavour with\n * no preset yet (e.g. `svelte`) is deliberately absent: `--init` skips it rather\n * than writing an `extends` to a module that does not exist. Adding a preset is one\n * new file here plus its entry in this list.\n */\nimport type { Flavour } from '../../core/domain.js'\n\nexport const SHIPPED_FLAVOURS = ['react', 'nest', 'node'] as const satisfies readonly Flavour[]\n\n/** Whether a flavour's TypeScript preset is available. */\nexport function hasShippedPreset(flavour: Flavour): boolean {\n return (SHIPPED_FLAVOURS as readonly Flavour[]).includes(flavour)\n}\n","/**\n * Read a module's ADOPTION + CONFORMITY of the TypeScript preset, straight from its\n * committed `tsconfig`. This is the truth `--status` reports, and it is deliberately\n * DETECTION-FREE: it reads the actual `extends` chain rather than guessing a flavour,\n * so a hoisted-deps monorepo (where detection falls back to `node`) can't corrupt it.\n *\n * - adopted = the resolved tsconfig extends a `@hublo/sentinel/tsconfig/*` preset\n * - preset = that exact preset string (or null), the real one, not a derived guess\n * - conformant = adopted AND no preset-owned `compilerOptions` drift (i.e. a re-`update`\n * would strip nothing), the same drift the update guard manages\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { parseJsonc } from '../../shared/jsonc.js'\nimport { presetOwnedKeys } from './config-policy.js'\nimport { resolveTsconfigTarget } from './resolve-tsconfig-target.js'\n\n/** Matches a sentinel tsconfig preset subpath, e.g. `@hublo/sentinel/tsconfig/nest`. */\nconst SENTINEL_PRESET = /^@hublo\\/sentinel\\/tsconfig\\/[a-z-]+$/\n\nexport interface TsconfigAdoption {\n /** The tsconfig inspected (relative to the module), or null when there is none. */\n configFile: string | null\n /** The sentinel preset the module extends, or null when it extends none. */\n preset: string | null\n /** True when the module extends a sentinel preset. */\n adopted: boolean\n /** True when adopted AND drift-free (a re-`update` would change nothing). */\n conformant: boolean\n /** Preset-owned `compilerOptions` kept locally (the drift); empty when conformant. */\n drift: string[]\n}\n\n/** `extends` as a list: a string, a TS 5.0 array, or absent, all normalised. */\nfunction normaliseExtends(value: unknown): string[] {\n if (typeof value === 'string') return [value]\n if (Array.isArray(value))\n return value.filter((entry): entry is string => typeof entry === 'string')\n return []\n}\n\nconst NOT_ADOPTED = (configFile: string | null): TsconfigAdoption => ({\n configFile,\n preset: null,\n adopted: false,\n conformant: false,\n drift: [],\n})\n\n/** Read a module's TypeScript adoption + conformity from its committed tsconfig. */\nexport function readTsconfigAdoption(cwd: string): TsconfigAdoption {\n const target = resolveTsconfigTarget(cwd)\n if (target.reason === 'none' || !existsSync(join(cwd, target.path))) {\n return NOT_ADOPTED(target.reason === 'none' ? null : target.path)\n }\n\n let parsed: { extends?: unknown; compilerOptions?: Record<string, unknown> }\n try {\n parsed = parseJsonc(readFileSync(join(cwd, target.path), 'utf8'), target.path)\n } catch {\n return NOT_ADOPTED(target.path) // malformed config: can't be conformant\n }\n\n const preset =\n normaliseExtends(parsed.extends).find((entry) => SENTINEL_PRESET.test(entry)) ?? null\n if (preset === null) return NOT_ADOPTED(target.path)\n\n const drift = presetOwnedKeys(parsed.compilerOptions)\n return { configFile: target.path, preset, adopted: true, conformant: drift.length === 0, drift }\n}\n","/**\n * Resolve which tsconfig file `--init --typescript` should write in a module.\n *\n * The rule (from the monorepo audit): target the file that currently `extends` the\n * shared base config, that is the entry point sentinel's preset replaces. Two\n * shapes exist:\n * - Pattern A (libs, nest services): `tsconfig.json` extends the base.\n * - Pattern B (Vite React apps): `tsconfig.json` is references-only and\n * `tsconfig.app.json` extends the base.\n * We check `tsconfig.app.json` before `tsconfig.json` so B is found first. The\n * `reason` distinguishes the three cases `--init` must handle differently:\n * - `extends-base`: found the file to migrate.\n * - `other-chain`: a tsconfig exists but extends something else (svelte's own\n * `.svelte-kit` chain), don't clobber it; handled separately.\n * - `none`: no tsconfig at all; `--init` creates one.\n *\n * The \"find the file that extends a shared root\" pattern is reusable; it will be\n * lifted to core when a second tool needs its own version.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { parseJsonc } from '../../shared/jsonc.js'\n\n/**\n * A file is the target if its `extends` references either the monorepo base (still\n * to migrate) or a sentinel preset (already migrated, so re-`update` re-strips any\n * new drift, keeping it idempotent and self-cleaning).\n */\nconst TARGET_EXTENDS_MARKERS = ['tsconfig.base.json', '@hublo/sentinel/tsconfig/'] as const\n\n/** Candidate entry points, most-specific first (Pattern B before Pattern A). */\nconst CANDIDATES = ['tsconfig.app.json', 'tsconfig.json'] as const\n\nexport interface TsconfigTarget {\n /** The tsconfig file to write, relative to the module root. */\n path: string\n /** How it was chosen, for observability and the per-case `--init` behaviour. */\n reason: 'extends-base' | 'other-chain' | 'none'\n}\n\n/**\n * The `extends` targets of a tsconfig as a list. `extends` may be a string or, since\n * TypeScript 5.0, an array of strings; both are normalised here (absent/unreadable\n * or non-string entries yield an empty list).\n */\nfunction readExtends(absolutePath: string): string[] {\n let parsed: { extends?: unknown }\n try {\n parsed = parseJsonc(readFileSync(absolutePath, 'utf8'), absolutePath)\n } catch {\n return [] // malformed candidate: skip it, try the next\n }\n if (typeof parsed.extends === 'string') return [parsed.extends]\n if (Array.isArray(parsed.extends)) {\n return parsed.extends.filter((entry): entry is string => typeof entry === 'string')\n }\n return []\n}\n\nexport function resolveTsconfigTarget(moduleDir: string): TsconfigTarget {\n let existing: string | undefined\n for (const candidate of CANDIDATES) {\n const absolutePath = join(moduleDir, candidate)\n if (!existsSync(absolutePath)) continue\n existing ??= candidate // remember the first tsconfig we saw\n const extendsValues = readExtends(absolutePath)\n const extendsBase = extendsValues.some((value) =>\n TARGET_EXTENDS_MARKERS.some((marker) => value.includes(marker)),\n )\n if (extendsBase) {\n return { path: candidate, reason: 'extends-base' }\n }\n }\n if (existing) return { path: existing, reason: 'other-chain' }\n return { path: 'tsconfig.json', reason: 'none' }\n}\n","/**\n * TypeScript role registration. The single entry the bootstrap\n * (`src/adapters.ts`) imports, so wiring stays greppable and the CLI never\n * changes. Adds the tsc adapter and makes it the default runner for `--typescript`.\n */\nimport { register, setDefaultRunner } from '../../core/registry.js'\nimport { TscAdapter } from './adapters/tsc/tsc.adapter.js'\n\nexport function registerTypescript(): void {\n register(new TscAdapter())\n setDefaultRunner('typescript', 'tsc')\n}\n","/**\n * Adapter bootstrap: the single place adapters are wired into the CLI.\n *\n * A `register(new MyAdapter())` call only runs if its module is imported, and the\n * CLI must not import every tool by hand, that would make the promise \"one tool =\n * one adapter, the CLI never changes\" false. So the CLI calls `registerAdapters()`\n * once at startup, and each tool ticket adds exactly ONE line here (its role's\n * registration), never touching the CLI entry point (`bin/sentinel.ts`) or the\n * registry.\n *\n * Empty until the first tool ticket lands. A tool ticket adds, e.g. (an explicit\n * module path, not a barrel, so the wiring stays greppable):\n *\n * import { registerTypescript } from './roles/typescript/register.js'\n * export function registerAdapters(): void {\n * registerTypescript()\n * }\n */\nimport { registerTypescript } from './roles/typescript/register.js'\n\nexport function registerAdapters(): void {\n registerTypescript()\n}\n","/**\n * Framework detection from a module's package.json dependencies. DETERMINISTIC:\n * a dependency maps to exactly one flavour, and a module with no framework\n * dependency is a plain TypeScript library (`node`). This is not the old silent\n * guessing (there is no \"assume react\" fallback); `node` is a real preset.\n *\n * Reusable across tools: every tool's `--init` needs the module's flavour to\n * pick its preset, so this lives in core, not in the TypeScript role.\n */\nimport type { Flavour } from './domain.js'\n\n/** The slice of a package.json we read for detection. */\nexport interface PackageDependencies {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n}\n\n/**\n * A framework signal in priority order: the first whose package is present wins.\n * Ordered most-specific-first so a backend (`@nestjs/core`) is never shadowed by a\n * transitive `react`; `react` before `svelte` (svelte is being removed). Adding a\n * framework is one entry here.\n */\nconst FRAMEWORK_SIGNALS: ReadonlyArray<{ flavour: Flavour; dependency: string }> = [\n { flavour: 'nest', dependency: '@nestjs/core' },\n { flavour: 'react', dependency: 'react' },\n { flavour: 'svelte', dependency: 'svelte' },\n]\n\n/**\n * Backend frameworks that do NOT map to a flavour, but whose presence alongside a FRONTEND\n * flavour (react/svelte) means the module is mixed and the pick is probably wrong (the\n * tester's Sails + React root). They make detection ambiguous, not the choice. Deliberately\n * NOT including React meta-frameworks (e.g. `next`): a Next app legitimately has react +\n * next, and `react` is the correct pick, so it must not warn.\n */\nconst BACKEND_HINTS = ['sails', 'express', 'koa', 'fastify', '@hapi/hapi'] as const\n\n/** The detected flavour plus WHY, so a wrong silent guess is visible and can be flagged. */\nexport interface FrameworkDetection {\n flavour: Flavour\n /** The dependency that decided it, or a marker when none (node). */\n source: string\n /** True when the module carries signals for more than one stack: the pick is a guess. */\n ambiguous: boolean\n /** The other signals that make it ambiguous (extra flavour deps, or backend hints). */\n conflicts: string[]\n}\n\n/**\n * Detect the flavour AND explain it: the deciding dependency (`source`), and whether the\n * module is ambiguous (more than one flavour signal, or a frontend flavour next to a\n * backend framework). The CLI surfaces the source and warns on ambiguity, recommending\n * `--flavour`, instead of choosing silently.\n */\nexport function describeFramework(packageJson: PackageDependencies): FrameworkDetection {\n const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }\n const matched = FRAMEWORK_SIGNALS.filter((s) => s.dependency in dependencies)\n const chosen = matched[0]\n const flavour: Flavour = chosen?.flavour ?? 'node'\n const source = chosen?.dependency ?? '(no framework dependency)'\n\n const otherFlavourSignals = matched.slice(1).map((s) => s.dependency)\n const backendHints =\n flavour === 'react' || flavour === 'svelte'\n ? BACKEND_HINTS.filter((hint) => hint in dependencies)\n : []\n const conflicts = [...otherFlavourSignals, ...backendHints]\n return { flavour, source, ambiguous: conflicts.length > 0, conflicts }\n}\n\n/** The flavour for a module, from its dependencies. `node` when no framework. */\nexport function detectFramework(packageJson: PackageDependencies): Flavour {\n return describeFramework(packageJson).flavour\n}\n","/**\n * Small text helpers: `ensure-lines` for the engine, and a line diff for the\n * `--init --dry-run` preview.\n */\n\n/**\n * Append any of `lines` not already present in `current` (matched as a full,\n * trimmed line). Idempotent: running it twice adds nothing the second time.\n * Preserves a trailing newline and never duplicates existing lines.\n */\nexport function ensureLines(current: string, lines: string[]): string {\n const present = new Set(current.split('\\n').map((line) => line.trim()))\n const missing = lines.filter((line) => !present.has(line.trim()))\n if (missing.length === 0) return current\n const prefix = current.length === 0 || current.endsWith('\\n') ? current : current + '\\n'\n return prefix + missing.join('\\n') + '\\n'\n}\n\n/** Split into lines for diffing, dropping a single trailing newline (not content). */\nfunction toLines(text: string): string[] {\n if (text.length === 0) return []\n return text.replace(/\\n$/, '').split('\\n')\n}\n\n/**\n * A minimal line-level diff (LCS-based) between `before` and `after`. Returns one\n * string per line, prefixed `- ` (removed), `+ ` (added) or ` ` (unchanged), so a\n * dry-run can show exactly what a write would change instead of dumping the result.\n */\nexport function diffLines(before: string, after: string): string[] {\n const from = toLines(before)\n const to = toLines(after)\n // Longest common subsequence length table, filled bottom-up. `lcs[i][j]` is the\n // LCS length of from[i:] and to[j:]; reads past the edge are 0 (the base case).\n const lcs: number[][] = Array.from({ length: from.length + 1 }, () =>\n new Array<number>(to.length + 1).fill(0),\n )\n const cell = (i: number, j: number): number => lcs[i]?.[j] ?? 0\n for (let i = from.length - 1; i >= 0; i--) {\n const row = lcs[i]\n if (!row) continue\n for (let j = to.length - 1; j >= 0; j--) {\n row[j] = from[i] === to[j] ? cell(i + 1, j + 1) + 1 : Math.max(cell(i + 1, j), cell(i, j + 1))\n }\n }\n\n const out: string[] = []\n let i = 0\n let j = 0\n while (i < from.length && j < to.length) {\n if (from[i] === to[j]) {\n out.push(` ${from[i] ?? ''}`)\n i++\n j++\n } else if (cell(i + 1, j) >= cell(i, j + 1)) {\n out.push(`- ${from[i] ?? ''}`)\n i++\n } else {\n out.push(`+ ${to[j] ?? ''}`)\n j++\n }\n }\n while (i < from.length) out.push(`- ${from[i++] ?? ''}`)\n while (j < to.length) out.push(`+ ${to[j++] ?? ''}`)\n return out\n}\n","/**\n * Plan application: the engine's filesystem port.\n *\n * Adapters return a pure, declarative `UpdatePlan` (see `FileOperation`); this is\n * the ONE place that touches the disk. It resolves paths against the module root,\n * reads existing files, does the generic read/merge/write mechanics, and writes.\n * Keeping all IO here is what lets adapters stay pure and decoupled from the repo\n * layout: they say WHAT to change, the engine knows HOW and WHERE.\n *\n * Three guarantees, so `--init` is safe to run on real projects:\n * - CONTENT-PRESERVING merge: `merge-json` edits the file in place via\n * jsonc-parser, so a tsconfig's comments, key order and formatting survive.\n * - PREPARED-THEN-WRITTEN: the whole plan is computed before any write, so an\n * invalid operation fails before touching disk, and each file is written\n * atomically (temp file + rename) so an interrupted write never leaves a\n * truncated file. (Across multiple files the writes are sequential, not one\n * transaction: a crash mid-plan can leave earlier files written, recoverable\n * via git; a single file is always all-or-nothing.)\n * - CONFINED: every path is resolved and rejected if it escapes the module root.\n */\nimport { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { resolve, sep } from 'node:path'\n\nimport { applyEdits, modify } from 'jsonc-parser'\n\nimport { isPlainObject } from '../shared/deep-merge.js'\nimport { ensureLines } from '../shared/text.js'\nimport type { FileOperation, UpdatePlan } from './types.js'\n\n/** Resolve a plan-relative path and reject anything escaping the module root. */\nfunction resolveWithinRoot(cwd: string, relativePath: string): string {\n const root = resolve(cwd)\n const absolutePath = resolve(root, relativePath)\n if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {\n throw new Error(`Refusing to write outside the module root: \"${relativePath}\".`)\n }\n return absolutePath\n}\n\nfunction readIfExists(absolutePath: string): string | undefined {\n return existsSync(absolutePath) ? readFileSync(absolutePath, 'utf8') : undefined\n}\n\n/** Yield every leaf ([path, value]) of a nested object, for deep in-place edits. */\nfunction* leaves(\n value: Record<string, unknown>,\n prefix: string[] = [],\n): Generator<[path: string[], leaf: unknown]> {\n for (const [key, keyValue] of Object.entries(value)) {\n const path = [...prefix, key]\n if (isPlainObject(keyValue)) yield* leaves(keyValue, path)\n else yield [path, keyValue]\n }\n}\n\n/**\n * Deep-merge `value` into JSONC `current` IN PLACE (preserving comments/format).\n * Each leaf is set at its own path, so sibling keys, including a project's own\n * `paths`/`include`, survive untouched.\n */\nfunction mergeJsonc(current: string, value: Record<string, unknown>): string {\n let text = current.trim().length > 0 ? current : '{}\\n'\n for (const [path, leaf] of leaves(value)) {\n const edits = modify(text, path, leaf, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n })\n text = applyEdits(text, edits)\n }\n return text.endsWith('\\n') ? text : text + '\\n'\n}\n\n/** Remove each key path from JSONC `current`, preserving comments/formatting. */\nfunction removeJsoncKeys(current: string, keys: string[][]): string {\n let text = current.trim().length > 0 ? current : '{}\\n'\n for (const path of keys) {\n const edits = modify(text, path, undefined, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n })\n text = applyEdits(text, edits)\n }\n return text.endsWith('\\n') ? text : text + '\\n'\n}\n\n/** Apply one operation to the current file content (pure transform). */\nfunction applyOperationTo(current: string, operation: FileOperation): string {\n switch (operation.kind) {\n case 'write':\n return operation.contents\n case 'merge-json':\n return mergeJsonc(current, operation.value)\n case 'ensure-lines':\n return ensureLines(current, operation.lines)\n case 'remove-json-keys':\n return removeJsoncKeys(current, operation.keys)\n default: {\n // Exhaustiveness: a new FileOperation kind without a case here fails to compile.\n const unreachable: never = operation\n throw new Error(`Unknown file operation: ${JSON.stringify(unreachable)}`)\n }\n }\n}\n\n/** A file the plan would write: its original content and the computed result. */\nexport interface PreparedFile {\n path: string\n absolutePath: string\n before: string\n after: string\n}\n\n/**\n * Compute what a plan WOULD write, without touching the disk (for `--dry-run`).\n * All-or-nothing (throws before returning anything on a bad op), and multiple\n * operations on the SAME file chain in order, so `before` is the original content\n * and `after` is the final result.\n */\nexport function preparePlan(cwd: string, plan: UpdatePlan): PreparedFile[] {\n const prepared = new Map<string, PreparedFile>()\n for (const operation of plan.operations) {\n const absolutePath = resolveWithinRoot(cwd, operation.path)\n const existing = prepared.get(operation.path)\n const before = existing?.before ?? readIfExists(absolutePath) ?? ''\n const current = existing?.after ?? before\n prepared.set(operation.path, {\n path: operation.path,\n absolutePath,\n before,\n after: applyOperationTo(current, operation),\n })\n }\n return [...prepared.values()]\n}\n\n/**\n * Write `contents` to `absolutePath` atomically: write a sibling temp file, then\n * rename it over the target. rename is atomic on a single filesystem, so a reader\n * (or an interrupted run) never sees a half-written file, only the old or new one.\n */\nfunction writeFileAtomic(absolutePath: string, contents: string): void {\n const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`\n writeFileSync(tempPath, contents)\n renameSync(tempPath, absolutePath)\n}\n\n/**\n * Apply a plan against the module root; return the paths written. Prepares the\n * whole plan before the first write, then writes each file atomically.\n */\nexport function applyPlan(cwd: string, plan: UpdatePlan): string[] {\n const prepared = preparePlan(cwd, plan)\n for (const file of prepared) writeFileAtomic(file.absolutePath, file.after)\n return prepared.map((file) => file.path)\n}\n","/**\n * Generic deep-merge for plain JSON objects. Used by the engine to apply a\n * `merge-json` op (pin the keys sentinel owns while preserving a project's own).\n */\n\n/** True for a mergeable plain object (not null, not an array). */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Deep-merge `patch` into `base`, preserving keys `patch` does not mention.\n * Scalars and arrays from `patch` replace wholesale (no array concat surprises).\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n const existing = out[key]\n out[key] = isPlainObject(existing) && isPlainObject(value) ? deepMerge(existing, value) : value\n }\n return out\n}\n","/**\n * The single-module WRITE path (`--init`). The read verbs (`--run`/`--inspect`/\n * `--report`/`--status`) go through the multi-module orchestrator (`analyse`), which\n * resolves each module's adapter and calls the matching method; `dispatch` is what\n * `--init` uses because a write targets exactly one module.\n *\n * The engine owns IO: it resolves the flavour (explicit `--flavour`, else\n * `detectFramework` on the module's package.json), hands the adapter a normalized\n * `UpdateContext`; the adapter plans, the engine applies (`applyPlan`) or previews.\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { diffLines } from '../shared/text.js'\nimport { applyPlan, preparePlan } from './apply-plan.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { Flavour, Target, Verb } from './domain.js'\nimport { resolve } from './registry.js'\nimport type { UpdateContext, UpdatePlan } from './types.js'\n\nexport type { Verb }\n\nexport interface DispatchOptions {\n /** Always `init` (dispatch is the write path); other verbs route through `analyse`. */\n verb: Verb\n target: Target\n runner?: string\n cwd: string\n /** Declared flavour (`--flavour`), else resolved by `detectFramework` from deps. */\n flavour?: Flavour\n /** `--dry-run`: preview the plan instead of writing anything. */\n dryRun?: boolean\n /** `--json`: emit the dry-run preview as machine-readable JSON. */\n json?: boolean\n}\n\n/**\n * The flavour for the init: the explicit one, else detected from deps. `--init` WRITES\n * a preset, so an ambiguous detection is risky; warn and point at `--flavour` rather than\n * silently writing the wrong preset.\n */\nfunction resolveFlavour(opts: DispatchOptions): Flavour {\n if (opts.flavour) return opts.flavour\n const detection = describeFramework(readProjectPackageJson(opts.cwd))\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: flavour is ambiguous, detected \"${detection.flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to write the intended preset.`,\n ) + '\\n',\n )\n }\n return detection.flavour\n}\n\n/**\n * `--init --dry-run`: show what the plan WOULD change and write nothing. Prints a\n * per-file line diff (or JSON with `--json`), plus the adapter's notes. An empty\n * plan is reported as \"nothing to change\" so the developer gets a clear signal\n * rather than silence. Always exits 0: previewing never fails a build.\n */\nfunction previewPlan(opts: DispatchOptions, plan: UpdatePlan): number {\n // Only files the plan would actually change: an idempotent re-run computes an\n // `after` equal to `before`, which is a no-op, not a change to preview.\n const changed = preparePlan(opts.cwd, plan).filter((file) => file.before !== file.after)\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n dryRun: true,\n notes: plan.notes ?? [],\n files: changed.map(({ path, before, after }) => ({\n path,\n action: before.length === 0 ? 'create' : 'update',\n before,\n after,\n })),\n },\n null,\n 2,\n ) + '\\n',\n )\n return 0\n }\n\n process.stderr.write(' dry run: no files written\\n')\n for (const note of plan.notes ?? []) process.stderr.write(` ${note}\\n`)\n if (changed.length === 0) {\n process.stderr.write(' nothing to change\\n')\n return 0\n }\n for (const { path, before, after } of changed) {\n const action = before.length === 0 ? 'create' : 'update'\n process.stdout.write(`\\n ${action} ${path}\\n`)\n for (const line of diffLines(before, after)) process.stdout.write(` ${line}\\n`)\n }\n return 0\n}\n\nexport async function dispatch(opts: DispatchOptions): Promise<number> {\n // dispatch is the write path only; read verbs route through `analyse`. Guard so a\n // future miswire fails loudly here instead of silently doing nothing.\n if (opts.verb !== 'init') {\n throw new Error(`dispatch handles --init only; --${opts.verb} routes through analyse()`)\n }\n const adapter = resolve(opts.target, opts.flavour, opts.runner)\n const flavour = resolveFlavour(opts)\n\n // Engine resolves + normalizes the context; the adapter plans, the engine applies the\n // operations against the module root (or previews them with --dry-run).\n const context: UpdateContext = { cwd: opts.cwd, flavour }\n const plan = await adapter.plan(context)\n if (opts.dryRun) {\n // Preview only: compute what WOULD be written, touch nothing on disk.\n return previewPlan(opts, plan)\n }\n const written = applyPlan(opts.cwd, plan)\n for (const path of written) process.stderr.write(` wrote ${path}\\n`)\n for (const note of plan.notes ?? []) process.stderr.write(` ${note}\\n`)\n return 0\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","existsSync","dirname","join","existsSync","readFileSync","join","existsSync","readFileSync","join","readFileSync","join","existsSync","existsSync","join","readFileSync","readFileSync","join","existsSync","i","j","existsSync","readFileSync","resolve","resolve","existsSync","readFileSync"]}
package/dist/index.d.ts CHANGED
@@ -10,8 +10,13 @@ declare function registerAdapters(): void;
10
10
  * Tunable behaviour (defaults, detection signals, marker filenames) lives in
11
11
  * `settings.ts`, not here.
12
12
  */
13
- /** Verbs: what to do. Each maps to an adapter method in dispatch. */
14
- declare const VERBS: readonly ["run", "inspect", "update", "report", "status"];
13
+ /**
14
+ * Verbs: what to do. `init` writes a module's config + the workspace prep it needs (the
15
+ * one-time setup); the read verbs (run/inspect/report/status) execute or introspect.
16
+ * `migrate` is reserved for changing an already-initialized setup and is not wired yet
17
+ * (the CLI lists it as planned and refuses it).
18
+ */
19
+ declare const VERBS: readonly ["run", "inspect", "init", "migrate", "report", "status"];
15
20
  type Verb = (typeof VERBS)[number];
16
21
  /** Targets: the kind of check. The CLI `--<target>` flags map 1:1 to these. */
17
22
  declare const TARGETS: readonly ["lint", "format", "typescript", "build", "test", "static-analysis", "runtime-analysis", "arch"];
@@ -31,7 +36,7 @@ type Flavour = (typeof FLAVOURS)[number];
31
36
  */
32
37
 
33
38
  /**
34
- * Normalized context for `--update`, built and guaranteed by the engine. The
39
+ * Normalized context for `--init`, built and guaranteed by the engine. The
35
40
  * adapter TRUSTS this shape (no re-validation): `cwd` is the absolute module root,
36
41
  * `flavour` is resolved (explicit `--flavour`, else detected). The adapter may READ
37
42
  * the module through bounded resolvers to plan, but never writes, the engine
@@ -63,7 +68,7 @@ interface RunContext {
63
68
  maxDiagnostics?: number;
64
69
  }
65
70
  /**
66
- * A single, DECLARATIVE file operation an `--update` wants to happen. Adapters
71
+ * A single, DECLARATIVE file operation an `--init` wants to happen. Adapters
67
72
  * describe intent in tool terms; they never touch the filesystem. The engine
68
73
  * (which owns IO and the repo layout) executes these against the project root.
69
74
  *
@@ -108,7 +113,7 @@ type FileOperation =
108
113
  keys: string[][];
109
114
  };
110
115
  /**
111
- * The result of planning an `--update`: an ordered list of declarative operations,
116
+ * The result of planning an `--init`: an ordered list of declarative operations,
112
117
  * plus optional human notes (what was created/stripped/skipped) surfaced by the
113
118
  * CLI. Multi-operation on purpose; the drift guard and golden tests diff the plan.
114
119
  */
@@ -143,7 +148,7 @@ interface StatusReport {
143
148
  /**
144
149
  * One tool implementation for one target (and, optionally, a subset of flavours,
145
150
  * see `appliesTo`). The CLI verbs map onto these: `--run` -> run, `--inspect` ->
146
- * inspect, `--update` -> plan(+write), `--report` -> report, `--status` -> status.
151
+ * inspect, `--init` -> plan(+write), `--report` -> report, `--status` -> status.
147
152
  */
148
153
  interface Adapter {
149
154
  /** The target this adapter serves (e.g. 'lint'). */
@@ -157,7 +162,7 @@ interface Adapter {
157
162
  */
158
163
  appliesTo(flavour: Flavour): boolean;
159
164
  /**
160
- * Plan what `--update` should write, as declarative operations (see
165
+ * Plan what `--init` should write, as declarative operations (see
161
166
  * `FileOperation`). Given a normalized `UpdateContext`, the adapter may READ the
162
167
  * module (through bounded resolvers) to decide the ops, but never writes, the
163
168
  * engine applies them and owns all IO. The drift guard diffs against this plan.
@@ -205,7 +210,7 @@ declare abstract class BaseAdapter implements Adapter {
205
210
  * dependency is a plain TypeScript library (`node`). This is not the old silent
206
211
  * guessing (there is no "assume react" fallback); `node` is a real preset.
207
212
  *
208
- * Reusable across tools: every tool's `--update` needs the module's flavour to
213
+ * Reusable across tools: every tool's `--init` needs the module's flavour to
209
214
  * pick its preset, so this lives in core, not in the TypeScript role.
210
215
  */
211
216
 
@@ -218,7 +223,7 @@ interface PackageDependencies {
218
223
  declare function detectFramework(packageJson: PackageDependencies): Flavour;
219
224
 
220
225
  interface DispatchOptions {
221
- /** Always `update` (dispatch is the write path); other verbs route through `analyse`. */
226
+ /** Always `init` (dispatch is the write path); other verbs route through `analyse`. */
222
227
  verb: Verb;
223
228
  target: Target;
224
229
  runner?: string;
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-6N2CLPWE.js";
10
+ } from "./chunk-6ZTM3PCI.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.12",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/registry.ts","../src/core/base-adapter.ts","../src/shared/color.ts","../src/shared/package-json.ts","../src/roles/typescript/adapters/tsc/tsc.adapter.ts","../src/shared/jsonc.ts","../src/shared/resolve-bin.ts","../src/roles/typescript/config-policy.ts","../src/roles/typescript/phased-rules.ts","../src/roles/typescript/presets.ts","../src/roles/typescript/read-adoption.ts","../src/roles/typescript/resolve-tsconfig-target.ts","../src/roles/typescript/register.ts","../src/adapters.ts","../src/core/detect-framework.ts","../src/shared/text.ts","../src/core/apply-plan.ts","../src/shared/deep-merge.ts","../src/core/dispatch.ts"],"sourcesContent":["/**\n * Adapter registry. Tool branches register their adapters here; the CLI resolves\n * an adapter by (target, flavour, runner). A default runner per target keeps the\n * common invocation tool-agnostic (`sentinel --lint`), while `--runner` overrides\n * it. Resolution is flavour-aware: candidates are filtered by `appliesTo(flavour)`,\n * so a React-only adapter is never picked for a Nest project, and two adapters can\n * share a (target, runner) if they specialise different flavours.\n */\nimport type { Flavour, Target } from './domain.js'\nimport type { Adapter } from './types.js'\n\nconst adapters: Adapter[] = []\n\n/** Default runner per target, so `--runner` stays optional. */\nconst defaultRunner: Partial<Record<Target, string>> = {\n // Filled in by tool branches, e.g. lint: 'eslint', typescript: 'tsc'.\n}\n\n/**\n * Register a tool adapter (called from each role's registration, wired into the\n * bootstrap in `src/adapters.ts`). No (target, runner) uniqueness guard here: two\n * adapters may share one for different flavours; a genuine clash (same target,\n * runner AND flavour) is caught at resolve time, where the flavour is known.\n */\nexport function register(adapter: Adapter): void {\n adapters.push(adapter)\n}\n\n/** Set the default runner for a target. */\nexport function setDefaultRunner(target: Target, runner: string): void {\n defaultRunner[target] = runner\n}\n\n/** All registered adapters (for `--all`, listing, and reports). */\nexport function all(): readonly Adapter[] {\n return adapters\n}\n\n/**\n * The targets that actually have an adapter wired today (distinct, in registration\n * order). The source of truth for \"what can sentinel really do right now\" — used to\n * flag a requested-but-unwired target and to show availability in `--help`, instead\n * of a hardcoded list that pretends every target is ready.\n */\nexport function availableTargets(): Target[] {\n return [...new Set(adapters.map((a) => a.target))]\n}\n\n/**\n * Resolve one adapter by target, honouring an explicit `--runner` or the target's\n * default. When a `flavour` is known it also filters by `appliesTo`, so a\n * React-only adapter is never picked for Nest; when it is absent (sentinel does\n * not detect it), every adapter for the target is a candidate. Throws with an\n * actionable message for each failure mode: no adapter for the target (yet), none\n * that handles the flavour, an ambiguous choice, an unknown runner, or two\n * adapters claiming the same (target, runner, flavour).\n */\nexport function resolve(target: Target, flavour?: Flavour, runner?: string): Adapter {\n const forTarget = adapters.filter((a) => a.target === target)\n if (forTarget.length === 0) {\n throw new Error(\n `No adapter registered for target \"${target}\" yet (it ships in a later ticket).`,\n )\n }\n\n const candidates = flavour ? forTarget.filter((a) => a.appliesTo(flavour)) : forTarget\n if (candidates.length === 0) {\n throw new Error(`No adapter for target \"${target}\" handles flavour \"${flavour}\".`)\n }\n\n const wanted = runner ?? defaultRunner[target]\n const available = candidates.map((a) => a.runner).join(', ')\n\n // No runner asked for and no default: only unambiguous when there is exactly one.\n if (!wanted) {\n const [first, ...rest] = candidates\n if (first && rest.length === 0) return first\n throw new Error(\n `Multiple runners for target \"${target}\" (${available}); pass --runner or set a default.`,\n )\n }\n\n const matching = candidates.filter((a) => a.runner === wanted)\n if (matching.length === 0) {\n throw new Error(\n `No runner \"${wanted}\" for target \"${target}\" (flavour \"${flavour}\"). Available: ${available}.`,\n )\n }\n if (matching.length > 1) {\n throw new Error(\n `Ambiguous: ${matching.length} adapters claim target \"${target}\", runner \"${wanted}\", flavour \"${flavour}\".`,\n )\n }\n return matching[0] as Adapter\n}\n","/**\n * `BaseAdapter`: an optional convenience base class for adapters. This is the\n * runtime part of the contract (the types live in `types.ts`); it provides\n * default `inspect`/`report` that throw \"not implemented yet\", so an adapter can\n * extend it and implement only what it needs.\n */\nimport type { Flavour, Target } from './domain.js'\nimport type {\n Adapter,\n AdapterResult,\n RunContext,\n StatusReport,\n UpdateContext,\n UpdatePlan,\n} from './types.js'\n\nexport abstract class BaseAdapter implements Adapter {\n abstract readonly target: Target\n abstract readonly runner: string\n abstract appliesTo(flavour: Flavour): boolean\n abstract plan(context: UpdateContext): UpdatePlan | Promise<UpdatePlan>\n abstract run(ctx: RunContext): Promise<AdapterResult>\n\n inspect(_ctx: RunContext): Promise<unknown> {\n throw new Error(`${this.runner}: --inspect not implemented yet`)\n }\n\n report(_ctx: RunContext): Promise<AdapterResult> {\n throw new Error(`${this.runner}: --report not implemented yet`)\n }\n\n status(_ctx: RunContext): Promise<StatusReport> {\n throw new Error(`${this.runner}: --status not implemented yet`)\n }\n}\n","/**\n * Terminal color, on top of Node's built-in `util.styleText` (no dependency).\n *\n * Passing `{ stream }` lets styleText itself decide whether to emit escape codes: it\n * returns the text UNCHANGED when the stream is not a TTY, when `NO_COLOR` is set, or\n * when the terminal has no color support. So the same code stays readable on a terminal\n * and clean in pipes, files, and CI logs, we never hand-roll a TTY check.\n */\nimport { styleText } from 'node:util'\n\ntype Format = Parameters<typeof styleText>[0]\n\n/** The semantic styles, each a `string -> string` painter. */\nexport interface Palette {\n /** A passing result. */ ok: (text: string) => string\n /** A failing result. */ fail: (text: string) => string\n /** A caution / deferred / deprecation notice. */ warn: (text: string) => string\n /** Emphasis (a name, a heading). */ strong: (text: string) => string\n /** Secondary, low-salience text (keys, timings, scope). */ dim: (text: string) => string\n /** A structural marker (a run header). */ accent: (text: string) => string\n}\n\n/**\n * Bind the semantic styles to ONE stream: `palette(process.stdout)` for the result\n * summary, `palette(process.stderr)` for notices/progress, so each is colored only when\n * that specific stream supports it.\n */\nexport function palette(stream: NodeJS.WriteStream): Palette {\n const paint = (format: Format, text: string): string => styleText(format, text, { stream })\n return {\n ok: (text) => paint(['green', 'bold'], text),\n fail: (text) => paint(['red', 'bold'], text),\n warn: (text) => paint('yellow', text),\n strong: (text) => paint('bold', text),\n dim: (text) => paint('dim', text),\n accent: (text) => paint('cyan', text),\n }\n}\n\n/**\n * An identity palette (no escapes), for pure rendering in tests and for any caller that\n * wants the plain text regardless of the stream.\n */\nexport const plainPalette: Palette = {\n ok: (text) => text,\n fail: (text) => text,\n warn: (text) => text,\n strong: (text) => text,\n dim: (text) => text,\n accent: (text) => text,\n}\n","/**\n * package.json helpers, shared by the CLI and the engine.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * sentinel's OWN version, for `--version`. We must NOT use\n * `process.env.npm_package_version`, that is the version of whatever script\n * invoked us (an app's package.json, or nothing). Instead walk up from this\n * module to the nearest package.json, which is always sentinel's own, whether\n * running from source or the bundled dist.\n */\n/**\n * sentinel's OWN name + version, read by walking up from this module to the nearest\n * package.json. Used both for `--version` and to pin the exact dependency `--update`\n * writes into an adopted module, so a module always references the version that adopted it.\n */\nexport function readOwnPackage(): { name: string; version: string } {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (;;) {\n const pkgPath = join(dir, 'package.json')\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string; version?: string }\n if (typeof pkg.version === 'string') {\n return { name: pkg.name ?? '@hublo/sentinel', version: pkg.version }\n }\n } catch {\n // Malformed package.json; keep walking up.\n }\n }\n const parent = dirname(dir)\n if (parent === dir) return { name: '@hublo/sentinel', version: '0.0.0' } // filesystem root\n dir = parent\n }\n}\n\nexport function readOwnVersion(): string {\n return readOwnPackage().version\n}\n\n/**\n * Read a module's package.json for framework detection; tolerant of a missing or\n * malformed file (returns `{}`, so detection falls through to the `node` default).\n */\nexport function readProjectPackageJson(dir: string): {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n} {\n const path = join(dir, 'package.json')\n if (!existsSync(path)) return {}\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as Record<string, never>\n } catch {\n process.stderr.write(`sentinel: could not parse ${path}; ignoring for detection.\\n`)\n return {}\n }\n}\n\n/**\n * The nx project name for a module, read from its `project.json`, or undefined if\n * there is none/unreadable. Used when `--update` has to scaffold a `package.json`\n * for a module that only has a `project.json` (common for nx apps/services): the\n * scaffolded file borrows this name so it is a valid, uniquely-named workspace\n * package. Reusable by any tool that scaffolds, not just TypeScript.\n */\nexport function readNxProjectName(dir: string): string | undefined {\n const path = join(dir, 'project.json')\n if (!existsSync(path)) return undefined\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as { name?: unknown }\n return typeof parsed.name === 'string' ? parsed.name : undefined\n } catch {\n return undefined\n }\n}\n","/**\n * TypeScript adapter (runner: `tsc`). Maps the verbs onto tsc for one module:\n * - plan (--update) → thin conformant tsconfig stub extending the preset\n * - run (--run) → `tsc -b` (build mode), the module's own tsc\n * - inspect (--inspect) → the module's resolved config (preset, target, flavour)\n * - report (--report) → type-error + implicit-any counts (the conformance signal)\n * - status (--status) → adoption + conformity, read from the committed config\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport { BaseAdapter } from '../../../../core/base-adapter.js'\nimport type { Flavour, Target } from '../../../../core/domain.js'\nimport { palette } from '../../../../shared/color.js'\nimport type {\n AdapterResult,\n FileOperation,\n RunContext,\n StatusReport,\n UpdateContext,\n UpdatePlan,\n} from '../../../../core/types.js'\nimport { parseJsonc } from '../../../../shared/jsonc.js'\nimport { readNxProjectName, readOwnPackage } from '../../../../shared/package-json.js'\nimport { resolveBin } from '../../../../shared/resolve-bin.js'\nimport { presetOwnedKeys } from '../../config-policy.js'\nimport { DEFERRED_RULES, deferredRuleNames } from '../../phased-rules.js'\nimport { hasShippedPreset } from '../../presets.js'\nimport { readTsconfigAdoption } from '../../read-adoption.js'\nimport { resolveTsconfigTarget } from '../../resolve-tsconfig-target.js'\n\nconst TYPECHECK_SCRIPT = { typecheck: 'sentinel --run --typescript' }\n\n/**\n * `--update` writes the exact-version `@hublo/sentinel` devDependency into the module (so\n * the `extends` and the `typecheck` script resolve). The one remaining step is to fetch it,\n * so the plan says to run `pnpm install`, and adoption stays a one-command, zero-manual move.\n */\nconst INSTALL_NOTE =\n 'run `pnpm install` to fetch @hublo/sentinel (added to the module devDependencies) so `extends` and the typecheck script resolve'\n\n/** Default cap on embedded diagnostics per module (the full count stays in `errors`). */\nconst DEFAULT_MAX_DIAGNOSTICS = 100\n\n/** One structured tsc diagnostic, parsed from `tsc -b` output. */\nexport interface TscDiagnostic {\n file: string\n line: number\n col: number\n /** The TS error code, e.g. `TS7006`. */\n code: string\n message: string\n}\n\n/** `path/to/file.ts(12,5): error TS7006: Parameter 'x' implicitly has an 'any' type.` */\nconst DIAGNOSTIC_RE = /^(.+?)\\((\\d+),(\\d+)\\): error (TS\\d+): (.+)$/\n\n/**\n * Parse structured diagnostics out of `tsc` output so `--report` carries the detail (file,\n * line, code, message), not just a count. Location-less errors (e.g. a config-level\n * TS18003) do not match and are left to the `errors` count, which stays the authoritative\n * total, so a report never quietly loses one.\n */\nexport function parseDiagnostics(output: string): TscDiagnostic[] {\n const diagnostics: TscDiagnostic[] = []\n for (const raw of output.split('\\n')) {\n const m = DIAGNOSTIC_RE.exec(raw.trim())\n if (m) {\n diagnostics.push({\n file: m[1] as string,\n line: Number(m[2]),\n col: Number(m[3]),\n code: m[4] as string,\n message: m[5] as string,\n })\n }\n }\n return diagnostics\n}\n\n/**\n * Phase-1 notice, generated from the single source of truth (`DEFERRED_RULES`). First\n * adoption is a NON-BREAKING lateral move; the deferred rules are enabled centrally in\n * a later wave. Surfaced on every run so the reduced strictness is visible in logs,\n * never a silent gap; `--inspect` lists the full set.\n */\nconst PHASED_STRICTNESS_WARNING =\n `sentinel typescript: phase 1 (non-breaking) — deferred: ${deferredRuleNames().join(', ')}` +\n '. Enabled centrally in a later wave; run `sentinel --inspect --typescript` for the list.'\n\n/**\n * Append `preset` to a tsconfig's `extends`, as a TS 5.0 array, WITHOUT dropping\n * what is already there (the base config the module extends). Accepts the current\n * `extends` as a string, an array, or absent; returns the composed chain with the\n * preset last (so its options win) and never duplicated, so a re-run is a no-op.\n */\nfunction composeExtends(current: unknown, preset: string): string[] {\n const chain =\n typeof current === 'string'\n ? [current]\n : Array.isArray(current)\n ? current.filter((entry): entry is string => typeof entry === 'string')\n : []\n return chain.includes(preset) ? chain : [...chain, preset]\n}\n\nexport class TscAdapter extends BaseAdapter {\n readonly target: Target = 'typescript'\n readonly runner = 'tsc'\n\n /**\n * The tsc adapter drives type-checking for any flavour: `--run`/`--report`/\n * `--inspect` just execute tsc against the module's existing config, which is\n * meaningful regardless of flavour. `--update` is the exception, it only WRITES a\n * preset for flavours that ship one (gated inside `plan`), so svelte is not\n * clobbered with a non-existent preset.\n */\n appliesTo(_flavour: Flavour): boolean {\n return true\n }\n\n /**\n * Plan `--update`: make the module extend the sentinel preset with a THIN,\n * conformant stub, and route type-checking through the CLI. The engine applies\n * the ops; ensuring the `@hublo/sentinel` dependency is an adoption step\n * (`pnpm add`), not a file write.\n *\n * Per resolved case:\n * - extends-base: append the preset to the `extends` chain (keep the base for\n * the monorepo's paths/structure) + strip preset-owned `compilerOptions`\n * (drift), keeping the project's own paths/include (the allowlist).\n * - none: create a fresh thin `tsconfig.json`.\n * - other-chain (svelte): skip, its config extends a different base.\n */\n plan(context: UpdateContext): UpdatePlan {\n // Only write a preset for a flavour that actually ships one. A declared-but-\n // unshipped flavour (e.g. svelte) is skipped, never pointed at a preset that\n // does not exist, which would break the module's typecheck.\n if (!hasShippedPreset(context.flavour)) {\n return {\n operations: [],\n notes: [`skipped: no TypeScript preset for flavour \"${context.flavour}\" yet`],\n }\n }\n const target = resolveTsconfigTarget(context.cwd)\n const preset = `@hublo/sentinel/tsconfig/${context.flavour}`\n const addScript = this.packageJsonOperation(context.cwd)\n\n if (target.reason === 'other-chain') {\n return {\n operations: [],\n notes: [`skipped: ${target.path} extends a non-base config; handled separately`],\n }\n }\n\n if (target.reason === 'none') {\n const contents = JSON.stringify({ extends: preset, include: ['src'] }, null, 2) + '\\n'\n return {\n operations: [{ kind: 'write', path: target.path, contents }, addScript],\n notes: [`created ${target.path} (no tsconfig found)`, INSTALL_NOTE],\n }\n }\n\n // extends-base: KEEP the base (it carries the monorepo's paths/types/structure,\n // not just tooling) and APPEND the preset to the extends chain (TS 5.0 array\n // extends). Later wins, so the preset's tooling overrides the base while the\n // base's config survives. Then strip the module's own preset-owned options so the\n // preset wins over stale local copies.\n const existing = parseJsonc<{ extends?: unknown; compilerOptions?: Record<string, unknown> }>(\n readFileSync(join(context.cwd, target.path), 'utf8'),\n target.path,\n )\n const extendsChain = composeExtends(existing.extends, preset)\n const drift = presetOwnedKeys(existing.compilerOptions)\n const operations: FileOperation[] = [\n { kind: 'merge-json', path: target.path, value: { extends: extendsChain } },\n ]\n const notes: string[] = []\n if (drift.length > 0) {\n operations.push({\n kind: 'remove-json-keys',\n path: target.path,\n keys: drift.map((key) => ['compilerOptions', key]),\n })\n notes.push(`stripped preset-owned compilerOptions: ${drift.join(', ')}`)\n }\n operations.push(addScript)\n notes.push(INSTALL_NOTE)\n return { operations, notes }\n }\n\n /**\n * The op that routes type-checking through the CLI. If the module already has a\n * `package.json`, merge the script in and leave the rest untouched. If it does\n * NOT (common for nx apps/services that carry only a `project.json`), scaffold a\n * minimal, workspace-valid one, its nx name + `private: true`, so pnpm accepts it\n * and it can then receive the `@hublo/sentinel` devDep (added via `pnpm add` at\n * adoption, never written here, so the lockfile stays authoritative).\n */\n private packageJsonOperation(cwd: string): FileOperation {\n // Write the exact-version devDependency too, so adoption needs no manual `pnpm add`:\n // `--update` then `pnpm install` is enough. `merge-json` preserves everything else and\n // makes a re-run idempotent (same version -> no change).\n const own = readOwnPackage()\n const devDependencies = { [own.name]: own.version }\n if (existsSync(join(cwd, 'package.json'))) {\n return {\n kind: 'merge-json',\n path: 'package.json',\n value: { scripts: TYPECHECK_SCRIPT, devDependencies },\n }\n }\n const name = readNxProjectName(cwd) ?? basename(cwd)\n return {\n kind: 'merge-json',\n path: 'package.json',\n value: { name, private: true, scripts: TYPECHECK_SCRIPT, devDependencies },\n }\n }\n\n /**\n * Type-check the module with `tsc -b` (build mode) on its solution config, the\n * way the monorepo itself does. Build mode walks the config's `references`, so a\n * references-only solution (Pattern A: app + spec) is actually checked instead of\n * passing vacuously; it also only caches SUCCESSFUL builds, so errors are always\n * re-reported. Uses the module's own tsc. Nothing to check is a pass.\n */\n async run(ctx: RunContext): Promise<AdapterResult> {\n const config = this.typecheckTarget(ctx.cwd)\n if (!config) {\n process.stderr.write('sentinel typescript(tsc): no tsconfig to check\\n')\n return { ok: true, code: 0 }\n }\n process.stderr.write(`${palette(process.stderr).warn(PHASED_STRICTNESS_WARNING)}\\n`)\n const tsc = resolveBin(ctx.cwd, 'tsc') ?? 'tsc'\n const result = spawnSync(tsc, ['-b', config], { cwd: ctx.cwd, stdio: 'inherit' })\n if (result.error) {\n process.stderr.write(\n `sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?\\n`,\n )\n return { ok: false, code: 1 }\n }\n const code = result.status ?? 1\n return { ok: code === 0, code }\n }\n\n /**\n * The config to type-check with `tsc -b`. Prefer the module's root `tsconfig.json`\n * (the solution the monorepo builds; `tsc -b` follows its `references` to cover\n * app + spec), else the base-extending file, else null when there is nothing to\n * check.\n */\n private typecheckTarget(cwd: string): string | null {\n if (existsSync(join(cwd, 'tsconfig.json'))) return 'tsconfig.json'\n const target = resolveTsconfigTarget(cwd)\n return target.reason === 'none' ? null : target.path\n }\n\n /**\n * The module's resolved TypeScript config: which preset, which file, how, and the\n * phased-strictness state (`deferred` rules that are off in phase 1). This is the\n * \"list what's deferred\" query, `sentinel --inspect --typescript`.\n */\n async inspect(ctx: RunContext): Promise<unknown> {\n const target = resolveTsconfigTarget(ctx.cwd)\n // The preset is READ from the module's committed `extends` chain, not derived from\n // the (possibly hoisted-fallback) flavour: a nest module reports `.../nest`, not\n // `.../node`, so `--inspect` reflects what the file actually uses.\n const { preset, adopted } = readTsconfigAdoption(ctx.cwd)\n return {\n module: ctx.module,\n target: 'typescript',\n flavour: ctx.flavour,\n configFile: target.path,\n configState: target.reason,\n preset,\n adopted,\n phase: 1,\n deferred: DEFERRED_RULES,\n }\n }\n\n /**\n * Adoption + conformity from the committed tsconfig, for `--status`. No tsc run and\n * no flavour guessing: reads the actual `extends` chain, so a workspace-wide scan is\n * a cheap coverage + drift dashboard (adopted? which preset? drifted?).\n */\n async status(ctx: RunContext): Promise<StatusReport> {\n const { adopted, preset, conformant, drift } = readTsconfigAdoption(ctx.cwd)\n return { adopted, preset, conformant, drift }\n }\n\n /**\n * Report conformance for the module: `tsc -b` (build mode, so app + spec are\n * covered) and count total type errors plus the implicit-`any` family (TS70xx:\n * 7006/7031/7053/… ), the signal that drives the noImplicitAny migration. No\n * tsconfig is a clean, empty report.\n */\n async report(ctx: RunContext): Promise<AdapterResult> {\n const config = this.typecheckTarget(ctx.cwd)\n if (!config) {\n return { ok: true, code: 0, metrics: { errors: 0, implicitAny: 'deferred', diagnostics: [] } }\n }\n const tsc = resolveBin(ctx.cwd, 'tsc') ?? 'tsc'\n const result = spawnSync(tsc, ['-b', config], { cwd: ctx.cwd, encoding: 'utf8' })\n if (result.error) {\n process.stderr.write(\n `sentinel typescript(tsc): could not run tsc (${result.error.message}); is TypeScript installed in the module?\\n`,\n )\n return { ok: false, code: 1, metrics: { error: 'tsc not available' } }\n }\n const output = `${result.stdout ?? ''}${result.stderr ?? ''}`\n const errors = (output.match(/error TS\\d+/g) ?? []).length\n // Embed the structured diagnostics so `--report --json` carries the detail (no need to\n // re-run `--run`). Cap to keep the envelope bounded; `errors` remains the full count and\n // `diagnosticsTruncated` flags when the cap dropped some, so nothing is hidden silently.\n const parsed = parseDiagnostics(output)\n const cap =\n ctx.maxDiagnostics === 0 ? Infinity : (ctx.maxDiagnostics ?? DEFAULT_MAX_DIAGNOSTICS)\n const diagnostics = Number.isFinite(cap) ? parsed.slice(0, cap) : parsed\n return {\n ok: errors === 0,\n code: result.status ?? 0,\n metrics: {\n errors,\n implicitAny: this.implicitAnyMetric(ctx.cwd, tsc, config, output),\n diagnostics,\n diagnosticsTruncated: diagnostics.length < parsed.length,\n },\n }\n }\n\n /**\n * The `implicitAny` report metric, honestly. Implicit-`any` violations (TS70xx) are\n * only *visible* to tsc when `noImplicitAny` is ON. In phase 1 the rule is DEFERRED\n * (off), so counting TS70xx from the committed-config run is structurally always 0 —\n * a misleading \"no implicit-any\" when the rule simply was not applied. So: report the\n * real count only when the rule is on, otherwise `'deferred'` (never a fake `0`). The\n * remaining debt while deferred is a separate, opt-in probe (a later `--migration`).\n */\n private implicitAnyMetric(\n cwd: string,\n tsc: string,\n config: string,\n mainOutput: string,\n ): number | 'deferred' {\n return this.noImplicitAnyEnabled(cwd, tsc, config, mainOutput)\n ? (mainOutput.match(/error TS70\\d\\d/g) ?? []).length\n : 'deferred'\n }\n\n /**\n * Whether `noImplicitAny` is effectively ON in the module's resolved config. Read from\n * `tsc --showConfig` (an explicit value wins; otherwise `strict` implies it). If\n * `--showConfig` is unavailable, fall back to the run's own evidence: implicit-`any`\n * errors in the output mean the rule must be on.\n */\n private noImplicitAnyEnabled(\n cwd: string,\n tsc: string,\n config: string,\n mainOutput: string,\n ): boolean {\n const shown = spawnSync(tsc, ['-p', config, '--showConfig'], { cwd, encoding: 'utf8' })\n if (shown.status === 0 && shown.stdout) {\n try {\n const co = parseJsonc<{ compilerOptions?: { noImplicitAny?: boolean; strict?: boolean } }>(\n shown.stdout,\n 'tsconfig(--showConfig)',\n ).compilerOptions\n if (co) return co.noImplicitAny ?? co.strict === true\n } catch {\n // malformed --showConfig output: fall through to the evidence-based check\n }\n }\n return /error TS70\\d\\d/.test(mainOutput)\n }\n}\n","/**\n * JSONC (JSON with comments + trailing commas) helpers. tsconfig files are JSONC,\n * so reading them with plain `JSON.parse` throws on real projects (e.g.\n * host-admin's tsconfig.app.json has comments). Backed by jsonc-parser (the VS\n * Code library), which also underpins content-preserving edits (added with the\n * `--update` merge).\n */\nimport { parse, printParseErrorCode, type ParseError } from 'jsonc-parser'\n\n/**\n * Parse JSONC text into a value. Throws with a clear message listing the parse\n * errors, so a malformed config fails loudly rather than silently mis-reading.\n */\nexport function parseJsonc<T = unknown>(text: string, source = 'config'): T {\n const errors: ParseError[] = []\n const value = parse(text, errors, { allowTrailingComma: true }) as T\n if (errors.length > 0) {\n const details = errors.map((error) => printParseErrorCode(error.error)).join(', ')\n throw new Error(`${source}: malformed JSONC (${details}).`)\n }\n return value\n}\n","/**\n * Find a tool binary the way node/npm would: walk up from a directory looking for\n * `node_modules/.bin/<name>`. Used so `--run` invokes the MODULE's own tool version\n * (its `tsc`), not sentinel's. Returns undefined if not found (caller falls back to\n * the name on PATH).\n */\nimport { existsSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport function resolveBin(fromDir: string, name: string): string | undefined {\n let dir = fromDir\n for (;;) {\n const candidate = join(dir, 'node_modules', '.bin', name)\n if (existsSync(candidate)) return candidate\n const parent = dirname(dir)\n if (parent === dir) return undefined\n dir = parent\n }\n}\n","/**\n * The allowlist: which tsconfig `compilerOptions` a module may keep locally. Only\n * genuinely project-specific settings, everything else is owned by the sentinel\n * preset and stripped by `--update`, so every migrated module is conformant from\n * the start. A sanctioned exception would be added here (visible + reviewed).\n */\nexport const PERMITTED_COMPILER_OPTIONS: readonly string[] = [\n 'paths',\n 'baseUrl',\n 'rootDir',\n 'outDir',\n 'tsBuildInfoFile',\n]\n\n/**\n * The `compilerOptions` keys the preset owns: present in the project but not in the\n * allowlist. These are the drift `--update` strips.\n */\nexport function presetOwnedKeys(compilerOptions: Record<string, unknown> | undefined): string[] {\n if (!compilerOptions) return []\n return Object.keys(compilerOptions).filter((key) => !PERMITTED_COMPILER_OPTIONS.includes(key))\n}\n","/**\n * Phased TypeScript strictness, the single source of truth.\n *\n * These are the rules sentinel DEFERS on first adoption so it stays non-breaking, then\n * enables centrally in a later wave. The preset deliberately does NOT set them (so a\n * repo base that relaxes a rule keeps it off), they are announced by the run/report\n * warning, and reported by `--inspect` so a developer, or a CI conformance check, can\n * list exactly what is deferred. Each `rule` here MUST stay absent from every preset\n * (asserted in the preset tests), so this list and the presets can never drift.\n *\n * The paired other half lives in the consuming repo: the base config marks the matching\n * relaxations `@deprecated` (the levers to remove). deferred (here, coming) ⇄\n * @deprecated (there, going).\n */\nexport interface PhasedRule {\n /** The tsconfig compilerOption that is deferred. */\n rule: string\n /** The migration wave that will enable it. */\n phase: number\n /** Why it is deferred (what enabling it will surface). */\n reason: string\n}\n\nexport const DEFERRED_RULES: readonly PhasedRule[] = [\n { rule: 'noImplicitAny', phase: 2, reason: 'the implicit-any migration (TS70xx)' },\n { rule: 'noUnusedLocals', phase: 2, reason: 'unused-local cleanup (TS6133)' },\n { rule: 'noUnusedParameters', phase: 2, reason: 'unused-parameter cleanup (TS6133)' },\n] as const\n\n/** The deferred rule names, e.g. for a one-line warning. */\nexport function deferredRuleNames(): string[] {\n return DEFERRED_RULES.map((entry) => entry.rule)\n}\n","/**\n * The flavours whose TypeScript preset actually ships: one `flavours/<flavour>.ts`\n * source, flattened to `dist/tsconfig/<flavour>.json` by `scripts/build-presets.ts`.\n *\n * Single source of truth, shared by the build script and the adapter's `appliesTo`,\n * so a flavour is only ever offered when its preset exists. A declared flavour with\n * no preset yet (e.g. `svelte`) is deliberately absent: `--update` skips it rather\n * than writing an `extends` to a module that does not exist. Adding a preset is one\n * new file here plus its entry in this list.\n */\nimport type { Flavour } from '../../core/domain.js'\n\nexport const SHIPPED_FLAVOURS = ['react', 'nest', 'node'] as const satisfies readonly Flavour[]\n\n/** Whether a flavour's TypeScript preset is available. */\nexport function hasShippedPreset(flavour: Flavour): boolean {\n return (SHIPPED_FLAVOURS as readonly Flavour[]).includes(flavour)\n}\n","/**\n * Read a module's ADOPTION + CONFORMITY of the TypeScript preset, straight from its\n * committed `tsconfig`. This is the truth `--status` reports, and it is deliberately\n * DETECTION-FREE: it reads the actual `extends` chain rather than guessing a flavour,\n * so a hoisted-deps monorepo (where detection falls back to `node`) can't corrupt it.\n *\n * - adopted = the resolved tsconfig extends a `@hublo/sentinel/tsconfig/*` preset\n * - preset = that exact preset string (or null), the real one, not a derived guess\n * - conformant = adopted AND no preset-owned `compilerOptions` drift (i.e. a re-`update`\n * would strip nothing), the same drift the update guard manages\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { parseJsonc } from '../../shared/jsonc.js'\nimport { presetOwnedKeys } from './config-policy.js'\nimport { resolveTsconfigTarget } from './resolve-tsconfig-target.js'\n\n/** Matches a sentinel tsconfig preset subpath, e.g. `@hublo/sentinel/tsconfig/nest`. */\nconst SENTINEL_PRESET = /^@hublo\\/sentinel\\/tsconfig\\/[a-z-]+$/\n\nexport interface TsconfigAdoption {\n /** The tsconfig inspected (relative to the module), or null when there is none. */\n configFile: string | null\n /** The sentinel preset the module extends, or null when it extends none. */\n preset: string | null\n /** True when the module extends a sentinel preset. */\n adopted: boolean\n /** True when adopted AND drift-free (a re-`update` would change nothing). */\n conformant: boolean\n /** Preset-owned `compilerOptions` kept locally (the drift); empty when conformant. */\n drift: string[]\n}\n\n/** `extends` as a list: a string, a TS 5.0 array, or absent, all normalised. */\nfunction normaliseExtends(value: unknown): string[] {\n if (typeof value === 'string') return [value]\n if (Array.isArray(value))\n return value.filter((entry): entry is string => typeof entry === 'string')\n return []\n}\n\nconst NOT_ADOPTED = (configFile: string | null): TsconfigAdoption => ({\n configFile,\n preset: null,\n adopted: false,\n conformant: false,\n drift: [],\n})\n\n/** Read a module's TypeScript adoption + conformity from its committed tsconfig. */\nexport function readTsconfigAdoption(cwd: string): TsconfigAdoption {\n const target = resolveTsconfigTarget(cwd)\n if (target.reason === 'none' || !existsSync(join(cwd, target.path))) {\n return NOT_ADOPTED(target.reason === 'none' ? null : target.path)\n }\n\n let parsed: { extends?: unknown; compilerOptions?: Record<string, unknown> }\n try {\n parsed = parseJsonc(readFileSync(join(cwd, target.path), 'utf8'), target.path)\n } catch {\n return NOT_ADOPTED(target.path) // malformed config: can't be conformant\n }\n\n const preset =\n normaliseExtends(parsed.extends).find((entry) => SENTINEL_PRESET.test(entry)) ?? null\n if (preset === null) return NOT_ADOPTED(target.path)\n\n const drift = presetOwnedKeys(parsed.compilerOptions)\n return { configFile: target.path, preset, adopted: true, conformant: drift.length === 0, drift }\n}\n","/**\n * Resolve which tsconfig file `--update --typescript` should write in a module.\n *\n * The rule (from the monorepo audit): target the file that currently `extends` the\n * shared base config, that is the entry point sentinel's preset replaces. Two\n * shapes exist:\n * - Pattern A (libs, nest services): `tsconfig.json` extends the base.\n * - Pattern B (Vite React apps): `tsconfig.json` is references-only and\n * `tsconfig.app.json` extends the base.\n * We check `tsconfig.app.json` before `tsconfig.json` so B is found first. The\n * `reason` distinguishes the three cases `--update` must handle differently:\n * - `extends-base`: found the file to migrate.\n * - `other-chain`: a tsconfig exists but extends something else (svelte's own\n * `.svelte-kit` chain), don't clobber it; handled separately.\n * - `none`: no tsconfig at all; `--update` creates one.\n *\n * The \"find the file that extends a shared root\" pattern is reusable; it will be\n * lifted to core when a second tool needs its own version.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { parseJsonc } from '../../shared/jsonc.js'\n\n/**\n * A file is the target if its `extends` references either the monorepo base (still\n * to migrate) or a sentinel preset (already migrated, so re-`update` re-strips any\n * new drift, keeping it idempotent and self-cleaning).\n */\nconst TARGET_EXTENDS_MARKERS = ['tsconfig.base.json', '@hublo/sentinel/tsconfig/'] as const\n\n/** Candidate entry points, most-specific first (Pattern B before Pattern A). */\nconst CANDIDATES = ['tsconfig.app.json', 'tsconfig.json'] as const\n\nexport interface TsconfigTarget {\n /** The tsconfig file to write, relative to the module root. */\n path: string\n /** How it was chosen, for observability and the per-case `--update` behaviour. */\n reason: 'extends-base' | 'other-chain' | 'none'\n}\n\n/**\n * The `extends` targets of a tsconfig as a list. `extends` may be a string or, since\n * TypeScript 5.0, an array of strings; both are normalised here (absent/unreadable\n * or non-string entries yield an empty list).\n */\nfunction readExtends(absolutePath: string): string[] {\n let parsed: { extends?: unknown }\n try {\n parsed = parseJsonc(readFileSync(absolutePath, 'utf8'), absolutePath)\n } catch {\n return [] // malformed candidate: skip it, try the next\n }\n if (typeof parsed.extends === 'string') return [parsed.extends]\n if (Array.isArray(parsed.extends)) {\n return parsed.extends.filter((entry): entry is string => typeof entry === 'string')\n }\n return []\n}\n\nexport function resolveTsconfigTarget(moduleDir: string): TsconfigTarget {\n let existing: string | undefined\n for (const candidate of CANDIDATES) {\n const absolutePath = join(moduleDir, candidate)\n if (!existsSync(absolutePath)) continue\n existing ??= candidate // remember the first tsconfig we saw\n const extendsValues = readExtends(absolutePath)\n const extendsBase = extendsValues.some((value) =>\n TARGET_EXTENDS_MARKERS.some((marker) => value.includes(marker)),\n )\n if (extendsBase) {\n return { path: candidate, reason: 'extends-base' }\n }\n }\n if (existing) return { path: existing, reason: 'other-chain' }\n return { path: 'tsconfig.json', reason: 'none' }\n}\n","/**\n * TypeScript role registration. The single entry the bootstrap\n * (`src/adapters.ts`) imports, so wiring stays greppable and the CLI never\n * changes. Adds the tsc adapter and makes it the default runner for `--typescript`.\n */\nimport { register, setDefaultRunner } from '../../core/registry.js'\nimport { TscAdapter } from './adapters/tsc/tsc.adapter.js'\n\nexport function registerTypescript(): void {\n register(new TscAdapter())\n setDefaultRunner('typescript', 'tsc')\n}\n","/**\n * Adapter bootstrap: the single place adapters are wired into the CLI.\n *\n * A `register(new MyAdapter())` call only runs if its module is imported, and the\n * CLI must not import every tool by hand, that would make the promise \"one tool =\n * one adapter, the CLI never changes\" false. So the CLI calls `registerAdapters()`\n * once at startup, and each tool ticket adds exactly ONE line here (its role's\n * registration), never touching the CLI entry point (`bin/sentinel.ts`) or the\n * registry.\n *\n * Empty until the first tool ticket lands. A tool ticket adds, e.g. (an explicit\n * module path, not a barrel, so the wiring stays greppable):\n *\n * import { registerTypescript } from './roles/typescript/register.js'\n * export function registerAdapters(): void {\n * registerTypescript()\n * }\n */\nimport { registerTypescript } from './roles/typescript/register.js'\n\nexport function registerAdapters(): void {\n registerTypescript()\n}\n","/**\n * Framework detection from a module's package.json dependencies. DETERMINISTIC:\n * a dependency maps to exactly one flavour, and a module with no framework\n * dependency is a plain TypeScript library (`node`). This is not the old silent\n * guessing (there is no \"assume react\" fallback); `node` is a real preset.\n *\n * Reusable across tools: every tool's `--update` needs the module's flavour to\n * pick its preset, so this lives in core, not in the TypeScript role.\n */\nimport type { Flavour } from './domain.js'\n\n/** The slice of a package.json we read for detection. */\nexport interface PackageDependencies {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n}\n\n/**\n * A framework signal in priority order: the first whose package is present wins.\n * Ordered most-specific-first so a backend (`@nestjs/core`) is never shadowed by a\n * transitive `react`; `react` before `svelte` (svelte is being removed). Adding a\n * framework is one entry here.\n */\nconst FRAMEWORK_SIGNALS: ReadonlyArray<{ flavour: Flavour; dependency: string }> = [\n { flavour: 'nest', dependency: '@nestjs/core' },\n { flavour: 'react', dependency: 'react' },\n { flavour: 'svelte', dependency: 'svelte' },\n]\n\n/**\n * Backend frameworks that do NOT map to a flavour, but whose presence alongside a FRONTEND\n * flavour (react/svelte) means the module is mixed and the pick is probably wrong (the\n * tester's Sails + React root). They make detection ambiguous, not the choice. Deliberately\n * NOT including React meta-frameworks (e.g. `next`): a Next app legitimately has react +\n * next, and `react` is the correct pick, so it must not warn.\n */\nconst BACKEND_HINTS = ['sails', 'express', 'koa', 'fastify', '@hapi/hapi'] as const\n\n/** The detected flavour plus WHY, so a wrong silent guess is visible and can be flagged. */\nexport interface FrameworkDetection {\n flavour: Flavour\n /** The dependency that decided it, or a marker when none (node). */\n source: string\n /** True when the module carries signals for more than one stack: the pick is a guess. */\n ambiguous: boolean\n /** The other signals that make it ambiguous (extra flavour deps, or backend hints). */\n conflicts: string[]\n}\n\n/**\n * Detect the flavour AND explain it: the deciding dependency (`source`), and whether the\n * module is ambiguous (more than one flavour signal, or a frontend flavour next to a\n * backend framework). The CLI surfaces the source and warns on ambiguity, recommending\n * `--flavour`, instead of choosing silently.\n */\nexport function describeFramework(packageJson: PackageDependencies): FrameworkDetection {\n const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }\n const matched = FRAMEWORK_SIGNALS.filter((s) => s.dependency in dependencies)\n const chosen = matched[0]\n const flavour: Flavour = chosen?.flavour ?? 'node'\n const source = chosen?.dependency ?? '(no framework dependency)'\n\n const otherFlavourSignals = matched.slice(1).map((s) => s.dependency)\n const backendHints =\n flavour === 'react' || flavour === 'svelte'\n ? BACKEND_HINTS.filter((hint) => hint in dependencies)\n : []\n const conflicts = [...otherFlavourSignals, ...backendHints]\n return { flavour, source, ambiguous: conflicts.length > 0, conflicts }\n}\n\n/** The flavour for a module, from its dependencies. `node` when no framework. */\nexport function detectFramework(packageJson: PackageDependencies): Flavour {\n return describeFramework(packageJson).flavour\n}\n","/**\n * Small text helpers: `ensure-lines` for the engine, and a line diff for the\n * `--update --dry-run` preview.\n */\n\n/**\n * Append any of `lines` not already present in `current` (matched as a full,\n * trimmed line). Idempotent: running it twice adds nothing the second time.\n * Preserves a trailing newline and never duplicates existing lines.\n */\nexport function ensureLines(current: string, lines: string[]): string {\n const present = new Set(current.split('\\n').map((line) => line.trim()))\n const missing = lines.filter((line) => !present.has(line.trim()))\n if (missing.length === 0) return current\n const prefix = current.length === 0 || current.endsWith('\\n') ? current : current + '\\n'\n return prefix + missing.join('\\n') + '\\n'\n}\n\n/** Split into lines for diffing, dropping a single trailing newline (not content). */\nfunction toLines(text: string): string[] {\n if (text.length === 0) return []\n return text.replace(/\\n$/, '').split('\\n')\n}\n\n/**\n * A minimal line-level diff (LCS-based) between `before` and `after`. Returns one\n * string per line, prefixed `- ` (removed), `+ ` (added) or ` ` (unchanged), so a\n * dry-run can show exactly what a write would change instead of dumping the result.\n */\nexport function diffLines(before: string, after: string): string[] {\n const from = toLines(before)\n const to = toLines(after)\n // Longest common subsequence length table, filled bottom-up. `lcs[i][j]` is the\n // LCS length of from[i:] and to[j:]; reads past the edge are 0 (the base case).\n const lcs: number[][] = Array.from({ length: from.length + 1 }, () =>\n new Array<number>(to.length + 1).fill(0),\n )\n const cell = (i: number, j: number): number => lcs[i]?.[j] ?? 0\n for (let i = from.length - 1; i >= 0; i--) {\n const row = lcs[i]\n if (!row) continue\n for (let j = to.length - 1; j >= 0; j--) {\n row[j] = from[i] === to[j] ? cell(i + 1, j + 1) + 1 : Math.max(cell(i + 1, j), cell(i, j + 1))\n }\n }\n\n const out: string[] = []\n let i = 0\n let j = 0\n while (i < from.length && j < to.length) {\n if (from[i] === to[j]) {\n out.push(` ${from[i] ?? ''}`)\n i++\n j++\n } else if (cell(i + 1, j) >= cell(i, j + 1)) {\n out.push(`- ${from[i] ?? ''}`)\n i++\n } else {\n out.push(`+ ${to[j] ?? ''}`)\n j++\n }\n }\n while (i < from.length) out.push(`- ${from[i++] ?? ''}`)\n while (j < to.length) out.push(`+ ${to[j++] ?? ''}`)\n return out\n}\n","/**\n * Plan application: the engine's filesystem port.\n *\n * Adapters return a pure, declarative `UpdatePlan` (see `FileOperation`); this is\n * the ONE place that touches the disk. It resolves paths against the module root,\n * reads existing files, does the generic read/merge/write mechanics, and writes.\n * Keeping all IO here is what lets adapters stay pure and decoupled from the repo\n * layout: they say WHAT to change, the engine knows HOW and WHERE.\n *\n * Three guarantees, so `--update` is safe to run on real projects:\n * - CONTENT-PRESERVING merge: `merge-json` edits the file in place via\n * jsonc-parser, so a tsconfig's comments, key order and formatting survive.\n * - PREPARED-THEN-WRITTEN: the whole plan is computed before any write, so an\n * invalid operation fails before touching disk, and each file is written\n * atomically (temp file + rename) so an interrupted write never leaves a\n * truncated file. (Across multiple files the writes are sequential, not one\n * transaction: a crash mid-plan can leave earlier files written, recoverable\n * via git; a single file is always all-or-nothing.)\n * - CONFINED: every path is resolved and rejected if it escapes the module root.\n */\nimport { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { resolve, sep } from 'node:path'\n\nimport { applyEdits, modify } from 'jsonc-parser'\n\nimport { isPlainObject } from '../shared/deep-merge.js'\nimport { ensureLines } from '../shared/text.js'\nimport type { FileOperation, UpdatePlan } from './types.js'\n\n/** Resolve a plan-relative path and reject anything escaping the module root. */\nfunction resolveWithinRoot(cwd: string, relativePath: string): string {\n const root = resolve(cwd)\n const absolutePath = resolve(root, relativePath)\n if (absolutePath !== root && !absolutePath.startsWith(root + sep)) {\n throw new Error(`Refusing to write outside the module root: \"${relativePath}\".`)\n }\n return absolutePath\n}\n\nfunction readIfExists(absolutePath: string): string | undefined {\n return existsSync(absolutePath) ? readFileSync(absolutePath, 'utf8') : undefined\n}\n\n/** Yield every leaf ([path, value]) of a nested object, for deep in-place edits. */\nfunction* leaves(\n value: Record<string, unknown>,\n prefix: string[] = [],\n): Generator<[path: string[], leaf: unknown]> {\n for (const [key, keyValue] of Object.entries(value)) {\n const path = [...prefix, key]\n if (isPlainObject(keyValue)) yield* leaves(keyValue, path)\n else yield [path, keyValue]\n }\n}\n\n/**\n * Deep-merge `value` into JSONC `current` IN PLACE (preserving comments/format).\n * Each leaf is set at its own path, so sibling keys, including a project's own\n * `paths`/`include`, survive untouched.\n */\nfunction mergeJsonc(current: string, value: Record<string, unknown>): string {\n let text = current.trim().length > 0 ? current : '{}\\n'\n for (const [path, leaf] of leaves(value)) {\n const edits = modify(text, path, leaf, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n })\n text = applyEdits(text, edits)\n }\n return text.endsWith('\\n') ? text : text + '\\n'\n}\n\n/** Remove each key path from JSONC `current`, preserving comments/formatting. */\nfunction removeJsoncKeys(current: string, keys: string[][]): string {\n let text = current.trim().length > 0 ? current : '{}\\n'\n for (const path of keys) {\n const edits = modify(text, path, undefined, {\n formattingOptions: { insertSpaces: true, tabSize: 2 },\n })\n text = applyEdits(text, edits)\n }\n return text.endsWith('\\n') ? text : text + '\\n'\n}\n\n/** Apply one operation to the current file content (pure transform). */\nfunction applyOperationTo(current: string, operation: FileOperation): string {\n switch (operation.kind) {\n case 'write':\n return operation.contents\n case 'merge-json':\n return mergeJsonc(current, operation.value)\n case 'ensure-lines':\n return ensureLines(current, operation.lines)\n case 'remove-json-keys':\n return removeJsoncKeys(current, operation.keys)\n default: {\n // Exhaustiveness: a new FileOperation kind without a case here fails to compile.\n const unreachable: never = operation\n throw new Error(`Unknown file operation: ${JSON.stringify(unreachable)}`)\n }\n }\n}\n\n/** A file the plan would write: its original content and the computed result. */\nexport interface PreparedFile {\n path: string\n absolutePath: string\n before: string\n after: string\n}\n\n/**\n * Compute what a plan WOULD write, without touching the disk (for `--dry-run`).\n * All-or-nothing (throws before returning anything on a bad op), and multiple\n * operations on the SAME file chain in order, so `before` is the original content\n * and `after` is the final result.\n */\nexport function preparePlan(cwd: string, plan: UpdatePlan): PreparedFile[] {\n const prepared = new Map<string, PreparedFile>()\n for (const operation of plan.operations) {\n const absolutePath = resolveWithinRoot(cwd, operation.path)\n const existing = prepared.get(operation.path)\n const before = existing?.before ?? readIfExists(absolutePath) ?? ''\n const current = existing?.after ?? before\n prepared.set(operation.path, {\n path: operation.path,\n absolutePath,\n before,\n after: applyOperationTo(current, operation),\n })\n }\n return [...prepared.values()]\n}\n\n/**\n * Write `contents` to `absolutePath` atomically: write a sibling temp file, then\n * rename it over the target. rename is atomic on a single filesystem, so a reader\n * (or an interrupted run) never sees a half-written file, only the old or new one.\n */\nfunction writeFileAtomic(absolutePath: string, contents: string): void {\n const tempPath = `${absolutePath}.sentinel-${process.pid}.tmp`\n writeFileSync(tempPath, contents)\n renameSync(tempPath, absolutePath)\n}\n\n/**\n * Apply a plan against the module root; return the paths written. Prepares the\n * whole plan before the first write, then writes each file atomically.\n */\nexport function applyPlan(cwd: string, plan: UpdatePlan): string[] {\n const prepared = preparePlan(cwd, plan)\n for (const file of prepared) writeFileAtomic(file.absolutePath, file.after)\n return prepared.map((file) => file.path)\n}\n","/**\n * Generic deep-merge for plain JSON objects. Used by the engine to apply a\n * `merge-json` op (pin the keys sentinel owns while preserving a project's own).\n */\n\n/** True for a mergeable plain object (not null, not an array). */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Deep-merge `patch` into `base`, preserving keys `patch` does not mention.\n * Scalars and arrays from `patch` replace wholesale (no array concat surprises).\n */\nexport function deepMerge(\n base: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n const existing = out[key]\n out[key] = isPlainObject(existing) && isPlainObject(value) ? deepMerge(existing, value) : value\n }\n return out\n}\n","/**\n * The single-module WRITE path (`--update`). The read verbs (`--run`/`--inspect`/\n * `--report`/`--status`) go through the multi-module orchestrator (`analyse`), which\n * resolves each module's adapter and calls the matching method; `dispatch` is what\n * `--update` uses because a write targets exactly one module.\n *\n * The engine owns IO: it resolves the flavour (explicit `--flavour`, else\n * `detectFramework` on the module's package.json), hands the adapter a normalized\n * `UpdateContext`; the adapter plans, the engine applies (`applyPlan`) or previews.\n */\nimport { palette } from '../shared/color.js'\nimport { readProjectPackageJson } from '../shared/package-json.js'\nimport { diffLines } from '../shared/text.js'\nimport { applyPlan, preparePlan } from './apply-plan.js'\nimport { describeFramework } from './detect-framework.js'\nimport type { Flavour, Target, Verb } from './domain.js'\nimport { resolve } from './registry.js'\nimport type { UpdateContext, UpdatePlan } from './types.js'\n\nexport type { Verb }\n\nexport interface DispatchOptions {\n /** Always `update` (dispatch is the write path); other verbs route through `analyse`. */\n verb: Verb\n target: Target\n runner?: string\n cwd: string\n /** Declared flavour (`--flavour`), else resolved by `detectFramework` from deps. */\n flavour?: Flavour\n /** `--dry-run`: preview the plan instead of writing anything. */\n dryRun?: boolean\n /** `--json`: emit the dry-run preview as machine-readable JSON. */\n json?: boolean\n}\n\n/**\n * The flavour for the update: the explicit one, else detected from deps. `--update` WRITES\n * a preset, so an ambiguous detection is risky; warn and point at `--flavour` rather than\n * silently writing the wrong preset.\n */\nfunction resolveFlavour(opts: DispatchOptions): Flavour {\n if (opts.flavour) return opts.flavour\n const detection = describeFramework(readProjectPackageJson(opts.cwd))\n if (detection.ambiguous) {\n const warn = palette(process.stderr)\n process.stderr.write(\n warn.warn(\n `sentinel: flavour is ambiguous, detected \"${detection.flavour}\" (from ${detection.source}), also found ${detection.conflicts.join(', ')}. Pass --flavour to write the intended preset.`,\n ) + '\\n',\n )\n }\n return detection.flavour\n}\n\n/**\n * `--update --dry-run`: show what the plan WOULD change and write nothing. Prints a\n * per-file line diff (or JSON with `--json`), plus the adapter's notes. An empty\n * plan is reported as \"nothing to change\" so the developer gets a clear signal\n * rather than silence. Always exits 0: previewing never fails a build.\n */\nfunction previewPlan(opts: DispatchOptions, plan: UpdatePlan): number {\n // Only files the plan would actually change: an idempotent re-run computes an\n // `after` equal to `before`, which is a no-op, not a change to preview.\n const changed = preparePlan(opts.cwd, plan).filter((file) => file.before !== file.after)\n if (opts.json) {\n process.stdout.write(\n JSON.stringify(\n {\n dryRun: true,\n notes: plan.notes ?? [],\n files: changed.map(({ path, before, after }) => ({\n path,\n action: before.length === 0 ? 'create' : 'update',\n before,\n after,\n })),\n },\n null,\n 2,\n ) + '\\n',\n )\n return 0\n }\n\n process.stderr.write(' dry run: no files written\\n')\n for (const note of plan.notes ?? []) process.stderr.write(` ${note}\\n`)\n if (changed.length === 0) {\n process.stderr.write(' nothing to change\\n')\n return 0\n }\n for (const { path, before, after } of changed) {\n const action = before.length === 0 ? 'create' : 'update'\n process.stdout.write(`\\n ${action} ${path}\\n`)\n for (const line of diffLines(before, after)) process.stdout.write(` ${line}\\n`)\n }\n return 0\n}\n\nexport async function dispatch(opts: DispatchOptions): Promise<number> {\n // dispatch is the write path only; read verbs route through `analyse`. Guard so a\n // future miswire fails loudly here instead of silently doing nothing.\n if (opts.verb !== 'update') {\n throw new Error(`dispatch handles --update only; --${opts.verb} routes through analyse()`)\n }\n const adapter = resolve(opts.target, opts.flavour, opts.runner)\n const flavour = resolveFlavour(opts)\n\n // Engine resolves + normalizes the context; the adapter plans, the engine applies the\n // operations against the module root (or previews them with --dry-run).\n const context: UpdateContext = { cwd: opts.cwd, flavour }\n const plan = await adapter.plan(context)\n if (opts.dryRun) {\n // Preview only: compute what WOULD be written, touch nothing on disk.\n return previewPlan(opts, plan)\n }\n const written = applyPlan(opts.cwd, plan)\n for (const path of written) process.stderr.write(` wrote ${path}\\n`)\n for (const note of plan.notes ?? []) process.stderr.write(` ${note}\\n`)\n return 0\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","existsSync","dirname","join","existsSync","readFileSync","join","existsSync","readFileSync","join","readFileSync","join","existsSync","existsSync","join","readFileSync","readFileSync","join","existsSync","i","j","existsSync","readFileSync","resolve","resolve","existsSync","readFileSync"]}