Files
msd-core/src/cli-skew-check.cts
Tom Boucher e075a41c86 feat(#1754): CLI version-skew detection — warn when a global install shadows project-local GSD (#1755)
* feat(#1754): CLI version-skew detection — warn when a global install shadows project-local GSD

Addresses #1754 (approved-enhancement). Detects when the running gsd-tools.cjs
is outside the project root while a project-local install exists — the shadowing
scenario from #1748 where a stale global canary CLI (retired @gsd-build/sdk)
silently overrides project-local GSD.

Implementation (Node CLI entry-point, not shell snippet — avoids bloating 93
workflow files past their size caps):

- src/cli-skew-check.cts: pure function checkCliSkew({resolvedPath, projectRoot,
  projectLocalExists}) → string|null. Compares paths via path.relative; returns
  a warning when the resolved CLI is outside the project root AND a project-local
  install exists. Includes @gsd-build/sdk removal hint when the path matches.
  No I/O (pure), no gsd-sdk literal (avoids bug-2801 lint).
- gsd-core/bin/gsd-tools.cjs: wired at startup via the existing findProjectRoot
  resolver. Non-blocking (try/catch; advisory stderr warning, never gates).
- eslint.config.mjs: registers the new ADR-457 generated artifact in the ignores.
- tests: 6-case suite (skew/no-skew/legacy/normalization); all green.
- Golden fixtures regenerated (UPDATE_GOLDEN=1) for the new compiled artifact.
- docs/how-to/update-gsd.md: Diátaxis reference note for the skew warning.

Full suite: 3354 pass, 0 regressions (1 pre-existing local AGENTS.md failure).
lint:ci green.

Closes #1754

* chore(#1754): backfill changeset pr placeholder

* chore(#1754): regenerate INVENTORY-MANIFEST for the new cli-skew-check source module

---------

Co-authored-by: review-bot <review-bot@gsd>
2026-06-26 12:19:39 -04:00

48 lines
1.9 KiB
TypeScript

'use strict';
/**
* cli-skew-check.cts — CLI version-skew detection (#1754).
*
* Pure function: compares the resolved gsd-tools.cjs path to the project root.
* If the resolved CLI is OUTSIDE the project root while a project-local install
* EXISTS, returns a warning string (the caller writes it to stderr). Non-blocking.
*
* Catches the shadowing scenario from #1748: a stale global canary CLI (e.g.
* from the retired @gsd-build/sdk) shadowing the project-local GSD install.
*
* The function is PURE (no I/O) — the caller provides the resolved path, the
* project root, and whether a project-local install exists. This makes it
* trivially testable without filesystem setup.
*/
import path from 'node:path';
/**
* Check for CLI version skew.
*
* @param opts.resolvedPath - The absolute path of the running gsd-tools.cjs (__filename).
* @param opts.projectRoot - The project root (from findProjectRoot), or null if no project.
* @param opts.projectLocalExists - Whether a project-local gsd-tools.cjs exists.
* @returns A warning string if skew is detected, or null if no skew.
*/
export function checkCliSkew(opts: {
resolvedPath: string;
projectRoot: string | null;
projectLocalExists: boolean;
}): string | null {
const { resolvedPath, projectRoot, projectLocalExists } = opts;
// No project context or no project-local install → no skew possible.
if (!projectRoot || !projectLocalExists) return null;
// If the resolved CLI is under the project root, it IS a project-local install.
const rel = path.relative(projectRoot, resolvedPath);
if (!rel.startsWith('..')) return null;
// Resolved CLI is outside project root while a project-local install exists → SKEW.
const hint = resolvedPath.includes('@gsd-build')
? ' If @gsd-build/sdk: npm uninstall -g @gsd-build/sdk'
: '';
return `⚠ GSD: ${resolvedPath} may shadow project-local GSD.${hint}`;
}