diff --git a/.changeset/flat-ghost-package.md b/.changeset/flat-ghost-package.md
new file mode 100644
index 00000000..29b36119
--- /dev/null
+++ b/.changeset/flat-ghost-package.md
@@ -0,0 +1,5 @@
+---
+"@anarchitecture/ghost": major
+---
+
+Flatten Ghost packages so manifest, facets, and checks live directly in the package directory, remove config.yml behavior, and use GHOST_PACKAGE_DIR for host-wrapper package discovery.
diff --git a/.ghost/fingerprint/composition.yml b/.ghost/composition.yml
similarity index 100%
rename from .ghost/fingerprint/composition.yml
rename to .ghost/composition.yml
diff --git a/.ghost/fingerprint/intent.yml b/.ghost/intent.yml
similarity index 100%
rename from .ghost/fingerprint/intent.yml
rename to .ghost/intent.yml
diff --git a/.ghost/fingerprint/inventory.yml b/.ghost/inventory.yml
similarity index 100%
rename from .ghost/fingerprint/inventory.yml
rename to .ghost/inventory.yml
diff --git a/.ghost/fingerprint/manifest.yml b/.ghost/manifest.yml
similarity index 100%
rename from .ghost/fingerprint/manifest.yml
rename to .ghost/manifest.yml
diff --git a/.ghost/fingerprint/validate.yml b/.ghost/validate.yml
similarity index 100%
rename from .ghost/fingerprint/validate.yml
rename to .ghost/validate.yml
diff --git a/CLAUDE.md b/CLAUDE.md
index 8194887c..89dd8174 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -36,16 +36,14 @@ structural diffs, drift checks, comparison math, and handoff packets.
The canonical root `.ghost/` package follows:
```text
-config.yml
-fingerprint/
- manifest.yml
- intent.yml
- inventory.yml
- composition.yml
- validate.yml
+manifest.yml
+intent.yml
+inventory.yml
+composition.yml
+validate.yml
```
-The three root facet files under `fingerprint/` are the core model:
+The three root facet files are the core model:
- `intent.yml` for surface intent.
- `inventory.yml` for curated material, exemplars, and source links.
@@ -53,8 +51,7 @@ The three root facet files under `fingerprint/` are the core model:
`validate.yml` validates output through deterministic checks; it is not
generation input.
-`.ghost/config.yml` remains local adapter/routing config outside portable
-context. Ordinary Git review is the approval boundary for fingerprint edits.
+Ordinary Git review is the approval boundary for fingerprint edits.
Legacy `resources.yml`, `map.md`, `survey.json`, and `patterns.yml` may still
appear in older repos or as migration source material. They are not canonical
@@ -74,7 +71,7 @@ fingerprint input for new Ghost work.
| Command | Description |
| --- | --- |
-| `ghost init` | Create `.ghost/fingerprint/` with manifest, facets, and deterministic checks. |
+| `ghost init` | Create `.ghost/` with manifest, facets, and deterministic checks. |
| `ghost scan` | Report fingerprint contribution facets and BYOA next-step guidance. |
| `ghost signals` | Emit raw repo signals as JSON for fingerprint authoring. |
| `ghost lint` | Validate a fingerprint package or single artifact. |
@@ -129,16 +126,13 @@ One sentence, user-facing, present tense.
```
Use `patch` for fixes and docs, `minor` for new commands/flags/exports, and
-`major` for removed or renamed public behavior. For this PR 81 package-shape
-change, the source version stays `0.0.0` and the changeset is `minor` so the
-first publish becomes `0.1.0`.
+`major` for removed or renamed public behavior.
## Conventions
- Keep publishable runtime code self-contained in `packages/ghost`; no
`workspace:*` runtime dependencies in the packed public artifact.
-- The canonical on-disk form is `.ghost/fingerprint/` plus optional
- `.ghost/config.yml`.
+- The canonical on-disk form is a flat `.ghost/` package.
- Direct `fingerprint.md` remains only for legacy/direct compare workflows.
- Skill recipes live in `packages/ghost/src/skill-bundle/references/`; install
them with `ghost skill install`.
diff --git a/README.md b/README.md
index 14930fc4..33ca2cc3 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ they generate UI and validate after they change it. The public package is
Agents can assemble components. What they need help preserving is the product
surface behind those components: hierarchy, density, restraint, behavior, copy,
accessibility, trust, and flow. Ghost keeps that surface composition in a
-portable `.ghost/fingerprint/` package that ordinary Git review can approve.
+portable `.ghost/` package that ordinary Git review can approve.
## The Shape
@@ -18,13 +18,11 @@ The canonical package is intentionally small:
```text
.ghost/
- config.yml # optional local routing, not portable context
- fingerprint/
- manifest.yml # ghost.fingerprint-package/v1 anchor
- intent.yml # surface intent
- inventory.yml # curated material and exemplars
- composition.yml # patterns, flows, states, and arrangements
- validate.yml # optional deterministic gates
+ manifest.yml # ghost.fingerprint-package/v1 anchor
+ intent.yml # surface intent
+ inventory.yml # curated material and exemplars
+ composition.yml # patterns, flows, states, and arrangements
+ validate.yml # optional deterministic gates
```
A package can be sparse: it contributes whichever facets are locally true. Generation usually uses intent + inventory + composition:
@@ -41,13 +39,13 @@ what the surface is trying to preserve.
Older `resources.yml`, `map.md`, `survey.json`, `patterns.yml`, and direct
`fingerprint.yml` artifacts can still inform migration workflows, but new Ghost
-work should target `.ghost/fingerprint/`.
+work should target `.ghost/`.
## Project Status: Beta
> [!WARNING]
> Ghost is pre-1.0 and under active development. The CLI, fingerprint schema,
-> on-disk `.ghost/fingerprint/` package shape, and public JavaScript exports may
+> on-disk `.ghost/` package shape, and public JavaScript exports may
> change in breaking ways before a stable 1.0 release.
>
> Breaking changes may ship in minor versions while Ghost is pre-1.0. Patch
@@ -92,25 +90,26 @@ edits, and asks you to curate the claims.
```bash
ghost init
+ghost init --package product-surface
ghost scan --format json
ghost signals .
ghost lint .ghost
+ghost lint product-surface
ghost verify .ghost --root .
```
-Use `--with-config`, `--reference`, or `--scope` only when the repo needs local
-routing, reference libraries, or nested product areas.
+Use `--reference` when a reference library should seed inventory, `--scope`
+for nested product areas, or `--package
` when initializing an exact
+package directory such as `product-surface/`.
For monorepos, `ghost init --monorepo` creates or preserves the root package,
detects workspace child roots, and prints proposed `ghost init --scope ...`
commands by default. Run `ghost init --monorepo --apply` to create the detected
child packages. Host wrappers that need Ghost files somewhere other than
-`.ghost` may set `GHOST_MEMORY_DIR=` on the child `ghost` process,
-or pass `--memory-dir ` explicitly. Explicit `init [dir]` and
-`--memory-dir ` values win over the environment default.
+`.ghost` may set `GHOST_PACKAGE_DIR=` on the child `ghost`
+process. Exact `--package ` values win over the environment default.
Drafted fingerprint edits are just ordinary file changes until Git review
-accepts them. Checked-in `fingerprint/` facet files are the Ghost source of
-truth.
+accepts them. Checked-in Ghost facet files are the Ghost source of truth.
## Generate From Ghost
@@ -159,7 +158,7 @@ context. It does not call an LLM.
| Command | Description |
| --- | --- |
-| `ghost init` | Create `.ghost/fingerprint/` package facet files. |
+| `ghost init` | Create `.ghost/` package facet files. |
| `ghost scan` | Report sparse fingerprint contribution facets. |
| `ghost lint` | Validate a fingerprint package or individual artifact. |
| `ghost verify` | Validate evidence paths, exemplar paths, and typed check refs. |
@@ -206,7 +205,7 @@ optional and only used by semantic embedding helpers when a host opts in.
| Resource | Description |
| --- | --- |
-| [docs/fingerprint-format.md](./docs/fingerprint-format.md) | Portable `.ghost/fingerprint/` package format. |
+| [docs/fingerprint-format.md](./docs/fingerprint-format.md) | Portable `.ghost/` package format. |
| [docs/generation-loop.md](./docs/generation-loop.md) | Brief, generate, check, review, and remediate loop. |
| [docs/language-fingerprints.md](./docs/language-fingerprints.md) | Voice and language capture through existing fingerprint facets. |
| [docs/host-adapters.md](./docs/host-adapters.md) | Adapter-neutral JSON, severity mapping, and custom fingerprint directories. |
diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx
index e83da1f9..12e77918 100644
--- a/apps/docs/src/app/page.tsx
+++ b/apps/docs/src/app/page.tsx
@@ -63,8 +63,7 @@ export default function Home() {
Ghost keeps that model compact:
-
-
.ghost/fingerprint/ is the portable fingerprint
- package
+ .ghost/ is the default portable fingerprint package
-
intent.yml, inventory.yml, and{" "}
diff --git a/apps/docs/src/content/docs/cli-reference.mdx b/apps/docs/src/content/docs/cli-reference.mdx
index 0227b5b5..a8294395 100644
--- a/apps/docs/src/content/docs/cli-reference.mdx
+++ b/apps/docs/src/content/docs/cli-reference.mdx
@@ -22,12 +22,12 @@ Canonical Ghost fingerprints start here, with optional child packages for scoped
product areas:
```text
-.ghost/fingerprint/manifest.yml
-.ghost/fingerprint/intent.yml
-.ghost/fingerprint/inventory.yml
-.ghost/fingerprint/composition.yml
-.ghost/fingerprint/validate.yml
-apps/checkout/.ghost/fingerprint/manifest.yml
+.ghost/manifest.yml
+.ghost/intent.yml
+.ghost/inventory.yml
+.ghost/composition.yml
+.ghost/validate.yml
+apps/checkout/.ghost/manifest.yml
```
The command tables below are generated from the CLI source. Run
@@ -39,15 +39,14 @@ The command tables below are generated from the CLI source. Run
### Initialize - `init`
-Create a `.ghost/fingerprint/` package with a manifest, raw facet files,
+Create a `.ghost/` package with a manifest, raw facet files,
and deterministic checks. Use `--scope ` for nested package roots. Use
`--monorepo` to create or preserve the root package, detect workspace child
roots, and print scoped init commands; add `--apply` to create the detected
child packages. Use
-`GHOST_MEMORY_DIR=` only when a host wrapper stores Ghost package
+`GHOST_PACKAGE_DIR=` only when a host wrapper stores Ghost package
roots under a different safe relative directory; raw `ghost` defaults to
-`.ghost`. Explicit `init [dir]` and `--memory-dir ` values win
-over the environment default.
+`.ghost`. Exact `--package ` values win over the environment default.
@@ -56,13 +55,15 @@ ghost init
ghost init --monorepo
ghost init --monorepo --apply
ghost init --scope apps/checkout
-GHOST_MEMORY_DIR=.agents/ghost ghost init
-ghost init --scope apps/checkout --memory-dir .design/memory
+ghost init --package product-surface
+ghost init --package .design/custom-ghost
+GHOST_PACKAGE_DIR=.agents/ghost ghost init
+GHOST_PACKAGE_DIR=.design/memory ghost init --scope apps/checkout
```
### Contribution facets - `scan`
-Report whether `fingerprint/manifest.yml` is present and which sparse facets
+Report whether `manifest.yml` is present and which sparse facets
this package contributes: `intent`, `inventory`, `composition`, and `validate`.
Raw repo signals do not count toward inventory contribution.
@@ -71,8 +72,8 @@ Raw repo signals do not count toward inventory contribution.
```bash
ghost scan
ghost scan --format json
-GHOST_MEMORY_DIR=.agents/ghost ghost scan --format json
-ghost scan --include-nested --memory-dir .design/memory --format json
+GHOST_PACKAGE_DIR=.agents/ghost ghost scan --format json
+GHOST_PACKAGE_DIR=.design/memory ghost scan --include-nested --format json
```
### Stack inspection - `stack`
@@ -109,21 +110,21 @@ Validate a root `.ghost` fingerprint package or an individual split artifact.
```bash
ghost lint
-ghost lint .ghost/fingerprint/intent.yml
-ghost lint .ghost/fingerprint/validate.yml --format json
+ghost lint .ghost/intent.yml
+ghost lint .ghost/validate.yml --format json
ghost lint --all
```
### Package fidelity - `verify`
-Validate fingerprint evidence and exemplar paths, typed check refs, optional
-rationale files, and config references.
+Validate fingerprint evidence and exemplar paths, typed check refs, and
+optional rationale files.
```bash
ghost verify .ghost --root .
-ghost verify --all --memory-dir .design/memory
+GHOST_PACKAGE_DIR=.design/memory ghost verify --all
```
### Reusable Review Command - `emit`
@@ -142,6 +143,11 @@ and gaps.
+```bash
+ghost relay gather apps/checkout/review/page.tsx
+ghost relay gather apps/checkout/review/page.tsx --package product-surface --format json
+```
+
### Inspection - `describe`
Print a markdown section map.
diff --git a/apps/docs/src/content/docs/fingerprint-authoring.mdx b/apps/docs/src/content/docs/fingerprint-authoring.mdx
index 1b72144c..6f285361 100644
--- a/apps/docs/src/content/docs/fingerprint-authoring.mdx
+++ b/apps/docs/src/content/docs/fingerprint-authoring.mdx
@@ -143,7 +143,7 @@ ghost verify --all
Uncommitted or unmerged fingerprint edits are drafts. Checked-in
-`fingerprint/` core files are canonical.
+Ghost package facet files are canonical.
Add deterministic checks sparingly, and only when a rule can be enforced
deterministically.
diff --git a/apps/docs/src/content/docs/getting-started.mdx b/apps/docs/src/content/docs/getting-started.mdx
index 7fad914a..634e6b6f 100644
--- a/apps/docs/src/content/docs/getting-started.mdx
+++ b/apps/docs/src/content/docs/getting-started.mdx
@@ -17,22 +17,18 @@ The canonical portable fingerprint is a folder:
```text
.ghost/
- config.yml
- fingerprint/
- manifest.yml
- intent.yml
- inventory.yml
- composition.yml
- validate.yml
+ manifest.yml
+ intent.yml
+ inventory.yml
+ composition.yml
+ validate.yml
```
Generation starts from `intent.yml`, `inventory.yml`, and `composition.yml`.
`validate.yml` checks validate the result afterward; they are not generation input.
-`.ghost/config.yml` stays outside the portable package because it is local
-routing config.
Nested product areas can add child package roots such as
-`apps/checkout/.ghost/fingerprint/`. Ghost resolves fingerprint stacks
+`apps/checkout/.ghost/`. Ghost resolves fingerprint stacks
root-to-leaf for the file or diff being reviewed.
@@ -42,7 +38,7 @@ root-to-leaf for the file or diff being reviewed.
Ghost is pre-1.0 and under active development. The CLI, fingerprint schema,
-on-disk `.ghost/fingerprint/` package shape, and public JavaScript exports may
+on-disk `.ghost/` package shape, and public JavaScript exports may
change in breaking ways before a stable 1.0 release.
Breaking changes may ship in minor versions while Ghost is pre-1.0. Patch
@@ -153,7 +149,7 @@ ghost track new-tracked.fingerprint.md
ghost diverge typography --reason "Editorial product uses a different type scale"
```
-Package comparison uses canonical `.ghost/fingerprint/` packages. `ack`,
+Package comparison uses canonical `.ghost/` packages. `ack`,
`track`, and `diverge` record stance for compatibility drift workflows that
track direct fingerprint markdown references.
diff --git a/apps/docs/src/generated/cli-manifest.json b/apps/docs/src/generated/cli-manifest.json
index 85a94b72..c7fd95d5 100644
--- a/apps/docs/src/generated/cli-manifest.json
+++ b/apps/docs/src/generated/cli-manifest.json
@@ -1,5 +1,5 @@
{
- "generatedAt": "2026-06-23T20:06:18.125Z",
+ "generatedAt": "2026-06-24T04:21:56.789Z",
"tools": [
{
"tool": "ghost",
@@ -29,55 +29,39 @@
"default": null,
"takesValue": false,
"negated": false
- },
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers, --all, and default package lookup (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
}
]
},
{
"tool": "ghost",
"name": "init",
- "rawName": "init [dir]",
+ "rawName": "init",
"description": "Create a root .ghost split fingerprint package",
"group": "core",
"defaultHelp": true,
"compactName": "init",
- "summary": "Create .ghost/fingerprint/ package facets.",
+ "summary": "Create .ghost/ package facets.",
"options": [
{
"rawName": "--scope ",
"name": "scope",
- "description": "Create a scoped / fingerprint package",
+ "description": "Create a scoped /.ghost fingerprint package",
"default": null,
"takesValue": true,
"negated": false
},
{
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers, init --scope, and default root init (env: GHOST_MEMORY_DIR; default: .ghost)",
+ "rawName": "--package ",
+ "name": "package",
+ "description": "Exact fingerprint package directory to initialize",
"default": null,
"takesValue": true,
"negated": false
},
- {
- "rawName": "--with-config",
- "name": "withConfig",
- "description": "Also create optional config.yml for implementation roots and reference registries/libraries",
- "default": null,
- "takesValue": false,
- "negated": false
- },
{
"rawName": "--reference ",
"name": "reference",
- "description": "Reference UI registry, library path, or fingerprint to record in config.yml and inventory building blocks",
+ "description": "Reference UI registry, library path, or fingerprint to record in inventory building blocks",
"default": null,
"takesValue": true,
"negated": false
@@ -149,14 +133,6 @@
"default": null,
"takesValue": false,
"negated": false
- },
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers, --all, and default package lookup (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
}
]
},
@@ -186,14 +162,6 @@
"takesValue": false,
"negated": false
},
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers, nested discovery, and default scan (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "--format ",
"name": "format",
@@ -214,14 +182,6 @@
"compactName": "stack",
"summary": "Inspect a nested fingerprint stack for repo paths.",
"options": [
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "--format ",
"name": "format",
@@ -353,14 +313,6 @@
"takesValue": true,
"negated": false
},
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers and --path stack resolution (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "-o, --out ",
"name": "out",
@@ -567,7 +519,7 @@
"tool": "ghost",
"name": "drift",
"rawName": "drift ",
- "description": "Inspect continuous design-loop drift status or run the stance-ledger check.",
+ "description": "Inspect Ghost drift status or run the stance-ledger check.",
"group": "compare",
"defaultHelp": false,
"compactName": "drift check",
@@ -649,14 +601,6 @@
"takesValue": true,
"negated": false
},
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers and stack resolution (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "--name ",
"name": "name",
@@ -745,14 +689,6 @@
"takesValue": true,
"negated": false
},
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "--format ",
"name": "format",
@@ -797,14 +733,6 @@
"takesValue": true,
"negated": false
},
- {
- "rawName": "--memory-dir ",
- "name": "memoryDir",
- "description": "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- "default": null,
- "takesValue": true,
- "negated": false
- },
{
"rawName": "--max-diff-bytes ",
"name": "maxDiffBytes",
diff --git a/docs/fingerprint-format.md b/docs/fingerprint-format.md
index d062281a..5f46fd1f 100644
--- a/docs/fingerprint-format.md
+++ b/docs/fingerprint-format.md
@@ -2,23 +2,19 @@
A Ghost fingerprint is a checked-in, repo-local surface-composition contract
that humans can approve and agents can act from. The canonical portable package
-lives under `.ghost/fingerprint/`:
+lives under `.ghost/`:
```text
.ghost/
- config.yml # optional local routing; not portable context
- fingerprint/
- manifest.yml # ghost.fingerprint-package/v1 package anchor
- intent.yml # core: surface intent
- inventory.yml # core: curated material and source links
- composition.yml # core: experience patterns
- validate.yml # optional deterministic gates
+ manifest.yml # ghost.fingerprint-package/v1 package anchor
+ intent.yml # core: surface intent
+ inventory.yml # core: curated material and source links
+ composition.yml # core: experience patterns
+ validate.yml # optional deterministic gates
```
Git is the staging and approval boundary: uncommitted or unmerged edits are
-draft work, and checked-in `fingerprint/` core files are canonical for Ghost.
-`.ghost/config.yml` stays outside the portable package because it routes local
-implementation roots and reference libraries rather than surface context.
+draft work, and checked-in facet files are canonical for Ghost.
`manifest.yml` is intentionally small:
@@ -129,7 +125,7 @@ valid, such as `inventory.exemplars[].refs`.
## Enforcement
-`fingerprint/validate.yml` uses `ghost.validate/v1`. Checks are
+`validate.yml` uses `ghost.validate/v1`. Checks are
deterministic validation, not generation input.
```yaml
@@ -183,13 +179,13 @@ until there is a reliable detector.
Large repos can add scoped packages below the root:
```text
-.ghost/fingerprint/...
-apps/checkout/.ghost/fingerprint/...
+.ghost/...
+apps/checkout/.ghost/...
apps/checkout/review/page.tsx
```
For a path like `apps/checkout/review/page.tsx`, Ghost resolves each
-`/fingerprint/manifest.yml` from root to leaf. Each package is a
+`/manifest.yml` from root to leaf. Each package is a
sparse patch: it contributes only the facets it knows, and the resolved stack
supplies the working context. The merged stack is broad-to-local:
@@ -269,10 +265,9 @@ Do not:
- treat cache output as canonical surface guidance;
- promote subjective taste directly into a check without a deterministic
detector;
-- put structural gate configuration in intent;
-- use `.ghost/config.yml` as portable surface context.
+- put structural gate configuration in intent.
Legacy `resources.yml`, `map.md`, `survey.json`, `patterns.yml`, direct
`fingerprint.md`, and direct `fingerprint.yml` files may appear in older repos
or explicit compatibility workflows. New Ghost work should target the split
-portable package under `.ghost/fingerprint/`.
+portable package under `.ghost/`.
diff --git a/docs/generation-loop.md b/docs/generation-loop.md
index 9ae53cd4..6dcc7861 100644
--- a/docs/generation-loop.md
+++ b/docs/generation-loop.md
@@ -5,7 +5,7 @@ product-surface composition fingerprint. Generation starts from checked-in
facets; checks and review govern the result afterward.
```text
-fingerprint/intent.yml + fingerprint/inventory.yml + fingerprint/composition.yml
+intent.yml + inventory.yml + composition.yml
|
v
host agent or generator
@@ -38,7 +38,7 @@ Use the brief in this order:
3. Inspect inventory hits as concrete anchors.
4. Use `inventory.building_blocks` as curated material.
5. Run `ghost signals` when raw repo observations would help find evidence.
-6. Skim active checks in `.ghost/fingerprint/validate.yml` so generation avoids
+6. Skim active checks in `.ghost/validate.yml` so generation avoids
deterministic failures.
7. Treat gaps as a signal to use local evidence provisionally or inspect the
full facet files.
@@ -101,13 +101,13 @@ ghost check --base main
ghost review --base main --format markdown
```
-Advanced wrappers that store fingerprint packages outside `.ghost` can pass
-`--memory-dir ` to stack-aware commands. `--package ` remains
-exact single-bundle mode and bypasses stack discovery.
+Advanced wrappers that store fingerprint packages outside `.ghost` can set
+`GHOST_PACKAGE_DIR=` on stack-aware commands. `--package `
+remains exact single-bundle mode and bypasses stack discovery.
## Legacy Cache Helpers
Older Ghost bundles used `resources.yml`, `map.md`, `survey.json`,
-`patterns.yml`, and direct `.ghost/fingerprint.yml` as capture material. Those
-files are now legacy/cache source material. Promote durable conclusions into
-`intent.yml`, `inventory.yml`, and `composition.yml`.
+`patterns.yml`, and direct `fingerprint.yml` files under `.ghost/` as capture
+material. Those files are now legacy/cache source material. Promote durable
+conclusions into `intent.yml`, `inventory.yml`, and `composition.yml`.
diff --git a/docs/ghost-fleet.md b/docs/ghost-fleet.md
index 740c7911..25aa2c9e 100644
--- a/docs/ghost-fleet.md
+++ b/docs/ghost-fleet.md
@@ -58,7 +58,7 @@ sections `World shape`, `Cohorts`, and `Tracks` from the deterministic output.
- Fleet consumes direct `map.md` and `fingerprint.md` snapshots for the private
fleet workflow. That compatibility shape does not change the public
- `.ghost/fingerprint/` package model.
+ `.ghost/` package model.
- Fleet may read scoped overlays from `fingerprints/.md`; those are
member snapshots, not nested package roots.
- Clusters are a narrative projection over distances and groupings. They are
diff --git a/docs/host-adapters.md b/docs/host-adapters.md
index 8c58c209..f6a3a6bd 100644
--- a/docs/host-adapters.md
+++ b/docs/host-adapters.md
@@ -9,10 +9,9 @@ review/check file formats.
Ghost provides:
-- `.ghost/fingerprint/` package loading and stack merging.
-- `fingerprint/intent.yml`, `fingerprint/inventory.yml`, and
- `fingerprint/composition.yml` as generation context.
-- Optional `fingerprint/validate.yml`.
+- `.ghost/` package loading and stack merging.
+- `intent.yml`, `inventory.yml`, and `composition.yml` as generation context.
+- Optional `validate.yml`.
- `ghost signals` stdout output for raw repo observations.
- `ghost check --format json` as the stable `ghost.check-report/v1` contract.
- `ghost review --format json` for advisory packets grounded in the resolved
@@ -20,8 +19,8 @@ Ghost provides:
- `ghost relay gather [target] --format json` as the `ghost.relay.gather/v2`
contract for generation context, including selected `context_hits`, match
reasons, suggested reads, omissions, and gaps.
-- `--memory-dir ` for wrappers that store Ghost package roots
- somewhere other than `.ghost`.
+- `GHOST_PACKAGE_DIR=` for wrappers that store Ghost package
+ roots somewhere other than `.ghost`.
Host adapters provide:
@@ -56,15 +55,15 @@ The exact labels belong to the host.
## Custom Fingerprint Directories
-The default package root is `.ghost`, and the portable package lives inside it
-at `fingerprint/`. Wrappers can use any safe relative package root:
+The default package root is `.ghost`. Wrappers can use any safe relative
+package root by setting `GHOST_PACKAGE_DIR` on the Ghost process:
```bash
-ghost init --scope apps/checkout --memory-dir .design/memory
-ghost stack apps/checkout/review/page.tsx --memory-dir .design/memory --format json
-ghost relay gather apps/checkout/review/page.tsx --memory-dir .design/memory --format json
-ghost check --base main --memory-dir .design/memory --format json
-ghost review --base main --memory-dir .design/memory --format json
+GHOST_PACKAGE_DIR=.design/memory ghost init --scope apps/checkout
+GHOST_PACKAGE_DIR=.design/memory ghost stack apps/checkout/review/page.tsx --format json
+GHOST_PACKAGE_DIR=.design/memory ghost relay gather apps/checkout/review/page.tsx --format json
+GHOST_PACKAGE_DIR=.design/memory ghost check --base main --format json
+GHOST_PACKAGE_DIR=.design/memory ghost review --base main --format json
```
`--package ` remains exact single-bundle mode. Use it when the caller
diff --git a/docs/ideas/README.md b/docs/ideas/README.md
index 20bd4efb..6f1dd212 100644
--- a/docs/ideas/README.md
+++ b/docs/ideas/README.md
@@ -14,7 +14,7 @@ Retained notes:
- `fingerprint-first-architecture.md` records the settled product center:
Ghost is fingerprint-first, and drift is one governance workflow over the
- portable `.ghost/fingerprint/` package.
+ portable `.ghost/` package.
- `ghost-ui.md` explores additive registry metadata for the private Ghost UI
reference package.
- `guided-migration.md` explores a future host-agent workflow for migrating one
diff --git a/docs/ideas/fingerprint-first-architecture.md b/docs/ideas/fingerprint-first-architecture.md
index 0954565c..c49f0e50 100644
--- a/docs/ideas/fingerprint-first-architecture.md
+++ b/docs/ideas/fingerprint-first-architecture.md
@@ -15,15 +15,15 @@ Drift remains important, but it is one governance workflow over the fingerprint,
not the center of the architecture.
This settles the mental model for follow-on work. Current docs describe
-`.ghost/fingerprint/` as the portable package and generation context.
+`.ghost/` as the portable package and generation context.
Superseded decomposition notes have been pruned; future idea docs should stay
subordinate to this hierarchy. The fingerprint owns surface context. Tools
consume, validate, compare, generate from, or govern that context.
## Decisions
-- The fingerprint package is the durable artifact. `.ghost/fingerprint/`
- remains the portable boundary, anchored by `fingerprint/manifest.yml`.
+- The fingerprint package is the durable artifact. `.ghost/` remains the
+ portable boundary, anchored by `manifest.yml`.
- Core generation input is `intent.yml`, `inventory.yml`, and
`composition.yml`. `intent.yml` captures the intent behind the surface,
`inventory.yml` points to curated material and exemplars, and
@@ -107,7 +107,7 @@ or create public API guarantees. It gives follow-on work a shared hierarchy.
generate from fingerprints before reviewing drift.
5. Schema and model exploration: write separate design notes for signature
moves and buildable inventory evidence before any schema change.
-6. Dogfood fingerprint: update `.ghost/fingerprint/` for this repo and validate
+6. Dogfood fingerprint: update `.ghost/` for this repo and validate
it with `ghost scan`, `ghost lint`, and `ghost verify`.
## Interface stance
@@ -125,5 +125,4 @@ The memo is successful if:
- A contributor can see why drift commands still matter.
- A follow-on agent can split docs, CLI, skill, schema, and dogfood work without
asking what the architectural center is.
-- Nothing here contradicts the current canonical package shape under
- `.ghost/fingerprint/`.
+- Nothing here contradicts the current canonical package shape under `.ghost/`.
diff --git a/docs/ideas/ghost-ui.md b/docs/ideas/ghost-ui.md
index 4b709ad5..da0a7b71 100644
--- a/docs/ideas/ghost-ui.md
+++ b/docs/ideas/ghost-ui.md
@@ -28,7 +28,7 @@ Tools see registries and filesystems; ghost-ui makes the registry case sing.
A three-part demonstration:
1. **Stays a shadcn registry package.** The registry extension is additive and
- does not change the public `.ghost/fingerprint/` package model.
+ does not change the public `.ghost/` package model.
2. **Per-component dimension tags.** Each component in registry.json can carry optional `meta.fingerprint_dimensions: [palette, spacing, typography, surfaces]` declaring which design dimensions the component primarily expresses. Review and comparison workflows can use this for higher-confidence attribution.
3. **Shape-aware exemplar tags.** Examples can distinguish atoms from composed response shapes with optional `meta.exemplar_kind: "atom" | "shape"` and `meta.response_shapes: ["article" | "tracker" | "comparison" | "card"]`. This gives generators a narrow reference set before they compose a freeform answer.
diff --git a/docs/language-fingerprints.md b/docs/language-fingerprints.md
index a7b36e50..026c1f30 100644
--- a/docs/language-fingerprints.md
+++ b/docs/language-fingerprints.md
@@ -8,11 +8,11 @@ Ghost does not need a new domain, schema, or dimension set to capture this.
Language flows through the same facets as every other
surface-composition concern:
-- `fingerprint/intent.yml` carries voice intent.
-- `fingerprint/inventory.yml` points at copy material and external writing
+- `intent.yml` carries voice intent.
+- `inventory.yml` points at copy material and external writing
standards.
-- `fingerprint/composition.yml` carries copy patterns.
-- `fingerprint/validate.yml` carries the deterministic subset.
+- `composition.yml` carries copy patterns.
+- `validate.yml` carries the deterministic subset.
This document shows the mapping. Nothing here changes the
`ghost.fingerprint/v1` schema.
@@ -24,7 +24,7 @@ rationale become principles. Surfaces with non-negotiable wording become
experience contracts.
```yaml
-# fingerprint/intent.yml
+# intent.yml
summary:
tone:
- plain
@@ -75,7 +75,7 @@ terminology list, a banned-phrase list. The fingerprint should point at that
source, not fork it. `inventory.sources` already supports this.
```yaml
-# fingerprint/inventory.yml
+# inventory.yml
building_blocks:
files:
- src/i18n/en.json
@@ -101,7 +101,7 @@ source applies and which surfaces it governs.
`composition.yml` already has `kind: content` for exactly this.
```yaml
-# fingerprint/composition.yml
+# composition.yml
patterns:
- id: error-message-shape
kind: content
@@ -143,7 +143,7 @@ Only the mechanically detectable subset belongs in `validate.yml`. The
required boilerplate today:
```yaml
-# fingerprint/validate.yml
+# validate.yml
checks:
- id: no-banned-phrases
title: Banned phrases stay out of user-facing copy
diff --git a/install/install.sh b/install/install.sh
index 5255b035..0afd6b83 100755
--- a/install/install.sh
+++ b/install/install.sh
@@ -194,6 +194,6 @@ printf 'Next:\n'
printf ' cd \n'
printf ' Tell your agent: "Set up the Ghost fingerprint for this repo"\n'
printf '\n'
-printf 'The agent will use .ghost/fingerprint/ as checked-in surface-composition context,\n'
+printf 'The agent will use .ghost/ as checked-in surface-composition context,\n'
printf 'generate from intent.yml, inventory.yml, and composition.yml, keep optional\n'
-printf 'deterministic gates in fingerprint/validate.yml, and run ghost lint/verify/check/review.\n'
+printf 'deterministic gates in validate.yml, and run ghost lint/verify/check/review.\n'
diff --git a/packages/ghost-core/src/fingerprint-package.ts b/packages/ghost-core/src/fingerprint-package.ts
index 3076032f..f91f3042 100644
--- a/packages/ghost-core/src/fingerprint-package.ts
+++ b/packages/ghost-core/src/fingerprint-package.ts
@@ -2,13 +2,11 @@ export const FINGERPRINT_PACKAGE_DIR = ".ghost" as const;
export const RESOURCES_FILENAME = "resources.yml" as const;
export const PATTERNS_FILENAME = "patterns.yml" as const;
export const FINGERPRINT_YML_FILENAME = "fingerprint.yml" as const;
-export const CONFIG_FILENAME = "config.yml" as const;
export const FINGERPRINT_FILENAME = "fingerprint.md" as const;
export interface FingerprintPackagePaths {
dir: string;
fingerprintYml: string;
- config: string;
resources: string;
map: string;
survey: string;
diff --git a/packages/ghost-core/src/index.ts b/packages/ghost-core/src/index.ts
index 71ebe12d..70a8dce4 100644
--- a/packages/ghost-core/src/index.ts
+++ b/packages/ghost-core/src/index.ts
@@ -61,7 +61,6 @@ export {
} from "./embedding/index.js";
// --- Map (ghost.map/v1) ---
export {
- CONFIG_FILENAME,
FINGERPRINT_FILENAME,
FINGERPRINT_PACKAGE_DIR,
FINGERPRINT_YML_FILENAME,
diff --git a/packages/ghost-ui/.ghost/fingerprint/intent.yml b/packages/ghost-ui/.ghost/intent.yml
similarity index 100%
rename from packages/ghost-ui/.ghost/fingerprint/intent.yml
rename to packages/ghost-ui/.ghost/intent.yml
diff --git a/packages/ghost-ui/.ghost/fingerprint/inventory.yml b/packages/ghost-ui/.ghost/inventory.yml
similarity index 100%
rename from packages/ghost-ui/.ghost/fingerprint/inventory.yml
rename to packages/ghost-ui/.ghost/inventory.yml
diff --git a/packages/ghost-ui/.ghost/fingerprint/manifest.yml b/packages/ghost-ui/.ghost/manifest.yml
similarity index 100%
rename from packages/ghost-ui/.ghost/fingerprint/manifest.yml
rename to packages/ghost-ui/.ghost/manifest.yml
diff --git a/packages/ghost-ui/README.md b/packages/ghost-ui/README.md
index 77d16c2a..e4d8c2c9 100644
--- a/packages/ghost-ui/README.md
+++ b/packages/ghost-ui/README.md
@@ -11,14 +11,14 @@ looking for the fingerprint capture and drift-review tool, that's
## Registry convention
This workspace carries a repo-local Ghost reference bundle in `.ghost/`.
-`fingerprint/intent.yml` and `fingerprint/inventory.yml` describe Ghost UI as implementation vocabulary: tokens,
+`.ghost/intent.yml` and `.ghost/inventory.yml` describe Ghost UI as implementation vocabulary: tokens,
component families, registry shape, and reference-registry boundaries. It does
not define product-specific flows, copy, trust obligations, or business intent
for consuming apps. New products should reference this bundle and the generated
`public/r/registry.json`, then fill their own surface-composition fingerprint
separately.
-Agents should read this README, `.ghost/fingerprint/`,
+Agents should read this README, `.ghost/`,
`public/r/registry.json`, `registry.json`, `.shadcn/skills.md`, and source files
when integrating components.
@@ -38,7 +38,7 @@ That distinction helps generators pick relevant references instead of treating e
- **Components** — 49 UI primitives (Radix-based) + 48 AI elements (chat, streaming, agent UI) + theme + hooks.
- **Tokens** — `src/styles/` CSS custom properties consumed by the registry and components.
- **Registry** — `public/r/registry.json`, generated shadcn-compatible catalogue for consumption. Source entries live in `registry.json`; rebuilt by `just build-registry`.
-- **Ghost reference context** — `.ghost/fingerprint/`, used as reference-registry context by consuming products.
+- **Ghost reference context** — `.ghost/`, used as reference-registry context by consuming products.
- **Agent context** — `.shadcn/skills.md`, generated from the registry and component sources for AI assistants.
## Use
diff --git a/packages/ghost/README.md b/packages/ghost/README.md
index 52d2c56f..bdef9c74 100644
--- a/packages/ghost/README.md
+++ b/packages/ghost/README.md
@@ -4,7 +4,7 @@
Ghost captures the composition of a product surface: the intent behind it, the
materials it draws from, and the patterns that make it feel intentional. It
-stores that composition in a repo-local `.ghost/fingerprint/` package that host
+stores that composition in a repo-local `.ghost/` package that host
agents can read before generation and validate after changes.
This package ships one CLI: `ghost`.
@@ -13,7 +13,7 @@ This package ships one CLI: `ghost`.
> [!WARNING]
> Ghost is pre-1.0 and under active development. The CLI, fingerprint schema,
-> on-disk `.ghost/fingerprint/` package shape, and public JavaScript exports may
+> on-disk `.ghost/` package shape, and public JavaScript exports may
> change in breaking ways before a stable 1.0 release.
>
> Breaking changes may ship in minor versions while Ghost is pre-1.0. Patch
@@ -88,7 +88,7 @@ import {
Ghost is bring-your-own-agent. The CLI performs deterministic work: repo
signals, readiness reporting, linting, verification, comparison, checks, and
advisory review packet generation. The installed `ghost` skill teaches a host
-agent how to capture canonical `.ghost/fingerprint/` surface-composition
+agent how to capture canonical `.ghost/` surface-composition
context, brief and generate work from it, review changes against it, verify
generated UI, remediate issues, and suggest fingerprint edits when the user
asks.
diff --git a/packages/ghost/src/cli.ts b/packages/ghost/src/cli.ts
index 604a05d6..3eabe4f7 100644
--- a/packages/ghost/src/cli.ts
+++ b/packages/ghost/src/cli.ts
@@ -173,10 +173,6 @@ export function buildCli(): ReturnType {
"--package ",
"Exact fingerprint package directory; bypasses stack discovery",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option("--format ", "Output format: markdown or json", {
default: "markdown",
})
@@ -195,8 +191,6 @@ export function buildCli(): ReturnType {
cwd: process.cwd(),
packageDir:
typeof opts.package === "string" ? opts.package : undefined,
- memoryDir:
- typeof opts.memoryDir === "string" ? opts.memoryDir : undefined,
base: typeof opts.base === "string" ? opts.base : undefined,
diffText,
});
@@ -229,10 +223,6 @@ export function buildCli(): ReturnType {
"--package ",
"Exact fingerprint package directory; bypasses stack discovery",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option(
"--max-diff-bytes ",
"Maximum diff bytes to include in the review packet (default: 200000)",
@@ -259,8 +249,6 @@ export function buildCli(): ReturnType {
: await readGitDiff(process.cwd(), opts.base ?? "HEAD");
const packet = await buildReviewPacket({
packageDir,
- memoryDir:
- typeof opts.memoryDir === "string" ? opts.memoryDir : undefined,
diffText,
maxDiffBytes,
});
diff --git a/packages/ghost/src/command-discovery.ts b/packages/ghost/src/command-discovery.ts
index ae99a378..2d8cf93c 100644
--- a/packages/ghost/src/command-discovery.ts
+++ b/packages/ghost/src/command-discovery.ts
@@ -36,7 +36,7 @@ const COMMAND_DISCOVERY = [
group: "core",
defaultHelp: true,
compactName: "init",
- summary: "Create .ghost/fingerprint/ package facets.",
+ summary: "Create .ghost/ package facets.",
},
{
name: "scan",
diff --git a/packages/ghost/src/comparable-fingerprint.ts b/packages/ghost/src/comparable-fingerprint.ts
index 0047fe7c..2d07c381 100644
--- a/packages/ghost/src/comparable-fingerprint.ts
+++ b/packages/ghost/src/comparable-fingerprint.ts
@@ -57,8 +57,8 @@ export async function loadComparableFingerprint(
function normalizeFingerprintPackageInput(path: string): string {
const normalized = path.replace(/\\/g, "/");
- return /(^|\/)fingerprint\/manifest\.ya?ml$/i.test(normalized)
- ? dirname(dirname(normalized))
+ return /(^|\/)manifest\.ya?ml$/i.test(normalized)
+ ? dirname(normalized)
: path;
}
diff --git a/packages/ghost/src/context/entrypoint-markdown.ts b/packages/ghost/src/context/entrypoint-markdown.ts
index 112488de..abd2861f 100644
--- a/packages/ghost/src/context/entrypoint-markdown.ts
+++ b/packages/ghost/src/context/entrypoint-markdown.ts
@@ -129,7 +129,7 @@ function formatUseThisContext(): string {
- Generate from intent + inventory + composition; use building blocks only when they support selected intent and patterns.
- Treat checks as validation; only active checks are blocking.
- When selected context is sparse or globally matched, label reasoning as provisional and non-Ghost-backed.
-- Treat fingerprint edits as ordinary Git-reviewed edits to \`fingerprint/\` files and optional local \`config.yml\` when present.`;
+- Treat fingerprint edits as ordinary Git-reviewed edits to Ghost package facet files.`;
}
function appendNodeGroup(
diff --git a/packages/ghost/src/context/entrypoint.ts b/packages/ghost/src/context/entrypoint.ts
index 133fce44..e8208827 100644
--- a/packages/ghost/src/context/entrypoint.ts
+++ b/packages/ghost/src/context/entrypoint.ts
@@ -263,37 +263,31 @@ function buildSuggestedReads(
): ContextEntrypoint["suggestedReads"] {
const reads = new Map();
if (selected.intent.length > 0) {
- reads.set(
- "fingerprint/intent.yml",
- "selected intent anchors and full intent",
- );
+ reads.set("intent.yml", "selected intent anchors and full intent");
}
if (selected.composition.length > 0) {
reads.set(
- "fingerprint/composition.yml",
+ "composition.yml",
"selected composition patterns and neighboring patterns",
);
}
if (selected.exemplars.length > 0) {
reads.set(
- "fingerprint/inventory.yml",
+ "inventory.yml",
"selected exemplars, topology, and building blocks",
);
}
if (selected.checks.length > 0) {
- reads.set(
- "fingerprint/validate.yml",
- "active deterministic validation rules",
- );
+ reads.set("validate.yml", "active deterministic validation rules");
}
for (const exemplar of selected.exemplars) {
const path = exemplar.appliesTo.paths[0];
if (path) reads.set(path, `source surface for ${exemplar.ref}`);
}
if (reads.size === 0) {
- reads.set("fingerprint/intent.yml", "global fingerprint intent");
- reads.set("fingerprint/inventory.yml", "topology and exemplars");
- reads.set("fingerprint/composition.yml", "composition patterns");
+ reads.set("intent.yml", "global fingerprint intent");
+ reads.set("inventory.yml", "topology and exemplars");
+ reads.set("composition.yml", "composition patterns");
}
return [...reads.entries()].map(([path, reason]) => ({ path, reason }));
}
@@ -358,22 +352,22 @@ function buildOmissions(
{
label: "Intent anchors",
omitted: Math.max(0, totals.intent - selected.intent.length),
- source: "fingerprint/intent.yml",
+ source: "intent.yml",
},
{
label: "Composition patterns",
omitted: Math.max(0, totals.composition - selected.composition.length),
- source: "fingerprint/composition.yml",
+ source: "composition.yml",
},
{
label: "Exemplars",
omitted: Math.max(0, totals.exemplars - selected.exemplars.length),
- source: "fingerprint/inventory.yml",
+ source: "inventory.yml",
},
{
label: "Active checks",
omitted: Math.max(0, totals.checks - selected.checks.length),
- source: "fingerprint/validate.yml",
+ source: "validate.yml",
},
];
}
diff --git a/packages/ghost/src/context/graph.ts b/packages/ghost/src/context/graph.ts
index ff4dafed..c14823a7 100644
--- a/packages/ghost/src/context/graph.ts
+++ b/packages/ghost/src/context/graph.ts
@@ -100,7 +100,7 @@ export function buildFingerprintGraph(
: "",
...(situation.refuses ?? []).map((entry) => `Refuses: ${entry}`),
].filter(Boolean),
- sourceFile: "fingerprint/intent.yml",
+ sourceFile: "intent.yml",
appliesTo: {
surfaceTypes: situation.surface_type ? [situation.surface_type] : [],
paths: evidencePaths(situation.evidence),
@@ -129,7 +129,7 @@ export function buildFingerprintGraph(
(entry) => `Counterexample: ${entry}`,
),
],
- sourceFile: "fingerprint/intent.yml",
+ sourceFile: "intent.yml",
appliesTo: applicabilityFromScope(principle.applies_to),
});
addRefEdges(ref, principle.check_refs, "principle check");
@@ -144,7 +144,7 @@ export function buildFingerprintGraph(
label: contract.id,
summary: contract.contract,
details: contract.obligations ?? [],
- sourceFile: "fingerprint/intent.yml",
+ sourceFile: "intent.yml",
appliesTo: applicabilityFromScope(contract.applies_to),
});
addRefEdges(ref, contract.check_refs, "experience contract check");
@@ -164,7 +164,7 @@ export function buildFingerprintGraph(
? [`Avoid: ${pattern.anti_patterns.join("; ")}`]
: []),
],
- sourceFile: "fingerprint/composition.yml",
+ sourceFile: "composition.yml",
appliesTo: applicabilityFromScope(pattern.applies_to),
});
addRefEdges(ref, pattern.check_refs, "composition check");
@@ -183,7 +183,7 @@ export function buildFingerprintGraph(
exemplar.surface_type ? `Surface type: ${exemplar.surface_type}` : "",
exemplar.scope ? `Scope: ${exemplar.scope}` : "",
].filter(Boolean),
- sourceFile: "fingerprint/inventory.yml",
+ sourceFile: "inventory.yml",
appliesTo: {
paths: [exemplar.path],
scopes: exemplar.scope ? [exemplar.scope] : [],
@@ -205,7 +205,7 @@ export function buildFingerprintGraph(
check.repair ? `Repair: ${check.repair}` : "",
detectorSummary(check),
].filter(Boolean),
- sourceFile: "fingerprint/validate.yml",
+ sourceFile: "validate.yml",
appliesTo: applicabilityFromCheck(check),
});
addRefEdges(ref, check.derivation?.intent, "check intent derivation");
diff --git a/packages/ghost/src/context/package-context.ts b/packages/ghost/src/context/package-context.ts
index 69e1269f..4dbf4c0d 100644
--- a/packages/ghost/src/context/package-context.ts
+++ b/packages/ghost/src/context/package-context.ts
@@ -13,7 +13,7 @@ import {
export interface PackageContext {
name: string;
- fingerprintDir?: string;
+ packageDir?: string;
targetPaths?: string[];
stackDirs?: string[];
fingerprint: GhostFingerprintDocument;
@@ -41,7 +41,7 @@ export async function loadPackageContext(
const checks = checksRaw ? parseChecks(checksRaw, fingerprint) : undefined;
return {
name: sanitizeName(nameOverride ?? inferPackageName(fingerprint)),
- fingerprintDir: paths.dir,
+ packageDir: paths.dir,
fingerprint,
fingerprintRaw: JSON.stringify(fingerprint, null, 2),
fingerprintLayers: {
@@ -57,13 +57,13 @@ function parseChecks(
raw: string,
fingerprint: GhostFingerprintDocument,
): GhostValidateDocument {
- const parsed = parseYamlSafe(raw, "fingerprint/validate.yml");
+ const parsed = parseYamlSafe(raw, "validate.yml");
const report = lintGhostValidate(parsed, { fingerprint });
if (report.errors > 0) {
const first = report.issues.find((issue) => issue.severity === "error");
const suffix = first?.path ? ` @ ${first.path}` : "";
throw new Error(
- `fingerprint/validate.yml failed lint with ${report.errors} error(s): ${
+ `validate.yml failed lint with ${report.errors} error(s): ${
first?.message ?? "invalid checks"
}${suffix}`,
);
@@ -71,7 +71,7 @@ function parseChecks(
const result = GhostValidateSchema.safeParse(parsed);
if (!result.success) {
- throw new Error("fingerprint/validate.yml failed schema validation.");
+ throw new Error("validate.yml failed schema validation.");
}
return result.data as GhostValidateDocument;
}
diff --git a/packages/ghost/src/context/package-review-command.ts b/packages/ghost/src/context/package-review-command.ts
index 82417e3f..340784f0 100644
--- a/packages/ghost/src/context/package-review-command.ts
+++ b/packages/ghost/src/context/package-review-command.ts
@@ -1,3 +1,4 @@
+import { isAbsolute, relative } from "node:path";
import type {
GhostCheck,
GhostFingerprintExemplar,
@@ -25,7 +26,7 @@ const REVIEW_FINDING_CATEGORIES = [
*
* The command stays intentionally light: it tells the host agent which Ghost
* files and CLI packets to use, then includes a compact fingerprint index.
- * Full canonical truth remains in fingerprint/ facet files and deterministic checks.
+ * Full canonical truth remains in facet files and deterministic checks.
*/
export function emitPackageReviewCommand(
input: EmitPackageReviewInput,
@@ -64,17 +65,16 @@ If \`$ARGUMENTS\` is provided, review that file, path, or diff range. If it is e
}
function packageWorkflowSection(context: PackageContext): string {
- const fingerprintDir = context.fingerprintDir ?? ".ghost";
- const memoryDirFlag = stackFingerprintDirFlag(context);
+ const packageDir = displayPackageDir(context);
return `## Review Workflow
-1. Run \`ghost review${memoryDirFlag}\` for the advisory packet when you need full diff context and selected context excerpts. If reviewing manually, read \`${fingerprintDir}/fingerprint/intent.yml\`, \`${fingerprintDir}/fingerprint/inventory.yml\`, and \`${fingerprintDir}/fingerprint/composition.yml\`.
+1. Run \`ghost review\` for the advisory packet when you need full diff context and selected context excerpts. If reviewing manually, read \`${packageDir}/intent.yml\`, \`${packageDir}/inventory.yml\`, and \`${packageDir}/composition.yml\`.
2. Start from selected intent and active obligations before assessing UI, copy, flow, disclosure, recovery, trust, or interaction behavior.
3. Apply composition guidance before choosing implementation details.
4. Inspect inventory exemplars and building blocks as evidence/material, not as authority over intent.
5. Treat validate checks as deterministic enforcement; only active checks can block.
6. Use selected-context gaps to label provisional reasoning or report \`missing-fingerprint\` / \`experience-gap\`.
-7. Run \`ghost check${memoryDirFlag}\` when a diff is available.
+7. Run \`ghost check\` when a diff is available.
8. Cite the diff location, fingerprint facet refs, relevant exemplars when useful, selected-context gaps when context is silent, and any active check when a finding blocks.`;
}
@@ -242,7 +242,7 @@ function formatExemplars(exemplars: GhostFingerprintExemplar[]): string {
}
if (exemplars.length > 12) {
lines.push(
- `- ${exemplars.length - 12} more exemplar(s); inspect \`fingerprint/inventory.yml\` before deciding.`,
+ `- ${exemplars.length - 12} more exemplar(s); inspect \`inventory.yml\` before deciding.`,
);
}
return lines.join("\n");
@@ -252,7 +252,7 @@ function packageChecksSection(activeChecks: GhostCheck[]): string {
if (activeChecks.length === 0) {
return `## Active Checks
-No active checks are recorded. Review remains advisory unless \`fingerprint/validate.yml\` adds deterministic active checks.`;
+No active checks are recorded. Review remains advisory unless \`validate.yml\` adds deterministic active checks.`;
}
const lines = ["## Active Checks", ""];
for (const check of activeChecks.slice(0, 12)) {
@@ -271,23 +271,39 @@ No active checks are recorded. Review remains advisory unless \`fingerprint/vali
}
if (activeChecks.length > 12) {
lines.push(
- `- ${activeChecks.length - 12} more active check(s); read \`fingerprint/validate.yml\` before deciding whether a finding blocks.`,
+ `- ${activeChecks.length - 12} more active check(s); read \`validate.yml\` before deciding whether a finding blocks.`,
);
}
return lines.join("\n");
}
function packageReviewFooter(context: PackageContext): string {
- const fingerprintDir = context.fingerprintDir ?? ".ghost";
+ const packageDir = displayPackageDir(context);
return `---
-Generated from \`${fingerprintDir}/fingerprint/\` for ${context.name}. Re-run \`ghost emit review-command${stackFingerprintDirFlag(context)}\` after updating fingerprint facets or deterministic checks.`;
+Generated from \`${packageDir}/\` for ${context.name}. Re-run \`ghost emit review-command\` after updating fingerprint facets or deterministic checks.`;
}
-function stackFingerprintDirFlag(context: PackageContext): string {
- return context.fingerprintDir && context.fingerprintDir !== ".ghost"
- ? ` --memory-dir ${context.fingerprintDir}`
- : "";
+function displayPackageDir(context: PackageContext): string {
+ return displayPath(context.packageDir ?? ".ghost");
+}
+
+function displayPath(path: string): string {
+ if (!isAbsolute(path)) return path;
+ const relativePath = relative(process.cwd(), path);
+ if (!relativePath) return ".";
+ if (
+ relativePath === ".." ||
+ relativePath.startsWith("../") ||
+ relativePath.startsWith("..\\")
+ ) {
+ return normalizePath(path);
+ }
+ return normalizePath(relativePath);
+}
+
+function normalizePath(path: string): string {
+ return path.replace(/\\/g, "/");
}
function pushJoined(
diff --git a/packages/ghost/src/context/selected-context.ts b/packages/ghost/src/context/selected-context.ts
index addf8065..8a4f7cb6 100644
--- a/packages/ghost/src/context/selected-context.ts
+++ b/packages/ghost/src/context/selected-context.ts
@@ -74,8 +74,8 @@ export function buildSelectedContext(
): SelectedContext {
const packageDirs = context.stackDirs?.length
? context.stackDirs
- : context.fingerprintDir
- ? [context.fingerprintDir]
+ : context.packageDir
+ ? [context.packageDir]
: [];
const stack = packageDirs.map((dir, index) => ({
dir,
@@ -115,7 +115,7 @@ export function formatSelectedContextMarkdown(
const parts = [heading];
if (options.includeIntro ?? true) {
parts.push(
- `Product context: **${context.title.replace(/ Relay Brief$/, "")}**. Use this as compact, target-specific selected context from the resolved fingerprint stack. It does not replace the checked-in \`fingerprint/\` files.`,
+ `Product context: **${context.title.replace(/ Relay Brief$/, "")}**. Use this as compact, target-specific selected context from the resolved fingerprint stack. It does not replace the checked-in Ghost package facets.`,
);
}
parts.push(
@@ -218,7 +218,7 @@ function gapsFromEntrypoint(
gaps.push({
kind: "no-composition",
message:
- "No composition patterns were selected; inspect fingerprint/composition.yml or nearby product surfaces if structure matters.",
+ "No composition patterns were selected; inspect composition.yml or nearby product surfaces if structure matters.",
});
}
if (entrypoint.selected.exemplars.length === 0) {
diff --git a/packages/ghost/src/core/check.ts b/packages/ghost/src/core/check.ts
index e010ec63..021dafe8 100644
--- a/packages/ghost/src/core/check.ts
+++ b/packages/ghost/src/core/check.ts
@@ -20,7 +20,7 @@ import {
import {
groupFingerprintStacksForPaths,
mapFromFingerprint,
- resolveMemoryDirDefault,
+ resolveGhostDirDefault,
} from "../scan/fingerprint-stack.js";
import {
INLINE_COLOR_LITERAL_PATTERN,
@@ -32,7 +32,7 @@ const execFileAsync = promisify(execFile);
export interface GhostDriftCheckOptions {
cwd?: string;
packageDir?: string;
- memoryDir?: string;
+ ghostDir?: string;
base?: string;
diffText?: string;
}
@@ -70,7 +70,7 @@ export interface GhostDriftCheckReport {
schema: "ghost.check-report/v1";
result: "pass" | "fail";
package_dir: string;
- fingerprint_dir?: string;
+ ghost_dir?: string;
base?: string;
changed_files: string[];
routed_files: GhostDriftRoutedFile[];
@@ -81,7 +81,7 @@ export interface GhostDriftCheckReport {
export interface GhostDriftCheckStack {
target_path: string;
package_dir: string;
- fingerprint_dir: string;
+ ghost_dir: string;
changed_files: string[];
stack_dirs: string[];
provenance: {
@@ -126,7 +126,7 @@ export async function runGhostDriftCheck(
const groups = await groupFingerprintStacksForPaths(
changedFiles.map((file) => file.path),
cwd,
- { memoryDir: resolveMemoryDirDefault(options.memoryDir) },
+ { ghostDir: resolveGhostDirDefault(options.ghostDir) },
);
const routedFiles: GhostDriftRoutedFile[] = [];
const findings: GhostDriftCheckFinding[] = [];
@@ -148,7 +148,7 @@ export async function runGhostDriftCheck(
stacks.push({
target_path: group.stack.target_path,
package_dir: pkg.dir,
- fingerprint_dir: group.stack.fingerprint_dir,
+ ghost_dir: group.stack.ghost_dir,
changed_files: group.changed_files,
stack_dirs: group.stack.layers.map((layer) => layer.dir),
provenance: {
@@ -165,7 +165,7 @@ export async function runGhostDriftCheck(
stacks.length === 1
? stacks[0].package_dir
: "fingerprint-stack/multiple",
- fingerprint_dir: stacks[0]?.fingerprint_dir ?? options.memoryDir,
+ ghost_dir: stacks[0]?.ghost_dir ?? options.ghostDir,
...(options.base ? { base: options.base } : {}),
changed_files: changedFiles.map((file) => file.path),
routed_files: routedFiles,
diff --git a/packages/ghost/src/drift-command.ts b/packages/ghost/src/drift-command.ts
index 02ee9a41..45aa1221 100644
--- a/packages/ghost/src/drift-command.ts
+++ b/packages/ghost/src/drift-command.ts
@@ -14,31 +14,19 @@ import {
resolveTarget,
resolveTrackedFingerprint,
} from "./core/index.js";
-import {
- readOptionalPackageConfig,
- resolveFingerprintPackage,
-} from "./fingerprint.js";
+import { resolveFingerprintPackage } from "./fingerprint.js";
const DEFAULT_SYNC_PATH = ".ghost-sync.json";
const DRIFT_STATUS_SCHEMA = "ghost.drift.status/v1" as const;
const DRIFT_CHECK_SCHEMA = "ghost.drift.check/v1" as const;
-export type DesignLoopMode = "off" | "advisory" | "required";
-
export interface DriftStatusReport {
schema: typeof DRIFT_STATUS_SCHEMA;
- designLoop: {
- enabled: boolean;
- mode: DesignLoopMode;
- source: "config" | "default";
- };
- fingerprintDir: string;
- configPath: string;
+ packageDir: string;
}
export interface DriftCheckReport {
schema: typeof DRIFT_CHECK_SCHEMA;
- designLoop: DriftStatusReport["designLoop"];
trackedFingerprintId: string;
localFingerprintId: string;
overall: GateReport["overall"];
@@ -64,18 +52,10 @@ export async function getDriftStatus(
): Promise {
const cwd = options.cwd ?? process.cwd();
const paths = resolveFingerprintPackage(options.packageDir, cwd);
- const config = await readOptionalPackageConfig(paths.config);
- const designLoop = config?.design_loop ?? { enabled: false, mode: "off" };
return {
schema: DRIFT_STATUS_SCHEMA,
- designLoop: {
- enabled: designLoop.enabled,
- mode: designLoop.mode,
- source: config ? "config" : "default",
- },
- fingerprintDir: paths.fingerprintDir,
- configPath: paths.config,
+ packageDir: paths.dir,
};
}
@@ -83,7 +63,6 @@ export async function runDriftCheck(
options: DriftCheckOptions = {},
): Promise {
const cwd = options.cwd ?? process.cwd();
- const status = await getDriftStatus(options);
const manifest = await readSyncManifest(cwd, options.sync);
const local = await loadLocalFingerprint(
cwd,
@@ -110,7 +89,6 @@ export async function runDriftCheck(
return {
schema: DRIFT_CHECK_SCHEMA,
- designLoop: status.designLoop,
trackedFingerprintId: gate.trackedFingerprintId,
localFingerprintId: gate.localFingerprintId,
overall: gate.overall,
@@ -120,23 +98,15 @@ export async function runDriftCheck(
}
export function formatDriftStatusMarkdown(report: DriftStatusReport): string {
- return [
- "# Ghost drift status",
- "",
- `Design loop: ${report.designLoop.enabled ? "enabled" : "disabled"} (${report.designLoop.mode})`,
- `Source: ${report.designLoop.source}`,
- `Fingerprint dir: ${report.fingerprintDir}`,
- `Config: ${report.configPath}`,
- "",
- ].join("\n");
+ return ["# Ghost drift status", "", `Package: ${report.packageDir}`, ""].join(
+ "\n",
+ );
}
export function formatDriftCheckMarkdown(report: DriftCheckReport): string {
const lines = [
"# Ghost drift check",
"",
- `Design loop: ${report.designLoop.enabled ? "enabled" : "disabled"} (${report.designLoop.mode})`,
- "",
formatGateReportCLI(report.gate).trimEnd(),
"",
];
@@ -147,7 +117,7 @@ export function registerDriftCommand(cli: CAC): void {
cli
.command(
"drift ",
- "Inspect continuous design-loop drift status or run the stance-ledger check.",
+ "Inspect Ghost drift status or run the stance-ledger check.",
)
.option("--package ", "Exact fingerprint package directory")
.option("--config ", "Path to ghost config file for tracked source")
@@ -216,9 +186,6 @@ export function registerDriftCommand(cli: CAC): void {
}
function driftCheckExitCode(report: DriftCheckReport): number {
- if (report.designLoop.enabled && report.designLoop.mode === "advisory") {
- return 0;
- }
return gateExitCode(report.gate);
}
@@ -281,7 +248,7 @@ async function loadLocalFingerprint(
return await loadComparableFingerprintFrom(cwd, source);
} catch (err) {
const defaultPackage = !localPath && !packageDir;
- const manifestPath = resolve(cwd, source, "fingerprint", "manifest.yml");
+ const manifestPath = resolve(cwd, source, "manifest.yml");
if (!defaultPackage || existsSync(manifestPath)) throw err;
return await loadComparableFingerprintFrom(cwd, ".ghost/fingerprint.md");
}
@@ -331,7 +298,7 @@ async function loadTargetFingerprint(
".ghost",
);
if (
- existsSync(resolve(packageGhostDir, "fingerprint", "manifest.yml")) ||
+ existsSync(resolve(packageGhostDir, "manifest.yml")) ||
existsSync(resolve(packageGhostDir, "fingerprint.md"))
) {
return loadComparableFingerprintFrom(cwd, packageGhostDir);
diff --git a/packages/ghost/src/evolution-commands.ts b/packages/ghost/src/evolution-commands.ts
index d017280e..89e3e92f 100644
--- a/packages/ghost/src/evolution-commands.ts
+++ b/packages/ghost/src/evolution-commands.ts
@@ -20,7 +20,7 @@ async function loadTrackedComparableFingerprint(
if (target.type === "npm") {
const packageGhostDir = resolve("node_modules", target.value, ".ghost");
if (
- existsSync(resolve(packageGhostDir, "fingerprint", "manifest.yml")) ||
+ existsSync(resolve(packageGhostDir, "manifest.yml")) ||
existsSync(resolve(packageGhostDir, "fingerprint.md"))
) {
return loadComparableFingerprint(packageGhostDir);
diff --git a/packages/ghost/src/fingerprint-commands.ts b/packages/ghost/src/fingerprint-commands.ts
index e008f346..9acbc2d0 100644
--- a/packages/ghost/src/fingerprint-commands.ts
+++ b/packages/ghost/src/fingerprint-commands.ts
@@ -1,5 +1,5 @@
import { readFile, stat, writeFile } from "node:fs/promises";
-import { basename, dirname, resolve } from "node:path";
+import { dirname, resolve } from "node:path";
import type { CAC } from "cac";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
import {
@@ -34,10 +34,9 @@ import { registerInitCommand } from "./init-command.js";
import { detectFileKind, lintDetectedFileKind } from "./scan/file-kind.js";
import {
discoverGhostPackages,
- FINGERPRINT_DIRNAME,
fingerprintPackageDisplayPath,
- normalizeMemoryDir,
- resolveMemoryDirDefault,
+ normalizeGhostDir,
+ resolveGhostDirDefault,
scanStatus,
signals,
} from "./scan/index.js";
@@ -68,24 +67,20 @@ export function registerFingerprintCommands(cli: CAC): void {
"--all",
"Validate every nested fingerprint package and its resolved fingerprint stack",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers, --all, and default package lookup (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.action(async (path: string | undefined, opts) => {
try {
- const memoryDir = memoryDirFromOpts(opts);
+ const ghostDir = ghostDirFromEnv();
if (opts.all) {
const report = await lintAllFingerprintStacks(
resolve(process.cwd(), path ?? "."),
- { memoryDir },
+ { ghostDir },
);
writeLintReport(report, opts.format);
process.exit(report.errors > 0 ? 1 : 0);
return;
}
- const packagePath = path ?? memoryDir;
+ const packagePath = path ?? ghostDir;
const target = resolveFingerprintPackage(
packagePath,
process.cwd(),
@@ -149,10 +144,6 @@ export function registerFingerprintCommands(cli: CAC): void {
"--all",
"Verify every nested fingerprint package and its resolved fingerprint stack",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers, --all, and default package lookup (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.action(async (dirArg: string | undefined, opts) => {
try {
if (opts.format !== "cli" && opts.format !== "json") {
@@ -161,15 +152,15 @@ export function registerFingerprintCommands(cli: CAC): void {
return;
}
- const memoryDir = memoryDirFromOpts(opts);
+ const ghostDir = ghostDirFromEnv();
const report = opts.all
? await verifyAllFingerprintStacks(
resolve(process.cwd(), dirArg ?? "."),
{
- memoryDir,
+ ghostDir,
},
)
- : await verifyFingerprintPackage(dirArg ?? memoryDir, process.cwd(), {
+ : await verifyFingerprintPackage(dirArg ?? ghostDir, process.cwd(), {
root: opts.root ? resolve(process.cwd(), opts.root) : undefined,
});
@@ -202,16 +193,12 @@ export function registerFingerprintCommands(cli: CAC): void {
"--include-nested",
"Also list nested fingerprint packages and contribution state",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers, nested discovery, and default scan (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option("--format ", "Output format: cli or json", { default: "cli" })
.action(async (dirArg: string | undefined, opts) => {
try {
- const memoryDir = memoryDirFromOpts(opts);
+ const ghostDir = ghostDirFromEnv();
const dir = resolveFingerprintPackage(
- dirArg ?? memoryDir,
+ dirArg ?? ghostDir,
process.cwd(),
).dir;
const status = await scanStatus(dir, {
@@ -219,8 +206,8 @@ export function registerFingerprintCommands(cli: CAC): void {
});
const nested = opts.includeNested
? await nestedPackageStatus(
- dirnameForFingerprintPackageDir(dir, memoryDir),
- memoryDir,
+ dirnameForFingerprintPackageDir(dir, ghostDir),
+ ghostDir,
)
: undefined;
if (opts.format === "json") {
@@ -234,15 +221,12 @@ export function registerFingerprintCommands(cli: CAC): void {
} else {
const fmt = (state: string) =>
state === "present" ? "present" : "missing";
- process.stdout.write(`fingerprint dir: ${status.dir}\n\n`);
- process.stdout.write(
- ` fingerprint (fingerprint/): ${fmt(status.fingerprint.state)}\n`,
- );
+ process.stdout.write(`package dir: ${status.dir}\n\n`);
process.stdout.write(
- ` config (config.yml): ${fmt(status.config.state)}\n`,
+ ` package (manifest.yml): ${fmt(status.fingerprint.state)}\n`,
);
process.stdout.write(
- ` validate (fingerprint/validate.yml): ${fmt(status.validate.state)}\n`,
+ ` validate (validate.yml): ${fmt(status.validate.state)}\n`,
);
process.stdout.write("\n");
if (status.recommended_next) {
@@ -321,7 +305,7 @@ export function registerFingerprintCommands(cli: CAC): void {
} else {
for (const pkg of nested) {
process.stdout.write(
- ` ${fingerprintPackageDisplayPath(pkg.relative_root, pkg.fingerprint_dir)}: ${pkg.contribution.state}\n`,
+ ` ${fingerprintPackageDisplayPath(pkg.relative_root, pkg.ghost_dir)}: ${pkg.contribution.state}\n`,
);
}
}
@@ -601,9 +585,9 @@ export function registerFingerprintCommands(cli: CAC): void {
async function nestedPackageStatus(
root: string,
- memoryDir: string,
+ ghostDir: string,
): Promise {
- const packages = await discoverGhostPackages(root, { memoryDir });
+ const packages = await discoverGhostPackages(root, { ghostDir });
return Promise.all(
packages.map(async (pkg) => {
const status = await scanStatus(pkg.dir);
@@ -621,7 +605,7 @@ interface NestedPackageStatus {
dir: string;
root: string;
relative_root: string;
- fingerprint_dir: string;
+ ghost_dir: string;
fingerprint: Awaited>["fingerprint"];
validate: Awaited>["validate"];
contribution: Awaited>["contribution"];
@@ -629,30 +613,26 @@ interface NestedPackageStatus {
function dirnameForFingerprintPackageDir(
dir: string,
- memoryDir: string,
+ ghostDir: string,
): string {
let root = dir;
- for (const _segment of normalizeMemoryDir(memoryDir).split("/")) {
+ for (const _segment of normalizeGhostDir(ghostDir).split("/")) {
root = dirname(root);
}
return root;
}
-function memoryDirFromOpts(opts: { memoryDir?: unknown }): string {
- return resolveMemoryDirDefault(opts.memoryDir);
+function ghostDirFromEnv(): string {
+ return resolveGhostDirDefault();
}
async function loadSiblingFingerprintForValidateLint(
fileTarget: string,
): Promise {
const validateDir = dirname(fileTarget);
- const packageRoot =
- basename(validateDir) === FINGERPRINT_DIRNAME
- ? resolve(validateDir, "..")
- : resolve(validateDir, "..", "..");
try {
return (
- await loadFingerprintPackage(resolveFingerprintPackage(packageRoot))
+ await loadFingerprintPackage(resolveFingerprintPackage(validateDir))
).fingerprint;
} catch {
return undefined;
@@ -799,7 +779,7 @@ function summarizeSurveyPatterns(survey: Survey): GhostPatternsDocument {
traits: traitsForPattern(entry.value, survey),
evidence: entry.evidence,
advisory: [
- "Use as advisory composition evidence; deterministic checks belong in fingerprint/validate.yml.",
+ "Use as advisory composition evidence; deterministic checks belong in validate.yml.",
],
})),
advisory: {
@@ -813,7 +793,7 @@ function surveyPatternReviewExpectations(survey: Survey): string[] {
return [
"No UI surface evidence is present; do not infer product composition patterns from values, tokens, or components alone.",
"Use survey values, tokens, and components as implementation vocabulary until implemented product surfaces are observed.",
- "Treat fingerprint/intent.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as canonical authoring facets.",
+ "Treat intent.yml, inventory.yml, and composition.yml as canonical authoring facets.",
];
}
@@ -824,14 +804,14 @@ function surveyPatternReviewExpectations(survey: Survey): string[] {
return [
"Treat story, fixture, and doc-example rows as component demonstration evidence, not product composition authority.",
"Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.",
- "Treat fingerprint/intent.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as canonical authoring facets.",
+ "Treat intent.yml, inventory.yml, and composition.yml as canonical authoring facets.",
];
}
return [
"Identify the surface type before assessing composition.",
"Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.",
- "Treat fingerprint/intent.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as canonical authoring facets.",
+ "Treat intent.yml, inventory.yml, and composition.yml as canonical authoring facets.",
];
}
diff --git a/packages/ghost/src/fingerprint.ts b/packages/ghost/src/fingerprint.ts
index 43d2d99d..eb1e395d 100644
--- a/packages/ghost/src/fingerprint.ts
+++ b/packages/ghost/src/fingerprint.ts
@@ -6,9 +6,7 @@ export type { DesignDecision } from "./scan/compose.js";
export { mergeFingerprint } from "./scan/compose.js";
export {
CHECKS_FILENAME,
- CONFIG_FILENAME,
FINGERPRINT_COMPOSITION_FILENAME,
- FINGERPRINT_DIRNAME,
FINGERPRINT_FILENAME,
FINGERPRINT_INTENT_FILENAME,
FINGERPRINT_INVENTORY_FILENAME,
@@ -67,21 +65,7 @@ export type {
MapLintSeverity,
} from "./scan/lint-map.js";
export { lintMap } from "./scan/lint-map.js";
-export type {
- GhostPackageConfig,
- GhostPackageConfigLibrary,
- GhostPackageConfigTarget,
-} from "./scan/package-config.js";
-export {
- GHOST_PACKAGE_CONFIG_SCHEMA,
- GhostPackageConfigSchema,
- lintGhostPackageConfig,
- normalizeReferenceInput,
- parsePackageConfig,
- readOptionalPackageConfig,
- readOptionalPackageConfigSync,
- templatePackageConfig,
-} from "./scan/package-config.js";
+export { normalizeReferenceInput } from "./scan/package-config.js";
export type { ParsedFingerprint, ParseOptions } from "./scan/parser.js";
export { parseFingerprint, splitRaw } from "./scan/parser.js";
export type { FrontmatterShape } from "./scan/schema.js";
diff --git a/packages/ghost/src/ghost-core/fingerprint-package.ts b/packages/ghost/src/ghost-core/fingerprint-package.ts
index f51358e0..294b48ab 100644
--- a/packages/ghost/src/ghost-core/fingerprint-package.ts
+++ b/packages/ghost/src/ghost-core/fingerprint-package.ts
@@ -2,23 +2,20 @@ export const FINGERPRINT_PACKAGE_DIR = ".ghost" as const;
export const RESOURCES_FILENAME = "resources.yml" as const;
export const PATTERNS_FILENAME = "patterns.yml" as const;
export const FINGERPRINT_YML_FILENAME = "fingerprint.yml" as const;
-export const FINGERPRINT_DIRNAME = "fingerprint" as const;
export const FINGERPRINT_MANIFEST_FILENAME = "manifest.yml" as const;
export const FINGERPRINT_INTENT_FILENAME = "intent.yml" as const;
export const FINGERPRINT_INVENTORY_FILENAME = "inventory.yml" as const;
export const FINGERPRINT_COMPOSITION_FILENAME = "composition.yml" as const;
-export const CONFIG_FILENAME = "config.yml" as const;
export const FINGERPRINT_FILENAME = "fingerprint.md" as const;
export interface FingerprintPackagePaths {
dir: string;
- fingerprintDir: string;
+ packageDir: string;
manifest: string;
intent: string;
inventory: string;
composition: string;
fingerprintYml: string;
- config: string;
resources: string;
map: string;
survey: string;
diff --git a/packages/ghost/src/ghost-core/index.ts b/packages/ghost/src/ghost-core/index.ts
index 1c2a5230..96e75bba 100644
--- a/packages/ghost/src/ghost-core/index.ts
+++ b/packages/ghost/src/ghost-core/index.ts
@@ -115,9 +115,7 @@ export {
} from "./fingerprint/index.js";
// --- Map (ghost.map/v1) ---
export {
- CONFIG_FILENAME,
FINGERPRINT_COMPOSITION_FILENAME,
- FINGERPRINT_DIRNAME,
FINGERPRINT_FILENAME,
FINGERPRINT_INTENT_FILENAME,
FINGERPRINT_INVENTORY_FILENAME,
@@ -132,7 +130,6 @@ export {
export {
type GitInfo,
getEffectiveMapScopes,
- type InventoryConfigSummary,
type InventoryOutput,
type LanguageHistogramEntry,
MAP_FILENAME,
diff --git a/packages/ghost/src/ghost-core/map/index.ts b/packages/ghost/src/ghost-core/map/index.ts
index 52b53be9..765c8f78 100644
--- a/packages/ghost/src/ghost-core/map/index.ts
+++ b/packages/ghost/src/ghost-core/map/index.ts
@@ -19,7 +19,6 @@ export type { MapFeatureArea } from "./scopes.js";
export { getEffectiveMapScopes, slugifyScopeId } from "./scopes.js";
export type {
GitInfo,
- InventoryConfigSummary,
InventoryOutput,
LanguageHistogramEntry,
TopLevelEntry,
diff --git a/packages/ghost/src/ghost-core/map/types.ts b/packages/ghost/src/ghost-core/map/types.ts
index 1b650add..598e8292 100644
--- a/packages/ghost/src/ghost-core/map/types.ts
+++ b/packages/ghost/src/ghost-core/map/types.ts
@@ -31,25 +31,6 @@ export interface GitInfo {
default_branch: string | null;
}
-/** Data-only `.ghost/config.yml` summary included when present. */
-export interface InventoryConfigSummary {
- /** Absolute path to the config file. */
- path: string;
- /** Configured implementation targets. */
- targets: Array<{
- id: string;
- platform?: string;
- roots: string[];
- }>;
- /** Configured reference libraries. */
- libraries: Array<{
- id: string;
- role: string;
- source: string;
- fingerprint?: string;
- }>;
-}
-
/** Full output shape of `ghost map inventory`. */
export interface InventoryOutput {
/** Resolved absolute path that was inventoried. */
@@ -81,6 +62,4 @@ export interface InventoryOutput {
git_remote: string | null;
/** Best-effort git default branch. */
git_default_branch: string | null;
- /** Parsed `.ghost/config.yml` summary when present. */
- config?: InventoryConfigSummary;
}
diff --git a/packages/ghost/src/init-command.ts b/packages/ghost/src/init-command.ts
index a6d2150e..f488385a 100644
--- a/packages/ghost/src/init-command.ts
+++ b/packages/ghost/src/init-command.ts
@@ -8,26 +8,22 @@ import {
initMonorepoFingerprintPackages,
writeMonorepoInitOutput,
} from "./monorepo-init-command.js";
-import { resolveMemoryDirDefault } from "./scan/index.js";
+import { resolveGhostDirDefault } from "./scan/index.js";
export function registerInitCommand(cli: CAC): void {
cli
- .command("init [dir]", "Create a root .ghost split fingerprint package")
+ .command("init", "Create a root .ghost split fingerprint package")
.option(
"--scope ",
- "Create a scoped / fingerprint package",
+ "Create a scoped /.ghost fingerprint package",
)
.option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers, init --scope, and default root init (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
- .option(
- "--with-config",
- "Also create optional config.yml for implementation roots and reference registries/libraries",
+ "--package ",
+ "Exact fingerprint package directory to initialize",
)
.option(
"--reference ",
- "Reference UI registry, library path, or fingerprint to record in config.yml and inventory building blocks",
+ "Reference UI registry, library path, or fingerprint to record in inventory building blocks",
)
.option(
"--monorepo",
@@ -36,10 +32,12 @@ export function registerInitCommand(cli: CAC): void {
.option("--apply", "With --monorepo, create detected child scoped packages")
.option("--force", "Overwrite existing Ghost fingerprint files")
.option("--format ", "Output format: cli or json", { default: "cli" })
- .action(async (dirArg: string | undefined, opts) => {
+ .action(async (opts) => {
try {
- if (opts.monorepo && dirArg) {
- console.error("Error: use either init [dir] or init --monorepo");
+ if (cli.args.length > 0) {
+ console.error(
+ "Error: ghost init no longer accepts a positional directory. Use --package for an exact package directory.",
+ );
process.exit(2);
return;
}
@@ -55,29 +53,35 @@ export function registerInitCommand(cli: CAC): void {
process.exit(2);
return;
}
- if (dirArg && typeof opts.scope === "string") {
- console.error("Error: use either init [dir] or init --scope ");
+ if (opts.monorepo && typeof opts.package === "string") {
+ console.error(
+ "Error: use either init --package or init --monorepo",
+ );
process.exit(2);
return;
}
- if (dirArg && typeof opts.memoryDir === "string") {
- console.error("Error: use either init [dir] or --memory-dir");
+ if (
+ typeof opts.scope === "string" &&
+ typeof opts.package === "string"
+ ) {
+ console.error(
+ "Error: use either init --package or init --scope ",
+ );
process.exit(2);
return;
}
- const memoryDir =
- typeof opts.scope === "string" || dirArg === undefined
- ? memoryDirFromOpts(opts)
- : undefined;
+ const exactPackage =
+ typeof opts.package === "string" ? opts.package : undefined;
+ const ghostDir =
+ exactPackage === undefined ? ghostDirFromEnv() : undefined;
const initOptions = {
- withConfig: Boolean(opts.withConfig || opts.reference),
reference:
typeof opts.reference === "string" ? opts.reference : undefined,
force: Boolean(opts.force),
};
if (opts.monorepo) {
const output = await initMonorepoFingerprintPackages({
- memoryDir: memoryDirFromOpts(opts),
+ ghostDir: ghostDir ?? ghostDirFromEnv(),
apply: Boolean(opts.apply),
initOptions,
});
@@ -89,22 +93,16 @@ export function registerInitCommand(cli: CAC): void {
typeof opts.scope === "string"
? await initScopedFingerprintPackage(opts.scope, process.cwd(), {
...initOptions,
- memoryDir,
+ ghostDir: ghostDir ?? ghostDirFromEnv(),
})
: await initFingerprintPackage(
- dirArg ?? memoryDir,
+ exactPackage ?? ghostDir,
process.cwd(),
initOptions,
);
if (opts.format === "json") {
process.stdout.write(
- `${JSON.stringify(
- initCommandOutput(paths, {
- includeConfig: Boolean(opts.withConfig || opts.reference),
- }),
- null,
- 2,
- )}\n`,
+ `${JSON.stringify(initCommandOutput(paths), null, 2)}\n`,
);
} else {
process.stdout.write(
@@ -115,9 +113,6 @@ export function registerInitCommand(cli: CAC): void {
process.stdout.write(` inventory.yml: ${paths.inventory}\n`);
process.stdout.write(` composition.yml: ${paths.composition}\n`);
process.stdout.write(` validate.yml: ${paths.checks}\n`);
- if (opts.withConfig || opts.reference) {
- process.stdout.write(` config.yml: ${paths.config}\n`);
- }
}
process.exit(0);
} catch (err) {
@@ -129,22 +124,19 @@ export function registerInitCommand(cli: CAC): void {
});
}
-function memoryDirFromOpts(opts: { memoryDir?: unknown }): string {
- return resolveMemoryDirDefault(opts.memoryDir);
+function ghostDirFromEnv(): string {
+ return resolveGhostDirDefault();
}
function initCommandOutput(
paths: ReturnType,
- options: { includeConfig: boolean },
): Record {
return {
dir: paths.dir,
- fingerprintDir: paths.fingerprintDir,
manifest: paths.manifest,
intent: paths.intent,
inventory: paths.inventory,
composition: paths.composition,
- ...(options.includeConfig ? { config: paths.config } : {}),
checks: paths.checks,
};
}
diff --git a/packages/ghost/src/monorepo-init-command.ts b/packages/ghost/src/monorepo-init-command.ts
index 940a7680..eb743c0f 100644
--- a/packages/ghost/src/monorepo-init-command.ts
+++ b/packages/ghost/src/monorepo-init-command.ts
@@ -9,7 +9,7 @@ import {
import {
detectMonorepoInitCandidates,
type MonorepoInitCandidate,
- normalizeMemoryDir,
+ normalizeGhostDir,
} from "./scan/index.js";
type InitOptions = NonNullable[2]>;
@@ -22,7 +22,7 @@ interface MonorepoInitOutput {
root: Record;
rootState: "created" | "exists";
mode: "plan" | "apply";
- memoryDir: string;
+ ghostDir: string;
candidates: MonorepoInitCandidateState[];
created: Array;
skipped: MonorepoInitCandidateState[];
@@ -31,12 +31,12 @@ interface MonorepoInitOutput {
}
export async function initMonorepoFingerprintPackages(options: {
- memoryDir: string;
+ ghostDir: string;
apply: boolean;
initOptions: InitOptions;
}): Promise {
const cwd = process.cwd();
- const rootPaths = resolveFingerprintPackage(options.memoryDir, cwd);
+ const rootPaths = resolveFingerprintPackage(options.ghostDir, cwd);
const rootExists = await hasFingerprintPackage(rootPaths);
const rootState =
rootExists && !options.initOptions.force ? "exists" : "created";
@@ -44,7 +44,7 @@ export async function initMonorepoFingerprintPackages(options: {
rootState === "exists"
? rootPaths
: await initFingerprintPackage(
- options.memoryDir,
+ options.ghostDir,
cwd,
options.initOptions,
);
@@ -55,7 +55,7 @@ export async function initMonorepoFingerprintPackages(options: {
...candidate,
state: (await hasScopeFingerprintPackage(
candidate,
- options.memoryDir,
+ options.ghostDir,
cwd,
))
? "exists"
@@ -66,7 +66,7 @@ export async function initMonorepoFingerprintPackages(options: {
const commands = candidates
.filter((candidate) => candidate.state === "candidate")
.map((candidate) =>
- formatScopedInitCommand(candidate.path, options.memoryDir),
+ formatScopedInitCommand(candidate.path, options.ghostDir),
);
const created: MonorepoInitOutput["created"] = [];
const skipped: MonorepoInitOutput["skipped"] = candidates.filter(
@@ -80,7 +80,7 @@ export async function initMonorepoFingerprintPackages(options: {
try {
await initScopedFingerprintPackage(candidate.path, cwd, {
...options.initOptions,
- memoryDir: options.memoryDir,
+ ghostDir: options.ghostDir,
});
created.push({ ...stripCandidateState(candidate), state: "created" });
} catch (err) {
@@ -93,12 +93,10 @@ export async function initMonorepoFingerprintPackages(options: {
}
return {
- root: initPackageOutput(root, {
- includeConfig: Boolean(options.initOptions.withConfig),
- }),
+ root: initPackageOutput(root),
rootState,
mode: options.apply ? "apply" : "plan",
- memoryDir: options.memoryDir,
+ ghostDir: options.ghostDir,
candidates,
created,
skipped,
@@ -163,11 +161,11 @@ export function writeMonorepoInitOutput(
async function hasScopeFingerprintPackage(
candidate: MonorepoInitCandidate,
- memoryDir: string,
+ ghostDir: string,
cwd: string,
): Promise {
return hasFingerprintPackage(
- resolveFingerprintPackage(memoryDir, resolve(cwd, candidate.path)),
+ resolveFingerprintPackage(ghostDir, resolve(cwd, candidate.path)),
);
}
@@ -191,11 +189,11 @@ function stripCandidateState(
};
}
-function formatScopedInitCommand(path: string, memoryDir: string): string {
+function formatScopedInitCommand(path: string, ghostDir: string): string {
const base = `ghost init --scope ${formatCommandArg(path)}`;
- return memoryDir === normalizeMemoryDir()
+ return ghostDir === normalizeGhostDir()
? base
- : `${base} --memory-dir ${formatCommandArg(memoryDir)}`;
+ : `GHOST_PACKAGE_DIR=${formatCommandArg(ghostDir)} ${base}`;
}
function formatCommandArg(value: string): string {
@@ -204,16 +202,13 @@ function formatCommandArg(value: string): string {
function initPackageOutput(
paths: FingerprintPackagePaths,
- options: { includeConfig: boolean },
): Record {
return {
dir: paths.dir,
- fingerprintDir: paths.fingerprintDir,
manifest: paths.manifest,
intent: paths.intent,
inventory: paths.inventory,
composition: paths.composition,
- ...(options.includeConfig ? { config: paths.config } : {}),
checks: paths.checks,
};
}
diff --git a/packages/ghost/src/relay.ts b/packages/ghost/src/relay.ts
index ab71d667..a080b6e8 100644
--- a/packages/ghost/src/relay.ts
+++ b/packages/ghost/src/relay.ts
@@ -14,7 +14,7 @@ import {
fingerprintStackToPackageContext,
type GhostFingerprintStack,
loadFingerprintStackForPath,
- resolveMemoryDirDefault,
+ resolveGhostDirDefault,
} from "./scan/fingerprint-stack.js";
export type {
@@ -33,7 +33,7 @@ export interface GatherRelayContextOptions {
cwd?: string;
target?: string;
packageDir?: string;
- memoryDir?: string;
+ ghostDir?: string;
name?: string;
}
@@ -42,7 +42,7 @@ export type RelayGatherSource =
kind: "stack";
repoRoot: string;
targetPath: string;
- fingerprintDir: string;
+ ghostDir: string;
stackDirs: string[];
provenance: {
merge: "child-wins-by-id";
@@ -60,7 +60,7 @@ export interface RelayGatherResult {
name: string;
source: RelayGatherSource;
targetPaths: string[];
- fingerprintDir?: string;
+ ghostDir?: string;
stackDirs: string[];
selected_context: SelectedContext;
brief: string;
@@ -82,22 +82,22 @@ export async function gatherRelayContext(
return gatherFromContext(context, {
source: {
kind: "package",
- packageDir: context.fingerprintDir ?? options.packageDir,
+ packageDir: context.packageDir ?? options.packageDir,
targetPath: targetPaths[0] ?? null,
},
targetPaths,
});
}
- const memoryDir = resolveMemoryDirDefault(options.memoryDir);
- const stack = await loadFingerprintStackForPath(target, cwd, { memoryDir });
+ const ghostDir = resolveGhostDirDefault(options.ghostDir);
+ const stack = await loadFingerprintStackForPath(target, cwd, { ghostDir });
const context = fingerprintStackToPackageContext(stack, options.name);
return gatherFromContext(context, {
source: {
kind: "stack",
repoRoot: stack.repo_root,
targetPath: stack.target_path,
- fingerprintDir: stack.fingerprint_dir,
+ ghostDir: stack.ghost_dir,
stackDirs: stack.layers.map((layer) => layer.dir),
provenance: {
merge: stack.provenance.merge,
@@ -124,10 +124,6 @@ export function registerRelayCommand(cli: CAC): void {
"--package ",
"Use exactly this fingerprint package directory instead of resolving a stack",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers and stack resolution (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option(
"--name ",
"Override the gathered context name (default: intent.yml product or resolved scope)",
@@ -152,8 +148,6 @@ export function registerRelayCommand(cli: CAC): void {
target: target ?? ".",
packageDir:
typeof opts.package === "string" ? opts.package : undefined,
- memoryDir:
- typeof opts.memoryDir === "string" ? opts.memoryDir : undefined,
name: typeof opts.name === "string" ? opts.name : undefined,
});
@@ -181,13 +175,19 @@ function gatherFromContext(
});
const selectedContext = buildSelectedContext(context, entrypoint);
const partial = { selected_context: selectedContext };
+ const stackDirs = context.stackDirs?.length
+ ? context.stackDirs
+ : context.packageDir
+ ? [context.packageDir]
+ : [];
return {
schema: RELAY_GATHER_SCHEMA,
name: context.name,
source: options.source,
targetPaths: entrypoint.match.requestedPaths,
- fingerprintDir: context.fingerprintDir,
- stackDirs: context.stackDirs ?? [],
+ ghostDir:
+ options.source.kind === "stack" ? options.source.ghostDir : undefined,
+ stackDirs,
selected_context: selectedContext,
brief: formatRelayBrief(partial),
};
diff --git a/packages/ghost/src/review-packet.ts b/packages/ghost/src/review-packet.ts
index 12240951..619a4b9b 100644
--- a/packages/ghost/src/review-packet.ts
+++ b/packages/ghost/src/review-packet.ts
@@ -1,4 +1,3 @@
-import { resolve } from "node:path";
import { stringify as stringifyYaml } from "yaml";
import { buildContextEntrypoint } from "./context/entrypoint.js";
import {
@@ -15,18 +14,14 @@ import {
fingerprintStackToPackageContext,
type GhostFingerprintStack,
groupFingerprintStacksForPaths,
- resolveMemoryDirDefault,
+ resolveGhostDirDefault,
} from "./scan/fingerprint-stack.js";
-import {
- type GhostPackageConfig,
- readOptionalPackageConfig,
-} from "./scan/package-config.js";
const DEFAULT_REVIEW_MAX_DIFF_BYTES = 200_000;
export async function buildReviewPacket(options: {
packageDir?: string;
- memoryDir?: string;
+ ghostDir?: string;
diffText: string;
maxDiffBytes?: number;
}): Promise {
@@ -58,13 +53,12 @@ async function buildSinglePackageReviewPacket(options: {
},
]),
checks: context.checksRaw ?? null,
- config: (await readOptionalPackageConfig(paths.config)) ?? null,
};
return packet;
}
async function buildStackReviewPacket(options: {
- memoryDir?: string;
+ ghostDir?: string;
diffText: string;
maxDiffBytes?: number;
}): Promise {
@@ -74,7 +68,7 @@ async function buildStackReviewPacket(options: {
const groups = await groupFingerprintStacksForPaths(
changedFiles,
process.cwd(),
- { memoryDir: resolveMemoryDirDefault(options.memoryDir) },
+ { ghostDir: resolveGhostDirDefault(options.ghostDir) },
);
const stacks = groups.map((group) =>
reviewStackFromFingerprintStack(group.stack, group.changed_files),
@@ -86,7 +80,7 @@ async function buildStackReviewPacket(options: {
group.changed_files,
);
return {
- title: group.stack.layers.at(-1)?.dir ?? group.stack.fingerprint_dir,
+ title: group.stack.layers.at(-1)?.dir ?? group.stack.ghost_dir,
markdown: formatReviewSelectedContextMarkdown(
context,
group.changed_files,
@@ -95,9 +89,6 @@ async function buildStackReviewPacket(options: {
};
});
const first = stacks[0];
- const config = await readOptionalPackageConfig(
- resolve(first.package_dir, "config.yml"),
- );
const packet: ReviewPacket = {
...baseReviewPacket(
stacks.length === 1 ? first.package_dir : "fingerprint-stack/multiple",
@@ -107,7 +98,6 @@ async function buildStackReviewPacket(options: {
fingerprint: first.merged.fingerprint,
context_markdown: formatReviewContextMarkdown(contextSections),
checks: stringifyYaml(first.merged.checks, { lineWidth: 0 }),
- config: config ?? null,
stacks,
};
return packet;
@@ -219,7 +209,7 @@ function reviewStackFromFingerprintStack(
return {
target_path: stack.target_path,
package_dir: leaf?.dir ?? stack.layers[0].dir,
- fingerprint_dir: stack.fingerprint_dir,
+ ghost_dir: stack.ghost_dir,
changed_files: changedFiles,
stack_dirs: stack.layers.map((layer) => layer.dir),
merged: {
@@ -255,7 +245,6 @@ interface ReviewPacket {
fingerprint: unknown;
context_markdown: string;
checks: string | null;
- config: GhostPackageConfig | null;
stacks?: ReviewStackPacket[];
diff: string;
budgets: ReviewPacketBudgets;
@@ -267,7 +256,7 @@ interface ReviewPacket {
interface ReviewStackPacket {
target_path: string;
package_dir: string;
- fingerprint_dir: string;
+ ghost_dir: string;
changed_files: string[];
stack_dirs: string[];
merged: {
@@ -285,7 +274,7 @@ export function formatReviewPacketMarkdown(packet: ReviewPacket): string {
Package: ${packet.package_dir}
-Review this diff as a non-blocking design-language critic. Advisory findings must be evidence-routed and must cite: ${packet.required_finding_citations.join(", ")}. Do not fail the build unless the issue is tied to an active deterministic check in fingerprint/validate.yml. Keep findings grounded in fingerprint/intent.yml, fingerprint/inventory.yml, fingerprint/composition.yml, active deterministic checks, and diff evidence; do not expand the review into unrelated audit categories.
+Review this diff as a non-blocking design-language critic. Advisory findings must be evidence-routed and must cite: ${packet.required_finding_citations.join(", ")}. Do not fail the build unless the issue is tied to an active deterministic check in validate.yml. Keep findings grounded in intent.yml, inventory.yml, composition.yml, active deterministic checks, and diff evidence; do not expand the review into unrelated audit categories.
Use the selected context first: intent → composition → inventory → validation. When selected context exposes gaps, label the reasoning provisional or report missing-fingerprint / experience-gap instead of pretending the fingerprint is more specific than it is.
@@ -301,8 +290,6 @@ ${formatReviewStacksSection(packet.stacks ?? null)}
${packet.context_markdown}
-${formatConfigSection(packet.config)}
-
## Diff
\`\`\`diff
@@ -356,19 +343,3 @@ function formatReviewContextMarkdown(
}
return lines.join("\n").trim();
}
-
-function formatConfigSection(config: GhostPackageConfig | null): string {
- if (!config) {
- return `## Implementation Config
-
-_No config.yml present. Review uses canonical fingerprint facets and the provided diff only._
-`;
- }
-
- return `## Implementation Config
-
-\`\`\`yaml
-${stringifyYaml(config)}
-\`\`\`
-`;
-}
diff --git a/packages/ghost/src/scan-emit-command.ts b/packages/ghost/src/scan-emit-command.ts
index 16e75766..c5b1329f 100644
--- a/packages/ghost/src/scan-emit-command.ts
+++ b/packages/ghost/src/scan-emit-command.ts
@@ -10,7 +10,7 @@ import { resolveFingerprintPackage } from "./fingerprint.js";
import {
fingerprintStackToPackageContext,
loadFingerprintStackForPath,
- resolveMemoryDirDefault,
+ resolveGhostDirDefault,
} from "./scan/fingerprint-stack.js";
const DEFAULT_REVIEW_OUT = ".claude/commands/design-review.md";
@@ -50,10 +50,6 @@ export function registerEmitCommand(cli: CAC): void {
"--package ",
"Use exactly this fingerprint package directory instead of resolving a stack",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers and --path stack resolution (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option(
"-o, --out ",
`Output path (review-command → ${DEFAULT_REVIEW_OUT})`,
@@ -108,7 +104,6 @@ export function registerEmitCommand(cli: CAC): void {
async function loadEmitPackageContext(opts: {
path?: unknown;
package?: unknown;
- memoryDir?: unknown;
}): Promise {
if (typeof opts.package === "string") {
return loadPackageContext(
@@ -120,7 +115,7 @@ async function loadEmitPackageContext(opts: {
typeof opts.path === "string" ? opts.path : ".",
process.cwd(),
{
- memoryDir: resolveMemoryDirDefault(opts.memoryDir),
+ ghostDir: resolveGhostDirDefault(),
},
);
return fingerprintStackToPackageContext(stack);
diff --git a/packages/ghost/src/scan-stack-command.ts b/packages/ghost/src/scan-stack-command.ts
index bd1f2df6..4e8d2d65 100644
--- a/packages/ghost/src/scan-stack-command.ts
+++ b/packages/ghost/src/scan-stack-command.ts
@@ -3,7 +3,7 @@ import {
fingerprintPackageDisplayPath,
type GhostFingerprintStack,
loadFingerprintStackForPath,
- resolveMemoryDirDefault,
+ resolveGhostDirDefault,
} from "./scan/index.js";
export function registerStackCommand(cli: CAC): void {
@@ -12,14 +12,10 @@ export function registerStackCommand(cli: CAC): void {
"stack [paths...]",
"Inspect the nested Ghost fingerprint stack for one or more repo paths.",
)
- .option(
- "--memory-dir ",
- "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)",
- )
.option("--format ", "Output format: cli or json", { default: "cli" })
.action(async (paths: string[] | string | undefined, opts) => {
try {
- const memoryDir = resolveMemoryDirDefault(opts.memoryDir);
+ const ghostDir = resolveGhostDirDefault();
const requestedPaths = Array.isArray(paths)
? paths
: typeof paths === "string"
@@ -28,7 +24,7 @@ export function registerStackCommand(cli: CAC): void {
const targets = requestedPaths.length > 0 ? requestedPaths : ["."];
const stacks = await Promise.all(
targets.map((path) =>
- loadFingerprintStackForPath(path, process.cwd(), { memoryDir }),
+ loadFingerprintStackForPath(path, process.cwd(), { ghostDir }),
),
);
if (opts.format === "json") {
@@ -56,12 +52,12 @@ function formatStackJson(
return {
target_path: stack.target_path,
repo_root: stack.repo_root,
- fingerprint_dir: stack.fingerprint_dir,
+ ghost_dir: stack.ghost_dir,
stack: stack.layers.map((layer) => ({
dir: layer.dir,
root: layer.root,
relative_root: layer.relative_root,
- fingerprint_dir: layer.fingerprint_dir,
+ ghost_dir: layer.ghost_dir,
fingerprint_id: layer.fingerprint.intent.summary.product ?? null,
checks: layer.checks?.checks.length ?? 0,
})),
@@ -83,7 +79,7 @@ function formatStackCli(stack: GhostFingerprintStack): string {
"stack:",
...stack.layers.map(
(layer) =>
- ` - ${fingerprintPackageDisplayPath(layer.relative_root, layer.fingerprint_dir)} (${layer.fingerprint.intent.summary.product ?? "unnamed"})`,
+ ` - ${fingerprintPackageDisplayPath(layer.relative_root, layer.ghost_dir)} (${layer.fingerprint.intent.summary.product ?? "unnamed"})`,
),
"merged:",
` situations: ${stack.merged.fingerprint.intent.situations.length}`,
diff --git a/packages/ghost/src/scan/constants.ts b/packages/ghost/src/scan/constants.ts
index 65a1e60b..a69bf53d 100644
--- a/packages/ghost/src/scan/constants.ts
+++ b/packages/ghost/src/scan/constants.ts
@@ -10,9 +10,6 @@ export const PATTERNS_FILENAME = "patterns.yml";
/** Canonical product-surface composition artifact. */
export const FINGERPRINT_YML_FILENAME = "fingerprint.yml";
-/** Portable fingerprint bundle directory under the package root. */
-export const FINGERPRINT_DIRNAME = "fingerprint";
-
/** Portable fingerprint package manifest filename. */
export const FINGERPRINT_MANIFEST_FILENAME = "manifest.yml";
@@ -21,9 +18,6 @@ export const FINGERPRINT_INTENT_FILENAME = "intent.yml";
export const FINGERPRINT_INVENTORY_FILENAME = "inventory.yml";
export const FINGERPRINT_COMPOSITION_FILENAME = "composition.yml";
-/** Optional data-only package configuration artifact. */
-export const CONFIG_FILENAME = "config.yml";
-
/** Legacy direct fingerprint filename. Not part of the root package shape. */
export const FINGERPRINT_FILENAME = "fingerprint.md";
diff --git a/packages/ghost/src/scan/file-kind.ts b/packages/ghost/src/scan/file-kind.ts
index f899b6e8..36100d81 100644
--- a/packages/ghost/src/scan/file-kind.ts
+++ b/packages/ghost/src/scan/file-kind.ts
@@ -14,7 +14,6 @@ import {
} from "#ghost-core";
import { lintFingerprint } from "./lint.js";
import { lintMap } from "./lint-map.js";
-import { lintGhostPackageConfig } from "./package-config.js";
export type DetectedFileKind =
| "survey"
@@ -26,9 +25,9 @@ export type DetectedFileKind =
| "fingerprint-inventory"
| "fingerprint-composition"
| "validate"
- | "config"
| "resources"
- | "patterns";
+ | "patterns"
+ | "unsupported-yaml";
export interface LintDetectedFileKindOptions {
fingerprint?: GhostFingerprintDocument;
@@ -37,58 +36,64 @@ export interface LintDetectedFileKindOptions {
/**
* Decide whether a file is a bundle artifact. JSON paths/contents route to
* the survey linter; markdown with `schema: ghost.map/v1` in frontmatter
- * routes to the map linter; YAML schemas route to fingerprint.yml,
- * config/resources/patterns/validate; everything else stays on the direct
- * fingerprint markdown path.
+ * routes to the map linter; YAML schemas and canonical package filenames route
+ * to their artifact linters. Unknown YAML remains unsupported instead of being
+ * guessed as `validate.yml`.
*/
export function detectFileKind(path: string, raw: string): DetectedFileKind {
- if (path.toLowerCase().endsWith(".json")) return "survey";
- if (path.toLowerCase().endsWith("fingerprint.yml")) {
+ const lowerPath = path.toLowerCase();
+ const filename = lowerPath.split(/[\\/]/).pop() ?? lowerPath;
+ if (lowerPath.endsWith(".json")) return "survey";
+ if (filename === "fingerprint.yml") {
return "fingerprint-yml";
}
- if (path.toLowerCase().endsWith("fingerprint.yaml")) {
+ if (filename === "fingerprint.yaml") {
return "fingerprint-yml";
}
- if (path.toLowerCase().endsWith("fingerprint/manifest.yml")) {
+ if (filename === "manifest.yml") {
return "fingerprint-manifest";
}
- if (path.toLowerCase().endsWith("fingerprint/manifest.yaml")) {
+ if (filename === "manifest.yaml") {
return "fingerprint-manifest";
}
- if (path.toLowerCase().endsWith("fingerprint/intent.yml")) {
+ if (filename === "intent.yml") {
return "fingerprint-intent";
}
- if (path.toLowerCase().endsWith("fingerprint/intent.yaml")) {
+ if (filename === "intent.yaml") {
return "fingerprint-intent";
}
- if (path.toLowerCase().endsWith("fingerprint/inventory.yml")) {
+ if (filename === "inventory.yml") {
return "fingerprint-inventory";
}
- if (path.toLowerCase().endsWith("fingerprint/inventory.yaml")) {
+ if (filename === "inventory.yaml") {
return "fingerprint-inventory";
}
- if (path.toLowerCase().endsWith("fingerprint/composition.yml")) {
+ if (filename === "composition.yml") {
return "fingerprint-composition";
}
- if (path.toLowerCase().endsWith("fingerprint/composition.yaml")) {
+ if (filename === "composition.yaml") {
return "fingerprint-composition";
}
- if (path.toLowerCase().endsWith("resources.yml")) return "resources";
- if (path.toLowerCase().endsWith("resources.yaml")) return "resources";
- if (path.toLowerCase().endsWith("patterns.yml")) return "patterns";
- if (path.toLowerCase().endsWith("patterns.yaml")) return "patterns";
- if (path.toLowerCase().endsWith("config.yml")) return "config";
- if (path.toLowerCase().endsWith("config.yaml")) return "config";
+ if (filename === "validate.yml" || filename === "validate.yaml") {
+ return "validate";
+ }
+ if (filename === "resources.yml") return "resources";
+ if (filename === "resources.yaml") return "resources";
+ if (filename === "patterns.yml") return "patterns";
+ if (filename === "patterns.yaml") return "patterns";
if (raw.trimStart().startsWith("{")) return "survey";
if (/^\s*schema:\s*ghost\.fingerprint\/v[12]\b/m.test(raw)) {
return "fingerprint-yml";
}
+ if (/^\s*schema:\s*ghost\.fingerprint-package\/v1\b/m.test(raw)) {
+ return "fingerprint-manifest";
+ }
if (/^\s*schema:\s*ghost\.resources\/v1\b/m.test(raw)) return "resources";
if (/^\s*schema:\s*ghost\.patterns\/v1\b/m.test(raw)) return "patterns";
- if (/^\s*schema:\s*ghost\.config\/v1\b/m.test(raw)) return "config";
if (/^\s*schema:\s*ghost\.validate\/v[12]\b/m.test(raw)) return "validate";
- if (path.toLowerCase().endsWith(".yml")) return "validate";
- if (path.toLowerCase().endsWith(".yaml")) return "validate";
+ if (lowerPath.endsWith(".yml") || lowerPath.endsWith(".yaml")) {
+ return "unsupported-yaml";
+ }
const fmEnd = raw.indexOf("\n---", 3);
if (raw.startsWith("---") && fmEnd > 0) {
const fm = raw.slice(0, fmEnd);
@@ -121,10 +126,10 @@ export function lintDetectedFileKind(
? lintResourcesFile(raw)
: kind === "patterns"
? lintPatternsFile(raw)
- : kind === "config"
- ? lintConfigFile(raw)
- : kind === "validate"
- ? lintValidateFile(raw, options.fingerprint)
+ : kind === "validate"
+ ? lintValidateFile(raw, options.fingerprint)
+ : kind === "unsupported-yaml"
+ ? lintUnsupportedYamlFile()
: lintFingerprint(raw);
}
@@ -163,14 +168,6 @@ function lintValidateFile(
}
}
-function lintConfigFile(raw: string): ReturnType {
- try {
- return lintGhostPackageConfig(parseYaml(raw));
- } catch (err) {
- return yamlErrorReport("config-not-yaml", "config.yml", err);
- }
-}
-
function lintFingerprintYmlFile(
raw: string,
): ReturnType {
@@ -191,7 +188,7 @@ function lintFingerprintManifestFile(
} catch (err) {
return yamlErrorReport(
"fingerprint-manifest-not-yaml",
- "fingerprint/manifest.yml",
+ "manifest.yml",
err,
);
}
@@ -213,7 +210,7 @@ function lintFingerprintLayerFile(
} catch (err) {
return yamlErrorReport(
`fingerprint-${facet}-not-yaml`,
- `fingerprint/${facet}.yml`,
+ `${facet}.yml`,
err,
);
}
@@ -257,6 +254,22 @@ function lintPatternsFile(raw: string): ReturnType {
}
}
+function lintUnsupportedYamlFile(): ReturnType {
+ return {
+ issues: [
+ {
+ severity: "error",
+ rule: "unsupported-yaml",
+ message:
+ "YAML file is not a recognized Ghost artifact. Use manifest.yml, intent.yml, inventory.yml, composition.yml, validate.yml, resources.yml, patterns.yml, fingerprint.yml, or include a supported ghost.* schema.",
+ },
+ ],
+ errors: 1,
+ warnings: 0,
+ info: 0,
+ };
+}
+
function yamlErrorReport(
rule: string,
label: string,
diff --git a/packages/ghost/src/scan/fingerprint-contribution.ts b/packages/ghost/src/scan/fingerprint-contribution.ts
index 000aab50..edd7607b 100644
--- a/packages/ghost/src/scan/fingerprint-contribution.ts
+++ b/packages/ghost/src/scan/fingerprint-contribution.ts
@@ -161,7 +161,7 @@ function contributionReasons(
): string[] {
if (state === "missing") {
return [
- "fingerprint/manifest.yml is missing, so no package contribution can be resolved.",
+ "manifest.yml is missing, so no package contribution can be resolved.",
];
}
if (state === "invalid") {
@@ -177,7 +177,7 @@ function contributionReasons(
? ` Absent facets may be inherited from broader stack context: ${input.absentFacets.join(", ")}.`
: "";
return [
- `fingerprint/ is valid but this package contributes no useful facets yet.${detail}${absent}`,
+ `Ghost package is valid but this package contributes no useful facets yet.${detail}${absent}`,
];
}
@@ -188,7 +188,7 @@ function contributionReasons(
? ` Empty facets: ${input.emptyFacets.join(", ")}.`
: "";
return [
- `fingerprint/ contributes ${input.contributingFacets.join(", ")}.${empty}${absent}`,
+ `Ghost package contributes ${input.contributingFacets.join(", ")}.${empty}${absent}`,
];
}
diff --git a/packages/ghost/src/scan/fingerprint-package-layers.ts b/packages/ghost/src/scan/fingerprint-package-layers.ts
index b7330154..e6c4e69a 100644
--- a/packages/ghost/src/scan/fingerprint-package-layers.ts
+++ b/packages/ghost/src/scan/fingerprint-package-layers.ts
@@ -32,23 +32,23 @@ export async function loadFingerprintPackage(
readOptional(paths.inventory),
readOptional(paths.composition),
]);
- const manifest = parseManifest(manifestRaw, "fingerprint/manifest.yml");
+ const manifest = parseManifest(manifestRaw, "manifest.yml");
const fingerprint = assembleFingerprint({
intent: parseLayer(
intentRaw,
- "fingerprint/intent.yml",
+ "intent.yml",
GhostFingerprintIntentSchema,
emptyIntent(),
),
inventory: parseLayer(
inventoryRaw,
- "fingerprint/inventory.yml",
+ "inventory.yml",
GhostFingerprintInventorySchema,
emptyInventory(),
),
composition: parseLayer(
compositionRaw,
- "fingerprint/composition.yml",
+ "composition.yml",
GhostFingerprintCompositionSchema,
emptyComposition(),
),
@@ -77,14 +77,14 @@ export function lintFingerprintPackageManifest(
raw: string,
issues: LintIssue[],
): void {
- const manifest = parseYamlSafe(raw, "fingerprint/manifest.yml", issues);
+ const manifest = parseYamlSafe(raw, "manifest.yml", issues);
if (manifest === undefined) return;
const manifestResult =
GhostFingerprintPackageManifestSchema.safeParse(manifest);
if (!manifestResult.success) {
issues.push(
...prefixIssues(
- "fingerprint/manifest.yml",
+ "manifest.yml",
zodLikeIssues(manifestResult.error.issues),
),
);
@@ -101,21 +101,21 @@ export function parseSplitFingerprintForLint(
): GhostFingerprintDocument | undefined {
const intent = parseLayerForLint(
input.intentRaw,
- "fingerprint/intent.yml",
+ "intent.yml",
GhostFingerprintIntentSchema,
emptyIntent(),
issues,
);
const inventory = parseLayerForLint(
input.inventoryRaw,
- "fingerprint/inventory.yml",
+ "inventory.yml",
GhostFingerprintInventorySchema,
emptyInventory(),
issues,
);
const composition = parseLayerForLint(
input.compositionRaw,
- "fingerprint/composition.yml",
+ "composition.yml",
GhostFingerprintCompositionSchema,
emptyComposition(),
issues,
@@ -294,17 +294,17 @@ function parseYamlSafe(
}
function splitFingerprintPath(path: string): string {
- if (path === "intent") return "fingerprint/intent.yml";
+ if (path === "intent") return "intent.yml";
if (path.startsWith("intent.")) {
- return `fingerprint/intent.yml.${path.slice("intent.".length)}`;
+ return `intent.yml.${path.slice("intent.".length)}`;
}
- if (path === "inventory") return "fingerprint/inventory.yml";
+ if (path === "inventory") return "inventory.yml";
if (path.startsWith("inventory.")) {
- return `fingerprint/inventory.yml.${path.slice("inventory.".length)}`;
+ return `inventory.yml.${path.slice("inventory.".length)}`;
}
- if (path === "composition") return "fingerprint/composition.yml";
+ if (path === "composition") return "composition.yml";
if (path.startsWith("composition.")) {
- return `fingerprint/composition.yml.${path.slice("composition.".length)}`;
+ return `composition.yml.${path.slice("composition.".length)}`;
}
return `fingerprint/${path}`;
}
diff --git a/packages/ghost/src/scan/fingerprint-package.ts b/packages/ghost/src/scan/fingerprint-package.ts
index f4239ac4..66e24c83 100644
--- a/packages/ghost/src/scan/fingerprint-package.ts
+++ b/packages/ghost/src/scan/fingerprint-package.ts
@@ -15,9 +15,7 @@ import {
readOptionalUtf8,
} from "../internal/fs.js";
import {
- CONFIG_FILENAME,
FINGERPRINT_COMPOSITION_FILENAME,
- FINGERPRINT_DIRNAME,
FINGERPRINT_FILENAME,
FINGERPRINT_INTENT_FILENAME,
FINGERPRINT_INVENTORY_FILENAME,
@@ -37,22 +35,17 @@ import {
templateManifest,
} from "./fingerprint-package-layers.js";
import type { LintIssue, LintReport } from "./lint.js";
-import {
- lintGhostPackageConfig,
- templatePackageConfig,
-} from "./package-config.js";
export { loadFingerprintPackage } from "./fingerprint-package-layers.js";
export interface FingerprintPackagePaths {
dir: string;
- fingerprintDir: string;
+ packageDir: string;
manifest: string;
intent: string;
inventory: string;
composition: string;
fingerprintYml: string;
- config: string;
resources: string;
map: string;
survey: string;
@@ -74,7 +67,6 @@ export interface LoadedFingerprintPackage {
}
export interface InitFingerprintPackageOptions {
- withConfig?: boolean;
reference?: string;
force?: boolean;
}
@@ -84,22 +76,21 @@ export function resolveFingerprintPackage(
cwd = process.cwd(),
): FingerprintPackagePaths {
const dir = resolve(cwd, dirArg ?? FINGERPRINT_PACKAGE_DIR);
- const fingerprintDir = join(dir, FINGERPRINT_DIRNAME);
+ const packageDir = dir;
return {
dir,
- fingerprintDir,
- manifest: join(fingerprintDir, FINGERPRINT_MANIFEST_FILENAME),
- intent: join(fingerprintDir, FINGERPRINT_INTENT_FILENAME),
- inventory: join(fingerprintDir, FINGERPRINT_INVENTORY_FILENAME),
- composition: join(fingerprintDir, FINGERPRINT_COMPOSITION_FILENAME),
+ packageDir,
+ manifest: join(packageDir, FINGERPRINT_MANIFEST_FILENAME),
+ intent: join(packageDir, FINGERPRINT_INTENT_FILENAME),
+ inventory: join(packageDir, FINGERPRINT_INVENTORY_FILENAME),
+ composition: join(packageDir, FINGERPRINT_COMPOSITION_FILENAME),
fingerprintYml: join(dir, FINGERPRINT_YML_FILENAME),
- config: join(dir, CONFIG_FILENAME),
resources: join(dir, RESOURCES_FILENAME),
map: join(dir, MAP_FILENAME),
survey: join(dir, SURVEY_FILENAME),
patterns: join(dir, PATTERNS_FILENAME),
fingerprint: join(dir, FINGERPRINT_FILENAME),
- checks: join(fingerprintDir, GHOST_VALIDATE_FILENAME),
+ checks: join(packageDir, GHOST_VALIDATE_FILENAME),
};
}
@@ -109,24 +100,13 @@ export async function initFingerprintPackage(
options: InitFingerprintPackageOptions = {},
): Promise {
const paths = resolveFingerprintPackage(dirArg, cwd);
- await Promise.all([
- mkdir(paths.fingerprintDir, { recursive: true }),
- ...(options.withConfig ? [mkdir(paths.dir, { recursive: true })] : []),
- ]);
+ await mkdir(paths.packageDir, { recursive: true });
const files = [
{ path: paths.manifest, content: templateManifest() },
{ path: paths.intent, content: templateIntent() },
{ path: paths.inventory, content: templateInventory(options.reference) },
{ path: paths.composition, content: templateComposition() },
{ path: paths.checks, content: templateChecks() },
- ...(options.withConfig
- ? [
- {
- path: paths.config,
- content: templatePackageConfig(options.reference),
- },
- ]
- : []),
];
if (!options.force) {
await assertInitDoesNotOverwrite(files.map((file) => file.path));
@@ -185,13 +165,12 @@ export async function lintFingerprintPackage(
const manifestRaw = await readRequired(
paths.manifest,
- "fingerprint/manifest.yml",
+ "manifest.yml",
issues,
);
const intentRaw = await readOptional(paths.intent);
const inventoryRaw = await readOptional(paths.inventory);
const compositionRaw = await readOptional(paths.composition);
- const configRaw = await readOptional(paths.config);
const checksRaw = await readOptional(paths.checks);
let fingerprint: GhostFingerprintDocument | undefined;
@@ -203,21 +182,11 @@ export async function lintFingerprintPackage(
);
}
- if (configRaw !== undefined) {
- const config = parseYamlSafe(configRaw, "config.yml", issues);
- if (config !== undefined) {
- const configReport = lintGhostPackageConfig(config);
- issues.push(...prefixIssues("config.yml", configReport.issues));
- }
- }
-
if (checksRaw !== undefined) {
- const checks = parseYamlSafe(checksRaw, "fingerprint/validate.yml", issues);
+ const checks = parseYamlSafe(checksRaw, "validate.yml", issues);
if (checks !== undefined) {
const checksReport = lintGhostValidate(checks, { fingerprint });
- issues.push(
- ...prefixIssues("fingerprint/validate.yml", checksReport.issues),
- );
+ issues.push(...prefixIssues("validate.yml", checksReport.issues));
}
}
diff --git a/packages/ghost/src/scan/fingerprint-stack.ts b/packages/ghost/src/scan/fingerprint-stack.ts
index 664a9dc5..aafde3e8 100644
--- a/packages/ghost/src/scan/fingerprint-stack.ts
+++ b/packages/ghost/src/scan/fingerprint-stack.ts
@@ -26,7 +26,6 @@ import {
import type { PackageContext } from "../context/package-context.js";
import { readOptionalUtf8 } from "../internal/fs.js";
import {
- FINGERPRINT_DIRNAME,
FINGERPRINT_MANIFEST_FILENAME,
FINGERPRINT_PACKAGE_DIR,
} from "./constants.js";
@@ -58,14 +57,14 @@ const BASE_SKIP_DISCOVERY_DIRS = new Set([
]);
export interface FingerprintDirectoryOptions {
- memoryDir?: string;
+ ghostDir?: string;
}
export interface GhostFingerprintStackLayerRef {
dir: string;
root: string;
relative_root: string;
- fingerprint_dir: string;
+ ghost_dir: string;
}
export interface GhostFingerprintStackLayer
@@ -79,7 +78,7 @@ export interface GhostFingerprintStackLayer
export interface GhostFingerprintStack {
target_path: string;
repo_root: string;
- fingerprint_dir: string;
+ ghost_dir: string;
layers: GhostFingerprintStackLayer[];
merged: {
fingerprint: GhostFingerprintDocument;
@@ -101,7 +100,7 @@ export interface DiscoveredGhostPackage {
dir: string;
root: string;
relative_root: string;
- fingerprint_dir: string;
+ ghost_dir: string;
}
export async function resolveGitRoot(cwd = process.cwd()): Promise {
@@ -124,14 +123,14 @@ export async function discoverGhostPackages(
options: FingerprintDirectoryOptions = {},
): Promise {
const repoRoot = await resolveGitRoot(root);
- const memoryDir = normalizeMemoryDir(options.memoryDir);
- const skipDirs = skipDiscoveryDirs(memoryDir);
+ const ghostDir = normalizeGhostDir(options.ghostDir);
+ const skipDirs = skipDiscoveryDirs(ghostDir);
const packages: DiscoveredGhostPackage[] = [];
async function walk(dir: string): Promise {
- const packageDir = resolve(dir, memoryDir);
+ const packageDir = resolve(dir, ghostDir);
if (await hasSplitFingerprintPackage(packageDir)) {
- packages.push(packageRef(packageDir, repoRoot, memoryDir));
+ packages.push(packageRef(packageDir, repoRoot, ghostDir));
}
let entries: Dirent[];
@@ -160,13 +159,13 @@ export async function discoverFingerprintStack(
options: FingerprintDirectoryOptions = {},
): Promise<{ target_path: string; repo_root: string; packages: string[] }> {
const repoRoot = await resolveGitRoot(cwd);
- const memoryDir = normalizeMemoryDir(options.memoryDir);
+ const ghostDir = normalizeGhostDir(options.ghostDir);
const target = resolve(cwd, targetPath);
let current = await startingDirectory(target);
const packages: string[] = [];
while (isWithinOrEqual(repoRoot, current)) {
- const packageDir = resolve(current, memoryDir);
+ const packageDir = resolve(current, ghostDir);
if (await hasSplitFingerprintPackage(packageDir)) {
packages.push(packageDir);
}
@@ -186,26 +185,26 @@ export async function loadFingerprintStackForPath(
cwd = process.cwd(),
options: FingerprintDirectoryOptions = {},
): Promise {
- const memoryDir = normalizeMemoryDir(options.memoryDir);
+ const ghostDir = normalizeGhostDir(options.ghostDir);
const discovered = await discoverFingerprintStack(targetPath, cwd, {
- memoryDir,
+ ghostDir,
});
if (discovered.packages.length === 0) {
throw new Error(
- `No ${memoryDir}/${FINGERPRINT_DIRNAME}/${FINGERPRINT_MANIFEST_FILENAME} found for ${targetPath}.`,
+ `No ${ghostDir}/${FINGERPRINT_MANIFEST_FILENAME} found for ${targetPath}.`,
);
}
const layers = await Promise.all(
discovered.packages.map((dir) =>
- loadFingerprintStackLayer(dir, discovered.repo_root, memoryDir),
+ loadFingerprintStackLayer(dir, discovered.repo_root, ghostDir),
),
);
return buildFingerprintStack(
discovered.target_path,
discovered.repo_root,
layers,
- memoryDir,
+ ghostDir,
);
}
@@ -215,11 +214,11 @@ export async function groupFingerprintStacksForPaths(
options: FingerprintDirectoryOptions = {},
): Promise {
const targets = paths.length > 0 ? paths : ["."];
- const memoryDir = normalizeMemoryDir(options.memoryDir);
+ const ghostDir = normalizeGhostDir(options.ghostDir);
const groups = new Map();
for (const path of targets) {
- const stack = await loadFingerprintStackForPath(path, cwd, { memoryDir });
+ const stack = await loadFingerprintStackForPath(path, cwd, { ghostDir });
const key = stack.layers.map((layer) => layer.dir).join("|");
const existing = groups.get(key);
if (existing) {
@@ -240,9 +239,9 @@ export function buildFingerprintStack(
targetPath: string,
repoRoot: string,
layers: GhostFingerprintStackLayer[],
- memoryDir = FINGERPRINT_PACKAGE_DIR,
+ ghostDir = FINGERPRINT_PACKAGE_DIR,
): GhostFingerprintStack {
- const normalizedMemoryDir = normalizeMemoryDir(memoryDir);
+ const normalizedGhostDir = normalizeGhostDir(ghostDir);
if (layers.length === 0) {
throw new Error("Cannot build a Ghost fingerprint stack without layers.");
}
@@ -267,7 +266,7 @@ export function buildFingerprintStack(
return {
target_path: targetPath,
repo_root: repoRoot,
- fingerprint_dir: normalizedMemoryDir,
+ ghost_dir: normalizedGhostDir,
layers,
merged: {
fingerprint,
@@ -283,11 +282,11 @@ export function buildFingerprintStack(
export async function loadFingerprintStackLayer(
packageDir: string,
repoRoot: string,
- memoryDir = FINGERPRINT_PACKAGE_DIR,
+ ghostDir = FINGERPRINT_PACKAGE_DIR,
): Promise {
const paths = resolveFingerprintPackage(packageDir, process.cwd());
- const normalizedMemoryDir = normalizeMemoryDir(memoryDir);
- const root = rootForFingerprintPackageDir(paths.dir, normalizedMemoryDir);
+ const normalizedGhostDir = normalizeGhostDir(ghostDir);
+ const root = rootForFingerprintPackageDir(paths.dir, normalizedGhostDir);
const [loaded, checksRaw] = await Promise.all([
loadFingerprintPackage(paths),
readOptional(paths.checks),
@@ -316,7 +315,7 @@ export async function loadFingerprintStackLayer(
}
return {
- ...packageRef(paths.dir, repoRoot, normalizedMemoryDir),
+ ...packageRef(paths.dir, repoRoot, normalizedGhostDir),
fingerprint,
fingerprint_raw: stringifyYaml(fingerprint, { lineWidth: 0 }),
...(checks ? { checks } : {}),
@@ -337,7 +336,7 @@ export function fingerprintStackToPackageContext(
);
return {
name,
- fingerprintDir: stack.fingerprint_dir,
+ packageDir: stack.layers.at(-1)?.dir,
targetPaths,
stackDirs: stack.layers.map((layer) => layer.dir),
fingerprint: stack.merged.fingerprint,
@@ -365,15 +364,15 @@ export async function lintAllFingerprintStacks(
root = process.cwd(),
options: FingerprintDirectoryOptions = {},
): Promise {
- const memoryDir = normalizeMemoryDir(options.memoryDir);
- const packages = await discoverGhostPackages(root, { memoryDir });
+ const ghostDir = normalizeGhostDir(options.ghostDir);
+ const packages = await discoverGhostPackages(root, { ghostDir });
const issues: LintIssue[] = [];
for (const pkg of packages) {
const rawReport = await lintFingerprintPackage(pkg.dir, root);
issues.push(
...prefixIssues(
- fingerprintPackageDisplayPath(pkg.relative_root, memoryDir),
+ fingerprintPackageDisplayPath(pkg.relative_root, ghostDir),
rawReport.issues,
),
);
@@ -381,20 +380,20 @@ export async function lintAllFingerprintStacks(
let stack: GhostFingerprintStack;
try {
- stack = await loadFingerprintStackForPath(pkg.root, root, { memoryDir });
+ stack = await loadFingerprintStackForPath(pkg.root, root, { ghostDir });
} catch (err) {
issues.push({
severity: "error",
rule: "stack-merge-invalid",
message: err instanceof Error ? err.message : String(err),
- path: fingerprintPackageDisplayPath(pkg.relative_root, memoryDir),
+ path: fingerprintPackageDisplayPath(pkg.relative_root, ghostDir),
});
continue;
}
const fingerprintReport = lintGhostFingerprint(stack.merged.fingerprint);
issues.push(
...prefixIssues(
- `${fingerprintPackageDisplayPath(pkg.relative_root, memoryDir)}/merged.fingerprint`,
+ `${fingerprintPackageDisplayPath(pkg.relative_root, ghostDir)}/merged.fingerprint`,
fingerprintReport.issues,
),
);
@@ -404,7 +403,7 @@ export async function lintAllFingerprintStacks(
});
issues.push(
...prefixIssues(
- `${fingerprintPackageDisplayPath(pkg.relative_root, memoryDir)}/merged.validate.yml`,
+ `${fingerprintPackageDisplayPath(pkg.relative_root, ghostDir)}/merged.validate.yml`,
checksReport.issues,
),
);
@@ -417,8 +416,8 @@ export async function verifyAllFingerprintStacks(
root = process.cwd(),
options: FingerprintDirectoryOptions = {},
): Promise {
- const memoryDir = normalizeMemoryDir(options.memoryDir);
- const packages = await discoverGhostPackages(root, { memoryDir });
+ const ghostDir = normalizeGhostDir(options.ghostDir);
+ const packages = await discoverGhostPackages(root, { ghostDir });
const issues: VerifyFingerprintIssue[] = [];
for (const pkg of packages) {
@@ -429,18 +428,18 @@ export async function verifyAllFingerprintStacks(
...report.issues.map((issue) => ({
...issue,
path: issue.path
- ? `${fingerprintPackageDisplayPath(pkg.relative_root, memoryDir)}.${issue.path}`
- : fingerprintPackageDisplayPath(pkg.relative_root, memoryDir),
+ ? `${fingerprintPackageDisplayPath(pkg.relative_root, ghostDir)}.${issue.path}`
+ : fingerprintPackageDisplayPath(pkg.relative_root, ghostDir),
})),
);
try {
- await loadFingerprintStackForPath(pkg.root, root, { memoryDir });
+ await loadFingerprintStackForPath(pkg.root, root, { ghostDir });
} catch (err) {
issues.push({
severity: "error",
rule: "stack-merge-invalid",
message: err instanceof Error ? err.message : String(err),
- path: fingerprintPackageDisplayPath(pkg.relative_root, memoryDir),
+ path: fingerprintPackageDisplayPath(pkg.relative_root, ghostDir),
});
}
}
@@ -457,10 +456,9 @@ export async function initScopedFingerprintPackage(
scopePath: string,
cwd = process.cwd(),
options: {
- withConfig?: boolean;
reference?: string;
force?: boolean;
- memoryDir?: string;
+ ghostDir?: string;
} = {},
): Promise {
const root = resolve(cwd, scopePath);
@@ -471,23 +469,18 @@ export async function initScopedFingerprintPackage(
async function resolveAndInit(
root: string,
options: {
- withConfig?: boolean;
reference?: string;
force?: boolean;
- memoryDir?: string;
+ ghostDir?: string;
},
): Promise {
const { initFingerprintPackage } = await import("./fingerprint-package.js");
- const { memoryDir, ...initOptions } = options;
- return initFingerprintPackage(
- normalizeMemoryDir(memoryDir),
- root,
- initOptions,
- );
+ const { ghostDir, ...initOptions } = options;
+ return initFingerprintPackage(normalizeGhostDir(ghostDir), root, initOptions);
}
function parseChecks(raw: string): GhostValidateDocument {
- const parsed = parseYamlSafe(raw, "fingerprint/validate.yml");
+ const parsed = parseYamlSafe(raw, "validate.yml");
return GhostValidateSchema.parse(parsed) as GhostValidateDocument;
}
@@ -813,22 +806,20 @@ async function pathExists(path: string): Promise {
async function hasSplitFingerprintPackage(
packageDir: string,
): Promise {
- return pathExists(
- resolve(packageDir, FINGERPRINT_DIRNAME, FINGERPRINT_MANIFEST_FILENAME),
- );
+ return pathExists(resolve(packageDir, FINGERPRINT_MANIFEST_FILENAME));
}
function packageRef(
dir: string,
repoRoot: string,
- memoryDir: string,
+ ghostDir: string,
): DiscoveredGhostPackage {
- const root = rootForFingerprintPackageDir(dir, memoryDir);
+ const root = rootForFingerprintPackageDir(dir, ghostDir);
return {
dir,
root,
relative_root: normalizeRelative(repoRoot, root),
- fingerprint_dir: memoryDir,
+ ghost_dir: ghostDir,
};
}
@@ -839,27 +830,25 @@ function layerRef(
dir: layer.dir,
root: layer.root,
relative_root: layer.relative_root,
- fingerprint_dir: layer.fingerprint_dir,
+ ghost_dir: layer.ghost_dir,
};
}
-export function normalizeMemoryDir(
- memoryDir = FINGERPRINT_PACKAGE_DIR,
-): string {
- const normalized = memoryDir
+export function normalizeGhostDir(ghostDir = FINGERPRINT_PACKAGE_DIR): string {
+ const normalized = ghostDir
.trim()
.replaceAll("\\", "/")
.replace(/\/+/g, "/")
.replace(/\/$/g, "");
if (!normalized) {
- throw new Error("--memory-dir must not be empty");
+ throw new Error("GHOST_PACKAGE_DIR must not be empty");
}
if (
- isAbsolute(memoryDir) ||
+ isAbsolute(ghostDir) ||
normalized.startsWith("/") ||
/^[A-Za-z]:/.test(normalized)
) {
- throw new Error("--memory-dir must be a relative directory path");
+ throw new Error("GHOST_PACKAGE_DIR must be a relative directory path");
}
const segments = normalized.split("/");
if (
@@ -868,48 +857,48 @@ export function normalizeMemoryDir(
)
) {
throw new Error(
- "--memory-dir must not contain '.', '..', or empty path segments",
+ "GHOST_PACKAGE_DIR must not contain '.', '..', or empty path segments",
);
}
return normalized;
}
-export const GHOST_MEMORY_DIR_ENV = "GHOST_MEMORY_DIR";
+export const GHOST_PACKAGE_DIR_ENV = "GHOST_PACKAGE_DIR";
-export function resolveMemoryDirDefault(
- explicitMemoryDir?: unknown,
+export function resolveGhostDirDefault(
+ explicitGhostDir?: unknown,
env: NodeJS.ProcessEnv = process.env,
): string {
- return normalizeMemoryDir(
- typeof explicitMemoryDir === "string"
- ? explicitMemoryDir
- : env[GHOST_MEMORY_DIR_ENV],
+ return normalizeGhostDir(
+ typeof explicitGhostDir === "string"
+ ? explicitGhostDir
+ : env[GHOST_PACKAGE_DIR_ENV],
);
}
export function fingerprintPackageDisplayPath(
relativeRoot: string,
- memoryDir = FINGERPRINT_PACKAGE_DIR,
+ ghostDir = FINGERPRINT_PACKAGE_DIR,
): string {
- const normalizedMemoryDir = normalizeMemoryDir(memoryDir);
+ const normalizedGhostDir = normalizeGhostDir(ghostDir);
return relativeRoot === "."
- ? normalizedMemoryDir
- : `${relativeRoot}/${normalizedMemoryDir}`;
+ ? normalizedGhostDir
+ : `${relativeRoot}/${normalizedGhostDir}`;
}
-function skipDiscoveryDirs(memoryDir: string): Set {
+function skipDiscoveryDirs(ghostDir: string): Set {
return new Set([
...BASE_SKIP_DISCOVERY_DIRS,
- normalizeMemoryDir(memoryDir).split("/")[0],
+ normalizeGhostDir(ghostDir).split("/")[0],
]);
}
function rootForFingerprintPackageDir(
packageDir: string,
- memoryDir: string,
+ ghostDir: string,
): string {
let root = packageDir;
- for (const _segment of normalizeMemoryDir(memoryDir).split("/")) {
+ for (const _segment of normalizeGhostDir(ghostDir).split("/")) {
root = dirname(root);
}
return root;
diff --git a/packages/ghost/src/scan/index.ts b/packages/ghost/src/scan/index.ts
index be5766b0..125b8975 100644
--- a/packages/ghost/src/scan/index.ts
+++ b/packages/ghost/src/scan/index.ts
@@ -1,8 +1,4 @@
-export {
- CONFIG_FILENAME,
- FINGERPRINT_DIRNAME,
- FINGERPRINT_PACKAGE_DIR,
-} from "./constants.js";
+export { FINGERPRINT_PACKAGE_DIR } from "./constants.js";
export type {
ScanBuildingBlockRows,
ScanContributionReport,
@@ -25,12 +21,12 @@ export {
discoverFingerprintStack,
discoverGhostPackages,
fingerprintPackageDisplayPath,
- GHOST_MEMORY_DIR_ENV,
+ GHOST_PACKAGE_DIR_ENV,
groupFingerprintStacksForPaths,
loadFingerprintStackForPath,
- normalizeMemoryDir,
+ normalizeGhostDir,
+ resolveGhostDirDefault,
resolveGitRoot,
- resolveMemoryDirDefault,
} from "./fingerprint-stack.js";
export { signals } from "./inventory.js";
export type { MonorepoInitCandidate } from "./monorepo-init.js";
diff --git a/packages/ghost/src/scan/inventory.ts b/packages/ghost/src/scan/inventory.ts
index 1e90997c..05cb6744 100644
--- a/packages/ghost/src/scan/inventory.ts
+++ b/packages/ghost/src/scan/inventory.ts
@@ -3,13 +3,10 @@ import { type Dirent, readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative, resolve, sep } from "node:path";
import type {
GitInfo,
- InventoryConfigSummary,
InventoryOutput,
LanguageHistogramEntry,
TopLevelEntry,
} from "#ghost-core";
-import { CONFIG_FILENAME, FINGERPRINT_PACKAGE_DIR } from "./constants.js";
-import { readOptionalPackageConfigSync } from "./package-config.js";
/**
* Canonical package manifests we scan for at the inventoried root.
@@ -316,7 +313,6 @@ export function signals(path: string): InventoryOutput {
const registryFiles = sortRelative(walkResult.registryFiles, root);
const topLevelTree = readTopLevel(root);
const git = readGit(root);
- const config = readInventoryConfig(root);
// Token directories surface as additional config candidates so the
// recipe can find directory-shaped token graphs without a separate scan.
@@ -345,27 +341,6 @@ export function signals(path: string): InventoryOutput {
top_level_tree: topLevelTree,
git_remote: git.remote,
git_default_branch: git.default_branch,
- ...(config ? { config } : {}),
- };
-}
-
-function readInventoryConfig(root: string): InventoryConfigSummary | undefined {
- const path = join(root, FINGERPRINT_PACKAGE_DIR, CONFIG_FILENAME);
- const config = readOptionalPackageConfigSync(path);
- if (!config) return undefined;
- return {
- path,
- targets: config.targets.map((target) => ({
- id: target.id,
- ...(target.platform ? { platform: target.platform } : {}),
- roots: target.roots,
- })),
- libraries: config.libraries.map((library) => ({
- id: library.id,
- role: library.role,
- source: library.source,
- ...(library.fingerprint ? { fingerprint: library.fingerprint } : {}),
- })),
};
}
diff --git a/packages/ghost/src/scan/package-config.ts b/packages/ghost/src/scan/package-config.ts
index e54ca1f2..a373665d 100644
--- a/packages/ghost/src/scan/package-config.ts
+++ b/packages/ghost/src/scan/package-config.ts
@@ -1,70 +1,4 @@
-import { existsSync, readFileSync } from "node:fs";
-import { readFile } from "node:fs/promises";
-import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
-import { z } from "zod";
-import type { LintIssue, LintReport } from "./lint.js";
-
-export const GHOST_PACKAGE_CONFIG_SCHEMA = "ghost.config/v1" as const;
-
-const SlugIdSchema = z
- .string()
- .min(1)
- .regex(/^[a-z0-9][a-z0-9._-]*$/, {
- message:
- "id must be a slug (lowercase alphanumeric plus . _ -, leading alphanumeric)",
- });
-
-const GhostPackageConfigTargetSchema = z
- .object({
- id: SlugIdSchema,
- platform: z.string().min(1).optional(),
- roots: z.array(z.string()).default([]),
- components: z.array(z.string().min(1)).optional(),
- tokens: z.array(z.string().min(1)).optional(),
- exclude: z.array(z.string().min(1)).optional(),
- })
- .strict();
-
-const GhostPackageConfigLibrarySchema = z
- .object({
- id: SlugIdSchema,
- role: z.string().min(1),
- source: z.string().min(1),
- fingerprint: z.string().min(1).optional(),
- })
- .strict();
-
-const GhostPackageDesignLoopModeSchema = z.enum([
- "off",
- "advisory",
- "required",
-]);
-
-const GhostPackageDesignLoopSchema = z
- .object({
- enabled: z.boolean().default(false),
- mode: GhostPackageDesignLoopModeSchema.default("off"),
- })
- .strict();
-
-export const GhostPackageConfigSchema = z
- .object({
- schema: z.literal(GHOST_PACKAGE_CONFIG_SCHEMA),
- targets: z.array(GhostPackageConfigTargetSchema).default([]),
- libraries: z.array(GhostPackageConfigLibrarySchema).default([]),
- design_loop: GhostPackageDesignLoopSchema.default({
- enabled: false,
- mode: "off",
- }),
- })
- .strict();
-
-export type GhostPackageConfig = z.infer;
-export type GhostPackageDesignLoop = GhostPackageConfig["design_loop"];
-export type GhostPackageConfigTarget = GhostPackageConfig["targets"][number];
-export type GhostPackageConfigLibrary = GhostPackageConfig["libraries"][number];
-
-export interface ReferenceConfigInput {
+export interface ReferenceInventoryInput {
id: string;
source: string;
fingerprint?: string;
@@ -72,16 +6,14 @@ export interface ReferenceConfigInput {
export function normalizeReferenceInput(
reference: string,
-): ReferenceConfigInput {
+): ReferenceInventoryInput {
const normalized = reference.replace(/\\/g, "/").replace(/\/+$/, "");
const explicitRegistry = normalized.startsWith("registry:");
const isLegacyFingerprint = /(^|\/)fingerprint\.ya?ml$/i.test(normalized);
- const isPackageManifest = /(^|\/)fingerprint\/manifest\.ya?ml$/i.test(
- normalized,
- );
+ const isPackageManifest = /(^|\/)manifest\.ya?ml$/i.test(normalized);
const isFingerprint = isLegacyFingerprint || isPackageManifest;
const baseReference = isPackageManifest
- ? normalized.replace(/\/fingerprint\/manifest\.ya?ml$/i, "")
+ ? normalized.replace(/\/manifest\.ya?ml$/i, "")
: isLegacyFingerprint
? normalized.replace(/\/fingerprint\.ya?ml$/i, "")
: normalized;
@@ -106,7 +38,7 @@ export function normalizeReferenceInput(
const fingerprint = isFingerprint
? fingerprintBase
: ghostIndex >= 0
- ? `${fingerprintBase}/fingerprint/manifest.yml`
+ ? `${fingerprintBase}/manifest.yml`
: undefined;
const referenceIdSource =
source.startsWith("registry:") &&
@@ -124,30 +56,6 @@ export function normalizeReferenceInput(
};
}
-export function templatePackageConfig(reference?: string): string {
- const libraries = reference
- ? [referenceLibraryConfig(normalizeReferenceInput(reference))]
- : [];
- const config: GhostPackageConfig = {
- schema: GHOST_PACKAGE_CONFIG_SCHEMA,
- targets: [{ id: "product", platform: "web", roots: [] }],
- libraries,
- design_loop: { enabled: false, mode: "off" },
- };
- return stringifyYaml(config, { lineWidth: 0 });
-}
-
-function referenceLibraryConfig(reference: ReferenceConfigInput) {
- return {
- id: reference.id,
- role: reference.source.startsWith("registry:")
- ? "primary-ui-registry"
- : "primary-ui-library",
- source: reference.source,
- ...(reference.fingerprint ? { fingerprint: reference.fingerprint } : {}),
- };
-}
-
function inferRegistrySource(
normalized: string,
sourcePath: string,
@@ -165,134 +73,19 @@ function inferRegistrySource(
return undefined;
}
-export async function readOptionalPackageConfig(
- path: string,
-): Promise {
- try {
- const raw = await readFile(path, "utf-8");
- return parsePackageConfig(raw, path);
- } catch (err) {
- if (isMissingFileError(err)) return undefined;
- throw err;
- }
-}
-
-export function readOptionalPackageConfigSync(
- path: string,
-): GhostPackageConfig | undefined {
- if (!existsSync(path)) return undefined;
- return parsePackageConfig(readFileSync(path, "utf-8"), path);
-}
-
-export function parsePackageConfig(
- raw: string,
- label = "config.yml",
-): GhostPackageConfig {
- let parsed: unknown;
- try {
- parsed = parseYaml(raw);
- } catch (err) {
- throw new Error(
- `${label} is not valid YAML: ${
- err instanceof Error ? err.message : String(err)
- }`,
- );
- }
-
- const result = GhostPackageConfigSchema.safeParse(parsed);
- if (!result.success) {
- const first = result.error.issues[0];
- const suffix = first?.path.length ? ` @ ${first.path.join(".")}` : "";
- throw new Error(
- `${label} failed schema validation: ${first?.message ?? "invalid config"}${suffix}`,
- );
- }
- return result.data;
-}
-
-export function lintGhostPackageConfig(input: unknown): LintReport {
- const issues: LintIssue[] = [];
- const result = GhostPackageConfigSchema.safeParse(input);
- if (!result.success) {
- for (const issue of result.error.issues) {
- issues.push({
- severity: "error",
- rule: "config-schema-invalid",
- message: issue.message,
- path: issue.path.length ? issue.path.join(".") : undefined,
- });
- }
- return finalize(issues);
- }
-
- collectDuplicateIds(
- result.data.targets.map((target, index) => ({
- id: target.id,
- path: `targets[${index}].id`,
- label: "target",
- })),
- issues,
- );
- collectDuplicateIds(
- result.data.libraries.map((library, index) => ({
- id: library.id,
- path: `libraries[${index}].id`,
- label: "library",
- })),
- issues,
- );
-
- return finalize(issues);
-}
-
-function collectDuplicateIds(
- entries: Array<{ id: string; path: string; label: string }>,
- issues: LintIssue[],
-): void {
- const seen = new Map();
- for (const entry of entries) {
- const previous = seen.get(entry.id);
- if (previous) {
- issues.push({
- severity: "error",
- rule: `${entry.label}-id-duplicate`,
- message: `${entry.label} id '${entry.id}' is duplicated (also at ${previous}).`,
- path: entry.path,
- });
- } else {
- seen.set(entry.id, entry.path);
- }
- }
-}
-
-function inferReferenceId(sourcePath: string): string {
- const withoutProtocol = sourcePath.replace(/^[a-z]+:/, "");
- const segments = withoutProtocol.split("/").filter(Boolean);
- const raw = segments.at(-1) ?? withoutProtocol;
- const npmName = raw.startsWith("@") ? raw.split("/").at(-1) : raw;
- const id = (npmName ?? "reference")
+function inferReferenceId(source: string): string {
+ const npmName = source.match(/(?:^npm:)?(@[^/]+\/[^/]+|[^/:]+)$/)?.[1];
+ const pathName = source
+ .replace(/^workspace:/, "")
+ .replace(/^registry:/, "")
+ .split("/")
+ .filter(Boolean)
+ .at(-1);
+ const id = (npmName ?? pathName ?? "reference")
.replace(/^@/, "")
- .toLowerCase()
- .replace(/[^a-z0-9._-]+/g, "-")
- .replace(/^[^a-z0-9]+/, "")
- .replace(/[^a-z0-9]+$/, "");
+ .replace(/\//g, "-")
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
+ .replace(/^-|-$/g, "")
+ .toLowerCase();
return id || "reference";
}
-
-function isMissingFileError(err: unknown): boolean {
- return (
- typeof err === "object" &&
- err !== null &&
- "code" in err &&
- (err as { code?: string }).code === "ENOENT"
- );
-}
-
-function finalize(issues: LintIssue[]): LintReport {
- return {
- issues,
- errors: issues.filter((issue) => issue.severity === "error").length,
- warnings: issues.filter((issue) => issue.severity === "warning").length,
- info: issues.filter((issue) => issue.severity === "info").length,
- };
-}
diff --git a/packages/ghost/src/scan/scan-status.ts b/packages/ghost/src/scan/scan-status.ts
index f4ed45b1..98ade9d8 100644
--- a/packages/ghost/src/scan/scan-status.ts
+++ b/packages/ghost/src/scan/scan-status.ts
@@ -10,11 +10,7 @@ import {
MapFrontmatterSchema,
SURVEY_FILENAME,
} from "#ghost-core";
-import {
- CONFIG_FILENAME,
- FINGERPRINTS_DIRNAME,
- SCOPE_SURVEYS_DIRNAME,
-} from "./constants.js";
+import { FINGERPRINTS_DIRNAME, SCOPE_SURVEYS_DIRNAME } from "./constants.js";
import {
type ScanContributionReport,
summarizeFingerprintContribution,
@@ -49,10 +45,9 @@ export interface ScanStatusOptions {
}
export interface ScanStatus {
- /** Absolute path to the Ghost fingerprint directory. */
+ /** Absolute path to the Ghost package directory. */
dir: string;
fingerprint: ScanStageReport;
- config: ScanStageReport;
validate: ScanStageReport;
scopes?: ScanScopeReport[];
scope_error?: string;
@@ -61,7 +56,7 @@ export interface ScanStatus {
}
/**
- * Inspect a Ghost fingerprint directory and report what sparse facets this
+ * Inspect a Ghost package directory and report what sparse facets this
* package contributes. A package can contribute only intent, inventory,
* composition, validate, or any combination; absent facets may be inherited
* from broader stack context.
@@ -72,19 +67,16 @@ export async function scanStatus(
): Promise {
const dir = resolve(dirPath);
const paths = resolveFingerprintPackage(dir, process.cwd());
- const fingerprintPath = paths.fingerprintDir;
- const configPath = resolve(dir, CONFIG_FILENAME);
+ const fingerprintPath = paths.packageDir;
const [
fingerprintPresent,
- configPresent,
intentPresent,
inventoryPresent,
compositionPresent,
validatePresent,
] = await Promise.all([
pathExists(paths.manifest, "file"),
- pathExists(configPath, "file"),
pathExists(paths.intent, "file"),
pathExists(paths.inventory, "file"),
pathExists(paths.composition, "file"),
@@ -99,11 +91,6 @@ export async function scanStatus(
state: validatePresent ? "present" : "missing",
path: paths.checks,
};
- const config: ScanStageReport = {
- state: configPresent ? "present" : "missing",
- path: configPath,
- };
-
const contribution = await scanContribution(paths, {
fingerprintPresent,
intentPresent,
@@ -115,7 +102,6 @@ export async function scanStatus(
const status: ScanStatus = {
dir,
fingerprint,
- config,
validate,
contribution,
recommended_next: fingerprintPresent ? null : "fingerprint",
@@ -185,7 +171,7 @@ async function readOptionalValidate(
const result = GhostValidateSchema.safeParse(parsed);
if (!result.success) {
throw new Error(
- `fingerprint/validate.yml failed schema validation: ${result.error.issues
+ `validate.yml failed schema validation: ${result.error.issues
.map((issue) => `${issue.path.join(".") || ""}: ${issue.message}`)
.join("; ")}`,
);
diff --git a/packages/ghost/src/scan/verify-package.ts b/packages/ghost/src/scan/verify-package.ts
index 15c2ae1b..3ee79757 100644
--- a/packages/ghost/src/scan/verify-package.ts
+++ b/packages/ghost/src/scan/verify-package.ts
@@ -14,8 +14,6 @@ import {
loadFingerprintPackage,
resolveFingerprintPackage,
} from "./fingerprint-package.js";
-import type { GhostPackageConfig } from "./package-config.js";
-import { readOptionalPackageConfig } from "./package-config.js";
import type {
VerifyFingerprintIssue,
VerifyFingerprintReport,
@@ -50,8 +48,6 @@ export async function verifyFingerprintPackage(
readOptionalChecks(paths.checks, issues),
]);
const fingerprint = loaded?.fingerprint;
- const config = await readOptionalConfig(paths.config, issues);
-
if (fingerprint) {
await verifyFingerprintEvidence(fingerprint, root, issues);
await verifyFingerprintExemplars(fingerprint, root, issues);
@@ -61,10 +57,6 @@ export async function verifyFingerprintPackage(
verifyFingerprintCheckRefs(fingerprint, checks.checks, issues);
}
- if (config) {
- await verifyPackageConfig(config, root, issues);
- }
-
return finalize(issues);
}
@@ -83,7 +75,7 @@ async function verifyFingerprintExemplars(
severity: "warning",
rule: "fingerprint-exemplar-unreachable",
message: `fingerprint exemplar path '${entry.path}' could not be resolved from ${root}.`,
- path: `fingerprint/inventory.yml.exemplars[${index}].path`,
+ path: `inventory.yml.exemplars[${index}].path`,
});
}),
);
@@ -119,9 +111,8 @@ async function readOptionalChecks(
issues.push({
severity: "error",
rule: "verify-checks-read-failed",
- message:
- "fingerprint/validate.yml failed schema validation after package lint.",
- path: "fingerprint/validate.yml",
+ message: "validate.yml failed schema validation after package lint.",
+ path: "validate.yml",
});
return undefined;
} catch (err) {
@@ -129,103 +120,15 @@ async function readOptionalChecks(
issues.push({
severity: "error",
rule: "verify-checks-read-failed",
- message: `fingerprint/validate.yml could not be read as YAML: ${
- err instanceof Error ? err.message : String(err)
- }`,
- path: "fingerprint/validate.yml",
- });
- return undefined;
- }
-}
-
-async function readOptionalConfig(
- path: string,
- issues: VerifyFingerprintIssue[],
-): Promise {
- try {
- return await readOptionalPackageConfig(path);
- } catch (err) {
- issues.push({
- severity: "error",
- rule: "verify-config-read-failed",
- message: `config.yml could not be read: ${
+ message: `validate.yml could not be read as YAML: ${
err instanceof Error ? err.message : String(err)
}`,
- path: "config.yml",
+ path: "validate.yml",
});
return undefined;
}
}
-async function verifyPackageConfig(
- config: GhostPackageConfig,
- root: string,
- issues: VerifyFingerprintIssue[],
-): Promise {
- const pathChecks: Array<{ path: string; label: string }> = [];
- config.targets.forEach((target, targetIndex) => {
- target.roots.forEach((entry, entryIndex) => {
- if (entry) {
- pathChecks.push({
- path: entry,
- label: `config.yml.targets[${targetIndex}].roots[${entryIndex}]`,
- });
- }
- });
- target.components?.forEach((entry, entryIndex) => {
- pathChecks.push({
- path: entry,
- label: `config.yml.targets[${targetIndex}].components[${entryIndex}]`,
- });
- });
- target.tokens?.forEach((entry, entryIndex) => {
- pathChecks.push({
- path: entry,
- label: `config.yml.targets[${targetIndex}].tokens[${entryIndex}]`,
- });
- });
- });
-
- config.libraries.forEach((library, libraryIndex) => {
- if (library.source.startsWith("workspace:")) {
- pathChecks.push({
- path: library.source.slice("workspace:".length),
- label: `config.yml.libraries[${libraryIndex}].source`,
- });
- }
- if (library.source.startsWith("registry:")) {
- const registryPath = library.source.slice("registry:".length);
- if (!isRemoteReference(registryPath)) {
- pathChecks.push({
- path: registryPath,
- label: `config.yml.libraries[${libraryIndex}].source`,
- });
- }
- }
- if (library.fingerprint) {
- pathChecks.push({
- path: library.fingerprint,
- label: `config.yml.libraries[${libraryIndex}].fingerprint`,
- });
- }
- });
-
- await Promise.all(
- pathChecks.map(async (entry) => {
- const resolved = isAbsolute(entry.path)
- ? entry.path
- : resolve(root, entry.path);
- if (await pathExists(resolved)) return;
- issues.push({
- severity: "error",
- rule: "config-path-unreachable",
- message: `config path '${entry.path}' could not be resolved from ${root}.`,
- path: entry.label,
- });
- }),
- );
-}
-
async function verifyFingerprintEvidence(
fingerprint: GhostFingerprintDocument,
root: string,
@@ -235,31 +138,31 @@ async function verifyFingerprintEvidence(
[
...fingerprint.intent.situations.map(
(entry, index) =>
- [
- `fingerprint/intent.yml.situations[${index}].evidence`,
- entry.evidence,
- ] as [string, GhostFingerprintEvidence[] | undefined],
+ [`intent.yml.situations[${index}].evidence`, entry.evidence] as [
+ string,
+ GhostFingerprintEvidence[] | undefined,
+ ],
),
...fingerprint.intent.principles.map(
(entry, index) =>
- [
- `fingerprint/intent.yml.principles[${index}].evidence`,
- entry.evidence,
- ] as [string, GhostFingerprintEvidence[] | undefined],
+ [`intent.yml.principles[${index}].evidence`, entry.evidence] as [
+ string,
+ GhostFingerprintEvidence[] | undefined,
+ ],
),
...fingerprint.intent.experience_contracts.map(
(entry, index) =>
[
- `fingerprint/intent.yml.experience_contracts[${index}].evidence`,
+ `intent.yml.experience_contracts[${index}].evidence`,
entry.evidence,
] as [string, GhostFingerprintEvidence[] | undefined],
),
...fingerprint.composition.patterns.map(
(entry, index) =>
- [
- `fingerprint/composition.yml.patterns[${index}].evidence`,
- entry.evidence,
- ] as [string, GhostFingerprintEvidence[] | undefined],
+ [`composition.yml.patterns[${index}].evidence`, entry.evidence] as [
+ string,
+ GhostFingerprintEvidence[] | undefined,
+ ],
),
];
@@ -292,24 +195,24 @@ function verifyFingerprintCheckRefs(
const checkRefLists: Array<[string, string[] | undefined]> = [
...fingerprint.intent.principles.map(
(entry, index) =>
- [
- `fingerprint/intent.yml.principles[${index}].check_refs`,
- entry.check_refs,
- ] as [string, string[] | undefined],
+ [`intent.yml.principles[${index}].check_refs`, entry.check_refs] as [
+ string,
+ string[] | undefined,
+ ],
),
...fingerprint.intent.experience_contracts.map(
(entry, index) =>
[
- `fingerprint/intent.yml.experience_contracts[${index}].check_refs`,
+ `intent.yml.experience_contracts[${index}].check_refs`,
entry.check_refs,
] as [string, string[] | undefined],
),
...fingerprint.composition.patterns.map(
(entry, index) =>
- [
- `fingerprint/composition.yml.patterns[${index}].check_refs`,
- entry.check_refs,
- ] as [string, string[] | undefined],
+ [`composition.yml.patterns[${index}].check_refs`, entry.check_refs] as [
+ string,
+ string[] | undefined,
+ ],
),
];
@@ -345,10 +248,6 @@ function isMissingFileError(err: unknown): boolean {
);
}
-function isRemoteReference(reference: string): boolean {
- return /^https?:\/\//i.test(reference);
-}
-
function finalize(issues: VerifyFingerprintIssue[]): VerifyFingerprintReport {
return {
issues,
diff --git a/packages/ghost/src/skill-bundle/SKILL.md b/packages/ghost/src/skill-bundle/SKILL.md
index 5e3a6d08..570f4567 100644
--- a/packages/ghost/src/skill-bundle/SKILL.md
+++ b/packages/ghost/src/skill-bundle/SKILL.md
@@ -14,16 +14,14 @@ materials it draws from, and the patterns that make it feel intentional.
```text
.ghost/
- config.yml
- fingerprint/
- manifest.yml
- intent.yml
- inventory.yml
- composition.yml
- validate.yml
+ manifest.yml
+ intent.yml
+ inventory.yml
+ composition.yml
+ validate.yml
```
-`fingerprint/` is the source of truth when it is checked in. Ordinary Git
+The checked-in `.ghost/` package is the source of truth. Ordinary Git
workflow is the staging and approval boundary: uncommitted or unmerged changes
are drafts, and committed fingerprint changes are canonical for Ghost. Checks are optional
deterministic gates. Ghost is not a lifecycle manager, proposal system,
@@ -31,36 +29,34 @@ design-system registry, or screenshot archive.
Generation uses **intent + inventory + composition**:
-- `fingerprint/intent.yml` captures the intent behind the surface.
+- `intent.yml` captures the intent behind the surface.
- `inventory` points to building blocks and precedents the agent can inspect
or use, including exemplars.
-- `fingerprint/composition.yml` captures the patterns that make the surface feel
+- `composition.yml` captures the patterns that make the surface feel
intentional.
Checks and review validate output; they are not generation input.
-`fingerprint/manifest.yml` anchors the package with
+`manifest.yml` anchors the package with
`schema: ghost.fingerprint-package/v1`. Add only sections that contain real
facet content; Ghost normalizes omitted facet files or sections internally for
checks, review, emit, and stack resolution.
-Optional deterministic gates live in `fingerprint/validate.yml`.
-`.ghost/config.yml` stays outside the portable package as local routing config.
+Optional deterministic gates live in `validate.yml`.
Use `ghost signals` as a stdout-only reconnaissance helper when an agent needs
raw repo observations while authoring curated fingerprint facets.
Advanced repos may contain nested fingerprint packages such as
`apps/checkout/.ghost/`. Host wrappers may set
-`GHOST_MEMORY_DIR=` on the child `ghost` process, or pass
-`--memory-dir ` explicitly, when they need repo-local Ghost files
-outside raw `ghost`'s `.ghost` default. Ghost stays adapter-neutral: wrappers
+`GHOST_PACKAGE_DIR=` on the child `ghost` process when they need
+repo-local Ghost files outside raw `ghost`'s `.ghost` default. Ghost stays adapter-neutral: wrappers
consume JSON and map severities into their own review or check format.
## Core CLI Verbs
| Verb | Purpose |
|---|---|
-| `ghost init [dir]` | Create `.ghost/fingerprint/` with manifest, facets, and deterministic checks. |
+| `ghost init` | Create `.ghost/` with manifest, facets, and deterministic checks. |
| `ghost scan [dir] [--format json]` | Report sparse fingerprint contribution facets. |
| `ghost lint [file-or-dir]` | Validate a fingerprint package or artifact. |
| `ghost verify [dir] --root ` | Validate evidence paths, exemplar paths, and typed check refs. |
@@ -74,7 +70,7 @@ consume JSON and map severities into their own review or check format.
| Verb | Purpose |
|---|---|
-| `ghost init --scope ` / `--memory-dir ` | Create or resolve scoped/custom fingerprint packages for nested packages or host wrappers. |
+| `ghost init --scope ` / `GHOST_PACKAGE_DIR= ghost init` | Create or resolve scoped/custom fingerprint packages for nested packages or host wrappers. |
| `ghost stack [path...]` | Inspect resolved broad-to-local fingerprint stack and merged output. |
| `ghost signals [path]` | Emit raw repo signals for fingerprint authoring. |
| `ghost lint --all` / `ghost verify --all` | Validate nested stack merges. |
@@ -102,9 +98,9 @@ evidence-backed facet entries, then ask the human to curate the claims.
## Always
-- Treat checked-in `fingerprint/` facet files as the source of truth.
+- Treat checked-in Ghost package facet files as the source of truth.
- Generate from intent, inventory, and composition.
-- Run active checks from `fingerprint/validate.yml`; only active deterministic checks block.
+- Run active checks from `validate.yml`; only active deterministic checks block.
- Use local evidence as provisional when fingerprint facets are silent.
- Treat auto-drafted fingerprint edits as ordinary uncommitted draft work until
the human curates them and Git review accepts them.
@@ -112,7 +108,7 @@ evidence-backed facet entries, then ask the human to curate the claims.
- Validate with `ghost lint` and `ghost verify --root ` before declaring
fingerprint facets useful.
- Run `ghost check` for deterministic gates and `ghost review` for advisory critique.
-- Use optional config, nested stacks, and custom fingerprint dirs only when
+- Use nested stacks and custom package dirs only when
present or requested.
## When Fingerprint Facets Are Silent
diff --git a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md
index 4e3de206..358285bf 100644
--- a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md
+++ b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md
@@ -154,7 +154,7 @@ ghost check --base HEAD
```
Use ordinary Git review as the approval boundary. Uncommitted or unmerged
-fingerprint edits are drafts; checked-in `fingerprint/` core files are the
+fingerprint edits are drafts; checked-in Ghost package facet files are the
canonical package.
## Never
diff --git a/packages/ghost/src/skill-bundle/references/capture.md b/packages/ghost/src/skill-bundle/references/capture.md
index 1c421ab7..c287c69a 100644
--- a/packages/ghost/src/skill-bundle/references/capture.md
+++ b/packages/ghost/src/skill-bundle/references/capture.md
@@ -12,19 +12,17 @@ handoffs:
# Recipe: Author Ghost Fingerprint
-**Goal:** record durable product-surface composition in `.ghost/fingerprint/`.
+**Goal:** record durable product-surface composition in `.ghost/`.
If a change is uncommitted or unmerged, it is draft work. If it is checked in,
Ghost treats the fingerprint package as canonical.
```text
.ghost/
- config.yml
- fingerprint/
- manifest.yml
- intent.yml
- inventory.yml
- composition.yml
- validate.yml
+ manifest.yml
+ intent.yml
+ inventory.yml
+ composition.yml
+ validate.yml
```
`intent.yml` captures the intent behind the surface. `inventory.yml` records
@@ -65,8 +63,8 @@ ghost init
ghost scan
```
-Use `--with-config --reference ` when local routing or a
-reference UI registry/library should be recorded in `.ghost/config.yml`.
+Use `--reference ` when a reference UI registry or library
+should seed `inventory.yml`.
### 3. Auto-Draft Mode
@@ -78,7 +76,7 @@ Set up the Ghost fingerprint for this repo with auto-draft.
Auto-draft is a skill workflow, not a Ghost CLI action or flag.
-1. If `.ghost/fingerprint/manifest.yml` is missing, run `ghost init`.
+1. If `.ghost/manifest.yml` is missing, run `ghost init`.
2. Run `ghost scan --format json`.
3. Gather raw repo signals:
@@ -130,7 +128,7 @@ treating draft content as durable fingerprint guidance.
### 6. Add Checks Sparingly
-`fingerprint/validate.yml` is the executable appendix. Add only
+`validate.yml` is the executable appendix. Add only
deterministic checks with typed derivation refs:
```yaml
@@ -156,7 +154,7 @@ packages exist.
## Never
-- Never describe any file outside `.ghost/fingerprint/` as canonical package input.
+- Never describe any file outside `.ghost/` as canonical package input.
- Never treat raw `ghost signals` output as canonical inventory.
- Never treat auto-draft as a CLI feature or a replacement for human curation.
- Never invent surface-composition obligations absent from evidence or human direction.
diff --git a/packages/ghost/src/skill-bundle/references/compare.md b/packages/ghost/src/skill-bundle/references/compare.md
index 214bdda7..71926534 100644
--- a/packages/ghost/src/skill-bundle/references/compare.md
+++ b/packages/ghost/src/skill-bundle/references/compare.md
@@ -23,7 +23,7 @@ handoffs:
ghost compare a/.ghost b/.ghost
-Output: distance (0 = identical, 1 = unrelated) and per-dimension deltas. Package inputs use canonical `.ghost/fingerprint/` packages.
+Output: distance (0 = identical, 1 = unrelated) and per-dimension deltas. Package inputs use canonical `.ghost/` packages.
Flags:
- `--temporal` — add drift velocity, trajectory, and ack bounds (reads `.ghost/history.jsonl`)
diff --git a/packages/ghost/src/skill-bundle/references/patterns.md b/packages/ghost/src/skill-bundle/references/patterns.md
index 1f7ef9d8..86e3e4ac 100644
--- a/packages/ghost/src/skill-bundle/references/patterns.md
+++ b/packages/ghost/src/skill-bundle/references/patterns.md
@@ -1,6 +1,6 @@
---
name: patterns
-description: Author surface-composition patterns inside .ghost/fingerprint/composition.yml.
+description: Author surface-composition patterns inside .ghost/composition.yml.
handoffs:
- label: Verify fingerprint package
command: ghost verify .ghost --root .
@@ -9,7 +9,7 @@ handoffs:
# Recipe: Author Fingerprint Patterns
-**Goal:** write useful `patterns[]` entries in `.ghost/fingerprint/composition.yml`.
+**Goal:** write useful `patterns[]` entries in `.ghost/composition.yml`.
Patterns are durable surface-composition guidance. They may describe rules,
layouts, structures, flows, states, content, behavior, or visual arrangements.
@@ -72,7 +72,7 @@ Allowed `kind` values:
- Put obligations that affect failure, disclosure, recovery, or trust in
`intent.experience_contracts`, not only `composition.patterns`.
- Put broad surface intent in `intent.principles`.
-- Add `check_refs` only when a deterministic check exists in `fingerprint/validate.yml`.
+- Add `check_refs` only when a deterministic check exists in `validate.yml`.
## Validate
diff --git a/packages/ghost/src/skill-bundle/references/recall.md b/packages/ghost/src/skill-bundle/references/recall.md
index 5174ce6d..890529d7 100644
--- a/packages/ghost/src/skill-bundle/references/recall.md
+++ b/packages/ghost/src/skill-bundle/references/recall.md
@@ -5,7 +5,7 @@ description: Recall applicable Ghost fingerprint facets for a task or file path.
# Recipe: Recall Ghost Fingerprint
-1. Read checked-in `fingerprint/intent.yml`, `fingerprint/inventory.yml`, and `fingerprint/composition.yml` entries.
+1. Read checked-in `intent.yml`, `inventory.yml`, and `composition.yml` entries.
2. Select relevant intent, inventory exemplars, composition patterns, and active
checks.
3. Use `ghost stack ` when the repo has nested fingerprint packages.
diff --git a/packages/ghost/src/skill-bundle/references/review.md b/packages/ghost/src/skill-bundle/references/review.md
index e0fed650..088de074 100644
--- a/packages/ghost/src/skill-bundle/references/review.md
+++ b/packages/ghost/src/skill-bundle/references/review.md
@@ -16,7 +16,7 @@ ghost check --base [
```
Fix deterministic failures first. These come from active
-`fingerprint/validate.yml` rules and are the only blocking findings.
+`validate.yml` rules and are the only blocking findings.
## 2. Build Advisory Context
@@ -27,7 +27,7 @@ ghost review --base ][
Use the emitted packet as context. It includes:
- selected context hits: fingerprint refs, why they matched, suggested reads, omissions, and gaps
-- active checks from `fingerprint/validate.yml`
+- active checks from `validate.yml`
- optional stack or config context when present or requested
- the diff
diff --git a/packages/ghost/src/skill-bundle/references/schema.md b/packages/ghost/src/skill-bundle/references/schema.md
index 71d62d7e..8f62e1c0 100644
--- a/packages/ghost/src/skill-bundle/references/schema.md
+++ b/packages/ghost/src/skill-bundle/references/schema.md
@@ -4,16 +4,14 @@ Canonical package:
```text
.ghost/
- config.yml optional local routing
- fingerprint/
- manifest.yml ghost.fingerprint-package/v1
- intent.yml core surface intent
- inventory.yml core material and source links
- composition.yml core patterns
- validate.yml optional ghost.validate/v1 gates
+ manifest.yml ghost.fingerprint-package/v1
+ intent.yml core surface intent
+ inventory.yml core material and source links
+ composition.yml core patterns
+ validate.yml optional ghost.validate/v1 gates
```
-Git is the approval boundary: checked-in `fingerprint/` core files are
+Git is the approval boundary: checked-in Ghost package facet files are
canonical, and uncommitted or unmerged edits are draft work.
`manifest.yml`:
@@ -37,6 +35,6 @@ Use these typed refs:
`inventory.sources[].kind` may be `registry`, `file`, `url`, or `package`.
-`fingerprint/validate.yml` remains deterministic only. Ref-backed
+`validate.yml` remains deterministic only. Ref-backed
checks are preferred; missing or unresolved derivation refs lint as warnings.
Inventory refs can support a check but do not establish surface guidance alone.
diff --git a/packages/ghost/src/skill-bundle/references/voice.md b/packages/ghost/src/skill-bundle/references/voice.md
index 74400e23..96d37c1e 100644
--- a/packages/ghost/src/skill-bundle/references/voice.md
+++ b/packages/ghost/src/skill-bundle/references/voice.md
@@ -26,7 +26,7 @@ Language maps onto the existing facets; do not invent new schema. See
and omits it elsewhere. Unscoped entries reach agents only through ref
edges or the global fallback.
4. Promote only the mechanically detectable subset into
- `fingerprint/validate.yml`:
+ `validate.yml`:
- Absolute rules (banned phrases, required boilerplate) become
`forbidden-regex` or `required-regex` checks with `status: active`.
- Recommendations become `status: proposed` so `ghost review` surfaces
diff --git a/packages/ghost/test/cli.test.ts b/packages/ghost/test/cli.test.ts
index 422f33eb..e6efa683 100644
--- a/packages/ghost/test/cli.test.ts
+++ b/packages/ghost/test/cli.test.ts
@@ -187,7 +187,7 @@ describe("ghost CLI", () => {
expect(result.stdout).toContain("Maintenance/legacy");
for (const command of [
"lint [file]",
- "init [dir]",
+ "init",
"verify [dir]",
"scan [dir]",
"stack [paths...]",
@@ -286,7 +286,7 @@ describe("ghost CLI", () => {
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
@@ -348,7 +348,7 @@ describe("ghost CLI", () => {
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
@@ -404,15 +404,7 @@ describe("ghost CLI", () => {
checks: false,
});
await writeFile(
- join(
- dir,
- "node_modules",
- "@scope",
- "tracked",
- ".ghost",
- "fingerprint",
- "manifest.yml",
- ),
+ join(dir, "node_modules", "@scope", "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
@@ -431,17 +423,13 @@ describe("ghost CLI", () => {
expect(report.overall.verdict).toBe("covered");
});
- it("reports design-loop status as disabled by default", async () => {
+ it("omits removed design-loop status by default", async () => {
const result = await runCli(["drift", "status", "--format", "json"], dir);
expect(result.code).toBe(0);
const status = JSON.parse(result.stdout);
expect(status.schema).toBe("ghost.drift.status/v1");
- expect(status.designLoop).toEqual({
- enabled: false,
- mode: "off",
- source: "default",
- });
+ expect(status.designLoop).toBeUndefined();
});
it("runs the Ghost-owned drift check contract through the stance ledger", async () => {
@@ -465,11 +453,7 @@ describe("ghost CLI", () => {
expect(result.code).toBe(0);
const report = JSON.parse(result.stdout);
expect(report.schema).toBe("ghost.drift.check/v1");
- expect(report.designLoop).toEqual({
- enabled: false,
- mode: "off",
- source: "default",
- });
+ expect(report.designLoop).toBeUndefined();
expect(report.trackedFingerprintId).toBe("tracked");
expect(report.localFingerprintId).toBe("local");
expect(report.overall.verdict).toBe("covered");
@@ -531,7 +515,7 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
@@ -597,7 +581,7 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeCoveredSyncManifest(dir, { tracked: "tracked/.ghost" });
@@ -614,7 +598,7 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
@@ -635,7 +619,7 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
@@ -655,7 +639,7 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeCoveredSyncManifest(dir, { tracked: "tracked/.ghost" });
@@ -665,7 +649,7 @@ composition_patterns: []
"drift",
"check",
"--tracked",
- "tracked/.ghost/fingerprint/manifest.yml",
+ "tracked/.ghost/manifest.yml",
"--format",
"json",
],
@@ -683,11 +667,11 @@ composition_patterns: []
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeCheckPackage(join(dir, "other"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
- join(dir, "other", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "other", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: other\n",
);
await writeCoveredSyncManifest(dir, { tracked: "tracked/.ghost" });
@@ -707,11 +691,11 @@ composition_patterns: []
await writeCheckPackage(dir, { checks: false });
await writeCheckPackage(join(dir, "tracked"), { checks: false });
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
- join(dir, ".ghost", "fingerprint", "intent.yml"),
+ join(dir, ".ghost", "intent.yml"),
`summary:
product: Cash iOS
situations: []
@@ -762,11 +746,11 @@ composition:
fingerprintRaw,
);
await writeFile(
- join(dir, "tracked", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "tracked", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: tracked\n",
);
await writeFile(
- join(dir, ".ghost", "fingerprint", "inventory.yml"),
+ join(dir, ".ghost", "inventory.yml"),
`topology:
surface_types: [native-feature, digest-only-change]
building_blocks: {}
@@ -822,58 +806,6 @@ sources: []
expect(report.dimensions.spacing.verdict).toBe("uncovered");
});
- it("keeps advisory design-loop drift non-blocking", async () => {
- await mkdir(join(dir, ".ghost"), { recursive: true });
- await writeFile(
- join(dir, ".ghost", "config.yml"),
- `schema: ghost.config/v1
-targets: []
-libraries: []
-design_loop:
- enabled: true
- mode: advisory
-`,
- );
- await writeFile(
- join(dir, ".ghost", "fingerprint.md"),
- fingerprintWithId("local"),
- );
- await writeFile(
- join(dir, "tracked.fingerprint.md"),
- fingerprintWithId("tracked"),
- );
- await runCli(["track", "tracked.fingerprint.md"], dir);
- await writeFile(
- join(dir, ".ghost", "fingerprint.md"),
- fingerprintWithId("local").replace(
- "spacing: { scale: [4, 8, 16], baseUnit: 4, regularity: 1 }",
- "spacing: { scale: [2, 3, 5, 7, 11, 13], baseUnit: 2, regularity: 0.1 }",
- ),
- );
-
- const result = await runCli(
- [
- "drift",
- "check",
- "--tracked",
- "tracked.fingerprint.md",
- "--format",
- "json",
- ],
- dir,
- );
-
- expect(result.code).toBe(0);
- const report = JSON.parse(result.stdout);
- expect(report.designLoop).toEqual({
- enabled: true,
- mode: "advisory",
- source: "config",
- });
- expect(report.overall.verdict).toBe("uncovered");
- expect(report.dimensions.spacing.verdict).toBe("uncovered");
- });
-
it("initializes the default fingerprint package without cache", async () => {
const init = await runCli(["init", "--format", "json"], dir);
const scan = await runCli(["scan", "--format", "json"], dir);
@@ -888,16 +820,15 @@ design_loop:
"checks",
"composition",
"dir",
- "fingerprintDir",
"intent",
"inventory",
"manifest",
]);
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "manifest.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
).resolves.toContain("schema: ghost.fingerprint-package/v1");
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "validate.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "validate.yml"), "utf-8"),
).resolves.toContain("schema: ghost.validate/v1");
const status = JSON.parse(scan.stdout);
expect(status.cache).toBeUndefined();
@@ -945,11 +876,11 @@ design_loop:
]);
expect(out.commands).toEqual(["ghost init --scope apps/checkout"]);
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "manifest.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
).resolves.toContain("ghost.fingerprint-package/v1");
await expect(
readFile(
- join(dir, "apps", "checkout", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "apps", "checkout", ".ghost", "manifest.yml"),
"utf-8",
),
).rejects.toThrow();
@@ -1107,7 +1038,7 @@ design_loop:
]);
await expect(
readFile(
- join(dir, "apps", "checkout", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "apps", "checkout", ".ghost", "manifest.yml"),
"utf-8",
),
).resolves.toContain("ghost.fingerprint-package/v1");
@@ -1143,7 +1074,7 @@ design_loop:
]);
});
- it("init --monorepo applies --memory-dir to root and child packages", async () => {
+ it("init --monorepo applies GHOST_PACKAGE_DIR to root and child packages", async () => {
await mkdir(join(dir, "apps", "checkout"), { recursive: true });
await writeFile(
join(dir, "package.json"),
@@ -1155,47 +1086,29 @@ design_loop:
);
const result = await runCli(
- [
- "init",
- "--monorepo",
- "--apply",
- "--memory-dir",
- ".design/memory",
- "--format",
- "json",
- ],
+ ["init", "--monorepo", "--apply", "--format", "json"],
dir,
+ { env: { GHOST_PACKAGE_DIR: ".design/memory" } },
);
expect(result.code).toBe(0);
const out = JSON.parse(result.stdout);
- expect(out.memoryDir).toBe(".design/memory");
+ expect(out.ghostDir).toBe(".design/memory");
expect(out.commands).toEqual([
- "ghost init --scope apps/checkout --memory-dir .design/memory",
+ "GHOST_PACKAGE_DIR=.design/memory ghost init --scope apps/checkout",
]);
await expect(
- readFile(
- join(dir, ".design", "memory", "fingerprint", "manifest.yml"),
- "utf-8",
- ),
+ readFile(join(dir, ".design", "memory", "manifest.yml"), "utf-8"),
).resolves.toContain("ghost.fingerprint-package/v1");
await expect(
readFile(
- join(
- dir,
- "apps",
- "checkout",
- ".design",
- "memory",
- "fingerprint",
- "manifest.yml",
- ),
+ join(dir, "apps", "checkout", ".design", "memory", "manifest.yml"),
"utf-8",
),
).resolves.toContain("ghost.fingerprint-package/v1");
});
- it("init --monorepo uses GHOST_MEMORY_DIR for root and child packages", async () => {
+ it("init --monorepo uses GHOST_PACKAGE_DIR for root and child packages", async () => {
await mkdir(join(dir, "apps", "checkout"), { recursive: true });
await writeFile(
join(dir, "package.json"),
@@ -1209,48 +1122,40 @@ design_loop:
const result = await runCli(
["init", "--monorepo", "--apply", "--format", "json"],
dir,
- { env: { GHOST_MEMORY_DIR: ".agents/ghost" } },
+ { env: { GHOST_PACKAGE_DIR: ".agents/ghost" } },
);
expect(result.code).toBe(0);
const out = JSON.parse(result.stdout);
- expect(out.memoryDir).toBe(".agents/ghost");
+ expect(out.ghostDir).toBe(".agents/ghost");
expect(out.commands).toEqual([
- "ghost init --scope apps/checkout --memory-dir .agents/ghost",
+ "GHOST_PACKAGE_DIR=.agents/ghost ghost init --scope apps/checkout",
]);
await expect(
- readFile(
- join(dir, ".agents", "ghost", "fingerprint", "manifest.yml"),
- "utf-8",
- ),
+ readFile(join(dir, ".agents", "ghost", "manifest.yml"), "utf-8"),
).resolves.toContain("ghost.fingerprint-package/v1");
await expect(
readFile(
- join(
- dir,
- "apps",
- "checkout",
- ".agents",
- "ghost",
- "fingerprint",
- "manifest.yml",
- ),
+ join(dir, "apps", "checkout", ".agents", "ghost", "manifest.yml"),
"utf-8",
),
).resolves.toContain("ghost.fingerprint-package/v1");
});
it("init --monorepo rejects exact scope and dir combinations", async () => {
- const withDir = await runCli(["init", "custom-dir", "--monorepo"], dir);
+ const withPackage = await runCli(
+ ["init", "--package", "custom-dir", "--monorepo"],
+ dir,
+ );
const withScope = await runCli(
["init", "--scope", "apps/checkout", "--monorepo"],
dir,
);
const withApplyOnly = await runCli(["init", "--apply"], dir);
- expect(withDir.code).toBe(2);
- expect(withDir.stderr).toContain(
- "use either init [dir] or init --monorepo",
+ expect(withPackage.code).toBe(2);
+ expect(withPackage.stderr).toContain(
+ "use either init --package or init --monorepo",
);
expect(withScope.code).toBe(2);
expect(withScope.stderr).toContain(
@@ -1262,9 +1167,9 @@ design_loop:
);
});
- it("uses GHOST_MEMORY_DIR as the default fingerprint package directory for init", async () => {
+ it("uses GHOST_PACKAGE_DIR as the default fingerprint package directory for init", async () => {
const init = await runCli(["init", "--format", "json"], dir, {
- env: { GHOST_MEMORY_DIR: ".agents/ghost" },
+ env: { GHOST_PACKAGE_DIR: ".agents/ghost" },
});
expect(init.code).toBe(0);
@@ -1273,20 +1178,21 @@ design_loop:
await realpath(join(dir, ".agents", "ghost")),
);
await expect(
- readFile(
- join(dir, ".agents", "ghost", "fingerprint", "manifest.yml"),
- "utf-8",
- ),
+ readFile(join(dir, ".agents", "ghost", "manifest.yml"), "utf-8"),
).resolves.toContain("schema: ghost.fingerprint-package/v1");
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "manifest.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
).rejects.toThrow();
});
- it("keeps explicit init directory positional args ahead of GHOST_MEMORY_DIR", async () => {
- const init = await runCli(["init", "custom-dir", "--format", "json"], dir, {
- env: { GHOST_MEMORY_DIR: ".agents/ghost" },
- });
+ it("keeps exact init package args ahead of invalid GHOST_PACKAGE_DIR", async () => {
+ const init = await runCli(
+ ["init", "--package", "custom-dir", "--format", "json"],
+ dir,
+ {
+ env: { GHOST_PACKAGE_DIR: "../outside" },
+ },
+ );
expect(init.code).toBe(0);
const initOutput = JSON.parse(init.stdout);
@@ -1294,30 +1200,43 @@ design_loop:
await realpath(join(dir, "custom-dir")),
);
await expect(
- readFile(join(dir, "custom-dir", "fingerprint", "manifest.yml"), "utf-8"),
+ readFile(join(dir, "custom-dir", "manifest.yml"), "utf-8"),
).resolves.toContain("schema: ghost.fingerprint-package/v1");
await expect(
- readFile(
- join(dir, ".agents", "ghost", "fingerprint", "manifest.yml"),
- "utf-8",
- ),
+ readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
).rejects.toThrow();
});
- it("rejects invalid GHOST_MEMORY_DIR with memory-dir validation errors", async () => {
+ it("rejects removed positional init package args with a migration hint", async () => {
+ const init = await runCli(["init", "custom-dir", "--format", "json"], dir);
+
+ expect(init.code).toBe(2);
+ expect(init.stderr).toContain(
+ "ghost init no longer accepts a positional directory",
+ );
+ expect(init.stderr).toContain("--package ");
+ await expect(
+ readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
+ ).rejects.toThrow();
+ await expect(
+ readFile(join(dir, "custom-dir", "manifest.yml"), "utf-8"),
+ ).rejects.toThrow();
+ });
+
+ it("rejects invalid GHOST_PACKAGE_DIR with env validation errors", async () => {
const init = await runCli(["init"], dir, {
- env: { GHOST_MEMORY_DIR: "../outside" },
+ env: { GHOST_PACKAGE_DIR: "../outside" },
});
expect(init.code).toBe(2);
- expect(init.stderr).toContain("--memory-dir must not contain");
+ expect(init.stderr).toContain("GHOST_PACKAGE_DIR must not contain");
});
- it("uses GHOST_MEMORY_DIR as the default package lookup for scan", async () => {
- await runCli(["init", ".agents/ghost"], dir);
+ it("uses GHOST_PACKAGE_DIR as the default package lookup for scan", async () => {
+ await runCli(["init", "--package", ".agents/ghost"], dir);
const scan = await runCli(["scan", "--format", "json"], dir, {
- env: { GHOST_MEMORY_DIR: ".agents/ghost" },
+ env: { GHOST_PACKAGE_DIR: ".agents/ghost" },
});
expect(scan.code).toBe(0);
@@ -1331,7 +1250,7 @@ design_loop:
it("refuses to overwrite existing fingerprint files unless forced", async () => {
await runCli(["init"], dir);
await writeFile(
- join(dir, ".ghost", "fingerprint", "intent.yml"),
+ join(dir, ".ghost", "intent.yml"),
"summary:\n product: Curated Surface\n",
);
@@ -1342,21 +1261,21 @@ design_loop:
"Refusing to overwrite existing Ghost fingerprint file(s)",
);
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "intent.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "intent.yml"), "utf-8"),
).resolves.toContain("Curated Surface");
const forced = await runCli(["init", "--force"], dir);
expect(forced.code).toBe(0);
await expect(
- readFile(join(dir, ".ghost", "fingerprint", "intent.yml"), "utf-8"),
+ readFile(join(dir, ".ghost", "intent.yml"), "utf-8"),
).resolves.toContain("summary: {}");
});
it("warns for checks grounded in omitted sparse fingerprint refs", async () => {
await runCli(["init"], dir);
await writeFile(
- join(dir, ".ghost", "fingerprint", "validate.yml"),
+ join(dir, ".ghost", "validate.yml"),
`schema: ghost.validate/v1
id: local
checks:
@@ -1386,19 +1305,19 @@ checks:
expect(report.issues[0]).toMatchObject({
severity: "warning",
rule: "check-grounding-unknown",
- path: "fingerprint/validate.yml.checks[0].derivation.intent[0]",
+ path: "validate.yml.checks[0].derivation.intent[0]",
});
});
it("validates standalone validate.yml derivation refs with a valid sibling fingerprint", async () => {
await writeCheckPackage(dir, { checks: false });
await writeFile(
- join(dir, ".ghost", "fingerprint", "validate.yml"),
+ join(dir, ".ghost", "validate.yml"),
checksFileWithDerivation("intent.principle:not-recorded"),
);
const lint = await runCli(
- ["lint", ".ghost/fingerprint/validate.yml", "--format", "json"],
+ ["lint", ".ghost/validate.yml", "--format", "json"],
dir,
);
@@ -1438,18 +1357,15 @@ checks:
});
it("keeps standalone validate.yml lint non-blocking when the sibling fingerprint is invalid", async () => {
- await mkdir(join(dir, ".ghost", "fingerprint"), { recursive: true });
- await writeFile(
- join(dir, ".ghost", "fingerprint", "manifest.yml"),
- "not: draft\n",
- );
+ await mkdir(join(dir, ".ghost"), { recursive: true });
+ await writeFile(join(dir, ".ghost", "manifest.yml"), "not: draft\n");
await writeFile(
- join(dir, ".ghost", "fingerprint", "validate.yml"),
+ join(dir, ".ghost", "validate.yml"),
checksFileWithDerivation("intent.principle:tokenized-ui-color"),
);
const lint = await runCli(
- ["lint", ".ghost/fingerprint/validate.yml", "--format", "json"],
+ ["lint", ".ghost/validate.yml", "--format", "json"],
dir,
);
@@ -1462,6 +1378,36 @@ checks:
});
});
+ it("does not guess arbitrary YAML files are validate.yml", async () => {
+ await writeFile(join(dir, "workflow.yml"), "name: ci\non: push\n");
+
+ const lint = await runCli(
+ ["lint", "workflow.yml", "--format", "json"],
+ dir,
+ );
+
+ expect(lint.code).toBe(1);
+ expect(JSON.parse(lint.stdout).issues[0]).toMatchObject({
+ severity: "error",
+ rule: "unsupported-yaml",
+ });
+ });
+
+ it("detects Ghost YAML artifacts by schema when the filename is arbitrary", async () => {
+ await writeFile(
+ join(dir, "package-anchor.yml"),
+ "schema: ghost.fingerprint-package/v1\nid: local\n",
+ );
+
+ const lint = await runCli(
+ ["lint", "package-anchor.yml", "--format", "json"],
+ dir,
+ );
+
+ expect(lint.code).toBe(0);
+ expect(JSON.parse(lint.stdout).errors).toBe(0);
+ });
+
it("initializes a bundle and reports fingerprint capture state as json", async () => {
const init = await runCli(["init"], dir);
const scan = await runCli(["scan", "--format", "json"], dir);
@@ -1476,10 +1422,7 @@ checks:
expect(init.stdout).not.toContain("cache/:");
expect(init.stdout).not.toContain("memory/intent.md:");
expect(
- await readFile(
- join(dir, ".ghost", "fingerprint", "manifest.yml"),
- "utf-8",
- ),
+ await readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"),
).toContain("schema: ghost.fingerprint-package/v1");
expect(scan.code).toBe(0);
const status = JSON.parse(scan.stdout);
@@ -1498,7 +1441,7 @@ checks:
"composition",
"validate",
]);
- expect(scanHuman.stdout).toContain("fingerprint dir:");
+ expect(scanHuman.stdout).toContain("package dir:");
expect(scanHuman.stdout).toContain("contribution: empty");
expect(scanHuman.stdout).toContain("intent: empty (0)");
expect(scanHuman.stdout).toContain("validate: empty (0)");
@@ -1513,16 +1456,9 @@ checks:
);
});
- it("initializes a blank product scaffold with config and reference library wiring", async () => {
+ it("initializes a blank product scaffold with reference inventory wiring", async () => {
const init = await runCli(
- [
- "init",
- "--with-config",
- "--reference",
- "packages/ghost-ui/.ghost",
- "--format",
- "json",
- ],
+ ["init", "--reference", "packages/ghost-ui/.ghost", "--format", "json"],
dir,
);
const scan = await runCli(["scan", "--format", "json"], dir);
@@ -1530,21 +1466,11 @@ checks:
await mkdir(join(dir, "packages", "ghost-ui", ".ghost"), {
recursive: true,
});
- await mkdir(join(dir, "packages", "ghost-ui", ".ghost", "fingerprint"), {
- recursive: true,
- });
await mkdir(join(dir, "packages", "ghost-ui", "public", "r"), {
recursive: true,
});
await writeFile(
- join(
- dir,
- "packages",
- "ghost-ui",
- ".ghost",
- "fingerprint",
- "manifest.yml",
- ),
+ join(dir, "packages", "ghost-ui", ".ghost", "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: ghost-ui\n",
);
await writeFile(
@@ -1556,15 +1482,9 @@ checks:
expect(init.code).toBe(0);
const initOutput = JSON.parse(init.stdout);
expect(initOutput.cache).toBeUndefined();
- expect(await realpath(initOutput.config)).toBe(
- await realpath(join(dir, ".ghost", "config.yml")),
- );
const fingerprint = parseYaml(
- await readFile(
- join(dir, ".ghost", "fingerprint", "inventory.yml"),
- "utf-8",
- ),
+ await readFile(join(dir, ".ghost", "inventory.yml"), "utf-8"),
) as Record;
expect(fingerprint).not.toHaveProperty("implementation_vocabulary");
expect(fingerprint).not.toHaveProperty("patterns");
@@ -1580,26 +1500,12 @@ checks:
},
],
});
-
- const config = parseYaml(
- await readFile(join(dir, ".ghost", "config.yml"), "utf-8"),
- ) as Record;
- expect(config).toMatchObject({
- schema: "ghost.config/v1",
- design_loop: { enabled: false, mode: "off" },
- targets: [{ id: "product", platform: "web", roots: [] }],
- libraries: [
- {
- id: "ghost-ui",
- role: "primary-ui-registry",
- source: "registry:packages/ghost-ui/public/r/registry.json",
- fingerprint: "packages/ghost-ui/.ghost/fingerprint/manifest.yml",
- },
- ],
- });
+ await expect(
+ readFile(join(dir, ".ghost", "config.yml"), "utf-8"),
+ ).rejects.toThrow();
const status = JSON.parse(scan.stdout);
- expect(status.config.state).toBe("present");
+ expect(status.config).toBeUndefined();
expect(status.contribution.state).toBe("contributing");
expect(status.contribution.contributing_facets).toEqual(["inventory"]);
expect(status.contribution.absent_facets).toEqual([]);
@@ -1610,7 +1516,7 @@ checks:
]);
const signalsOutput = JSON.parse(signals.stdout);
- expect(signalsOutput.config.libraries[0].id).toBe("ghost-ui");
+ expect(signalsOutput.config).toBeUndefined();
expect(verify.code).toBe(0);
});
@@ -1764,7 +1670,7 @@ checks:
"utf-8",
);
expect(emittedReviewCommand).toContain(
- ".ghost/fingerprint/intent.yml`, `.ghost/fingerprint/inventory.yml`, and `.ghost/fingerprint/composition.yml",
+ ".ghost/intent.yml`, `.ghost/inventory.yml`, and `.ghost/composition.yml",
);
expect(emittedReviewCommand).toContain("Exemplars");
expect(emittedReviewCommand).toContain("lending-tokenized-screen");
@@ -1897,25 +1803,38 @@ checks:
expect(json.brief).toContain("## Context Hits");
});
- it("ignores memory-dir when gathering Relay context from an exact package", async () => {
- await writeCheckPackage(dir);
+ it("ignores GHOST_PACKAGE_DIR when gathering Relay context from an exact package", async () => {
+ await writeSplitFingerprintPackage(
+ join(dir, "product-surface"),
+ `schema: ghost.fingerprint/v1
+intent:
+ summary:
+ product: Product Surface
+ situations: []
+ principles: []
+ experience_contracts: []
+inventory:
+ topology: {}
+ exemplars: []
+ building_blocks: {}
+composition:
+ patterns: []
+`,
+ );
const result = await runCli(
- [
- "relay",
- "gather",
- "--package",
- ".ghost",
- "--memory-dir",
- "../not-used",
- "--format",
- "json",
- ],
+ ["relay", "gather", "--package", "product-surface", "--format", "json"],
dir,
+ { env: { GHOST_PACKAGE_DIR: "elsewhere" } },
);
expect(result.code).toBe(0);
- expect(JSON.parse(result.stdout).source.kind).toBe("package");
+ const json = JSON.parse(result.stdout);
+ const expectedPackageDir = await realpath(join(dir, "product-surface"));
+ expect(json.source.kind).toBe("package");
+ expect(json.source.packageDir).toBe(expectedPackageDir);
+ expect(json).not.toHaveProperty("ghostDir");
+ expect(json.stackDirs).toEqual([expectedPackageDir]);
});
it("rejects invalid Relay output formats", async () => {
@@ -1941,7 +1860,7 @@ checks:
expect.arrayContaining([
expect.objectContaining({
rule: "fingerprint-exemplar-unreachable",
- path: "fingerprint/inventory.yml.exemplars[0].path",
+ path: "inventory.yml.exemplars[0].path",
}),
]),
);
@@ -2177,40 +2096,6 @@ checks:
);
});
- it("review includes config reference registries when config.yml is present", async () => {
- await writeCheckPackage(dir);
- await writeFile(
- join(dir, ".ghost", "config.yml"),
- `schema: ghost.config/v1
-targets:
- - id: product
- platform: web
- roots: []
-libraries:
- - id: ghost-ui
- role: primary-ui-registry
- source: registry:packages/ghost-ui/public/r/registry.json
- fingerprint: packages/ghost-ui/.ghost/fingerprint/manifest.yml
-`,
- );
- await writeFile(
- join(dir, "change.patch"),
- lendingPatch("let color = CashTheme.primary"),
- );
-
- const result = await runCli(
- ["review", "--diff", "change.patch", "--format", "json"],
- dir,
- );
-
- expect(result.code).toBe(0);
- const packet = JSON.parse(result.stdout);
- expect(packet.config.libraries[0]).toMatchObject({
- id: "ghost-ui",
- role: "primary-ui-registry",
- });
- });
-
it("review omits removed memory fields", async () => {
await writeCheckPackage(dir);
await writeFile(
@@ -2272,7 +2157,7 @@ libraries:
const report = JSON.parse(result.stdout);
expect(report.schema).toBe("ghost.check-report/v1");
expect(report.result).toBe("pass");
- expect(report.fingerprint_dir).toBe(".ghost");
+ expect(report.ghost_dir).toBe(".ghost");
expect(report.memory_dir).toBeUndefined();
expect(report.stacks[0].memory_dir).toBeUndefined();
expect(report.stacks[0].stack_dirs).toHaveLength(2);
@@ -2318,7 +2203,7 @@ libraries:
expect(report.stacks).toBeUndefined();
});
- it("resolves stack checks from a custom fingerprint directory", async () => {
+ it("resolves stack checks from a custom package directory", async () => {
await writeNestedCheckPackage(dir, ".design/memory");
await writeFile(
join(dir, "change.patch"),
@@ -2326,24 +2211,17 @@ libraries:
);
const result = await runCli(
- [
- "check",
- "--diff",
- "change.patch",
- "--memory-dir",
- ".design/memory",
- "--format",
- "json",
- ],
+ ["check", "--diff", "change.patch", "--format", "json"],
dir,
+ { env: { GHOST_PACKAGE_DIR: ".design/memory" } },
);
expect(result.code).toBe(0);
const report = JSON.parse(result.stdout);
- expect(report.fingerprint_dir).toBe(".design/memory");
+ expect(report.ghost_dir).toBe(".design/memory");
expect(report.memory_dir).toBeUndefined();
expect(report.stacks[0]).toMatchObject({
- fingerprint_dir: ".design/memory",
+ ghost_dir: ".design/memory",
changed_files: ["apps/checkout/review/page.tsx"],
});
expect(report.stacks[0].memory_dir).toBeUndefined();
@@ -2371,7 +2249,7 @@ libraries:
expect(result.code).toBe(0);
const packet = JSON.parse(result.stdout);
expect(packet.stacks).toHaveLength(2);
- expect(packet.stacks[0].fingerprint_dir).toBe(".ghost");
+ expect(packet.stacks[0].ghost_dir).toBe(".ghost");
expect(packet.stacks[0].memory_dir).toBeUndefined();
expect(packet.stacks[0].merged.fingerprint.intent.summary.product).toBe(
"Checkout",
@@ -2391,7 +2269,7 @@ libraries:
expect(result.code).toBe(0);
const stacks = JSON.parse(result.stdout);
expect(stacks[0].stack).toHaveLength(2);
- expect(stacks[0].fingerprint_dir).toBe(".ghost");
+ expect(stacks[0].ghost_dir).toBe(".ghost");
expect(stacks[0].memory_dir).toBeUndefined();
expect(stacks[0].stack[0].memory_dir).toBeUndefined();
expect(stacks[0].merged.fingerprint.intent.summary.product).toBe(
@@ -2399,14 +2277,13 @@ libraries:
);
});
- it("rejects unsafe memory directory overrides", async () => {
- const result = await runCli(
- ["stack", ".", "--memory-dir", "../outside"],
- dir,
- );
+ it("rejects unsafe package directory env overrides", async () => {
+ const result = await runCli(["stack", "."], dir, {
+ env: { GHOST_PACKAGE_DIR: "../outside" },
+ });
expect(result.code).toBe(2);
- expect(result.stderr).toContain("--memory-dir must not contain");
+ expect(result.stderr).toContain("GHOST_PACKAGE_DIR must not contain");
});
it("emit review-command resolves merged fingerprint stack for --path", async () => {
@@ -2441,30 +2318,23 @@ libraries:
await realpath(join(dir, "apps", "checkout", ".ghost")),
);
const manifest = await readFile(
- join(dir, "apps", "checkout", ".ghost", "fingerprint", "manifest.yml"),
+ join(dir, "apps", "checkout", ".ghost", "manifest.yml"),
"utf-8",
);
expect(manifest).toContain("ghost.fingerprint-package/v1");
const intent = await readFile(
- join(dir, "apps", "checkout", ".ghost", "fingerprint", "intent.yml"),
+ join(dir, "apps", "checkout", ".ghost", "intent.yml"),
"utf-8",
);
expect(intent).not.toContain("review_policy");
expect(intent).not.toContain("proposal");
});
- it("init --scope creates a nested package under a custom fingerprint directory", async () => {
+ it("init --scope creates a nested package under a custom package directory", async () => {
const result = await runCli(
- [
- "init",
- "--scope",
- "apps/checkout",
- "--memory-dir",
- ".design/memory",
- "--format",
- "json",
- ],
+ ["init", "--scope", "apps/checkout", "--format", "json"],
dir,
+ { env: { GHOST_PACKAGE_DIR: ".design/memory" } },
);
expect(result.code).toBe(0);
@@ -2474,15 +2344,7 @@ libraries:
);
expect(
await readFile(
- join(
- dir,
- "apps",
- "checkout",
- ".design",
- "memory",
- "fingerprint",
- "manifest.yml",
- ),
+ join(dir, "apps", "checkout", ".design", "memory", "manifest.yml"),
"utf-8",
),
).toContain("ghost.fingerprint-package/v1");
@@ -2506,24 +2368,16 @@ libraries:
it("lint, verify, and scan discover nested custom fingerprint directories", async () => {
await writeNestedCheckPackage(dir, ".design/memory");
- const lint = await runCli(
- ["lint", "--all", "--memory-dir", ".design/memory", "--format", "json"],
- dir,
- );
- const verify = await runCli(
- ["verify", "--all", "--memory-dir", ".design/memory", "--format", "json"],
- dir,
- );
+ const lint = await runCli(["lint", "--all", "--format", "json"], dir, {
+ env: { GHOST_PACKAGE_DIR: ".design/memory" },
+ });
+ const verify = await runCli(["verify", "--all", "--format", "json"], dir, {
+ env: { GHOST_PACKAGE_DIR: ".design/memory" },
+ });
const scan = await runCli(
- [
- "scan",
- "--include-nested",
- "--memory-dir",
- ".design/memory",
- "--format",
- "json",
- ],
+ ["scan", "--include-nested", "--format", "json"],
dir,
+ { env: { GHOST_PACKAGE_DIR: ".design/memory" } },
);
expect(lint.code).toBe(0);
@@ -2687,16 +2541,16 @@ async function writeSplitFingerprintPackage(
fingerprintRaw: string,
checksRaw?: string,
): Promise {
- const fingerprintDir = join(pkg, "fingerprint");
+ const packageDir = pkg;
const doc = parseYaml(fingerprintRaw) as Record;
- await mkdir(fingerprintDir, { recursive: true });
+ await mkdir(packageDir, { recursive: true });
await Promise.all([
writeFile(
- join(fingerprintDir, "manifest.yml"),
+ join(packageDir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: local\n",
),
writeFile(
- join(fingerprintDir, "intent.yml"),
+ join(packageDir, "intent.yml"),
stringifyYaml(
doc.intent ?? {
summary: {},
@@ -2707,7 +2561,7 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "inventory.yml"),
+ join(packageDir, "inventory.yml"),
stringifyYaml(
doc.inventory ?? {
topology: {},
@@ -2718,11 +2572,11 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "composition.yml"),
+ join(packageDir, "composition.yml"),
stringifyYaml(doc.composition ?? { patterns: [] }),
),
...(checksRaw
- ? [writeFile(join(fingerprintDir, "validate.yml"), checksRaw)]
+ ? [writeFile(join(packageDir, "validate.yml"), checksRaw)]
: []),
]);
}
@@ -2753,20 +2607,17 @@ checks:
async function writeNestedCheckPackage(
dir: string,
- memoryDir = ".ghost",
+ ghostDir = ".ghost",
): Promise {
- const rootMemory = memoryPackagePath(dir, memoryDir);
- const checkoutMemory = memoryPackagePath(
- join(dir, "apps", "checkout"),
- memoryDir,
- );
+ const rootPackage = packagePath(dir, ghostDir);
+ const checkoutPackage = packagePath(join(dir, "apps", "checkout"), ghostDir);
await mkdir(join(dir, "apps", "checkout", "review"), { recursive: true });
await mkdir(join(dir, "shared"), { recursive: true });
await writeFile(join(dir, "apps", "checkout", "review", "page.tsx"), "");
await writeFile(join(dir, "shared", "home.tsx"), "");
await writeSplitFingerprintPackage(
- rootMemory,
+ rootPackage,
`schema: ghost.fingerprint/v1
intent:
summary:
@@ -2812,7 +2663,7 @@ checks:
);
await writeSplitFingerprintPackage(
- checkoutMemory,
+ checkoutPackage,
`schema: ghost.fingerprint/v1
intent:
summary:
@@ -2852,8 +2703,8 @@ checks:
);
}
-function memoryPackagePath(root: string, memoryDir: string): string {
- return join(root, ...memoryDir.split("/"));
+function packagePath(root: string, ghostDir: string): string {
+ return join(root, ...ghostDir.split("/"));
}
function webPatch(path: string, added: string): string {
diff --git a/packages/ghost/test/context-entrypoint.test.ts b/packages/ghost/test/context-entrypoint.test.ts
index 96bcfa64..a8caa922 100644
--- a/packages/ghost/test/context-entrypoint.test.ts
+++ b/packages/ghost/test/context-entrypoint.test.ts
@@ -71,7 +71,7 @@ describe("context entrypoint", () => {
expect.objectContaining({
label: "Exemplars",
omitted: 2,
- source: "fingerprint/inventory.yml",
+ source: "inventory.yml",
}),
]),
);
@@ -165,11 +165,11 @@ describe("context entrypoint", () => {
"source surface for inventory.exemplar:refund-settings-tertiary",
},
{
- path: "fingerprint/intent.yml",
+ path: "intent.yml",
reason: "selected intent anchors and full intent",
},
{
- path: "fingerprint/composition.yml",
+ path: "composition.yml",
reason: "selected composition patterns and neighboring patterns",
},
]);
@@ -197,9 +197,9 @@ describe("context entrypoint", () => {
);
expect(entrypoint.suggestedReads.map((read) => read.path)).toEqual(
expect.arrayContaining([
- "fingerprint/intent.yml",
- "fingerprint/inventory.yml",
- "fingerprint/composition.yml",
+ "intent.yml",
+ "inventory.yml",
+ "composition.yml",
]),
);
});
@@ -288,7 +288,7 @@ function context(
];
return {
name: "cash-dashboard",
- fingerprintDir: ".ghost",
+ packageDir: ".ghost",
targetPaths: ["apps/refunds/settings/page.tsx"],
stackDirs: ["/repo/.ghost", "/repo/apps/refunds/.ghost"],
fingerprintRaw: "",
diff --git a/packages/ghost/test/fingerprint-package.test.ts b/packages/ghost/test/fingerprint-package.test.ts
index 36ada077..2aced029 100644
--- a/packages/ghost/test/fingerprint-package.test.ts
+++ b/packages/ghost/test/fingerprint-package.test.ts
@@ -53,7 +53,7 @@ describe("split fingerprint package", () => {
it("accepts inventory source links without making source material canonical", async () => {
await writeManifest(dir);
await writeFile(
- join(dir, "fingerprint", "inventory.yml"),
+ join(dir, "inventory.yml"),
`topology: {}
building_blocks: {}
exemplars: []
@@ -79,7 +79,7 @@ sources:
it("reports duplicate inventory source ids", async () => {
await writeManifest(dir);
await writeFile(
- join(dir, "fingerprint", "inventory.yml"),
+ join(dir, "inventory.yml"),
`topology: {}
building_blocks: {}
exemplars: []
@@ -98,31 +98,31 @@ sources:
expect(report.errors).toBe(1);
expect(report.issues[0]).toMatchObject({
rule: "duplicate-id",
- path: "fingerprint/inventory.yml.sources[1].id",
+ path: "inventory.yml.sources[1].id",
});
});
it("reports invalid raw layer YAML at the split path", async () => {
await writeManifest(dir);
- await writeFile(join(dir, "fingerprint", "intent.yml"), "{nope");
+ await writeFile(join(dir, "intent.yml"), "{nope");
const report = await lintFingerprintPackage(dir);
expect(report.errors).toBe(1);
expect(report.issues[0]).toMatchObject({
rule: "package-yaml-invalid",
- path: "fingerprint/intent.yml",
+ path: "intent.yml",
});
});
it("does not silently treat unreadable optional layer paths as missing", async () => {
await writeManifest(dir);
- await mkdir(join(dir, "fingerprint", "intent.yml"));
+ await mkdir(join(dir, "intent.yml"));
await expect(lintFingerprintPackage(dir)).rejects.toThrow();
});
- it("does not discover old .ghost/fingerprint.yml alone as a package", async () => {
+ it("does not discover old .ghost.yml alone as a package", async () => {
await writeFile(
join(dir, "fingerprint.yml"),
"schema: ghost.fingerprint/v1\n",
@@ -133,15 +133,15 @@ sources:
expect(report.errors).toBe(1);
expect(report.issues[0]).toMatchObject({
rule: "package-artifact-missing",
- path: "fingerprint/manifest.yml",
+ path: "manifest.yml",
});
});
});
async function writeManifest(dir: string): Promise {
- await mkdir(join(dir, "fingerprint"), { recursive: true });
+ await mkdir(dir, { recursive: true });
await writeFile(
- join(dir, "fingerprint", "manifest.yml"),
+ join(dir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: local\n",
);
}
diff --git a/packages/ghost/test/fingerprint-stack.test.ts b/packages/ghost/test/fingerprint-stack.test.ts
index fed98aea..4f7e3308 100644
--- a/packages/ghost/test/fingerprint-stack.test.ts
+++ b/packages/ghost/test/fingerprint-stack.test.ts
@@ -150,20 +150,20 @@ intent:
const stack = await loadFingerprintStackForPath(
"apps/checkout/review/page.tsx",
dir,
- { memoryDir: ".design/memory" },
+ { ghostDir: ".design/memory" },
);
const groups = await groupFingerprintStacksForPaths(
["apps/checkout/review/page.tsx", "shared/home.tsx"],
dir,
- { memoryDir: ".design/memory" },
+ { ghostDir: ".design/memory" },
);
- expect(stack.fingerprint_dir).toBe(".design/memory");
+ expect(stack.ghost_dir).toBe(".design/memory");
expect(stack.layers.map((layer) => layer.relative_root)).toEqual([
".",
"apps/checkout",
]);
- expect(stack.layers.map((layer) => layer.fingerprint_dir)).toEqual([
+ expect(stack.layers.map((layer) => layer.ghost_dir)).toEqual([
".design/memory",
".design/memory",
]);
@@ -173,10 +173,10 @@ intent:
async function writeStackFixture(
dir: string,
- memoryDir = ".ghost",
+ ghostDir = ".ghost",
): Promise {
- await writeRootBundle(dir, memoryDir);
- await writeChildBundle(join(dir, "apps", "checkout"), memoryDir);
+ await writeRootBundle(dir, ghostDir);
+ await writeChildBundle(join(dir, "apps", "checkout"), ghostDir);
await mkdir(join(dir, "shared"), { recursive: true });
await writeFile(join(dir, "shared", "home.tsx"), "");
await writeFile(join(dir, "apps", "checkout", "review", "page.tsx"), "");
@@ -184,9 +184,9 @@ async function writeStackFixture(
async function writeRootBundle(
dir: string,
- memoryDir = ".ghost",
+ ghostDir = ".ghost",
): Promise {
- const ghost = memoryPackagePath(dir, memoryDir);
+ const ghost = packagePath(dir, ghostDir);
await writeSplitFingerprintPackage(
ghost,
`schema: ghost.fingerprint/v1
@@ -252,9 +252,9 @@ checks:
async function writeChildBundle(
root: string,
- memoryDir = ".ghost",
+ ghostDir = ".ghost",
): Promise {
- const ghost = memoryPackagePath(root, memoryDir);
+ const ghost = packagePath(root, ghostDir);
await mkdir(join(root, "review"), { recursive: true });
await writeSplitFingerprintPackage(
ghost,
@@ -330,8 +330,8 @@ checks:
);
}
-function memoryPackagePath(root: string, memoryDir: string): string {
- return join(root, ...memoryDir.split("/"));
+function packagePath(root: string, ghostDir: string): string {
+ return join(root, ...ghostDir.split("/"));
}
async function writeSplitFingerprintPackage(
@@ -339,16 +339,16 @@ async function writeSplitFingerprintPackage(
fingerprintRaw: string,
checksRaw?: string,
): Promise {
- const fingerprintDir = join(pkg, "fingerprint");
+ const packageDir = pkg;
const doc = parseYaml(fingerprintRaw) as Record;
- await mkdir(fingerprintDir, { recursive: true });
+ await mkdir(packageDir, { recursive: true });
await Promise.all([
writeFile(
- join(fingerprintDir, "manifest.yml"),
+ join(packageDir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: local\n",
),
writeFile(
- join(fingerprintDir, "intent.yml"),
+ join(packageDir, "intent.yml"),
stringifyYaml(
doc.intent ?? {
summary: {},
@@ -359,7 +359,7 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "inventory.yml"),
+ join(packageDir, "inventory.yml"),
stringifyYaml(
doc.inventory ?? {
topology: {},
@@ -370,11 +370,11 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "composition.yml"),
+ join(packageDir, "composition.yml"),
stringifyYaml(doc.composition ?? { patterns: [] }),
),
...(checksRaw
- ? [writeFile(join(fingerprintDir, "validate.yml"), checksRaw)]
+ ? [writeFile(join(packageDir, "validate.yml"), checksRaw)]
: []),
]);
}
diff --git a/packages/ghost/test/fixtures/context-sandboxes/harness.ts b/packages/ghost/test/fixtures/context-sandboxes/harness.ts
index efd70e42..00bdea5d 100644
--- a/packages/ghost/test/fixtures/context-sandboxes/harness.ts
+++ b/packages/ghost/test/fixtures/context-sandboxes/harness.ts
@@ -174,25 +174,25 @@ async function writePackage(
pkg: string,
options: { fingerprint: string; checks?: string },
): Promise {
- const fingerprintDir = join(pkg, "fingerprint");
- await mkdir(fingerprintDir, { recursive: true });
+ const packageDir = pkg;
+ await mkdir(packageDir, { recursive: true });
await writeFile(
- join(fingerprintDir, "manifest.yml"),
+ join(packageDir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: local\n",
"utf-8",
);
- await writeFile(join(fingerprintDir, "intent.yml"), intentLayer(options.fingerprint));
+ await writeFile(join(packageDir, "intent.yml"), intentLayer(options.fingerprint));
await writeFile(
- join(fingerprintDir, "inventory.yml"),
+ join(packageDir, "inventory.yml"),
inventoryLayer(options.fingerprint),
);
await writeFile(
- join(fingerprintDir, "composition.yml"),
+ join(packageDir, "composition.yml"),
compositionLayer(options.fingerprint),
);
if (options.checks) {
await writeFile(
- join(fingerprintDir, "validate.yml"),
+ join(packageDir, "validate.yml"),
options.checks,
"utf-8",
);
diff --git a/packages/ghost/test/relay.test.ts b/packages/ghost/test/relay.test.ts
index baea895e..4948532a 100644
--- a/packages/ghost/test/relay.test.ts
+++ b/packages/ghost/test/relay.test.ts
@@ -441,16 +441,16 @@ async function writeSplitFingerprintPackage(
fingerprintRaw: string,
checksRaw?: string,
): Promise {
- const fingerprintDir = join(pkg, "fingerprint");
+ const packageDir = pkg;
const doc = parseYaml(fingerprintRaw) as Record;
- await mkdir(fingerprintDir, { recursive: true });
+ await mkdir(packageDir, { recursive: true });
await Promise.all([
writeFile(
- join(fingerprintDir, "manifest.yml"),
+ join(packageDir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: local\n",
),
writeFile(
- join(fingerprintDir, "intent.yml"),
+ join(packageDir, "intent.yml"),
stringifyYaml(
doc.intent ?? {
summary: {},
@@ -461,7 +461,7 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "inventory.yml"),
+ join(packageDir, "inventory.yml"),
stringifyYaml(
doc.inventory ?? {
topology: {},
@@ -472,11 +472,11 @@ async function writeSplitFingerprintPackage(
),
),
writeFile(
- join(fingerprintDir, "composition.yml"),
+ join(packageDir, "composition.yml"),
stringifyYaml(doc.composition ?? { patterns: [] }),
),
...(checksRaw
- ? [writeFile(join(fingerprintDir, "validate.yml"), checksRaw)]
+ ? [writeFile(join(packageDir, "validate.yml"), checksRaw)]
: []),
]);
}
diff --git a/packages/ghost/test/scan-status.test.ts b/packages/ghost/test/scan-status.test.ts
index e97d9fb2..c79ba447 100644
--- a/packages/ghost/test/scan-status.test.ts
+++ b/packages/ghost/test/scan-status.test.ts
@@ -34,7 +34,7 @@ describe("scanStatus contribution", () => {
"validate",
]);
expect(status.contribution.reasons.join(" ")).toContain(
- "fingerprint/manifest.yml is missing",
+ "manifest.yml is missing",
);
});
@@ -99,13 +99,10 @@ checks: []
});
it("does not report sources cache as package contribution", async () => {
- await mkdir(join(dir, "fingerprint", "sources", "cache"), {
+ await mkdir(join(dir, "sources", "cache"), {
recursive: true,
});
- await writeFile(
- join(dir, "fingerprint", "sources", "cache", "inventory.json"),
- "{}\n",
- );
+ await writeFile(join(dir, "sources", "cache", "inventory.json"), "{}\n");
await writePackage(dir, {});
const status = await scanStatus(dir);
@@ -333,15 +330,15 @@ async function writePackage(
validate?: string;
},
): Promise {
- const fingerprintDir = join(dir, "fingerprint");
- await mkdir(fingerprintDir, { recursive: true });
+ const packageDir = dir;
+ await mkdir(packageDir, { recursive: true });
await writeFile(
- join(fingerprintDir, "manifest.yml"),
+ join(packageDir, "manifest.yml"),
"schema: ghost.fingerprint-package/v1\nid: test\n",
);
await Promise.all(
Object.entries(facets).map(([facet, content]) =>
- writeFile(join(fingerprintDir, `${facet}.yml`), content),
+ writeFile(join(packageDir, `${facet}.yml`), content),
),
);
}
diff --git a/scripts/check-file-sizes.mjs b/scripts/check-file-sizes.mjs
index 9fdee56e..05d504da 100644
--- a/scripts/check-file-sizes.mjs
+++ b/scripts/check-file-sizes.mjs
@@ -18,7 +18,7 @@ const EXCEPTIONS = {
"packages/ghost/src/fingerprint-commands.ts": {
limit: 1135,
justification:
- "Fingerprint package command registry — temporarily holds package lifecycle, legacy markdown, survey/cache, scan readiness, and adapter-neutral memory-dir routing until command groups are split further",
+ "Fingerprint package command registry — temporarily holds package lifecycle, legacy markdown, survey/cache, scan readiness, and adapter-neutral package-dir routing until command groups are split further",
},
"packages/ghost/src/scan/inventory.ts": {
limit: 1120,
@@ -28,7 +28,7 @@ const EXCEPTIONS = {
"packages/ghost/src/scan/fingerprint-stack.ts": {
limit: 1120,
justification:
- "Canonical nested fingerprint stack loader — discovery, merge, path normalization, memory-dir validation, and stack validation stay together so CLI routing shares one provenance model",
+ "Canonical nested fingerprint stack loader — discovery, merge, path normalization, package-dir validation, and stack validation stay together so CLI routing shares one provenance model",
},
"packages/ghost/src/scan/verify-fingerprint.ts": {
limit: 900,
diff --git a/scripts/check-packed-package.mjs b/scripts/check-packed-package.mjs
index f272013e..54c6d637 100644
--- a/scripts/check-packed-package.mjs
+++ b/scripts/check-packed-package.mjs
@@ -117,7 +117,7 @@ try {
cwd: consumerDir,
});
const initOutput = JSON.parse(init);
- if (!initOutput.manifest?.endsWith(".ghost/fingerprint/manifest.yml")) {
+ if (!initOutput.manifest?.endsWith(".ghost/manifest.yml")) {
fail("packed ghost init did not emit the expected manifest path");
}
run("pnpm", ["exec", "ghost", "lint", ".ghost"], { cwd: consumerDir });
diff --git a/scripts/check-release-tarball.mjs b/scripts/check-release-tarball.mjs
index 1e7df938..d1c9e1b9 100644
--- a/scripts/check-release-tarball.mjs
+++ b/scripts/check-release-tarball.mjs
@@ -93,7 +93,7 @@ try {
{ cwd: tmpRoot },
);
const initOutput = JSON.parse(init);
- if (!initOutput.manifest?.endsWith(".ghost/fingerprint/manifest.yml")) {
+ if (!initOutput.manifest?.endsWith(".ghost/manifest.yml")) {
fail("release tarball ghost init did not emit the expected manifest path");
}
diff --git a/scripts/check-terminology.mjs b/scripts/check-terminology.mjs
index 99602281..b1ccb32c 100644
--- a/scripts/check-terminology.mjs
+++ b/scripts/check-terminology.mjs
@@ -82,7 +82,6 @@ const FORBIDDEN_PATTERNS = [
];
const ALLOWED_MEMORY_TERMS = [
- "--memory-dir",
"--include-memory",
"missing-memory",
"muscle memory",
]