From 5a2be98f4ee77e372fb2bf5e2bac6cfc0a854ceb Mon Sep 17 00:00:00 2001 From: Chai Landau Date: Thu, 25 Jun 2026 11:48:17 -0400 Subject: [PATCH] fix: honor GHOST_PACKAGE_DIR in drift, ack, history, and review-command paths Several local-fingerprint lookups hardcoded ".ghost" instead of the configured package directory, so relocating the package via GHOST_PACKAGE_DIR broke drift, ack, diverge, compare --temporal history, and the review-command emit footer. Resolve those through resolveGhostDirDefault() while keeping the .ghost default and leaving published node_modules//.ghost paths unchanged. Co-authored-by: Goose Ai-assisted: true --- .changeset/ghost-package-dir-everywhere.md | 5 ++ .../src/context/package-review-command.ts | 3 +- packages/ghost/src/core/evolution/history.ts | 13 ++--- packages/ghost/src/drift-command.ts | 9 +++- packages/ghost/src/evolution-commands.ts | 3 +- .../evolution/history-package-dir.test.ts | 47 +++++++++++++++++++ 6 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 .changeset/ghost-package-dir-everywhere.md create mode 100644 packages/ghost/test/evolution/history-package-dir.test.ts diff --git a/.changeset/ghost-package-dir-everywhere.md b/.changeset/ghost-package-dir-everywhere.md new file mode 100644 index 00000000..ef78aeae --- /dev/null +++ b/.changeset/ghost-package-dir-everywhere.md @@ -0,0 +1,5 @@ +--- +"@anarchitecture/ghost": patch +--- + +Honor `GHOST_PACKAGE_DIR` in the drift, ack, diverge, compare-history, and review-command emit paths so relocated fingerprint packages resolve consistently. diff --git a/packages/ghost/src/context/package-review-command.ts b/packages/ghost/src/context/package-review-command.ts index 340784f0..18153482 100644 --- a/packages/ghost/src/context/package-review-command.ts +++ b/packages/ghost/src/context/package-review-command.ts @@ -7,6 +7,7 @@ import type { GhostFingerprintPrinciple, GhostFingerprintSituation, } from "#ghost-core"; +import { resolveGhostDirDefault } from "../scan/index.js"; import type { PackageContext } from "./package-context.js"; export interface EmitPackageReviewInput { @@ -285,7 +286,7 @@ Generated from \`${packageDir}/\` for ${context.name}. Re-run \`ghost emit revie } function displayPackageDir(context: PackageContext): string { - return displayPath(context.packageDir ?? ".ghost"); + return displayPath(context.packageDir ?? resolveGhostDirDefault()); } function displayPath(path: string): string { diff --git a/packages/ghost/src/core/evolution/history.ts b/packages/ghost/src/core/evolution/history.ts index ee907c7f..b501c81e 100644 --- a/packages/ghost/src/core/evolution/history.ts +++ b/packages/ghost/src/core/evolution/history.ts @@ -2,23 +2,24 @@ import { existsSync } from "node:fs"; import { appendFile, mkdir, readFile } from "node:fs/promises"; import { resolve } from "node:path"; import type { FingerprintHistoryEntry } from "#ghost-core"; +import { resolveGhostDirDefault } from "../../scan/index.js"; -const GHOST_DIR = ".ghost"; const HISTORY_FILE = "history.jsonl"; function historyPath(cwd: string): string { - return resolve(cwd, GHOST_DIR, HISTORY_FILE); + return resolve(cwd, resolveGhostDirDefault(), HISTORY_FILE); } /** - * Append a fingerprint history entry to .ghost/history.jsonl. - * Creates the .ghost directory if it doesn't exist. + * Append a fingerprint history entry to the fingerprint package's + * history.jsonl (honors GHOST_PACKAGE_DIR; defaults to .ghost). + * Creates the package directory if it doesn't exist. */ export async function appendHistory( entry: FingerprintHistoryEntry, cwd: string = process.cwd(), ): Promise { - const dir = resolve(cwd, GHOST_DIR); + const dir = resolve(cwd, resolveGhostDirDefault()); if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } @@ -27,7 +28,7 @@ export async function appendHistory( } /** - * Read all history entries from .ghost/history.jsonl. + * Read all history entries from the package history.jsonl. * Returns an empty array if no history exists. */ export async function readHistory( diff --git a/packages/ghost/src/drift-command.ts b/packages/ghost/src/drift-command.ts index 45aa1221..df68a8ab 100644 --- a/packages/ghost/src/drift-command.ts +++ b/packages/ghost/src/drift-command.ts @@ -15,6 +15,7 @@ import { resolveTrackedFingerprint, } from "./core/index.js"; import { resolveFingerprintPackage } from "./fingerprint.js"; +import { resolveGhostDirDefault } from "./scan/index.js"; const DEFAULT_SYNC_PATH = ".ghost-sync.json"; const DRIFT_STATUS_SCHEMA = "ghost.drift.status/v1" as const; @@ -243,14 +244,18 @@ async function loadLocalFingerprint( localPath: string | undefined, packageDir: string | undefined, ): Promise { - const source = localPath ?? packageDir ?? ".ghost"; + const ghostDir = resolveGhostDirDefault(); + const source = localPath ?? packageDir ?? ghostDir; try { return await loadComparableFingerprintFrom(cwd, source); } catch (err) { const defaultPackage = !localPath && !packageDir; const manifestPath = resolve(cwd, source, "manifest.yml"); if (!defaultPackage || existsSync(manifestPath)) throw err; - return await loadComparableFingerprintFrom(cwd, ".ghost/fingerprint.md"); + return await loadComparableFingerprintFrom( + cwd, + `${ghostDir}/fingerprint.md`, + ); } } diff --git a/packages/ghost/src/evolution-commands.ts b/packages/ghost/src/evolution-commands.ts index 89e3e92f..4529e9fe 100644 --- a/packages/ghost/src/evolution-commands.ts +++ b/packages/ghost/src/evolution-commands.ts @@ -8,9 +8,10 @@ import { loadConfig, resolveTrackedFingerprint, } from "./core/index.js"; +import { resolveGhostDirDefault } from "./scan/index.js"; async function loadLocalFingerprint() { - return loadComparableFingerprint(".ghost"); + return loadComparableFingerprint(resolveGhostDirDefault()); } async function loadTrackedComparableFingerprint( diff --git a/packages/ghost/test/evolution/history-package-dir.test.ts b/packages/ghost/test/evolution/history-package-dir.test.ts new file mode 100644 index 00000000..95cfead3 --- /dev/null +++ b/packages/ghost/test/evolution/history-package-dir.test.ts @@ -0,0 +1,47 @@ +import { existsSync } from "node:fs"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { appendHistory, readHistory } from "../../src/core/evolution/index.js"; + +const ENV = "GHOST_PACKAGE_DIR"; + +describe("history honors GHOST_PACKAGE_DIR", () => { + let cwd: string; + let prev: string | undefined; + + beforeEach(async () => { + cwd = await mkdtemp(join(tmpdir(), "ghost-history-")); + prev = process.env[ENV]; + }); + + afterEach(async () => { + if (prev === undefined) delete process.env[ENV]; + else process.env[ENV] = prev; + await rm(cwd, { recursive: true, force: true }); + }); + + const entry = { + schema: "ghost.fingerprint.history/v1" as const, + timestamp: "2026-01-01T00:00:00.000Z", + target: "self", + } as unknown as Parameters[0]; + + it("defaults to .ghost/history.jsonl", async () => { + delete process.env[ENV]; + await appendHistory(entry, cwd); + expect(existsSync(join(cwd, ".ghost", "history.jsonl"))).toBe(true); + expect(await readHistory(cwd)).toHaveLength(1); + }); + + it("writes under the configured package dir (.agents/ghost)", async () => { + process.env[ENV] = ".agents/ghost"; + await appendHistory(entry, cwd); + expect(existsSync(join(cwd, ".agents", "ghost", "history.jsonl"))).toBe( + true, + ); + expect(existsSync(join(cwd, ".ghost", "history.jsonl"))).toBe(false); + expect(await readHistory(cwd)).toHaveLength(1); + }); +});