@mmnto/totem 1.53.3 → 1.53.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Version-pinned parity drift detector (PR-1, mmnto-ai/totem#2069).
3
+ *
4
+ * First detection slice on top of the merged manifest-parser skeleton (#2070).
5
+ * Senses ONE tractability class — `version-pinned` — and only its DEPS subset
6
+ * (the four `@mmnto/*` cohort-floor contracts: `mmnto-cli-version`,
7
+ * `mmnto-totem-version`, `mmnto-mcp-version`,
8
+ * `mmnto-pack-rust-architecture-version`). The verdict is **pin currency only**
9
+ * (Tenet 20 claim-class bound): does the consumer's `@mmnto/*` pin resolve to
10
+ * the current published cohort floor? It NEVER asserts semantic / file-content
11
+ * drift — that would over-claim the `version-pinned` class.
12
+ *
13
+ * Layering: core must NOT import cli's `DiagnosticResult` (wrong dependency
14
+ * direction). This module returns a core-local `ParityContractVerdict`; the CLI
15
+ * (`doctor-parity.ts`) maps it to `DiagnosticResult` and owns the
16
+ * `--strict`/`blocking` fail-promotion. The detector itself returns ONLY
17
+ * `pass`/`warn`/`skip` — never `fail` — so the gate edge stays a CLI concern.
18
+ *
19
+ * Design invariants (mirroring `parity-manifest.ts` + `strategy-resolver.ts`):
20
+ * - **Honest-absent (Tenet 14):** absence is never an error. Not-a-consumer,
21
+ * floor-unresolvable, or a doctrine pin this slice doesn't handle → `skip`
22
+ * (the manifest's `-` "cohort permits absence"). An *applicable* consumer
23
+ * missing an expected pin is also `skip` while the manifest is scaffold, but
24
+ * kept DISTINCT (expected-but-absent → a `warn` once the consumers lists are
25
+ * verified) so the `consumers` field still catches the missing case. Never a
26
+ * fabricated verdict.
27
+ * - **NEVER networks:** the cohort floor is derived LOCALLY — self-in-tree
28
+ * (the totem monorepo at the current git root) or a `../totem` sibling
29
+ * checkout. Neither reachable → honest-absent `skip` with a reason.
30
+ * - **Side-effect-free / no caching:** every call reads from scratch. Each
31
+ * filesystem / git seam is injectable so tests drive synthetic fixtures.
32
+ * - **Never throws:** every read failure degrades to a `skip`/`warn` verdict;
33
+ * the sensor must never crash the doctor pipeline.
34
+ */
35
+ import * as fs from 'node:fs';
36
+ import * as path from 'node:path';
37
+ import semver from 'semver';
38
+ import { safeExec } from './sys/exec.js';
39
+ import { resolveGitRoot } from './sys/git.js';
40
+ // ─── Constants ──────────────────────────────────────────
41
+ /**
42
+ * Fixed scope for every PR-1 deps contract. The id slug (`mmnto-<x>-version`)
43
+ * is the literal middle of the package name `@mmnto/<x>`; the scope is constant.
44
+ */
45
+ const MMNTO_SCOPE = '@mmnto/';
46
+ /** The `mmnto-…-version` id convention PR-1 maps to a deps package name. */
47
+ const DEPS_CONTRACT_ID = /^mmnto-(.+)-version$/;
48
+ /** Sibling totem-monorepo dirname probed for a cohort floor (mirrors `resolveStrategyRoot` layer 3). */
49
+ const SIBLING_TOTEM_DIRNAME = 'totem';
50
+ /** Bounded git-command timeout for the origin-remote probe (matches `sys/git.ts`). */
51
+ const GIT_REMOTE_TIMEOUT_MS = 15_000;
52
+ /**
53
+ * The dep fields, in resolution order, that a consumer can declare an `@mmnto/*`
54
+ * pin under. `dependencies` first so a normal runtime dep wins display, but the
55
+ * detector treats a declaration in ANY of these as "the pin is declared here."
56
+ */
57
+ const DEP_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies'];
58
+ /**
59
+ * Derive the current repo's cohort id (e.g. `totem-status`) used to evaluate a
60
+ * contract's `consumers` applicability. Precedence:
61
+ * 1. git `origin` remote — `…mmnto-ai/<name>(.git)?` → `<name>`.
62
+ * 2. `package.json` `name` basename (scope stripped) — `@mmnto/totem-status`
63
+ * → `totem-status`.
64
+ * 3. git-root directory basename.
65
+ *
66
+ * Returns `undefined` only when nothing resolves. NEVER throws and NEVER
67
+ * networks beyond the local `git remote get-url` read (which is swallowed on
68
+ * failure — git being unavailable is a routine fall-through, not an error).
69
+ */
70
+ export function deriveCohortRepoId(cwd, options = {}) {
71
+ // ── 1. git origin remote ──
72
+ const remoteUrl = readOriginRemote(cwd, options);
73
+ const fromRemote = repoIdFromRemoteUrl(remoteUrl);
74
+ if (fromRemote !== undefined)
75
+ return fromRemote;
76
+ // Anchor the fallbacks at the git root (not the deep cwd) so the dir-basename
77
+ // fallback names the repo, not a nested subdir. Lazy: only probed when the
78
+ // remote miss forces a fallback.
79
+ const root = resolveRootForFallback(cwd, options.gitRoot);
80
+ // ── 2. package.json name basename (scope stripped) ──
81
+ const fromPkg = repoIdFromPackageName(root);
82
+ if (fromPkg !== undefined)
83
+ return fromPkg;
84
+ // ── 3. git-root dir basename ──
85
+ const base = path.basename(root);
86
+ return base.length > 0 ? base : undefined;
87
+ }
88
+ /**
89
+ * Read the git `origin` remote URL, honoring the injected seams. A throwing
90
+ * reader (or a real git failure) is swallowed — the caller falls through to the
91
+ * package.json / dir-basename fallbacks.
92
+ */
93
+ function readOriginRemote(cwd, options) {
94
+ if (options.readRemote !== undefined) {
95
+ try {
96
+ return options.readRemote(cwd);
97
+ // totem-context: an injected remote-reader that throws is treated as "no remote resolvable" (fall through to the package.json / dir-basename fallbacks); rethrowing would break the never-throws contract for a routine git miss.
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ if ('remoteUrl' in options)
104
+ return options.remoteUrl;
105
+ try {
106
+ return safeExec('git', ['remote', 'get-url', 'origin'], {
107
+ cwd,
108
+ timeout: GIT_REMOTE_TIMEOUT_MS,
109
+ });
110
+ // totem-context: a missing remote / non-git dir / absent git binary is a routine fall-through to the package.json + dir-basename fallbacks, not a sensor failure — the doctor runs against repos without an origin remote by design.
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ }
116
+ /** Extract `<name>` from a `…mmnto-ai/<name>(.git)?` remote URL, else undefined. */
117
+ function repoIdFromRemoteUrl(remoteUrl) {
118
+ if (typeof remoteUrl !== 'string' || remoteUrl.trim().length === 0)
119
+ return undefined;
120
+ // Match the org segment in both ssh (`git@…:mmnto-ai/x.git`) and https
121
+ // (`https://…/mmnto-ai/x.git`) forms; tolerate a trailing `.git` + slashes.
122
+ const match = remoteUrl.trim().match(/mmnto-ai\/([^/\s]+?)(?:\.git)?\/?$/);
123
+ return match?.[1];
124
+ }
125
+ /** Read `package.json` `name`, strip a leading `@scope/`, return the basename. */
126
+ function repoIdFromPackageName(root) {
127
+ const name = readPackageName(path.join(root, 'package.json'));
128
+ if (name === undefined)
129
+ return undefined;
130
+ // `@mmnto/totem-status` → `totem-status`; `plain-name` → `plain-name`.
131
+ const slash = name.lastIndexOf('/');
132
+ const base = slash === -1 ? name : name.slice(slash + 1);
133
+ return base.length > 0 ? base : undefined;
134
+ }
135
+ /** Resolve the anchor for fallbacks: injected gitRoot, else `resolveGitRoot(cwd)`, else cwd. */
136
+ function resolveRootForFallback(cwd, injected) {
137
+ if (injected !== undefined)
138
+ return injected ?? cwd;
139
+ try {
140
+ return resolveGitRoot(cwd) ?? cwd;
141
+ // totem-context: resolveGitRoot throws on permission errors / corrupted index; falling back to cwd keeps the dir-basename fallback working rather than crashing the sensor on a git hiccup.
142
+ }
143
+ catch {
144
+ return cwd;
145
+ }
146
+ }
147
+ // ─── packageNameForContract ─────────────────────────────
148
+ /**
149
+ * Resolve the `@mmnto/*` (or vendor) package name a deps/vendor contract pins.
150
+ * Precedence:
151
+ * 1. **explicit `package:` field** (mmnto-ai/totem-strategy#517) — the
152
+ * machine-parseable name, derive-not-guess. Preferred when present.
153
+ * 2. **canonical-source path locator** — when `canonicalSource` carries a
154
+ * `:path/to/package.json` segment (e.g. `mmnto-cli-version`'s
155
+ * `mmnto-ai/totem:packages/cli/package.json#version`), read the `name`
156
+ * from that package.json under `floorRoot`. Authoritative — no id guess.
157
+ * 3. **id convention** — `mmnto-<x>-version` → `@mmnto/<x>` (fallback until all
158
+ * contracts carry `package:`).
159
+ *
160
+ * Returns `undefined` for contracts with no `package:`, no path locator, and an
161
+ * id that doesn't match the convention (e.g. `governance-doctrine`, `gate-config`)
162
+ * so ONLY the contracts this slice handles resolve a name; the CLI keeps the rest
163
+ * as `skip` stubs.
164
+ *
165
+ * @param floorRoot Optional root the canonical-source path locator anchors at
166
+ * (the resolved cohort-floor repo). Omit when only `package:` /
167
+ * the id convention is wanted.
168
+ */
169
+ export function packageNameForContract(contract, floorRoot) {
170
+ // ── 1. explicit `package:` field (derive-not-guess; strategy#517) ──
171
+ if (typeof contract.package === 'string' && contract.package.trim().length > 0) {
172
+ return contract.package.trim();
173
+ }
174
+ // ── 2. canonical-source path locator → read `name` from that package.json ──
175
+ if (floorRoot !== undefined) {
176
+ const fromLocator = packageNameFromCanonicalSource(contract.canonicalSource, floorRoot);
177
+ if (fromLocator !== undefined)
178
+ return fromLocator;
179
+ }
180
+ // ── 3. id convention (mmnto-<x>-version → @mmnto/<x>) ──
181
+ const match = contract.id.match(DEPS_CONTRACT_ID);
182
+ if (match?.[1] === undefined)
183
+ return undefined;
184
+ // Concatenate (not template-interpolate) so the `@mmnto/` scope's own trailing
185
+ // `/` reads as the delimiter between scope and package slug.
186
+ return MMNTO_SCOPE + match[1];
187
+ }
188
+ /**
189
+ * Read the `name` from the package.json referenced by a `repo:path#fragment`
190
+ * canonical-source locator, anchored at `floorRoot`. Returns undefined when the
191
+ * locator has no `:package.json` path segment or the file is unreadable.
192
+ */
193
+ function packageNameFromCanonicalSource(canonicalSource, floorRoot) {
194
+ if (typeof canonicalSource !== 'string')
195
+ return undefined;
196
+ // Shape: `<repo>:<path>#<fragment>` — we only want the `<path>` between the
197
+ // first `:` and an optional `#`. A bare `mmnto-ai/totem` (no `:`) has no path.
198
+ const colon = canonicalSource.indexOf(':');
199
+ if (colon === -1)
200
+ return undefined;
201
+ const afterColon = canonicalSource.slice(colon + 1);
202
+ const hash = afterColon.indexOf('#');
203
+ const relPath = (hash === -1 ? afterColon : afterColon.slice(0, hash)).trim();
204
+ if (!relPath.endsWith('package.json'))
205
+ return undefined;
206
+ return readPackageName(path.join(floorRoot, relPath));
207
+ }
208
+ /**
209
+ * Resolve the "current published version" cohort floor for `packageName`,
210
+ * derived LOCALLY (NEVER networks), in precedence order:
211
+ * (a) **self-in-tree** — the current git root IS the canonical-source repo
212
+ * (the totem monorepo): glob `<gitRoot>/packages/*​/package.json`, find
213
+ * the one whose `name === packageName`, read its `version`.
214
+ * (b) **sibling** — `<gitRoot>/../totem` exists as a directory: glob its
215
+ * `packages/*​/package.json` the same way.
216
+ * (c) **honest-absent** — neither reachable → `{ resolved: false, reason }`.
217
+ *
218
+ * NEVER fabricates a floor and NEVER fetches. Anchors at `gitRoot`, not cwd
219
+ * (mirroring `strategy-resolver.ts`). Read failures within the glob are
220
+ * swallowed per-file so one corrupt package.json can't crash the resolver.
221
+ *
222
+ * The floor is keyed structurally on `packageName` (the matching
223
+ * `packages/*​/package.json` `name`), NOT on the consumer's cohort id — a
224
+ * misderived repoId can't mask a genuine in-tree floor.
225
+ */
226
+ export function resolveCohortFloor(packageName, gitRoot) {
227
+ // ── (a) self-in-tree ──
228
+ // The canonical-source repo for every deps contract is the totem monorepo. If
229
+ // the current repo IS that monorepo, its own packages/*/package.json is the
230
+ // floor. We detect "is the monorepo" structurally (a packages/*/package.json
231
+ // whose name === packageName), not just by id, so a misderived repoId can't
232
+ // mask a genuine in-tree floor.
233
+ const selfVersion = readVersionFromPackagesGlob(gitRoot, packageName);
234
+ if (selfVersion !== undefined) {
235
+ return { resolved: true, version: selfVersion, source: 'self-in-tree' };
236
+ }
237
+ // ── (b) sibling ../totem ──
238
+ const sibling = path.normalize(path.join(gitRoot, '..', SIBLING_TOTEM_DIRNAME));
239
+ if (isDirectory(sibling)) {
240
+ const siblingVersion = readVersionFromPackagesGlob(sibling, packageName);
241
+ if (siblingVersion !== undefined) {
242
+ return { resolved: true, version: siblingVersion, source: 'sibling' };
243
+ }
244
+ }
245
+ // ── (c) honest-absent ──
246
+ return {
247
+ resolved: false,
248
+ reason: `cohort floor for ${packageName} not locally determinable; clone mmnto-ai/totem as a sibling (../${SIBLING_TOTEM_DIRNAME}) or run from the totem monorepo`,
249
+ };
250
+ }
251
+ /**
252
+ * Glob `<root>/packages/*​/package.json`, return the `version` of the one whose
253
+ * `name === packageName`. Returns undefined when `<root>/packages` is absent,
254
+ * no package matches, or every read fails. Per-file read failures are swallowed
255
+ * so one corrupt manifest doesn't sink the whole probe.
256
+ */
257
+ function readVersionFromPackagesGlob(root, packageName) {
258
+ const packagesDir = path.join(root, 'packages');
259
+ let names;
260
+ try {
261
+ names = fs.readdirSync(packagesDir);
262
+ // totem-context: an absent or unreadable packages/ dir is the "not the monorepo here" signal — return undefined so the resolver falls through to the sibling / honest-absent layer rather than throwing.
263
+ }
264
+ catch {
265
+ return undefined;
266
+ }
267
+ for (const name of names) {
268
+ const entryPath = path.join(packagesDir, name);
269
+ // Use the statSync-based `isDirectory` (follows symlinks) rather than a
270
+ // `Dirent.isDirectory()` filter — pnpm / workspace setups symlink package
271
+ // dirs, which the lstat-like Dirent check reports as non-directories and
272
+ // would skip (GCA review #2071).
273
+ if (!isDirectory(entryPath))
274
+ continue;
275
+ const parsed = readPackageJson(path.join(entryPath, 'package.json'));
276
+ if (parsed?.name === packageName && typeof parsed.version === 'string') {
277
+ return parsed.version;
278
+ }
279
+ }
280
+ return undefined;
281
+ }
282
+ /**
283
+ * Detect drift for ONE `version-pinned` deps contract. Returns a
284
+ * `ParityContractVerdict`:
285
+ * - **pass** — the consumer's resolved-installed `@mmnto/*` version is ≥ the
286
+ * cohort floor (current).
287
+ * - **warn** — installed < floor (stale pin). Sensor-not-gate default: the
288
+ * detector returns `warn` even for a `blocking` contract; the CLI promotes
289
+ * to `fail` only under `--strict`.
290
+ * - **skip** — not-a-consumer / pin not declared / floor unresolvable / not a
291
+ * deps contract this slice handles / unparseable range (honest-absent).
292
+ *
293
+ * NEVER emits `fail` (CLI-edge concern). NEVER throws (read failures degrade to
294
+ * `skip`). NEVER networks (floor is local-only). NEVER asserts content drift
295
+ * (claim-class bound to pin currency).
296
+ */
297
+ export function detectVersionPinnedContract(contract, ctx) {
298
+ // ── Applicability: consumers list ──
299
+ if (contract.consumers !== undefined) {
300
+ if (ctx.repoId === undefined) {
301
+ // Can't decide applicability without a cohort id — surface it (honest-
302
+ // absent) rather than silently treating the contract as applicable, which
303
+ // would make the consumers scope a no-op for this repo (Tenet 4 / Greptile
304
+ // review #2071).
305
+ return {
306
+ status: 'skip',
307
+ message: `cannot determine applicability — repo id unresolvable; contract is scoped to consumers [${contract.consumers.join(', ')}]`,
308
+ };
309
+ }
310
+ if (!contract.consumers.includes(ctx.repoId)) {
311
+ return {
312
+ status: 'skip',
313
+ message: `cohort permits absence here (${ctx.repoId} not in consumers)`,
314
+ };
315
+ }
316
+ }
317
+ // ── Resolve the package name (`package:` → locator → id convention) ──
318
+ // Prefer a name pre-resolved by the CLI (avoids re-reading a locator's
319
+ // package.json — Greptile review #2071); fall back to resolving here for
320
+ // standalone callers (tests). gitRoot is the floorRoot for a path locator.
321
+ const packageName = ctx.packageName ?? packageNameForContract(contract, ctx.gitRoot);
322
+ if (packageName === undefined) {
323
+ return {
324
+ status: 'skip',
325
+ message: `not a deps version-pinned contract this slice handles (${contract.id})`,
326
+ };
327
+ }
328
+ // ── Read the consumer package.json + its declared range for this pkg ──
329
+ const readPkg = ctx.readPackageJson ?? readPackageJson;
330
+ const consumerPkg = readPkg(path.join(ctx.cwd, 'package.json'));
331
+ const declaredRange = consumerPkg ? findDeclaredRange(consumerPkg, packageName) : undefined;
332
+ if (declaredRange === undefined) {
333
+ // Applicable-but-missing: we already passed the `consumers` gate above, so
334
+ // this repo IS an applicable consumer (in `consumers`, or `consumers` absent
335
+ // = applies to all) — an expected pin that's absent is drift, NOT
336
+ // cohort-permitted absence. Held as
337
+ // a skip-with-note while the manifest is scaffold (consumers lists are
338
+ // best-effort); flips to `warn` once verified. Kept DISTINCT from the
339
+ // not-a-consumer skip so the `consumers` field still does its job (strategy
340
+ // ack 2026-06-03T0156Z, design-call 3).
341
+ return {
342
+ status: 'skip',
343
+ message: `${packageName} expected but not declared — applicable consumer (scaffold: skip; becomes a drift warn once consumers are verified)`,
344
+ remediation: `Declare ${packageName} in this repo's package.json, or drop this repo from the contract's consumers list if it genuinely does not apply here.`,
345
+ };
346
+ }
347
+ // Guard an unparseable declared range BEFORE resolving anything else — a
348
+ // garbage range can't yield a currency verdict, so honest-absent skip.
349
+ if (semver.validRange(declaredRange) === null) {
350
+ return {
351
+ status: 'skip',
352
+ message: `${packageName} declared range "${declaredRange}" is not a valid semver range`,
353
+ };
354
+ }
355
+ // ── Resolve the consumer's installed version (fallback: minVersion(range)) ──
356
+ const installed = resolveInstalledVersion(ctx.cwd, packageName, declaredRange);
357
+ if (installed === undefined) {
358
+ return {
359
+ status: 'skip',
360
+ message: `${packageName} pinned (${declaredRange}) but no resolvable installed version`,
361
+ };
362
+ }
363
+ // ── Resolve the cohort floor (local-only; honest-absent on miss) ──
364
+ const floor = resolveCohortFloor(packageName, ctx.gitRoot);
365
+ if (!floor.resolved) {
366
+ return { status: 'skip', message: floor.reason };
367
+ }
368
+ // Guard an invalid floor version (corrupt in-tree manifest) → honest-absent.
369
+ if (semver.valid(floor.version) === null) {
370
+ return {
371
+ status: 'skip',
372
+ message: `cohort floor for ${packageName} ("${floor.version}") is not a valid version`,
373
+ };
374
+ }
375
+ // ── Verdict: pin currency only (semver compare) ──
376
+ // installed ≥ floor → current; installed < floor → stale. The claim is pin
377
+ // currency, NOT semantic content — a version-pinned contract may never assert
378
+ // file/content equality.
379
+ if (semver.gte(installed, floor.version)) {
380
+ return {
381
+ status: 'pass',
382
+ message: `${packageName} pin current — installed ${installed} ≥ cohort floor ${floor.version} (${floor.source})`,
383
+ };
384
+ }
385
+ return {
386
+ status: 'warn',
387
+ message: `${packageName} pin stale — declared ${declaredRange}, installed ${installed} < cohort floor ${floor.version} (${floor.source})`,
388
+ remediation: `Bump the ${packageName} dependency to >= ${floor.version} and reinstall, then re-run totem doctor --parity.`,
389
+ };
390
+ }
391
+ /**
392
+ * Resolve the consumer's installed `@mmnto/*` version. Walks UP the directory
393
+ * tree from `cwd` reading `<dir>/node_modules/<pkg>/package.json#version` at each
394
+ * ancestor — monorepo / pnpm / npm-workspace installs hoist deps to a parent or
395
+ * root `node_modules` rather than the sub-package's, so a cwd-only read would
396
+ * miss them (GCA review #2071). Mirrors Node's own upward node_modules
397
+ * resolution. On no hit, falls back to `semver.minVersion(declaredRange)` (the
398
+ * floor the caret range implies). Returns undefined only when neither resolves
399
+ * to a valid version.
400
+ */
401
+ function resolveInstalledVersion(cwd, packageName, declaredRange) {
402
+ const segments = packageName.split('/');
403
+ let dir = path.resolve(cwd);
404
+ for (;;) {
405
+ const installedPkg = readPackageJson(path.join(dir, 'node_modules', ...segments, 'package.json'));
406
+ if (installedPkg?.version !== undefined && semver.valid(installedPkg.version) !== null) {
407
+ return installedPkg.version;
408
+ }
409
+ const parent = path.dirname(dir);
410
+ if (parent === dir)
411
+ break; // reached the filesystem root
412
+ dir = parent;
413
+ }
414
+ // Fallback: the minimum version the declared range admits. `minVersion`
415
+ // returns a SemVer or null; coerce to the bare version string.
416
+ const min = semver.minVersion(declaredRange);
417
+ return min?.version;
418
+ }
419
+ /** Find the declared range for `packageName` across the three dep fields, in order. */
420
+ function findDeclaredRange(pkg, packageName) {
421
+ for (const field of DEP_FIELDS) {
422
+ // `pkg` is loose untrusted JSON; optional-chain the field read so a missing
423
+ // or non-object dep section yields undefined instead of throwing, then
424
+ // type-check the value itself. Avoids a null-guard idiom that two compiled
425
+ // rules disagree on.
426
+ const range = pkg[field]?.[packageName];
427
+ if (typeof range === 'string' && range.trim().length > 0)
428
+ return range;
429
+ }
430
+ return undefined;
431
+ }
432
+ // ─── Shared filesystem helpers ──────────────────────────
433
+ /**
434
+ * Read + JSON-parse a package.json into the loose {@link PackageJsonShape}.
435
+ * Returns undefined on any read / parse failure or a non-object payload —
436
+ * mirroring `readEngineRange`'s ENOENT-tolerant JSON read so a missing or
437
+ * corrupt manifest is honest-absent, never a throw.
438
+ */
439
+ function readPackageJson(absPath) {
440
+ let raw;
441
+ try {
442
+ raw = fs.readFileSync(absPath, 'utf-8');
443
+ // totem-context: a missing / unreadable package.json is the honest-absent signal callers degrade to a skip on; rethrowing would force every caller to wrap a routine absence in try/catch.
444
+ }
445
+ catch {
446
+ return undefined;
447
+ }
448
+ let parsed;
449
+ try {
450
+ parsed = JSON.parse(raw);
451
+ // totem-context: a corrupt package.json degrades to undefined (honest-absent skip), not a crash — the sensor must never sink the doctor pipeline on a malformed consumer manifest.
452
+ }
453
+ catch {
454
+ return undefined;
455
+ }
456
+ if (typeof parsed !== 'object' || parsed === null)
457
+ return undefined;
458
+ return parsed;
459
+ }
460
+ /** Read just the `name` from a package.json, or undefined on any failure. */
461
+ function readPackageName(absPath) {
462
+ const parsed = readPackageJson(absPath);
463
+ return typeof parsed?.name === 'string' ? parsed.name : undefined;
464
+ }
465
+ /**
466
+ * `fs.statSync` raises on missing paths and on EACCES/ENOTDIR; treat any stat
467
+ * failure as "not a directory" (mirrors `strategy-resolver.ts`).
468
+ */
469
+ function isDirectory(p) {
470
+ try {
471
+ return fs.statSync(p).isDirectory();
472
+ // totem-context: stat failures (ENOENT, EACCES, ENOTDIR) are the sibling-miss signal; rethrowing would force the resolver to wrap a routine absence in try/catch.
473
+ }
474
+ catch {
475
+ return false;
476
+ }
477
+ }
478
+ //# sourceMappingURL=parity-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parity-detect.js","sourceRoot":"","sources":["../src/parity-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,2DAA2D;AAE3D;;;GAGG;AACH,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,4EAA4E;AAC5E,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEhD,wGAAwG;AACxG,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAEtC,sFAAsF;AACtF,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,cAAc,EAAE,iBAAiB,EAAE,sBAAsB,CAAU,CAAC;AAoCxF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAW,EACX,UAAqC,EAAE;IAEvC,6BAA6B;IAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAEhD,8EAA8E;IAC9E,2EAA2E;IAC3E,iCAAiC;IACjC,MAAM,IAAI,GAAG,sBAAsB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1D,uDAAuD;IACvD,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAE1C,iCAAiC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,GAAW,EAAE,OAAkC;IACvE,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAC/B,kOAAkO;QACpO,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,WAAW,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,SAAS,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE;YACtD,GAAG;YACH,OAAO,EAAE,qBAAqB;SAC/B,CAAC,CAAC;QACH,qOAAqO;IACvO,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,mBAAmB,CAAC,SAA6B;IACxD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACrF,uEAAuE;IACvE,4EAA4E;IAC5E,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAC3E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,kFAAkF;AAClF,SAAS,qBAAqB,CAAC,IAAY;IACzC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;IAC9D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,uEAAuE;IACvE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED,gGAAgG;AAChG,SAAS,sBAAsB,CAAC,GAAW,EAAE,QAAmC;IAC9E,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,IAAI,GAAG,CAAC;IACnD,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;QAClC,4LAA4L;IAC9L,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,2DAA2D;AAE3D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAwB,EACxB,SAAkB;IAElB,sEAAsE;IACtE,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/E,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC;IAED,8EAA8E;IAC9E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,8BAA8B,CAAC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;QACxF,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO,WAAW,CAAC;IACpD,CAAC;IAED,0DAA0D;IAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC/C,+EAA+E;IAC/E,6DAA6D;IAC7D,OAAO,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,8BAA8B,CACrC,eAA8B,EAC9B,SAAiB;IAEjB,IAAI,OAAO,eAAe,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,4EAA4E;IAC5E,+EAA+E;IAC/E,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9E,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AACxD,CAAC;AAgBD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB,EAAE,OAAe;IACrE,yBAAyB;IACzB,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,gCAAgC;IAChC,MAAM,WAAW,GAAG,2BAA2B,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACtE,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC1E,CAAC;IAED,6BAA6B;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAChF,IAAI,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,MAAM,cAAc,GAAG,2BAA2B,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACzE,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YACjC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,oBAAoB,WAAW,oEAAoE,qBAAqB,kCAAkC;KACnK,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,2BAA2B,CAAC,IAAY,EAAE,WAAmB;IACpE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAChD,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACpC,yMAAyM;IAC3M,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC/C,wEAAwE;QACxE,0EAA0E;QAC1E,yEAAyE;QACzE,iCAAiC;QACjC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;YAAE,SAAS;QACtC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;QACrE,IAAI,MAAM,EAAE,IAAI,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACvE,OAAO,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAsCD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAwB,EACxB,GAA+B;IAE/B,sCAAsC;IACtC,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,uEAAuE;YACvE,0EAA0E;YAC1E,2EAA2E;YAC3E,iBAAiB;YACjB,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,2FAA2F,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aACrI,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7C,OAAO;gBACL,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,gCAAgC,GAAG,CAAC,MAAM,oBAAoB;aACxE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,2EAA2E;IAC3E,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,sBAAsB,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACrF,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,0DAA0D,QAAQ,CAAC,EAAE,GAAG;SAClF,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,IAAI,eAAe,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,2EAA2E;QAC3E,6EAA6E;QAC7E,kEAAkE;QAClE,oCAAoC;QACpC,uEAAuE;QACvE,sEAAsE;QACtE,4EAA4E;QAC5E,wCAAwC;QACxC,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,WAAW,qHAAqH;YAC5I,WAAW,EAAE,WAAW,WAAW,yHAAyH;SAC7J,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,uEAAuE;IACvE,IAAI,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,WAAW,oBAAoB,aAAa,+BAA+B;SACxF,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,MAAM,SAAS,GAAG,uBAAuB,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;IAC/E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,WAAW,YAAY,aAAa,uCAAuC;SACxF,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,MAAM,KAAK,GAAG,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACnD,CAAC;IAED,6EAA6E;IAC7E,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QACzC,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,oBAAoB,WAAW,MAAM,KAAK,CAAC,OAAO,2BAA2B;SACvF,CAAC;IACJ,CAAC;IAED,oDAAoD;IACpD,2EAA2E;IAC3E,8EAA8E;IAC9E,yBAAyB;IACzB,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,WAAW,4BAA4B,SAAS,mBAAmB,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,GAAG;SACjH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,GAAG,WAAW,yBAAyB,aAAa,eAAe,SAAS,mBAAmB,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,GAAG;QACzI,WAAW,EAAE,YAAY,WAAW,qBAAqB,KAAK,CAAC,OAAO,oDAAoD;KAC3H,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,uBAAuB,CAC9B,GAAW,EACX,WAAmB,EACnB,aAAqB;IAErB,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC;QACR,MAAM,YAAY,GAAG,eAAe,CAClC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,QAAQ,EAAE,cAAc,CAAC,CAC5D,CAAC;QACF,IAAI,YAAY,EAAE,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YACvF,OAAO,YAAY,CAAC,OAAO,CAAC;QAC9B,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,MAAM,CAAC,8BAA8B;QACzD,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IACD,wEAAwE;IACxE,+DAA+D;IAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAC7C,OAAO,GAAG,EAAE,OAAO,CAAC;AACtB,CAAC;AAED,uFAAuF;AACvF,SAAS,iBAAiB,CAAC,GAAqB,EAAE,WAAmB;IACnE,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,4EAA4E;QAC5E,uEAAuE;QACvE,2EAA2E;QAC3E,qBAAqB;QACrB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;IACzE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2DAA2D;AAE3D;;;;;GAKG;AACH,SAAS,eAAe,CAAC,OAAe;IACtC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxC,2LAA2L;IAC7L,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,mLAAmL;IACrL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpE,OAAO,MAA0B,CAAC;AACpC,CAAC;AAED,6EAA6E;AAC7E,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACpC,kKAAkK;IACpK,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tests for the version-pinned parity drift detector (PR-1,
3
+ * mmnto-ai/totem#2069 on top of skeleton #2070).
4
+ *
5
+ * The detector is pure + side-effect-free: every filesystem / git seam is
6
+ * injectable so these tests drive it against synthetic fixtures, NOT the live
7
+ * cohort. Each invariant from the spec's "Invariants to lock via tests" list
8
+ * gets a case: pass / warn / fail-under-blocking+strict (the fail-promotion is a
9
+ * CLI-edge concern — the detector itself only ever returns warn here) / skip on
10
+ * not-declared / skip on not-a-consumer / skip on floor-unresolved /
11
+ * no-throw-on-read-failure / never-networks / claim-class-bound (pin currency
12
+ * only, never content equality).
13
+ *
14
+ * Mirrors the temp-file + discriminated-union style of `parity-manifest.test.ts`
15
+ * and the injected-seam style of `strategy-resolver.test.ts`.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=parity-detect.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parity-detect.test.d.ts","sourceRoot":"","sources":["../src/parity-detect.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}