/** * @fileoverview Manifest manager for skill installation tracking * @module @skillsmith/core/services/skill-manifest * @see SMI-3483: Extracted from skill-installation.service.ts to meet 500-line standard */ import type { SkillManifest } from './skill-installation.types.js'; /** * SMI-6343 Wave 1 — runtime backstop for the test-fixture manifest leak. * * `vitest.setup.ts` redirects `$HOME`/`%USERPROFILE%` to a per-run temp * directory so that homedir-derived manifest paths land in a sandbox. This * guard is the defense-in-depth half: if a test ever resolves a manifest path * under the developer's REAL home anyway — a hardcoded `/Users//...`, a * captured-before-setup constant, a config that somehow skipped the preset — * it fails loudly at the write boundary instead of silently corrupting * `~/.skillsmith/manifest.json`. * * Ground truth for "the real home" is `SKILLSMITH_TEST_REAL_HOME`, captured by * `vitest.setup.ts` BEFORE it installs the sandbox. `os.homedir()` is useless * here (it returns the sandbox once the override is in place); `os.userInfo()` * reads the password-file entry and ignores `$HOME`, so it is the fallback for * the case where the env var is missing — which itself means the sandbox never * ran, exactly when the guard matters most. * * Only active under `process.env.VITEST`. Production code paths are untouched. * * Exported (adversarial-review finding, SMI-6343 follow-up) because * `ManifestManager` is not the only homedir-defaulting manifest writer in the * repo — `packages/mcp-server/src/tools/install.helpers.manifest.ts`, * `packages/cli/src/utils/manifest.ts`, and * `packages/core/src/install/fan-out.ts` each have their own raw-`fs` * save/lock functions with the identical `os.homedir()`-derived path and no * override parameter, so they need the same guard. Reusing this one function * (rather than three independent copies) is the CLAUDE.md-documented * duplicate-security-gate fix: one gate implementation is far harder to * regress than four. */ export declare function realHomeUnderTest(): string | undefined; export declare function assertNotRealUserHome(manifestPath: string, operation: string): void; /** * Manages the skill manifest file (~/.skillsmith/manifest.json) with * file-level locking for concurrent access safety (CLI + MCP server). */ export declare class ManifestManager { private readonly manifestPath; constructor(manifestPath: string); /** * ADR-139 (SMI-6274 Wave 4): the manifest path this instance was * constructed with — read-only accessor for callers that need to name it * in a diagnostic message (e.g. `performUninstall`'s adoption-failure * error, which must name the skill, the path, AND the manifest it tried * to write, per ADR-139 point 1). */ get path(): string; /** * SMI-6007: distinguishes "no manifest yet" (ENOENT — legitimate first-run * case, safe to synthesize an empty manifest) from "a manifest file exists * but couldn't be read/parsed" (corrupt JSON, permission error, I/O * failure). The latter used to be silently swallowed into the same empty * manifest, which is a real data-loss risk: a caller that then `save()`s * that empty snapshot back out would erase every previously-recorded * install. Now it throws loudly instead, so a corrupt manifest surfaces as * an error rather than silently wiping state on the next write. */ load(): Promise; /** * SMI-6007: the temp filename now includes a `randomUUID()` suffix (not * just `process.pid`) — two concurrent `save()` calls in the same process * previously collided on an identical `.tmp.` path, letting one * call's temp file win the write while the other's `rename()` either * clobbered it mid-flight or failed outright. Each call now owns a * uniquely-named temp file for its own lifetime. The write+rename is * wrapped in try/catch so a failure best-effort removes only *this * invocation's* temp file before rethrowing the original error (mirrors * `sqljsDriver.ts`'s `persist()`, SMI-5997) — the error is never swallowed. */ save(manifest: SkillManifest): Promise; acquireLock(): Promise; releaseLock(): Promise; updateSafely(updateFn: (manifest: SkillManifest) => SkillManifest): Promise; } //# sourceMappingURL=skill-manifest.d.ts.map