diff --git a/.changeset/quiet-signals-major.md b/.changeset/quiet-signals-major.md new file mode 100644 index 00000000..0d021f94 --- /dev/null +++ b/.changeset/quiet-signals-major.md @@ -0,0 +1,5 @@ +--- +"@anarchitecture/ghost": major +--- + +Remove optional memory and cache fingerprint package support, flatten checks to `fingerprint/checks.yml`, and rename `ghost inventory` to `ghost signals`. diff --git a/.ghost/fingerprint/enforcement/checks.yml b/.ghost/fingerprint/checks.yml similarity index 100% rename from .ghost/fingerprint/enforcement/checks.yml rename to .ghost/fingerprint/checks.yml diff --git a/.ghost/fingerprint/composition.yml b/.ghost/fingerprint/composition.yml index e6090d81..690dddca 100644 --- a/.ghost/fingerprint/composition.yml +++ b/.ghost/fingerprint/composition.yml @@ -1,12 +1,12 @@ patterns: - id: core-layers-stay-prominent kind: structure - pattern: Portable Ghost packages keep prose.yml, inventory.yml, and composition.yml at the root of fingerprint/ while enforcement, memory, and source material sit in purpose folders. + pattern: Portable Ghost packages keep prose.yml, inventory.yml, and composition.yml prominent in fingerprint/ while checks.yml remains optional validation support. guidance: - - Do not require generated cache for a valid new project. - - Do not create cache during initialization; cache appears only after explicit inventory work. + - Do not require generated signal files for a valid new project. + - Keep ghost signals as stdout-only reconnaissance for agents, not package material. - Allow sparse layer authoring; omitted layer files normalize to empty layers internally. - - Keep generated cache outside canonical inventory while allowing inventory.yml to cite it as a source. + - Curate durable building blocks and exemplars into inventory.yml instead of treating raw signals as inventory authorship. - Use Git review for fingerprint approval instead of Ghost-specific draft files. evidence: - path: README.md diff --git a/.ghost/fingerprint/inventory.yml b/.ghost/fingerprint/inventory.yml index f8cc25da..164be4a9 100644 --- a/.ghost/fingerprint/inventory.yml +++ b/.ghost/fingerprint/inventory.yml @@ -73,10 +73,10 @@ exemplars: surface_type: docs-home scope: docs-site note: Public entry point describes the split fingerprint folder as canonical prose, inventory, and composition. - why: Shows how Ghost explains repo-local fingerprint layers, curated inventory, generated cache, and Git-reviewed approval. + why: Shows how Ghost explains repo-local fingerprint layers, curated inventory, raw signals, and Git-reviewed approval. refs: - prose.principle:fingerprint-folder-is-canonical - - prose.principle:generated-cache-is-source-material + - prose.principle:signals-are-authoring-evidence - id: review-command-fingerprint-packet path: packages/ghost/src/context/package-review-command.ts title: Generated review command @@ -87,8 +87,4 @@ exemplars: refs: - prose.experience_contract:review-cites-fingerprint-and-diff - composition.pattern:compact-agent-handoff -sources: - - id: generated-inventory - kind: cache - ref: sources/cache/inventory.json - note: Optional refreshable repo facts produced by ghost inventory. +sources: [] diff --git a/.ghost/fingerprint/memory/intent.md b/.ghost/fingerprint/memory/intent.md deleted file mode 100644 index 5a7f7b7f..00000000 --- a/.ghost/fingerprint/memory/intent.md +++ /dev/null @@ -1,22 +0,0 @@ -# Intent - -Ghost dogfoods its own fingerprint package so the artifact is shaped by real -maintenance pressure before it becomes ceremony for other repos. - -The package should make five boundaries easy to feel: - -- `fingerprint/prose.yml`, `fingerprint/inventory.yml`, and - `fingerprint/composition.yml` are the core model. -- `fingerprint/enforcement/checks.yml` is deterministic enforcement and only - active checks can block. -- `fingerprint/memory/` holds human intent and rationale history. -- `fingerprint/sources/cache/` is optional generated cache; it can help capture - but it is not canonical truth. -- Git is the staging and approval boundary for fingerprint edits. - -For Ghost's own UI and docs, preserve the editorial/workbench tension: plain -foundations, concrete command examples, restrained surfaces, strong display -type where it clarifies hierarchy, compact borders, and token-first color -usage. Divergence is welcome when it teaches the package something, but it -should be named through a fingerprint edit or decision instead of hidden inside -generated UI. diff --git a/.ghost/fingerprint/prose.yml b/.ghost/fingerprint/prose.yml index f42aae48..517b2605 100644 --- a/.ghost/fingerprint/prose.yml +++ b/.ghost/fingerprint/prose.yml @@ -17,7 +17,7 @@ summary: - Let advisory review block work without deterministic checks. tradeoffs: - Prefer compact durable prose over exhaustive surveys. - - Keep enforcement, memory, and sources organized around the core layer files. + - Keep optional checks in the fingerprint package without treating them as core generation layers. - Preserve OSS-friendly language over company-specific strategy. tone: - plain @@ -28,10 +28,10 @@ situations: - id: capturing-fingerprint title: Capturing fingerprint layers user_intent: Create or update the Ghost fingerprint for a project. - product_obligation: Help the agent distinguish canonical prose/inventory/composition from generated cache, enforcement, and rationale history. + product_obligation: Help the agent distinguish canonical prose/inventory/composition from optional checks and raw repo signals. principles: - prose.principle:fingerprint-folder-is-canonical - - prose.principle:generated-cache-is-source-material + - prose.principle:signals-are-authoring-evidence experience_contracts: - prose.experience_contract:git-is-approval-boundary patterns: @@ -66,22 +66,22 @@ principles: note: Public format docs define the split fingerprint folder as the source of truth. - path: packages/ghost/src/scan/fingerprint-package.ts note: init writes manifest.yml and the three core layer files under fingerprint/. - - id: generated-cache-is-source-material - principle: Generated cache answers what exists; curated inventory points to the building blocks, sources, and precedents an agent can inspect or use. + - id: signals-are-authoring-evidence + principle: Raw repo signals answer what exists; curated inventory points to the building blocks, sources, and precedents an agent can inspect or use. guidance: - - Store generated cache under fingerprint/sources/cache/ when useful. + - Use ghost signals as scratch evidence while authoring or refreshing fingerprint layers. - Curate durable building blocks, files, routes, libraries, assets, source links, notes, and exemplars into inventory.yml. - - Do not let cache or building blocks become surface authority without prose and composition. + - Do not let signals or building blocks become surface authority without prose and composition. evidence: - path: README.md - note: README frames generated cache as optional source material. + note: README frames ghost signals as authoring evidence. - id: checks-are-enforcement - principle: Deterministic checks live under fingerprint/enforcement/ and should cite typed fingerprint refs when they enforce surface-composition rules. + principle: Deterministic checks live in fingerprint/checks.yml and should cite typed fingerprint refs when they enforce surface-composition rules. guidance: - Active checks can block; advisory findings cannot block unless check-backed. - Prefer typed refs such as composition.pattern:token-first-interface. - Ungrounded checks may exist, but lint should make their missing grounding visible. - - Checks keep enforcement status because gates need lifecycle state. + - Checks keep explicit status because gates need lifecycle state. evidence: - path: packages/ghost/src/ghost-core/checks/lint.ts note: Check grounding is validated and missing derivation is reported as a warning. @@ -98,7 +98,7 @@ experience_contracts: contract: Advisory review findings must cite the diff location and the relevant fingerprint refs. obligations: - Use active checks only when a finding should block. - - Use missing-memory or experience-gap when the fingerprint cannot support a confident finding. + - Use missing-fingerprint or experience-gap when the fingerprint cannot support a confident finding. evidence: - path: packages/ghost/src/context/package-review-command.ts note: Emitted review command tells agents what to cite. @@ -107,7 +107,7 @@ experience_contracts: obligations: - Do not rewrite canonical fingerprint layers silently during generation or review. - Treat uncommitted or unmerged fingerprint edits as draft work. - - Record durable surface-composition guidance in prose.yml, inventory.yml, and composition.yml; deterministic gates belong in enforcement/checks.yml. + - Record durable surface-composition guidance in prose.yml, inventory.yml, and composition.yml; deterministic gates belong in checks.yml. evidence: - path: packages/ghost/src/skill-bundle/references/capture.md - id: emitted-context-prefers-fingerprint-layers diff --git a/.ghost/fingerprint/sources/cache/.gitkeep b/.ghost/fingerprint/sources/cache/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/.ghost/fingerprint/sources/cache/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/CLAUDE.md b/CLAUDE.md index 576c42d0..16e7ef2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,7 +30,7 @@ pnpm --filter @anarchitecture/ghost exec ghost Ghost is **BYOA (bring your own agent)**. Claude Code, Codex, Cursor, Goose, or another host agent reads, decides, and writes. Ghost is the deterministic -calculator the agent reaches for: schema validation, inventory/cache helpers, +calculator the agent reaches for: schema validation, repo-signal helpers, structural diffs, drift checks, comparison math, and handoff packets. The canonical root `.ghost/` package follows: @@ -42,10 +42,7 @@ fingerprint/ prose.yml inventory.yml composition.yml - enforcement/checks.yml - memory/intent.md - memory/decisions/ - sources/cache/ + checks.yml ``` The three root layer files under `fingerprint/` are the core model: @@ -54,13 +51,10 @@ The three root layer files under `fingerprint/` are the core model: - `inventory.yml` for curated material, exemplars, and source links. - `composition.yml` for experience patterns. -Supporting files live under purpose folders. `enforcement/checks.yml` is -validation and enforcement, not generation input. `memory/intent.md` and -`memory/decisions/` hold optional human context and rationale. Generated cache -in `sources/cache/` is optional source material; it answers what exists, while -core fingerprint layers answer what the surface is trying to do. `.ghost/config.yml` -remains local adapter/routing config outside portable context. Ordinary Git -review is the approval boundary for fingerprint edits. +`checks.yml` validates output and can enforce deterministic gates; 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. 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 @@ -80,14 +74,14 @@ fingerprint input for new Ghost work. | Command | Description | | --- | --- | -| `ghost init` | Create `.ghost/fingerprint/` with manifest, core layers, and enforcement checks. | +| `ghost init` | Create `.ghost/fingerprint/` with manifest, core layers, and deterministic checks. | | `ghost scan` | Report fingerprint layer readiness and BYOA next-step guidance. | -| `ghost inventory` | Emit raw repo signals as JSON for optional cache/source material. | +| `ghost signals` | Emit raw repo signals as JSON for fingerprint authoring. | | `ghost lint` | Validate a fingerprint package or single artifact. | -| `ghost verify` | Validate fingerprint evidence and exemplar paths, typed check refs, and optional rationale files. | -| `ghost describe` | Print optional `fingerprint/memory/intent.md` or direct markdown section ranges. | +| `ghost verify` | Validate fingerprint evidence and exemplar paths, and typed check refs. | +| `ghost describe` | Print direct markdown section ranges. | | `ghost diff` | Structural prose-level diff between direct fingerprints. | -| `ghost survey ` | Legacy/cache helpers for optional `ghost.survey/v1` workflows. | +| `ghost survey ` | Legacy survey helpers for optional `ghost.survey/v1` workflows. | | `ghost check` | Run active `ghost.checks/v1` deterministic gates against a diff. | | `ghost review` | Emit an evidence-routed advisory review packet grounded in fingerprint layers, inventory exemplars, checks, and the diff. | | `ghost compare` | Pairwise or composite comparison over packages or direct fingerprints. | diff --git a/README.md b/README.md index 40dd09a1..36adfd51 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,7 @@ The canonical package is intentionally small: prose.yml # surface intent inventory.yml # curated material and exemplars composition.yml # patterns, flows, states, and arrangements - enforcement/checks.yml # optional deterministic gates - memory/intent.md # optional human-approved intent - memory/decisions/ # optional rationale history - sources/cache/ # optional generated observations + checks.yml # optional deterministic gates ``` Generation uses the three core layers: @@ -37,14 +34,14 @@ Generation uses the three core layers: - `composition.yml` captures the patterns that make those materials feel like one intentional product. -Checks, memory, generated cache, nested packages, and custom host-wrapper -package locations are supporting features. They do not replace the core -fingerprint layers. Generated cache answers what exists; curated fingerprint -layers answer what the surface is trying to preserve. +Checks, nested packages, custom host-wrapper package locations, and raw repo +signals are supporting features. They do not replace the core fingerprint +layers. `ghost signals` answers what exists; curated fingerprint layers answer +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 or cache workflows, but -new Ghost work should target `.ghost/fingerprint/`. +`fingerprint.yml` artifacts can still inform migration workflows, but new Ghost +work should target `.ghost/fingerprint/`. ## Project Status: Beta @@ -96,14 +93,13 @@ edits, and asks you to curate the claims. ```bash ghost init ghost scan --format json -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ghost lint .ghost ghost verify .ghost --root . ``` -Use `--with-intent`, `--with-config`, `--reference`, or `--scope` only when the -repo needs those optional files, reference libraries, or nested product areas. +Use `--with-config`, `--reference`, or `--scope` only when the repo needs local +routing, reference libraries, or nested product areas. 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 @@ -134,7 +130,7 @@ same fingerprint: ```bash ghost check --base main -ghost review --base main --include-memory +ghost review --base main ``` `ghost check` runs active `ghost.checks/v1` gates and can fail. `ghost review` @@ -165,15 +161,15 @@ useful layer content. It does not call an LLM. | `ghost init` | Create `.ghost/fingerprint/` package layers. | | `ghost scan` | Report fingerprint layer readiness for prose, inventory, and composition. | | `ghost lint` | Validate a fingerprint package or individual artifact. | -| `ghost verify` | Validate evidence paths, exemplar paths, typed check refs, and optional rationale files. | +| `ghost verify` | Validate evidence paths, exemplar paths, and typed check refs. | | `ghost check` | Run active deterministic gates against a diff. | | `ghost review` | Emit an evidence-routed advisory packet from fingerprint layers and a diff. | | `ghost relay gather` | Gather fingerprint-grounded context for an agent target. | | `ghost emit ` | Emit `review-command` artifacts. | | `ghost skill install` | Install the unified Ghost skill bundle. | | `ghost stack` | Inspect resolved root-to-leaf fingerprint stacks. | -| `ghost inventory` | Emit raw repo signals as JSON for optional cache material. | -| `ghost describe` | Print optional intent or markdown section ranges. | +| `ghost signals` | Emit raw repo signals as JSON for fingerprint authoring. | +| `ghost describe` | Print markdown section ranges. | | `ghost compare` | Compare fingerprint packages. | | `ghost ack` / `track` / `diverge` | Record stance toward tracked drift. | | `ghost diff` / `survey` | Maintain direct markdown fingerprints or survey/cache files for compatibility workflows. | diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 3fa54c31..e48825a8 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -56,10 +56,9 @@ export default function Home() { surface together, and which examples show it at its best.

- Components, tokens, libraries, and generated cache become - implementation material. Ghost does not replace them. It gives - agents the surface context that tells them when and how those - materials belong. + Components, tokens, and libraries become implementation material. + Ghost does not replace them. It gives agents the surface context + that tells them when and how those materials belong.

