@mmnto/cli 1.53.3 → 1.53.5

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,75 @@
1
+ /**
2
+ * Parity-drift sensor for `totem doctor --parity` (mmnto-ai/totem-strategy#448).
3
+ *
4
+ * PR-1 (mmnto-ai/totem#2069): resolve the consumer-configured
5
+ * `orient.parityManifest` config-path → parse + Zod-validate the strategy-owned
6
+ * `parity-manifest.yaml` → emit `DiagnosticResult`s. The FIRST detection slice
7
+ * is wired here: each `version-pinned` contract whose id resolves a deps package
8
+ * name (`mmnto-cli-version`, `mmnto-totem-version`, `mmnto-mcp-version`,
9
+ * `mmnto-pack-rust-architecture-version`) runs through the core
10
+ * `detectVersionPinnedContract` engine (pin-currency verdict, local-only floor).
11
+ * ALL other contracts — mechanical, manual-attestation, and the version-pinned
12
+ * DOCTRINE pins (`governance-doctrine` / `agent-memory-doctrine`, which derive
13
+ * no deps package name) — keep the `skip` info stub (their drift detection is a
14
+ * follow-on).
15
+ *
16
+ * Sensor-not-gate: the core detector returns `skip`/`warn`/`pass` only — never
17
+ * `fail`. The `--strict` exit-code decision lives at the CLI edge: a `warn` from
18
+ * a `blocking: true` contract is promoted to `fail` (non-zero) ONLY under
19
+ * `--strict`. The detector carries that promotion eligibility back via
20
+ * `blockingDriftIds` so the command can gate without re-loading the manifest.
21
+ *
22
+ * Honest-absent (Tenet 14): unconfigured → exactly one `skip` line; never an
23
+ * error. Configured-but-missing / unparseable / unsupported-schema → `warn`,
24
+ * never a crash (mirrors the `findStaleRules` best-effort fallback idiom in
25
+ * doctor.ts). Dynamic-import `@mmnto/totem` to keep core off the CLI cold-start
26
+ * graph, matching the other doctor checks.
27
+ */
28
+ import type { DiagnosticResult } from './doctor.js';
29
+ /**
30
+ * Result of a parity check: the rendered `DiagnosticResult` lines plus the set
31
+ * of contract ids that produced a drift `warn` AND are `blocking: true`. The
32
+ * command promotes exactly these to `fail` under `--strict` — carrying the ids
33
+ * here avoids re-loading the manifest at the CLI edge to recover the `blocking`
34
+ * flag (which `DiagnosticResult` does not carry).
35
+ */
36
+ export interface ParityCheckResult {
37
+ results: DiagnosticResult[];
38
+ /** Contract ids whose `warn` is `--strict`-promotable (blocking + drift). */
39
+ blockingDriftIds: string[];
40
+ }
41
+ /**
42
+ * Resolve, parse, and report the parity manifest as `DiagnosticResult`s.
43
+ *
44
+ * Returns `{ results, blockingDriftIds }`: `results[0]` is always the section
45
+ * summary line; in the `ok` path it is followed by one line per contract — a
46
+ * pin-currency verdict for the deps version-pinned contracts, a `skip` stub for
47
+ * everything else. All non-`ok` paths return a single summary entry and an empty
48
+ * `blockingDriftIds`.
49
+ *
50
+ * @param cwd The directory to resolve config + manifest against (config/repo root).
51
+ */
52
+ export declare function checkParity(cwd: string): Promise<ParityCheckResult>;
53
+ export interface ParityCliOptions {
54
+ /**
55
+ * Strict mode (Proposal 273 / 279 `--strict` semantics): promote drift to a
56
+ * gate failure (non-zero exit) via a thrown TotemError.
57
+ *
58
+ * Sensor-not-gate is the default: a drift `warn` reports and exits 0. Under
59
+ * `--strict`, a `warn` from a `blocking: true` contract (its id carried in
60
+ * `checkParity`'s `blockingDriftIds`) is rendered as `FAIL` and promoted to a
61
+ * non-zero exit. Non-blocking drift stays a `warn` even under `--strict` — the
62
+ * contract's `blocking` flag, not the flag alone, gates the exit code.
63
+ */
64
+ strict?: boolean;
65
+ /** Test seam — production callers omit and the command uses `process.cwd()`. */
66
+ cwdForTest?: string;
67
+ }
68
+ /**
69
+ * CLI entry — runs `checkParity`, renders each `DiagnosticResult`, and throws a
70
+ * `TotemError` when a blocking contract drifted under `--strict` so the
71
+ * top-level `handleError` produces the non-zero exit code (no direct
72
+ * `process.exit` per AGENTS.md).
73
+ */
74
+ export declare function doctorParityCliCommand(options?: ParityCliOptions): Promise<void>;
75
+ //# sourceMappingURL=doctor-parity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-parity.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwHzE;AA6CD,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkE1F"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Parity-drift sensor for `totem doctor --parity` (mmnto-ai/totem-strategy#448).
3
+ *
4
+ * PR-1 (mmnto-ai/totem#2069): resolve the consumer-configured
5
+ * `orient.parityManifest` config-path → parse + Zod-validate the strategy-owned
6
+ * `parity-manifest.yaml` → emit `DiagnosticResult`s. The FIRST detection slice
7
+ * is wired here: each `version-pinned` contract whose id resolves a deps package
8
+ * name (`mmnto-cli-version`, `mmnto-totem-version`, `mmnto-mcp-version`,
9
+ * `mmnto-pack-rust-architecture-version`) runs through the core
10
+ * `detectVersionPinnedContract` engine (pin-currency verdict, local-only floor).
11
+ * ALL other contracts — mechanical, manual-attestation, and the version-pinned
12
+ * DOCTRINE pins (`governance-doctrine` / `agent-memory-doctrine`, which derive
13
+ * no deps package name) — keep the `skip` info stub (their drift detection is a
14
+ * follow-on).
15
+ *
16
+ * Sensor-not-gate: the core detector returns `skip`/`warn`/`pass` only — never
17
+ * `fail`. The `--strict` exit-code decision lives at the CLI edge: a `warn` from
18
+ * a `blocking: true` contract is promoted to `fail` (non-zero) ONLY under
19
+ * `--strict`. The detector carries that promotion eligibility back via
20
+ * `blockingDriftIds` so the command can gate without re-loading the manifest.
21
+ *
22
+ * Honest-absent (Tenet 14): unconfigured → exactly one `skip` line; never an
23
+ * error. Configured-but-missing / unparseable / unsupported-schema → `warn`,
24
+ * never a crash (mirrors the `findStaleRules` best-effort fallback idiom in
25
+ * doctor.ts). Dynamic-import `@mmnto/totem` to keep core off the CLI cold-start
26
+ * graph, matching the other doctor checks.
27
+ */
28
+ import * as path from 'node:path';
29
+ const CHECK_NAME = 'Parity';
30
+ /**
31
+ * Resolve, parse, and report the parity manifest as `DiagnosticResult`s.
32
+ *
33
+ * Returns `{ results, blockingDriftIds }`: `results[0]` is always the section
34
+ * summary line; in the `ok` path it is followed by one line per contract — a
35
+ * pin-currency verdict for the deps version-pinned contracts, a `skip` stub for
36
+ * everything else. All non-`ok` paths return a single summary entry and an empty
37
+ * `blockingDriftIds`.
38
+ *
39
+ * @param cwd The directory to resolve config + manifest against (config/repo root).
40
+ */
41
+ export async function checkParity(cwd) {
42
+ const { loadConfig, resolveConfigPath, isGlobalConfigPath } = await import('../utils.js');
43
+ const { deriveCohortRepoId, detectVersionPinnedContract, loadParityManifest, packageNameForContract, resolveGitRoot, SUPPORTED_PARITY_SCHEMA_VERSION, } = await import('@mmnto/totem');
44
+ // Read the config best-effort: a missing/corrupt config is the honest-absent
45
+ // path (no parity manifest configured), not a crash. Mirrors the config-load
46
+ // fallback in doctorCommand — surface only on a defective error object so
47
+ // sentinels still propagate.
48
+ let configValue;
49
+ // Relative manifest paths anchor at the config's OWN directory, not the
50
+ // invocation cwd, so the field resolves consistently no matter which subdir
51
+ // the doctor runs from. resolveConfigPath only checks cwd + the global profile
52
+ // today (no upward walk), so this equals cwd for the local case — the explicit
53
+ // anchor just keeps it correct if resolution ever changes.
54
+ let manifestRoot = cwd;
55
+ try {
56
+ const configPath = resolveConfigPath(cwd);
57
+ // Repo-scoped by design: the manifest location is per-repo, so a config-less
58
+ // repo that only resolves the GLOBAL ~/.totem profile is honest-absent for
59
+ // parity. Never leak a global orient.parityManifest into a repo-less result
60
+ // (that would make the sensor machine-dependent) — only a repo-local config
61
+ // contributes the field.
62
+ if (isGlobalConfigPath(configPath)) {
63
+ configValue = undefined;
64
+ }
65
+ else {
66
+ manifestRoot = path.dirname(configPath);
67
+ const config = await loadConfig(configPath);
68
+ configValue = config.orient?.parityManifest;
69
+ }
70
+ // totem-context: a missing/corrupt totem config is the honest-absent path (treated as "no parity manifest configured"), not a sensor failure — the doctor runs against config-less repos by design.
71
+ }
72
+ catch (err) {
73
+ if (err instanceof Error && err.message.length === 0) {
74
+ throw err;
75
+ }
76
+ configValue = undefined;
77
+ }
78
+ const result = loadParityManifest(configValue, manifestRoot);
79
+ switch (result.status) {
80
+ case 'not-configured':
81
+ // Honest-absent: exactly one skip line. Not a failure.
82
+ return single({
83
+ name: CHECK_NAME,
84
+ status: 'skip',
85
+ message: 'no parity manifest configured',
86
+ });
87
+ case 'not-found':
88
+ return single({
89
+ name: CHECK_NAME,
90
+ status: 'warn',
91
+ message: `parity manifest not found at ${rel(cwd, result.path)}`,
92
+ remediation: 'Fix orient.parityManifest in your totem config to point at the manifest.',
93
+ });
94
+ case 'unparseable':
95
+ return single({
96
+ name: CHECK_NAME,
97
+ status: 'warn',
98
+ message: `parity manifest unreadable at ${rel(cwd, result.path)}: ${result.reason}`,
99
+ remediation: 'Fix the manifest YAML / schema, then re-run totem doctor --parity.',
100
+ });
101
+ case 'unsupported-schema':
102
+ return single({
103
+ name: CHECK_NAME,
104
+ status: 'warn',
105
+ message: `parity manifest schema v${result.schemaVersion} unsupported (this doctor supports v${SUPPORTED_PARITY_SCHEMA_VERSION})`,
106
+ remediation: 'Upgrade @mmnto/cli or align the manifest schema-version.',
107
+ });
108
+ case 'ok': {
109
+ const { contracts } = result.manifest;
110
+ const summary = {
111
+ name: CHECK_NAME,
112
+ status: 'pass',
113
+ message: `parity manifest: ${contracts.length} contract(s) loaded`,
114
+ };
115
+ // Shared detection context. The cohort floor + repoId derive from the git
116
+ // root (anchored there, not the deep cwd — mirrors the core resolver).
117
+ // resolveGitRoot returns null outside a repo; fall back to cwd so the
118
+ // local self-in-tree / sibling probes still have an anchor.
119
+ const gitRoot = safeGitRoot(resolveGitRoot, cwd) ?? cwd;
120
+ const repoId = deriveCohortRepoId(cwd, { gitRoot });
121
+ const blockingDriftIds = [];
122
+ const perContract = contracts.map((c) => {
123
+ // PR-1 only senses version-pinned DEPS contracts (those that resolve a
124
+ // package name). Everything else — mechanical, manual-attestation, and
125
+ // the version-pinned doctrine pins (no deps package name) — keeps the
126
+ // skip stub until its detection slice lands.
127
+ const packageName = c.tractability === 'version-pinned' ? packageNameForContract(c, gitRoot) : undefined;
128
+ if (packageName === undefined) {
129
+ return {
130
+ name: `Parity: ${c.id}`,
131
+ status: 'skip',
132
+ message: `${c.dimension} (${c.tractability}) — drift detection not yet implemented`,
133
+ };
134
+ }
135
+ const verdict = detectVersionPinnedContract(c, { cwd, gitRoot, repoId, packageName });
136
+ if (verdict.status === 'warn' && c.blocking === true) {
137
+ blockingDriftIds.push(c.id);
138
+ }
139
+ return verdictToDiagnostic(c, verdict);
140
+ });
141
+ return { results: [summary, ...perContract], blockingDriftIds };
142
+ }
143
+ }
144
+ }
145
+ /** Wrap a single summary line in the `ParityCheckResult` shape (no blocking ids). */
146
+ function single(result) {
147
+ return { results: [result], blockingDriftIds: [] };
148
+ }
149
+ /** Map a core `ParityContractVerdict` to a CLI `DiagnosticResult` for one contract. */
150
+ function verdictToDiagnostic(contract, verdict) {
151
+ return {
152
+ name: `Parity: ${contract.id}`,
153
+ status: verdict.status,
154
+ message: verdict.message,
155
+ ...(verdict.remediation !== undefined ? { remediation: verdict.remediation } : {}),
156
+ };
157
+ }
158
+ /**
159
+ * Resolve the git root, swallowing the `TotemGitError` that `resolveGitRoot`
160
+ * throws on a git hiccup (permission error / corrupted index) — the parity
161
+ * sensor degrades to the cwd anchor rather than crashing the doctor pipeline.
162
+ */
163
+ function safeGitRoot(resolve, cwd) {
164
+ try {
165
+ return resolve(cwd);
166
+ // totem-context: resolveGitRoot throws on permission errors / corrupted index; the parity sensor degrades to a cwd anchor rather than crashing — a git hiccup must not sink the doctor pipeline.
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ /** Repo-root-relative display path; falls back to the absolute path. */
173
+ function rel(cwd, target) {
174
+ const r = path.relative(cwd, target);
175
+ return r.length > 0 ? r : target;
176
+ }
177
+ // ─── CLI entry ──────────────────────────────────────────
178
+ // Same value as CHECK_NAME — aliased (not re-literal'd) so the two can't drift.
179
+ const TAG = CHECK_NAME;
180
+ /**
181
+ * CLI entry — runs `checkParity`, renders each `DiagnosticResult`, and throws a
182
+ * `TotemError` when a blocking contract drifted under `--strict` so the
183
+ * top-level `handleError` produces the non-zero exit code (no direct
184
+ * `process.exit` per AGENTS.md).
185
+ */
186
+ export async function doctorParityCliCommand(options = {}) {
187
+ const { TotemError, sanitizeForTerminal } = await import('@mmnto/totem');
188
+ const { bold, errorColor, log, success: successColor, warn: warnColor, } = await import('../ui.js');
189
+ // Manifest-derived text (paths, parse reasons, contract metadata) is sourced
190
+ // from repo-controlled files; sanitize + flatten before logging so embedded
191
+ // ANSI / newlines can't forge extra doctor lines (matches checkStrategyRoot
192
+ // in doctor.ts).
193
+ const render = (text) => sanitizeForTerminal(text)
194
+ .replace(/[\t\n]+/g, ' ')
195
+ .replace(/ {2,}/g, ' ')
196
+ .trim();
197
+ const cwd = options.cwdForTest ?? process.cwd();
198
+ const { results, blockingDriftIds } = await checkParity(cwd);
199
+ // Under --strict, a blocking contract's drift `warn` is rendered + gated as a
200
+ // FAIL. We match by the `Parity: <id>` line name so the rendered status agrees
201
+ // with the exit code. blockingDriftIds is empty in the non-strict path's
202
+ // effect (the promotion only fires under --strict), so this Set is cheap.
203
+ const promotable = new Set(blockingDriftIds.map((id) => `Parity: ${id}`));
204
+ for (const r of results) {
205
+ const status = options.strict && r.status === 'warn' && promotable.has(r.name) ? 'fail' : r.status;
206
+ switch (status) {
207
+ case 'pass':
208
+ log.success(TAG, `${successColor(bold('PASS'))} — ${render(r.message)}`);
209
+ break;
210
+ case 'warn':
211
+ log.warn(TAG, `${warnColor(bold('WARN'))} — ${render(r.message)}`);
212
+ if (r.remediation)
213
+ log.dim(TAG, `→ ${render(r.remediation)}`);
214
+ break;
215
+ case 'fail':
216
+ // Mandated 'Totem Error' tag (packages/cli convention) — marks internal
217
+ // error output, distinct from the contextual TAG used for pass/warn/skip.
218
+ log.error('Totem Error', `${errorColor(bold('FAIL'))} — ${render(r.message)}`);
219
+ if (r.remediation)
220
+ log.dim(TAG, `→ ${render(r.remediation)}`);
221
+ break;
222
+ case 'skip':
223
+ log.dim(TAG, `SKIP — ${render(r.message)}`);
224
+ break;
225
+ }
226
+ }
227
+ // Sensor-not-gate: drift is report-only by default (exit 0). Only `--strict`
228
+ // promotes a `blocking: true` contract's drift to a non-zero exit; non-blocking
229
+ // drift never gates. PR-1's detector emits no raw `fail` status (version-pinned
230
+ // is pass/warn/skip), so the gate is purely the strict+blocking promotion — the
231
+ // gating model for a future slice that DOES emit a `fail` is settled when that
232
+ // slice lands (CR review #2071: keep the gate from suggesting a non-strict path
233
+ // that would break sensor-not-gate).
234
+ if (options.strict && blockingDriftIds.length > 0) {
235
+ throw new TotemError('PARITY_DRIFT_DETECTED', `${blockingDriftIds.length} parity contract(s) reported blocking drift under --strict.`, 'Reconcile each blocking contract against its canonical source, then re-run totem doctor --parity --strict.');
236
+ }
237
+ }
238
+ //# sourceMappingURL=doctor-parity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-parity.js","sourceRoot":"","sources":["../../src/commands/doctor-parity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tests for the `totem doctor --parity` sensor (mmnto-ai/totem-strategy#448,
3
+ * PR-1 mmnto-ai/totem#2069).
4
+ *
5
+ * Drives `checkParity` against real temp dirs with a YAML totem config so the
6
+ * config-resolution → manifest-resolution → parse → DiagnosticResult mapping is
7
+ * exercised end-to-end (no mocking of `loadConfig`). Each test writes a local
8
+ * `totem.yaml` so `resolveConfigPath` never falls through to the global
9
+ * `~/.totem/` profile (which would make the not-configured case nondeterministic).
10
+ *
11
+ * The version-pinned wiring tests write a self-in-tree fixture (`packages/*​/
12
+ * package.json`) under the temp dir so the core detector's cohort-floor probe
13
+ * resolves locally (NEVER networks — the temp dir is not a git repo, so the
14
+ * origin-remote read fails fast + degrades to the dir/name fallbacks).
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=doctor-parity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-parity.test.d.ts","sourceRoot":"","sources":["../../src/commands/doctor-parity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Tests for the `totem doctor --parity` sensor (mmnto-ai/totem-strategy#448,
3
+ * PR-1 mmnto-ai/totem#2069).
4
+ *
5
+ * Drives `checkParity` against real temp dirs with a YAML totem config so the
6
+ * config-resolution → manifest-resolution → parse → DiagnosticResult mapping is
7
+ * exercised end-to-end (no mocking of `loadConfig`). Each test writes a local
8
+ * `totem.yaml` so `resolveConfigPath` never falls through to the global
9
+ * `~/.totem/` profile (which would make the not-configured case nondeterministic).
10
+ *
11
+ * The version-pinned wiring tests write a self-in-tree fixture (`packages/*​/
12
+ * package.json`) under the temp dir so the core detector's cohort-floor probe
13
+ * resolves locally (NEVER networks — the temp dir is not a git repo, so the
14
+ * origin-remote read fails fast + degrades to the dir/name fallbacks).
15
+ */
16
+ import * as fs from 'node:fs';
17
+ import * as os from 'node:os';
18
+ import * as path from 'node:path';
19
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
20
+ import { cleanTmpDir } from '../test-utils.js';
21
+ import { checkParity, doctorParityCliCommand } from './doctor-parity.js';
22
+ // Minimal valid totem config — `targets` is the only required array; everything
23
+ // else defaults. `orient.parityManifest` is added per-test as needed.
24
+ const BASE_CONFIG = `targets:
25
+ - glob: "**/*.ts"
26
+ type: code
27
+ strategy: typescript-ast
28
+ `;
29
+ // A small manifest mirroring the real shape (version-pinned + null source-note +
30
+ // optional title/blocking/consumers), reused across the success-path tests.
31
+ const VALID_MANIFEST_YAML = `schema-version: 1
32
+ status: scaffold
33
+ contracts:
34
+ - id: session-start-orientation
35
+ dimension: orientation
36
+ canonical-source: mmnto-ai/totem:packages/cli/src/commands/init-templates.ts#SessionStart
37
+ detection-method: SessionStart hook present
38
+ expected-value-or-derivation: hook managed-block matches distributed template
39
+ tractability: mechanical
40
+ tracking-issue: mmnto-ai/totem-strategy#438
41
+ - id: mmnto-cli-version
42
+ dimension: toolchain-version
43
+ canonical-source: mmnto-ai/totem:packages/cli/package.json#version
44
+ detection-method: consumer package.json caret range
45
+ expected-value-or-derivation: consumer pin resolves to current @mmnto/cli
46
+ tractability: version-pinned
47
+ tracking-issue: mmnto-ai/totem-strategy#482
48
+ - id: mcp-corpus-indexing
49
+ dimension: knowledge-index
50
+ canonical-source: null
51
+ source-note: consumer-local capability; no external canonical source
52
+ detection-method: .lancedb index present
53
+ expected-value-or-derivation: index present + fresh
54
+ tractability: mechanical
55
+ tracking-issue: mmnto-ai/totem#2018
56
+ - id: gate-config
57
+ dimension: enforcement
58
+ title: Canonical gate set
59
+ canonical-source: mmnto-ai/totem
60
+ detection-method: installed gates vs canonical gate set
61
+ expected-value-or-derivation: consumer installed gates == canonical
62
+ tractability: mechanical
63
+ tracking-issue: mmnto-ai/totem-strategy#482
64
+ blocking: false
65
+ consumers:
66
+ - mmnto-ai/totem
67
+ `;
68
+ let tmpDir;
69
+ beforeEach(() => {
70
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-doctor-parity-'));
71
+ });
72
+ afterEach(() => {
73
+ cleanTmpDir(tmpDir);
74
+ });
75
+ function writeConfig(body) {
76
+ fs.writeFileSync(path.join(tmpDir, 'totem.yaml'), body, 'utf-8');
77
+ }
78
+ function writeManifest(relPath, yamlText) {
79
+ const abs = path.join(tmpDir, relPath);
80
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
81
+ fs.writeFileSync(abs, yamlText, 'utf-8');
82
+ }
83
+ describe('checkParity — honest-absent', () => {
84
+ it('emits exactly one skip line when orient.parityManifest is absent (no throw)', async () => {
85
+ writeConfig(BASE_CONFIG);
86
+ const { results } = await checkParity(tmpDir);
87
+ expect(results).toHaveLength(1);
88
+ expect(results[0].status).toBe('skip');
89
+ expect(results[0].message).toContain('no parity manifest configured');
90
+ });
91
+ it('treats a config-less repo as not-configured (skip, no throw)', async () => {
92
+ // No totem config at all → resolveConfigPath either throws (caught,
93
+ // best-effort) or resolves the GLOBAL ~/.totem profile, which checkParity
94
+ // explicitly ignores for repo-scoping (isGlobalConfigPath). Either way the
95
+ // result is a deterministic skip, regardless of any global orient.parityManifest.
96
+ const { results } = await checkParity(tmpDir);
97
+ expect(results).toHaveLength(1);
98
+ expect(results[0].status).toBe('skip');
99
+ });
100
+ });
101
+ describe('checkParity — configured-but-missing', () => {
102
+ it('warns (no throw) when the configured manifest path does not exist', async () => {
103
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: doctrine/parity-manifest.yaml\n`);
104
+ const { results } = await checkParity(tmpDir);
105
+ expect(results).toHaveLength(1);
106
+ expect(results[0].status).toBe('warn');
107
+ expect(results[0].message).toContain('not found');
108
+ });
109
+ });
110
+ describe('checkParity — unparseable / unsupported-schema', () => {
111
+ it('warns (never crashes) on unparseable YAML', async () => {
112
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: bad.yaml\n`);
113
+ writeManifest('bad.yaml', 'schema-version: 1\ncontracts: [unclosed');
114
+ const { results } = await checkParity(tmpDir);
115
+ expect(results).toHaveLength(1);
116
+ expect(results[0].status).toBe('warn');
117
+ expect(results[0].message).toContain('unreadable');
118
+ });
119
+ it('warns (never crashes) on a Zod-invalid manifest', async () => {
120
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: bad.yaml\n`);
121
+ writeManifest('bad.yaml', `schema-version: 1
122
+ status: scaffold
123
+ contracts:
124
+ - id: broken
125
+ dimension: orientation
126
+ canonical-source: null
127
+ detection-method: x
128
+ tractability: mechanical
129
+ `);
130
+ const { results } = await checkParity(tmpDir);
131
+ expect(results[0].status).toBe('warn');
132
+ });
133
+ it('warns and does NOT parse contracts on an unsupported schema-version', async () => {
134
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: future.yaml\n`);
135
+ writeManifest('future.yaml', VALID_MANIFEST_YAML.replace('schema-version: 1', 'schema-version: 2'));
136
+ const { results } = await checkParity(tmpDir);
137
+ expect(results).toHaveLength(1);
138
+ expect(results[0].status).toBe('warn');
139
+ expect(results[0].message).toContain('schema v2 unsupported');
140
+ });
141
+ });
142
+ describe('checkParity — success', () => {
143
+ it('emits a summary line + one line per contract on a valid manifest', async () => {
144
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: doctrine/parity-manifest.yaml\n`);
145
+ writeManifest('doctrine/parity-manifest.yaml', VALID_MANIFEST_YAML);
146
+ const { results } = await checkParity(tmpDir);
147
+ // 1 summary + 4 per-contract lines.
148
+ expect(results).toHaveLength(5);
149
+ const summary = results[0];
150
+ expect(summary.status).toBe('pass');
151
+ expect(summary.message).toContain('4 contract(s) loaded');
152
+ const perContract = results.slice(1);
153
+ // The mechanical contracts keep the skip stub.
154
+ const orientation = perContract.find((r) => r.name === 'Parity: session-start-orientation');
155
+ expect(orientation.status).toBe('skip');
156
+ expect(orientation.message).toContain('mechanical');
157
+ // The version-pinned deps contract (mmnto-cli-version) now runs the
158
+ // detector — with no consumer pin declared here it lands on a `skip`
159
+ // (cohort permits absence), NOT the old "not yet implemented" stub.
160
+ const cliVersion = perContract.find((r) => r.name === 'Parity: mmnto-cli-version');
161
+ expect(cliVersion.status).toBe('skip');
162
+ expect(cliVersion.message).not.toContain('not yet implemented');
163
+ });
164
+ it('never emits a fail status by default (sensor-not-gate)', async () => {
165
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: doctrine/parity-manifest.yaml\n`);
166
+ writeManifest('doctrine/parity-manifest.yaml', VALID_MANIFEST_YAML);
167
+ const { results } = await checkParity(tmpDir);
168
+ expect(results.some((r) => r.status === 'fail')).toBe(false);
169
+ });
170
+ });
171
+ // ─── version-pinned detection wiring (PR-1) ─────────────
172
+ /** A manifest with a single deps version-pinned contract (no consumers = applies). */
173
+ const DEPS_MANIFEST_YAML = `schema-version: 1
174
+ status: scaffold
175
+ contracts:
176
+ - id: mmnto-totem-version
177
+ dimension: dependency-cohort
178
+ canonical-source: mmnto-ai/totem
179
+ detection-method: consumer package.json caret range + resolved install vs floor
180
+ expected-value-or-derivation: consumer pin resolves to the current published @mmnto/totem
181
+ tractability: version-pinned
182
+ tracking-issue: mmnto-ai/totem-strategy#482
183
+ `;
184
+ /** Same contract marked blocking — exercises the --strict fail-promotion edge. */
185
+ const BLOCKING_DEPS_MANIFEST_YAML = DEPS_MANIFEST_YAML.replace('tracking-issue: mmnto-ai/totem-strategy#482\n', 'tracking-issue: mmnto-ai/totem-strategy#482\n blocking: true\n');
186
+ /** Write a self-in-tree `packages/<dir>/package.json` so the cohort floor resolves locally. */
187
+ function writeFloorPackage(dir, name, version) {
188
+ const pkgDir = path.join(tmpDir, 'packages', dir);
189
+ fs.mkdirSync(pkgDir, { recursive: true });
190
+ fs.writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name, version }, null, 2), 'utf-8');
191
+ }
192
+ /** Write the consumer's root package.json declaring a dependency pin. */
193
+ function writeConsumerDeps(deps) {
194
+ fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'consumer-repo', dependencies: deps }, null, 2), 'utf-8');
195
+ }
196
+ /** Write `node_modules/<pkg>/package.json#version` so installed-version resolution wins. */
197
+ function writeInstalled(pkg, version) {
198
+ const dir = path.join(tmpDir, 'node_modules', ...pkg.split('/'));
199
+ fs.mkdirSync(dir, { recursive: true });
200
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: pkg, version }, null, 2), 'utf-8');
201
+ }
202
+ describe('checkParity — version-pinned wiring', () => {
203
+ it('PASS — installed @mmnto/totem >= the self-in-tree cohort floor', async () => {
204
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
205
+ writeManifest('m.yaml', DEPS_MANIFEST_YAML);
206
+ writeFloorPackage('totem', '@mmnto/totem', '1.50.0'); // floor
207
+ writeConsumerDeps({ '@mmnto/totem': '^1.50.0' });
208
+ writeInstalled('@mmnto/totem', '1.53.3'); // installed >= floor
209
+ const { results, blockingDriftIds } = await checkParity(tmpDir);
210
+ const line = results.find((r) => r.name === 'Parity: mmnto-totem-version');
211
+ expect(line.status).toBe('pass');
212
+ expect(blockingDriftIds).toHaveLength(0);
213
+ });
214
+ it('WARN — installed @mmnto/totem < the cohort floor (stale pin), no blocking promotion', async () => {
215
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
216
+ writeManifest('m.yaml', DEPS_MANIFEST_YAML);
217
+ writeFloorPackage('totem', '@mmnto/totem', '1.53.3'); // floor
218
+ writeConsumerDeps({ '@mmnto/totem': '^1.40.0' });
219
+ writeInstalled('@mmnto/totem', '1.40.0'); // installed < floor
220
+ const { results, blockingDriftIds } = await checkParity(tmpDir);
221
+ const line = results.find((r) => r.name === 'Parity: mmnto-totem-version');
222
+ expect(line.status).toBe('warn');
223
+ expect(line.message).toContain('1.53.3'); // floor surfaced
224
+ // Non-blocking contract → not eligible for --strict promotion.
225
+ expect(blockingDriftIds).toHaveLength(0);
226
+ });
227
+ it('a blocking contract drift tags blockingDriftIds (the --strict promotion seam)', async () => {
228
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
229
+ writeManifest('m.yaml', BLOCKING_DEPS_MANIFEST_YAML);
230
+ writeFloorPackage('totem', '@mmnto/totem', '1.53.3');
231
+ writeConsumerDeps({ '@mmnto/totem': '^1.40.0' });
232
+ writeInstalled('@mmnto/totem', '1.40.0');
233
+ const { results, blockingDriftIds } = await checkParity(tmpDir);
234
+ const line = results.find((r) => r.name === 'Parity: mmnto-totem-version');
235
+ // The CHECK still returns warn — fail-promotion is the CLI edge's job.
236
+ expect(line.status).toBe('warn');
237
+ expect(blockingDriftIds).toEqual(['mmnto-totem-version']);
238
+ });
239
+ });
240
+ describe('doctorParityCliCommand — --strict fail-promotion', () => {
241
+ it('throws PARITY_DRIFT_DETECTED when a blocking contract drifted under --strict', async () => {
242
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
243
+ writeManifest('m.yaml', BLOCKING_DEPS_MANIFEST_YAML);
244
+ writeFloorPackage('totem', '@mmnto/totem', '1.53.3');
245
+ writeConsumerDeps({ '@mmnto/totem': '^1.40.0' });
246
+ writeInstalled('@mmnto/totem', '1.40.0');
247
+ await expect(doctorParityCliCommand({ strict: true, cwdForTest: tmpDir })).rejects.toThrow(/PARITY_DRIFT_DETECTED|blocking drift/i);
248
+ });
249
+ it('does NOT throw for the same blocking drift without --strict (sensor-not-gate)', async () => {
250
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
251
+ writeManifest('m.yaml', BLOCKING_DEPS_MANIFEST_YAML);
252
+ writeFloorPackage('totem', '@mmnto/totem', '1.53.3');
253
+ writeConsumerDeps({ '@mmnto/totem': '^1.40.0' });
254
+ writeInstalled('@mmnto/totem', '1.40.0');
255
+ await expect(doctorParityCliCommand({ strict: false, cwdForTest: tmpDir })).resolves.toBeUndefined();
256
+ });
257
+ it('does NOT throw under --strict when drift is NON-blocking (only blocking gates)', async () => {
258
+ writeConfig(`${BASE_CONFIG}orient:\n parityManifest: m.yaml\n`);
259
+ writeManifest('m.yaml', DEPS_MANIFEST_YAML); // non-blocking
260
+ writeFloorPackage('totem', '@mmnto/totem', '1.53.3');
261
+ writeConsumerDeps({ '@mmnto/totem': '^1.40.0' });
262
+ writeInstalled('@mmnto/totem', '1.40.0');
263
+ await expect(doctorParityCliCommand({ strict: true, cwdForTest: tmpDir })).resolves.toBeUndefined();
264
+ });
265
+ });
266
+ //# sourceMappingURL=doctor-parity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-parity.test.js","sourceRoot":"","sources":["../../src/commands/doctor-parity.test.ts"],"names":[],"mappings":"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"}