Ghost keeps that model compact:

    @@ -72,35 +71,24 @@ export default function Home() { composition.yml store the three core layers
  • - enforcement/checks.yml stores optional - deterministic gates grounded in fingerprint refs + checks.yml stores optional deterministic gates + grounded in fingerprint refs
  • ordinary Git review separates draft fingerprint edits from checked-in truth
  • -
  • - memory/intent.md and memory/decisions{" "} - record optional human-approved context inside the package -
  • -
  • - sources/cache holds generated cache without - becoming canonical truth -

The split is deliberate. prose.yml captures the intent behind the surface. inventory.yml captures the materials it draws from. composition.yml captures the - patterns that make it feel intentional. Cache answers what exists. - Checks validate output; they are not generation input. + patterns that make it feel intentional. Checks validate output; + they are not generation input.

A typical loop becomes:

    -
  1. - Brief from the fingerprint layers, generated cache when useful, - and exemplars -
  2. +
  3. Brief from the fingerprint layers and exemplars
  4. Generate or edit with the host agent
  5. Run active deterministic checks and advisory review
  6. @@ -127,7 +115,7 @@ export default function Home() {

    • incorrect generation: agent failure
    • -
    • missing-memory: under-specified surface context
    • +
    • missing-fingerprint: under-specified surface context
    • intentional product evolution

    diff --git a/apps/docs/src/content/docs/cli-reference.mdx b/apps/docs/src/content/docs/cli-reference.mdx index 8e0586e7..ac7e7866 100644 --- a/apps/docs/src/content/docs/cli-reference.mdx +++ b/apps/docs/src/content/docs/cli-reference.mdx @@ -26,7 +26,7 @@ product areas: .ghost/fingerprint/prose.yml .ghost/fingerprint/inventory.yml .ghost/fingerprint/composition.yml -.ghost/fingerprint/enforcement/checks.yml +.ghost/fingerprint/checks.yml apps/checkout/.ghost/fingerprint/manifest.yml ``` @@ -40,7 +40,7 @@ The command tables below are generated from the CLI source. Run ### Initialize - `init` Create a `.ghost/fingerprint/` package with a manifest, raw core layer files, -and enforcement checks. Use `--scope ` for nested package roots. Use +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 @@ -53,10 +53,9 @@ over the environment default. ```bash ghost init -ghost init --with-intent ghost init --monorepo ghost init --monorepo --apply -ghost init --scope apps/checkout --with-intent +ghost init --scope apps/checkout GHOST_MEMORY_DIR=.agents/ghost ghost init ghost init --scope apps/checkout --memory-dir .design/memory ``` @@ -64,7 +63,7 @@ ghost init --scope apps/checkout --memory-dir .design/memory ### Layer readiness - `scan` Report whether `fingerprint/manifest.yml` is present and which useful layers it -has: `prose`, `inventory`, and `composition`. Generated cache does not count +has: `prose`, `inventory`, and `composition`. Raw repo signals do not count toward inventory readiness. @@ -86,16 +85,15 @@ Inspect the root-to-leaf fingerprint stack for one or more paths. ghost stack apps/checkout/review/page.tsx --format json ``` -### Capture source material - `inventory` +### Inspect repo signals - `signals` -Emit raw signals about a frontend repo as JSON. Use this as optional source -material under `.ghost/fingerprint/sources/cache/`. +Emit raw signals about a frontend repo as JSON. Use this as scratch evidence +while authoring curated fingerprint layers. - + ```bash -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ``` @@ -112,7 +110,7 @@ Validate a root `.ghost` fingerprint package or an individual split artifact. ```bash ghost lint ghost lint .ghost/fingerprint/prose.yml -ghost lint .ghost/fingerprint/enforcement/checks.yml --format json +ghost lint .ghost/fingerprint/checks.yml --format json ghost lint --all ``` @@ -145,7 +143,7 @@ suggested reads, active checks, and provenance. ### Inspection - `describe` -Print optional `.ghost/fingerprint/memory/intent.md` or a markdown section map. +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 ef2c89e8..ace21668 100644 --- a/apps/docs/src/content/docs/fingerprint-authoring.mdx +++ b/apps/docs/src/content/docs/fingerprint-authoring.mdx @@ -70,15 +70,14 @@ Set up the Ghost fingerprint for this repo with auto-draft. ```bash ghost scan --format json -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory . > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ghost lint .ghost ghost verify .ghost --root . ``` -Generated cache is source material only. It can support curated inventory, but -it does not establish surface-composition guidance by itself. Scan frequency and -raw cache may seed a draft, but they do not decide what the surface should do. +Raw repo signals are source evidence only. They can support curated inventory, +but they do not establish surface-composition guidance by themselves. Signal +frequency may seed a draft, but it does not decide what the surface should do. @@ -91,9 +90,7 @@ Keep each claim in the file that will make it useful later: | `prose.yml` | Audience, goals, anti-goals, situations, principles, and experience contracts. | | `inventory.yml` | Scopes, surface types, files, routes, libraries, assets, building blocks, exemplars, and source links. | | `composition.yml` | Repeatable rules, layouts, structures, flows, states, content patterns, behavior, and visual arrangements. | -| `enforcement/checks.yml` | Deterministic gates that can be checked from a diff. | -| `memory/` | Human-approved intent and accepted or rejected rationale. | -| `sources/cache/` | Refreshable generated observations. | +| `checks.yml` | Deterministic gates that can be checked from a diff. | @@ -110,7 +107,7 @@ Keep broad product-family guidance at the root. Put local obligations in the nearest package that owns the surface. ```bash -ghost init --scope apps/checkout --with-intent +ghost init --scope apps/checkout ghost stack apps/checkout ghost lint --all ghost verify --all @@ -123,9 +120,7 @@ ghost verify --all Uncommitted or unmerged fingerprint edits are drafts. Checked-in `fingerprint/` core files are canonical. -Use optional `fingerprint/memory/intent.md` only for human-authored or -human-approved intent. Use `fingerprint/memory/decisions/` for rationale that -explains why a product choice changed. Add deterministic checks sparingly, and -only when a rule can be enforced deterministically. +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 c91ef963..cd7889c9 100644 --- a/apps/docs/src/content/docs/getting-started.mdx +++ b/apps/docs/src/content/docs/getting-started.mdx @@ -23,10 +23,7 @@ The canonical portable fingerprint is a folder: prose.yml inventory.yml composition.yml - enforcement/checks.yml - memory/intent.md - memory/decisions/ - sources/cache/ + checks.yml ``` Generation starts from `prose.yml`, `inventory.yml`, and `composition.yml`. @@ -89,10 +86,9 @@ composition work: interviewing, reading repo evidence, drafting layer edits, and asking you to curate the claims. ```bash -ghost init --with-intent +ghost init ghost scan --format json -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ghost lint .ghost ghost verify .ghost --root . ``` @@ -107,9 +103,9 @@ The fingerprint records durable surface-composition guidance: 3. **Composition** - the patterns that make it intentional: rules, layouts, structures, flows, states, content, behavior, and visual arrangements. -Generated cache is optional source material. Curate durable prose, inventory, -and composition into the core layer files, then use normal Git review for -approval. For a fuller human-agent workflow, read +Raw repo signals are optional authoring evidence. Curate durable prose, +inventory, and composition into the core layer files, then use normal Git +review for approval. For a fuller human-agent workflow, read [Fingerprint Authoring](/docs/fingerprint-authoring). @@ -134,7 +130,7 @@ After implementation, run Ghost against the same fingerprint: ```bash ghost check --base main -ghost review --base main --include-memory +ghost review --base main ``` `ghost check` applies active deterministic gates from the resolved fingerprint diff --git a/apps/docs/src/generated/cli-manifest.json b/apps/docs/src/generated/cli-manifest.json index 1394dab6..9c55e730 100644 --- a/apps/docs/src/generated/cli-manifest.json +++ b/apps/docs/src/generated/cli-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-06-22T15:49:43.175Z", + "generatedAt": "2026-06-23T04:21:38.371Z", "tools": [ { "tool": "ghost", @@ -66,14 +66,6 @@ "takesValue": true, "negated": false }, - { - "rawName": "--with-intent", - "name": "withIntent", - "description": "Also create optional fingerprint/memory/intent.md for human-authored or human-approved intent", - "default": null, - "takesValue": false, - "negated": false - }, { "rawName": "--with-config", "name": "withConfig", @@ -242,24 +234,24 @@ }, { "tool": "ghost", - "name": "inventory", - "rawName": "inventory [path]", - "description": "Emit deterministic raw signals about a frontend repo as JSON for optional cache/source material: package manifests, language histogram, candidate config files, registry presence, top-level tree, and git remote.", + "name": "signals", + "rawName": "signals [path]", + "description": "Emit deterministic raw repo signals as JSON: package manifests, language histogram, candidate config files, registry presence, top-level tree, and git remote.", "group": "advanced", "defaultHelp": false, - "compactName": "inventory", - "summary": "Emit raw repo signals for optional cache material.", + "compactName": "signals", + "summary": "Emit raw repo signals for fingerprint authoring.", "options": [] }, { "tool": "ghost", "name": "describe", - "rawName": "describe [fingerprint]", - "description": "Print a section map of fingerprint/memory/intent.md or a markdown file (line ranges + token estimates).", + "rawName": "describe ", + "description": "Print a section map of a markdown file (line ranges + token estimates).", "group": "advanced", "defaultHelp": false, "compactName": "describe", - "summary": "Print intent or markdown section ranges.", + "summary": "Print markdown section ranges.", "options": [ { "rawName": "--format ", @@ -299,7 +291,7 @@ "group": "maintenance", "defaultHelp": false, "compactName": "survey", - "summary": "Run survey/cache helpers.", + "summary": "Run legacy survey helpers.", "options": [ { "rawName": "-o, --out ", @@ -813,14 +805,6 @@ "takesValue": true, "negated": false }, - { - "rawName": "--include-memory", - "name": "includeMemory", - "description": "Include accepted decisions as accepted_decisions in the advisory packet (flag name retained).", - "default": null, - "takesValue": false, - "negated": false - }, { "rawName": "--max-diff-bytes ", "name": "maxDiffBytes", diff --git a/docs/fingerprint-format.md b/docs/fingerprint-format.md index 620a9b92..17bade80 100644 --- a/docs/fingerprint-format.md +++ b/docs/fingerprint-format.md @@ -12,16 +12,7 @@ lives under `.ghost/fingerprint/`: prose.yml # core: surface intent inventory.yml # core: curated material and source links composition.yml # core: experience patterns - - enforcement/ - checks.yml # optional deterministic gates - - memory/ - intent.md # optional human-approved intent - decisions/ # optional accepted/rejected rationale - - sources/ - cache/ # optional refreshable generated observations + checks.yml # optional deterministic gates ``` Git is the staging and approval boundary: uncommitted or unmerged edits are @@ -51,8 +42,8 @@ summary: goals: - Preserve task-first documentation and product trust. principles: - - id: prose-before-cache - principle: Prose captures the intent behind the surface; generated cache only explains what exists. + - id: prose-before-material + principle: Prose captures the intent behind the surface; inventory points to replaceable material. experience_contracts: - id: review-cites-memory contract: Advisory review findings must cite the diff and relevant fingerprint refs. @@ -87,17 +78,13 @@ exemplars: why: Shows command facts and examples kept visually adjacent. refs: [composition.pattern:reference-before-decoration] sources: - - id: generated-inventory - kind: cache - ref: sources/cache/inventory.json - note: Refreshable observed repo facts. - id: ghost-ui-registry kind: registry ref: packages/ghost-ui/public/r/registry.json ``` -Supported `inventory.sources[].kind` values are `cache`, `registry`, `file`, -`url`, and `package`. Source links are provenance and orientation; they do not +Supported `inventory.sources[].kind` values are `registry`, `file`, `url`, and +`package`. Source links are provenance and orientation; they do not make generated material canonical by themselves. `composition.yml` captures the patterns that make a surface feel intentional: @@ -136,7 +123,7 @@ valid, such as `inventory.exemplars[].refs`. ## Enforcement -`fingerprint/enforcement/checks.yml` uses `ghost.checks/v1`. Checks are +`fingerprint/checks.yml` uses `ghost.checks/v1`. Checks are deterministic validation, not generation input. ```yaml @@ -185,36 +172,6 @@ errors, so teams can draft gates while curation catches up. Promote only rules that can be detected deterministically; taste stays in prose or composition until there is a reliable detector. -## Memory And Sources - -`fingerprint/memory/intent.md` is optional human-authored or human-approved -intent: constraints, tradeoffs, audience notes, and known exceptions that -should not be inferred from code alone. - -`fingerprint/memory/decisions/*.yml` stores accepted, rejected, or superseded -surface-composition rationale using `ghost.decision/v1`: - -```yaml -schema: ghost.decision/v1 -id: keep-reference-dense -status: accepted -title: Keep CLI reference pages dense -claim: CLI reference pages should remain compact and task-first. -rationale: Users arrive with a command-shaped task and need fast comparison. -scope: - surface_types: [reference-page] -evidence: - - path: apps/docs/src/content/docs/cli-reference.mdx -decided_at: '2026-06-04T00:00:00-04:00' -``` - -`ghost review --include-memory` reads accepted decisions. Rejected or -superseded decisions are history, not canonical instructions. - -`fingerprint/sources/cache/` is refreshable generated material. It can help an -agent update `inventory.yml`, but cache files never count as fingerprint -readiness by themselves. - ## Nested Packages Large repos can add scoped packages below the root: @@ -264,7 +221,7 @@ ghost scan --format json ghost lint .ghost ghost verify .ghost --root . ghost check --base main --format json -ghost review --base main --include-memory +ghost review --base main ghost emit review-command --path apps/checkout/review/page.tsx ghost relay gather apps/checkout/review/page.tsx ``` @@ -274,11 +231,10 @@ ghost relay gather apps/checkout/review/page.tsx contract. Useful `inventory` means topology scopes or surface types, curated building blocks, or exemplars. Useful `composition` means at least one pattern. -Use generated cache when observed repo facts are useful source material: +Use raw repo signals when observed repo facts are useful authoring evidence: ```bash -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ``` Curate durable conclusions into `prose.yml`, `inventory.yml`, or @@ -289,9 +245,7 @@ Curate durable conclusions into `prose.yml`, `inventory.yml`, or - Write durable surface intent in `prose.yml`. - Write curated repo material and exemplars in `inventory.yml`. - Write repeatable experience patterns in `composition.yml`. -- Write deterministic gates in `enforcement/checks.yml`. -- Write human-approved intent and rationale in `memory/`. -- Keep generated observations in `sources/cache/`. +- Write deterministic gates in `checks.yml`. - Prefer typed refs over prose-only cross-links. - Keep ids stable after review because refs and checks depend on them. - Let Git review approve changes to canonical fingerprint layers. diff --git a/docs/generation-loop.md b/docs/generation-loop.md index 44046dc8..16e4a28e 100644 --- a/docs/generation-loop.md +++ b/docs/generation-loop.md @@ -33,20 +33,18 @@ ghost relay gather apps/checkout/review/page.tsx 2. Select relevant situations, principles, experience contracts, and patterns. 3. Inspect matching inventory exemplars as concrete anchors. 4. Use `inventory.building_blocks` as curated material. -5. Use optional `.ghost/fingerprint/sources/cache/` only as source material. -6. Skim active checks in `.ghost/fingerprint/enforcement/checks.yml` so +5. Run `ghost signals` when raw repo observations would help find evidence. +6. Skim active checks in `.ghost/fingerprint/checks.yml` so generation avoids deterministic failures. -7. Use optional `fingerprint/memory/intent.md`, accepted decisions, and nested - stacks only when the project has opted into them. +7. Use nested stacks only when the project has opted into them. -Generated cache can help orient an agent: +Raw repo signals can help orient an agent: ```bash -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ``` -Cache answers what exists now and does not count toward scan readiness. +Signals answer what exists now and do not count toward scan readiness. `prose.yml` captures the intent behind the surface. Curated inventory points to building blocks and exemplars. `composition.yml` captures the patterns that make the surface feel intentional. @@ -65,13 +63,13 @@ stack and runs merged checks for each group. Only active checks can block. `ghost review` is advisory: ```bash -ghost review --base main --include-memory +ghost review --base main ``` Advisory review packets include the current diff, the split fingerprint core -layers, relevant inventory exemplars, active checks, optional accepted -decisions, and finding categories for fixes, intentional divergence, missing -missing grounding, experience gaps, and eval uncertainty. +layers, relevant inventory exemplars, active checks, and finding categories for +fixes, intentional divergence, missing fingerprint grounding, experience gaps, +and eval uncertainty. Review findings should cite the diff location, relevant fingerprint refs, relevant exemplars when useful, and any active check when blocking. @@ -83,8 +81,8 @@ smallest useful response: - Fix the generated or changed code. - Explain why a divergence is intentional. -- Update the split fingerprint package or optional rationale files when the - user asks to change the Ghost fingerprint. +- Update the split fingerprint package when the user asks to change the Ghost + fingerprint. ## CI diff --git a/docs/host-adapters.md b/docs/host-adapters.md index cdf6ed87..30dd5d82 100644 --- a/docs/host-adapters.md +++ b/docs/host-adapters.md @@ -12,9 +12,8 @@ Ghost provides: - `.ghost/fingerprint/` package loading and stack merging. - `fingerprint/prose.yml`, `fingerprint/inventory.yml`, and `fingerprint/composition.yml` as generation context. -- Optional `fingerprint/enforcement/checks.yml`, - `fingerprint/memory/intent.md`, `fingerprint/memory/decisions/`, and - `fingerprint/sources/cache/`. +- Optional `fingerprint/checks.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 fingerprint stack. @@ -33,9 +32,8 @@ Host adapters provide: - policy for when a finding blocks, comments, or remains advisory - normal Git review for fingerprint edits -Generated cache is optional source material. Adapters should not treat -`.ghost/fingerprint/sources/cache/inventory.json` as canonical inventory; the -checked-in core layer files remain the authority. +Raw repo signals are authoring evidence, not canonical inventory. The checked-in +core layer files remain the authority. ## Check Flow diff --git a/docs/ideas/fingerprint-first-architecture.md b/docs/ideas/fingerprint-first-architecture.md index 0e04f54f..645e27ea 100644 --- a/docs/ideas/fingerprint-first-architecture.md +++ b/docs/ideas/fingerprint-first-architecture.md @@ -29,9 +29,7 @@ consume, validate, compare, generate from, or govern that context. `inventory.yml` points to curated material and exemplars, and `composition.yml` captures the patterns that make it feel intentional. - Supporting material stays subordinate to the core layers: - `enforcement/checks.yml` validates deterministic obligations, - `memory/intent.md` and `memory/decisions/` preserve optional human-approved - rationale, and `sources/cache/` stores refreshable observations. + `checks.yml` validates deterministic obligations. - Tools are consumers or authors of the fingerprint. They may capture, validate, apply, govern, or compare the contract, but they do not replace it. - Drift is governance. `check`, `review`, `ack`, `track`, and `diverge` answer @@ -67,7 +65,7 @@ fingerprint lifecycle: | Lifecycle | Commands and consumers | Purpose | | --- | --- | --- | -| Capture | `init`, `inventory`, `scan` | Create the package, gather optional source material, and report layer readiness. | +| Capture | `init`, `signals`, `scan` | Create the package, inspect repo signals, and report layer readiness. | | Validate | `lint`, `verify` | Check schema shape, refs, evidence, exemplars, and deterministic package quality. | | Generate / apply | `relay gather`, host agents, future generation consumers | Give agents the upstream surface-composition contract before they build or revise. | | Govern | `check`, `review`, `ack`, `track`, `diverge` | Validate changed surfaces, produce advisory findings, and record stance toward divergence. | diff --git a/docs/language-fingerprints.md b/docs/language-fingerprints.md index 2c97dd5d..8ebf67c9 100644 --- a/docs/language-fingerprints.md +++ b/docs/language-fingerprints.md @@ -12,7 +12,7 @@ surface-composition concern: - `fingerprint/inventory.yml` points at copy material and external writing standards. - `fingerprint/composition.yml` carries copy patterns. -- `fingerprint/enforcement/checks.yml` carries the deterministic subset. +- `fingerprint/checks.yml` carries the deterministic subset. This document shows the mapping. Nothing here changes the `ghost.fingerprint/v1` schema. @@ -143,7 +143,7 @@ Only the mechanically detectable subset belongs in `checks.yml`. The required boilerplate today: ```yaml -# fingerprint/enforcement/checks.yml +# fingerprint/checks.yml checks: - id: no-banned-phrases title: Banned phrases stay out of user-facing copy diff --git a/packages/ghost-core/src/fingerprint-package.ts b/packages/ghost-core/src/fingerprint-package.ts index 4e4fd9bf..3076032f 100644 --- a/packages/ghost-core/src/fingerprint-package.ts +++ b/packages/ghost-core/src/fingerprint-package.ts @@ -3,10 +3,7 @@ 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 INTENT_FILENAME = "intent.md" as const; export const FINGERPRINT_FILENAME = "fingerprint.md" as const; -export const DECISIONS_DIRNAME = "decisions" as const; -export const CACHE_DIRNAME = "cache" as const; export interface FingerprintPackagePaths { dir: string; @@ -19,7 +16,4 @@ export interface FingerprintPackagePaths { /** Legacy direct markdown path; not part of the canonical root bundle. */ fingerprint: string; checks: string; - intent: string; - decisions: string; - cache: string; } diff --git a/packages/ghost-core/src/index.ts b/packages/ghost-core/src/index.ts index e06e8241..b8782c65 100644 --- a/packages/ghost-core/src/index.ts +++ b/packages/ghost-core/src/index.ts @@ -61,14 +61,11 @@ export { } from "./embedding/index.js"; // --- Map (ghost.map/v1) --- export { - CACHE_DIRNAME, CONFIG_FILENAME, - DECISIONS_DIRNAME, FINGERPRINT_FILENAME, FINGERPRINT_PACKAGE_DIR, FINGERPRINT_YML_FILENAME, type FingerprintPackagePaths, - INTENT_FILENAME, PATTERNS_FILENAME, RESOURCES_FILENAME, } from "./fingerprint-package.js"; @@ -89,24 +86,6 @@ export { slugifyScopeId, type TopLevelEntry, } from "./map/index.js"; -// --- Memory (ghost.decision/v1) --- -export type { - GhostDecisionDocument, - GhostDecisionLintIssue, - GhostDecisionLintReport, - GhostDecisionLintSeverity, - GhostDecisionStatus, - GhostExperienceEvidence, - GhostExperienceScope, -} from "./memory/index.js"; -export { - GHOST_DECISION_SCHEMA, - GHOST_DECISIONS_DIRNAME, - GhostDecisionSchema, - GhostExperienceEvidenceSchema, - GhostExperienceScopeSchema, - lintGhostDecision, -} from "./memory/index.js"; // --- Patterns (ghost.patterns/v1) --- export type { GhostCompositionAnatomy, diff --git a/packages/ghost-core/src/memory/index.ts b/packages/ghost-core/src/memory/index.ts deleted file mode 100644 index 663a16ea..00000000 --- a/packages/ghost-core/src/memory/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { lintGhostDecision } from "./lint.js"; -export { - GhostDecisionSchema, - GhostExperienceEvidenceSchema, - GhostExperienceScopeSchema, -} from "./schema.js"; -export type { - GhostDecisionDocument, - GhostDecisionLintIssue, - GhostDecisionLintReport, - GhostDecisionLintSeverity, - GhostDecisionStatus, - GhostExperienceEvidence, - GhostExperienceScope, -} from "./types.js"; -export { GHOST_DECISION_SCHEMA, GHOST_DECISIONS_DIRNAME } from "./types.js"; diff --git a/packages/ghost-core/src/memory/lint.ts b/packages/ghost-core/src/memory/lint.ts deleted file mode 100644 index 8ea25fec..00000000 --- a/packages/ghost-core/src/memory/lint.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ZodIssue } from "zod"; -import { GhostDecisionSchema } from "./schema.js"; -import type { - GhostDecisionLintIssue, - GhostDecisionLintReport, -} from "./types.js"; - -export function lintGhostDecision(input: unknown): GhostDecisionLintReport { - const result = GhostDecisionSchema.safeParse(input); - if (!result.success) return finalize(zodIssues(result.error.issues)); - return finalize([]); -} - -function zodIssues(issues: ZodIssue[]): GhostDecisionLintIssue[] { - return issues.map((issue) => ({ - severity: "error" as const, - rule: `schema/${issue.code}`, - message: issue.message, - path: formatZodPath(issue.path), - })); -} - -function formatZodPath(path: ZodIssue["path"]): string | undefined { - if (path.length === 0) return undefined; - return path.reduce((formatted, segment) => { - if (typeof segment === "number") return `${formatted}[${segment}]`; - const key = String(segment); - return formatted ? `${formatted}.${key}` : key; - }, ""); -} - -function finalize(issues: GhostDecisionLintIssue[]): GhostDecisionLintReport { - 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-core/src/memory/schema.ts b/packages/ghost-core/src/memory/schema.ts deleted file mode 100644 index 30330331..00000000 --- a/packages/ghost-core/src/memory/schema.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { z } from "zod"; -import { GHOST_DECISION_SCHEMA } from "./types.js"; - -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 NonEmptyStringArraySchema = z.array(z.string().min(1)).min(1); - -export const GhostExperienceScopeSchema = z - .object({ - roles: NonEmptyStringArraySchema.optional(), - scopes: NonEmptyStringArraySchema.optional(), - surface_types: NonEmptyStringArraySchema.optional(), - pattern_ids: NonEmptyStringArraySchema.optional(), - paths: NonEmptyStringArraySchema.optional(), - }) - .strict(); - -export const GhostExperienceEvidenceSchema = z - .object({ - path: z.string().min(1).optional(), - survey_surface_id: z.string().min(1).optional(), - locator: z.string().min(1).optional(), - note: z.string().min(1).optional(), - }) - .strict() - .refine( - (evidence) => - Boolean( - evidence.path || - evidence.survey_surface_id || - evidence.locator || - evidence.note, - ), - { - message: - "evidence must include at least one of path, survey_surface_id, locator, or note", - }, - ); - -export const GhostDecisionSchema = z - .object({ - schema: z.literal(GHOST_DECISION_SCHEMA), - id: SlugIdSchema, - status: z.enum(["accepted", "rejected", "superseded"]), - title: z.string().min(1), - claim: z.string().min(1), - rationale: z.string().min(1), - scope: GhostExperienceScopeSchema.optional(), - evidence: z.array(GhostExperienceEvidenceSchema).min(1), - decided_at: z.string().datetime({ offset: true }), - }) - .strict(); diff --git a/packages/ghost-core/src/memory/types.ts b/packages/ghost-core/src/memory/types.ts deleted file mode 100644 index 005a67e0..00000000 --- a/packages/ghost-core/src/memory/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const GHOST_DECISION_SCHEMA = "ghost.decision/v1" as const; - -export const GHOST_DECISIONS_DIRNAME = "decisions" as const; - -export type GhostDecisionStatus = "accepted" | "rejected" | "superseded"; - -export interface GhostExperienceScope { - roles?: string[]; - scopes?: string[]; - surface_types?: string[]; - pattern_ids?: string[]; - paths?: string[]; -} - -export interface GhostExperienceEvidence { - path?: string; - survey_surface_id?: string; - locator?: string; - note?: string; -} - -export interface GhostDecisionDocument { - schema: typeof GHOST_DECISION_SCHEMA; - id: string; - status: GhostDecisionStatus; - title: string; - claim: string; - rationale: string; - scope?: GhostExperienceScope; - evidence: GhostExperienceEvidence[]; - decided_at: string; -} - -export type GhostDecisionLintSeverity = "error" | "warning" | "info"; - -export interface GhostDecisionLintIssue { - severity: GhostDecisionLintSeverity; - rule: string; - message: string; - path?: string; -} - -export interface GhostDecisionLintReport { - issues: GhostDecisionLintIssue[]; - errors: number; - warnings: number; - info: number; -} diff --git a/packages/ghost-core/test/memory.test.ts b/packages/ghost-core/test/memory.test.ts deleted file mode 100644 index cb9182b6..00000000 --- a/packages/ghost-core/test/memory.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, it } from "vitest"; -import * as core from "../src/index.js"; - -const VALID_DECISION = { - schema: core.GHOST_DECISION_SCHEMA, - id: "checkout-reversibility", - status: "accepted", - title: "Reversibility before money movement", - claim: "Payment review must make reversibility visible before submission.", - rationale: "Users need confidence before committing money movement.", - scope: { - roles: ["design", "engineering", "pm", "qa"], - scopes: ["checkout"], - surface_types: ["payment-review"], - pattern_ids: ["confirmation-before-commit"], - }, - evidence: [ - { - path: "apps/checkout/review.tsx", - note: "Review step exposes edit affordances before submit.", - }, - ], - decided_at: "2026-05-17T00:00:00.000Z", -}; - -describe("Ghost decision rationale schemas", () => { - it("accepts a valid ghost.decision/v1 document", () => { - const report = core.lintGhostDecision(VALID_DECISION); - - expect(report.errors).toBe(0); - }); - - it("rejects a decision without auditable evidence", () => { - const report = core.lintGhostDecision({ - ...VALID_DECISION, - evidence: [], - }); - - expect(report.errors).toBeGreaterThan(0); - expect(report.issues.map((issue) => issue.path)).toContain("evidence"); - }); - - it("does not expose proposal-era memory symbols from private core", () => { - for (const symbol of [ - "GHOST_PROPOSAL_SCHEMA", - "GHOST_PROPOSALS_DIRNAME", - "GhostProposalActionSchema", - "GhostProposalSchema", - "lintGhostProposal", - ]) { - expect(core).not.toHaveProperty(symbol); - } - }); -}); diff --git a/packages/ghost-ui/.ghost/fingerprint/enforcement/checks.yml b/packages/ghost-ui/.ghost/fingerprint/checks.yml similarity index 100% rename from packages/ghost-ui/.ghost/fingerprint/enforcement/checks.yml rename to packages/ghost-ui/.ghost/fingerprint/checks.yml diff --git a/packages/ghost-ui/.ghost/fingerprint/inventory.yml b/packages/ghost-ui/.ghost/fingerprint/inventory.yml index b1e76982..348962cd 100644 --- a/packages/ghost-ui/.ghost/fingerprint/inventory.yml +++ b/packages/ghost-ui/.ghost/fingerprint/inventory.yml @@ -30,8 +30,4 @@ building_blocks: notes: - Ghost UI is registry-distributed implementation inventory, not an installable npm package or consuming-product experience memory. exemplars: [] -sources: - - id: generated-inventory - kind: cache - ref: sources/cache/inventory.json - note: Optional refreshable registry observations. +sources: [] diff --git a/packages/ghost-ui/.ghost/fingerprint/sources/cache/.gitkeep b/packages/ghost-ui/.ghost/fingerprint/sources/cache/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/packages/ghost-ui/.ghost/fingerprint/sources/cache/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/ghost/README.md b/packages/ghost/README.md index dc18f4ba..52d2c56f 100644 --- a/packages/ghost/README.md +++ b/packages/ghost/README.md @@ -37,7 +37,7 @@ command index. Create and validate the fingerprint package: ```bash -ghost init --with-intent +ghost init ghost scan --format json ghost lint .ghost ghost verify .ghost --root . @@ -53,7 +53,7 @@ Govern changes afterward: ```bash ghost check --base main -ghost review --base main --include-memory +ghost review --base main ``` Install the BYOA skill bundle so your host agent can author, brief, review, @@ -63,7 +63,7 @@ verify, remediate, and update fingerprints: ghost skill install ``` -Advanced commands such as `inventory`, `stack`, `compare`, `ack`, `track`, and +Advanced commands such as `signals`, `stack`, `compare`, `ack`, `track`, and `diverge` remain available in the full command index. Zero config for every verb. No API key is required. `OPENAI_API_KEY` / @@ -85,12 +85,13 @@ import { ## BYOA -Ghost is bring-your-own-agent. The CLI performs deterministic work: inventory, -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 context, brief and -generate work from it, review changes against it, verify generated UI, -remediate issues, and suggest fingerprint edits when the user asks. +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 +context, brief and generate work from it, review changes against it, verify +generated UI, remediate issues, and suggest fingerprint edits when the user +asks. ```text Set up the Ghost fingerprint for this repo. diff --git a/packages/ghost/src/cli.ts b/packages/ghost/src/cli.ts index 32246e74..f24daf51 100644 --- a/packages/ghost/src/cli.ts +++ b/packages/ghost/src/cli.ts @@ -233,10 +233,6 @@ export function buildCli(): ReturnType { "--memory-dir ", "Relative fingerprint package directory for host wrappers and stack discovery (env: GHOST_MEMORY_DIR; default: .ghost)", ) - .option( - "--include-memory", - "Include accepted decisions as accepted_decisions in the advisory packet (flag name retained).", - ) .option( "--max-diff-bytes ", "Maximum diff bytes to include in the review packet (default: 200000)", @@ -266,7 +262,6 @@ export function buildCli(): ReturnType { memoryDir: typeof opts.memoryDir === "string" ? opts.memoryDir : undefined, diffText, - includeAcceptedDecisions: Boolean(opts.includeMemory), maxDiffBytes, }); if (opts.format === "json") { diff --git a/packages/ghost/src/command-discovery.ts b/packages/ghost/src/command-discovery.ts index 9345760d..b1fe396f 100644 --- a/packages/ghost/src/command-discovery.ts +++ b/packages/ghost/src/command-discovery.ts @@ -27,7 +27,7 @@ const GROUPS: ReadonlyArray<{ { group: "core", title: "Core workflow" }, { group: "advanced", title: "Advanced/package inspection" }, { group: "compare", title: "Compare/stance" }, - { group: "maintenance", title: "Maintenance/cache" }, + { group: "maintenance", title: "Maintenance/legacy" }, ]; const COMMAND_DISCOVERY = [ @@ -102,18 +102,18 @@ const COMMAND_DISCOVERY = [ summary: "Inspect a nested fingerprint stack for repo paths.", }, { - name: "inventory", + name: "signals", group: "advanced", defaultHelp: false, - compactName: "inventory", - summary: "Emit raw repo signals for optional cache material.", + compactName: "signals", + summary: "Emit raw repo signals for fingerprint authoring.", }, { name: "describe", group: "advanced", defaultHelp: false, compactName: "describe", - summary: "Print intent or markdown section ranges.", + summary: "Print markdown section ranges.", }, { name: "compare", @@ -162,7 +162,7 @@ const COMMAND_DISCOVERY = [ group: "maintenance", defaultHelp: false, compactName: "survey", - summary: "Run survey/cache helpers.", + summary: "Run legacy survey helpers.", }, ] satisfies ReadonlyArray>; diff --git a/packages/ghost/src/context/entrypoint-markdown.ts b/packages/ghost/src/context/entrypoint-markdown.ts index df528297..809d3be4 100644 --- a/packages/ghost/src/context/entrypoint-markdown.ts +++ b/packages/ghost/src/context/entrypoint-markdown.ts @@ -1,5 +1,4 @@ import type { ContextEntrypoint, FingerprintGraphNode } from "./entrypoint.js"; -import type { PackageInventory } from "./package-context.js"; export function formatContextEntrypointMarkdown( entrypoint: ContextEntrypoint, @@ -17,7 +16,6 @@ export function formatContextEntrypointMarkdown( parts.push(formatActionContract(entrypoint)); parts.push(formatReadFirst(entrypoint)); parts.push(formatValidation(entrypoint)); - parts.push(formatGeneratedCache(entrypoint.generatedCache)); parts.push(formatSuggestedReads(entrypoint)); parts.push(formatOmissions(entrypoint)); parts.push(formatUseThisContext()); @@ -103,42 +101,6 @@ function formatValidation(entrypoint: ContextEntrypoint): string { return lines.join("\n"); } -function formatGeneratedCache(inventory: PackageInventory): string { - const lines = ["## Generated Cache"]; - lines.push( - "- Generated cache is optional source material, not canonical fingerprint context.", - ); - if (inventory.state === "missing") { - lines.push("- State: missing."); - return lines.join("\n"); - } - if (inventory.state === "unreadable") { - lines.push( - `- State: present at \`${inventory.path}\`, but it could not be read: ${inventory.error}.`, - ); - return lines.join("\n"); - } - lines.push(`- State: present at \`${inventory.path}\`.`); - pushJoined(lines, "Platform hints", inventory.summary.platform_hints, { - code: true, - }); - pushJoined(lines, "Build hints", inventory.summary.build_system_hints, { - code: true, - }); - pushJoined(lines, "Package manifests", inventory.summary.package_manifests, { - code: true, - }); - pushJoined( - lines, - "Config candidates", - inventory.summary.candidate_config_files, - { - code: true, - }, - ); - return lines.join("\n"); -} - function formatSuggestedReads(entrypoint: ContextEntrypoint): string { const lines = ["## Suggested Reads"]; for (const read of entrypoint.suggestedReads) { diff --git a/packages/ghost/src/context/entrypoint.ts b/packages/ghost/src/context/entrypoint.ts index cccd58e4..3086a066 100644 --- a/packages/ghost/src/context/entrypoint.ts +++ b/packages/ghost/src/context/entrypoint.ts @@ -11,7 +11,7 @@ import { sortNodes, unique, } from "./graph.js"; -import type { PackageContext, PackageInventory } from "./package-context.js"; +import type { PackageContext } from "./package-context.js"; export type { FingerprintGraph, @@ -53,7 +53,6 @@ export interface ContextEntrypoint { }; suggestedReads: Array<{ path: string; reason: string }>; omissions: Array<{ label: string; omitted: number; source: string }>; - generatedCache: PackageInventory; } export interface BuildContextEntrypointOptions { @@ -123,7 +122,6 @@ export function buildContextEntrypoint( selected, suggestedReads, omissions: buildOmissions(graph, selected), - generatedCache: context.inventory, }; } @@ -256,7 +254,7 @@ function expandOneHop( } function buildSuggestedReads( - context: PackageContext, + _context: PackageContext, selected: ContextEntrypoint["selected"], ): ContextEntrypoint["suggestedReads"] { const reads = new Map(); @@ -280,7 +278,7 @@ function buildSuggestedReads( } if (selected.checks.length > 0) { reads.set( - "fingerprint/enforcement/checks.yml", + "fingerprint/checks.yml", "active deterministic validation rules", ); } @@ -288,12 +286,6 @@ function buildSuggestedReads( const path = exemplar.appliesTo.paths[0]; if (path) reads.set(path, `source surface for ${exemplar.ref}`); } - if (context.intent?.trim()) { - reads.set( - "fingerprint/memory/intent.md", - "supplemental human-authored intent", - ); - } if (reads.size === 0) { reads.set("fingerprint/prose.yml", "global fingerprint intent"); reads.set("fingerprint/inventory.yml", "topology and exemplars"); @@ -377,7 +369,7 @@ function buildOmissions( { label: "Active checks", omitted: Math.max(0, totals.checks - selected.checks.length), - source: "fingerprint/enforcement/checks.yml", + source: "fingerprint/checks.yml", }, ]; } diff --git a/packages/ghost/src/context/graph.ts b/packages/ghost/src/context/graph.ts index 5e603450..7acc453e 100644 --- a/packages/ghost/src/context/graph.ts +++ b/packages/ghost/src/context/graph.ts @@ -205,7 +205,7 @@ export function buildFingerprintGraph( check.repair ? `Repair: ${check.repair}` : "", detectorSummary(check), ].filter(Boolean), - sourceFile: "fingerprint/enforcement/checks.yml", + sourceFile: "fingerprint/checks.yml", appliesTo: applicabilityFromCheck(check), }); addRefEdges(ref, check.derivation?.prose, "check prose derivation"); diff --git a/packages/ghost/src/context/package-context.ts b/packages/ghost/src/context/package-context.ts index 9ed71826..a4f9709c 100644 --- a/packages/ghost/src/context/package-context.ts +++ b/packages/ghost/src/context/package-context.ts @@ -1,4 +1,3 @@ -import { join } from "node:path"; import { parse as parseYaml } from "yaml"; import { type GhostChecksDocument, @@ -12,26 +11,6 @@ import { loadFingerprintPackage, } from "../scan/fingerprint-package.js"; -export interface PackageInventorySummary { - root?: string; - platform_hints: string[]; - build_system_hints: string[]; - language_histogram: Array<{ name: string; files: number }>; - package_manifests: string[]; - candidate_config_files: string[]; - registry_files: string[]; - top_level_tree: Array<{ path: string; kind: string; child_count: number }>; - config?: { - targets?: Array<{ id: string; platform?: string; roots?: string[] }>; - libraries?: Array<{ id: string; role?: string; source?: string }>; - }; -} - -export type PackageInventory = - | { state: "missing"; path: string } - | { state: "present"; path: string; summary: PackageInventorySummary } - | { state: "unreadable"; path: string; error: string }; - export interface PackageContext { name: string; fingerprintDir?: string; @@ -47,19 +26,15 @@ export interface PackageContext { }; checks?: GhostChecksDocument; checksRaw?: string; - intent?: string; - inventory: PackageInventory; } export async function loadPackageContext( paths: FingerprintPackagePaths, nameOverride?: string, ): Promise { - const [loaded, checksRaw, intent, inventory] = await Promise.all([ + const [loaded, checksRaw] = await Promise.all([ loadFingerprintPackage(paths), readOptional(paths.checks), - readOptional(paths.intent), - loadPackageInventory(paths), ]); const fingerprint = loaded.fingerprint; @@ -75,39 +50,20 @@ export async function loadPackageContext( }, checks, checksRaw, - intent, - inventory, }; } -export async function loadPackageInventory( - paths: Pick, -): Promise { - const path = join(paths.cache, "inventory.json"); - const raw = await readOptional(path); - if (!raw) return { state: "missing", path }; - try { - return { state: "present", path, summary: summarizeInventory(raw) }; - } catch (err) { - return { - state: "unreadable", - path, - error: err instanceof Error ? err.message : String(err), - }; - } -} - function parseChecks( raw: string, fingerprint: GhostFingerprintDocument, ): GhostChecksDocument { - const parsed = parseYamlSafe(raw, "fingerprint/enforcement/checks.yml"); + const parsed = parseYamlSafe(raw, "fingerprint/checks.yml"); const report = lintGhostChecks(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/enforcement/checks.yml failed lint with ${report.errors} error(s): ${ + `fingerprint/checks.yml failed lint with ${report.errors} error(s): ${ first?.message ?? "invalid checks" }${suffix}`, ); @@ -115,9 +71,7 @@ function parseChecks( const result = GhostChecksSchema.safeParse(parsed); if (!result.success) { - throw new Error( - "fingerprint/enforcement/checks.yml failed schema validation.", - ); + throw new Error("fingerprint/checks.yml failed schema validation."); } return result.data as GhostChecksDocument; } @@ -134,81 +88,6 @@ function parseYamlSafe(raw: string, label: string): unknown { } } -function summarizeInventory(raw: string): PackageInventorySummary { - const parsed = JSON.parse(raw) as Record; - return { - root: typeof parsed.root === "string" ? parsed.root : undefined, - platform_hints: stringArray(parsed.platform_hints).slice(0, 8), - build_system_hints: stringArray(parsed.build_system_hints).slice(0, 8), - language_histogram: recordArray(parsed.language_histogram) - .map((entry) => ({ - name: typeof entry.name === "string" ? entry.name : "", - files: typeof entry.files === "number" ? entry.files : 0, - })) - .filter((entry) => entry.name) - .slice(0, 8), - package_manifests: stringArray(parsed.package_manifests).slice(0, 12), - candidate_config_files: stringArray(parsed.candidate_config_files).slice( - 0, - 12, - ), - registry_files: stringArray(parsed.registry_files).slice(0, 8), - top_level_tree: recordArray(parsed.top_level_tree) - .map((entry) => ({ - path: typeof entry.path === "string" ? entry.path : "", - kind: typeof entry.kind === "string" ? entry.kind : "", - child_count: - typeof entry.child_count === "number" ? entry.child_count : 0, - })) - .filter((entry) => entry.path && entry.kind) - .slice(0, 12), - config: summarizeInventoryConfig(parsed.config), - }; -} - -function summarizeInventoryConfig( - value: unknown, -): PackageInventorySummary["config"] { - if (!value || typeof value !== "object" || Array.isArray(value)) { - return undefined; - } - const record = value as Record; - const targets = recordArray(record.targets) - .map((entry) => ({ - id: typeof entry.id === "string" ? entry.id : "", - ...(typeof entry.platform === "string" - ? { platform: entry.platform } - : {}), - roots: stringArray(entry.roots), - })) - .filter((entry) => entry.id) - .slice(0, 8); - const libraries = recordArray(record.libraries) - .map((entry) => ({ - id: typeof entry.id === "string" ? entry.id : "", - ...(typeof entry.role === "string" ? { role: entry.role } : {}), - ...(typeof entry.source === "string" ? { source: entry.source } : {}), - })) - .filter((entry) => entry.id) - .slice(0, 8); - if (targets.length === 0 && libraries.length === 0) return undefined; - return { targets, libraries }; -} - -function stringArray(value: unknown): string[] { - return Array.isArray(value) - ? value.filter((entry): entry is string => typeof entry === "string") - : []; -} - -function recordArray(value: unknown): Array> { - return Array.isArray(value) - ? value.filter((entry): entry is Record => - Boolean(entry && typeof entry === "object" && !Array.isArray(entry)), - ) - : []; -} - const readOptional = readOptionalUtf8; function inferPackageName(fingerprint: GhostFingerprintDocument): string { diff --git a/packages/ghost/src/context/package-review-command.ts b/packages/ghost/src/context/package-review-command.ts index 08825745..28208d3e 100644 --- a/packages/ghost/src/context/package-review-command.ts +++ b/packages/ghost/src/context/package-review-command.ts @@ -15,7 +15,7 @@ export interface EmitPackageReviewInput { const REVIEW_FINDING_CATEGORIES = [ "fix", "intentional-divergence", - "missing-memory", + "missing-fingerprint", "experience-gap", "eval-uncertainty", ] as const; @@ -25,7 +25,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/ core files and enforcement checks. + * Full canonical truth remains in fingerprint/ core files and deterministic checks. */ export function emitPackageReviewCommand( input: EmitPackageReviewInput, @@ -74,7 +74,7 @@ function packageWorkflowSection(context: PackageContext): string { 4. Inspect relevant inventory exemplars as concrete anchors for what good looks like. 5. Use inventory building blocks only as replaceable material that may help satisfy the selected prose and composition. 6. Run \`ghost check${memoryDirFlag}\` when a diff is available. Active checks are deterministic and can block. -7. Run \`ghost review${memoryDirFlag}\` for the advisory packet when you need full diff context and fingerprint excerpts; add \`--include-memory\` only when accepted decisions matter. +7. Run \`ghost review${memoryDirFlag}\` for the advisory packet when you need full diff context and fingerprint excerpts. 8. Cite the diff location, fingerprint core layer refs, relevant exemplars when useful, and any active check when a finding blocks.`; } @@ -87,9 +87,9 @@ Only findings backed by an active check should be treated as blocking. Everythin Review only what fingerprint layers or active checks make relevant to the product surface. -When fingerprint layers are silent, local evidence can still support advisory critique. Label those findings as provisional and non-Ghost-backed, and ground them in nearby product surfaces, local components, token or copy conventions, or optional rationale files when present. Ask the human before assessing high-risk, irreversible, privacy/security/legal, or product-surface-defining choices. +When fingerprint layers are silent, local evidence can still support advisory critique. Label those findings as provisional and non-Ghost-backed, and ground them in nearby product surfaces, local components, or token and copy conventions. Ask the human before assessing high-risk, irreversible, privacy/security/legal, or product-surface-defining choices. -If the diff reveals missing fingerprint grounding or layer coverage, report \`missing-memory\` or \`experience-gap\` as a review finding. Do not silently rewrite the Ghost package during review; fingerprint edits are ordinary edits that go through normal Git review.`; +If the diff reveals missing fingerprint grounding or layer coverage, report \`missing-fingerprint\` or \`experience-gap\` as a review finding. Do not silently rewrite the Ghost package during review; fingerprint edits are ordinary edits that go through normal Git review.`; } function packageFingerprintIndex(context: PackageContext): string { @@ -150,7 +150,7 @@ function formatSummary(context: PackageContext): string { function formatSituations(situations: GhostFingerprintSituation[]): string { if (situations.length === 0) { - return "### Situations\n- No situations recorded yet. Treat unclear obligations as `missing-memory`."; + return "### Situations\n- No situations recorded yet. Treat unclear obligations as `missing-fingerprint`."; } const lines = ["### Situations"]; for (const situation of situations.slice(0, 8)) { @@ -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/enforcement/checks.yml\` adds deterministic active checks.`; +No active checks are recorded. Review remains advisory unless \`fingerprint/checks.yml\` adds deterministic active checks.`; } const lines = ["## Active Checks", ""]; for (const check of activeChecks.slice(0, 12)) { @@ -271,7 +271,7 @@ No active checks are recorded. Review remains advisory unless \`fingerprint/enfo } if (activeChecks.length > 12) { lines.push( - `- ${activeChecks.length - 12} more active check(s); read \`fingerprint/enforcement/checks.yml\` before deciding whether a finding blocks.`, + `- ${activeChecks.length - 12} more active check(s); read \`fingerprint/checks.yml\` before deciding whether a finding blocks.`, ); } return lines.join("\n"); @@ -281,7 +281,7 @@ function packageReviewFooter(context: PackageContext): string { const fingerprintDir = context.fingerprintDir ?? ".ghost"; return `--- -Generated from \`${fingerprintDir}/fingerprint/\` for ${context.name}. Re-run \`ghost emit review-command${stackFingerprintDirFlag(context)}\` after updating fingerprint core layers, enforcement checks, or optional rationale files.`; +Generated from \`${fingerprintDir}/fingerprint/\` for ${context.name}. Re-run \`ghost emit review-command${stackFingerprintDirFlag(context)}\` after updating fingerprint core layers or deterministic checks.`; } function stackFingerprintDirFlag(context: PackageContext): string { diff --git a/packages/ghost/src/fingerprint-commands.ts b/packages/ghost/src/fingerprint-commands.ts index 7bde37a0..92de9442 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 { dirname, resolve } from "node:path"; +import { basename, dirname, resolve } from "node:path"; import type { CAC } from "cac"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; import { @@ -34,11 +34,12 @@ import { registerInitCommand } from "./init-command.js"; import { detectFileKind, lintDetectedFileKind } from "./scan/file-kind.js"; import { discoverGhostPackages, + FINGERPRINT_DIRNAME, fingerprintPackageDisplayPath, - inventory, normalizeMemoryDir, resolveMemoryDirDefault, scanStatus, + signals, } from "./scan/index.js"; import { registerEmitCommand } from "./scan-emit-command.js"; import { registerStackCommand } from "./scan-stack-command.js"; @@ -48,7 +49,7 @@ import { registerStackCommand } from "./scan-stack-command.js"; * * Verbs author and validate the root `.ghost/` fingerprint package: * `lint` (schema check, auto-detects file kind), `verify` (cross-artifact - * fidelity), `describe` (section ranges + token estimates for intent or direct + * fidelity), `describe` (section ranges + token estimates for direct * fingerprint markdown), `diff` (structural prose-level diff between direct * fingerprint files), `emit` (derive review-command artifacts), and `survey` * operations for deterministic `ghost.survey/v1` @@ -241,14 +242,9 @@ export function registerFingerprintCommands(cli: CAC): void { ` config (config.yml): ${fmt(status.config.state)}\n`, ); process.stdout.write( - ` checks (fingerprint/enforcement/checks.yml): ${fmt(status.checks.state)}\n`, - ); - process.stdout.write( - ` cache (fingerprint/sources/cache/): ${fmt(status.cache.state)}\n`, - ); - process.stdout.write( - ` intent (fingerprint/memory/intent.md): ${fmt(status.intent.state)}\n\n`, + ` checks (fingerprint/checks.yml): ${fmt(status.checks.state)}\n`, ); + process.stdout.write("\n"); if (status.recommended_next) { process.stdout.write( `next: run the ${status.recommended_next} stage\n`, @@ -332,16 +328,16 @@ export function registerFingerprintCommands(cli: CAC): void { registerStackCommand(cli); - // --- inventory --- + // --- signals --- cli .command( - "inventory [path]", - "Emit deterministic raw signals about a frontend repo as JSON for optional cache/source material: package manifests, language histogram, candidate config files, registry presence, top-level tree, and git remote.", + "signals [path]", + "Emit deterministic raw repo signals as JSON: package manifests, language histogram, candidate config files, registry presence, top-level tree, and git remote.", ) .action(async (path: string | undefined) => { try { const target = resolve(process.cwd(), path ?? "."); - const out = inventory(target); + const out = signals(target); process.stdout.write(`${JSON.stringify(out, null, 2)}\n`); process.exit(0); } catch (err) { @@ -355,15 +351,13 @@ export function registerFingerprintCommands(cli: CAC): void { // --- describe --- cli .command( - "describe [fingerprint]", - "Print a section map of fingerprint/memory/intent.md or a markdown file (line ranges + token estimates).", + "describe ", + "Print a section map of a markdown file (line ranges + token estimates).", ) .option("--format ", "Output format: cli or json", { default: "cli" }) - .action(async (path: string | undefined, opts) => { + .action(async (path: string, opts) => { try { - const target = path - ? resolve(process.cwd(), path) - : resolveFingerprintPackage(undefined, process.cwd()).intent; + const target = resolve(process.cwd(), path); const raw = await readFile(target, "utf-8"); const layout = layoutFingerprint(raw); if (opts.format === "json") { @@ -607,7 +601,6 @@ async function nestedPackageStatus( ...pkg, fingerprint: status.fingerprint, checks: status.checks, - intent: status.intent, readiness: status.readiness, }; }), @@ -621,7 +614,6 @@ interface NestedPackageStatus { fingerprint_dir: string; fingerprint: Awaited>["fingerprint"]; checks: Awaited>["checks"]; - intent: Awaited>["intent"]; readiness: Awaited>["readiness"]; } @@ -644,7 +636,10 @@ async function loadSiblingFingerprintForChecksLint( fileTarget: string, ): Promise { const checksDir = dirname(fileTarget); - const packageRoot = resolve(checksDir, "..", ".."); + const packageRoot = + basename(checksDir) === FINGERPRINT_DIRNAME + ? resolve(checksDir, "..") + : resolve(checksDir, "..", ".."); try { return ( await loadFingerprintPackage(resolveFingerprintPackage(packageRoot)) @@ -794,7 +789,7 @@ function summarizeSurveyPatterns(survey: Survey): GhostPatternsDocument { traits: traitsForPattern(entry.value, survey), evidence: entry.evidence, advisory: [ - "Use as advisory composition evidence; deterministic enforcement belongs in fingerprint/enforcement/checks.yml.", + "Use as advisory composition evidence; deterministic checks belong in fingerprint/checks.yml.", ], })), advisory: { @@ -808,7 +803,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/memory/intent.md as human authority when present.", + "Treat fingerprint/prose.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as the canonical authoring layers.", ]; } @@ -819,14 +814,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/memory/intent.md as human authority when present.", + "Treat fingerprint/prose.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as the canonical authoring layers.", ]; } return [ "Identify the surface type before assessing composition.", "Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.", - "Treat fingerprint/memory/intent.md as human authority when present.", + "Treat fingerprint/prose.yml, fingerprint/inventory.yml, and fingerprint/composition.yml as the canonical authoring layers.", ]; } diff --git a/packages/ghost/src/fingerprint.ts b/packages/ghost/src/fingerprint.ts index 1bc83d2b..6c26a2f2 100644 --- a/packages/ghost/src/fingerprint.ts +++ b/packages/ghost/src/fingerprint.ts @@ -5,22 +5,17 @@ export { parseBody } from "./scan/body.js"; export type { DesignDecision } from "./scan/compose.js"; export { mergeFingerprint } from "./scan/compose.js"; export { - CACHE_DIRNAME, CHECKS_FILENAME, CONFIG_FILENAME, FINGERPRINT_COMPOSITION_FILENAME, FINGERPRINT_DIRNAME, - FINGERPRINT_ENFORCEMENT_DIRNAME, FINGERPRINT_FILENAME, FINGERPRINT_INVENTORY_FILENAME, FINGERPRINT_MANIFEST_FILENAME, - FINGERPRINT_MEMORY_DIRNAME, FINGERPRINT_PACKAGE_DIR, FINGERPRINT_PROSE_FILENAME, - FINGERPRINT_SOURCES_DIRNAME, FINGERPRINT_YML_FILENAME, FINGERPRINTS_DIRNAME, - INTENT_FILENAME, PATTERNS_FILENAME, RESOURCES_FILENAME, SCOPE_SURVEYS_DIRNAME, diff --git a/packages/ghost/src/ghost-core/fingerprint-package.ts b/packages/ghost/src/ghost-core/fingerprint-package.ts index 61140f8a..57a60e2b 100644 --- a/packages/ghost/src/ghost-core/fingerprint-package.ts +++ b/packages/ghost/src/ghost-core/fingerprint-package.ts @@ -7,14 +7,8 @@ export const FINGERPRINT_MANIFEST_FILENAME = "manifest.yml" as const; export const FINGERPRINT_PROSE_FILENAME = "prose.yml" as const; export const FINGERPRINT_INVENTORY_FILENAME = "inventory.yml" as const; export const FINGERPRINT_COMPOSITION_FILENAME = "composition.yml" as const; -export const FINGERPRINT_ENFORCEMENT_DIRNAME = "enforcement" as const; -export const FINGERPRINT_MEMORY_DIRNAME = "memory" as const; -export const FINGERPRINT_SOURCES_DIRNAME = "sources" as const; export const CONFIG_FILENAME = "config.yml" as const; -export const INTENT_FILENAME = "intent.md" as const; export const FINGERPRINT_FILENAME = "fingerprint.md" as const; -export const DECISIONS_DIRNAME = "decisions" as const; -export const CACHE_DIRNAME = "cache" as const; export interface FingerprintPackagePaths { dir: string; @@ -23,7 +17,6 @@ export interface FingerprintPackagePaths { prose: string; inventory: string; composition: string; - enforcement: string; fingerprintYml: string; config: string; resources: string; @@ -33,9 +26,4 @@ export interface FingerprintPackagePaths { /** Legacy direct markdown path; not part of the canonical root bundle. */ fingerprint: string; checks: string; - memory: string; - intent: string; - decisions: string; - sources: string; - cache: string; } diff --git a/packages/ghost/src/ghost-core/fingerprint/schema.ts b/packages/ghost/src/ghost-core/fingerprint/schema.ts index 3a17c5e9..d57744c1 100644 --- a/packages/ghost/src/ghost-core/fingerprint/schema.ts +++ b/packages/ghost/src/ghost-core/fingerprint/schema.ts @@ -175,7 +175,6 @@ export const GhostFingerprintInventoryBuildingBlocksSchema = z .strict(); export const GhostFingerprintInventorySourceKindSchema = z.enum([ - "cache", "registry", "file", "url", diff --git a/packages/ghost/src/ghost-core/fingerprint/types.ts b/packages/ghost/src/ghost-core/fingerprint/types.ts index e29648f3..a45c46c5 100644 --- a/packages/ghost/src/ghost-core/fingerprint/types.ts +++ b/packages/ghost/src/ghost-core/fingerprint/types.ts @@ -77,7 +77,6 @@ export interface GhostFingerprintInventoryBuildingBlocks { } export type GhostFingerprintInventorySourceKind = - | "cache" | "registry" | "file" | "url" diff --git a/packages/ghost/src/ghost-core/index.ts b/packages/ghost/src/ghost-core/index.ts index 8c7435d3..e7b7dd78 100644 --- a/packages/ghost/src/ghost-core/index.ts +++ b/packages/ghost/src/ghost-core/index.ts @@ -115,22 +115,16 @@ export { } from "./fingerprint/index.js"; // --- Map (ghost.map/v1) --- export { - CACHE_DIRNAME, CONFIG_FILENAME, - DECISIONS_DIRNAME, FINGERPRINT_COMPOSITION_FILENAME, FINGERPRINT_DIRNAME, - FINGERPRINT_ENFORCEMENT_DIRNAME, FINGERPRINT_FILENAME, FINGERPRINT_INVENTORY_FILENAME, FINGERPRINT_MANIFEST_FILENAME, - FINGERPRINT_MEMORY_DIRNAME, FINGERPRINT_PACKAGE_DIR, FINGERPRINT_PROSE_FILENAME, - FINGERPRINT_SOURCES_DIRNAME, FINGERPRINT_YML_FILENAME, type FingerprintPackagePaths, - INTENT_FILENAME, PATTERNS_FILENAME, RESOURCES_FILENAME, } from "./fingerprint-package.js"; @@ -152,24 +146,6 @@ export { slugifyScopeId, type TopLevelEntry, } from "./map/index.js"; -// --- Memory (ghost.decision/v1) --- -export type { - GhostDecisionDocument, - GhostDecisionLintIssue, - GhostDecisionLintReport, - GhostDecisionLintSeverity, - GhostDecisionStatus, - GhostExperienceEvidence, - GhostExperienceScope, -} from "./memory/index.js"; -export { - GHOST_DECISION_SCHEMA, - GHOST_DECISIONS_DIRNAME, - GhostDecisionSchema, - GhostExperienceEvidenceSchema, - GhostExperienceScopeSchema, - lintGhostDecision, -} from "./memory/index.js"; // --- Patterns (ghost.patterns/v1) --- export type { GhostCompositionAnatomy, diff --git a/packages/ghost/src/ghost-core/memory/index.ts b/packages/ghost/src/ghost-core/memory/index.ts deleted file mode 100644 index 663a16ea..00000000 --- a/packages/ghost/src/ghost-core/memory/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { lintGhostDecision } from "./lint.js"; -export { - GhostDecisionSchema, - GhostExperienceEvidenceSchema, - GhostExperienceScopeSchema, -} from "./schema.js"; -export type { - GhostDecisionDocument, - GhostDecisionLintIssue, - GhostDecisionLintReport, - GhostDecisionLintSeverity, - GhostDecisionStatus, - GhostExperienceEvidence, - GhostExperienceScope, -} from "./types.js"; -export { GHOST_DECISION_SCHEMA, GHOST_DECISIONS_DIRNAME } from "./types.js"; diff --git a/packages/ghost/src/ghost-core/memory/lint.ts b/packages/ghost/src/ghost-core/memory/lint.ts deleted file mode 100644 index 8ea25fec..00000000 --- a/packages/ghost/src/ghost-core/memory/lint.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ZodIssue } from "zod"; -import { GhostDecisionSchema } from "./schema.js"; -import type { - GhostDecisionLintIssue, - GhostDecisionLintReport, -} from "./types.js"; - -export function lintGhostDecision(input: unknown): GhostDecisionLintReport { - const result = GhostDecisionSchema.safeParse(input); - if (!result.success) return finalize(zodIssues(result.error.issues)); - return finalize([]); -} - -function zodIssues(issues: ZodIssue[]): GhostDecisionLintIssue[] { - return issues.map((issue) => ({ - severity: "error" as const, - rule: `schema/${issue.code}`, - message: issue.message, - path: formatZodPath(issue.path), - })); -} - -function formatZodPath(path: ZodIssue["path"]): string | undefined { - if (path.length === 0) return undefined; - return path.reduce((formatted, segment) => { - if (typeof segment === "number") return `${formatted}[${segment}]`; - const key = String(segment); - return formatted ? `${formatted}.${key}` : key; - }, ""); -} - -function finalize(issues: GhostDecisionLintIssue[]): GhostDecisionLintReport { - 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/ghost-core/memory/schema.ts b/packages/ghost/src/ghost-core/memory/schema.ts deleted file mode 100644 index 30330331..00000000 --- a/packages/ghost/src/ghost-core/memory/schema.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { z } from "zod"; -import { GHOST_DECISION_SCHEMA } from "./types.js"; - -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 NonEmptyStringArraySchema = z.array(z.string().min(1)).min(1); - -export const GhostExperienceScopeSchema = z - .object({ - roles: NonEmptyStringArraySchema.optional(), - scopes: NonEmptyStringArraySchema.optional(), - surface_types: NonEmptyStringArraySchema.optional(), - pattern_ids: NonEmptyStringArraySchema.optional(), - paths: NonEmptyStringArraySchema.optional(), - }) - .strict(); - -export const GhostExperienceEvidenceSchema = z - .object({ - path: z.string().min(1).optional(), - survey_surface_id: z.string().min(1).optional(), - locator: z.string().min(1).optional(), - note: z.string().min(1).optional(), - }) - .strict() - .refine( - (evidence) => - Boolean( - evidence.path || - evidence.survey_surface_id || - evidence.locator || - evidence.note, - ), - { - message: - "evidence must include at least one of path, survey_surface_id, locator, or note", - }, - ); - -export const GhostDecisionSchema = z - .object({ - schema: z.literal(GHOST_DECISION_SCHEMA), - id: SlugIdSchema, - status: z.enum(["accepted", "rejected", "superseded"]), - title: z.string().min(1), - claim: z.string().min(1), - rationale: z.string().min(1), - scope: GhostExperienceScopeSchema.optional(), - evidence: z.array(GhostExperienceEvidenceSchema).min(1), - decided_at: z.string().datetime({ offset: true }), - }) - .strict(); diff --git a/packages/ghost/src/ghost-core/memory/types.ts b/packages/ghost/src/ghost-core/memory/types.ts deleted file mode 100644 index 005a67e0..00000000 --- a/packages/ghost/src/ghost-core/memory/types.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const GHOST_DECISION_SCHEMA = "ghost.decision/v1" as const; - -export const GHOST_DECISIONS_DIRNAME = "decisions" as const; - -export type GhostDecisionStatus = "accepted" | "rejected" | "superseded"; - -export interface GhostExperienceScope { - roles?: string[]; - scopes?: string[]; - surface_types?: string[]; - pattern_ids?: string[]; - paths?: string[]; -} - -export interface GhostExperienceEvidence { - path?: string; - survey_surface_id?: string; - locator?: string; - note?: string; -} - -export interface GhostDecisionDocument { - schema: typeof GHOST_DECISION_SCHEMA; - id: string; - status: GhostDecisionStatus; - title: string; - claim: string; - rationale: string; - scope?: GhostExperienceScope; - evidence: GhostExperienceEvidence[]; - decided_at: string; -} - -export type GhostDecisionLintSeverity = "error" | "warning" | "info"; - -export interface GhostDecisionLintIssue { - severity: GhostDecisionLintSeverity; - rule: string; - message: string; - path?: string; -} - -export interface GhostDecisionLintReport { - issues: GhostDecisionLintIssue[]; - errors: number; - warnings: number; - info: number; -} diff --git a/packages/ghost/src/init-command.ts b/packages/ghost/src/init-command.ts index 9e7db210..c25828cf 100644 --- a/packages/ghost/src/init-command.ts +++ b/packages/ghost/src/init-command.ts @@ -21,10 +21,6 @@ export function registerInitCommand(cli: CAC): void { "--memory-dir ", "Relative fingerprint package directory for host wrappers, init --scope, and default root init (env: GHOST_MEMORY_DIR; default: .ghost)", ) - .option( - "--with-intent", - "Also create optional fingerprint/memory/intent.md for human-authored or human-approved intent", - ) .option( "--with-config", "Also create optional config.yml for implementation roots and reference registries/libraries", @@ -74,7 +70,6 @@ export function registerInitCommand(cli: CAC): void { ? memoryDirFromOpts(opts) : undefined; const initOptions = { - withIntent: Boolean(opts.withIntent), withConfig: Boolean(opts.withConfig || opts.reference), reference: typeof opts.reference === "string" ? opts.reference : undefined, @@ -105,7 +100,6 @@ export function registerInitCommand(cli: CAC): void { process.stdout.write( `${JSON.stringify( initCommandOutput(paths, { - includeIntent: Boolean(opts.withIntent), includeConfig: Boolean(opts.withConfig || opts.reference), }), null, @@ -120,13 +114,10 @@ export function registerInitCommand(cli: CAC): void { process.stdout.write(` prose.yml: ${paths.prose}\n`); process.stdout.write(` inventory.yml: ${paths.inventory}\n`); process.stdout.write(` composition.yml: ${paths.composition}\n`); - process.stdout.write(` enforcement/checks.yml: ${paths.checks}\n`); + process.stdout.write(` checks.yml: ${paths.checks}\n`); if (opts.withConfig || opts.reference) { process.stdout.write(` config.yml: ${paths.config}\n`); } - if (opts.withIntent) { - process.stdout.write(` memory/intent.md: ${paths.intent}\n`); - } } process.exit(0); } catch (err) { @@ -144,7 +135,7 @@ function memoryDirFromOpts(opts: { memoryDir?: unknown }): string { function initCommandOutput( paths: ReturnType, - options: { includeIntent: boolean; includeConfig: boolean }, + options: { includeConfig: boolean }, ): Record { return { dir: paths.dir, @@ -155,6 +146,5 @@ function initCommandOutput( composition: paths.composition, ...(options.includeConfig ? { config: paths.config } : {}), checks: paths.checks, - ...(options.includeIntent ? { intent: paths.intent } : {}), }; } diff --git a/packages/ghost/src/monorepo-init-command.ts b/packages/ghost/src/monorepo-init-command.ts index eb35b1e8..76ca961c 100644 --- a/packages/ghost/src/monorepo-init-command.ts +++ b/packages/ghost/src/monorepo-init-command.ts @@ -94,7 +94,6 @@ export async function initMonorepoFingerprintPackages(options: { return { root: initPackageOutput(root, { - includeIntent: Boolean(options.initOptions.withIntent), includeConfig: Boolean(options.initOptions.withConfig), }), rootState, @@ -205,7 +204,7 @@ function formatCommandArg(value: string): string { function initPackageOutput( paths: FingerprintPackagePaths, - options: { includeIntent: boolean; includeConfig: boolean }, + options: { includeConfig: boolean }, ): Record { return { dir: paths.dir, @@ -216,6 +215,5 @@ function initPackageOutput( composition: paths.composition, ...(options.includeConfig ? { config: paths.config } : {}), checks: paths.checks, - ...(options.includeIntent ? { intent: paths.intent } : {}), }; } diff --git a/packages/ghost/src/review-packet.ts b/packages/ghost/src/review-packet.ts index e9217c04..0807a6a5 100644 --- a/packages/ghost/src/review-packet.ts +++ b/packages/ghost/src/review-packet.ts @@ -1,13 +1,9 @@ -import type { Dirent } from "node:fs"; -import { readdir, readFile } from "node:fs/promises"; import { resolve } from "node:path"; -import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; -import { type GhostDecisionDocument, lintGhostDecision } from "#ghost-core"; +import { stringify as stringifyYaml } from "yaml"; import { buildContextEntrypoint } from "./context/entrypoint.js"; import { formatContextEntrypointMarkdown } from "./context/entrypoint-markdown.js"; import { loadPackageContext } from "./context/package-context.js"; import { parseUnifiedDiff } from "./core/index.js"; -import { readOptionalUtf8 } from "./internal/fs.js"; import { resolveFingerprintPackage } from "./scan/fingerprint-package.js"; import { fingerprintStackToPackageContext, @@ -26,7 +22,6 @@ export async function buildReviewPacket(options: { packageDir?: string; memoryDir?: string; diffText: string; - includeAcceptedDecisions: boolean; maxDiffBytes?: number; }): Promise { return options.packageDir @@ -37,7 +32,6 @@ export async function buildReviewPacket(options: { async function buildSinglePackageReviewPacket(options: { packageDir?: string; diffText: string; - includeAcceptedDecisions: boolean; maxDiffBytes?: number; }): Promise { const paths = resolveFingerprintPackage(options.packageDir, process.cwd()); @@ -63,20 +57,15 @@ async function buildSinglePackageReviewPacket(options: { ), }, ]), - intent: (await readOptional(paths.intent)) ?? null, checks: context.checksRaw ?? null, config: (await readOptionalPackageConfig(paths.config)) ?? null, }; - if (options.includeAcceptedDecisions) { - packet.accepted_decisions = await readAcceptedDecisions(paths.decisions); - } return packet; } async function buildStackReviewPacket(options: { memoryDir?: string; diffText: string; - includeAcceptedDecisions: boolean; maxDiffBytes?: number; }): Promise { const changedFiles = parseUnifiedDiff(options.diffText).map( @@ -121,16 +110,10 @@ async function buildStackReviewPacket(options: { ), fingerprint: first.merged.fingerprint, context_markdown: formatReviewContextMarkdown(contextSections), - intent: first.merged.intent, checks: stringifyYaml(first.merged.checks, { lineWidth: 0 }), config: config ?? null, stacks, }; - if (options.includeAcceptedDecisions) { - packet.accepted_decisions = stacks - .flatMap((stack) => stack.merged.decisions) - .filter((decision) => decision.status === "accepted"); - } return packet; } @@ -149,7 +132,7 @@ function baseReviewPacket( finding_categories: [ "fix", "intentional-divergence", - "missing-memory", + "missing-fingerprint", "experience-gap", "eval-uncertainty", ], @@ -231,9 +214,7 @@ function reviewStackFromFingerprintStack( layer_dirs: stack.layers.map((layer) => layer.dir), merged: { fingerprint: stack.merged.fingerprint, - intent: stack.merged.intent, checks: stack.merged.checks, - decisions: stack.merged.decisions, }, provenance: stack.provenance, }; @@ -260,10 +241,8 @@ interface ReviewPacket { package_dir: string; fingerprint: unknown; context_markdown: string; - intent: string | null; checks: string | null; config: GhostPackageConfig | null; - accepted_decisions?: GhostDecisionDocument[]; stacks?: ReviewStackPacket[]; diff: string; budgets: ReviewPacketBudgets; @@ -280,9 +259,7 @@ interface ReviewStackPacket { layer_dirs: string[]; merged: { fingerprint: unknown; - intent: string | null; checks: unknown; - decisions: GhostDecisionDocument[]; }; provenance: GhostFingerprintStack["provenance"]; } @@ -292,28 +269,20 @@ 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/enforcement/checks.yml. Keep findings grounded in fingerprint/prose.yml, fingerprint/inventory.yml, fingerprint/composition.yml, active deterministic checks, and optional rationale files when present; 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 fingerprint/checks.yml. Keep findings grounded in fingerprint/prose.yml, fingerprint/inventory.yml, fingerprint/composition.yml, active deterministic checks, and diff evidence; do not expand the review into unrelated audit categories. Use these finding categories: ${packet.finding_categories.join(", ")}. ${formatReviewBudgetSection(packet)} -When fingerprint layers are silent, local evidence can still support advisory critique. Label those findings as provisional and non-Ghost-backed, and ground them in nearby product surfaces, local components, token or copy conventions, or optional rationale files when present. Ask the human before assessing high-risk, irreversible, privacy/security/legal, or product-surface-defining choices. +When fingerprint layers are silent, local evidence can still support advisory critique. Label those findings as provisional and non-Ghost-backed, and ground them in nearby product surfaces, local components, token or copy conventions. Ask the human before assessing high-risk, irreversible, privacy/security/legal, or product-surface-defining choices. -If the diff exposes missing fingerprint grounding or layer coverage, report it as missing-memory or experience-gap. Do not silently rewrite the Ghost package during review; fingerprint and check edits are ordinary Git-reviewed edits. +If the diff exposes missing fingerprint grounding or layer coverage, report it as missing-fingerprint or experience-gap. Do not silently rewrite the Ghost package during review; fingerprint and check edits are ordinary Git-reviewed edits. ${formatReviewStacksSection(packet.stacks ?? null)} ${packet.context_markdown} -## Human Intent - -\`\`\`markdown -${packet.intent ?? "_No fingerprint/memory/intent.md present. Treat fingerprint core layers as the canonical prose, inventory, and composition source._"} -\`\`\` - -${formatAcceptedDecisionsSection(packet.accepted_decisions ?? null)} - ${formatConfigSection(packet.config)} ## Diff @@ -352,9 +321,6 @@ function formatReviewStacksSection(stacks: ReviewStackPacket[] | null): string { lines.push(`Layers: ${stack.layer_dirs.join(" -> ")}`); lines.push(`Merge: ${stack.provenance.merge}`); lines.push(""); - if (stack.merged.intent?.trim()) { - lines.push("```markdown", stack.merged.intent.trim(), "```", ""); - } } return `${lines.join("\n")}\n`; @@ -375,79 +341,6 @@ function formatReviewContextMarkdown( return lines.join("\n").trim(); } -const readOptional = readOptionalUtf8; - -async function readAcceptedDecisions( - dirPath: string, -): Promise { - let entries: Dirent[]; - try { - entries = await readdir(dirPath, { withFileTypes: true }); - } catch { - return []; - } - - const decisions: GhostDecisionDocument[] = []; - for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { - if (!entry.isFile()) continue; - if (entry.name.startsWith(".")) continue; - if (!/\.ya?ml$/i.test(entry.name)) continue; - - const path = resolve(dirPath, entry.name); - const parsed = parseYaml(await readFile(path, "utf-8")); - const report = lintGhostDecision(parsed); - if (report.errors > 0) { - const first = report.issues.find((issue) => issue.severity === "error"); - const suffix = first?.path ? ` @ ${first.path}` : ""; - throw new Error( - `${path} failed decision lint: ${first?.message ?? "invalid decision"}${suffix}`, - ); - } - const decision = parsed as GhostDecisionDocument; - if (decision.status === "accepted") decisions.push(decision); - } - - return decisions; -} - -function formatAcceptedDecisionsSection( - decisions: GhostDecisionDocument[] | null, -): string { - if (!decisions) return ""; - if (decisions.length === 0) { - return `## Accepted Surface-Composition Decisions - -_No accepted decisions found in fingerprint/memory/decisions._ -`; - } - - const lines = ["## Accepted Surface-Composition Decisions", ""]; - for (const decision of decisions) { - lines.push(`### ${decision.title}`); - lines.push(""); - lines.push(`- **ID:** \`${decision.id}\``); - lines.push(`- **Claim:** ${decision.claim}`); - lines.push(`- **Rationale:** ${decision.rationale}`); - if (decision.scope) { - lines.push(`- **Scope:** ${formatDecisionScope(decision.scope)}`); - } - lines.push( - `- **Evidence:** ${decision.evidence - .map( - (entry) => - entry.path ?? - entry.survey_surface_id ?? - entry.locator ?? - entry.note, - ) - .join(", ")}`, - ); - lines.push(""); - } - - return lines.join("\n"); -} - function formatConfigSection(config: GhostPackageConfig | null): string { if (!config) { return `## Implementation Config @@ -463,19 +356,3 @@ ${stringifyYaml(config)} \`\`\` `; } - -function formatDecisionScope( - scope: NonNullable, -): string { - const parts: string[] = []; - if (scope.roles?.length) parts.push(`roles=${scope.roles.join("/")}`); - if (scope.scopes?.length) parts.push(`scopes=${scope.scopes.join("/")}`); - if (scope.surface_types?.length) { - parts.push(`surface_types=${scope.surface_types.join("/")}`); - } - if (scope.pattern_ids?.length) { - parts.push(`pattern_ids=${scope.pattern_ids.join("/")}`); - } - if (scope.paths?.length) parts.push(`paths=${scope.paths.join("/")}`); - return parts.length ? parts.join("; ") : "global"; -} diff --git a/packages/ghost/src/scan-stack-command.ts b/packages/ghost/src/scan-stack-command.ts index 2cfd2c41..b738e183 100644 --- a/packages/ghost/src/scan-stack-command.ts +++ b/packages/ghost/src/scan-stack-command.ts @@ -68,8 +68,6 @@ function formatStackJson( merged: { fingerprint: stack.merged.fingerprint, checks: stack.merged.checks, - intent: stack.merged.intent, - decisions: stack.merged.decisions, }, provenance: stack.provenance, }; diff --git a/packages/ghost/src/scan/constants.ts b/packages/ghost/src/scan/constants.ts index 96718120..ff4e44bd 100644 --- a/packages/ghost/src/scan/constants.ts +++ b/packages/ghost/src/scan/constants.ts @@ -21,17 +21,9 @@ export const FINGERPRINT_PROSE_FILENAME = "prose.yml"; export const FINGERPRINT_INVENTORY_FILENAME = "inventory.yml"; export const FINGERPRINT_COMPOSITION_FILENAME = "composition.yml"; -/** Purpose folders inside the portable fingerprint bundle. */ -export const FINGERPRINT_ENFORCEMENT_DIRNAME = "enforcement"; -export const FINGERPRINT_MEMORY_DIRNAME = "memory"; -export const FINGERPRINT_SOURCES_DIRNAME = "sources"; - /** Optional data-only package configuration artifact. */ export const CONFIG_FILENAME = "config.yml"; -/** Optional filename for human-authored or human-approved intent. */ -export const INTENT_FILENAME = "intent.md"; - /** Legacy direct fingerprint filename. Not part of the root package shape. */ export const FINGERPRINT_FILENAME = "fingerprint.md"; @@ -43,9 +35,3 @@ export const SCOPE_SURVEYS_DIRNAME = "modules"; /** Canonical filename for human-promoted deterministic gates. */ export const CHECKS_FILENAME = "checks.yml"; - -/** Optional directory containing accepted/rejected surface-composition decisions. */ -export const DECISIONS_DIRNAME = "decisions"; - -/** Optional directory containing generated, non-canonical caches. */ -export const CACHE_DIRNAME = "cache"; diff --git a/packages/ghost/src/scan/fingerprint-package-layers.ts b/packages/ghost/src/scan/fingerprint-package-layers.ts index 8d4a7b45..4120870a 100644 --- a/packages/ghost/src/scan/fingerprint-package-layers.ts +++ b/packages/ghost/src/scan/fingerprint-package-layers.ts @@ -183,13 +183,6 @@ checks: [] `; } -export function templateIntent(): string { - return `# Intent - -This optional file is reserved for human-authored or human-approved product intent. -`; -} - const readOptional = readOptionalUtf8; function parseManifest( diff --git a/packages/ghost/src/scan/fingerprint-package.ts b/packages/ghost/src/scan/fingerprint-package.ts index f09373aa..13445b25 100644 --- a/packages/ghost/src/scan/fingerprint-package.ts +++ b/packages/ghost/src/scan/fingerprint-package.ts @@ -1,5 +1,4 @@ -import type { Dirent } from "node:fs"; -import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises"; +import { access, mkdir, readFile, writeFile } from "node:fs/promises"; import { join, resolve } from "node:path"; import { parse as parseYaml } from "yaml"; import { @@ -7,7 +6,6 @@ import { type GhostFingerprintDocument, type GhostFingerprintPackageManifest, lintGhostChecks, - lintGhostDecision, MAP_FILENAME, SURVEY_FILENAME, } from "#ghost-core"; @@ -17,21 +15,15 @@ import { readOptionalUtf8, } from "../internal/fs.js"; import { - CACHE_DIRNAME, CONFIG_FILENAME, - DECISIONS_DIRNAME, FINGERPRINT_COMPOSITION_FILENAME, FINGERPRINT_DIRNAME, - FINGERPRINT_ENFORCEMENT_DIRNAME, FINGERPRINT_FILENAME, FINGERPRINT_INVENTORY_FILENAME, FINGERPRINT_MANIFEST_FILENAME, - FINGERPRINT_MEMORY_DIRNAME, FINGERPRINT_PACKAGE_DIR, FINGERPRINT_PROSE_FILENAME, - FINGERPRINT_SOURCES_DIRNAME, FINGERPRINT_YML_FILENAME, - INTENT_FILENAME, PATTERNS_FILENAME, RESOURCES_FILENAME, } from "./constants.js"; @@ -40,7 +32,6 @@ import { parseSplitFingerprintForLint, templateChecks, templateComposition, - templateIntent, templateInventory, templateManifest, templateProse, @@ -60,7 +51,6 @@ export interface FingerprintPackagePaths { prose: string; inventory: string; composition: string; - enforcement: string; fingerprintYml: string; config: string; resources: string; @@ -70,11 +60,6 @@ export interface FingerprintPackagePaths { /** Legacy direct markdown path; not part of the canonical root bundle. */ fingerprint: string; checks: string; - memory: string; - intent: string; - decisions: string; - sources: string; - cache: string; } export interface LoadedFingerprintPackage { @@ -89,7 +74,6 @@ export interface LoadedFingerprintPackage { } export interface InitFingerprintPackageOptions { - withIntent?: boolean; withConfig?: boolean; reference?: string; force?: boolean; @@ -101,9 +85,6 @@ export function resolveFingerprintPackage( ): FingerprintPackagePaths { const dir = resolve(cwd, dirArg ?? FINGERPRINT_PACKAGE_DIR); const fingerprintDir = join(dir, FINGERPRINT_DIRNAME); - const enforcement = join(fingerprintDir, FINGERPRINT_ENFORCEMENT_DIRNAME); - const memory = join(fingerprintDir, FINGERPRINT_MEMORY_DIRNAME); - const sources = join(fingerprintDir, FINGERPRINT_SOURCES_DIRNAME); return { dir, fingerprintDir, @@ -111,7 +92,6 @@ export function resolveFingerprintPackage( prose: join(fingerprintDir, FINGERPRINT_PROSE_FILENAME), inventory: join(fingerprintDir, FINGERPRINT_INVENTORY_FILENAME), composition: join(fingerprintDir, FINGERPRINT_COMPOSITION_FILENAME), - enforcement, fingerprintYml: join(dir, FINGERPRINT_YML_FILENAME), config: join(dir, CONFIG_FILENAME), resources: join(dir, RESOURCES_FILENAME), @@ -119,12 +99,7 @@ export function resolveFingerprintPackage( survey: join(dir, SURVEY_FILENAME), patterns: join(dir, PATTERNS_FILENAME), fingerprint: join(dir, FINGERPRINT_FILENAME), - checks: join(enforcement, GHOST_CHECKS_FILENAME), - memory, - intent: join(memory, INTENT_FILENAME), - decisions: join(memory, DECISIONS_DIRNAME), - sources, - cache: join(sources, CACHE_DIRNAME), + checks: join(fingerprintDir, GHOST_CHECKS_FILENAME), }; } @@ -136,8 +111,6 @@ export async function initFingerprintPackage( const paths = resolveFingerprintPackage(dirArg, cwd); await Promise.all([ mkdir(paths.fingerprintDir, { recursive: true }), - mkdir(paths.enforcement, { recursive: true }), - ...(options.withIntent ? [mkdir(paths.memory, { recursive: true })] : []), ...(options.withConfig ? [mkdir(paths.dir, { recursive: true })] : []), ]); const files = [ @@ -154,9 +127,6 @@ export async function initFingerprintPackage( }, ] : []), - ...(options.withIntent - ? [{ path: paths.intent, content: templateIntent() }] - : []), ]; if (!options.force) { await assertInitDoesNotOverwrite(files.map((file) => file.path)); @@ -223,14 +193,6 @@ export async function lintFingerprintPackage( const compositionRaw = await readOptional(paths.composition); const configRaw = await readOptional(paths.config); const checksRaw = await readOptional(paths.checks); - const intentRaw = await readOptional(paths.intent); - await lintDecisionDirectory( - paths.decisions, - "fingerprint/memory/decisions", - "decision", - lintGhostDecision, - issues, - ); let fingerprint: GhostFingerprintDocument | undefined; if (manifestRaw !== undefined) { @@ -250,92 +212,18 @@ export async function lintFingerprintPackage( } if (checksRaw !== undefined) { - const checks = parseYamlSafe( - checksRaw, - "fingerprint/enforcement/checks.yml", - issues, - ); + const checks = parseYamlSafe(checksRaw, "fingerprint/checks.yml", issues); if (checks !== undefined) { const checksReport = lintGhostChecks(checks, { fingerprint }); issues.push( - ...prefixIssues( - "fingerprint/enforcement/checks.yml", - checksReport.issues, - ), + ...prefixIssues("fingerprint/checks.yml", checksReport.issues), ); } } - if (intentRaw !== undefined && intentRaw.trim().length === 0) { - issues.push({ - severity: "warning", - rule: "intent-empty", - message: - "intent.md is optional, but when present it should contain human-authored or human-approved intent.", - path: "fingerprint/memory/intent.md", - }); - } - return finalize(issues); } -async function lintDecisionDirectory( - dirPath: string, - label: "fingerprint/memory/decisions", - itemLabel: "decision", - lint: (input: unknown) => ReturnType, - issues: LintIssue[], -): Promise { - let entries: Dirent[]; - try { - entries = await readdir(dirPath, { withFileTypes: true }); - } catch { - return; - } - - const docs: Array<{ id: string; path: string }> = []; - for (const entry of entries) { - if (!entry.isFile()) continue; - if (entry.name.startsWith(".")) continue; - if (!/\.ya?ml$/i.test(entry.name)) continue; - - const relPath = `${label}/${entry.name}`; - const raw = await readRequired(join(dirPath, entry.name), relPath, issues); - if (raw === undefined) continue; - const parsed = parseYamlSafe(raw, relPath, issues); - if (parsed === undefined) continue; - - const report = lint(parsed); - issues.push(...prefixIssues(relPath, report.issues)); - if ( - report.errors === 0 && - isRecord(parsed) && - typeof parsed.id === "string" - ) { - docs.push({ id: parsed.id, path: `${relPath}.id` }); - } - } - - const seen = new Map(); - for (const doc of docs) { - const previous = seen.get(doc.id); - if (previous) { - issues.push({ - severity: "error", - rule: `${itemLabel}-id-duplicate`, - message: `${itemLabel} id '${doc.id}' is duplicated (also at ${previous})`, - path: doc.path, - }); - } else { - seen.set(doc.id, doc.path); - } - } -} - -function isRecord(value: unknown): value is Record { - return Boolean(value && typeof value === "object" && !Array.isArray(value)); -} - async function readRequired( path: string, label: string, diff --git a/packages/ghost/src/scan/fingerprint-stack.ts b/packages/ghost/src/scan/fingerprint-stack.ts index 5f8df5ce..1b42313f 100644 --- a/packages/ghost/src/scan/fingerprint-stack.ts +++ b/packages/ghost/src/scan/fingerprint-stack.ts @@ -1,6 +1,6 @@ import { execFile } from "node:child_process"; import type { Dirent } from "node:fs"; -import { access, mkdir, readdir, readFile, stat } from "node:fs/promises"; +import { access, mkdir, readdir, stat } from "node:fs/promises"; import { dirname, isAbsolute, relative, resolve, sep } from "node:path"; import { promisify } from "node:util"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; @@ -10,10 +10,6 @@ import { type GhostCheck, type GhostChecksDocument, GhostChecksSchema, - type GhostDecisionDocument, - GhostDecisionSchema, - type GhostExperienceEvidence, - type GhostExperienceScope, type GhostFingerprintComposition, type GhostFingerprintDocument, type GhostFingerprintEvidence, @@ -24,15 +20,10 @@ import { type GhostFingerprintTopology, type GhostFingerprintTopologyScope, lintGhostChecks, - lintGhostDecision, lintGhostFingerprint, type MapFrontmatter, } from "#ghost-core"; -import { - loadPackageInventory, - type PackageContext, - type PackageInventory, -} from "../context/package-context.js"; +import type { PackageContext } from "../context/package-context.js"; import { readOptionalUtf8 } from "../internal/fs.js"; import { FINGERPRINT_DIRNAME, @@ -83,9 +74,6 @@ export interface GhostFingerprintStackLayer fingerprint_raw: string; checks?: GhostChecksDocument; checks_raw?: string; - intent?: string; - inventory: PackageInventory; - decisions: GhostDecisionDocument[]; } export interface GhostFingerprintStack { @@ -96,8 +84,6 @@ export interface GhostFingerprintStack { merged: { fingerprint: GhostFingerprintDocument; checks: GhostChecksDocument; - intent: string | null; - decisions: GhostDecisionDocument[]; }; provenance: { merge: "child-wins-by-id"; @@ -265,7 +251,6 @@ export function buildFingerprintStack( layers.map((layer) => layer.fingerprint), ); const checks = mergeChecks(layers.map((layer) => layer.checks)); - const decisions = mergeById(layers.flatMap((layer) => layer.decisions)); const checkLint = lintGhostChecks(checks, { fingerprint, map: mapFromFingerprint(fingerprint), @@ -287,8 +272,6 @@ export function buildFingerprintStack( merged: { fingerprint, checks, - intent: mergeIntent(layers), - decisions, }, provenance: { merge: "child-wins-by-id", @@ -305,12 +288,9 @@ export async function loadFingerprintStackLayer( const paths = resolveFingerprintPackage(packageDir, process.cwd()); const normalizedMemoryDir = normalizeMemoryDir(memoryDir); const root = rootForFingerprintPackageDir(paths.dir, normalizedMemoryDir); - const [loaded, checksRaw, intent, inventory, decisions] = await Promise.all([ + const [loaded, checksRaw] = await Promise.all([ loadFingerprintPackage(paths), readOptional(paths.checks), - readOptional(paths.intent), - loadPackageInventory(paths), - readDecisionDirectory(paths.decisions), ]); const fingerprint = normalizeFingerprintPaths( @@ -341,11 +321,6 @@ export async function loadFingerprintStackLayer( fingerprint_raw: stringifyYaml(fingerprint, { lineWidth: 0 }), ...(checks ? { checks } : {}), ...(checksRaw ? { checks_raw: checksRaw } : {}), - ...(intent ? { intent } : {}), - inventory, - decisions: decisions.map((decision) => - normalizeDecisionPaths(decision, root, repoRoot), - ), }; } @@ -369,11 +344,6 @@ export function fingerprintStackToPackageContext( fingerprintRaw: stringifyYaml(stack.merged.fingerprint, { lineWidth: 0 }), checks: stack.merged.checks, checksRaw: stringifyYaml(stack.merged.checks, { lineWidth: 0 }), - intent: stack.merged.intent ?? undefined, - inventory: stack.layers.at(-1)?.inventory ?? { - state: "missing", - path: `${stack.fingerprint_dir}/fingerprint/sources/cache/inventory.json`, - }, }; } @@ -434,7 +404,7 @@ export async function lintAllFingerprintStacks( }); issues.push( ...prefixIssues( - `${fingerprintPackageDisplayPath(pkg.relative_root, memoryDir)}/merged.enforcement.checks.yml`, + `${fingerprintPackageDisplayPath(pkg.relative_root, memoryDir)}/merged.checks.yml`, checksReport.issues, ), ); @@ -487,7 +457,6 @@ export async function initScopedFingerprintPackage( scopePath: string, cwd = process.cwd(), options: { - withIntent?: boolean; withConfig?: boolean; reference?: string; force?: boolean; @@ -502,7 +471,6 @@ export async function initScopedFingerprintPackage( async function resolveAndInit( root: string, options: { - withIntent?: boolean; withConfig?: boolean; reference?: string; force?: boolean; @@ -519,58 +487,10 @@ async function resolveAndInit( } function parseChecks(raw: string): GhostChecksDocument { - const parsed = parseYamlSafe(raw, "fingerprint/enforcement/checks.yml"); + const parsed = parseYamlSafe(raw, "fingerprint/checks.yml"); return GhostChecksSchema.parse(parsed) as GhostChecksDocument; } -async function readDecisionDirectory( - dirPath: string, -): Promise { - const parsed = await readYamlFiles(dirPath); - const docs: GhostDecisionDocument[] = []; - for (const { path, value } of parsed) { - const report = lintGhostDecision(value); - if (report.errors > 0) throwFingerprintLintError(path, report.issues); - docs.push(GhostDecisionSchema.parse(value) as GhostDecisionDocument); - } - return docs; -} - -async function readYamlFiles( - dirPath: string, -): Promise> { - let entries: Dirent[]; - try { - entries = await readdir(dirPath, { withFileTypes: true }); - } catch { - return []; - } - - const docs: Array<{ path: string; value: unknown }> = []; - for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { - if (!entry.isFile()) continue; - if (entry.name.startsWith(".")) continue; - if (!/\.ya?ml$/i.test(entry.name)) continue; - const path = resolve(dirPath, entry.name); - docs.push({ - path, - value: parseYamlSafe(await readFile(path, "utf-8"), path), - }); - } - return docs; -} - -function throwFingerprintLintError( - path: string, - issues: Array<{ severity: string; message: string; path?: string }>, -): never { - const first = issues.find((issue) => issue.severity === "error"); - const suffix = first?.path ? ` @ ${first.path}` : ""; - throw new Error( - `${path} failed lint: ${first?.message ?? "invalid fingerprint package"}${suffix}`, - ); -} - function mergeFingerprints( fingerprints: GhostFingerprintDocument[], ): GhostFingerprintDocument { @@ -733,16 +653,6 @@ function mergeStrings(a?: string[], b?: string[]): string[] | undefined { return out.length ? out : undefined; } -function mergeIntent(layers: GhostFingerprintStackLayer[]): string | null { - const chunks = layers - .filter((layer) => layer.intent?.trim()) - .map( - (layer) => - `# ${fingerprintPackageDisplayPath(layer.relative_root, layer.fingerprint_dir)}/fingerprint/memory/intent.md\n\n${layer.intent?.trim()}`, - ); - return chunks.length ? `${chunks.join("\n\n")}\n` : null; -} - function normalizeFingerprintPaths( input: GhostFingerprintDocument, baseRoot: string, @@ -826,23 +736,6 @@ function normalizeChecksPaths( return checks; } -function normalizeDecisionPaths( - input: GhostDecisionDocument, - baseRoot: string, - repoRoot: string, -): GhostDecisionDocument { - const decision = clone(input); - return { - ...decision, - scope: normalizeExperienceScopePaths(decision.scope, baseRoot, repoRoot), - evidence: normalizeExperienceEvidence( - decision.evidence, - baseRoot, - repoRoot, - ), - }; -} - function normalizeScopePaths( scope: T | undefined, baseRoot: string, @@ -855,14 +748,6 @@ function normalizeScopePaths( }; } -function normalizeExperienceScopePaths( - scope: GhostExperienceScope | undefined, - baseRoot: string, - repoRoot: string, -): GhostExperienceScope | undefined { - return normalizeScopePaths(scope, baseRoot, repoRoot); -} - function normalizeFingerprintEvidence( evidence: GhostFingerprintEvidence[] | undefined, baseRoot: string, @@ -875,18 +760,6 @@ function normalizeFingerprintEvidence( ); } -function normalizeExperienceEvidence( - evidence: GhostExperienceEvidence[], - baseRoot: string, - repoRoot: string, -): GhostExperienceEvidence[] { - return evidence.map((entry) => - entry.path - ? { ...entry, path: normalizePath(entry.path, baseRoot, repoRoot) } - : entry, - ); -} - function normalizePath( path: string, baseRoot: string, diff --git a/packages/ghost/src/scan/index.ts b/packages/ghost/src/scan/index.ts index 71247d1f..02e32142 100644 --- a/packages/ghost/src/scan/index.ts +++ b/packages/ghost/src/scan/index.ts @@ -1,10 +1,7 @@ export { - CACHE_DIRNAME, CONFIG_FILENAME, FINGERPRINT_DIRNAME, - FINGERPRINT_MEMORY_DIRNAME, FINGERPRINT_PACKAGE_DIR, - FINGERPRINT_SOURCES_DIRNAME, } from "./constants.js"; export type { DiscoveredGhostPackage, @@ -26,7 +23,7 @@ export { resolveGitRoot, resolveMemoryDirDefault, } from "./fingerprint-stack.js"; -export { inventory } from "./inventory.js"; +export { signals } from "./inventory.js"; export type { MonorepoInitCandidate } from "./monorepo-init.js"; export { detectMonorepoInitCandidates } from "./monorepo-init.js"; export type { diff --git a/packages/ghost/src/scan/inventory.ts b/packages/ghost/src/scan/inventory.ts index e8fb23b0..1e90997c 100644 --- a/packages/ghost/src/scan/inventory.ts +++ b/packages/ghost/src/scan/inventory.ts @@ -302,7 +302,7 @@ const HISTOGRAM_TOP_N = 20; * No LLM calls, no network, no filesystem mutations. Pure reads plus a * best-effort git invocation. */ -export function inventory(path: string): InventoryOutput { +export function signals(path: string): InventoryOutput { const root = resolve(path); const packageManifests = collectAllManifests(root); diff --git a/packages/ghost/src/scan/scan-status.ts b/packages/ghost/src/scan/scan-status.ts index 6cb2f8de..17220cb2 100644 --- a/packages/ghost/src/scan/scan-status.ts +++ b/packages/ghost/src/scan/scan-status.ts @@ -81,8 +81,6 @@ export interface ScanStatus { fingerprint: ScanStageReport; config: ScanStageReport; checks: ScanStageReport; - intent: ScanStageReport; - cache: ScanStageReport; scopes?: ScanScopeReport[]; scope_error?: string; readiness: ScanReadinessReport; @@ -93,9 +91,7 @@ export interface ScanStatus { * Inspect a Ghost fingerprint directory and report whether the canonical * `fingerprint/manifest.yml` exists with useful prose, inventory, and * composition layers. - * Generated cache is source material, not readiness for the curated - * inventory layer. Optional checks and rationale files are supplemental when - * present. + * Optional checks are supplemental when present. */ export async function scanStatus( dirPath: string, @@ -106,21 +102,11 @@ export async function scanStatus( const fingerprintPath = paths.fingerprintDir; const configPath = resolve(dir, CONFIG_FILENAME); const checksPath = paths.checks; - const intentPath = paths.intent; - const cachePath = paths.cache; - - const [ - fingerprintPresent, - configPresent, - checksPresent, - intentPresent, - cachePresent, - ] = await Promise.all([ + + const [fingerprintPresent, configPresent, checksPresent] = await Promise.all([ pathExists(paths.manifest, "file"), pathExists(configPath, "file"), pathExists(checksPath, "file"), - pathExists(intentPath, "file"), - pathExists(cachePath, "directory"), ]); const fingerprint: ScanStageReport = { @@ -135,22 +121,12 @@ export async function scanStatus( state: configPresent ? "present" : "missing", path: configPath, }; - const intent: ScanStageReport = { - state: intentPresent ? "present" : "missing", - path: intentPath, - }; - const cache: ScanStageReport = { - state: cachePresent ? "present" : "missing", - path: cachePath, - }; const status: ScanStatus = { dir, fingerprint, config, checks, - intent, - cache, readiness: await scanReadiness(paths, fingerprintPresent), recommended_next: fingerprintPresent ? null : "fingerprint", }; diff --git a/packages/ghost/src/scan/verify-package.ts b/packages/ghost/src/scan/verify-package.ts index c9801777..1b1b8c69 100644 --- a/packages/ghost/src/scan/verify-package.ts +++ b/packages/ghost/src/scan/verify-package.ts @@ -120,8 +120,8 @@ async function readOptionalChecks( severity: "error", rule: "verify-checks-read-failed", message: - "fingerprint/enforcement/checks.yml failed schema validation after package lint.", - path: "fingerprint/enforcement/checks.yml", + "fingerprint/checks.yml failed schema validation after package lint.", + path: "fingerprint/checks.yml", }); return undefined; } catch (err) { @@ -129,10 +129,10 @@ async function readOptionalChecks( issues.push({ severity: "error", rule: "verify-checks-read-failed", - message: `fingerprint/enforcement/checks.yml could not be read as YAML: ${ + message: `fingerprint/checks.yml could not be read as YAML: ${ err instanceof Error ? err.message : String(err) }`, - path: "fingerprint/enforcement/checks.yml", + path: "fingerprint/checks.yml", }); return undefined; } diff --git a/packages/ghost/src/skill-bundle/SKILL.md b/packages/ghost/src/skill-bundle/SKILL.md index 27b19f59..fa3ac4cd 100644 --- a/packages/ghost/src/skill-bundle/SKILL.md +++ b/packages/ghost/src/skill-bundle/SKILL.md @@ -20,10 +20,7 @@ materials it draws from, and the patterns that make it feel intentional. prose.yml inventory.yml composition.yml - enforcement/checks.yml - memory/intent.md - memory/decisions/ - sources/cache/ + checks.yml ``` `fingerprint/` is the source of truth when it is checked in. Ordinary Git @@ -47,12 +44,10 @@ Checks and review validate output; they are not generation input. layer content; Ghost normalizes omitted layer files or sections internally for checks, review, emit, and stack resolution. -Optional support material lives under purpose folders: -`fingerprint/enforcement/checks.yml` for deterministic gates, -`fingerprint/memory/intent.md` for human-approved intent, -`fingerprint/memory/decisions/` for rationale history, and -`fingerprint/sources/cache/` for generated observations. `.ghost/config.yml` -stays outside the portable package as local routing config. +Optional deterministic gates live in `fingerprint/checks.yml`. +`.ghost/config.yml` stays outside the portable package as local routing config. +Use `ghost signals` as a stdout-only reconnaissance helper when an agent needs +raw repo observations while authoring curated fingerprint layers. Advanced repos may contain nested fingerprint packages such as `apps/checkout/.ghost/`. Host wrappers may set @@ -65,7 +60,7 @@ consume JSON and map severities into their own review or check format. | Verb | Purpose | |---|---| -| `ghost init [dir]` | Create `.ghost/fingerprint/` with manifest, core layers, and enforcement checks. | +| `ghost init [dir]` | Create `.ghost/fingerprint/` with manifest, core layers, and deterministic checks. | | `ghost scan [dir] [--format json]` | Report fingerprint layer readiness for prose, inventory, and composition. | | `ghost lint [file-or-dir]` | Validate a fingerprint package or artifact. | | `ghost verify [dir] --root

    ` | Validate evidence paths, exemplar paths, and typed check refs. | @@ -81,7 +76,7 @@ consume JSON and map severities into their own review or check format. |---|---| | `ghost init --scope ` / `--memory-dir ` | 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 inventory [path]` | Emit raw repo signals for optional generated cache/source material. | +| `ghost signals [path]` | Emit raw repo signals for fingerprint authoring. | | `ghost lint --all` / `ghost verify --all` | Validate nested stack merges. | | `ghost compare [...more]` | Compare root fingerprint packages. | | `ghost ack` / `track` / `diverge` | Record stance toward tracked drift. | @@ -109,7 +104,7 @@ evidence-backed core layer entries, then ask the human to curate the claims. - Treat checked-in `fingerprint/` core files as the source of truth. - Generate from prose, inventory, and composition. -- Run active checks from `fingerprint/enforcement/checks.yml`; only active deterministic checks block. +- Run active checks from `fingerprint/checks.yml`; only active deterministic checks block. - Use local evidence as provisional when fingerprint layers are silent. - Treat auto-drafted fingerprint edits as ordinary uncommitted draft work until the human curates them and Git review accepts them. @@ -117,15 +112,14 @@ evidence-backed core layer entries, then ask the human to curate the claims. - Validate with `ghost lint` and `ghost verify --root ` before declaring fingerprint layers complete. - Run `ghost check` for deterministic gates and `ghost review` for advisory critique. -- Use optional config, intent, decisions, generated cache, nested stacks, and custom fingerprint - dirs only when present or requested. +- Use optional config, nested stacks, and custom fingerprint dirs only when + present or requested. ## When Fingerprint Layers Are Silent Silent fingerprint layers do not require stopping by default. When the fingerprint does not cover the task, proceed from nearby product surfaces, local components, -token and copy conventions, optional rationale files when present, and ordinary -UX reasoning when safe. Label that reasoning as provisional and +token and copy conventions, and ordinary UX reasoning when safe. Label that reasoning as provisional and non-Ghost-backed. Ask a human before making high-risk, irreversible, privacy/security/legal, or product-surface-defining choices. @@ -135,5 +129,3 @@ product-surface-defining choices. - Never treat advisory composition critique as a CI gate. - Never claim provisional reasoning, local convention, or general UX reasoning as Ghost-backed. -- Never treat `fingerprint/memory/intent.md` as authoritative unless human-authored or human-approved. -- Never treat rejected decisions as canonical inputs. diff --git a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md index 4ca66e94..0df1812d 100644 --- a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md +++ b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md @@ -58,8 +58,8 @@ Ask only high-leverage questions that change the fingerprint: - Where do trust, density, pacing, accessibility, recovery, or disclosure matter most? - Are there surfaces where the same UI decision should be assessed differently? -Use human-authored or human-approved answers in `prose.yml` and optional -`fingerprint/memory/intent.md`. Do not treat unapproved notes as canonical. +Use human-authored or human-approved answers in `prose.yml`. Do not treat +unapproved notes as canonical. When auto-draft is requested, move the interview after the starter draft and use it to curate claims instead of asking every question up front. @@ -71,20 +71,19 @@ stories, tests, docs, screenshots, examples, copy, tokens, assets, and UI library references that reveal hierarchy, behavior, accessibility, trust, and flow. -Optional cache: +Optional signals: ```bash -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory . > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ``` -Treat generated cache as scratch evidence. It can support curated entries in +Treat signals as scratch evidence. They can support curated entries in `inventory.yml`, but it does not establish surface-composition guidance by itself. -In auto-draft mode, always create or refresh this cache before drafting, then -inspect the high-signal files it points to. Scan facts may seed `inventory.yml`; -scan frequency and raw cache do not establish surface-composition guidance. +In auto-draft mode, always gather signals before drafting, then inspect the +high-signal files they point to. Signal facts may seed `inventory.yml`; scan +frequency and raw signals do not establish surface-composition guidance. ## 4. Draft The Core Layers @@ -112,9 +111,8 @@ important claims: - keep as canonical - soften into guidance - reject as accidental or legacy -- move to generated cache or scratch notes +- move to scratch notes - scope to a nested package -- record as a decision - convert into a deterministic check Only add checks when the rule can be enforced deterministically. Subjective @@ -163,7 +161,5 @@ canonical package. - Never copy raw inventory into canonical layers without curation. - Never claim scan frequency is product authority. -- Never make `fingerprint/memory/intent.md` authoritative unless human-authored - or human-approved. - Never create nested packages just to mirror directory structure. - Never turn advisory composition critique into a deterministic gate. diff --git a/packages/ghost/src/skill-bundle/references/brief.md b/packages/ghost/src/skill-bundle/references/brief.md index a802d4d3..dcc96d4b 100644 --- a/packages/ghost/src/skill-bundle/references/brief.md +++ b/packages/ghost/src/skill-bundle/references/brief.md @@ -8,14 +8,14 @@ description: Build a concise pre-generation brief from Ghost fingerprint layers. 1. Read checked-in `fingerprint/prose.yml`, `fingerprint/inventory.yml`, and `fingerprint/composition.yml`. 2. Select the relevant `prose.situations`, `prose.principles`, `prose.experience_contracts`, and `composition.patterns`. 3. Inspect matching `inventory.exemplars` as concrete generation anchors. -4. Use optional `fingerprint/sources/cache/` when present to understand what exists. +4. Run `ghost signals ` when raw repo observations would help you find evidence. 5. Skim active checks so generation avoids deterministic failures. -6. Use `ghost stack `, accepted decisions, and `fingerprint/memory/intent.md` only when the repo has opted into those advanced inputs. +6. Use `ghost stack ` when the repo has nested fingerprint packages. Return a short brief with relevant fingerprint refs, product obligations, -inventory exemplars and building blocks to inspect, cache facts when present, -active checks to avoid, local evidence, and provisional assumptions when layers +inventory exemplars and building blocks to inspect, active checks to avoid, +local evidence, and provisional assumptions when layers are silent. Fingerprint edits are ordinary Git-reviewed edits to the split fingerprint -package and optional rationale files. +package. diff --git a/packages/ghost/src/skill-bundle/references/capture.md b/packages/ghost/src/skill-bundle/references/capture.md index e0b3b045..b5ed77e8 100644 --- a/packages/ghost/src/skill-bundle/references/capture.md +++ b/packages/ghost/src/skill-bundle/references/capture.md @@ -24,10 +24,7 @@ Ghost treats the fingerprint package as canonical. prose.yml inventory.yml composition.yml - enforcement/checks.yml - memory/intent.md - memory/decisions/ - sources/cache/ + checks.yml ``` `prose.yml` captures the intent behind the surface. `inventory.yml` records @@ -68,9 +65,8 @@ ghost init ghost scan ``` -Use `--with-intent` only when you have human-authored or human-approved context -to preserve. Use `--with-config --reference ` when local -routing or a reference UI registry/library should be recorded in `.ghost/config.yml`. +Use `--with-config --reference ` when local routing or a +reference UI registry/library should be recorded in `.ghost/config.yml`. ### 3. Auto-Draft Mode @@ -84,14 +80,13 @@ Auto-draft is a skill workflow, not a Ghost CLI action or flag. 1. If `.ghost/fingerprint/manifest.yml` is missing, run `ghost init`. 2. Run `ghost scan --format json`. -3. Create generated cache: +3. Gather raw repo signals: ```bash - mkdir -p .ghost/fingerprint/sources/cache - ghost inventory . > .ghost/fingerprint/sources/cache/inventory.json + ghost signals . ``` -4. Inspect high-signal files from the inventory, plus routes, docs, stories, +4. Inspect high-signal files from the signals, plus routes, docs, stories, tests, tokens, registries, assets, screenshots, and exemplars. 5. Write only the smallest evidence-backed starter entries into `prose.yml`, `inventory.yml`, and `composition.yml`. @@ -110,11 +105,10 @@ behavior, copy, accessibility, trust, and flow. Optional helper: ```bash -mkdir -p .ghost/fingerprint/sources/cache -ghost inventory . > .ghost/fingerprint/sources/cache/inventory.json +ghost signals . ``` -Treat generated cache as scratch material. Do not copy raw inventory into +Treat signals as scratch observations. Do not copy raw signals into `inventory.yml` without curation. ### 5. Write Core Layers @@ -132,7 +126,7 @@ treating draft content as durable fingerprint guidance. ### 6. Add Checks Sparingly -`fingerprint/enforcement/checks.yml` is the executable appendix. Add only +`fingerprint/checks.yml` is the executable appendix. Add only deterministic checks with typed derivation refs: ```yaml @@ -159,7 +153,7 @@ packages exist. ## Never - Never describe any file outside `.ghost/fingerprint/` as canonical package input. -- Never treat generated cache as canonical inventory. +- 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. - Never promote subjective taste directly into checks; make it deterministic or keep it advisory. diff --git a/packages/ghost/src/skill-bundle/references/critique.md b/packages/ghost/src/skill-bundle/references/critique.md index 9a92aa4b..12ab8b26 100644 --- a/packages/ghost/src/skill-bundle/references/critique.md +++ b/packages/ghost/src/skill-bundle/references/critique.md @@ -13,10 +13,10 @@ description: Critique generated or changed UI using Ghost fingerprint layers. inventory exemplars, active checks, and repairs where relevant. When fingerprint layers are silent, you may use nearby product surfaces, local -components, token and copy conventions, accepted decisions, or human intent when -present. Label that reasoning as provisional and non-Ghost-backed. +components, token and copy conventions. Label that reasoning as provisional and +non-Ghost-backed. Do not make advisory taste critique sound blocking unless an active check backs it. If fingerprint grounding or layer coverage is missing or contradictory, -name that as `missing-memory` or `experience-gap`; edit the Ghost package only -when the user asks you to. +name that as `missing-fingerprint` or `experience-gap`; edit the Ghost package +only when the user asks you to. diff --git a/packages/ghost/src/skill-bundle/references/patterns.md b/packages/ghost/src/skill-bundle/references/patterns.md index 9db6f2eb..91e58b05 100644 --- a/packages/ghost/src/skill-bundle/references/patterns.md +++ b/packages/ghost/src/skill-bundle/references/patterns.md @@ -26,7 +26,8 @@ Add a pattern when it helps a future agent choose or review: - a restraint rule that preserves hierarchy, density, trust, or pacing Do not add a pattern just because a value or component exists. Put raw -observations in `.ghost/fingerprint/sources/cache/` or keep them in scratch notes. +observations in scratch notes; use `ghost signals` when raw repo facts help +find candidate evidence. ## Shape @@ -64,7 +65,7 @@ Allowed `kind` values: - Put obligations that affect failure, disclosure, recovery, or trust in `prose.experience_contracts`, not only `composition.patterns`. - Put broad surface intent in `prose.principles`. -- Add `check_refs` only when a deterministic check exists in `fingerprint/enforcement/checks.yml`. +- Add `check_refs` only when a deterministic check exists in `fingerprint/checks.yml`. ## Validate diff --git a/packages/ghost/src/skill-bundle/references/recall.md b/packages/ghost/src/skill-bundle/references/recall.md index 7932dab4..70633aed 100644 --- a/packages/ghost/src/skill-bundle/references/recall.md +++ b/packages/ghost/src/skill-bundle/references/recall.md @@ -8,8 +8,7 @@ description: Recall applicable Ghost fingerprint layers for a task or file path. 1. Read checked-in `fingerprint/prose.yml`, `fingerprint/inventory.yml`, and `fingerprint/composition.yml` entries. 2. Select relevant prose, inventory exemplars, composition patterns, and active checks. -3. Use `ghost stack `, accepted decisions, and intent only when the repo - has opted into those advanced inputs. +3. Use `ghost stack ` when the repo has nested fingerprint packages. 4. Summarize only fingerprint refs that apply to the task. Return: @@ -17,7 +16,6 @@ Return: - Applicable fingerprint refs and short claims. - Inventory exemplars to inspect when generation or review needs a concrete anchor. - Active checks that may affect the work. -- Optional decisions or intent that explain why, when present. - Any gaps where local evidence must carry the reasoning. If the fingerprint is silent, say that plainly and continue with provisional diff --git a/packages/ghost/src/skill-bundle/references/review.md b/packages/ghost/src/skill-bundle/references/review.md index 2576c147..5f6c699c 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/enforcement/checks.yml` rules and are the only blocking findings. +`fingerprint/checks.yml` rules and are the only blocking findings. ## 2. Build Advisory Context @@ -28,14 +28,14 @@ Use the emitted packet as context. It includes: - `fingerprint/prose.yml`, `fingerprint/inventory.yml`, and `fingerprint/composition.yml` - curated inventory exemplars -- active checks from `fingerprint/enforcement/checks.yml` -- optional stack, config, intent, or accepted decision context when present or requested +- active checks from `fingerprint/checks.yml` +- optional stack or config context when present or requested - the diff ## 3. Write Advisory Findings Advisory findings are non-blocking unless tied to an active deterministic check. -Classify each finding as `fix`, `intentional-divergence`, `missing-memory`, +Classify each finding as `fix`, `intentional-divergence`, `missing-fingerprint`, `experience-gap`, or `eval-uncertainty`. Each finding must cite the diff location, relevant fingerprint core layer refs, @@ -46,5 +46,5 @@ When fingerprint layers are silent, local evidence can still support advisory critique. Label those findings as provisional and non-Ghost-backed. Fingerprint edits are ordinary Git-reviewed edits to the split fingerprint -package and optional rationale files. Do not silently rewrite the Ghost package -during review unless the user asks for fingerprint edits. +package. Do not silently rewrite the Ghost package during review unless the user +asks for fingerprint edits. diff --git a/packages/ghost/src/skill-bundle/references/schema.md b/packages/ghost/src/skill-bundle/references/schema.md index 5ccd02f4..2216410e 100644 --- a/packages/ghost/src/skill-bundle/references/schema.md +++ b/packages/ghost/src/skill-bundle/references/schema.md @@ -10,10 +10,7 @@ Canonical package: prose.yml core surface intent inventory.yml core material and source links composition.yml core patterns - enforcement/checks.yml optional ghost.checks/v1 gates - memory/intent.md optional human-approved intent - memory/decisions/ optional ghost.decision/v1 rationale - sources/cache/ optional generated observations + checks.yml optional ghost.checks/v1 gates ``` Git is the approval boundary: checked-in `fingerprint/` core files are @@ -38,9 +35,8 @@ Use these typed refs: - `composition.pattern:` - `check:` -`inventory.sources[].kind` may be `cache`, `registry`, `file`, `url`, or -`package`. +`inventory.sources[].kind` may be `registry`, `file`, `url`, or `package`. -`fingerprint/enforcement/checks.yml` remains deterministic only. Ref-backed +`fingerprint/checks.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/verify.md b/packages/ghost/src/skill-bundle/references/verify.md index b09db2b3..54233448 100644 --- a/packages/ghost/src/skill-bundle/references/verify.md +++ b/packages/ghost/src/skill-bundle/references/verify.md @@ -8,7 +8,7 @@ description: Verify generated UI or fingerprint edits against Ghost. 1. Run `ghost lint .ghost` and `ghost verify .ghost --root ` after fingerprint edits. 2. Run `ghost check --base ` after implementation changes. -3. For advisory review, run `ghost review --base --include-memory`. +3. For advisory review, run `ghost review --base `. 4. For generation setup, run `ghost relay gather ` and read suggested fingerprint files when the task widens. 5. Inspect generated UI manually or with screenshots when visual fidelity diff --git a/packages/ghost/src/skill-bundle/references/voice.md b/packages/ghost/src/skill-bundle/references/voice.md index d79f820d..59467f07 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 layers; 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/enforcement/checks.yml`: + `fingerprint/checks.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 0a20e2cb..1b782dca 100644 --- a/packages/ghost/test/cli.test.ts +++ b/packages/ghost/test/cli.test.ts @@ -184,15 +184,15 @@ describe("ghost CLI", () => { expect(result.stdout).toContain("Core workflow"); expect(result.stdout).toContain("Advanced/package inspection"); expect(result.stdout).toContain("Compare/stance"); - expect(result.stdout).toContain("Maintenance/cache"); + expect(result.stdout).toContain("Maintenance/legacy"); for (const command of [ "lint [file]", "init [dir]", "verify [dir]", "scan [dir]", "stack [paths...]", - "inventory [path]", - "describe [fingerprint]", + "signals [path]", + "describe ", "diff ", "survey [...surveys]", "relay [target]", @@ -897,13 +897,10 @@ design_loop: readFile(join(dir, ".ghost", "fingerprint", "manifest.yml"), "utf-8"), ).resolves.toContain("schema: ghost.fingerprint-package/v1"); await expect( - readFile( - join(dir, ".ghost", "fingerprint", "enforcement", "checks.yml"), - "utf-8", - ), + readFile(join(dir, ".ghost", "fingerprint", "checks.yml"), "utf-8"), ).resolves.toContain("schema: ghost.checks/v1"); const status = JSON.parse(scan.stdout); - expect(status.cache.state).toBe("missing"); + expect(status.cache).toBeUndefined(); const lint = await runCli(["lint"], dir); const verify = await runCli(["verify", ".ghost", "--root", "."], dir); @@ -1359,12 +1356,12 @@ design_loop: it("warns for checks grounded in omitted sparse fingerprint refs", async () => { await runCli(["init"], dir); await writeFile( - join(dir, ".ghost", "fingerprint", "enforcement", "checks.yml"), + join(dir, ".ghost", "fingerprint", "checks.yml"), `schema: ghost.checks/v1 id: local checks: - - id: missing-memory-check - title: Missing memory check + - id: missing-fingerprint-check + title: Missing fingerprint check status: active severity: serious derivation: @@ -1389,19 +1386,19 @@ checks: expect(report.issues[0]).toMatchObject({ severity: "warning", rule: "check-grounding-unknown", - path: "fingerprint/enforcement/checks.yml.checks[0].derivation.prose[0]", + path: "fingerprint/checks.yml.checks[0].derivation.prose[0]", }); }); it("validates standalone checks.yml derivation refs with a valid sibling fingerprint", async () => { await writeCheckPackage(dir, { checks: false }); await writeFile( - join(dir, ".ghost", "fingerprint", "enforcement", "checks.yml"), + join(dir, ".ghost", "fingerprint", "checks.yml"), checksFileWithDerivation("prose.principle:not-recorded"), ); const lint = await runCli( - ["lint", ".ghost/fingerprint/enforcement/checks.yml", "--format", "json"], + ["lint", ".ghost/fingerprint/checks.yml", "--format", "json"], dir, ); @@ -1438,20 +1435,18 @@ checks: }); it("keeps standalone checks.yml lint non-blocking when the sibling fingerprint is invalid", async () => { - await mkdir(join(dir, ".ghost", "fingerprint", "enforcement"), { - recursive: true, - }); + await mkdir(join(dir, ".ghost", "fingerprint"), { recursive: true }); await writeFile( join(dir, ".ghost", "fingerprint", "manifest.yml"), "not: draft\n", ); await writeFile( - join(dir, ".ghost", "fingerprint", "enforcement", "checks.yml"), + join(dir, ".ghost", "fingerprint", "checks.yml"), checksFileWithDerivation("prose.principle:tokenized-ui-color"), ); const lint = await runCli( - ["lint", ".ghost/fingerprint/enforcement/checks.yml", "--format", "json"], + ["lint", ".ghost/fingerprint/checks.yml", "--format", "json"], dir, ); @@ -1465,7 +1460,7 @@ checks: }); it("initializes a bundle and reports fingerprint capture state as json", async () => { - const init = await runCli(["init", "--with-intent"], dir); + const init = await runCli(["init"], dir); const scan = await runCli(["scan", "--format", "json"], dir); const scanHuman = await runCli(["scan"], dir); @@ -1476,23 +1471,19 @@ checks: expect(init.stdout).toContain("composition.yml:"); expect(init.stdout).toContain("checks.yml:"); 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", ), ).toContain("schema: ghost.fingerprint-package/v1"); - expect( - await readFile( - join(dir, ".ghost", "fingerprint", "memory", "intent.md"), - "utf-8", - ), - ).toContain("# Intent"); expect(scan.code).toBe(0); const status = JSON.parse(scan.stdout); expect(status.fingerprint.state).toBe("present"); expect(status.proposals).toBeUndefined(); - expect(status.cache.state).toBe("missing"); + expect(status.cache).toBeUndefined(); + expect(status.intent).toBeUndefined(); expect(status.readiness.state).toBe("fingerprint-empty"); expect(status.readiness.layer_counts).toEqual({ prose: 0, @@ -1515,6 +1506,12 @@ checks: expect(scanHuman.stdout).not.toContain("memory dir:"); }); + it("rejects removed init intent flag", async () => { + await expect(runCli(["init", "--with-intent"], dir)).rejects.toThrow( + "Unknown option `--withIntent`", + ); + }); + it("initializes a blank product scaffold with config and reference library wiring", async () => { const init = await runCli( [ @@ -1528,7 +1525,7 @@ checks: dir, ); const scan = await runCli(["scan", "--format", "json"], dir); - const inventory = await runCli(["inventory"], dir); + const signals = await runCli(["signals"], dir); await mkdir(join(dir, "packages", "ghost-ui", ".ghost"), { recursive: true, }); @@ -1605,19 +1602,19 @@ checks: expect(status.readiness.state).toBe("inventory-only"); expect(status.readiness.missing_layers).toEqual(["prose", "composition"]); - const inventoryOutput = JSON.parse(inventory.stdout); - expect(inventoryOutput.config.libraries[0].id).toBe("ghost-ui"); + const signalsOutput = JSON.parse(signals.stdout); + expect(signalsOutput.config.libraries[0].id).toBe("ghost-ui"); expect(verify.code).toBe(0); }); - it("runs inventory, lint, and verify from the unified cli", async () => { + it("runs signals, lint, and verify from the unified cli", async () => { await writeCheckPackage(dir); - const inventory = await runCli(["inventory"], dir); + const signals = await runCli(["signals"], dir); const lint = await runCli(["lint"], dir); const verify = await runCli(["verify", ".ghost", "--root", "."], dir); - expect(inventory.code).toBe(0); - expect(await realpath(JSON.parse(inventory.stdout).root)).toBe( + expect(signals.code).toBe(0); + expect(await realpath(JSON.parse(signals.stdout).root)).toBe( await realpath(dir), ); expect(lint.code).toBe(0); @@ -1644,7 +1641,7 @@ checks: expect(status.fingerprint.state).toBe("present"); expect(status.checks.state).toBe("present"); expect(status.proposals).toBeUndefined(); - expect(status.cache.state).toBe("present"); + expect(status.cache).toBeUndefined(); expect(status.readiness.state).toBe("fingerprint-partial"); expect(status.readiness.missing_layers).toEqual(["composition"]); expect(status.readiness.reasons[0]).toContain( @@ -1736,26 +1733,6 @@ checks: it("emits review commands from the unified cli", async () => { await writeCheckPackage(dir); - await mkdir(join(dir, ".ghost", "fingerprint", "sources", "cache"), { - recursive: true, - }); - await writeFile( - join(dir, ".ghost", "fingerprint", "sources", "cache", "inventory.json"), - JSON.stringify( - { - root: dir, - platform_hints: ["ios"], - build_system_hints: ["spm"], - language_histogram: [{ name: "swift", files: 12 }], - package_manifests: ["Package.swift"], - candidate_config_files: ["Code/Theme.swift"], - registry_files: [], - top_level_tree: [{ path: "Code/", kind: "dir", child_count: 3 }], - }, - null, - 2, - ), - ); await writeFile( join(dir, ".ghost", "fingerprint.md"), fingerprintWithId("local"), @@ -2062,7 +2039,7 @@ checks: expect(result.stdout).not.toContain("Proposal Threshold"); expect(result.stdout).toContain("provisional and non-Ghost-backed"); expect(result.stdout).not.toContain("recommend-proposal"); - expect(result.stdout).toContain("missing-memory"); + expect(result.stdout).toContain("missing-fingerprint"); expect(result.stdout).toContain("experience-gap"); expect(result.stdout).toContain("repair or intentional-divergence"); expect(result.stdout).not.toContain("schema: ghost.fingerprint/v1"); @@ -2169,9 +2146,8 @@ libraries: }); }); - it("review omits accepted decisions by default", async () => { + it("review omits removed memory fields", async () => { await writeCheckPackage(dir); - await writeMemoryFiles(dir); await writeFile( join(dir, "change.patch"), lendingPatch("let color = CashTheme.primary"), @@ -2189,98 +2165,30 @@ libraries: expect(packet.proposal_types).toBeUndefined(); expect(packet.open_proposals).toBeUndefined(); expect(packet.accepted_decisions).toBeUndefined(); + expect(packet.intent).toBeUndefined(); expect(packet.memory).toBeUndefined(); }); - it("review includes only accepted decisions when requested", async () => { - await writeCheckPackage(dir); - await writeMemoryFiles(dir); - await writeFile( - join(dir, "change.patch"), - lendingPatch("let color = CashTheme.primary"), - ); - - const result = await runCli( - [ - "review", - "--diff", - "change.patch", - "--include-memory", - "--format", - "json", - ], - dir, - ); - - expect(result.code).toBe(0); - const packet = JSON.parse(result.stdout); - expect(packet.accepted_decisions).toHaveLength(1); - expect(packet.accepted_decisions[0].id).toBe("checkout-reversibility"); - expect(packet.memory).toBeUndefined(); - expect(JSON.stringify(packet.accepted_decisions)).not.toContain( - "rejected-decision", - ); - expect(JSON.stringify(packet.accepted_decisions)).not.toContain( - "saved-payment-empty-state", - ); - }); - - it("review --package includes only accepted decisions when requested", async () => { - await writeCheckPackage(dir); - await writeMemoryFiles(dir); - await writeFile( - join(dir, "change.patch"), - lendingPatch("let color = CashTheme.primary"), - ); - - const result = await runCli( - [ - "review", - "--diff", - "change.patch", - "--package", - ".ghost", - "--include-memory", - "--format", - "json", - ], - dir, - ); - - expect(result.code).toBe(0); - const packet = JSON.parse(result.stdout); - expect(packet.accepted_decisions).toHaveLength(1); - expect(packet.accepted_decisions[0].id).toBe("checkout-reversibility"); - expect(JSON.stringify(packet.accepted_decisions)).not.toContain( - "rejected-decision", - ); - expect(JSON.stringify(packet.accepted_decisions)).not.toContain( - "saved-payment-empty-state", - ); - }); - - it("review --package points empty decision memory to the canonical path", async () => { + it("rejects removed review memory flag", async () => { await writeCheckPackage(dir); await writeFile( join(dir, "change.patch"), lendingPatch("let color = CashTheme.primary"), ); - const result = await runCli( - [ - "review", - "--diff", - "change.patch", - "--package", - ".ghost", - "--include-memory", - ], - dir, - ); - - expect(result.code).toBe(0); - expect(result.stdout).toContain("fingerprint/memory/decisions"); - expect(result.stdout).not.toContain(".ghost/decisions"); + await expect( + runCli( + [ + "review", + "--diff", + "change.patch", + "--include-memory", + "--format", + "json", + ], + dir, + ), + ).rejects.toThrow("Unknown option `--includeMemory`"); }); it("check routes changed files through nested stacks by default", async () => { @@ -2714,7 +2622,7 @@ async function writeSplitFingerprintPackage( ): Promise { const fingerprintDir = join(pkg, "fingerprint"); const doc = parseYaml(fingerprintRaw) as Record; - await mkdir(join(fingerprintDir, "enforcement"), { recursive: true }); + await mkdir(fingerprintDir, { recursive: true }); await Promise.all([ writeFile( join(fingerprintDir, "manifest.yml"), @@ -2747,12 +2655,7 @@ async function writeSplitFingerprintPackage( stringifyYaml(doc.composition ?? { patterns: [] }), ), ...(checksRaw - ? [ - writeFile( - join(fingerprintDir, "enforcement", "checks.yml"), - checksRaw, - ), - ] + ? [writeFile(join(fingerprintDir, "checks.yml"), checksRaw)] : []), ]); } @@ -2781,43 +2684,6 @@ checks: `; } -async function writeMemoryFiles(dir: string): Promise { - const pkg = join(dir, ".ghost", "fingerprint", "memory"); - await mkdir(join(pkg, "decisions"), { recursive: true }); - await writeFile( - join(pkg, "decisions", "checkout-reversibility.yml"), - `schema: ghost.decision/v1 -id: checkout-reversibility -status: accepted -title: Reversibility before money movement -claim: Payment review must make reversibility visible before final submission. -rationale: Users need confidence before committing money movement. -scope: - roles: [design, engineering, pm, qa] - scopes: [checkout] - surface_types: [payment-review] - pattern_ids: [confirmation-before-commit] -evidence: - - path: apps/checkout/review.tsx - note: Review step exposes edit affordances before submit. -decided_at: "2026-05-17T00:00:00.000Z" -`, - ); - await writeFile( - join(pkg, "decisions", "rejected-decision.yml"), - `schema: ghost.decision/v1 -id: rejected-decision -status: rejected -title: Rejected experience direction -claim: This should not appear in drift packets. -rationale: Rejected decisions are non-canonical rationale. -evidence: - - note: Rejected in design review. -decided_at: "2026-05-17T00:00:00.000Z" -`, - ); -} - async function writeNestedCheckPackage( dir: string, memoryDir = ".ghost", @@ -2827,12 +2693,6 @@ async function writeNestedCheckPackage( join(dir, "apps", "checkout"), memoryDir, ); - await mkdir(join(rootMemory, "fingerprint", "sources", "cache"), { - recursive: true, - }); - await mkdir(join(checkoutMemory, "fingerprint", "sources", "cache"), { - recursive: true, - }); 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"), ""); diff --git a/packages/ghost/test/context-entrypoint.test.ts b/packages/ghost/test/context-entrypoint.test.ts index c16b94a7..28a37f16 100644 --- a/packages/ghost/test/context-entrypoint.test.ts +++ b/packages/ghost/test/context-entrypoint.test.ts @@ -290,10 +290,6 @@ function context( targetPaths: ["apps/refunds/settings/page.tsx"], layerDirs: ["/repo/.ghost", "/repo/apps/refunds/.ghost"], fingerprintRaw: "", - inventory: { - state: "missing", - path: ".ghost/fingerprint/sources/cache/inventory.json", - }, fingerprintLayers: { manifest: "schema: ghost.fingerprint-package/v1\nid: local\n", }, diff --git a/packages/ghost/test/fingerprint-package.test.ts b/packages/ghost/test/fingerprint-package.test.ts index 6015609b..8e2254fa 100644 --- a/packages/ghost/test/fingerprint-package.test.ts +++ b/packages/ghost/test/fingerprint-package.test.ts @@ -58,10 +58,10 @@ describe("split fingerprint package", () => { building_blocks: {} exemplars: [] sources: - - id: generated-inventory - kind: cache - ref: sources/cache/inventory.json - note: Refreshable observed repo facts. + - id: repo-signals + kind: file + ref: docs/architecture.md + note: Human-curated source material. `, ); @@ -70,9 +70,9 @@ sources: expect(report.errors).toBe(0); expect(loaded.fingerprint.inventory.sources[0]).toMatchObject({ - id: "generated-inventory", - kind: "cache", - ref: "sources/cache/inventory.json", + id: "repo-signals", + kind: "file", + ref: "docs/architecture.md", }); }); @@ -84,10 +84,10 @@ sources: building_blocks: {} exemplars: [] sources: - - id: generated-inventory - kind: cache - ref: sources/cache/inventory.json - - id: generated-inventory + - id: repo-signals + kind: file + ref: docs/architecture.md + - id: repo-signals kind: file ref: tmp/inventory.json `, diff --git a/packages/ghost/test/fingerprint-stack.test.ts b/packages/ghost/test/fingerprint-stack.test.ts index dcb25833..6c94ac70 100644 --- a/packages/ghost/test/fingerprint-stack.test.ts +++ b/packages/ghost/test/fingerprint-stack.test.ts @@ -82,11 +82,6 @@ describe("nested Ghost fingerprint stacks", () => { (check) => check.id === "no-hardcoded-color", )?.status, ).toBe("disabled"); - expect( - stack.merged.decisions.find( - (decision) => decision.id === "shared-decision", - )?.status, - ).toBe("rejected"); }); it("groups changed files by resolved fingerprint stack", async () => { @@ -192,9 +187,6 @@ async function writeRootBundle( memoryDir = ".ghost", ): Promise { const ghost = memoryPackagePath(dir, memoryDir); - await mkdir(join(ghost, "fingerprint", "memory", "decisions"), { - recursive: true, - }); await writeSplitFingerprintPackage( ghost, `schema: ghost.fingerprint/v1 @@ -254,19 +246,6 @@ checks: observed_count: 3 examples: - apps/example.tsx -`, - ); - await writeFile( - join(ghost, "fingerprint", "memory", "decisions", "shared-decision.yml"), - `schema: ghost.decision/v1 -id: shared-decision -status: accepted -title: Parent decision -claim: Parent claim. -rationale: Parent rationale. -evidence: - - note: Parent evidence. -decided_at: "2026-05-17T00:00:00.000Z" `, ); } @@ -276,9 +255,6 @@ async function writeChildBundle( memoryDir = ".ghost", ): Promise { const ghost = memoryPackagePath(root, memoryDir); - await mkdir(join(ghost, "fingerprint", "memory", "decisions"), { - recursive: true, - }); await mkdir(join(root, "review"), { recursive: true }); await writeSplitFingerprintPackage( ghost, @@ -350,19 +326,6 @@ checks: observed_count: 4 examples: - review/page.tsx -`, - ); - await writeFile( - join(ghost, "fingerprint", "memory", "decisions", "shared-decision.yml"), - `schema: ghost.decision/v1 -id: shared-decision -status: rejected -title: Child decision -claim: Child claim. -rationale: Child rationale. -evidence: - - path: review/page.tsx -decided_at: "2026-05-18T00:00:00.000Z" `, ); } @@ -378,7 +341,7 @@ async function writeSplitFingerprintPackage( ): Promise { const fingerprintDir = join(pkg, "fingerprint"); const doc = parseYaml(fingerprintRaw) as Record; - await mkdir(join(fingerprintDir, "enforcement"), { recursive: true }); + await mkdir(fingerprintDir, { recursive: true }); await Promise.all([ writeFile( join(fingerprintDir, "manifest.yml"), @@ -411,12 +374,7 @@ async function writeSplitFingerprintPackage( stringifyYaml(doc.composition ?? { patterns: [] }), ), ...(checksRaw - ? [ - writeFile( - join(fingerprintDir, "enforcement", "checks.yml"), - checksRaw, - ), - ] + ? [writeFile(join(fingerprintDir, "checks.yml"), checksRaw)] : []), ]); } diff --git a/packages/ghost/test/fixtures/context-sandboxes/harness.ts b/packages/ghost/test/fixtures/context-sandboxes/harness.ts index 54911930..0dde26bd 100644 --- a/packages/ghost/test/fixtures/context-sandboxes/harness.ts +++ b/packages/ghost/test/fixtures/context-sandboxes/harness.ts @@ -10,10 +10,8 @@ export const REPO_ROOT = resolve( "../../../../..", ); -export type CacheState = "missing" | "present" | "malformed"; - export async function createSingleSurfaceSandbox( - options: { cache?: CacheState; reorderUnrelated?: boolean } = {}, + options: { reorderUnrelated?: boolean } = {}, ): Promise { const root = await createTempRoot("single"); await writeFiles(root, [ @@ -29,7 +27,6 @@ export async function createSingleSurfaceSandbox( fingerprint: singleSurfaceFingerprint(options.reorderUnrelated ?? false), checks: refundChecks(), }); - await writeCache(root, options.cache ?? "missing"); return root; } @@ -178,7 +175,7 @@ async function writePackage( options: { fingerprint: string; checks?: string }, ): Promise { const fingerprintDir = join(pkg, "fingerprint"); - await mkdir(join(fingerprintDir, "enforcement"), { recursive: true }); + await mkdir(fingerprintDir, { recursive: true }); await writeFile( join(fingerprintDir, "manifest.yml"), "schema: ghost.fingerprint-package/v1\nid: local\n", @@ -195,35 +192,13 @@ async function writePackage( ); if (options.checks) { await writeFile( - join(fingerprintDir, "enforcement", "checks.yml"), + join(fingerprintDir, "checks.yml"), options.checks, "utf-8", ); } } -async function writeCache(root: string, state: CacheState): Promise { - if (state === "missing") return; - const cacheDir = join(root, ".ghost", "fingerprint", "sources", "cache"); - await mkdir(cacheDir, { recursive: true }); - await writeFile( - join(cacheDir, "inventory.json"), - state === "malformed" - ? "{nope" - : JSON.stringify( - { - platform_hints: ["web"], - build_system_hints: ["vite"], - package_manifests: ["package.json"], - candidate_config_files: ["apps/refunds/theme.ts"], - }, - null, - 2, - ), - "utf-8", - ); -} - function proseLayer(raw: string): string { return raw.split("# inventory")[0].replace("# prose\n", ""); } diff --git a/packages/ghost/test/public-exports.test.ts b/packages/ghost/test/public-exports.test.ts index 7e878292..15e31540 100644 --- a/packages/ghost/test/public-exports.test.ts +++ b/packages/ghost/test/public-exports.test.ts @@ -29,7 +29,7 @@ describe.runIf(hasBuiltExports)("built public exports", () => { expect(fingerprintApi.writeContextBundle).toBeUndefined(); expect(scanApi.scanStatus).toBeTypeOf("function"); - expect(scanApi.inventory).toBeTypeOf("function"); + expect(scanApi.signals).toBeTypeOf("function"); expect(scanApi.loadFingerprintStackForPath).toBeTypeOf("function"); expect(scanApi.initFingerprintPackage).toBeUndefined(); expect(scanApi.lintFingerprintPackage).toBeUndefined(); diff --git a/packages/ghost/test/relay.test.ts b/packages/ghost/test/relay.test.ts index 08d094e8..9b4b2bad 100644 --- a/packages/ghost/test/relay.test.ts +++ b/packages/ghost/test/relay.test.ts @@ -13,7 +13,7 @@ describe("relay", () => { }); it("gathers structured fingerprint context for a target", async () => { - const root = await track(createSingleSurfaceSandbox({ cache: "present" })); + const root = await track(createSingleSurfaceSandbox()); const result = await gatherRelayContext({ cwd: root, diff --git a/packages/ghost/test/scan-status.test.ts b/packages/ghost/test/scan-status.test.ts index e8bf769e..d327675b 100644 --- a/packages/ghost/test/scan-status.test.ts +++ b/packages/ghost/test/scan-status.test.ts @@ -42,7 +42,7 @@ describe("scanStatus readiness", () => { const status = await scanStatus(dir); expect(status.fingerprint.state).toBe("present"); - expect(status.cache.state).toBe("missing"); + expect("cache" in status).toBe(false); expect(status.recommended_next).toBeNull(); expect(status.readiness.state).toBe("fingerprint-empty"); expect(status.readiness.layer_counts).toEqual({ @@ -57,7 +57,7 @@ describe("scanStatus readiness", () => { ]); }); - it("does not count generated cache as canonical inventory readiness", async () => { + it("does not report sources cache as package readiness", async () => { await mkdir(join(dir, "fingerprint", "sources", "cache"), { recursive: true, }); @@ -69,7 +69,7 @@ describe("scanStatus readiness", () => { const status = await scanStatus(dir); - expect(status.cache.state).toBe("present"); + expect("cache" in status).toBe(false); expect(status.readiness.state).toBe("fingerprint-empty"); expect(status.readiness.layer_counts.inventory).toBe(0); }); @@ -213,7 +213,7 @@ composition: const status = await scanStatus(dir); - expect(status.cache.state).toBe("missing"); + expect("cache" in status).toBe(false); expect(status.readiness.state).toBe("fingerprint-ready"); expect(status.readiness.layer_counts).toEqual({ prose: 1,