From 55cf80904784e5cda3ec63fd63c91da1d1046239 Mon Sep 17 00:00:00 2001 From: Nahiyan Khan Date: Fri, 17 Jul 2026 14:35:45 -0400 Subject: [PATCH 1/3] feat: adopt ghost package terminology --- .agents/skills/create-pr/SKILL.md | 20 +++--- .changeset/calm-guidance-language.md | 5 ++ .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/deploy-pages.yml | 4 +- CLAUDE.md | 48 ++++++------- CONTRIBUTING.md | 20 +++--- README.md | 62 ++++++++-------- apps/docs/.ghost/manifest.yml | 2 +- apps/docs/src/generated/cli-manifest.json | 38 +++++----- docs/purposes.md | 33 +++++---- install/install.sh | 8 +-- install/manifest.json | 2 +- packages/context-control/README.md | 10 +-- packages/context-control/cli.mjs | 8 +-- packages/context-control/lib/ghost.mjs | 2 +- packages/context-control/lib/model.mjs | 4 +- packages/context-control/ui/index.html | 2 +- packages/ghost/README.md | 46 ++++++------ packages/ghost/package.json | 8 ++- packages/ghost/src/commands/checks-command.ts | 6 +- .../ghost/src/commands/command-discovery.ts | 8 +-- packages/ghost/src/commands/export-command.ts | 30 ++++---- .../src/commands/fingerprint-commands.ts | 21 +++--- packages/ghost/src/commands/gather-command.ts | 30 ++++---- packages/ghost/src/commands/init-command.ts | 31 ++++---- packages/ghost/src/commands/pull-command.ts | 10 +-- packages/ghost/src/commands/pulse-command.ts | 12 ++-- packages/ghost/src/commands/review-command.ts | 14 ++-- packages/ghost/src/commands/skill-command.ts | 4 +- packages/ghost/src/fingerprint.ts | 3 + packages/ghost/src/ghost-core/catalog/menu.ts | 2 +- packages/ghost/src/ghost-core/check/index.ts | 2 +- packages/ghost/src/ghost-core/check/lint.ts | 4 +- packages/ghost/src/ghost-core/check/load.ts | 6 +- packages/ghost/src/ghost-core/check/types.ts | 6 +- packages/ghost/src/ghost-core/index.ts | 12 +++- packages/ghost/src/ghost-core/markdown.ts | 2 +- packages/ghost/src/ghost-core/node/schema.ts | 2 +- packages/ghost/src/ghost-core/node/types.ts | 2 +- .../ghost/src/ghost-core/package-manifest.ts | 54 ++++++++------ .../src/ghost-core/skill-bundle-loader.ts | 2 +- packages/ghost/src/index.ts | 4 +- .../src/init-payloads/median/cliche.median.md | 12 ++-- .../src/init-payloads/median/median-tells.md | 2 +- .../ghost/src/init-payloads/skeleton/brand.md | 2 +- .../src/init-payloads/skeleton/glossary.md | 2 +- packages/ghost/src/package.ts | 22 ++++++ packages/ghost/src/review/resolve.ts | 2 +- packages/ghost/src/review/review-packet.ts | 33 +++++---- packages/ghost/src/scan/constants.ts | 14 ++-- packages/ghost/src/scan/file-kind.ts | 30 ++++---- .../src/scan/fingerprint-package-lint.ts | 31 ++++---- .../src/scan/fingerprint-package-loader.ts | 36 +++++----- .../ghost/src/scan/fingerprint-package.ts | 57 +++++++++------ packages/ghost/src/scan/index.ts | 7 +- packages/ghost/src/scan/tarball.ts | 6 +- packages/ghost/src/scan/templates.ts | 33 +++++---- packages/ghost/src/skill-bundle/SKILL.md | 47 ++++++------- .../references/adapting-a-starter.md | 22 +++--- .../references/authoring-scenarios.md | 60 ++++++++-------- .../src/skill-bundle/references/blocks.md | 28 ++++---- .../src/skill-bundle/references/brief.md | 10 +-- .../src/skill-bundle/references/capture.md | 59 ++++++++-------- .../skill-bundle/references/concrete-tiers.md | 14 ++-- .../src/skill-bundle/references/recall.md | 14 ++-- .../src/skill-bundle/references/schema.md | 14 ++-- .../src/skill-bundle/references/self-check.md | 24 +++---- .../skill-bundle/references/steering-audit.md | 8 +-- packages/ghost/test/cli.test.ts | 29 ++++---- .../ghost/test/fingerprint-package.test.ts | 70 +++++++++++-------- .../ghost/test/ghost-core/node-schema.test.ts | 2 +- packages/ghost/test/public-exports.test.ts | 26 +++++-- .../ghost/test/skill-bundle-manifest.test.ts | 2 +- packages/steering-control/README.md | 16 ++--- packages/steering-control/lib/arms.mjs | 6 +- .../vessel-light/.ghost/anti-goal.median.md | 12 ++-- .../.ghost/checks/motion-restraint.md | 2 +- packages/vessel-light/.ghost/manifest.yml | 2 +- .../materials/ref/composition.form.html | 4 +- .../materials/ref/composition.overlay.html | 6 +- packages/vessel-light/README.md | 2 +- packages/vessel-light/demo/README.md | 8 +-- packages/vessel-light/demo/run-arm.md | 2 +- packages/vessel-react/.ghost/index.md | 4 +- .../.ghost/principle.reference-not-brand.md | 2 +- packages/vessel-react/.shadcn/skills.md | 4 +- packages/vessel-react/README.md | 24 +++---- .../fingerprint/checks/component-integrity.md | 6 +- packages/vessel-react/fingerprint/index.md | 8 +-- .../vessel-react/fingerprint/manifest.yml | 2 +- packages/vessel-react/public/r/registry.json | 2 +- .../vessel-react/public/r/vessel-base.json | 2 +- packages/vessel-react/registry.json | 2 +- .../vessel-react/scripts/build-base-vars.mjs | 2 +- .../vessel-react/scripts/generate-skills.mjs | 4 +- packages/vessel-react/src/index.ts | 2 +- packages/vessel-react/src/mcp/resources.ts | 4 +- packages/vessel-react/src/mcp/tools.ts | 10 +-- scripts/check-packed-package.mjs | 1 + scripts/dump-cli-help.mjs | 2 +- 100 files changed, 776 insertions(+), 674 deletions(-) create mode 100644 .changeset/calm-guidance-language.md create mode 100644 packages/ghost/src/package.ts diff --git a/.agents/skills/create-pr/SKILL.md b/.agents/skills/create-pr/SKILL.md index 68941f28..ee94346b 100644 --- a/.agents/skills/create-pr/SKILL.md +++ b/.agents/skills/create-pr/SKILL.md @@ -1,16 +1,16 @@ --- name: create-pr description: >- - Create a GitHub pull request for the Ghost repo from the current branch: - inspect branch changes, handle scoped commits, verify release and Ghost + Create a GitHub pull request for the ghost repo from the current branch: + inspect branch changes, handle scoped commits, verify release and ghost review requirements, push safely, and open a draft PR. Use when the user says "create PR", "open PR", "submit PR", "push PR", "make a pull request", or - asks to publish the current Ghost branch for review. + asks to publish the current ghost branch for review. --- # Create PR -Create a GitHub pull request for the Ghost repo from the current branch. Prefer +Create a GitHub pull request for the ghost repo from the current branch. Prefer a draft PR unless the user explicitly asks for a ready PR and validation has passed. @@ -67,12 +67,12 @@ Run these in parallel where possible: Before pushing, verify that `origin` points to a Block-managed GitHub organization for this repo, normally `git@github.com:block/ghost.git` or an -equivalent `https://github.com/block/ghost` remote. Do not push Ghost source to +equivalent `https://github.com/block/ghost` remote. Do not push ghost source to personal or non-Block repositories. ## Step 4: Check Release Hygiene -Ghost publishes only `@design-intelligence/ghost`; private packages are ignored by +ghost publishes only `@design-intelligence/ghost`; private packages are ignored by Changesets. Inspect the diff and decide whether a changeset is required: @@ -118,7 +118,7 @@ pnpm test pnpm check ``` -Ghost-specific validation: +ghost-specific validation: ```bash ghost check --base @@ -134,7 +134,7 @@ node packages/ghost/dist/bin.js review --base --include-memory Run `ghost check` for implementation or fingerprint changes that can affect a surface. Run `ghost review` when the change touches UI generation behavior, -fingerprint semantics, review packets, composition rules, docs about Ghost's +fingerprint semantics, review packets, composition rules, docs about ghost's surface model, or anything where advisory drift context would help reviewers. For fingerprint edits, also run: @@ -185,7 +185,7 @@ Use this body structure: **Changeset:** added | not needed because ... -**Ghost Review:** +**ghost Review:** - `ghost check --base `: result | not run because ... - `ghost review --base --include-memory`: result | not run because ... @@ -226,7 +226,7 @@ unless the user explicitly asked for a ready PR and validation is complete. Use a body file or heredoc to preserve formatting. After creation, output the PR URL as a clickable link and mention any skipped validation, missing issue link, -or advisory Ghost review finding the user should know about. +or advisory ghost review finding the user should know about. ## Tone diff --git a/.changeset/calm-guidance-language.md b/.changeset/calm-guidance-language.md new file mode 100644 index 00000000..e0bd54b6 --- /dev/null +++ b/.changeset/calm-guidance-language.md @@ -0,0 +1,5 @@ +--- +"@design-intelligence/ghost": minor +--- + +Adopt ghost package terminology with the `ghost.package/v1` schema and package API, preserve legacy aliases, and name exported archives `-ghost-package.tgz`. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9ea1a184..a7c7b5a2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ contact_links: - name: Questions and discussion url: https://github.com/block/ghost/discussions - about: Ask questions and discuss Ghost. The issue tracker is for bugs. + about: Ask questions and discuss ghost. The issue tracker is for bugs. diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index a7ed52f8..312676f7 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages # Builds apps/docs with DEPLOY_BASE=/ghost/ and uploads the dist as the # Pages artifact. apps/docs is the one deployed site — it owns the home, -# the design language catalogue (/ui/*), and the Ghost tooling docs +# the design language catalogue (/ui/*), and the ghost tooling docs # (/tools/drift/*) under a single aesthetic. on: @@ -32,7 +32,7 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - - name: Build Ghost CLI package + - name: Build ghost CLI package run: pnpm --filter @design-intelligence/ghost build - name: Build vessel-react library diff --git a/CLAUDE.md b/CLAUDE.md index 9b1429d6..4198dba7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,14 +1,14 @@ -# Ghost +# ghost Agents now do the making — screens, emails, empty states, sentences. What they cannot reliably preserve is the brand behind that work: the stance, the density, the restraint, the trust moves, and the choices that make an output feel intentional. An agent holds nothing it isn't handed. -Ghost hands it the brand as a portable steering packet: a repo-local `.ghost/` -fingerprint package, a flat corpus of prose nodes read before anything is made. The public npm shape is one package, +ghost hands it the brand as a portable steering packet: a repo-local `.ghost/` +package, a flat corpus of prose nodes read before anything is made. The public npm shape is one package, `@design-intelligence/ghost`, with one user-facing bin, `ghost`. The CLI -validates the corpus, emits the fingerprint menu, pulls selected nodes, records +validates the corpus, emits the guidance menu, pulls selected nodes, records local selection events, and assembles advisory review packets from checks. Optional review checks attach under `.ghost/checks/`. The host agent does all selection and interpretive BYOA work through the installed `ghost` skill. @@ -32,13 +32,13 @@ pnpm --filter @design-intelligence/ghost exec ghost ## Architecture -Ghost is **BYOA (bring your own agent)**. Claude Code, Codex, Cursor, Goose, or -another host agent reads, decides, and writes. Ghost grounds that work with a +ghost is **BYOA (bring your own agent)**. Claude Code, Codex, Cursor, Goose, or +another host agent reads, decides, and writes. ghost grounds that work with a deterministic CLI and an interpretive skill bundle. The CLI does repeatable work with no LLM: schema and node validation, glossary kind-prefix checks, check validation, a flat gather menu, selected-node pulls, local pulse summaries, and review packet assembly. The skill teaches the agent how to author and consume -the fingerprint. +the brand guidance. The canonical root `.ghost/` package is a flat set of prose nodes plus optional checks: @@ -46,31 +46,31 @@ checks: ```text manifest.yml # schema + id (the package anchor) glossary.md # the author's category vocabulary + what each kind means -..md # a brand truth of a declared kind (principle.density.md) -.md # an uncategorized brand truth (voice.md) +..md # guidance of a declared kind (principle.density.md) +.md # uncategorized guidance (voice.md) checks/ # optional review assertions; never a node source ``` The **corpus is flat**. A node is a markdown file: a `description` in -frontmatter (the retrieval payload), optional `materials`, and a brand truth in +frontmatter (the retrieval payload), optional `materials`, and brand guidance in the prose body. A node's identity is its filename minus `.md`; its kind is the filename prefix before the first dot, declared in the glossary. There is no hierarchy, no inheritance, no edges; nesting into folders is a browsing convenience only. -`materials` is the single locator field for concrete materials the truth is +`materials` is the single locator field for concrete materials the guidance is about. It accepts repo-relative paths/globs and absolute HTTPS URLs. Components, patterns, logos, motion files, illustrations, and external asset libraries all use the same field. Guidance stays in prose; `materials` only says where the material is. -While drafting a body, ask three questions of every truth (drafting prompts, +While drafting a body, ask three questions of every node (drafting prompts, never fields): **why** (the stance), **with what** (the materials, and pointers to implementation or assets the agent can inspect), and **how it is assembled** (the patterns that make the output feel intentional). -Altitude lives in the prose: a universal truth is stated plainly; a narrower -truth names its **condition**, the situation it applies in, never a filing +Altitude lives in the prose: universal guidance is stated plainly; narrower +guidance names its **condition**, the situation it applies in, never a filing destination. `ghost gather` emits the complete menu (every node's id, kind, description, material count, and payload labels); the agent pulls every node whose stated situation applies to the actual task. `ghost pull` emits selected node bodies and materials. `ghost @@ -78,10 +78,10 @@ review` reads a diff, matches touched files to node materials, offers relevant checks, and emits an advisory packet for the host agent to judge. **Checks** (`.ghost/checks/*.md`) are optional review assertions that declare -`references` to fingerprint node ids (with optional heading anchors) and prose +`references` to guidance node ids (with optional heading anchors) and prose instructions for the reviewing agent. Checks are feed-back only and never leak into generation context. Scaffold them with `ghost checks init` or `ghost init ---with checks`. Ordinary Git review is the approval boundary for fingerprint +--with checks`. Ordinary Git review is the approval boundary for guidance edits and checks. ## Packages @@ -89,9 +89,9 @@ edits and checks. | Package | Published? | Description | | --- | --- | --- | | `packages/ghost` | yes: `@design-intelligence/ghost` | The public package. Ships the `ghost` CLI, node authoring, corpus validation, gather/pull/pulse, review packet assembly, and the unified skill bundle. Shared runtime lives in `packages/ghost/src/ghost-core`. | -| `packages/vessel-react` | no | A standalone shadcn component registry plus `vessel-mcp` MCP server: the opinionated default reference body. Design-system-agnostic; nothing in Ghost requires it. | -| `packages/vessel-light` | no | Vessel's design language as a portable `.ghost/` fingerprint package for agents writing raw HTML/CSS. No build, no dependencies. | -| `packages/steering-control` | no | Before/after evaluation harness: measures what handing an agent a `.ghost` fingerprint buys, as a deterministic `report.html`. | +| `packages/vessel-react` | no | A standalone shadcn component registry plus `vessel-mcp` MCP server: the opinionated default reference body. Design-system-agnostic; nothing in ghost requires it. | +| `packages/vessel-light` | no | Vessel's design language as a portable `.ghost/` package for agents writing raw HTML/CSS. No build, no dependencies. | +| `packages/steering-control` | no | Before/after evaluation harness: measures what handing an agent a `.ghost` package buys, as a deterministic `report.html`. | | `apps/docs` | no | Docs site. | ## CLI Commands @@ -100,14 +100,14 @@ Core workflow: | Command | Description | | --- | --- | -| `ghost init` | Scaffold `.ghost/` with the skeleton starter: manifest, glossary, a `brand.md` cover, foundation chapters, context nodes, and the cliche floor. `--template minimal` writes a smaller cover-led starter; `--body vessel-light` installs a full inhabited fingerprint instead. `--with checks` also adds the checks directory. | +| `ghost init` | Scaffold `.ghost/` with the skeleton starter: manifest, glossary, a `brand.md` cover, foundation chapters, context nodes, and the cliche floor. `--template minimal` writes a smaller cover-led starter; `--body vessel-light` installs a full inhabited package instead. `--with checks` also adds the checks directory. | | `ghost checks init` | Scaffold `.ghost/checks/` with an example review assertion. | | `ghost validate` | Validate the package: manifest shape, node validity, material locators, check references, and glossary kind prefixes. | | `ghost gather [ask…]` | Emit the complete guidance menu so the agent can pull applicable nodes. | | `ghost pull […]` | Emit selected nodes' bodies and materials; append the selection to the local `.ghost/.events` tape. | | `ghost review` | Emit an advisory review packet for a diff using material-backed nodes and checks (requires `.ghost/checks/`). | | `ghost pulse` | Summarize local gather/pull events from `.ghost/.events`. | -| `ghost export` | Package the fingerprint as a portable tarball with a materials audit (`--strict` fails on stranded locators). | +| `ghost export` | Bundle the guidance as a portable tarball with a materials audit (`--strict` fails on stranded locators). | | `ghost skill install` | Install the unified `ghost` skill bundle. | Advanced/maintenance: @@ -120,13 +120,13 @@ Advanced/maintenance: - `@design-intelligence/ghost` for the combined surface. - `@design-intelligence/ghost/scan` for package-path resolution helpers. -- `@design-intelligence/ghost/fingerprint` for node package authoring, validation, parsing, and serialization. +- `@design-intelligence/ghost/package` for node package authoring, validation, parsing, and serialization. - `@design-intelligence/ghost/core` for shared schemas, types, and loaders. - `@design-intelligence/ghost/cli` for `buildCli()`. ## Environment Variables -No API key is required to run Ghost. Optional variables: +No API key is required to run ghost. Optional variables: - `GHOST_PACKAGE_DIR` selects a custom package directory (or pass `--package`). @@ -155,7 +155,7 @@ Use `patch` for fixes and docs, `minor` for new commands/flags/exports, and - Shipped prose is plain, precise, restrained, and product-minded. When completeness and compactness compete, compactness wins; link out for depth. - Concrete over aspirational: name the decision a truth forces, not the value + Concrete over aspirational: name the decision the guidance forces, not the value it serves. Teach terms by worked example before defining them. No em dashes, no brand-deck filler, no exclamation points. `scripts/check-terminology.mjs` enforces the retired-vocabulary list; run `pnpm check:terminology` before diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c3f390c..88c6e94e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ -# Contributing to Ghost +# Contributing to ghost Contributions are welcome: fixes, docs, dogfooding reports, and friction you -hit as a user are all valuable. Ghost is pre-1.0 and moving fast, so this doc +hit as a user are all valuable. ghost is pre-1.0 and moving fast, so this doc is organized around what gets a PR declined, because that's the information you need. Everything below is enforced by review, by CI, or both. @@ -11,10 +11,10 @@ These are project-defining. A PR that violates one gets declined regardless of quality elsewhere. 1. **The CLI computes; it never decides.** No LLM calls in the CLI, ever. - Ghost is BYOA (bring your own agent): the host agent does all interpretive + ghost is BYOA (bring your own agent): the host agent does all interpretive work. The CLI's value is determinism: same input, same output, no key required. -2. **The fingerprint corpus stays flat.** No hierarchy, no inheritance, no +2. **The corpus stays flat.** No hierarchy, no inheritance, no edges between nodes. A node's identity is its filename; its kind is a glossary-declared prefix. Altitude lives in prose, not structure. 3. **Feed-back never leaks into generation context.** Checks and @@ -28,11 +28,11 @@ of quality elsewhere. `scripts/check-terminology.mjs` fails CI on banned phrasings. The script is the source of truth; these are the clusters and their reasons: -- **The "memory" compounds.** Ghost holds nothing; an agent holds nothing it - isn't handed. The fingerprint is a steering packet, not recall. +- **The "memory" compounds.** ghost holds nothing; an agent holds nothing it + isn't handed. The ghost package is a steering packet, not recall. - **The "judg-" words.** The CLI computes; the host agent decides. Review output is advisory. -- **Product-UI-centric framing.** Ghost is brand through every medium +- **Product-UI-centric framing.** ghost is brand through every medium (screens, emails, empty states, sentences), so phrases that anchor it to product UI alone are banned. - **Bare next-major version markers.** No speculative roadmap language in @@ -56,10 +56,10 @@ the list. ## Dogfooding -Surfaces in this repo carry their own fingerprints: `apps/docs/.ghost` and +Surfaces in this repo carry their own ghost packages: `apps/docs/.ghost` and `packages/vessel-react/.ghost`. If you touch a surface, `ghost gather` from its -fingerprint before you build, and expect review against it. This is where -you experience Ghost as a user; treat friction you hit here as a bug worth +package before you build, and expect review against it. This is where +you experience ghost as a user; treat friction you hit here as a bug worth reporting. ## Governance diff --git a/README.md b/README.md index 3b502de0..a0f5ce6c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Ghost +# ghost -**Ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose -truths (your stance, your voice, your trust moves) checked into the repo and +**ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose +guidance (your stance, your voice, your trust moves) checked into the repo and read by any agent before it makes anything: a screen, an email, an empty state, a sentence. @@ -14,7 +14,7 @@ state, a sentence. ``` Today those decisions live in reviewers' heads: "that's not our voice," again, -on every surface. The agent that built the thing never saw them. Ghost writes +on every surface. The agent that built the thing never saw them. ghost writes them down once, where the agent looks first. One portable packet; Claude Code, Codex, Cursor, and Goose all read the same @@ -31,18 +31,18 @@ npx ghost skill install ## Use It -Ghost is **bring-your-own-agent**. Install the skill bundle so Claude Code, +ghost is **bring-your-own-agent**. Install the skill bundle so Claude Code, Codex, Cursor, Goose, or another host agent knows how to author and use the -fingerprint, then ask in plain English: +ghost package, then ask in plain English: ```text -Set up the Ghost fingerprint for this repo. +Set up the ghost package for this repo. Write down the decision I keep repeating about checkout. -Brief this work from the Ghost fingerprint. -Review this diff against the Ghost checks. +Brief this work from the ghost package. +Review this diff against the ghost checks. ``` -Ghost never calls an LLM itself; your agent does the thinking. No API key, +ghost never calls an LLM itself; your agent does the thinking. No API key, no lock-in. ## The Loop @@ -50,15 +50,15 @@ no lock-in. ```bash ghost init # scaffold .ghost/ with the skeleton starter ghost checks init # opt in to review assertions -ghost validate # make sure the fingerprint is well-formed -ghost gather # before building: list every truth relevant to this task -ghost pull # read the picked truths' full bodies -ghost review # during review: match a diff to truths and checks -ghost export # package the fingerprint as a portable artifact +ghost validate # make sure the package is well-formed +ghost gather # before building: list all available guidance +ghost pull # read the picked nodes' full bodies +ghost review # during review: match a diff to guidance and checks +ghost export # bundle the guidance as a portable artifact ghost pulse # while tuning: see what agents reached for ``` -Ghost keeps a private local log of what agents reached for; `ghost pulse` +ghost keeps a private local log of what agents reached for; `ghost pulse` reads it so you can tune descriptions. It stays on your machine and never enters version control. @@ -74,15 +74,15 @@ generation starts from the model's average again. The work that compounds is architectural: decide where that average serves, decide where the brand must win, and put those decisions where the agent reads before it makes. -Ghost is that artifact: a fingerprint checked into the repo, carrying the -truths, the materials they point at, and the conditions they hold under. +ghost is that artifact: a `.ghost/` package checked into the repo, carrying the +guidance, the materials they point at, and the conditions they hold under. Buttons stay buttons. The moments that carry your brand get your stance instead of the default. The few author it once. Every agent it travels to builds from it. ## How It Works -A fingerprint is a small folder of prose. The CLI computes; your agent reads, +A ghost package is a small folder of prose. The CLI computes; your agent reads, writes, and decides. ```text @@ -90,16 +90,16 @@ writes, and decides. manifest.yml # schema + package id + optional cover id glossary.md # your kind vocabulary + what each kind means brand.md # example cover inlined by gather - principle.trust.md # a brand truth of kind `principle` - asset.logo.md # a truth that may point at concrete materials + principle.trust.md # guidance of kind `principle` + asset.logo.md # guidance that points at concrete materials checks/ # optional review assertions; never nodes ``` -The fingerprint is a **flat set of nodes**. The optional `cover:` in +The package is a **flat set of nodes**. The optional `cover:` in `manifest.yml` may name any node; `ghost gather` inlines it before the menu. The default skeleton calls that node `brand`, but the filename is not reserved. A node is one markdown file: a `description` in frontmatter, optional -`materials`, and a brand truth in the prose body. +`materials`, and a brand guidance in the prose body. ```markdown --- @@ -114,7 +114,7 @@ constrained or when brand presence should recede. ``` `materials` is a list of paths or URLs pointing at the concrete stuff the -truth is about: repo-relative paths/globs or absolute HTTPS URLs. Components, +guidance is about: repo-relative paths/globs or absolute HTTPS URLs. Components, patterns, logos, motion files, illustrations, and external asset libraries all use the same field. Guidance stays in prose; `materials` only says where the material is. @@ -147,12 +147,12 @@ output never enters generation context. The packet is the product; the CLI is the courier. Everything above (gather, pull, review, checks, the local log) is machinery around the -fingerprint, and the fingerprint outlives all of it. +brand guidance, and the guidance outlives all of it. ## Portable by Design -The fingerprint travels. It is agent-agnostic (every host agent reads the same -packet), medium-agnostic (the same truths steer a screen, a page, an email, a +The package travels. It is agent-agnostic (every host agent reads the same +packet), medium-agnostic (the same guidance steers a screen, a page, an email, a sentence), and repo-native (it moves with a clone, a fork, a new hire's first checkout). When you need it as a standalone artifact: @@ -166,7 +166,7 @@ point at things that moved. ## Project Status: Beta > [!WARNING] -> Ghost is pre-1.0 and under active development. The CLI, fingerprint schema, +> ghost is pre-1.0 and under active development. The CLI, package schema, > on-disk `.ghost/` package shape, and public JavaScript exports may change in > breaking ways before a stable 1.0 release. @@ -174,10 +174,10 @@ point at things that moved. | Path | Role | Published? | | ---- | ---- | --- | -| [`packages/ghost`](./packages/ghost) | The public `ghost` CLI, node authoring, fingerprint validation, gather/pull, review packet assembly, and the skill bundle. | yes: `@design-intelligence/ghost` | +| [`packages/ghost`](./packages/ghost) | The public `ghost` CLI, node authoring, package validation, gather/pull, review packet assembly, and the skill bundle. | yes: `@design-intelligence/ghost` | | [`packages/vessel-react`](./packages/vessel-react) | A standalone shadcn component registry plus `vessel-mcp` MCP server. | no | -| [`packages/vessel-light`](./packages/vessel-light) | Vessel's design language as a portable `.ghost/` fingerprint for agents writing raw HTML/CSS. | no | -| [`packages/steering-control`](./packages/steering-control) | Before/after evaluation harness: measures what a `.ghost` fingerprint buys as a self-contained `report.html`. | no | +| [`packages/vessel-light`](./packages/vessel-light) | Vessel's design language as a portable `.ghost/` package for agents writing raw HTML/CSS. | no | +| [`packages/steering-control`](./packages/steering-control) | Before/after evaluation harness: measures what a `.ghost` package buys as a self-contained `report.html`. | no | | [`apps/docs`](./apps/docs) | Docs site. | no | ## Development diff --git a/apps/docs/.ghost/manifest.yml b/apps/docs/.ghost/manifest.yml index 22e030bc..266b52ab 100644 --- a/apps/docs/.ghost/manifest.yml +++ b/apps/docs/.ghost/manifest.yml @@ -1,2 +1,2 @@ -schema: ghost.fingerprint-package/v1 +schema: ghost.package/v1 id: local diff --git a/apps/docs/src/generated/cli-manifest.json b/apps/docs/src/generated/cli-manifest.json index 8c44fc8f..f9ec1148 100644 --- a/apps/docs/src/generated/cli-manifest.json +++ b/apps/docs/src/generated/cli-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-15T12:11:34.119Z", + "generatedAt": "2026-07-17T03:34:39.594Z", "tools": [ { "tool": "ghost", @@ -8,16 +8,16 @@ "tool": "ghost", "name": "validate", "rawName": "validate [file]", - "description": "Validate the Ghost fingerprint package — manifest shape, node validity, material locators, check references, and glossary kind prefixes. Defaults to .ghost.", + "description": "Validate the ghost package: manifest shape, node validity, material locators, check references, and glossary kind prefixes. Defaults to .ghost.", "group": "core", "defaultHelp": true, "compactName": "validate", - "summary": "Validate the fingerprint: artifact shape + node validity.", + "summary": "Validate the ghost package: artifact shape + node validity.", "options": [ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -36,16 +36,16 @@ "tool": "ghost", "name": "init", "rawName": "init", - "description": "Create a root .ghost node fingerprint package", + "description": "Create a root .ghost package of nodes", "group": "core", "defaultHelp": true, "compactName": "init", - "summary": "Scaffold a .ghost/ fingerprint: manifest, glossary, and a node.", + "summary": "Scaffold a .ghost/ package: manifest, glossary, and a node.", "options": [ { "rawName": "--package ", "name": "package", - "description": "Exact fingerprint package directory to initialize", + "description": "Exact ghost package directory to initialize", "default": null, "takesValue": true, "negated": false @@ -77,7 +77,7 @@ { "rawName": "--force", "name": "force", - "description": "Overwrite existing Ghost fingerprint files", + "description": "Overwrite existing ghost package files", "default": null, "takesValue": false, "negated": false @@ -105,7 +105,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -141,7 +141,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -201,7 +201,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -229,7 +229,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -280,11 +280,11 @@ "tool": "ghost", "name": "export", "rawName": "export", - "description": "Package the fingerprint as a portable brand artifact with a locator audit.", + "description": "Package the ghost package as a portable brand artifact with a locator audit.", "group": "core", "defaultHelp": true, "compactName": "export", - "summary": "Package the fingerprint as a portable brand artifact.", + "summary": "Package the ghost package as a portable brand artifact.", "options": [ { "rawName": "--out ", @@ -313,7 +313,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -341,7 +341,7 @@ { "rawName": "--package ", "name": "package", - "description": "Use this fingerprint package directory (default: ./.ghost)", + "description": "Use this ghost package directory (default: ./.ghost)", "default": null, "takesValue": true, "negated": false @@ -380,11 +380,11 @@ "tool": "ghost", "name": "skill", "rawName": "skill ", - "description": "Install the unified Ghost skill bundle.", + "description": "Install the unified ghost skill bundle.", "group": "core", "defaultHelp": true, "compactName": "skill install", - "summary": "Install the Ghost skill bundle.", + "summary": "Install the ghost skill bundle.", "options": [ { "rawName": "--dest ", @@ -405,7 +405,7 @@ { "rawName": "--force", "name": "force", - "description": "Overwrite an existing installed Ghost skill", + "description": "Overwrite an existing installed ghost skill", "default": null, "takesValue": false, "negated": false diff --git a/docs/purposes.md b/docs/purposes.md index 9f1536ab..bd630c8d 100644 --- a/docs/purposes.md +++ b/docs/purposes.md @@ -1,26 +1,25 @@ -# Architecture: What Fingerprints Are For +# Architecture: What ghost Packages Are For -> **Audience: Ghost maintainers and contributors.** This is the internal model -> doc. It defends the boundary between the fingerprint artifact and the consumers +> **Audience: ghost maintainers and contributors.** This is the internal model +> doc. It defends the boundary between the `.ghost/` package and the consumers > that read it. It is not an onboarding guide and it assumes the full vocabulary -> (projection, leak, corpus, glossary). If you are adopting Ghost, start with -> [Five-Minute Ghost](../apps/docs/src/content/docs/quickstart.mdx) and +> (projection, leak, corpus, glossary). If you are adopting ghost, start with > [Getting Started](../apps/docs/src/content/docs/getting-started.mdx) instead. -Ghost has one artifact, the `.ghost/` fingerprint package, and several consumers +ghost has one artifact, the `.ghost/` package, and several consumers that read it. This page exists to keep them honest. ## The rule -> A consumer may read the fingerprint through any **projection** it likes. A -> consumer may **not** change the shape of the fingerprint to suit itself. +> A consumer may read the package through any **projection** it likes. A +> consumer may **not** change the shape of the package to suit itself. -The fingerprint is a deliberately dumb source of truth. It does not know who is +The package is a deliberately dumb source of truth. It does not know who is asking. Every purpose lives in the projection, not in hidden routing logic. The test for any feature that "feels bundled": -> Does serving this purpose require changing the *shape* of the fingerprint: the +> Does serving this purpose require changing the *shape* of the package: the > flat corpus, node frontmatter, filename-kind convention, checks reservation, or > glossary? > - **No** then it is a projection. Fine. Keep it out of the model. @@ -38,8 +37,8 @@ into folders is a browsing convenience only. | Part | Job | | --- | --- | | `manifest.yml` | Schema version and package id; the package's anchor. | -| `glossary.md` | The author's dictionary: every term with defined meaning in the corpus. Ghost ships no fixed vocabulary. | -| Prose nodes (`..md`, `.md`) | Durable brand truths; each body answers why (the stance), with what (the materials), or how it is assembled (the patterns). Altitude lives in prose; narrower truths name their condition. | +| `glossary.md` | The author's dictionary: every term with defined meaning in the corpus. ghost ships no fixed vocabulary. | +| Prose nodes (`..md`, `.md`) | Durable brand guidance; each body answers why (the stance), with what (the materials), or how it is assembled (the patterns). Altitude lives in prose; narrower guidance names its condition. | | Node frontmatter | `description` (retrieval payload) and optional `materials` (repo-relative paths/globs or HTTPS URLs for concrete materials the prose governs). | | `checks/` | Optional review assertions binding to nodes with `references`. Never a node source and never generation input. | @@ -47,7 +46,7 @@ One resolution mechanism, read-only: - **The menu.** `ghost gather` emits every node's id, kind, description, and material count. The agent reads the ask against descriptions and pulls every - truth whose stated situation applies. Ghost does no NLP and no selection. + node whose stated situation applies. ghost does no NLP and no selection. The optional `cover` in `manifest.yml` names the human-curated front door. `ghost gather` inlines that node before the menu and excludes it from the @@ -69,11 +68,11 @@ Two rules keep the reservation honest: | Consumer | CLI surface | Projection it needs | Reads | Changes the model? | | --- | --- | --- | --- | --- | -| **Authoring** | `ghost init`, `ghost validate`, `ghost checks init` | The raw nodes, checks, and glossary for a human or agent writing the fingerprint. | the package | **No**, this is the model. | +| **Authoring** | `ghost init`, `ghost validate`, `ghost checks init` | The raw nodes, checks, and glossary for a human or agent writing the guidance. | the package | **No**, this is the model. | | **Generation** | `ghost gather [ask…]`, `ghost pull ` | The flat menu, then selected node bodies and materials. | nodes only | **No** if selection stays with the agent and checks stay invisible. | | **Local signal** | `ghost pulse` | The gitignored event tape (`.ghost/.events`) written by `gather` and `pull`, used to tune descriptions and menu ergonomics. | event ids and miss suggestions | **No**, observability must not become ranking, memory, or canonical state. | | **Diff review** | `ghost review` | Touched files matched to node `materials`, relevant checks, referenced prose, gaps, and the diff. | nodes, checks, diff | **No** if checks bind by `references` and are not gathered. | -| **Fleet** | (future) | Many fingerprints at once: distances, cohorts, summaries. | many corpora, read-only | **No**, consumes exports read-only. | +| **Fleet** | (future) | Many ghost packages at once: distances, cohorts, summaries. | many corpora, read-only | **No**, consumes exports read-only. | ## Known leaks @@ -83,7 +82,7 @@ Two rules keep the reservation honest: *Fix: `description` is the retrieval payload; sharpen descriptions, show the menu, let the agent pick.* -2. **Filing by destination.** A truth authored as `for-emails.md` smuggles a +2. **Filing by destination.** A node authored as `for-emails.md` smuggles a routing model into the corpus. Conditions belong in prose as situations, not filename buckets. @@ -117,7 +116,7 @@ Two rules keep the reservation honest: ## One line -The flat corpus is how brand truths are **stored and owned**; the glossary plus +The flat corpus is how brand guidance is **stored and owned**; the glossary plus the `gather` menu is how context is **selected**; checks are how review is **grounded**. One model, many projections, and the model never bends to serve a projection. diff --git a/install/install.sh b/install/install.sh index 91a8a4cb..0edf8a63 100755 --- a/install/install.sh +++ b/install/install.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Ghost — install the unified brand-fingerprint skill bundle. +# ghost — install the unified brand-guidance skill bundle. # # Usage: # curl -fsSL https://raw.githubusercontent.com/block/ghost/main/install/install.sh | sh @@ -143,7 +143,7 @@ fetch() { $DL_CMD "$url" } -printf 'Ghost — installing %s skill bundle\n' "$GHOST_AGENT" +printf 'ghost — installing %s skill bundle\n' "$GHOST_AGENT" printf ' source: %s\n' "$GHOST_SOURCE" printf ' dest: %s\n' "$GHOST_DEST" printf '\n' @@ -198,8 +198,8 @@ printf '\nInstalled %d files to %s\n' "$count" "$GHOST_DEST" printf '\n' printf 'Next:\n' printf ' cd \n' -printf ' Tell your agent: "Set up the Ghost fingerprint for this repo"\n' +printf ' Tell your agent: "Set up the ghost package for this repo"\n' printf '\n' -printf 'The agent will use .ghost/ as the checked-in brand fingerprint: a flat\n' +printf 'The agent will use .ghost/ as the checked-in ghost package: a flat\n' printf 'corpus of prose nodes it gathers and pulls before building, validates\n' printf 'with ghost validate, and can review diffs against with ghost review.\n' diff --git a/install/manifest.json b/install/manifest.json index 0e61dba3..8205cb1b 100644 --- a/install/manifest.json +++ b/install/manifest.json @@ -1,7 +1,7 @@ { "$schema": "ghost.install-manifest/v1", "name": "ghost", - "description": "Ghost recipes \u2014 create, author, and gather the repo-local brand fingerprint bundle.", + "description": "ghost recipes \u2014 create, author, and gather the repo-local ghost package.", "version": "0.1.0", "source": { "package": "packages/ghost/src/skill-bundle" diff --git a/packages/context-control/README.md b/packages/context-control/README.md index 90ad988b..f961bb07 100644 --- a/packages/context-control/README.md +++ b/packages/context-control/README.md @@ -1,14 +1,14 @@ # context-control -A local UI for testing the selection half of Ghost's loop. steering-control -measures what a fingerprint buys downstream of generation; context-control -measures whether the right truths even make it into context. Selection is +A local UI for testing the selection half of ghost's loop. steering-control +measures what a ghost package buys downstream of generation; context-control +measures whether the right guidance even makes it into context. Selection is the unit. No generation, ever. `ghost gather` does no filtering: the menu is always the whole catalog, and selection happens in the model's head against each node's `description`. So what this bench actually tests is whether descriptions are good enough -retrieval payloads — a truth with a vague description is invisible at +retrieval payloads — a node with a vague description is invisible at selection time no matter how good its body is. ## Run @@ -29,7 +29,7 @@ else `ghost` on PATH). ## Screens -**fingerprint** — the catalog rendered as the selection surface the model +**package** — the catalog rendered as the selection surface the model sees: id, kind, description, material count, coverage line. Click a node to see its real `ghost pull` output in a drawer. Review descriptions as retrieval payloads, not file contents; a node with no description is flagged diff --git a/packages/context-control/cli.mjs b/packages/context-control/cli.mjs index 352f6454..1af5b26f 100644 --- a/packages/context-control/cli.mjs +++ b/packages/context-control/cli.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// context-control: view a fingerprint as the selection surface it is, and +// context-control: view a ghost package as the selection surface it is, and // bench single-shot selection against it. No generation, ever. import { existsSync } from "node:fs"; import { resolve } from "node:path"; @@ -20,10 +20,10 @@ const args = process.argv.slice(2); if (args.includes("--help") || args.includes("-h")) { console.log(`usage: context-control [options] -Serves the context-control UI for a .ghost fingerprint package. +Serves the context-control UI for a .ghost package. Options: - --package fingerprint package directory (default: ./.ghost) + --package ghost package directory (default: ./.ghost) --asks asks.md shared with steering-control --port port (default: 4114) --ghost ghost binary (default: repo dist build, else PATH)`); @@ -42,7 +42,7 @@ for (let i = 0; i < args.length; i += 2) { const packageDir = resolve(process.cwd(), opts.package); if (!existsSync(packageDir)) { - console.error(`no fingerprint package at ${packageDir}`); + console.error(`no ghost package at ${packageDir}`); process.exit(2); } diff --git a/packages/context-control/lib/ghost.mjs b/packages/context-control/lib/ghost.mjs index 23be51a5..f176413f 100644 --- a/packages/context-control/lib/ghost.mjs +++ b/packages/context-control/lib/ghost.mjs @@ -1,5 +1,5 @@ // Thin shell adapter over the real ghost CLI. context-control never -// re-implements Ghost semantics; everything deterministic comes from +// re-implements ghost semantics; everything deterministic comes from // `ghost gather --format json` and `ghost pull --format json`. import { execFile } from "node:child_process"; import { existsSync } from "node:fs"; diff --git a/packages/context-control/lib/model.mjs b/packages/context-control/lib/model.mjs index 53693378..2b03f47d 100644 --- a/packages/context-control/lib/model.mjs +++ b/packages/context-control/lib/model.mjs @@ -86,14 +86,14 @@ export function fakeModel() { // (packages/ghost/src/skill-bundle/references/). The bench measures actual // agent behavior, so the protocol those recipes install is always on — // there is no skill-less arm. If the recipes change, change this with them. -const SELECT_SYSTEM = `You are an agent selecting brand-fingerprint nodes for a task, +const SELECT_SYSTEM = `You are an agent selecting brand guidance nodes for a task, following the ghost skill's recall recipe. You will get an ask, the cover already in context, and the ghost gather menu. Select only menu node ids against their descriptions. Do not select the cover. - Pull every node whose description indicates its stated situation applies and - whose truth, material, structure, or refusal governs the work. + whose guidance, material, structure, or refusal governs the work. - Skip inapplicable nodes. Topic overlap alone is not applicability. - Do not add nodes for completeness or omit applicable nodes to meet a count. - Anti-goal nodes are review-critical negative space; pull each one whose diff --git a/packages/context-control/ui/index.html b/packages/context-control/ui/index.html index 5f6da428..497533af 100644 --- a/packages/context-control/ui/index.html +++ b/packages/context-control/ui/index.html @@ -102,7 +102,7 @@

context-control · selection is the unit

diff --git a/packages/ghost/README.md b/packages/ghost/README.md index 69722a1b..600bd9b3 100644 --- a/packages/ghost/README.md +++ b/packages/ghost/README.md @@ -1,12 +1,12 @@ # @design-intelligence/ghost -**Ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose -truths (your stance, your voice, your trust moves) checked into the repo and +**ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose +guidance (your stance, your voice, your trust moves) checked into the repo and read by any agent before it makes anything: a screen, an email, an empty state, a sentence. Today those decisions live in reviewers' heads: "that's not our voice," again, -on every surface. The agent that built the thing never saw them. Ghost writes +on every surface. The agent that built the thing never saw them. ghost writes them down once, where the agent looks first. [Documentation](https://block.github.io/ghost/) · @@ -21,18 +21,18 @@ npx ghost skill install ## Use It -Ghost is **bring-your-own-agent**. Install the skill bundle so Claude Code, +ghost is **bring-your-own-agent**. Install the skill bundle so Claude Code, Codex, Cursor, Goose, or another host agent knows how to author and use the -fingerprint, then ask in plain English: +ghost package, then ask in plain English: ```text -Set up the Ghost fingerprint for this repo. +Set up the ghost package for this repo. Write down the decision I keep repeating about checkout. -Brief this work from the Ghost fingerprint. -Review this diff against the Ghost checks. +Brief this work from the ghost package. +Review this diff against the ghost checks. ``` -Ghost never calls an LLM itself; your agent does the thinking. No API key, +ghost never calls an LLM itself; your agent does the thinking. No API key, no lock-in. ## The Loop @@ -40,11 +40,11 @@ no lock-in. ```bash ghost init # scaffold .ghost/ with the skeleton starter ghost checks init # opt in to review assertions -ghost validate # make sure the fingerprint is well-formed -ghost gather # before building: list every truth relevant to this task -ghost pull # read the picked truths' full bodies -ghost review # during review: match a diff to truths and checks -ghost export # package the fingerprint as a portable artifact +ghost validate # make sure the package is well-formed +ghost gather # before building: list all available guidance +ghost pull # read the picked nodes' full bodies +ghost review # during review: match a diff to guidance and checks +ghost export # bundle the guidance as a portable artifact ghost pulse # while tuning: see what agents reached for ``` @@ -58,23 +58,23 @@ covers the full model. Stuck? See ```ts import { - initFingerprintPackage, - lintFingerprintPackage, - loadFingerprintPackage, -} from "@design-intelligence/ghost/fingerprint"; + initghostPackage, + lintghostPackage, + loadghostPackage, +} from "@design-intelligence/ghost/package"; import { buildCatalogMenu } from "@design-intelligence/ghost/core"; import { buildCli } from "@design-intelligence/ghost/cli"; ``` Available subpath exports: `@design-intelligence/ghost`, -`@design-intelligence/ghost/fingerprint`, -`@design-intelligence/ghost/core`, -`@design-intelligence/ghost/cli`, and -`@design-intelligence/ghost/scan`. +`@design-intelligence/ghost/package`, +`@design-intelligence/ghost/core`, and +`@design-intelligence/ghost/cli`. The former package API remains available as +an explicitly deprecated compatibility alias. ## Project Status: Beta -Ghost is pre-1.0 and under active development. The CLI, fingerprint schema, +ghost is pre-1.0 and under active development. The CLI, package schema, on-disk `.ghost/` package shape, and public JavaScript exports may change in breaking ways before a stable 1.0 release. Breaking changes may ship in minor versions; patch versions are reserved for fixes that should not require diff --git a/packages/ghost/package.json b/packages/ghost/package.json index 15a4d3c9..80b7f331 100644 --- a/packages/ghost/package.json +++ b/packages/ghost/package.json @@ -1,7 +1,7 @@ { "name": "@design-intelligence/ghost", "version": "0.23.0", - "description": "Ghost CLI for the brand fingerprint: a portable steering packet of prose truths your agent reads before it builds, with advisory review after", + "description": "ghost CLI for portable .ghost/ packages of brand guidance your agent reads before it builds, with advisory review after", "license": "Apache-2.0", "author": "Block, Inc.", "repository": { @@ -13,7 +13,7 @@ "url": "https://github.com/block/ghost/issues" }, "keywords": [ - "fingerprint", + "brand-guidance", "product-surface", "surface-composition", "design-system", @@ -48,6 +48,10 @@ "types": "./dist/ghost-core/index.d.ts", "import": "./dist/ghost-core/index.js" }, + "./package": { + "types": "./dist/package.d.ts", + "import": "./dist/package.js" + }, "./fingerprint": { "types": "./dist/fingerprint.d.ts", "import": "./dist/fingerprint.js" diff --git a/packages/ghost/src/commands/checks-command.ts b/packages/ghost/src/commands/checks-command.ts index 2fa22d7a..e05f733f 100644 --- a/packages/ghost/src/commands/checks-command.ts +++ b/packages/ghost/src/commands/checks-command.ts @@ -1,5 +1,5 @@ import type { CAC } from "cac"; -import { resolveFingerprintPackage } from "../fingerprint.js"; +import { resolveGhostPackage } from "../package.js"; import { addChecksDir } from "../scan/check-scaffold.js"; import { failFromError } from "./errors.js"; @@ -14,7 +14,7 @@ export function registerChecksCommand(cli: CAC): void { .command("checks ", "Manage review checks: init.") .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: cli or json", { default: "cli" }) .action(async (action: string, opts) => { @@ -30,7 +30,7 @@ export function registerChecksCommand(cli: CAC): void { return; } - const paths = resolveFingerprintPackage(opts.package, process.cwd()); + const paths = resolveGhostPackage(opts.package, process.cwd()); const result = await addChecksDir(paths.packageDir); if (opts.format === "json") { process.stdout.write( diff --git a/packages/ghost/src/commands/command-discovery.ts b/packages/ghost/src/commands/command-discovery.ts index 5e9fa82c..ffa52a53 100644 --- a/packages/ghost/src/commands/command-discovery.ts +++ b/packages/ghost/src/commands/command-discovery.ts @@ -110,14 +110,14 @@ const COMMAND_DISCOVERY = [ group: "core", defaultHelp: true, compactName: "init", - summary: "Scaffold a .ghost/ fingerprint: manifest, glossary, and a node.", + summary: "Scaffold a .ghost/ package: manifest, glossary, and a node.", }, { name: "validate", group: "core", defaultHelp: true, compactName: "validate", - summary: "Validate the fingerprint: artifact shape + node validity.", + summary: "Validate the ghost package: artifact shape + node validity.", }, { name: "gather", @@ -154,7 +154,7 @@ const COMMAND_DISCOVERY = [ group: "core", defaultHelp: true, compactName: "export", - summary: "Package the fingerprint as a portable brand artifact.", + summary: "Package the ghost package as a portable brand artifact.", }, { name: "checks", @@ -168,7 +168,7 @@ const COMMAND_DISCOVERY = [ group: "core", defaultHelp: true, compactName: "skill install", - summary: "Install the Ghost skill bundle.", + summary: "Install the ghost skill bundle.", }, { name: "manifest", diff --git a/packages/ghost/src/commands/export-command.ts b/packages/ghost/src/commands/export-command.ts index 8f09ea20..c6e40f60 100644 --- a/packages/ghost/src/commands/export-command.ts +++ b/packages/ghost/src/commands/export-command.ts @@ -7,10 +7,7 @@ import { resolveLocalMaterialLocator, type TransportedMaterialTier, } from "#ghost-core"; -import { - lintFingerprintPackage, - resolveFingerprintPackage, -} from "../fingerprint.js"; +import { lintGhostPackage, resolveGhostPackage } from "../package.js"; import { readPackageVersion } from "../package-version.js"; import { GHOST_CHECKS_DIR } from "../scan/check-files.js"; import { @@ -18,8 +15,8 @@ import { GHOST_MATERIALS_DIR, } from "../scan/constants.js"; import { - type LoadedFingerprintPackage, - loadFingerprintPackage, + type LoadedGhostPackage, + loadGhostPackage, } from "../scan/fingerprint-package.js"; import { resolveGitRoot } from "../scan/package-paths.js"; import { defaultArchiveName, writeDirectoryTarball } from "../scan/tarball.js"; @@ -47,7 +44,7 @@ export function registerExportCommand(cli: CAC): void { cli .command( "export", - "Package the fingerprint as a portable brand artifact with a locator audit.", + "Package the ghost package as a portable brand artifact with a locator audit.", ) .option("--out ", "Write the archive to this path") .option("--no-checks", "Exclude the checks/ directory from the archive") @@ -57,7 +54,7 @@ export function registerExportCommand(cli: CAC): void { ) .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: markdown or json", { default: "markdown", @@ -70,19 +67,16 @@ export function registerExportCommand(cli: CAC): void { return; } - const paths = resolveFingerprintPackage(opts.package, process.cwd()); - const report = await lintFingerprintPackage( - opts.package, - process.cwd(), - ); + const paths = resolveGhostPackage(opts.package, process.cwd()); + const report = await lintGhostPackage(opts.package, process.cwd()); if (report.errors > 0) { console.error( - "Error: fingerprint package has validation errors. Run `ghost validate` and fix them before exporting.", + "Error: ghost package has validation errors. Run `ghost validate` and fix them before exporting.", ); process.exit(2); return; } - const loaded = await loadFingerprintPackage(paths); + const loaded = await loadGhostPackage(paths); const archive = typeof opts.out === "string" @@ -148,7 +142,7 @@ export function registerExportCommand(cli: CAC): void { } function buildExportAudit( - loaded: LoadedFingerprintPackage, + loaded: LoadedGhostPackage, options: { repoRoot: string; packageDir: string }, ): ExportAudit { const travels: ExportAuditTravelingLocator[] = []; @@ -207,10 +201,10 @@ function formatExportMarkdown(fields: { audit: ExportAudit; }): string { const lines = [ - "# Ghost Export", + "# ghost Export", "", `Archive: \`${fields.archive}\``, - `Fingerprint: \`${fields.id}\``, + `Package: \`${fields.id}\``, "", "## Locator audit", "", diff --git a/packages/ghost/src/commands/fingerprint-commands.ts b/packages/ghost/src/commands/fingerprint-commands.ts index 20e12e34..74f23fd1 100644 --- a/packages/ghost/src/commands/fingerprint-commands.ts +++ b/packages/ghost/src/commands/fingerprint-commands.ts @@ -3,17 +3,17 @@ import { resolve } from "node:path"; import type { CAC } from "cac"; import { type LintReport, - lintFingerprintPackage, - resolveFingerprintPackage, -} from "../fingerprint.js"; + lintGhostPackage, + resolveGhostPackage, +} from "../package.js"; import { detectFileKind, lintDetectedFileKind } from "../scan/file-kind.js"; import { failFromError } from "./errors.js"; import { registerInitCommand } from "./init-command.js"; /** - * Register fingerprint package commands on the unified Ghost CLI. + * Register ghost package commands on the unified ghost CLI. * - * Verbs author and validate the root `.ghost/` fingerprint package: `init` + * Verbs author and validate the root `.ghost/` package: `init` * (scaffold) and `validate` (manifest shape, node validity, material locators, * check references, and glossary kind prefixes). */ @@ -22,11 +22,11 @@ export function registerFingerprintCommands(cli: CAC): void { cli .command( "validate [file]", - "Validate the Ghost fingerprint package — manifest shape, node validity, material locators, check references, and glossary kind prefixes. Defaults to .ghost.", + "Validate the ghost package: manifest shape, node validity, material locators, check references, and glossary kind prefixes. Defaults to .ghost.", ) .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: cli or json", { default: "cli" }) .action(async (path: string | undefined, opts) => { @@ -34,13 +34,10 @@ export function registerFingerprintCommands(cli: CAC): void { const exactPackage = typeof opts.package === "string" ? opts.package : undefined; const packagePath = exactPackage ?? path; - const target = resolveFingerprintPackage( - packagePath, - process.cwd(), - ).dir; + const target = resolveGhostPackage(packagePath, process.cwd()).dir; let report: LintReport; if (path === undefined || (await isDirectory(target))) { - report = await lintFingerprintPackage(packagePath, process.cwd()); + report = await lintGhostPackage(packagePath, process.cwd()); writeLintReport(report, opts.format); process.exit(report.errors > 0 ? 1 : 0); return; diff --git a/packages/ghost/src/commands/gather-command.ts b/packages/ghost/src/commands/gather-command.ts index cb8555ca..64ea04cc 100644 --- a/packages/ghost/src/commands/gather-command.ts +++ b/packages/ghost/src/commands/gather-command.ts @@ -5,10 +5,10 @@ import { type CatalogMenuEntry, parseGlossary, } from "#ghost-core"; -import { resolveFingerprintPackage } from "../fingerprint.js"; import { isMissingPathError } from "../internal/fs.js"; import { appendGhostEvent, resolveRunId } from "../observability-events.js"; -import { loadFingerprintPackage } from "../scan/fingerprint-package.js"; +import { resolveGhostPackage } from "../package.js"; +import { loadGhostPackage } from "../scan/fingerprint-package.js"; import { failFromError } from "./errors.js"; /** @@ -29,7 +29,7 @@ export function registerGatherCommand(cli: CAC): void { ) .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: markdown or json", { default: "markdown", @@ -47,8 +47,8 @@ export function registerGatherCommand(cli: CAC): void { } const ask = normalizeAsk(askParts); - const paths = resolveFingerprintPackage(opts.package, process.cwd()); - const loaded = await loadFingerprintPackage(paths); + const paths = resolveGhostPackage(opts.package, process.cwd()); + const loaded = await loadGhostPackage(paths); const coverId = loaded.manifest.cover; const coverNode = coverId ? loaded.catalog.nodes.get(coverId) @@ -65,7 +65,7 @@ export function registerGatherCommand(cli: CAC): void { menu: menu.map((entry) => entry.id), }); - // Ghost does no selection. It emits the complete catalog; the agent + // ghost does no selection. It emits the complete catalog; the agent // reads the ask against it and pulls the nodes whose described // conditions apply. if (opts.format === "json") { @@ -75,7 +75,7 @@ export function registerGatherCommand(cli: CAC): void { kind: "menu", ...(ask ? { ask } : {}), source: { - artifact: "Ghost brand fingerprint", + artifact: "ghost package", list: "Available guidance", }, contract: gatherContract(ask), @@ -92,7 +92,7 @@ export function registerGatherCommand(cli: CAC): void { next: { command: "ghost pull […]" }, silence: { ifNoneApply: - "Name the fingerprint's silence, follow the cover silence posture when present, and do not invent Ghost-backed guidance.", + "Name the package's silence, follow the cover silence posture when present, and do not invent ghost-backed guidance.", }, coverage: menuCoverage(menu), ...(kinds.length > 0 ? { kinds } : {}), @@ -187,7 +187,7 @@ function gatherContract(ask: string | undefined): GatherContract { selection: { basis: "applicability", instruction: ask - ? "Pull every node whose description indicates its stated situation applies and whose truth, material, structure, or refusal governs the work; skip inapplicable nodes." + ? "Pull every node whose description indicates its stated situation applies and whose guidance, material, structure, or refusal governs the work; skip inapplicable nodes." : "Bare gather is catalog inspection. Do not treat the menu as task grounding until an ask is supplied; when grounding a task, pull every applicable node and skip inapplicable nodes.", topicOverlapAloneIsApplicability: false, addForCompleteness: false, @@ -246,7 +246,7 @@ function formatMenuMarkdown( kinds: MenuKind[], options: FormatMenuOptions = {}, ): string { - const lines: string[] = ["# Ghost brand fingerprint", ""]; + const lines: string[] = ["# ghost package", ""]; if (options.ask) lines.push(`Ask: ${options.ask}`, ""); if (options.cover) { lines.push( @@ -263,16 +263,16 @@ function formatMenuMarkdown( lines.push("## Available guidance", "", menuCoverageLine(menu), ""); if (options.ask) { lines.push( - "Complete, unfiltered, unranked list from the fingerprint. Ghost has not selected nodes for this ask.", - "Pull every node whose description indicates its stated situation applies and whose truth, material, structure, or refusal governs the work. Skip inapplicable nodes. Topic overlap alone is not applicability. Do not add nodes for completeness or omit applicable nodes to meet a count.", + "Complete, unfiltered, unranked list from the ghost package. ghost has not selected nodes for this ask.", + "Pull every node whose description indicates its stated situation applies and whose guidance, material, structure, or refusal governs the work. Skip inapplicable nodes. Topic overlap alone is not applicability. Do not add nodes for completeness or omit applicable nodes to meet a count.", "Next: `ghost pull […]`.", - "If nothing applies, name the fingerprint's silence, follow the cover silence posture, and do not invent Ghost-backed guidance.", + "If nothing applies, name the package's silence, follow the cover silence posture, and do not invent ghost-backed guidance.", "", ); } else { lines.push( - "Complete, unfiltered, unranked list from the fingerprint. Bare gather is catalog inspection; Ghost has not grounded a task or selected nodes.", - "When grounding an ask, pull every applicable node with `ghost pull […]`. Skip inapplicable nodes and do not invent Ghost-backed guidance when the fingerprint is silent.", + "Complete, unfiltered, unranked list from the ghost package. Bare gather is catalog inspection; ghost has not grounded a task or selected nodes.", + "When grounding an ask, pull every applicable node with `ghost pull […]`. Skip inapplicable nodes and do not invent ghost-backed guidance when the ghost package is silent.", "", ); } diff --git a/packages/ghost/src/commands/init-command.ts b/packages/ghost/src/commands/init-command.ts index 85a70448..75646f15 100644 --- a/packages/ghost/src/commands/init-command.ts +++ b/packages/ghost/src/commands/init-command.ts @@ -1,17 +1,14 @@ import type { CAC } from "cac"; import { UsageError } from "#ghost-core"; -import { initFingerprintPackage } from "../fingerprint.js"; +import { initGhostPackage } from "../package.js"; import { addChecksDir } from "../scan/check-scaffold.js"; import { getInitBody } from "../scan/templates.js"; import { failFromError } from "./errors.js"; export function registerInitCommand(cli: CAC): void { cli - .command("init", "Create a root .ghost node fingerprint package") - .option( - "--package ", - "Exact fingerprint package directory to initialize", - ) + .command("init", "Create a root .ghost package of nodes") + .option("--package ", "Exact ghost package directory to initialize") .option( "--template ", "Init template to scaffold (default: skeleton)", @@ -24,7 +21,7 @@ export function registerInitCommand(cli: CAC): void { "--with ", "Comma-separated capabilities to add after scaffolding (e.g. checks)", ) - .option("--force", "Overwrite existing Ghost fingerprint files") + .option("--force", "Overwrite existing ghost package files") .option("--format ", "Output format: cli or json", { default: "cli" }) .action(async (opts) => { try { @@ -53,17 +50,13 @@ export function registerInitCommand(cli: CAC): void { ); } - const result = await initFingerprintPackage( - exactPackage, - process.cwd(), - { - ...(typeof opts.template === "string" - ? { template: opts.template } - : {}), - ...(typeof opts.body === "string" ? { body: opts.body } : {}), - force: Boolean(opts.force), - }, - ); + const result = await initGhostPackage(exactPackage, process.cwd(), { + ...(typeof opts.template === "string" + ? { template: opts.template } + : {}), + ...(typeof opts.body === "string" ? { body: opts.body } : {}), + force: Boolean(opts.force), + }); const addedChecks = withIds.includes("checks") ? await addChecksDir(result.paths.packageDir) : undefined; @@ -89,7 +82,7 @@ export function registerInitCommand(cli: CAC): void { ); } else { process.stdout.write( - `Initialized Ghost fingerprint package: ${result.paths.dir}\n`, + `Initialized ghost package: ${result.paths.dir}\n`, ); for (const relativePath of result.written) { process.stdout.write(` ${relativePath}\n`); diff --git a/packages/ghost/src/commands/pull-command.ts b/packages/ghost/src/commands/pull-command.ts index 0024b7e2..83747dbd 100644 --- a/packages/ghost/src/commands/pull-command.ts +++ b/packages/ghost/src/commands/pull-command.ts @@ -10,17 +10,17 @@ import { type TransportedMaterial, transportMaterials, } from "#ghost-core"; -import { resolveFingerprintPackage } from "../fingerprint.js"; import { appendGhostEvent, type PullMiss, resolveRunId, } from "../observability-events.js"; +import { resolveGhostPackage } from "../package.js"; import { GHOST_EVENTS_FILENAME, GHOST_MATERIALS_DIR, } from "../scan/constants.js"; -import { loadFingerprintPackage } from "../scan/fingerprint-package.js"; +import { loadGhostPackage } from "../scan/fingerprint-package.js"; import { resolveGitRoot } from "../scan/package-paths.js"; import { failFromError } from "./errors.js"; @@ -43,7 +43,7 @@ export function registerPullCommand(cli: CAC): void { ) .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: markdown or json", { default: "markdown", @@ -73,8 +73,8 @@ export function registerPullCommand(cli: CAC): void { return; } - const paths = resolveFingerprintPackage(opts.package, process.cwd()); - const loaded = await loadFingerprintPackage(paths); + const paths = resolveGhostPackage(opts.package, process.cwd()); + const loaded = await loadGhostPackage(paths); const catalog = loaded.catalog; const requested = [...new Set(ids)]; diff --git a/packages/ghost/src/commands/pulse-command.ts b/packages/ghost/src/commands/pulse-command.ts index 2a1ba6d1..3b16c431 100644 --- a/packages/ghost/src/commands/pulse-command.ts +++ b/packages/ghost/src/commands/pulse-command.ts @@ -1,12 +1,12 @@ import type { CAC } from "cac"; import { buildCatalogMenu, type CatalogMenuEntry } from "#ghost-core"; -import { resolveFingerprintPackage } from "../fingerprint.js"; import { type GhostObservabilityEvent, type PullMiss, readGhostEvents, } from "../observability-events.js"; -import { loadFingerprintPackage } from "../scan/fingerprint-package.js"; +import { resolveGhostPackage } from "../package.js"; +import { loadGhostPackage } from "../scan/fingerprint-package.js"; import { failFromError } from "./errors.js"; export function registerPulseCommand(cli: CAC): void { @@ -14,7 +14,7 @@ export function registerPulseCommand(cli: CAC): void { .command("pulse", "Summarize local gather/pull events from .ghost/.events.") .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--format ", "Output format: markdown or json", { default: "markdown", @@ -27,8 +27,8 @@ export function registerPulseCommand(cli: CAC): void { return; } - const paths = resolveFingerprintPackage(opts.package, process.cwd()); - const loaded = await loadFingerprintPackage(paths); + const paths = resolveGhostPackage(opts.package, process.cwd()); + const loaded = await loadGhostPackage(paths); const menu = buildCatalogMenu(loaded.catalog); const events = await readGhostEvents(paths.packageDir); const report = buildPulseReport(events, menu); @@ -256,7 +256,7 @@ function recordMiss( function formatPulseMarkdown(report: PulseReport): string { const lines: string[] = [ - "# Ghost Pulse", + "# ghost Pulse", "", `- Events: ${report.events}`, `- Gathers: ${report.gathers}`, diff --git a/packages/ghost/src/commands/review-command.ts b/packages/ghost/src/commands/review-command.ts index 730e027c..a2a0cdc5 100644 --- a/packages/ghost/src/commands/review-command.ts +++ b/packages/ghost/src/commands/review-command.ts @@ -3,12 +3,12 @@ import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import { promisify } from "node:util"; import type { CAC } from "cac"; -import { resolveFingerprintPackage } from "../fingerprint.js"; +import { resolveGhostPackage } from "../package.js"; import { buildReviewPacket, formatReviewPacket, } from "../review/review-packet.js"; -import { loadFingerprintPackage } from "../scan/fingerprint-package.js"; +import { loadGhostPackage } from "../scan/fingerprint-package.js"; import { failFromError } from "./errors.js"; const execFileAsync = promisify(execFile); @@ -21,7 +21,7 @@ export function registerReviewCommand(cli: CAC): void { ) .option( "--package ", - "Use this fingerprint package directory (default: ./.ghost)", + "Use this ghost package directory (default: ./.ghost)", ) .option("--base ", "Git ref to diff against (default: HEAD)") .option("--diff ", "Read diff from a file, or '-' for stdin") @@ -39,9 +39,9 @@ export function registerReviewCommand(cli: CAC): void { return; } - const paths = resolveFingerprintPackage(opts.package, process.cwd()); - const fingerprint = await loadFingerprintPackage(paths); - if (!fingerprint.hasChecksDir) { + const paths = resolveGhostPackage(opts.package, process.cwd()); + const ghostPackage = await loadGhostPackage(paths); + if (!ghostPackage.hasChecksDir) { console.error( "No checks directory. Run `ghost checks init` to add review assertions.", ); @@ -52,7 +52,7 @@ export function registerReviewCommand(cli: CAC): void { base: opts.base, diff: opts.diff, }); - const packet = await buildReviewPacket(fingerprint, diffText, { + const packet = await buildReviewPacket(ghostPackage, diffText, { packageDir: paths.packageDir, runProbes: opts.probes !== false, cwd: process.cwd(), diff --git a/packages/ghost/src/commands/skill-command.ts b/packages/ghost/src/commands/skill-command.ts index 30b2858b..1108e015 100644 --- a/packages/ghost/src/commands/skill-command.ts +++ b/packages/ghost/src/commands/skill-command.ts @@ -23,7 +23,7 @@ type SupportedAgent = (typeof SUPPORTED_AGENTS)[number]; export function registerSkillCommand(cli: CAC): void { cli - .command("skill ", "Install the unified Ghost skill bundle.") + .command("skill ", "Install the unified ghost skill bundle.") .option( "--dest ", "Install destination (default: detected agent skills directory + /ghost)", @@ -32,7 +32,7 @@ export function registerSkillCommand(cli: CAC): void { "--agent ", "Agent destination to use when --dest is omitted: claude, cursor, codex, opencode, goose", ) - .option("--force", "Overwrite an existing installed Ghost skill") + .option("--force", "Overwrite an existing installed ghost skill") .action(async (action: string, opts) => { try { if (action !== "install") { diff --git a/packages/ghost/src/fingerprint.ts b/packages/ghost/src/fingerprint.ts index 5a7ed793..eae3f64b 100644 --- a/packages/ghost/src/fingerprint.ts +++ b/packages/ghost/src/fingerprint.ts @@ -1,9 +1,12 @@ +/** @deprecated Import from `@design-intelligence/ghost/package`. */ export { FINGERPRINT_MANIFEST_FILENAME, FINGERPRINT_PACKAGE_DIR, } from "./scan/constants.js"; export type { FingerprintPackagePaths, + InitFingerprintPackageOptions, + InitFingerprintPackageResult, LoadedFingerprintPackage, } from "./scan/fingerprint-package.js"; export { diff --git a/packages/ghost/src/ghost-core/catalog/menu.ts b/packages/ghost/src/ghost-core/catalog/menu.ts index 46c70d9d..7df450ed 100644 --- a/packages/ghost/src/ghost-core/catalog/menu.ts +++ b/packages/ghost/src/ghost-core/catalog/menu.ts @@ -4,7 +4,7 @@ import type { GhostCatalog } from "./types.js"; * One entry in the gather menu: a node presented as `id` + `kind` + * `description`, the retrieval payload the agent selects against. The agent * matches a natural-language ask against these and pulls applicable nodes; - * Ghost does no NLP and no selection. + * ghost does no NLP and no selection. */ export interface CatalogMenuEntry { id: string; diff --git a/packages/ghost/src/ghost-core/check/index.ts b/packages/ghost/src/ghost-core/check/index.ts index c14b29f5..c1a421ef 100644 --- a/packages/ghost/src/ghost-core/check/index.ts +++ b/packages/ghost/src/ghost-core/check/index.ts @@ -1,6 +1,6 @@ /** * Public surface for `ghost.check/v1` — markdown + frontmatter checks an agent - * evaluates (Ghost never runs them). Every check is offered to the reviewer; + * evaluates (ghost never runs them). Every check is offered to the reviewer; * the agent judges relevance against the diff and the grounded prose. A check's * optional `source:` names the fingerprint prose it enforces. */ diff --git a/packages/ghost/src/ghost-core/check/lint.ts b/packages/ghost/src/ghost-core/check/lint.ts index 7ced7ca9..21f16ab3 100644 --- a/packages/ghost/src/ghost-core/check/lint.ts +++ b/packages/ghost/src/ghost-core/check/lint.ts @@ -7,9 +7,9 @@ import { } from "./types.js"; /** - * Lint a Ghost check markdown file (`ghost.check/v1`): required frontmatter + * Lint a ghost check markdown file (`ghost.check/v1`): required frontmatter * (`name`, `description`, `severity`), an optional `source:` provenance pointer, - * and a non-empty body. Ghost never executes the check — this only validates + * and a non-empty body. ghost never executes the check — this only validates * that it is well-formed. */ export function lintGhostCheck(raw: string): GhostCheckLintReport { diff --git a/packages/ghost/src/ghost-core/check/load.ts b/packages/ghost/src/ghost-core/check/load.ts index 13d3189e..231205b9 100644 --- a/packages/ghost/src/ghost-core/check/load.ts +++ b/packages/ghost/src/ghost-core/check/load.ts @@ -5,20 +5,20 @@ import type { } from "./types.js"; /** - * Parse a well-formed Ghost check into a typed document. Assumes the input has + * Parse a well-formed ghost check into a typed document. Assumes the input has * already passed `lintGhostCheck` (throws on missing required frontmatter). */ export function loadGhostCheck(raw: string): GhostCheckDocument { const { frontmatter, body } = parseCheckMarkdown(raw); if (frontmatter === null) { - throw new Error("Ghost check is missing a YAML frontmatter block."); + throw new Error("ghost check is missing a YAML frontmatter block."); } const name = frontmatter.name; const description = frontmatter.description; const severity = frontmatter.severity; if (typeof name !== "string" || typeof description !== "string") { - throw new Error("Ghost check frontmatter is missing name or description."); + throw new Error("ghost check frontmatter is missing name or description."); } const tools = Array.isArray(frontmatter.tools) diff --git a/packages/ghost/src/ghost-core/check/types.ts b/packages/ghost/src/ghost-core/check/types.ts index fbae800a..a1af0194 100644 --- a/packages/ghost/src/ghost-core/check/types.ts +++ b/packages/ghost/src/ghost-core/check/types.ts @@ -6,9 +6,9 @@ export type GhostCheckMarkdownSeverity = (typeof GHOST_CHECK_SEVERITIES)[number]; /** - * A Ghost check: markdown + frontmatter, evaluated by an agent — never run by - * Ghost. Shape-compatible with the established `.agents/checks` format, plus the - * Ghost addition `source:` (the fingerprint prose the check enforces). Every + * A ghost check: markdown + frontmatter, evaluated by an agent — never run by + * ghost. Shape-compatible with the established `.agents/checks` format, plus the + * ghost addition `source:` (the fingerprint prose the check enforces). Every * check is offered to the reviewer; the agent judges relevance. */ export interface GhostCheckFrontmatter { diff --git a/packages/ghost/src/ghost-core/index.ts b/packages/ghost/src/ghost-core/index.ts index 86303b71..72c88490 100644 --- a/packages/ghost/src/ghost-core/index.ts +++ b/packages/ghost/src/ghost-core/index.ts @@ -83,11 +83,19 @@ export { serializeNode, stripSkeletonSections, } from "./node/index.js"; -// --- Fingerprint package manifest (ghost.fingerprint-package/v1) --- -export type { GhostFingerprintPackageManifest } from "./package-manifest.js"; +// --- ghost package manifest --- +export type { + /** @deprecated Use `GhostPackageManifest`. */ + GhostFingerprintPackageManifest, + GhostPackageManifest, +} from "./package-manifest.js"; export { + /** @deprecated Use `GHOST_PACKAGE_SCHEMA`. */ GHOST_FINGERPRINT_PACKAGE_SCHEMA, + GHOST_PACKAGE_SCHEMA, + /** @deprecated Use `GhostPackageManifestSchema`. */ GhostFingerprintPackageManifestSchema, + GhostPackageManifestSchema, } from "./package-manifest.js"; // --- Skill bundle loader --- export type { SkillBundleFile } from "./skill-bundle-loader.js"; diff --git a/packages/ghost/src/ghost-core/markdown.ts b/packages/ghost/src/ghost-core/markdown.ts index 6a25fb24..99631ef1 100644 --- a/packages/ghost/src/ghost-core/markdown.ts +++ b/packages/ghost/src/ghost-core/markdown.ts @@ -11,7 +11,7 @@ export interface ParsedMarkdown { * `---\n\n---\n`. Returns `frontmatter: null` when there is no * leading frontmatter block (callers report it as an error). * - * Shared by every Ghost markdown+frontmatter artifact (checks, nodes): one + * Shared by every ghost markdown+frontmatter artifact (checks, nodes): one * envelope, one splitter. */ export function splitMarkdownFrontmatter(raw: string): ParsedMarkdown { diff --git a/packages/ghost/src/ghost-core/node/schema.ts b/packages/ghost/src/ghost-core/node/schema.ts index d773d997..ab60d06b 100644 --- a/packages/ghost/src/ghost-core/node/schema.ts +++ b/packages/ghost/src/ghost-core/node/schema.ts @@ -48,7 +48,7 @@ export const GhostNodeFrontmatterSchema = z .optional(), }) // Passthrough, not strict: authors may add free-form descriptive keys - // (e.g. `audience`, `stage`) that describe what the node is. Ghost does not + // (e.g. `audience`, `stage`) that describe what the node is. ghost does not // gate on them — they ride along as part of the node's descriptive surface. .passthrough(); diff --git a/packages/ghost/src/ghost-core/node/types.ts b/packages/ghost/src/ghost-core/node/types.ts index 3f4ff75d..262c143a 100644 --- a/packages/ghost/src/ghost-core/node/types.ts +++ b/packages/ghost/src/ghost-core/node/types.ts @@ -17,7 +17,7 @@ export interface GhostNodeFrontmatter { */ description?: string; /** - * Optional locators for the concrete materials this truth is about: repo-relative + * Optional locators for the concrete materials this guidance is about: repo-relative * paths/globs and absolute https URLs. Guidance stays in prose; this list is * only where the agent or review harness can find the material. */ diff --git a/packages/ghost/src/ghost-core/package-manifest.ts b/packages/ghost/src/ghost-core/package-manifest.ts index eb0c2f65..fca73e7e 100644 --- a/packages/ghost/src/ghost-core/package-manifest.ts +++ b/packages/ghost/src/ghost-core/package-manifest.ts @@ -1,5 +1,9 @@ import { z } from "zod"; +/** Schema emitted for newly initialized `.ghost/` packages. */ +export const GHOST_PACKAGE_SCHEMA = "ghost.package/v1" as const; + +/** @deprecated Accepted for packages created before `ghost.package/v1`. */ export const GHOST_FINGERPRINT_PACKAGE_SCHEMA = "ghost.fingerprint-package/v1" as const; @@ -16,27 +20,33 @@ const NodeIdSchema = z .min(1) .regex(/^[a-z0-9][a-z0-9./_-]*$/, "cover must be a node id"); -/** `manifest.yml` — anchors a `.ghost/` package. */ -export const GhostFingerprintPackageManifestSchema = z - .object({ - schema: z.literal(GHOST_FINGERPRINT_PACKAGE_SCHEMA), - id: SlugIdSchema, - /** - * Optional id of the cover node: the one node gather inlines above the menu - * on every invocation. Guaranteed presence for content selection cannot - * retrieve — essence, temperature, brand-level refusals. - */ - cover: NodeIdSchema.optional(), - }) - .strict(); - -export interface GhostFingerprintPackageManifest { - schema: typeof GHOST_FINGERPRINT_PACKAGE_SCHEMA; - id: string; +const ManifestFields = { + id: SlugIdSchema, /** - * Optional id of the cover node: the one node gather inlines above the menu on - * every invocation. Guaranteed presence for content selection cannot retrieve - * — essence, temperature, brand-level refusals. + * Optional id of the cover node: the one node gather inlines above the menu + * on every invocation. Guaranteed presence for content selection cannot + * retrieve: essence, temperature, and brand-level refusals. */ - cover?: string; -} + cover: NodeIdSchema.optional(), +}; + +/** `manifest.yml` schema for a `.ghost/` package, including the legacy id. */ +export const GhostPackageManifestSchema = z.discriminatedUnion("schema", [ + z + .object({ schema: z.literal(GHOST_PACKAGE_SCHEMA), ...ManifestFields }) + .strict(), + z + .object({ + schema: z.literal(GHOST_FINGERPRINT_PACKAGE_SCHEMA), + ...ManifestFields, + }) + .strict(), +]); + +export type GhostPackageManifest = z.infer; + +/** @deprecated Use `GhostPackageManifestSchema`. */ +export const GhostFingerprintPackageManifestSchema = GhostPackageManifestSchema; + +/** @deprecated Use `GhostPackageManifest`. */ +export type GhostFingerprintPackageManifest = GhostPackageManifest; diff --git a/packages/ghost/src/ghost-core/skill-bundle-loader.ts b/packages/ghost/src/ghost-core/skill-bundle-loader.ts index 33745aa8..cdcfa27d 100644 --- a/packages/ghost/src/ghost-core/skill-bundle-loader.ts +++ b/packages/ghost/src/ghost-core/skill-bundle-loader.ts @@ -1,7 +1,7 @@ /** * Generic loader for an agentskills.io-compatible skill bundle. * - * Each tool in the Ghost monorepo (`ghost`, `ghost`, …) ships + * Each tool in the ghost monorepo (`ghost`, `ghost`, …) ships * its own skill bundle as real markdown under `src/skill-bundle/` and copies * it verbatim to `dist/skill-bundle/` at build time. This loader walks any * given root directory and returns a flat, deterministically ordered list of diff --git a/packages/ghost/src/index.ts b/packages/ghost/src/index.ts index acc8f1c3..2278d9c6 100644 --- a/packages/ghost/src/index.ts +++ b/packages/ghost/src/index.ts @@ -1,4 +1,6 @@ +/** @deprecated Use `ghostPackage` or `@design-intelligence/ghost/package`. */ export * as fingerprint from "./fingerprint.js"; export * as core from "./ghost-core/index.js"; -/** @deprecated Use `fingerprint` or `@design-intelligence/ghost/fingerprint`. */ +export * as ghostPackage from "./package.js"; +/** @deprecated Use `ghostPackage` or `@design-intelligence/ghost/package`. */ export * as scan from "./scan/index.js"; diff --git a/packages/ghost/src/init-payloads/median/cliche.median.md b/packages/ghost/src/init-payloads/median/cliche.median.md index a0a5cefb..e51be423 100644 --- a/packages/ghost/src/init-payloads/median/cliche.median.md +++ b/packages/ghost/src/init-payloads/median/cliche.median.md @@ -1,5 +1,5 @@ --- -description: "The model's median defaults this fingerprint refuses — gather for any greenfield visual surface or first-draft copy. Each rule is reject→replace; delete lines your brand legitimately violates." +description: "The model's median defaults this package refuses — gather for any greenfield visual surface or first-draft copy. Each rule is reject→replace; delete lines your brand legitimately violates." --- This is the model's median, not your brand. Each rule is reject→replace. @@ -17,16 +17,16 @@ verified at review, never steered in prose. ### Hover-lift Reject hover-lift (`translateY` + growing shadow) as the default interaction -→ confirm with color and background change at the fingerprint's fast +→ confirm with color and background change at the package's fast duration. (measured: 341) ### Indigo accent Reject the indigo/blue/purple default accent (`#4f46e5`, `#2563eb`, -`#8b5cf6` family; purple/violet hue 260–310) → the fingerprint's declared +`#8b5cf6` family; purple/violet hue 260–310) → the package's declared palette; absent one, monochrome plus a single functional accent. ### Dark theme -Reject unprompted dark theme → the fingerprint's declared surface. +Reject unprompted dark theme → the package's declared surface. Dark-to-look-cool and light-to-be-safe are the same retreat from a decision. (measured: 271) @@ -47,7 +47,7 @@ the ground. ### Glassmorphism Reject glassmorphism (decorative backdrop blur) → flat surfaces with real -borders and the fingerprint's elevation tiers. +borders and the package's elevation tiers. ### Chat bubbles Reject chat bubbles with initials-circle avatars for assistant turns → @@ -73,7 +73,7 @@ prominent metric showing real user data is fine. ### Every button primary Reject every button a primary button → one primary per view; the rest step -down the fingerprint's emphasis ladder. +down the package's emphasis ladder. ### Eyebrow kicker Reject an eyebrow kicker on every section → at most one, where the register diff --git a/packages/ghost/src/init-payloads/median/median-tells.md b/packages/ghost/src/init-payloads/median/median-tells.md index 3ad1d3d6..2762827b 100644 --- a/packages/ghost/src/init-payloads/median/median-tells.md +++ b/packages/ghost/src/init-payloads/median/median-tells.md @@ -25,7 +25,7 @@ Pruning a rule from `cliche.median` orphans its paired reference here — Flag `transform` with `translateY` inside a `:hover` rule on cards, buttons, or list items, especially paired with a shadow increase. Hover -confirmation in this fingerprint is color and background change, not lift. +confirmation in this package is color and background change, not lift. (`cliche.median > Hover-lift`) Flag accent values in the indigo/blue/purple default family (`#4f46e5`, diff --git a/packages/ghost/src/init-payloads/skeleton/brand.md b/packages/ghost/src/init-payloads/skeleton/brand.md index 3d117868..a720b27d 100644 --- a/packages/ghost/src/init-payloads/skeleton/brand.md +++ b/packages/ghost/src/init-payloads/skeleton/brand.md @@ -2,7 +2,7 @@ description: "The brand on one page; what this brand is, how it feels, and what only it refuses." --- -This cover is unwritten. Ghost gather always places this page in an agent's +This cover is unwritten. ghost gather always places this page in an agent's context; that is delivery status, not a claim that every sentence applies to every task. Use it for what cannot be retrieved by task: what this brand is about, in one paragraph, in the brand's own voice. The temperature its words and diff --git a/packages/ghost/src/init-payloads/skeleton/glossary.md b/packages/ghost/src/init-payloads/skeleton/glossary.md index fcbfe1ed..b578fbed 100644 --- a/packages/ghost/src/init-payloads/skeleton/glossary.md +++ b/packages/ghost/src/init-payloads/skeleton/glossary.md @@ -34,6 +34,6 @@ checks at review. The cover node (`brand.md`, declared in the manifest) is the one page always in context: what the brand is, its temperature, and the refusals only it makes. The ghost skill's `adapting-a-starter` recipe is the procedure for -growing this skeleton into a real fingerprint: write the cover, answer each +growing this skeleton into a real ghost package: write the cover, answer each chapter's open question, prune the cliches your chapters absorb, then add materials and refs. diff --git a/packages/ghost/src/package.ts b/packages/ghost/src/package.ts new file mode 100644 index 00000000..a7b44e0c --- /dev/null +++ b/packages/ghost/src/package.ts @@ -0,0 +1,22 @@ +export type { GhostPackageManifest } from "./ghost-core/package-manifest.js"; +export { + GHOST_PACKAGE_SCHEMA, + GhostPackageManifestSchema, +} from "./ghost-core/package-manifest.js"; +export { + GHOST_MANIFEST_FILENAME, + GHOST_PACKAGE_DIR, +} from "./scan/constants.js"; +export type { + GhostPackagePaths, + InitGhostPackageOptions, + InitGhostPackageResult, + LoadedGhostPackage, +} from "./scan/fingerprint-package.js"; +export { + initGhostPackage, + lintGhostPackage, + loadGhostPackage, + resolveGhostPackage, +} from "./scan/fingerprint-package.js"; +export type { LintIssue, LintReport, LintSeverity } from "./scan/lint.js"; diff --git a/packages/ghost/src/review/resolve.ts b/packages/ghost/src/review/resolve.ts index 294d6273..de18d346 100644 --- a/packages/ghost/src/review/resolve.ts +++ b/packages/ghost/src/review/resolve.ts @@ -110,7 +110,7 @@ export function resolveReview( gaps.push({ kind: "unmatched-file", detail: - "changed files match no node `materials` locators — no fingerprint material claims them", + "changed files match no node `materials` locators — no ghost package guidance claims them", files: unmatched, }); } diff --git a/packages/ghost/src/review/review-packet.ts b/packages/ghost/src/review/review-packet.ts index 0f5ba4c8..6e42e0d7 100644 --- a/packages/ghost/src/review/review-packet.ts +++ b/packages/ghost/src/review/review-packet.ts @@ -1,7 +1,7 @@ import { join } from "node:path"; import type { GhostCatalogNode } from "#ghost-core"; import { GHOST_MATERIALS_DIR } from "../scan/constants.js"; -import type { LoadedFingerprintPackage } from "../scan/fingerprint-package.js"; +import type { LoadedGhostPackage } from "../scan/fingerprint-package.js"; import { resolveGitRoot } from "../scan/package-paths.js"; import { type BaselineProse, resolveBaseline } from "./baseline.js"; import { type ProbeEvidence, runProbe } from "./probes.js"; @@ -31,6 +31,8 @@ export interface PacketCheck { } export interface ReviewPacket { + packageId: string; + /** @deprecated Use `packageId`. */ fingerprintId: string; touchedFiles: string[]; materialNodes: PacketMaterialNode[]; @@ -40,7 +42,7 @@ export interface ReviewPacket { } export interface BuildReviewPacketOptions { - /** Absolute path of the fingerprint package directory (default: cwd/.ghost). */ + /** Absolute path of the ghost package directory (default: cwd/.ghost). */ packageDir?: string; runProbes?: boolean; cwd?: string; @@ -48,14 +50,14 @@ export interface BuildReviewPacketOptions { } export async function buildReviewPacket( - fingerprint: LoadedFingerprintPackage, + ghostPackage: LoadedGhostPackage, diffText: string, options: BuildReviewPacketOptions = {}, ): Promise { const cwd = options.cwd ?? process.cwd(); const resolution = resolveReview( - fingerprint.catalog, - fingerprint.checks, + ghostPackage.catalog, + ghostPackage.checks, diffText, { repoRoot: await resolveGitRoot(cwd), @@ -65,12 +67,12 @@ export async function buildReviewPacket( ); const materialNodes: PacketMaterialNode[] = resolution.materialNodes.map( - (matched) => materialNodeFromMatch(fingerprint, matched), + (matched) => materialNodeFromMatch(ghostPackage, matched), ); const checks: PacketCheck[] = await Promise.all( resolution.offeredChecks.map(async (offered) => { - const check = fingerprint.checks.get(offered.id); + const check = ghostPackage.checks.get(offered.id); const probeCommand = check?.doc.frontmatter.probe; const probe = options.runProbes !== false && probeCommand !== undefined @@ -87,7 +89,7 @@ export async function buildReviewPacket( prose: check?.doc.body.trim() ?? "", baseline: check?.references - .map((ref) => resolveBaseline(ref, fingerprint.catalog)) + .map((ref) => resolveBaseline(ref, ghostPackage.catalog)) .filter((ref): ref is BaselineProse => ref !== null) ?? [], ...(probe ? { probe } : {}), }; @@ -95,7 +97,8 @@ export async function buildReviewPacket( ); return { - fingerprintId: fingerprint.manifest.id, + packageId: ghostPackage.manifest.id, + fingerprintId: ghostPackage.manifest.id, touchedFiles: resolution.touchedFiles.map((file) => file.path), materialNodes, checks, @@ -105,10 +108,10 @@ export async function buildReviewPacket( } function materialNodeFromMatch( - fingerprint: LoadedFingerprintPackage, + ghostPackage: LoadedGhostPackage, matched: { id: string; locators: string[]; files: string[] }, ): PacketMaterialNode { - const node = fingerprint.catalog.nodes.get(matched.id) as GhostCatalogNode; + const node = ghostPackage.catalog.nodes.get(matched.id) as GhostCatalogNode; return { id: node.id, ...(node.kind !== undefined ? { kind: node.kind } : {}), @@ -124,14 +127,14 @@ function materialNodeFromMatch( export function formatReviewPacket(packet: ReviewPacket): string { const out: string[] = []; - out.push(`# Ghost review — fingerprint \`${packet.fingerprintId}\``, ""); + out.push(`# ghost review — package \`${packet.packageId}\``, ""); out.push( - "You are reviewing a diff against a Ghost fingerprint. The command has", + "You are reviewing a diff against ghost package guidance. The command has", "assembled the touched files, matched material-backed nodes, offered", "checks, and optional probe evidence. Probes are repo-owned shell commands", "with the same trust class as npm scripts; git review is the boundary.", "Weigh which checks apply. Do not invent obligations that are not grounded", - "in the fingerprint prose or check text.", + "in the ghost package guidance or check text.", "", ); @@ -181,7 +184,7 @@ export function formatReviewPacket(packet: ReviewPacket): string { } if (check.probe) { out.push( - "Probe evidence (shell run by Ghost; evidence only, not a pass/fail verdict):", + "Probe evidence (shell run by ghost; evidence only, not a pass/fail verdict):", `- command: \`${check.probe.command}\``, `- exit code: ${check.probe.exitCode ?? "unknown"}${check.probe.timedOut ? " (timed out)" : ""}`, "- stdout:", diff --git a/packages/ghost/src/scan/constants.ts b/packages/ghost/src/scan/constants.ts index b60d71b0..f45a2ea9 100644 --- a/packages/ghost/src/scan/constants.ts +++ b/packages/ghost/src/scan/constants.ts @@ -1,8 +1,14 @@ -/** Canonical directory for the Ghost fingerprint package. */ -export const FINGERPRINT_PACKAGE_DIR = ".ghost"; +/** Canonical directory for a ghost package. */ +export const GHOST_PACKAGE_DIR = ".ghost"; -/** Portable fingerprint package manifest filename. */ -export const FINGERPRINT_MANIFEST_FILENAME = "manifest.yml"; +/** ghost package manifest filename. */ +export const GHOST_MANIFEST_FILENAME = "manifest.yml"; + +/** @deprecated Use `GHOST_PACKAGE_DIR`. */ +export const FINGERPRINT_PACKAGE_DIR = GHOST_PACKAGE_DIR; + +/** @deprecated Use `GHOST_MANIFEST_FILENAME`. */ +export const FINGERPRINT_MANIFEST_FILENAME = GHOST_MANIFEST_FILENAME; /** Reserved package-root glossary filename. */ export const GHOST_GLOSSARY_FILENAME = "glossary.md"; diff --git a/packages/ghost/src/scan/file-kind.ts b/packages/ghost/src/scan/file-kind.ts index da8960f1..d8d951bf 100644 --- a/packages/ghost/src/scan/file-kind.ts +++ b/packages/ghost/src/scan/file-kind.ts @@ -1,6 +1,6 @@ import { parse as parseYaml } from "yaml"; import { - GhostFingerprintPackageManifestSchema, + GhostPackageManifestSchema, lintGhostCheck, lintGhostNode, } from "#ghost-core"; @@ -8,7 +8,7 @@ import { GHOST_MATERIALS_DIR } from "./constants.js"; import type { LintReport } from "./lint.js"; export type DetectedFileKind = - | "fingerprint-manifest" + | "package-manifest" | "check" | "material" | "node" @@ -25,10 +25,10 @@ export function detectFileKind(path: string, raw: string): DetectedFileKind { const lowerPath = path.toLowerCase(); const filename = lowerPath.split(/[\\/]/).pop() ?? lowerPath; if (filename === "manifest.yml") { - return "fingerprint-manifest"; + return "package-manifest"; } if (filename === "manifest.yaml") { - return "fingerprint-manifest"; + return "package-manifest"; } if (new RegExp(`(^|[\\\\/])${GHOST_MATERIALS_DIR}[\\\\/]`).test(lowerPath)) { return "material"; @@ -42,8 +42,8 @@ export function detectFileKind(path: string, raw: string): DetectedFileKind { if (filename.endsWith(".md")) { return "node"; } - if (/^\s*schema:\s*ghost\.fingerprint-package\/v1\b/m.test(raw)) { - return "fingerprint-manifest"; + if (/^\s*schema:\s*ghost\.(?:package|fingerprint-package)\/v1\b/m.test(raw)) { + return "package-manifest"; } return "unsupported"; } @@ -52,8 +52,8 @@ export function lintDetectedFileKind( kind: DetectedFileKind, raw: string, ): LintReport { - return kind === "fingerprint-manifest" - ? lintFingerprintManifestFile(raw) + return kind === "package-manifest" + ? lintPackageManifestFile(raw) : kind === "check" ? lintGhostCheck(raw) : kind === "material" @@ -67,17 +67,11 @@ function emptyLintReport(): LintReport { return { issues: [], errors: 0, warnings: 0, info: 0 }; } -function lintFingerprintManifestFile(raw: string): LintReport { +function lintPackageManifestFile(raw: string): LintReport { try { - return zodLintReport( - GhostFingerprintPackageManifestSchema.safeParse(parseYaml(raw)), - ); + return zodLintReport(GhostPackageManifestSchema.safeParse(parseYaml(raw))); } catch (err) { - return yamlErrorReport( - "fingerprint-manifest-not-yaml", - "manifest.yml", - err, - ); + return yamlErrorReport("package-manifest-not-yaml", "manifest.yml", err); } } @@ -110,7 +104,7 @@ function lintUnsupportedFile(): LintReport { severity: "error", rule: "unsupported-artifact", message: - "File is not a recognized Ghost artifact. Use manifest.yml, a checks/*.md check, or a *.md node.", + "File is not a recognized ghost artifact. Use manifest.yml, a checks/*.md check, or a *.md node.", }, ], errors: 1, diff --git a/packages/ghost/src/scan/fingerprint-package-lint.ts b/packages/ghost/src/scan/fingerprint-package-lint.ts index 8b9fe0d6..c4eea2ad 100644 --- a/packages/ghost/src/scan/fingerprint-package-lint.ts +++ b/packages/ghost/src/scan/fingerprint-package-lint.ts @@ -16,12 +16,12 @@ import { isMissingPathError } from "../internal/fs.js"; import type { LoadedCheck } from "./check-files.js"; import { GHOST_GLOSSARY_FILENAME, GHOST_MATERIALS_DIR } from "./constants.js"; import { - type FingerprintPackagePaths, - resolveFingerprintPackage, + type GhostPackagePaths, + resolveGhostPackage, } from "./fingerprint-package.js"; import { - lintFingerprintPackageManifest, - loadFingerprintPackage, + lintGhostPackageManifest, + loadGhostPackage, } from "./fingerprint-package-loader.js"; import type { LintIssue, LintReport } from "./lint.js"; import { resolveGitRoot } from "./package-paths.js"; @@ -31,11 +31,11 @@ import { resolveGitRoot } from "./package-paths.js"; * deterministic kind-prefix lint enabled by glossary.md. The catalog is flat; * loading collects malformed nodes so they can be surfaced as structured issues. */ -export async function lintFingerprintPackage( +export async function lintGhostPackage( dirArg: string | undefined, cwd = process.cwd(), ): Promise { - const paths = resolveFingerprintPackage(dirArg, cwd); + const paths = resolveGhostPackage(dirArg, cwd); const issues: LintIssue[] = []; const manifestRaw = await readRequired( @@ -49,7 +49,7 @@ export async function lintFingerprintPackage( const beforeManifestErrors = issues.filter( (issue) => issue.severity === "error", ).length; - lintFingerprintPackageManifest(manifestRaw, issues); + lintGhostPackageManifest(manifestRaw, issues); const manifestHasErrors = issues.filter((issue) => issue.severity === "error").length > beforeManifestErrors; @@ -57,7 +57,7 @@ export async function lintFingerprintPackage( // catalog pass: load + validate the node catalog. try { const { manifest, catalog, checks, invalid, invalidChecks } = - await loadFingerprintPackage(paths); + await loadGhostPackage(paths); // node pass: a node that failed its own schema was skipped while loading // the catalog; surface it here so a malformed node is loud, not silent. issues.push( @@ -130,7 +130,7 @@ function lintCover( severity: "warning", rule: "cover-undeclared", message: - 'no cover declared in manifest.yml — fingerprints steer better with one node guaranteed in context; add "cover: "', + 'no cover declared in manifest.yml — ghost packages steer better with one node guaranteed in context; add "cover: "', path: "manifest.yml.cover", }); return; @@ -159,7 +159,7 @@ function lintCover( } async function lintKindPrefixes( - paths: FingerprintPackagePaths, + paths: GhostPackagePaths, catalog: GhostCatalog, issues: LintIssue[], ): Promise { @@ -203,7 +203,7 @@ function lintNodeDescriptions( severity: "warning", rule: "node-description-missing", message: - "node has no `description`, so `gather` lists it as a bare id without applicability context; add a one-line description of what this truth governs, when it applies, and what it contributes", + "node has no `description`, so `gather` lists it as a bare id without applicability context; add a one-line description of what this guidance governs, when it applies, and what it contributes", path: `${node.id}.md`, }); } @@ -229,7 +229,7 @@ function lintSkeletonSections( } async function lintMaterialLocators( - paths: FingerprintPackagePaths, + paths: GhostPackagePaths, catalog: GhostCatalog, issues: LintIssue[], cwd: string, @@ -311,7 +311,7 @@ function lintCheckReferences( issues.push({ severity: "warning", rule: "check-reference-unresolved", - message: `check reference '${raw}' does not resolve to a fingerprint node — if you pruned this rule from the node, delete its paired flag in the check too`, + message: `check reference '${raw}' does not resolve to a ghost package node — if you pruned this rule from the node, delete its paired flag in the check too`, path: `checks/${check.id}.md.references`, }); continue; @@ -358,13 +358,16 @@ async function readRequired( issues.push({ severity: "error", rule: "package-artifact-missing", - message: `Fingerprint package is missing ${label}.`, + message: `ghost package is missing ${label}.`, path: label, }); return undefined; } } +/** @deprecated Use `lintGhostPackage`. */ +export const lintFingerprintPackage = lintGhostPackage; + function finalize(issues: LintIssue[]): LintReport { return { issues, diff --git a/packages/ghost/src/scan/fingerprint-package-loader.ts b/packages/ghost/src/scan/fingerprint-package-loader.ts index ddb1e6be..6a2f5eff 100644 --- a/packages/ghost/src/scan/fingerprint-package-loader.ts +++ b/packages/ghost/src/scan/fingerprint-package-loader.ts @@ -2,22 +2,22 @@ import { readFile } from "node:fs/promises"; import { parse as parseYaml } from "yaml"; import { assembleCatalog, - type GhostFingerprintPackageManifest, - GhostFingerprintPackageManifestSchema, + type GhostPackageManifest, + GhostPackageManifestSchema, UsageError, } from "#ghost-core"; import { isMissingPathError } from "../internal/fs.js"; import { loadCheckFiles } from "./check-files.js"; import type { - FingerprintPackagePaths, - LoadedFingerprintPackage, + GhostPackagePaths, + LoadedGhostPackage, } from "./fingerprint-package.js"; import type { LintIssue } from "./lint.js"; import { loadNodeFiles } from "./node-files.js"; -export async function loadFingerprintPackage( - paths: FingerprintPackagePaths, -): Promise { +export async function loadGhostPackage( + paths: GhostPackagePaths, +): Promise { let manifestRaw: string; try { manifestRaw = await readFile(paths.manifest, "utf-8"); @@ -25,7 +25,7 @@ export async function loadFingerprintPackage( // A missing package is a usage error (run `ghost init`), not a crash. if (isMissingPathError(err)) { throw new UsageError( - `No Ghost fingerprint package found at ${paths.packageDir} (expected manifest.yml). Run \`ghost init\` or pass --package .`, + `No ghost package found at ${paths.packageDir} (expected manifest.yml). Run \`ghost init\` or pass --package .`, ); } throw err; @@ -49,14 +49,13 @@ export async function loadFingerprintPackage( }; } -export function lintFingerprintPackageManifest( +export function lintGhostPackageManifest( raw: string, issues: LintIssue[], ): void { const manifest = parseYamlSafe(raw, "manifest.yml", issues); if (manifest === undefined) return; - const manifestResult = - GhostFingerprintPackageManifestSchema.safeParse(manifest); + const manifestResult = GhostPackageManifestSchema.safeParse(manifest); if (!manifestResult.success) { issues.push( ...manifestResult.error.issues.map((issue) => ({ @@ -72,14 +71,9 @@ export function lintFingerprintPackageManifest( } } -function parseManifest( - raw: string, - label: string, -): GhostFingerprintPackageManifest { +function parseManifest(raw: string, label: string): GhostPackageManifest { const parsed = parseYamlStrict(raw, label); - return GhostFingerprintPackageManifestSchema.parse( - parsed, - ) as GhostFingerprintPackageManifest; + return GhostPackageManifestSchema.parse(parsed) as GhostPackageManifest; } function parseYamlStrict(raw: string, label: string): unknown { @@ -94,6 +88,12 @@ function parseYamlStrict(raw: string, label: string): unknown { } } +/** @deprecated Use `loadGhostPackage`. */ +export const loadFingerprintPackage = loadGhostPackage; + +/** @deprecated Use `lintGhostPackageManifest`. */ +export const lintFingerprintPackageManifest = lintGhostPackageManifest; + function parseYamlSafe( raw: string, label: string, diff --git a/packages/ghost/src/scan/fingerprint-package.ts b/packages/ghost/src/scan/fingerprint-package.ts index 0031e670..10f2ff8d 100644 --- a/packages/ghost/src/scan/fingerprint-package.ts +++ b/packages/ghost/src/scan/fingerprint-package.ts @@ -2,7 +2,7 @@ import { access, mkdir, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { type GhostCatalog, - type GhostFingerprintPackageManifest, + type GhostPackageManifest, UsageError, } from "#ghost-core"; import { isExistingPathError, isMissingPathError } from "../internal/fs.js"; @@ -14,7 +14,8 @@ import { FINGERPRINT_MANIFEST_FILENAME, GHOST_GLOSSARY_FILENAME, } from "./constants.js"; -import { loadFingerprintPackage } from "./fingerprint-package-loader.js"; +import { lintGhostPackage } from "./fingerprint-package-lint.js"; +import { loadGhostPackage } from "./fingerprint-package-loader.js"; import { resolveGhostDirDefault } from "./package-paths.js"; import { DEFAULT_TEMPLATE_NAME, @@ -27,10 +28,9 @@ import { // The lint pass lives beside this module; re-export it so `validate` callers // keep a single import site for package loading and linting. -export { lintFingerprintPackage } from "./fingerprint-package-lint.js"; -export { loadFingerprintPackage }; +export { lintGhostPackage, loadGhostPackage }; -export interface FingerprintPackagePaths { +export interface GhostPackagePaths { dir: string; packageDir: string; manifest: string; @@ -41,10 +41,10 @@ export interface FingerprintPackagePaths { composition: string; } -export interface LoadedFingerprintPackage { - manifest: GhostFingerprintPackageManifest; +export interface LoadedGhostPackage { + manifest: GhostPackageManifest; manifestRaw: string; - /** The in-memory flat node catalog — the only fingerprint model. */ + /** The in-memory flat node catalog — the ghost package model. */ catalog: GhostCatalog; /** Whether `.ghost/checks/` exists; `ghost review` requires it. */ hasChecksDir: boolean; @@ -60,7 +60,7 @@ export interface LoadedFingerprintPackage { invalidChecks: Array<{ file: string; message: string }>; } -export interface InitFingerprintPackageOptions { +export interface InitGhostPackageOptions { /** Init template name (default: "skeleton"). Mutually exclusive with `body`. */ template?: string; /** @@ -72,24 +72,24 @@ export interface InitFingerprintPackageOptions { force?: boolean; } -export interface InitFingerprintPackageResult { - paths: FingerprintPackagePaths; +export interface InitGhostPackageResult { + paths: GhostPackagePaths; /** Package-relative paths of the files the template wrote. */ written: string[]; } /** - * Resolve the fingerprint package directory. `dirArg` (an explicit + * Resolve the ghost package directory. `dirArg` (an explicit * `--package `) always wins and is used exactly as given — it may be * absolute or relative, unlike `GHOST_PACKAGE_DIR`. When `dirArg` is * omitted, `GHOST_PACKAGE_DIR` is honored so every command — not just `init` * and `validate` — respects a host-configured package location. Falls back * to the default `.ghost` when neither is set. */ -export function resolveFingerprintPackage( +export function resolveGhostPackage( dirArg: string | undefined, cwd = process.cwd(), -): FingerprintPackagePaths { +): GhostPackagePaths { const dir = resolve(cwd, dirArg ?? resolveGhostDirDefault()); const packageDir = dir; return { @@ -103,11 +103,11 @@ export function resolveFingerprintPackage( }; } -export async function initFingerprintPackage( +export async function initGhostPackage( dirArg: string | undefined, cwd = process.cwd(), - options: InitFingerprintPackageOptions = {}, -): Promise { + options: InitGhostPackageOptions = {}, +): Promise { if (options.body !== undefined && options.template !== undefined) { throw new UsageError( "--body and --template are mutually exclusive. A template is a shape of emptiness; a body is a full inhabited package — pick one.", @@ -134,7 +134,7 @@ export async function initFingerprintPackage( source = template; } - const paths = resolveFingerprintPackage(dirArg, cwd); + const paths = resolveGhostPackage(dirArg, cwd); await mkdir(paths.packageDir, { recursive: true }); const files = (await source.files()).map((file) => ({ @@ -174,7 +174,7 @@ async function writeInitFile( } catch (err) { if (!force && isExistingPathError(err)) { throw new UsageError( - `Refusing to overwrite existing Ghost fingerprint file:\n ${path}\nPass --force to overwrite.`, + `Refusing to overwrite existing ghost package file:\n ${path}\nPass --force to overwrite.`, ); } throw err; @@ -195,7 +195,24 @@ async function assertInitDoesNotOverwrite(paths: string[]): Promise { if (existing.length > 0) { const formatted = existing.map((path) => ` ${path}`).join("\n"); throw new UsageError( - `Refusing to overwrite existing Ghost fingerprint file(s):\n${formatted}\nPass --force to overwrite.`, + `Refusing to overwrite existing ghost package file(s):\n${formatted}\nPass --force to overwrite.`, ); } } + +/** @deprecated Use `GhostPackagePaths`. */ +export type FingerprintPackagePaths = GhostPackagePaths; +/** @deprecated Use `LoadedGhostPackage`. */ +export type LoadedFingerprintPackage = LoadedGhostPackage; +/** @deprecated Use `InitGhostPackageOptions`. */ +export type InitFingerprintPackageOptions = InitGhostPackageOptions; +/** @deprecated Use `InitGhostPackageResult`. */ +export type InitFingerprintPackageResult = InitGhostPackageResult; +/** @deprecated Use `resolveGhostPackage`. */ +export const resolveFingerprintPackage = resolveGhostPackage; +/** @deprecated Use `initGhostPackage`. */ +export const initFingerprintPackage = initGhostPackage; +/** @deprecated Use `loadGhostPackage`. */ +export const loadFingerprintPackage = loadGhostPackage; +/** @deprecated Use `lintGhostPackage`. */ +export const lintFingerprintPackage = lintGhostPackage; diff --git a/packages/ghost/src/scan/index.ts b/packages/ghost/src/scan/index.ts index 823a2528..9c301509 100644 --- a/packages/ghost/src/scan/index.ts +++ b/packages/ghost/src/scan/index.ts @@ -1,4 +1,9 @@ -export { FINGERPRINT_PACKAGE_DIR } from "./constants.js"; +export { + /** @deprecated Use `GHOST_PACKAGE_DIR`. */ + FINGERPRINT_PACKAGE_DIR, + GHOST_MANIFEST_FILENAME, + GHOST_PACKAGE_DIR, +} from "./constants.js"; export { GHOST_PACKAGE_DIR_ENV, normalizeGhostDir, diff --git a/packages/ghost/src/scan/tarball.ts b/packages/ghost/src/scan/tarball.ts index faa50f89..98e510c8 100644 --- a/packages/ghost/src/scan/tarball.ts +++ b/packages/ghost/src/scan/tarball.ts @@ -28,9 +28,9 @@ interface FileEntry { const BLOCK_SIZE = 512; /** - * Write a dependency-free `.tgz` archive for a Ghost package directory. + * Write a dependency-free `.tgz` archive for a ghost package directory. * - * This intentionally implements only the portable subset Ghost needs: ustar + * This intentionally implements only the portable subset ghost needs: ustar * regular-file entries, deterministic path ordering, no symlink traversal. */ export async function writeDirectoryTarball( @@ -206,5 +206,5 @@ function normalizeTarPath(path: string): string { } export function defaultArchiveName(id: string): string { - return `${id}-fingerprint.tgz`; + return `${id}-ghost-package.tgz`; } diff --git a/packages/ghost/src/scan/templates.ts b/packages/ghost/src/scan/templates.ts index ee4517ef..2646a19a 100644 --- a/packages/ghost/src/scan/templates.ts +++ b/packages/ghost/src/scan/templates.ts @@ -1,4 +1,4 @@ -import { GHOST_FINGERPRINT_PACKAGE_SCHEMA } from "#ghost-core"; +import { GHOST_PACKAGE_SCHEMA } from "#ghost-core"; import { GHOST_EVENTS_FILENAME, LEGACY_PULL_HISTORY_FILENAME, @@ -26,13 +26,13 @@ export interface GhostInitTemplate { function manifestFile(cover?: string): TemplateFile { return { relativePath: "manifest.yml", - content: `schema: ${GHOST_FINGERPRINT_PACKAGE_SCHEMA}\nid: local\n${cover ? `cover: ${cover}\n` : ""}`, + content: `schema: ${GHOST_PACKAGE_SCHEMA}\nid: local\n${cover ? `cover: ${cover}\n` : ""}`, }; } /** * Keep events tapes out of version control: they are disposable - * per-machine signals for authors iterating on the fingerprint, never canonical + * per-machine signals for authors iterating on the package, never canonical * state. */ function gitignoreFile(): TemplateFile { @@ -108,7 +108,7 @@ Reusable composition or product pattern whose purpose is distinguishable from ne { relativePath: "index.md", content: `--- -description: Always read first — the non-negotiables and how to read this fingerprint. +description: Always read first — the non-negotiables and how to read this package. --- Replace this placeholder prose with two things: @@ -118,9 +118,9 @@ medium, no matter what else is gathered — hard invariants, the anti-goals, the one-sentence stance. Anything that must never be missed belongs here, stated briefly; link out to the full node by id for depth. -**How to read the rest.** What this fingerprint covers, how its kinds organize -the corpus, and where the fingerprint deliberately stays silent — including, if -you want one, a stricter silence posture ("when this fingerprint is silent on X, +**How to read the rest.** What this package covers, how its kinds organize +the corpus, and where the package deliberately stays silent — including, if +you want one, a stricter silence posture ("when this package is silent on X, ask a human") that overrides the default proceed-provisionally behavior. \`index\` is this package's manifest-declared cover: \`ghost gather\` inlines its @@ -144,7 +144,7 @@ it applies — never a filing destination. * The composition starter: everything in `minimal`, plus a worked composition * ladder — an invariants floor (`principle.composition`), one bound/open * pattern, and a cover that teaches the convention. For teams whose - * fingerprint must steer *what agents build*, not only what they say. + * package must steer *what agents build*, not only what they say. * * The ladder is an authoring convention, not a schema: patterns state what is * bound (do not redecide) and what is open (the generator's call), and every @@ -221,10 +221,10 @@ them. { relativePath: "index.md", content: `--- -description: Start here — how this fingerprint steers composition, from patterns down to principles. +description: Start here — how this package steers composition, from patterns down to principles. --- -This fingerprint steers what gets built, not only what gets said. It is +This package steers what gets built, not only what gets said. It is organized as a ladder of binding depth: 1. **Patterns** (\`pattern.*\`) are mostly-decided compositions. When an ask @@ -257,7 +257,7 @@ opinions before trusting them. description: The composition floor — hard layout invariants that hold for every screen, whether or not a pattern matches the ask. --- -These invariants apply to everything built against this fingerprint. Patterns +These invariants apply to everything built against this package. Patterns narrow them; nothing loosens them. Each is written as a refusal or a limit with a number, because a stance an agent can check is a stance that steers. @@ -369,7 +369,7 @@ const TEMPLATES = new Map([ ]); /** - * An init body: a full inhabited fingerprint package — answered signature + * An init body: a full inhabited ghost package — answered signature * dials, materials, refs, and its own checks. Templates are shapes of * emptiness awaiting the owner's truths; a body is the same anatomy with a * real brand's values plugged in. Bodies keep their own manifest id (e.g. @@ -391,6 +391,15 @@ const VESSEL_LIGHT_BODY: GhostInitBody = { includesChecks: true, async files() { const payload = await loadPackedPayload("vessel-light"); + const manifest = payload.find( + (file) => file.relativePath === "manifest.yml", + ); + if (manifest && typeof manifest.content === "string") { + manifest.content = manifest.content.replace( + /^schema:\s*ghost\.fingerprint-package\/v1\s*$/m, + `schema: ${GHOST_PACKAGE_SCHEMA}`, + ); + } payload.sort((a, b) => { const ao = BODY_FILE_ORDER.get(a.relativePath); const bo = BODY_FILE_ORDER.get(b.relativePath); diff --git a/packages/ghost/src/skill-bundle/SKILL.md b/packages/ghost/src/skill-bundle/SKILL.md index d0a71ac2..d7a2058d 100644 --- a/packages/ghost/src/skill-bundle/SKILL.md +++ b/packages/ghost/src/skill-bundle/SKILL.md @@ -1,34 +1,33 @@ --- name: ghost -description: Author, validate, consume, and review against a repo-local Ghost fingerprint — the medium-agnostic articulation of a product's brand. Use when the user wants to set up a .ghost fingerprint, write or update brand-truth nodes, gather brand context before generation, or assemble a review packet from Ghost checks. +description: Author, validate, consume, and review against a repo-local ghost package — the medium-agnostic articulation of a product's brand. Use when the user wants to set up a .ghost package, write or update guidance nodes, gather brand context before generation, or assemble a review packet from ghost checks. license: Apache-2.0 metadata: homepage: https://github.com/block/ghost cli: ghost --- -# Ghost — Brand Fingerprints +# ghost — Brand Guidance Packages -A Ghost fingerprint is the medium-agnostic articulation of a brand: its truths, -its stance, its conditions, and optional pointers to the concrete materials those -truths govern. One brand truth is stated once, at the altitude it is actually -true, and an agent reads the relevant truths before building. +A ghost package is the medium-agnostic articulation of a brand: its guidance, +its stance, its conditions, and optional pointers to the concrete materials that +guidance governs. Each brand decision is stated once, at the broadest level where +it applies, and an agent reads the relevant guidance before building. ```text .ghost/ manifest.yml # schema + id (the package anchor) glossary.md # the author's kind vocabulary materials/ # bundled materials; reserved, never nodes - ..md # a brand truth of a declared kind - .md # a brand truth without a kind + ..md # guidance of a declared kind + .md # guidance without a kind checks/ # optional review assertions; never nodes ``` ## The model in one breath -- A **node** is a markdown file: `description`, optional `materials`, and a prose - brand truth. -- `materials` is one list of locators for the concrete stuff the truth is about: +- A **node** is a markdown file: `description`, optional `materials`, and prose brand guidance. +- `materials` is one list of locators for the concrete stuff the guidance is about: repo-relative paths/globs or absolute HTTPS URLs. `materials/` is reserved for bundled materials; reference living implementations where they already live. Guidance stays in prose. @@ -63,11 +62,11 @@ ghost pulse # summarize local gather/pull events while tuning ``` `gather` does no selection. It emits the complete, unfiltered, unranked menu -from the Ghost brand fingerprint. You read the ask against descriptions, then -pull every applicable truth and skip inapplicable nodes. Topic overlap alone is +from the ghost package. You read the ask against descriptions, then +pull every applicable node and skip inapplicable nodes. Topic overlap alone is not applicability. Its header includes a coverage line: total nodes and nodes carrying concrete material. `gather` labels materials, substantial fenced -examples, and Skeletons separately, so an all-prose fingerprint is visible +examples, and Skeletons separately, so an all-prose package is visible before generation. Prefer `ghost pull` over reading files directly: it emits the same prose, @@ -104,7 +103,7 @@ from the node body and emits the fences at the end under a begin-from-this banne If a pulled Skeleton matches the task, start the artifact from it verbatim, then fill with task facts. -## Receiving a fingerprint +## Receiving a ghost package Unpack the exported archive, run `ghost validate --package `, then run `ghost skill install` in the receiving workspace. From there, gather and pull @@ -112,27 +111,27 @@ against the unpacked package with `--package `. ## Workflows -- Author or update the fingerprint: follow [references/capture.md](references/capture.md). +- Author or update the package: follow [references/capture.md](references/capture.md). - Author material-backed nodes: follow [references/blocks.md](references/blocks.md). - Choose which concrete code tiers the package carries: follow [references/concrete-tiers.md](references/concrete-tiers.md). - Choose the right human-agent authoring workflow: follow [references/authoring-scenarios.md](references/authoring-scenarios.md). - Adapt a starter (a body or the skeleton) to your brand: follow [references/adapting-a-starter.md](references/adapting-a-starter.md). -- Gather applicable truths for a task: follow [references/recall.md](references/recall.md). +- Gather applicable guidance for a task: follow [references/recall.md](references/recall.md). - Shape a pre-generation brief: follow [references/brief.md](references/brief.md). - Probe readiness before generating: follow [references/self-check.md](references/self-check.md). - Audit steering coverage: follow [references/steering-audit.md](references/steering-audit.md). - Understand the package shape: see [references/schema.md](references/schema.md). -Fingerprint authoring is **elicitation, not scanning**. The raw material is what +ghost package authoring is **elicitation, not scanning**. The raw material is what the human brings and points at: words, images, links, exemplar products, brand docs, copy they love or hate. Repo code can supply material locators and local -conventions, but durable brand truth should be curated by the human. +conventions, but durable brand guidance should be curated by the human. -## When the fingerprint is silent +## When the package is silent -A silent fingerprint does not require stopping. Proceed from nearby product +A silent package does not require stopping. Proceed from nearby product surfaces, local conventions, and ordinary reasoning when safe, and label that -reasoning as provisional and non-Ghost-backed unless the fingerprint itself +reasoning as provisional and non-ghost-backed unless the package itself declares a stricter silence posture (check the cover), which overrides this default. Ask a human before high-risk, irreversible, privacy, security, legal, or brand-defining choices. @@ -140,7 +139,7 @@ legal, or brand-defining choices. ## Never - Never invent hierarchy, inheritance, or cross-node edges. -- Never file a truth by destination (`for-emails.md`); state its condition in prose. +- Never file a node by destination (`for-emails.md`); state its condition in prose. - Never put guidance in `materials`; it belongs in the node body. - Never gather checks as generation context. -- Never claim provisional or local-convention reasoning as Ghost-backed. +- Never claim provisional or local-convention reasoning as ghost-backed. diff --git a/packages/ghost/src/skill-bundle/references/adapting-a-starter.md b/packages/ghost/src/skill-bundle/references/adapting-a-starter.md index dc6a0052..1eb7d800 100644 --- a/packages/ghost/src/skill-bundle/references/adapting-a-starter.md +++ b/packages/ghost/src/skill-bundle/references/adapting-a-starter.md @@ -1,17 +1,17 @@ --- name: adapting-a-starter -description: Transplant a starter fingerprint — a body like vessel-light or the naked skeleton — into your own brand, one procedure from manifest id to regenerated refs. +description: Transplant a starter ghost package — a body like vessel-light or the naked skeleton — into your own brand, one procedure from manifest id to regenerated refs. handoffs: - label: Validate the adapted package command: ghost validate --format json - prompt: Does the adapted fingerprint validate, and did any pruned rule orphan a check reference? + prompt: Does the adapted package validate, and did any pruned rule orphan a check reference? --- -# Recipe: Adapt A Starter Fingerprint +# Recipe: Adapt A Starter Package **Goal:** turn an installed starter — a full body (`ghost init --body vessel-light`) or the naked skeleton (`ghost init`) — into *your* brand's -fingerprint without shipping a self-contradicting package. +brand guidance without shipping a self-contradicting package. The naked skeleton is factored by what must happen during adaptation: @@ -26,7 +26,7 @@ The naked skeleton is factored by what must happen during adaptation: Do the steps **in order and in one sitting** where possible. A half-adapted package is worse than an unadapted one: stale refs steer harder than any prose -you rewrote, so stopping before refs and checks are current ships a fingerprint +you rewrote, so stopping before refs and checks are current ships a package that contradicts itself. ## The procedure @@ -64,7 +64,7 @@ that contradicts itself. stale ref loses. Rebuild each ref against the new values and chapter answers, keep any annotation headers current, and make each ref demonstrate its closed sets completely. If the starter shipped no refs, generate them now — a - fingerprint with no exemplars steers at half strength. + package with no exemplars steers at half strength. 8. **Rewrite checks and near-miss nodes.** For each answered foundation chapter, name the failure mode one step away from the answer and its replacement. Put pre-generation guidance in nodes and review assertions in `checks/`; never @@ -81,11 +81,11 @@ Work does not block on adaptation. Before the procedure runs (or midway through it), cite starter content honestly: - Cover scaffolding and unanswered foundation sections: **provisional** — never - plain brand truth. -- Foundation usage rules: **Ghost-backed starter structure** until the human + plain brand guidance. +- Foundation usage rules: **ghost-backed starter structure** until the human answers the open sections and edits the misuse lists. -- Surviving median rules: **owner-backed after init** — Ghost stamps this - measured model truth into every initialized package; you own the pruning and +- Surviving median rules: **owner-backed after init** — ghost stamps this + measured model behavior into every initialized package; you own the pruning and any adaptation thereafter. - Context nodes: **conditional** — read only when their situation matches, and revisit them after the foundation answers change. @@ -94,7 +94,7 @@ it), cite starter content honestly: ## Never -- Never leave cover scaffolding in a real fingerprint. +- Never leave cover scaffolding in a real ghost package. - Never present your provisional value as the brand's answer. - Never leave stale refs alongside new values — regenerate or delete; a stale exemplar outweighs rewritten prose. diff --git a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md index 43129dc8..7436035e 100644 --- a/packages/ghost/src/skill-bundle/references/authoring-scenarios.md +++ b/packages/ghost/src/skill-bundle/references/authoring-scenarios.md @@ -1,19 +1,19 @@ --- name: authoring-scenarios -description: Choose the right human-agent workflow for authoring Ghost brand fingerprints. +description: Choose the right human-agent workflow for authoring ghost packages. handoffs: - - label: Validate the fingerprint + - label: Validate the package command: ghost validate --format json - prompt: Classify this fingerprint's authoring scenario and confirm the package validates. + prompt: Classify this package's authoring scenario and confirm the package validates. --- -# Recipe: Collaborative Fingerprint Authoring +# Recipe: Collaborative Package Authoring -**Goal:** help a human and agent co-author durable brand truths through +**Goal:** help a human and agent co-author durable brand guidance through conversation — without laundering what a codebase happens to do into what the brand means. -The fingerprint is medium-agnostic and is not derived from a repo. Its raw +The ghost package is medium-agnostic and is not derived from a repo. Its raw material is **elicitation**: what the human says, shows, and points at — words, screenshots, links, exemplar products, marketing copy, a competitor they define themselves against. Agent synthesis is draft work until the human curates it @@ -22,15 +22,15 @@ and ordinary Git review accepts it. Repo-bound reality — components, tokens, paths, building blocks — can be recorded as `materials` on the node whose prose explains their purpose. When a conversation surfaces "we have a component for that," treat it as material to -locate and interpret, not as brand truth by itself. +locate and interpret, not as brand guidance on its own. ## 1. Start With One Repeated Decision -Do not try to fingerprint the whole brand at once. Ask the human for the one +Do not try to capture the whole brand at once. Ask the human for the one decision whose feedback keeps repeating — the checkout always flagged for trust, the voice always re-toned, the empty state always rewritten — and -capture that one truth as a node first. One high-confidence truth beats an -empty catalog; the fingerprint grows as the next repeated decision shows up. +capture that one decision as a node first. One well-grounded node beats an +empty catalog; the package grows as the next repeated decision shows up. The scenario below tunes the authoring *posture* for that first node; it is not a gate to clear before writing anything. @@ -38,11 +38,11 @@ a gate to clear before writing anything. | Scenario | Default authoring posture | | --- | --- | | Net new brand | Stance-first. Elicit feel, audience, and early anti-goals before anything else exists to point at. | -| Established brand, first fingerprint | Artifact-rich interview. The human can show a lot — shipped surfaces, brand docs, campaigns. Elicit which of it is *intentional*. | +| Established brand, first ghost package | Artifact-rich interview. The human can show a lot — shipped surfaces, brand docs, campaigns. Elicit which of it is *intentional*. | | Strong opinions, weak articulation | Example-led. The human knows it when they see it: work from exemplars and counter-exemplars ("this feels like us, this never would") toward the stance underneath. | -| Brand doc or design-language deck exists | Distillation. The document is testimony, not truth: pull the claims that actually steer decisions, drop the aspirational filler, and have the human ratify each survivor. | +| Brand doc or design-language deck exists | Distillation. Treat the document as evidence: pull the claims that actually steer decisions, drop the aspirational filler, and have the human ratify each survivor. | | Rebrand, redesign, migration | Transition-led. Capture current, target, and what must not be lost in between. | -| Fork, white label, tenant variant | Shared base + local divergence. Keep common truths broad; scope divergence with conditions. | +| Fork, white label, tenant variant | Shared base + local divergence. Keep shared guidance broad; scope divergence with conditions. | | Monorepo or product suite | One contract per package. | If more than one applies, start with the broad scenario, then narrow. @@ -50,7 +50,7 @@ If more than one applies, start with the broad scenario, then narrow. ## 2. Interview The Human The interview is the engine. Ask only high-leverage questions that change the -fingerprint: +guidance: - What should this brand feel like, and what should it never become? (The "never become" answers are anti-goal nodes — capture them with the same care @@ -63,7 +63,7 @@ fingerprint: - What keeps getting flagged in review, re-toned, or rewritten? - Where do trust, density, pacing, accessibility, recovery, or disclosure matter most? -- Which truths are universal, and which only hold under a specific situation? +- Which guidance is universal, and which guidance holds only under a specific situation? Capture human-authored or human-approved answers as nodes. Do not treat unapproved notes as canonical. @@ -73,7 +73,7 @@ unapproved notes as canonical. Ask for artifacts and read them closely: screenshots, links, exemplar products, brand docs, marketing copy, past campaigns, a rejected design and why it was rejected. Treat every artifact as *testimony* — evidence of a stance the human -holds — never as truth by itself. A pattern that appears everywhere may be +holds — never as guidance by itself. A pattern that appears everywhere may be legacy; a pattern that appears once may be the brand at its best. The human says which. @@ -85,15 +85,15 @@ and enforced in review, not repeated as the model's main example. ## 4. Draft The Nodes -Write the smallest useful set of nodes, each a purpose-coherent prose truth with +Write the smallest useful set of nodes, each carrying purpose-coherent prose guidance with a one-line `description`, named `..md` (or a bare slug when no kind is present). Ask three questions of each node body: why (the stance), with what (the materials), and how it is assembled (the patterns). These are drafting prompts, not fields. -Draft only what the human said or showed. State universal truths plainly; give -narrower truths a **condition** in the prose — the situation they apply in, +Draft only what the human said or showed. State universal guidance plainly; give +narrower guidance a **condition** in the prose — the situation they apply in, never a destination. Label uncertain reasoning as provisional. Prefer a few -high-confidence truths over a broad catalog. Hold draft prose to the node prose +focused guidance over a broad catalog. Hold draft prose to the node prose stances in [capture.md](capture.md) and score each node against its drafting gate before presenting drafts for curation. @@ -103,7 +103,7 @@ Before treating draft content as durable, ask the human to classify important claims: - keep as canonical -- soften into guidance +- soften the guidance - reject as accidental or legacy - move to scratch notes - restate at a broader or narrower altitude (add or drop a condition) @@ -115,10 +115,10 @@ Two authoring decisions replace any notion of hierarchy: - **Kind** — declare the kind vocabulary in `glossary.md` and name each node `..md` so its normative weight is clear. The glossary defines what each kind means and how strongly it binds. Kinds are your choice; - Ghost ships no fixed vocabulary. -- **Altitude** — state a truth at the level it is actually true. Universal → state + ghost ships no fixed vocabulary. +- **Altitude** — state guidance at the level where it applies. Universal → state it plainly. Narrower → name the situation that activates it, in the prose. Never - file a truth by destination (`for-emails.md`); the model reads the condition and + file a node by destination (`for-emails.md`); the model reads the condition and decides when it applies. ## 7. Validate And Ratify @@ -137,7 +137,7 @@ edits are drafts; checked-in nodes are canonical. The shapes below teach what steering prose looks like when it works. The examples use **Morrow Ledger**, a fictional bookkeeping product — teaching fiction for this recipe only, never content for a real corpus. Each shape is a -pattern to imitate with the human's real truths. +pattern to imitate with the human's real guidance. - **Stance as forced choices.** Not adjectives — decisions under pressure: "Clarity beats momentum: write '3 invoices are 14+ days late', not 'Cash @@ -160,25 +160,25 @@ pattern to imitate with the human's real truths. teaches its accidents as brand. - **Material inventory.** `materials` locates; prose explains meaning: "`src/styles/tokens.css` should define the ink, ledger, amber, and paper - colors." Never duplicate API docs unless the API itself is the brand truth. + colors." Never duplicate API docs unless the API itself is the brand guidance. - **Decision trace.** A worked tradeoff with the reversal condition: "We chose visible evidence over shorter surfaces… Reverses when the evidence would expose sensitive third-party details — then show the count only." Models imitate reasoning as well as outputs. State the silence posture on the manifest-declared cover, which `gather` -inlines for every task: when the fingerprint is silent, proceed provisionally +inlines for every task: when the package is silent, proceed provisionally from local conventions only when safe, and ask before brand-defining, irreversible, legal, privacy-sensitive, or high-risk choices. ## Never -- Never derive brand truth from repo code alone; what the codebase repeats may +- Never derive brand guidance from repo code alone; what the codebase repeats may be legacy, not stance. Use repo paths as `materials` only after the prose - truth has been curated. + guidance has been curated. - Never draft a node the human neither said nor showed; that is invention wearing the brand's clothes. - Never treat a brand doc, an artifact, or repetition frequency as brand authority; the human ratifies what is intentional. - Never invent a hierarchy, inheritance, or edges — the package is flat. -- Never file a truth by destination; state its condition in the prose. +- Never file a node by destination; state its condition in the prose. diff --git a/packages/ghost/src/skill-bundle/references/blocks.md b/packages/ghost/src/skill-bundle/references/blocks.md index 3b44688a..9fa3ff78 100644 --- a/packages/ghost/src/skill-bundle/references/blocks.md +++ b/packages/ghost/src/skill-bundle/references/blocks.md @@ -1,21 +1,21 @@ --- name: blocks -description: Author block and material nodes so an agent can match a fingerprint's stance to concrete building blocks. +description: Author block and material nodes so an agent can match a ghost package's stance to concrete building blocks. handoffs: - label: List the node menu command: ghost gather - prompt: What materials does this fingerprint document, and what is absent? + prompt: What materials does this package document, and what is absent? --- # Recipe: Author Block And Material Nodes **Goal:** when you are documenting the **materials** a brand draws from — its building blocks, assets, and reusable pieces — write that prose so an agent can -translate the fingerprint's stance into concrete building blocks without the -fingerprint ever naming a component. +translate the package's stance into concrete building blocks without the +guidance ever naming a component. This is opinionated method, not new schema. **"Block node" is shorthand in this -recipe, not a Ghost concept**: it means any node whose truth is a reusable +recipe, not a ghost concept**: it means any node whose guidance is a reusable building block, whatever kind the author's glossary declares for it (`block`, `asset`, `pattern`, …). A block node is a node like any other: a markdown file with a `description` and a prose body, named `..md` (or a bare @@ -25,10 +25,10 @@ whatever keeps each node purpose-coherent. ## Where it sits -A fingerprint declares stance. Block and material nodes ground that stance in +A ghost package declares stance. Block and material nodes ground that stance in concrete material: materials, substantial fenced examples, or Skeletons. A realizing agent **reads** them and matches against them. Strip every block node -and the fingerprint is still valid — it just gives the agent less to draw on, +and the package is still valid — it just gives the agent less to draw on, and prose-only steering is weak steering. The agent does the matching. ## Concreteness is the grounding dial @@ -50,7 +50,7 @@ Neither is correct. A concrete block node is a deliberate trade, not a leak. the absence of a body is the signal that training priors are acceptable here. When a primitive is itself brand-distinctive (a button whose shape, weight, or focus treatment is a recognizable brand move), its *divergence from the - generic form* is a truth worth a body: state what differs and why, not the + generic form* is a decision worth a node: state what differs and why, not the full API. - **Anything that encodes a user moment** (confirmation, plan, task, tool, reasoning, sources…) earns **one short prose body**. This is what the method is @@ -84,15 +84,15 @@ when (use X instead) / never*: Keep props, markup, and API reference out; the body documents purpose, and the implementation beneath it is swappable. Explicit values are the exception when -the value itself is the brand truth — an exact color, a specific corner radius — +the value itself is the brand guidance — an exact color, a specific corner radius — not a swappable implementation detail. ## How a match runs -The agent reads the fingerprint's stance, `gather`s the menu, pulls block nodes +The agent reads the package's stance, `gather`s the menu, pulls block nodes whose descriptions apply, separates near-neighbors on *not when* and *never*, and assembles. The realizing surface authors the chosen blocks in its medium. -The fingerprint never named a component; the agent bridged via documented +The guidance never named a component; the agent bridged via documented purpose. ## Curation rule @@ -155,7 +155,7 @@ divergence from the generic form would earn a short body.) ## Materials: bundle brand-owned materials, reference implementations Use the rule of thumb literally. Bundle brand-owned materials: brand-owned artifacts that -should travel with the fingerprint and survive export or refactors — tokens.css, +should travel with the package and survive export or refactors — tokens.css, motion.json, logo.svg, type materials. Reference implementations: living app code, components, stories, or tests whose home is still the product repo. The `materials` list locates both; the prose says what the material proves. @@ -173,7 +173,7 @@ prop APIs re-imports implementation opinion and creates a mirror to maintain. of a body is the signal. A brand-distinctive primitive earns a body for its divergence, never for its API. - Never put props, markup, or API reference in a block body; explicit - values belong only when the value itself is the brand truth. -- Never let the fingerprint reference the realizing surface; blocks are read, + values belong only when the value itself is the brand guidance. +- Never let the package reference the realizing surface; blocks are read, not addressed. - Never split two blocks that answer the same first question; that is one node. diff --git a/packages/ghost/src/skill-bundle/references/brief.md b/packages/ghost/src/skill-bundle/references/brief.md index 679326e2..831d6d6f 100644 --- a/packages/ghost/src/skill-bundle/references/brief.md +++ b/packages/ghost/src/skill-bundle/references/brief.md @@ -1,9 +1,9 @@ --- name: brief -description: Build a compact pre-generation packet from pulled Ghost truths. +description: Build a compact pre-generation packet from pulled ghost guidance. --- -# Recipe: Brief Work From Ghost Fingerprint +# Recipe: Brief Work From ghost Package A brief is an ephemeral steering packet for the generating pass. It is not a new schema and is never written back into `.ghost/`. @@ -15,8 +15,8 @@ schema and is never written back into `.ghost/`. 3. Note gather payload labels where present: `materials`, substantial fenced examples, or a `## Skeleton`. If there is no concrete material for this surface, the readiness ceiling is **Yellow**. -4. If no node applies, name the fingerprint's silence, follow the cover silence - posture, and keep provisional reasoning separate from Ghost-backed claims. +4. If no node applies, name the package's silence, follow the cover silence + posture, and keep provisional reasoning separate from ghost-backed claims. ## The packet: five sections only @@ -36,7 +36,7 @@ Return this shape: - matching pulled Skeleton, or "none pulled" ## Silent / provisional -- what Ghost does not cover and what local evidence carries +- what ghost does not cover and what local evidence carries ``` Rules: diff --git a/packages/ghost/src/skill-bundle/references/capture.md b/packages/ghost/src/skill-bundle/references/capture.md index 3d3572c2..d7ab8eb6 100644 --- a/packages/ghost/src/skill-bundle/references/capture.md +++ b/packages/ghost/src/skill-bundle/references/capture.md @@ -1,26 +1,26 @@ --- name: capture -description: Author repo-local Ghost brand fingerprints as a flat set of prose nodes. +description: Author repo-local ghost packages as a flat set of prose nodes. handoffs: - label: Inspect the package command: ghost validate - prompt: Does this fingerprint package validate, and what is absent? + prompt: Does this ghost package validate, and what is absent? --- -# Recipe: Author Ghost Fingerprint +# Recipe: Author ghost Package -**Goal:** record durable brand truths in `.ghost/` as a flat set of prose +**Goal:** record durable brand guidance in `.ghost/` as a flat set of prose **nodes**. If a change is uncommitted, it is draft work. If it is checked in, -Ghost treats the fingerprint package as canonical. +ghost treats the `.ghost/` package as canonical. ```text .ghost/ manifest.yml # schema + id glossary.md # the kind vocabulary + what each kind means - principle.trust.md # a brand truth of kind `principle` + principle.trust.md # guidance of kind `principle` pattern.invoice.md # a pattern with an optional ## Skeleton anti-goal.generic.md # a review-critical replacement rule - voice.md # a brand truth without a kind + voice.md # guidance without a kind ``` A **node** is a markdown file: a `description`, optional `materials`, and a @@ -184,10 +184,10 @@ action beats completeness... against applicability. - **Kind is the filename prefix** and must be a kind the glossary declares. A bare name (`voice.md`) has no kind. -- **Altitude lives in the prose.** State a universal truth plainly; give a - narrower truth its **condition** — the situation it applies in — in the prose +- **Altitude lives in the prose.** State universal guidance plainly; give a + narrower guidance a **condition** — the situation it applies in — in the prose and usually in the description. Do not use broad universal imperatives unless - universal retrieval is intended. Never file a truth by destination + universal retrieval is intended. Never file a node by destination (`for-emails.md`). - **Concreteness is derived.** A node carries concrete material when it has `materials`, a substantial fenced example, or a `## Skeleton`. You do not @@ -195,7 +195,7 @@ action beats completeness... ## What a node body answers -While drafting, ask three questions of every truth — *why* (the stance), *with +While drafting, ask three questions of every node — *why* (the stance), *with what* (the materials), and *how it is assembled* (the patterns). These are drafting prompts, never frontmatter keys, node types, or required sections, and a node may answer only one. Each answer lands as a steering dimension the @@ -211,8 +211,8 @@ machinery already scores: strongest steering; Skeleton fences are extracted and emitted dead last so generation starts from them. -Keep a node **purpose-coherent**: one truth, any length. Split only when it is -genuinely a different truth. +Keep a node **purpose-coherent**: one coherent decision, any length. Split only when it +contains a separate decision with different applicability. ## Node prose stances @@ -232,7 +232,7 @@ Two carve-outs come first, because they invert ordinary prose advice: Everywhere else: - **No aspirational abstractions.** "We value clarity and trust" steers - nothing. Name the decision the truth forces: what gets picked when two goods + nothing. Name the decision the guidance forces: what gets picked when two goods compete, and what gets given up. - **Descriptions must discriminate.** Read the description alone. If it also fits a competitor's brand, it is retrieval-dead; rewrite it until it could @@ -243,7 +243,7 @@ Everywhere else: - **Ban brand-deck filler.** "Elevate," "delight," "seamless," "best-in-class," "empower." When a brand doc supplies these words, they are testimony to distill, never prose to keep. -- **Settle the altitude on purpose.** Every truth is either claimed universal +- **Settle the altitude on purpose.** Every node is either claimed universal or given its condition in the prose. A node body that does neither was never curated for altitude; ask the human which it is. @@ -266,12 +266,12 @@ in curation still decides what becomes canonical. ## Author through steering jobs -The steering jobs are questions, not mandatory fields. Encode the truth in the +The steering jobs are questions, not mandatory fields. Encode the guidance in the strongest form that fixes the observed failure. | If the agent keeps... | Author... | | --- | --- | -| missing the truth | sharper `description`; move universal truth to the cover | +| missing the guidance | sharper `description`; move universal guidance to the cover | | inventing values | `asset.*` node with materials and exact names | | producing generic output | `anti-goal.*` replacement plus annotated `exemplar.*` | | choosing the wrong structure | `pattern.*` with bound/open and a `## Skeleton` | @@ -282,11 +282,11 @@ strongest form that fixes the observed failure. A `concept.*` node holds a one-time creative move — a launch hook, a campaign idea, a specific non-median leap the corpus would not have produced -by recombining its other truths. Scope it explicitly to the task or surface +by recombining its other guidance. Scope it explicitly to the task or surface it was written for, keep it distinct from invariants, and do not generalize it into permanent brand law. If it proves reusable later, promote it into an exemplar or pattern deliberately; do not let a one-off default into durable -truth by accumulation. +guidance through accumulation. Ask while authoring: @@ -305,7 +305,7 @@ Ask while authoring: Decide which posture fits before scaffolding. Follow [authoring-scenarios.md](authoring-scenarios.md) when setting up or substantially -revising a fingerprint. Human intent anchors the truths; what the human says and +revising a ghost package. Human intent anchors the guidance; what the human says and shows — words, images, links, exemplars — is the evidence; agent synthesis is draft work until a human curates it and Git review accepts it. @@ -320,12 +320,12 @@ ghost validate `ghost init` seeds the skeleton starter: the manifest, a starter `glossary.md`, a `brand.md` cover, foundation chapters with open questions, and -the model cliche floor. Replace open questions with real product truth before +the model cliche floor. Replace open questions with real product guidance before using it to steer generation. Use `ghost init --template minimal` for a manifest, glossary, cover, and cliche floor. Write the manifest-declared cover as the human-curated front door: -non-negotiables that apply to every task, what the fingerprint covers, and any +non-negotiables that apply to every task, what the package covers, and any stricter silence posture. `ghost gather` inlines it before the menu, so anything that must never be missed belongs there. @@ -340,7 +340,7 @@ feed-back only; they are never gathered. Declare the kinds you will use in `glossary.md` — the frontmatter `kinds` list plus a `#` section per kind explaining its meaning and normative weight. Kinds -are your choice; Ghost ships no fixed vocabulary. A node's filename prefix must +are your choice; ghost ships no fixed vocabulary. A node's filename prefix must match a declared kind (or the node has no kind). The glossary is a dictionary of every term with defined meaning in the corpus. @@ -353,17 +353,16 @@ hygiene, not over-structure. Elicit the brand from the human, not from a codebase. Interview for stance, audience, anti-goals, and exemplars; ask for the material they can show — screenshots, links, exemplar products, brand docs, copy they love or hate. Treat -every artifact as testimony to curate, never truth to copy verbatim. Repo-bound +every artifact as evidence to curate, never guidance to copy verbatim. Repo-bound reality can be recorded as `materials` on the node whose prose explains its purpose. ### 5. Write sparse nodes -Add the smallest useful set of nodes, each a purpose-coherent prose truth +Add the smallest useful set of nodes, each carrying purpose-coherent prose guidance answering why, with what, or how it is assembled, named `..md` or a bare slug. Draft only what the -human said or showed. State conditions as situations in the prose. Prefer a few -high-confidence truths over a noisy catalog. Hold each draft to the node prose +human said or showed. State conditions as situations in the prose. Prefer a small amount of focused, well-grounded guidance over a noisy catalog. Hold each draft to the node prose stances and score it before curation; a node below the gate returns to the interview. Ask the human to keep, soften, reject, or re-title important claims before treating draft nodes as durable. @@ -381,12 +380,12 @@ Undeclared kind prefixes and malformed Skeleton sections are warnings. ## Never - Never describe any file outside `.ghost/` as canonical package input. -- Never derive a brand truth from repo code alone; what a codebase repeats may - be legacy, not stance. Use repo paths as `materials` only when the prose truth +- Never derive brand guidance from repo code alone; what a codebase repeats may + be legacy, not stance. Use repo paths as `materials` only when the prose guidance has been curated. - Never draft a node the human neither said nor showed. - Never invent a hierarchy, inheritance, or cross-node edges — the package is flat. -- Never file a truth by destination; state its condition in the prose. +- Never file a node by destination; state its condition in the prose. - Never ship a blacklist-only anti-goal; state the replacement and enforce the hard line in review. diff --git a/packages/ghost/src/skill-bundle/references/concrete-tiers.md b/packages/ghost/src/skill-bundle/references/concrete-tiers.md index 140ad6ba..8efcb1ed 100644 --- a/packages/ghost/src/skill-bundle/references/concrete-tiers.md +++ b/packages/ghost/src/skill-bundle/references/concrete-tiers.md @@ -1,6 +1,6 @@ --- name: concrete-tiers -description: Decide which concrete code tiers a fingerprint carries — tokens, skeletons, components, exemplars — and justify every absence. +description: Decide which concrete code tiers a ghost package carries — tokens, skeletons, components, exemplars — and justify every absence. handoffs: - label: Audit what the package carries today command: ghost gather @@ -10,10 +10,10 @@ handoffs: # Recipe: Choose The Concrete Code Tiers **Goal:** decide, deliberately, which tiers of concrete code material a -fingerprint carries — and be able to say why any tier is absent. Absence +package carries — and be able to say why any tier is absent. Absence should be a decision, not a default. -Prose states a truth; concrete code makes it the cheapest continuation. A +Prose states guidance; concrete code makes it the cheapest continuation. A package can carry code at four tiers, and each does a different job during generation: @@ -30,7 +30,7 @@ Two properties decide whether a tier steers, and they are not the same thing: skeleton one block, an exemplar one whole surface. - **Delivery** — whether the material is guaranteed to reach the agent. Skeleton blocks live in node bodies, so `ghost pull` delivers them with the - truth. Files behind `materials:` locators reach only agents that spend a + guidance. Files behind `materials:` locators reach only agents that spend a turn reading them. A tier the agent never sees contributes nothing; presence in the package is not presence in context. @@ -57,15 +57,15 @@ The other three tiers are earned by the package's shape: ## Every tier is a copy that can drift -Each tier repeats the token truth, and a stale copy steers harder than the +Each tier repeats the token decision, and a stale copy steers harder than the prose that corrects it. A hardcoded value in an exemplar where a token exists is a lie waiting for a rename. Before adding a tier, accept its maintenance bill; after any token change, sweep every tier for stranded literals. -A tier can also fight the package's stance. A package whose truth is +A tier can also fight the package's stance. A package whose guidance is "compose each surface fresh from the tokens" contradicts itself by shipping -a component kit; a package whose truth is "compose from the grammar, the +a component kit; a package whose guidance is "compose from the grammar, the refs are examples not a framework" hardens toward template convergence if every pattern carries a mandatory skeleton. When a tier and the stance conflict, the stance wins — that absence is the package expressing itself. diff --git a/packages/ghost/src/skill-bundle/references/recall.md b/packages/ghost/src/skill-bundle/references/recall.md index 0b9adbb1..96b830bf 100644 --- a/packages/ghost/src/skill-bundle/references/recall.md +++ b/packages/ghost/src/skill-bundle/references/recall.md @@ -1,15 +1,15 @@ --- name: recall -description: Gather and pull the applicable Ghost brand truths for a task. +description: Gather and pull the applicable ghost brand guidance for a task. --- -# Recipe: Recall Ghost Fingerprint +# Recipe: Recall ghost Package 1. Run `ghost gather ` for the actual task. The cover is inlined by gather; - do not pull it separately. Read the coverage line: all-prose fingerprints are + do not pull it separately. Read the coverage line: all-prose packages are weak steering. -2. Select against `description`; Ghost never selects for you. Pull every node - whose stated situation applies and whose truth, material, structure, or +2. Select against `description`; ghost never selects for you. Pull every node + whose stated situation applies and whose guidance, material, structure, or refusal governs the work. Skip inapplicable nodes. Topic overlap alone is not applicability. 3. Run `ghost pull […]`. Prefer `pull` over reading files directly: it @@ -30,7 +30,7 @@ description: Gather and pull the applicable Ghost brand truths for a task. 4. Skeleton fences dead last: if one matches the surface, start from that structure verbatim. -Return the selected truths with node ids, how each applies, and where the -fingerprint is silent. If no node applies, say so and do not invent Ghost-backed +Return the selected guidance with node ids, how each applies, and where the +package is silent. If no node applies, say so and do not invent ghost-backed guidance. If silence is safe, proceed provisionally and label it; if it is brand-defining or high-risk, ask or author a node first. diff --git a/packages/ghost/src/skill-bundle/references/schema.md b/packages/ghost/src/skill-bundle/references/schema.md index 5500a49e..a37cac2a 100644 --- a/packages/ghost/src/skill-bundle/references/schema.md +++ b/packages/ghost/src/skill-bundle/references/schema.md @@ -1,19 +1,19 @@ --- name: schema -description: The Ghost fingerprint package shape: flat nodes, derived concreteness, Skeletons, probes, and checks. +description: The ghost package shape: flat nodes, derived concreteness, Skeletons, probes, and checks. --- -# Ghost Fingerprint Package Reference +# ghost Package Reference Canonical package: ```text .ghost/ - manifest.yml ghost.fingerprint-package/v1: schema + id + optional cover + manifest.yml ghost.package/v1: schema + id + optional cover glossary.md kind vocabulary + meanings materials/ bundled materials; never a node source - ..md a brand truth of a declared kind - .md a brand truth without a kind + ..md guidance of a declared kind + .md guidance without a kind checks/ optional review assertions; never a node source ``` @@ -55,7 +55,7 @@ Use the full lockup when recognition matters. - `materials` accepts repo-relative paths/globs plus absolute HTTPS URLs. It is a locator list, not guidance. -Ghost derives whether a node carries concrete material from structure: +ghost derives whether a node carries concrete material from structure: non-empty `materials`, a fenced code block of at least 3 lines, or a `## Skeleton` section. `gather` reports these payload labels for clarity; they are not ranking signals. @@ -100,7 +100,7 @@ Grade whether the change preserves the logo guidance in `asset.logo`. `references` are node ids with optional heading anchors. `probe` is optional: a repo-root shell command that `ghost review` runs for offered checks by default (timeout 30s; stdout/stderr truncated). Probe output is evidence only, never a -Ghost pass/fail verdict. Use `ghost review --no-probes` to skip. Trust model: +ghost pass/fail verdict. Use `ghost review --no-probes` to skip. Trust model: probes are the same class as npm scripts; Git review is the boundary. ## Gather / Pull / Review diff --git a/packages/ghost/src/skill-bundle/references/self-check.md b/packages/ghost/src/skill-bundle/references/self-check.md index 83bbb2c3..f6ba67ba 100644 --- a/packages/ghost/src/skill-bundle/references/self-check.md +++ b/packages/ghost/src/skill-bundle/references/self-check.md @@ -1,30 +1,30 @@ --- name: self-check -description: A pre-generation probe that reveals whether you have actually gathered and reasoned about the brand fingerprint, before you build. +description: A pre-generation probe that reveals whether you have actually gathered and reasoned about the brand guidance, before you build. --- # Recipe: Self-Check Before Generating Before writing UI, copy, email, or any output, check whether you are grounded in -the fingerprint or about to fall back on generic instincts. These questions test -your **grounding and provenance**, not the fingerprint's contents, so they hold -for any fingerprint, however sparse, whatever the medium, whoever authored it. +the guidance or about to fall back on generic instincts. These questions test +your **grounding and provenance**, not the package's contents, so they hold +for any ghost package, however sparse, whatever the medium, whoever authored it. Ask yourself: -1. **What did you gather?** Which Ghost nodes did you pull for this task (from +1. **What did you gather?** Which ghost nodes did you pull for this task (from `ghost gather`), and can you cite them by id? If you have not gathered, you are not grounded. Gather first. -2. **What is Ghost-backed vs. provisional?** For each claim you are about to +2. **What is ghost-backed vs. provisional?** For each claim you are about to encode, is it backed by a gathered node (cite the id), or is it your own provisional local reasoning? You must be able to label every claim as one or the other. -3. **Do the conditions apply?** For each conditional truth you pulled, does its +3. **Do the conditions apply?** For each conditional node you pulled, does its stated situation actually hold for this task? For each kind with conditional or scoped meaning **per the glossary**, apply it only when its stated situation holds; do not apply it where it does not, and do not ignore it where it does. -4. **Where is the fingerprint silent?** What does the fingerprint not cover for +4. **Where is the package silent?** What does the package not cover for your task, and what will carry the reasoning in those gaps? Naming the silence is part of being grounded; pretending coverage you do not have is not. @@ -40,11 +40,11 @@ Before generating, can you cite: - the hard invariants? - the conditions that apply or do not apply? - any decision trace for ambiguous tradeoffs? -- where the fingerprint is silent? +- where the package is silent? Classify readiness: -- **Green:** enough Ghost-backed guidance and concrete material for this surface +- **Green:** enough ghost-backed guidance and concrete material for this surface to generate. - **Yellow:** generation is safe, but some reasoning must be labeled provisional; if there is no concrete material for this surface, readiness is @@ -58,8 +58,8 @@ When you cannot answer the grounding questions: work to nodes by their descriptions. 2. Read the selected nodes' bodies and re-ask the questions, citing node ids. -A genuinely silent fingerprint is an expected state, not a blocker. When it does +A genuinely silent package is an expected state, not a blocker. When it does not cover the task, say so plainly and proceed with provisional local reasoning -when safe; label it non-Ghost-backed. If the fingerprint's cover declares a stricter +when safe; label it non-ghost-backed. If the package's cover declares a stricter silence posture, honor it over this default. Ask a human before high-risk or brand-defining choices. diff --git a/packages/ghost/src/skill-bundle/references/steering-audit.md b/packages/ghost/src/skill-bundle/references/steering-audit.md index 1b8d8e7b..46ad1845 100644 --- a/packages/ghost/src/skill-bundle/references/steering-audit.md +++ b/packages/ghost/src/skill-bundle/references/steering-audit.md @@ -1,11 +1,11 @@ --- name: steering-audit -description: Audit a Ghost fingerprint for concrete steering coverage. +description: Audit a ghost package for concrete steering coverage. --- # Recipe: Audit Steering Coverage -A steering audit asks whether a fingerprint can move generation away from the +A steering audit asks whether a ghost package can move generation away from the generic median. It is not a validation pass; `ghost validate` handles package shape. @@ -36,7 +36,7 @@ Report first: | Row | Status | Evidence | Next move | | --- | --- | --- | --- | -| Retrieval | strong / weak | descriptions, ids, cover | sharpen descriptions or move universal truth to the cover | +| Retrieval | strong / weak | descriptions, ids, cover | sharpen descriptions or move universal guidance to the cover | | Concreteness | strong / thin | materials, fenced examples, Skeletons | add concrete locators, exemplars, or opening structures | | Anti-goals | present / missing / vague | `anti-goal.*`, review packet | write not-X-instead-Y replacements and material locators | | Consistency | clean / conflicting | concrete bodies vs rules | update stale examples; examples average with rules | @@ -51,7 +51,7 @@ Report first: For a task, gather, pull, and report: -- **Green:** enough Ghost-backed concrete guidance to generate. +- **Green:** enough ghost-backed concrete guidance to generate. - **Yellow:** safe to generate, but some reasoning is provisional. If there is no concrete material for this surface, readiness is at most Yellow. - **Red:** missing brand-defining, high-risk, irreversible, legal, privacy, or diff --git a/packages/ghost/test/cli.test.ts b/packages/ghost/test/cli.test.ts index de05e5ab..064d1e52 100644 --- a/packages/ghost/test/cli.test.ts +++ b/packages/ghost/test/cli.test.ts @@ -237,7 +237,7 @@ describe("ghost CLI", () => { await expect( readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"), - ).resolves.toContain("schema: ghost.fingerprint-package/v1"); + ).resolves.toContain("schema: ghost.package/v1"); await expect( readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"), ).resolves.toContain("cover: brand"); @@ -456,6 +456,7 @@ describe("ghost CLI", () => { join(dir, ".ghost", "manifest.yml"), "utf-8", ); + expect(manifest).toContain("schema: ghost.package/v1"); expect(manifest).toContain("id: vessel-light"); // Fonts survive the packed payload byte-identically. @@ -517,7 +518,7 @@ describe("ghost CLI", () => { ); await expect( readFile(join(dir, ".agents", "ghost", "manifest.yml"), "utf-8"), - ).resolves.toContain("schema: ghost.fingerprint-package/v1"); + ).resolves.toContain("schema: ghost.package/v1"); await expect( readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"), ).rejects.toThrow(); @@ -539,7 +540,7 @@ describe("ghost CLI", () => { ); await expect( readFile(join(dir, "custom-dir", "manifest.yml"), "utf-8"), - ).resolves.toContain("schema: ghost.fingerprint-package/v1"); + ).resolves.toContain("schema: ghost.package/v1"); await expect( readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"), ).rejects.toThrow(); @@ -588,7 +589,7 @@ describe("ghost CLI", () => { allowNoExit: true, }); expect(result.code).toBe(2); - expect(result.stderr).toContain("No Ghost fingerprint package found"); + expect(result.stderr).toContain("No ghost package found"); }); it("uses GHOST_PACKAGE_DIR as the default package lookup for validate", async () => { @@ -613,7 +614,7 @@ describe("ghost CLI", () => { expect(refused.code).toBe(2); expect(refused.stderr).toContain( - "Refusing to overwrite existing Ghost fingerprint file(s)", + "Refusing to overwrite existing ghost package file(s)", ); await expect( readFile(join(dir, ".ghost", "index.md"), "utf-8"), @@ -642,7 +643,7 @@ describe("ghost CLI", () => { }); }); - it("detects Ghost YAML artifacts by schema when the filename is arbitrary", async () => { + it("detects ghost YAML artifacts by schema when the filename is arbitrary", async () => { await writeFile( join(dir, "package-anchor.yml"), "schema: ghost.fingerprint-package/v1\nid: local\n", @@ -668,7 +669,7 @@ describe("ghost CLI", () => { expect(init.stdout).not.toContain("memory/intent.md:"); expect( await readFile(join(dir, ".ghost", "manifest.yml"), "utf-8"), - ).toContain("schema: ghost.fingerprint-package/v1"); + ).toContain("schema: ghost.package/v1"); const validate = await runCli(["validate", "--format", "json"], dir); expect(validate.code).toBe(0); @@ -912,7 +913,7 @@ describe("ghost CLI", () => { // line says so, and validate warns on it. await writeFile( join(dir, ".ghost", "principle.mute.md"), - "---\n{}\n---\n\nUndescribed truth.\n", + "---\n{}\n---\n\nUndescribed guidance.\n", ); const gatherMute = await runCli(["gather"], dir); expect(gatherMute.stdout).toContain( @@ -1140,7 +1141,7 @@ describe("ghost CLI", () => { const menuPayload = JSON.parse(gather.stdout); expect(menuPayload.ask).toBe("checkout confirmation"); expect(menuPayload.source).toEqual({ - artifact: "Ghost brand fingerprint", + artifact: "ghost package", list: "Available guidance", }); expect(menuPayload.contract).toMatchObject({ @@ -1164,7 +1165,7 @@ describe("ghost CLI", () => { ).toBe(true); const gatherMarkdown = await runCli(["gather", "checkout", "hero"], dir); - expect(gatherMarkdown.stdout).toContain("# Ghost brand fingerprint"); + expect(gatherMarkdown.stdout).toContain("# ghost package"); expect(gatherMarkdown.stdout).toContain("Ask: checkout hero"); expect(gatherMarkdown.stdout).toContain("## Available guidance"); @@ -1504,7 +1505,7 @@ describe("ghost CLI", () => { expect(report.coldNodes).not.toContain("index"); const md = await runCli(["pulse"], dir); - expect(md.stdout).toContain("# Ghost Pulse"); + expect(md.stdout).toContain("# ghost Pulse"); expect(md.stdout).toContain("## Kind hit rates"); expect(md.stdout).toContain("- Abandoned gathers: 1"); }); @@ -1607,7 +1608,7 @@ describe("ghost CLI", () => { }), ]), ); - expect(json.brief).toContain("# Ghost Relay Brief"); + expect(json.brief).toContain("# ghost Relay Brief"); expect(json.brief).toContain("## Context Hits"); }); @@ -1634,7 +1635,7 @@ describe("ghost CLI", () => { } await expect( readFile(join(dir, "skills", "ghost", "SKILL.md"), "utf-8"), - ).resolves.toContain("When the fingerprint is silent"); + ).resolves.toContain("When the package is silent"); await expect( readFile(join(dir, "skills", "ghost", "SKILL.md"), "utf-8"), ).resolves.toContain( @@ -1969,7 +1970,7 @@ describe("ghost CLI", () => { expect(payload).toMatchObject({ kind: "export", id: "local", - archive: expect.stringContaining("local-fingerprint.tgz"), + archive: expect.stringContaining("local-ghost-package.tgz"), }); expect(payload.audit.travels).toEqual([ { diff --git a/packages/ghost/test/fingerprint-package.test.ts b/packages/ghost/test/fingerprint-package.test.ts index 8a55e055..fd322f81 100644 --- a/packages/ghost/test/fingerprint-package.test.ts +++ b/packages/ghost/test/fingerprint-package.test.ts @@ -2,22 +2,22 @@ import { mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { - lintFingerprintPackage, - loadFingerprintPackage, - resolveFingerprintPackage, -} from "../src/fingerprint.js"; import { parseGlossary } from "../src/ghost-core/index.js"; +import { + lintGhostPackage, + loadGhostPackage, + resolveGhostPackage, +} from "../src/package.js"; import { detectFileKind } from "../src/scan/file-kind.js"; import { loadNodeFiles } from "../src/scan/node-files.js"; -describe("split fingerprint package", () => { +describe("ghost package", () => { let dir: string; beforeEach(async () => { dir = join( tmpdir(), - `ghost-fingerprint-package-${Date.now()}-${Math.random().toString(36).slice(2)}`, + `ghost-package-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); await mkdir(dir, { recursive: true }); }); @@ -29,16 +29,30 @@ describe("split fingerprint package", () => { it("loads a manifest-only package as an empty catalog", async () => { await writeManifest(dir); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); expect(loaded.manifest).toEqual({ - schema: "ghost.fingerprint-package/v1", + schema: "ghost.package/v1", id: "local", }); // Only the implicit root, no authored nodes. expect([...loaded.catalog.nodes.keys()]).toEqual([]); }); + it("accepts the deprecated fingerprint package schema", async () => { + await writeFile( + join(dir, "manifest.yml"), + "schema: ghost.fingerprint-package/v1\nid: legacy\n", + ); + + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); + + expect(loaded.manifest).toEqual({ + schema: "ghost.fingerprint-package/v1", + id: "legacy", + }); + }); + it("loads *.md node files into the flat catalog", async () => { await writeManifest(dir); await mkdir(join(dir, "checkout"), { recursive: true }); @@ -47,7 +61,7 @@ describe("split fingerprint package", () => { "---\ndescription: Trust at the payment moment.\n---\n\nReduce felt risk near payment.\n", ); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); // id is the path; kind/slug come from the filename. const authored = loaded.catalog.nodes.get("checkout/principle.trust"); @@ -67,7 +81,7 @@ describe("split fingerprint package", () => { "---\ndescription: All feature UI.\nrelates:\n - to: core\n---\n\nFeature prose.\n", ); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); // The malformed node is excluded from the catalog but retained as invalid. expect(loaded.catalog.nodes.has("features")).toBe(false); expect(loaded.invalid).toEqual([ @@ -78,7 +92,7 @@ describe("split fingerprint package", () => { ]); // `validate` promotes it to a loud error keyed to the offending file. - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(1); expect(report.issues[0]).toMatchObject({ rule: "node-invalid", @@ -120,7 +134,7 @@ Replacement rule. "---\ndescription: Density stance.\n---\n\nUse density deliberately.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); // The only warning is the undeclared cover, never a kind complaint. @@ -138,7 +152,7 @@ Replacement rule. "---\ndescription: Voice.\n---\n\nSpeak plainly.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); // The only warning is the undeclared cover, never a kind complaint. @@ -156,7 +170,7 @@ Replacement rule. "---\n{}\n---\n\nUse density deliberately.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); // Undeclared cover + missing description. @@ -178,7 +192,7 @@ Replacement rule. "---\ndescription: Density stance.\n---\n\nUse density deliberately.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); expect(report.issues).not.toContainEqual( @@ -194,7 +208,7 @@ Replacement rule. "---\ndescription: Density stance.\n---\n\nUse density deliberately.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); // Undeclared cover + undeclared kind. @@ -218,7 +232,7 @@ Replacement rule. "---\ndescription: Density stance.\n---\n\nUse density deliberately.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(0); // The only warning is the undeclared cover, never a kind complaint. @@ -235,7 +249,7 @@ Replacement rule. "---\ndescription: Logo.\nmaterials:\n - brand/logo*.svg\n - https://example.com/logo\n---\n\nLogo prose.\n", ); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); expect(loaded.catalog.nodes.get("asset.logo")?.materials).toEqual([ "brand/logo*.svg", @@ -276,7 +290,7 @@ Replacement rule. "---\ndescription: Logo.\nmaterials:\n - materials/claimed.txt\n - missing/logo.svg\n - https://example.com/logo.svg\n---\n\nLogo prose.\n", ); - const report = await lintFingerprintPackage(dir, dir); + const report = await lintGhostPackage(dir, dir); expect(report.errors).toBe(0); // Undeclared cover + dead locator + orphaned material. @@ -308,7 +322,7 @@ Replacement rule. "---\ndescription: Logo.\nmaterials:\n - /absolute/logo.svg\n---\n\nLogo prose.\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(1); expect(report.issues[0]).toMatchObject({ @@ -330,7 +344,7 @@ Replacement rule. ], ]); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); expect([...loaded.catalog.nodes.keys()]).toEqual(["asset.logo"]); expect(loaded.hasChecksDir).toBe(true); @@ -342,7 +356,7 @@ Replacement rule. it("reports an absent checks/ directory without error", async () => { await writeManifest(dir); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); expect(loaded.hasChecksDir).toBe(false); expect(loaded.checks.size).toBe(0); @@ -353,7 +367,7 @@ Replacement rule. await writeManifest(dir); await mkdir(join(dir, "checks", "nested"), { recursive: true }); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(1); expect(report.issues[0]).toMatchObject({ @@ -374,7 +388,7 @@ Replacement rule. "---\ndescription: Email surface.\n---\n\nEmail.\n", ); - const loaded = await loadFingerprintPackage(resolveFingerprintPackage(dir)); + const loaded = await loadGhostPackage(resolveGhostPackage(dir)); // The id rule is uniform: path minus .md. expect([...loaded.catalog.nodes.keys()]).toEqual(["email/index", "index"]); @@ -389,7 +403,7 @@ Replacement rule. "schema: ghost.fingerprint-package/v1\nid: local\nplugins:\n - retired\n", ); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(1); expect(report.issues[0]).toMatchObject({ @@ -401,7 +415,7 @@ Replacement rule. it("reports a missing manifest", async () => { await writeFile(join(dir, "index.md"), "---\n---\n\nRoot prose.\n"); - const report = await lintFingerprintPackage(dir); + const report = await lintGhostPackage(dir); expect(report.errors).toBe(1); expect(report.issues[0]).toMatchObject({ @@ -415,7 +429,7 @@ async function writeManifest(dir: string): Promise { await mkdir(dir, { recursive: true }); await writeFile( join(dir, "manifest.yml"), - "schema: ghost.fingerprint-package/v1\nid: local\n", + "schema: ghost.package/v1\nid: local\n", ); } diff --git a/packages/ghost/test/ghost-core/node-schema.test.ts b/packages/ghost/test/ghost-core/node-schema.test.ts index baae60d8..282a1e45 100644 --- a/packages/ghost/test/ghost-core/node-schema.test.ts +++ b/packages/ghost/test/ghost-core/node-schema.test.ts @@ -39,7 +39,7 @@ describe("ghost.node/v1 schema", () => { }); it("passes through free-form descriptive keys (e.g. audience)", () => { - // Authors may add descriptive keys; Ghost does not gate on them. + // Authors may add descriptive keys; ghost does not gate on them. expect(lintGhostNode(node("audience: enterprise")).errors).toBe(0); }); diff --git a/packages/ghost/test/public-exports.test.ts b/packages/ghost/test/public-exports.test.ts index 2c55650e..f17947cb 100644 --- a/packages/ghost/test/public-exports.test.ts +++ b/packages/ghost/test/public-exports.test.ts @@ -5,22 +5,36 @@ import { describe, expect, it } from "vitest"; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); const hasBuiltExports = existsSync( - resolve(REPO_ROOT, "packages/ghost/dist/fingerprint.js"), + resolve(REPO_ROOT, "packages/ghost/dist/package.js"), ); describe.runIf(hasBuiltExports)("built public exports", () => { - it("exposes fingerprint-first package subpaths", async () => { - const [fingerprint, scan] = await Promise.all([ + it("exposes the package API and deprecated fingerprint compatibility", async () => { + const [packageApiModule, fingerprint, scan] = await Promise.all([ + import("@design-intelligence/ghost/package"), import("@design-intelligence/ghost/fingerprint"), import("@design-intelligence/ghost/scan"), ]); + const packageApi = packageApiModule as Record; const fingerprintApi = fingerprint as Record; const scanApi = scan as Record; - expect(fingerprintApi.initFingerprintPackage).toBeTypeOf("function"); - expect(fingerprintApi.lintFingerprintPackage).toBeTypeOf("function"); - expect(fingerprintApi.loadFingerprintPackage).toBeTypeOf("function"); + expect(packageApi.initGhostPackage).toBeTypeOf("function"); + expect(packageApi.lintGhostPackage).toBeTypeOf("function"); + expect(packageApi.loadGhostPackage).toBeTypeOf("function"); + expect(packageApi.resolveGhostPackage).toBeTypeOf("function"); + expect(packageApi.GHOST_PACKAGE_SCHEMA).toBe("ghost.package/v1"); + + expect(fingerprintApi.initFingerprintPackage).toBe( + packageApi.initGhostPackage, + ); + expect(fingerprintApi.lintFingerprintPackage).toBe( + packageApi.lintGhostPackage, + ); + expect(fingerprintApi.loadFingerprintPackage).toBe( + packageApi.loadGhostPackage, + ); // Direct fingerprint.md loading was removed with compare/drift/fleet. expect(fingerprintApi.loadFingerprint).toBeUndefined(); expect(fingerprintApi.writePackageContextBundle).toBeUndefined(); diff --git a/packages/ghost/test/skill-bundle-manifest.test.ts b/packages/ghost/test/skill-bundle-manifest.test.ts index e9ab0c57..47e28e0a 100644 --- a/packages/ghost/test/skill-bundle-manifest.test.ts +++ b/packages/ghost/test/skill-bundle-manifest.test.ts @@ -13,7 +13,7 @@ interface InstallManifest { } describe("install manifest", () => { - it("lists exactly the files in the Ghost skill bundle", async () => { + it("lists exactly the files in the ghost skill bundle", async () => { const manifestPath = resolve(REPO_ROOT, "install", "manifest.json"); const manifest = JSON.parse( await readFile(manifestPath, "utf-8"), diff --git a/packages/steering-control/README.md b/packages/steering-control/README.md index 3a2b559d..7fd25595 100644 --- a/packages/steering-control/README.md +++ b/packages/steering-control/README.md @@ -1,7 +1,7 @@ # steering-control -A before/after evaluation harness for Ghost fingerprints. It measures what -handing an agent a `.ghost` fingerprint buys — output quality, run-to-run +A before/after evaluation harness for ghost packages. It measures what +handing an agent a `.ghost` package buys — output quality, run-to-run consistency, retrieval precision — as a ratio of the context tokens spent. It produces one artifact: a self-contained `report.html` with screenshot @@ -15,16 +15,16 @@ the report. | Arm | Context the generating agent sees | Claim it tests | | --- | --- | --- | | `naked` | ballast + ask | baseline: median model output, no brand | -| `dump` | full fingerprint prose up front + ballast + ask | the naive "paste the brand guide in the system prompt" | -| `gather` | ballast + `ghost gather` menu + agent-selected `ghost pull`s near the ask | Ghost's actual loop | -| `dump-growth` | dump of this fingerprint **plus** extra corpora + ballast + ask | how dumping degrades as the corpus grows, while the gather menu grows one line per node | +| `dump` | full package prose up front + ballast + ask | the naive "paste the brand guide in the system prompt" | +| `gather` | ballast + `ghost gather` menu + agent-selected `ghost pull`s near the ask | ghost's actual loop | +| `dump-growth` | dump of this package **plus** extra corpora + ballast + ask | how dumping degrades as the corpus grows, while the gather menu grows one line per node | -Fresh context per run. The `naked` arm must never see the fingerprint. +Fresh context per run. The `naked` arm must never see the ghost package. ## Setup You need: Node 20+, the `ghost` CLI on PATH (or set `ghostBin` in config), -a `.ghost/` fingerprint package, and an agent that drives the loop (Claude +a `.ghost/` package, and an agent that drives the loop (Claude Code, goose, Cursor — anything that can read a prompt file and write HTML). ```bash @@ -51,7 +51,7 @@ Fill in `eval.config.json`: } ``` -- `package` — the fingerprint under test. +- `package` — the ghost package under test. - `asks` — a markdown file of numbered asks (see format below). - `ballast` — fixed, realistic-shaped irrelevant context, identical across arms. It exists to create window pressure. Never edit it between runs. diff --git a/packages/steering-control/lib/arms.mjs b/packages/steering-control/lib/arms.mjs index f2d70dcd..791fdb04 100644 --- a/packages/steering-control/lib/arms.mjs +++ b/packages/steering-control/lib/arms.mjs @@ -18,7 +18,7 @@ export function assemblePrompt(config, arm, askN, runK) { brand += `\n\n${dumpPackage(extra)}`; } } - segments.push(section("Brand fingerprint", brand)); + segments.push(section("Brand guidance", brand)); } else if (arm === "gather") { const [ghostCmd, ...ghostPre] = config.ghostBin.split(/\s+/u); menu = execFileSync( @@ -33,7 +33,7 @@ export function assemblePrompt(config, arm, askN, runK) { ], { encoding: "utf8" }, ); - segments.push(section("Ghost gather menu", menu.trim())); + segments.push(section("ghost gather menu", menu.trim())); segments.push( section("Selection instructions", gatherInstructions(config)), ); @@ -94,7 +94,7 @@ function findAsk(config, askN) { function gatherInstructions(config) { return [ "Read the menu above against the ask.", - "Select only the Ghost node ids that are relevant.", + "Select only the ghost node ids that are relevant.", `Before generating, run: ${config.ghostBin} pull --package ${config.package}`, "Use the pulled node bodies as steering context, then generate the artifact.", ].join("\n"); diff --git a/packages/vessel-light/.ghost/anti-goal.median.md b/packages/vessel-light/.ghost/anti-goal.median.md index a0a5cefb..db3aeb9b 100644 --- a/packages/vessel-light/.ghost/anti-goal.median.md +++ b/packages/vessel-light/.ghost/anti-goal.median.md @@ -1,5 +1,5 @@ --- -description: "The model's median defaults this fingerprint refuses — gather for any greenfield visual surface or first-draft copy. Each rule is reject→replace; delete lines your brand legitimately violates." +description: "The model's median defaults this ghost package refuses — gather for any greenfield visual surface or first-draft copy. Each rule is reject→replace; delete lines your brand legitimately violates." --- This is the model's median, not your brand. Each rule is reject→replace. @@ -17,16 +17,16 @@ verified at review, never steered in prose. ### Hover-lift Reject hover-lift (`translateY` + growing shadow) as the default interaction -→ confirm with color and background change at the fingerprint's fast +→ confirm with color and background change at the package's fast duration. (measured: 341) ### Indigo accent Reject the indigo/blue/purple default accent (`#4f46e5`, `#2563eb`, -`#8b5cf6` family; purple/violet hue 260–310) → the fingerprint's declared +`#8b5cf6` family; purple/violet hue 260–310) → the package's declared palette; absent one, monochrome plus a single functional accent. ### Dark theme -Reject unprompted dark theme → the fingerprint's declared surface. +Reject unprompted dark theme → the package's declared surface. Dark-to-look-cool and light-to-be-safe are the same retreat from a decision. (measured: 271) @@ -47,7 +47,7 @@ the ground. ### Glassmorphism Reject glassmorphism (decorative backdrop blur) → flat surfaces with real -borders and the fingerprint's elevation tiers. +borders and the package's elevation tiers. ### Chat bubbles Reject chat bubbles with initials-circle avatars for assistant turns → @@ -73,7 +73,7 @@ prominent metric showing real user data is fine. ### Every button primary Reject every button a primary button → one primary per view; the rest step -down the fingerprint's emphasis ladder. +down the package's emphasis ladder. ### Eyebrow kicker Reject an eyebrow kicker on every section → at most one, where the register diff --git a/packages/vessel-light/.ghost/checks/motion-restraint.md b/packages/vessel-light/.ghost/checks/motion-restraint.md index 52040bfb..f32bdc89 100644 --- a/packages/vessel-light/.ghost/checks/motion-restraint.md +++ b/packages/vessel-light/.ghost/checks/motion-restraint.md @@ -11,7 +11,7 @@ Review changed transitions and animations for vocabulary first. Flag durations that do not use `--duration-fast`, `--duration-normal`, or `--duration-slow`. A hard-coded millisecond value is a drift even when it matches the token today. -Flag custom easing unless it uses the fingerprint's one ease or a browser default already required by the primitive. +Flag custom easing unless it uses the package's one ease or a browser default already required by the primitive. Flag looping animations outside explicit loading states. Loading must be tied to ongoing work, not ambient motion. diff --git a/packages/vessel-light/.ghost/manifest.yml b/packages/vessel-light/.ghost/manifest.yml index a24901fd..f8a4d61d 100644 --- a/packages/vessel-light/.ghost/manifest.yml +++ b/packages/vessel-light/.ghost/manifest.yml @@ -1,3 +1,3 @@ -schema: ghost.fingerprint-package/v1 +schema: ghost.package/v1 id: vessel-light cover: index diff --git a/packages/vessel-light/.ghost/materials/ref/composition.form.html b/packages/vessel-light/.ghost/materials/ref/composition.form.html index 87996e81..fbbf0dcb 100644 --- a/packages/vessel-light/.ghost/materials/ref/composition.form.html +++ b/packages/vessel-light/.ghost/materials/ref/composition.form.html @@ -35,7 +35,7 @@

Profile

- +
@@ -70,7 +70,7 @@

Profile

Delete this workspace - Removes every fingerprint and review record immediately. + Removes every guidance node and review record immediately.
diff --git a/packages/vessel-light/.ghost/materials/ref/composition.overlay.html b/packages/vessel-light/.ghost/materials/ref/composition.overlay.html index beda887a..49c7b9cb 100644 --- a/packages/vessel-light/.ghost/materials/ref/composition.overlay.html +++ b/packages/vessel-light/.ghost/materials/ref/composition.overlay.html @@ -25,13 +25,13 @@
-

Fingerprint

+

ghost package

Three nodes changed since the last publish. Review the diff, then publish from the actions menu.

-
diff --git a/apps/docs/src/app/docs/page.tsx b/apps/docs/src/app/docs/page.tsx index 2ffae40e..61e0b93c 100644 --- a/apps/docs/src/app/docs/page.tsx +++ b/apps/docs/src/app/docs/page.tsx @@ -1,95 +1,59 @@ -"use client"; - -import { useStaggerReveal } from "@design-intelligence/vessel-react"; -import { BookOpen, FileText, Rocket, ShieldCheck, Zap } from "lucide-react"; -import type { ReactNode } from "react"; -import { Link } from "react-router"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; +import { BetaWarning } from "@/components/docs/beta-warning"; +import { DocIndex, type DocIndexItem } from "@/components/docs/doc-index"; +import { PageHeader } from "@/components/docs/page-header"; import { SectionWrapper } from "@/components/docs/wrappers"; -const sections: { - name: string; - href: string; - description: string; - icon: ReactNode; -}[] = [ - { - name: "Five-Minute Ghost", - href: "/docs/quickstart", - description: - "Write down the one decision you keep repeating, once, and let your agent read it before it builds.", - icon: , - }, +const learn: DocIndexItem[] = [ { - name: "Getting Started", + name: "Getting started", href: "/docs/getting-started", description: - "Install Ghost, set up the repo fingerprint, and learn the loop around .ghost.", - icon: , + "Record the first decision, brief an agent from selected guidance, and learn the complete loop.", }, { - name: "Fingerprint Authoring", - href: "/docs/fingerprint-authoring", + name: "Authoring", + href: "/docs/authoring", description: - "Co-author flat prose nodes that steer generation, with kinds from the glossary.", - icon: , + "Turn intent, shown material, and repeated decisions into guidance an agent can use.", }, { - name: "Checks And Review", + name: "Checks and review", href: "/docs/checks-and-review", description: - "Opt in to review checks, bind review assertions to nodes, and assemble advisory packets from a diff.", - icon: , + "Attach review assertions to brand guidance and inspect changed work after generation.", }, +]; + +const reference: DocIndexItem[] = [ { - name: "CLI Reference", + name: "CLI reference", href: "/docs/cli", description: - "Every command around the flat fingerprint: init, checks, validate, gather, pull, pulse, and review.", - icon: , + "Exact commands, flags, outputs, and exit behavior for the ghost CLI.", + }, + { + name: "Troubleshooting", + href: "/docs/troubleshooting", + description: + "Diagnose package discovery, validation, context gathering, review, and installation problems.", }, ]; export default function DocsIndex() { - const ref = useStaggerReveal(".doc-card", { - stagger: 0.06, - y: 30, - duration: 0.7, - }); - return ( - + + + +
+

learn

+ +
-
- {sections.map((item) => ( - -
- {item.icon} -
- - - {item.name} - - - -

- {item.description} -

- - ))} -
+
+

reference

+ +
); } diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 7b80cec0..e15b82b9 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -1,82 +1,251 @@ -import { useStaggerReveal } from "@design-intelligence/vessel-react"; +import { Link } from "react-router"; +import { GatherDemo } from "@/components/docs/gather-demo"; import { Hero } from "@/components/docs/hero"; import { SectionWrapper } from "@/components/docs/wrappers"; -export default function Home() { - const thesisRef = useStaggerReveal(".thesis-item", { - stagger: 0.08, - y: 20, - duration: 0.8, - }); +function SectionLabel({ n, children }: { n: number; children: string }) { + return ( +

+ {n} + {children} +

+ ); +} + +const fragments = [ + ["product", "components, tokens, interaction patterns, UI copy"], + ["marketing", "campaign systems, messaging frameworks, asset libraries"], + ["content", "voice and editorial guidance"], + ["support", "macros and service conventions"], + ["motion", "behavior and timing"], + ["legal", "approved claims and language"], +] as const; + +const guidanceExamples = [ + { + label: "visual decision", + path: "pattern.crop-with-intent.md", + body: `--- +description: Photography — gather when a composition uses a photo. +materials: + - brand/photography/** +--- + +# Crop with intent + +Crop around one clear subject. Let the subject meet at least one edge. + +Reject cautious full-object framing and collages that avoid choosing a focal point.`, + palette: [], + }, + { + label: "product pattern", + path: "condition.blocked-progress.md", + body: `--- +description: Blocked progress — gather when someone cannot continue a task. +materials: + - src/components/error-state/** +--- + +# Keep a path forward + +Keep the explanation and one next action together. + +If the person cannot resolve the problem, state what happens next. Do not end on a disabled control.`, + palette: [], + }, + { + label: "exact material", + path: "asset.color-roles.md", + body: `--- +description: Exact color roles and values — gather before assigning color. +materials: + - src/styles/brand-tokens.css +--- + +# Color roles +Ink carries content. Signal marks selection. Correction marks review. + +\`\`\`css +--ink: #171714; +--signal: #e8df55; +--correction: #c83e36; +\`\`\``, + palette: [ + ["ink", "#171714"], + ["signal", "#e8df55"], + ["correction", "#c83e36"], + ], + }, +] as const; + +export default function Home() { return ( <>
-

fracture +

+

Brand has never lived in one place.

+

+ Each discipline maintains the version of the brand it can use. + Brand guidelines often sit above them, describing a common intent + it cannot carry into the work. +

+
+ +
- Thesis -

-
-

- Brand is not the logo.{" "} - It's the accumulated stance behind everything you ship: what - you say plainly, where you slow down, what you refuse to do, how - you behave at the moment someone trusts you. It flows through - every surface — the screen, the email, the empty state, the - sentence. +

+ one brand, maintained in pieces +
+ {fragments.map(([discipline, artifacts]) => ( +
+
{discipline}
+
{artifacts}
+
+ ))} +
+ +
+

+ People have always bridged the gaps. They interpret, review, + correct, and repeat.

-

- Agents now do the making.{" "} - They're fast, they're capable, and they hold nothing - they aren't handed. Every generated screen and sentence is an - answer to a question the agent was never asked:{" "} - - what would this brand do here? - +

+ The fragmentation followed the organization. Different specialists + worked in different tools, on different materials, at different + times.

-

- - Ghost writes the answer down, where the making happens. - {" "} - The fingerprint is a portable steering packet: plain prose truths, - one file each, checked into the repo. A truth is stated once, at - the altitude it is actually true — “near the moment of - payment, reduce felt risk” — and steers whatever is being - made from it. Any agent reads the same packet: Claude, Codex, - Cursor, Goose. Any medium takes the same steer: a screen, a page, - a sentence. It travels with the repo, and it outlives your choice - of agent. +

+
+ +
+ maker +
+

Agents already cross boundaries the brand does not.

+

+ Agents can build a payment screen, write its confirmation email, + produce a launch campaign, and revise the support response. But + the guidance still arrives as departmental debris: a component + library for one task, a voice guide for another, a campaign deck + for the next, all triggered separately by different people.

-

- - Around the packet, machinery — and only machinery. - {" "} - ghost gather emits the menu of truths;{" "} - ghost pull delivers the ones that fit the task;{" "} - ghost review assembles an advisory packet after the - work, never during. The CLI computes and never decides. The packet - is the product; the CLI is the courier. +

+
+ +
+ guidance +
+

ghost gives brand decisions a shared, agent-legible form.

+

+ A `.ghost/` package holds the brand's broad stances that should + survive every medium, the narrower decisions that belong to a + particular situation, and pointers to the materials where those + decisions become concrete.

-

- - Brand used to survive by being remembered - {" "} - — carried in heads, enforced one review comment at a time. Ghost - makes it something stronger: written once, read before anything is - made, carried by everything that ships. +

+ One brand decision can be stated once without forcing every output + to look the same. The guidance stays specific without remaining + isolated by discipline.

+ +
+ {guidanceExamples.map((example) => ( +
+
+ {example.label} + .ghost/{example.path} +
+
+                  {example.body}
+                
+ {example.palette.length > 0 ? ( +
+ {example.palette.map(([name, value]) => ( +
+ + {name} +
+ ))} +
+ ) : null} +
+ ))} +
+
+ +
+ gather +
+

+ A useful ghost package may hold hundreds of decisions. Loading all + of them into every task would turn shared guidance into one giant + prompt. +

+

+ ghost gather gives the agent a compact view of the + complete ghost package. The agent chooses, then pulls the guidance + that applies to the work. +

+
+ + + +
+

observable

+

+ Gather shows the complete menu before the agent chooses. If useful + guidance does not reach the work, you can see whether the node was + missing, its description was unclear, or the agent skipped it. +

+ +

efficiency

+

+ Gather represents every node with a compact ID and description. + Pull loads the full prose and materials only for the selected + nodes. The agent sees the shape of the whole brand while keeping + generation context small and free of unrelated instructions. +

+
+ +
+

+ One ghost package can guide product, marketing, support, and + motion without loading the entire brand into every task. The + guidance lives together. Each task receives only what applies. +

+
+
+ +
+ + See how to build a ghost package → +
diff --git a/apps/docs/src/app/tools/drift/page.tsx b/apps/docs/src/app/tools/drift/page.tsx deleted file mode 100644 index 9b977ef9..00000000 --- a/apps/docs/src/app/tools/drift/page.tsx +++ /dev/null @@ -1,81 +0,0 @@ -"use client"; - -import { useStaggerReveal } from "@design-intelligence/vessel-react"; -import { BookOpen, Orbit, Rocket } from "lucide-react"; -import type { ReactNode } from "react"; -import { Link } from "react-router"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; -import { SectionWrapper } from "@/components/docs/wrappers"; - -const cards: { - name: string; - href: string; - description: string; - icon: ReactNode; -}[] = [ - { - name: "Checks and review", - href: "/docs/checks-and-review", - description: - "Opt in to review checks, bind assertions to nodes, and assemble advisory packets.", - icon: , - }, - { - name: "Get started", - href: "/docs/getting-started", - description: - "Install the skill bundle and review changed work against the .ghost fingerprint.", - icon: , - }, - { - name: "CLI reference", - href: "/docs/cli", - description: - "Every command around the flat fingerprint, including review flags and exit codes.", - icon: , - }, -]; - -export default function GhostDriftLanding() { - const ref = useStaggerReveal(".tool-card", { - stagger: 0.06, - y: 30, - duration: 0.7, - }); - - return ( - - - -
- {cards.map((item) => ( - -
- {item.icon} -
- - - {item.name} - - - -

- {item.description} -

- - ))} -
-
- ); -} diff --git a/apps/docs/src/app/tools/page.tsx b/apps/docs/src/app/tools/page.tsx deleted file mode 100644 index c5881296..00000000 --- a/apps/docs/src/app/tools/page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { useStaggerReveal } from "@design-intelligence/vessel-react"; -import { FileText, Orbit } from "lucide-react"; -import type { ReactNode } from "react"; -import { Link } from "react-router"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; -import { SectionWrapper } from "@/components/docs/wrappers"; - -const tools: { - name: string; - href: string; - blurb: string; - icon: ReactNode; -}[] = [ - { - name: "ghost gather", - href: "/tools/scan", - blurb: "Gather brand context before building", - icon: , - }, - { - name: "ghost review", - href: "/tools/drift", - blurb: "Review a diff against the fingerprint", - icon: , - }, -]; - -function ToolStrip() { - const ref = useStaggerReveal(".tool-chip", { - stagger: 0.05, - y: 16, - duration: 0.5, - }); - - return ( -
- {tools.map((tool) => ( - -
- {tool.icon} - - {tool.name} - -
-

- {tool.blurb} -

- - ))} -
- ); -} - -export default function ToolsIndex() { - return ( - - - - - - ); -} diff --git a/apps/docs/src/app/tools/scan/page.tsx b/apps/docs/src/app/tools/scan/page.tsx deleted file mode 100644 index 47162a68..00000000 --- a/apps/docs/src/app/tools/scan/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useStaggerReveal } from "@design-intelligence/vessel-react"; -import { BookOpen, FileText, Rocket } from "lucide-react"; -import type { ReactNode } from "react"; -import { Link } from "react-router"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; -import { SectionWrapper } from "@/components/docs/wrappers"; - -const cards: { - name: string; - href: string; - description: string; - icon: ReactNode; -}[] = [ - { - name: "Get started", - href: "/docs/getting-started", - description: - "Install the skill bundle and set up repo-local Ghost fingerprints.", - icon: , - }, - { - name: "CLI reference", - href: "/docs/cli", - description: - "Emit Available guidance with gather, pull selected truths with pull, and tune with pulse.", - icon: , - }, - { - name: "Authoring", - href: "/docs/fingerprint-authoring", - description: "How to write node prose that steers generation.", - icon: , - }, -]; - -export default function GhostScanLanding() { - const ref = useStaggerReveal(".tool-card", { - stagger: 0.06, - y: 30, - duration: 0.7, - }); - - return ( - - - -
- {cards.map((item) => ( - -
- {item.icon} -
- - - {item.name} - - - -

- {item.description} -

- - ))} -
-
- ); -} diff --git a/apps/docs/src/components/docs/animated-page-header.tsx b/apps/docs/src/components/docs/animated-page-header.tsx deleted file mode 100644 index 89e07524..00000000 --- a/apps/docs/src/components/docs/animated-page-header.tsx +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import gsap from "gsap"; -import { useEffect, useRef } from "react"; - -interface AnimatedPageHeaderProps { - kicker: string; - title: string; - description?: string; -} - -export function AnimatedPageHeader({ - kicker, - title, - description, -}: AnimatedPageHeaderProps) { - const ref = useRef(null); - - useEffect(() => { - const ctx = gsap.context(() => { - const tl = gsap.timeline({ defaults: { ease: "expo.out" } }); - - const kickerEl = ref.current?.querySelector(".page-kicker"); - const titleEl = ref.current?.querySelector(".page-title"); - const descEl = ref.current?.querySelector(".page-desc"); - const lineEl = ref.current?.querySelector(".page-line"); - - if (kickerEl) { - gsap.set(kickerEl, { y: 20, opacity: 0 }); - tl.to(kickerEl, { y: 0, opacity: 1, duration: 0.6 }); - } - if (titleEl) { - gsap.set(titleEl, { y: 60, opacity: 0 }); - tl.to(titleEl, { y: 0, opacity: 1, duration: 1 }, "-=0.4"); - } - if (lineEl) { - gsap.set(lineEl, { scaleX: 0 }); - tl.to( - lineEl, - { scaleX: 1, duration: 0.8, ease: "power2.inOut" }, - "-=0.5", - ); - } - if (descEl) { - gsap.set(descEl, { y: 20, opacity: 0 }); - tl.to(descEl, { y: 0, opacity: 1, duration: 0.6 }, "-=0.4"); - } - }, ref); - - return () => ctx.revert(); - }, []); - - return ( -
-

- {kicker} -

-
- {title} -
-
- {description && ( -

- {description} -

- )} -
- ); -} diff --git a/apps/docs/src/components/docs/beta-warning.tsx b/apps/docs/src/components/docs/beta-warning.tsx new file mode 100644 index 00000000..09550b34 --- /dev/null +++ b/apps/docs/src/components/docs/beta-warning.tsx @@ -0,0 +1,21 @@ +import { Callout } from "./callout"; + +export function BetaWarning() { + return ( + +

+ ghost is still taking shape. We do not recommend relying on it yet. The + CLI, package format, and APIs may change before 1.0 without a migration + path. +

+

+ Feel free to explore it. Read the docs, try it in a branch or throwaway + repo, follow development on{" "} + + GitHub + + , and tell us what feels wrong or missing. +

+
+ ); +} diff --git a/apps/docs/src/components/docs/callout.tsx b/apps/docs/src/components/docs/callout.tsx index 5f2443bb..660bf6e4 100644 --- a/apps/docs/src/components/docs/callout.tsx +++ b/apps/docs/src/components/docs/callout.tsx @@ -1,52 +1,50 @@ import { cn } from "@design-intelligence/vessel-react"; import type { ReactNode } from "react"; -type Variant = "info" | "warning" | "wip"; +type Variant = "info" | "warning" | "wip" | "flag"; interface CalloutProps { variant?: Variant; title?: string; + hideTitle?: boolean; children: ReactNode; } -const styles: Record = { - info: "border-border-card bg-muted/40 text-foreground", - warning: - "border-yellow-200 bg-yellow-100/20 text-foreground dark:bg-yellow-200/10", - wip: "border-yellow-200 bg-yellow-100/20 text-foreground dark:bg-yellow-200/10", +const defaultTitle: Record = { + info: "note", + warning: "warning", + wip: "work in progress", + flag: "caught in review", }; -const titleStyles: Record = { - info: "text-muted-foreground", - warning: "text-yellow-200 dark:text-yellow-100", - wip: "text-yellow-200 dark:text-yellow-100", +const variants: Record = { + info: "bg-transparent", + warning: "bg-[var(--doc-mark-soft)]", + wip: "bg-[var(--doc-mark-soft)]", + flag: "bg-[var(--doc-flag)]", }; -const defaultTitle: Record = { - info: "Note", - warning: "Warning", - wip: "Work in progress", -}; +export function Callout({ + variant = "info", + title, + hideTitle = false, + children, +}: CalloutProps) { + const displayTitle = hideTitle ? null : (title ?? defaultTitle[variant]); -export function Callout({ variant = "info", title, children }: CalloutProps) { return ( ); } diff --git a/apps/docs/src/components/docs/cli-help.tsx b/apps/docs/src/components/docs/cli-help.tsx index e944ff29..ae80e0ec 100644 --- a/apps/docs/src/components/docs/cli-help.tsx +++ b/apps/docs/src/components/docs/cli-help.tsx @@ -38,10 +38,10 @@ interface ToolEntry { const tools = (manifest as { tools: ToolEntry[] }).tools; function findCommand(tool: ToolName, name: string): CliCommand | undefined { - const entry = tools.find((t) => t.tool === tool); + const entry = tools.find((candidate) => candidate.tool === tool); if (!entry) return undefined; return entry.commands.find( - (c) => c.name === name || c.rawName.startsWith(name), + (command) => command.name === name || command.rawName.startsWith(name), ); } @@ -53,9 +53,9 @@ export function CliHelp({ }: CliHelpProps) { const cmd = findCommand(tool, command); if (!cmd) { - const knownTools = tools.map((t) => t.tool).join(", "); + const knownTools = tools.map((entry) => entry.tool).join(", "); return ( -
+
Unknown CLI command: {`${tool} ${command}`}. Tools in manifest: {knownTools}
@@ -63,48 +63,42 @@ export function CliHelp({ } return ( -
+
{(show === "all" || show === "signature") && ( -
-
+
+
{tool} {cmd.rawName}
- {!hideDescription && cmd.description && ( -
- {cmd.description} -
- )} -
+ {!hideDescription && cmd.description ? ( +
{cmd.description}
+ ) : null} + )} - {(show === "all" || show === "options") && cmd.options.length > 0 && ( -
- {cmd.options.map((opt) => ( + {(show === "all" || show === "options") && cmd.options.length > 0 ? ( +
+ {cmd.options.map((option) => (
-
- - {opt.rawName} +
+ + {option.rawName} - {opt.default !== null && ( - - default: {String(opt.default)} - - )} -
-
- {opt.description} + {option.default !== null ? ( +
+ default: {String(option.default)} +
+ ) : null}
+
{option.description}
))}
- )} - {(show === "all" || show === "options") && cmd.options.length === 0 && ( -
- No options. -
- )} -
+ ) : null} + {(show === "all" || show === "options") && cmd.options.length === 0 ? ( +
no options.
+ ) : null} +
); } diff --git a/apps/docs/src/components/docs/doc-index.tsx b/apps/docs/src/components/docs/doc-index.tsx new file mode 100644 index 00000000..e8696249 --- /dev/null +++ b/apps/docs/src/components/docs/doc-index.tsx @@ -0,0 +1,56 @@ +import type { ReactNode } from "react"; +import { Link } from "react-router"; + +export interface DocIndexItem { + name: string; + href: string; + description: string; +} + +export function DocIndex({ + items, + startAt = 1, + className = "", +}: { + items: DocIndexItem[]; + startAt?: number; + className?: string; +}) { + return ( + + ); +} + +export function InlineIndex({ + items, +}: { + items: { label: string; href: string; prefix?: ReactNode }[]; +}) { + return ( + + ); +} diff --git a/apps/docs/src/components/docs/doc-prose.tsx b/apps/docs/src/components/docs/doc-prose.tsx index 7ffbea7e..d1296a4c 100644 --- a/apps/docs/src/components/docs/doc-prose.tsx +++ b/apps/docs/src/components/docs/doc-prose.tsx @@ -9,30 +9,29 @@ export function DocProse({ return (
code]:rounded [&_:not(pre)>code]:bg-muted [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-sm [&_:not(pre)>code]:font-mono", - // Code blocks - "[&_pre]:rounded-lg [&_pre]:border [&_pre]:border-border-card [&_pre]:bg-muted/50 [&_pre]:p-4 [&_pre]:mb-4 [&_pre]:overflow-x-auto [&_pre]:text-sm [&_pre]:leading-relaxed [&_pre]:font-mono", - // Tables - "[&_table]:w-full [&_table]:mb-4 [&_table]:text-sm", - "[&_th]:text-left [&_th]:font-semibold [&_th]:text-foreground [&_th]:border-b [&_th]:border-border-strong [&_th]:pb-2 [&_th]:pr-4", - "[&_td]:text-muted-foreground [&_td]:border-b [&_td]:border-border-card [&_td]:py-2.5 [&_td]:pr-4 [&_td]:align-top", - // Horizontal rules - "[&_hr]:my-8 [&_hr]:border-border-card", - // Strong - "[&_strong]:text-foreground [&_strong]:font-semibold", + "doc-prose doc-section-stack pb-28 font-mono text-[0.8125rem] leading-5 text-foreground", + // Headings: hierarchy through weight and rhythm, not scale. + "[&_h2]:mt-10 [&_h2]:mb-4 [&_h2]:font-mono [&_h2]:text-[0.8125rem] [&_h2]:font-bold [&_h2]:leading-5 [&_h2]:lowercase", + "[&_h3]:mt-8 [&_h3]:mb-4 [&_h3]:max-w-[54ch] [&_h3]:font-mono [&_h3]:text-[0.8125rem] [&_h3]:font-bold [&_h3]:leading-5", + // Reading measure. + "[&_p]:mb-4 [&_p]:max-w-[54ch] [&_p]:text-foreground", + // Links use the single highlighter gesture. + "[&_a]:text-inherit [&_a]:underline [&_a]:underline-offset-[0.24ch] [&_a]:[text-decoration-skip-ink:none] hover:[&_a]:bg-[var(--doc-mark)] hover:[&_a]:text-[var(--doc-on-mark)] hover:[&_a]:no-underline", + // Hanging bullets and compact ordered lists. + "[&_ul]:mb-4 [&_ul]:max-w-[54ch] [&_ul]:list-none [&_ul]:pl-[2ch]", + "[&_ul_li]:relative [&_ul_li]:before:absolute [&_ul_li]:before:-ml-[2ch] [&_ul_li]:before:content-['•_']", + "[&_ol]:mb-4 [&_ol]:max-w-[54ch] [&_ol]:list-decimal [&_ol]:pl-[4ch]", + "[&_li]:mb-1", + // Inline code is a soft annotation, not a pill. + "[&_:not(pre)>code]:bg-[var(--doc-mark-soft)] [&_:not(pre)>code]:px-[0.5ch] [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-[inherit]", + // Machine artifacts: square, transparent, hairline-bound. + "[&_pre]:mb-6 [&_pre]:max-w-[76ch] [&_pre]:overflow-x-auto [&_pre]:border [&_pre]:border-[var(--doc-line)] [&_pre]:bg-transparent [&_pre]:p-4 [&_pre]:px-[2ch] [&_pre]:font-mono [&_pre]:text-[0.8125rem] [&_pre]:leading-5", + // Tables read as listings, with rows instead of containers. + "[&_table]:mb-6 [&_table]:w-full [&_table]:max-w-[76ch] [&_table]:border-collapse [&_table]:text-[0.8125rem]", + "[&_th]:border-b [&_th]:border-[var(--doc-line)] [&_th]:py-2 [&_th]:pr-[2ch] [&_th]:text-left [&_th]:font-bold [&_th]:text-foreground", + "[&_td]:border-b [&_td]:border-[var(--doc-line)] [&_td]:py-2 [&_td]:pr-[2ch] [&_td]:align-top [&_td]:text-foreground", + "[&_hr]:my-8 [&_hr]:border-[var(--doc-line)]", + "[&_strong]:font-bold [&_strong]:text-foreground", className, )} {...props} diff --git a/apps/docs/src/components/docs/dock.tsx b/apps/docs/src/components/docs/dock.tsx index 64ca8318..96f9aa80 100644 --- a/apps/docs/src/components/docs/dock.tsx +++ b/apps/docs/src/components/docs/dock.tsx @@ -8,41 +8,24 @@ import { CommandItem, CommandList, cn, - Tooltip, - TooltipContent, - TooltipTrigger, useTheme, } from "@design-intelligence/vessel-react"; -import type { LucideIcon } from "lucide-react"; -import { - BookOpen, - Home, - Monitor, - Moon, - Rocket, - Search, - Sun, - Wrench, -} from "lucide-react"; -import { motion } from "motion/react"; import { useCallback, useEffect, useState } from "react"; import { Link, useLocation, useNavigate } from "react-router"; -const nav: { - name: string; - path: string; - icon: LucideIcon; - activePath?: string; -}[] = [ - { name: "Home", path: "/", icon: Home }, - { - name: "Start", - path: "/docs/getting-started", - activePath: "/docs", - icon: Rocket, - }, - { name: "Tools", path: "/tools", icon: Wrench }, -]; +const nav = [ + { name: "home", path: "/" }, + { name: "docs", path: "/docs", activePath: "/docs" }, +] as const; + +const searchPages = [ + { label: "home", path: "/" }, + { label: "getting started", path: "/docs/getting-started" }, + { label: "authoring", path: "/docs/authoring" }, + { label: "checks and review", path: "/docs/checks-and-review" }, + { label: "cli reference", path: "/docs/cli" }, + { label: "troubleshooting", path: "/docs/troubleshooting" }, +] as const; export function Dock() { const { pathname } = useLocation(); @@ -51,9 +34,7 @@ export function Dock() { const [searchOpen, setSearchOpen] = useState(false); const [mounted, setMounted] = useState(false); - useEffect(() => { - setMounted(true); - }, []); + useEffect(() => setMounted(true), []); const cycleTheme = useCallback(() => { if (theme === "light") setTheme("dark"); @@ -62,14 +43,14 @@ export function Dock() { }, [theme, setTheme]); useEffect(() => { - const down = (e: KeyboardEvent) => { - if (e.key === "k" && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); + const handleKey = (event: KeyboardEvent) => { + if (event.key === "k" && (event.metaKey || event.ctrlKey)) { + event.preventDefault(); setSearchOpen((open) => !open); } }; - document.addEventListener("keydown", down); - return () => document.removeEventListener("keydown", down); + document.addEventListener("keydown", handleKey); + return () => document.removeEventListener("keydown", handleKey); }, []); const isActive = useCallback( @@ -80,180 +61,72 @@ export function Dock() { [pathname], ); + const go = (path: string) => { + navigate(path); + setSearchOpen(false); + }; + return ( <> - {/* Dock: bottom center, horizontal on all screens */} - {/* Search command palette */} - + - No matches. - - - { - navigate("/"); - setSearchOpen(false); - }} - > - - Home - - { - navigate("/docs/getting-started"); - setSearchOpen(false); - }} - > - - Start - - { - navigate("/tools"); - setSearchOpen(false); - }} - > - - Tools - - - - - { - navigate("/docs/cli"); - setSearchOpen(false); - }} - > - - CLI Reference - - { - navigate("/docs/checks-and-review"); - setSearchOpen(false); - }} - > - - Checks And Review - - - - - { - navigate("/tools/scan"); - setSearchOpen(false); - }} - > - - ghost gather - - { - navigate("/tools/drift"); - setSearchOpen(false); - }} - > - - ghost review - + no matches. + + {searchPages.map((page, index) => ( + go(page.path)}> + + {String(index + 1).padStart(2, "0")} + + {page.label} + + ))} diff --git a/apps/docs/src/components/docs/docs-mdx-layout.tsx b/apps/docs/src/components/docs/docs-mdx-layout.tsx index 28ab4a18..440e8d58 100644 --- a/apps/docs/src/components/docs/docs-mdx-layout.tsx +++ b/apps/docs/src/components/docs/docs-mdx-layout.tsx @@ -2,9 +2,9 @@ import { MDXProvider } from "@mdx-js/react"; import type { ComponentType, ReactNode } from "react"; import type { DocsFrontmatter } from "@/content/docs-frontmatter"; import { mdxComponents } from "@/mdx-components"; -import { AnimatedPageHeader } from "./animated-page-header"; import { DocProse } from "./doc-prose"; import { DocsPageLayout } from "./docs-page-layout"; +import { PageHeader } from "./page-header"; interface DocsMdxRouteProps { frontmatter: DocsFrontmatter; @@ -14,7 +14,7 @@ interface DocsMdxRouteProps { export function DocsMdxRoute({ frontmatter, Content }: DocsMdxRouteProps) { return ( - {children}
; + return ( +
+ {children} +
+ ); } -/** - * A single documentation section rendered as a two-column row on lg+. - * - * Left column: sticky section title (replaces inline

). - * Right column: section content with DocProse-compatible styling. - */ export function DocSection({ id, title, @@ -32,20 +26,16 @@ export function DocSection({
- {/* Left: sticky title */} -
-

+

+

{title}

- - {/* Right: content */} -
{children}
+
{children}
); } diff --git a/apps/docs/src/components/docs/gather-demo.tsx b/apps/docs/src/components/docs/gather-demo.tsx new file mode 100644 index 00000000..5820d648 --- /dev/null +++ b/apps/docs/src/components/docs/gather-demo.tsx @@ -0,0 +1,148 @@ +import { useEffect, useMemo, useRef, useState } from "react"; + +const nodes = [ + { id: "brand", description: "the cover: the whole brand on one page" }, + { id: "voice", description: "how we talk; grab for anything with words" }, + { id: "motion", description: "when and how things move on screen" }, + { + id: "email.transactional", + description: "when money moved and the reader is checking", + }, + { + id: "layout.spacing", + description: "spacing logic; grab before laying out a page", + }, + { id: "never.ai-defaults", description: "the AI defaults we refuse" }, + { + id: "logo.usage", + description: "the mark, its clearspace, and where it may not appear", + }, +] as const; + +const prompts = { + "refund-email": { + label: "write the refund confirmation email", + picked: ["brand", "voice", "email.transactional"], + command: "ghost pull voice email.transactional", + }, + "failed-screen": { + label: "build the failed-payment screen", + picked: ["brand", "voice", "layout.spacing", "never.ai-defaults"], + command: "ghost pull voice layout.spacing never.ai-defaults", + }, + "empty-state": { + label: "animate the empty-state illustration", + picked: ["brand", "motion", "never.ai-defaults"], + command: "ghost pull motion never.ai-defaults", + }, +} as const; + +type PromptKey = keyof typeof prompts; + +export function GatherDemo() { + const [active, setActive] = useState(null); + const [revealed, setRevealed] = useState(0); + const timerRef = useRef(undefined); + + useEffect(() => { + window.clearInterval(timerRef.current); + if (!active) { + setRevealed(0); + return; + } + + const reduceMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + if (reduceMotion) { + setRevealed(nodes.length); + return; + } + + setRevealed(0); + timerRef.current = window.setInterval(() => { + setRevealed((count) => { + if (count >= nodes.length) { + window.clearInterval(timerRef.current); + return count; + } + return count + 1; + }); + }, 220); + + return () => window.clearInterval(timerRef.current); + }, [active]); + + const selection = active ? prompts[active] : null; + const picked = useMemo>( + () => new Set(selection?.picked ?? []), + [selection], + ); + + return ( +
+
a gather
+
+ {( + Object.entries(prompts) as [PromptKey, (typeof prompts)[PromptKey]][] + ).map(([key, prompt]) => ( + + ))} +
+ +
+ {nodes.map((node, index) => { + const visible = active ? index < revealed : true; + const isPicked = Boolean(active && visible && picked.has(node.id)); + return ( +
+
{node.id}
+
+ {node.description} + {node.id === "brand" ? ( + + {" "} + · always in context + + ) : active && visible ? ( + + {isPicked ? " ← picked" : " · skipped"} + + ) : null} +
+
+ ); + })} +
+ +
+

{selection?.command ?? "pick a task above"}

+

+ {selection + ? "the cover stays first; selected nodes follow in a fixed reading order" + : "the agent reads the full menu before deciding what applies"} +

+
+
+ ); +} diff --git a/apps/docs/src/components/docs/hero.tsx b/apps/docs/src/components/docs/hero.tsx index 892eb842..dc23e623 100644 --- a/apps/docs/src/components/docs/hero.tsx +++ b/apps/docs/src/components/docs/hero.tsx @@ -1,72 +1,25 @@ -"use client"; +import { InlineIndex } from "./doc-index"; -import gsap from "gsap"; -import { useEffect, useRef } from "react"; +const links = [ + { label: "fracture", href: "/#fracture" }, + { label: "maker", href: "/#maker" }, + { label: "guidance", href: "/#guidance" }, + { label: "gather", href: "/#gather" }, + { label: "docs", href: "/docs" }, +]; export function Hero() { - const containerRef = useRef(null); - const headingRef = useRef(null); - - useEffect(() => { - const ctx = gsap.context(() => { - const tl = gsap.timeline({ defaults: { ease: "power2.out" } }); - - const lines = headingRef.current?.querySelectorAll(".hero-line"); - if (lines) { - gsap.set(lines, { y: 40, opacity: 0 }); - tl.to(lines, { - y: 0, - opacity: 1, - duration: 0.9, - stagger: 0.08, - }); - } - }, containerRef); - - return () => ctx.revert(); - }, []); - return ( - <> - {/* Concentric circles: fixed backdrop, persists through page scroll */} -
- {[3, 4, 5].map((i) => { - const size = Math.pow(i, 1.6) * 12; - return ( -
- ); - })} -
- -
-
-

- Ghost -

-

- Your brand, packed for agents: a steering packet they read before - they make anything. Design authority without a center. -

-
-
- +
+

ghost

+

+ a model always generates the likeliest thing. ghost makes your brand the + likeliest thing. +

+ +
); } diff --git a/apps/docs/src/components/docs/marked.tsx b/apps/docs/src/components/docs/marked.tsx new file mode 100644 index 00000000..e624d2b0 --- /dev/null +++ b/apps/docs/src/components/docs/marked.tsx @@ -0,0 +1,139 @@ +import { cn } from "@design-intelligence/vessel-react"; +import type { ComponentProps, ReactNode } from "react"; + +export function Mark({ className, ...props }: ComponentProps<"mark">) { + return ( + + ); +} + +export function Flag({ className, ...props }: ComponentProps<"span">) { + return ( + + ); +} + +export function FileFigure({ + caption, + className, + children, +}: { + caption: ReactNode; + className?: string; + children: ReactNode; +}) { + return ( +
+
{caption}
+
+ {children} +
+
+ ); +} + +export function Listing({ + caption, + className, + children, +}: { + caption?: ReactNode; + className?: string; + children: ReactNode; +}) { + return ( +
+ {caption ? ( +
{caption}
+ ) : null} +
{children}
+
+ ); +} + +export function ListingRow({ + id, + picked = false, + skipped = false, + className, + children, +}: { + id: ReactNode; + picked?: boolean; + skipped?: boolean; + className?: string; + children: ReactNode; +}) { + return ( +
+
{id}
+
+ {children} + {skipped ? ( + · skipped + ) : null} +
+
+ ); +} + +export function Steps({ className, children }: ComponentProps<"div">) { + return ( +
+ {children} +
+ ); +} + +export function Step({ + n, + label, + children, +}: { + n: string | number; + label: ReactNode; + children: ReactNode; +}) { + return ( +
+
+ [{n}] + {label} +
+
{children}
+
+ ); +} + +export function Split({ className, children }: ComponentProps<"div">) { + return ( +
*]:p-4 [&>*+*]:border-t md:grid-cols-2 md:[&>*+*]:border-l md:[&>*+*]:border-t-0", + className, + )} + > + {children} +
+ ); +} + +export function Repair({ className, children }: ComponentProps<"div">) { + return {children}; +} diff --git a/apps/docs/src/components/docs/page-header.tsx b/apps/docs/src/components/docs/page-header.tsx index c122d6f2..cc78e91b 100644 --- a/apps/docs/src/components/docs/page-header.tsx +++ b/apps/docs/src/components/docs/page-header.tsx @@ -1,53 +1,17 @@ -import { cn } from "@design-intelligence/vessel-react"; -import { ComponentProps } from "react"; - -function PageHeader({ - className, - children, - ...props -}: ComponentProps<"section">) { - return ( -
-
{children}
-
- ); -} - -function PageHeaderHeading({ className, ...props }: ComponentProps<"h1">) { - return ( -

- ); -} - -function PageHeaderDescription({ className, ...props }: ComponentProps<"p">) { - return ( -

- ); +interface PageHeaderProps { + kicker: string; + title: string; + description?: string; } -function PageActions({ className, ...props }: ComponentProps<"div">) { +export function PageHeader({ kicker, title, description }: PageHeaderProps) { return ( -

+
+

{title}

+

{kicker}

+ {description ? ( +

{description}

+ ) : null} +
); } - -export { PageActions, PageHeader, PageHeaderDescription, PageHeaderHeading }; diff --git a/apps/docs/src/components/docs/wrappers.tsx b/apps/docs/src/components/docs/wrappers.tsx index 18fad502..47bbd725 100644 --- a/apps/docs/src/components/docs/wrappers.tsx +++ b/apps/docs/src/components/docs/wrappers.tsx @@ -1,5 +1,5 @@ import { cn } from "@design-intelligence/vessel-react"; -import { ComponentProps } from "react"; +import type { ComponentProps } from "react"; interface SectionWrapperProps extends ComponentProps<"div"> { withCane?: boolean; @@ -8,28 +8,12 @@ interface SectionWrapperProps extends ComponentProps<"div"> { function SectionWrapper({ children, className, - withCane = false, + withCane: _withCane = false, ...props }: SectionWrapperProps) { - if (withCane) { - return ( -
- {children} -
-
-
- ); - } - return (
{children} @@ -45,41 +29,15 @@ interface ContainerWrapperProps extends ComponentProps<"div"> { function ContainerWrapper({ children, className, - withCane = false, + withCane: _withCane = false, inverse = false, ...props }: ContainerWrapperProps) { - if (withCane) { - return ( -
- {children} -

@@ -134,13 +130,16 @@ export default function Home() { > maker
-

Agents already cross boundaries the brand does not.

+

+ Agents work across the boundaries that split brand guidance into + pieces. +

Agents can build a payment screen, write its confirmation email, - produce a launch campaign, and revise the support response. But - the guidance still arrives as departmental debris: a component - library for one task, a voice guide for another, a campaign deck - for the next, all triggered separately by different people. + produce a launch campaign, and revise the support response. The + agent still receives a component library for one task, a voice + guide for another, and a campaign deck for the next. Different + people provide each source at different times.

@@ -151,7 +150,10 @@ export default function Home() { > guidance
-

ghost gives brand decisions a shared, agent-legible form.

+

+ ghost records brand decisions in prose that agents can find and + use. +

A `.ghost/` package holds the brand's broad stances that should survive every medium, the narrower decisions that belong to a @@ -159,9 +161,8 @@ export default function Home() { decisions become concrete.

- One brand decision can be stated once without forcing every output - to look the same. The guidance stays specific without remaining - isolated by discipline. + One decision can guide a screen, email, campaign, and support + response. Each medium still keeps its own form.

@@ -236,8 +237,8 @@ export default function Home() {

One ghost package can guide product, marketing, support, and - motion without loading the entire brand into every task. The - guidance lives together. Each task receives only what applies. + motion. The agent loads only the guidance that applies to its + current task.

diff --git a/apps/docs/src/components/docs/hero.tsx b/apps/docs/src/components/docs/hero.tsx index dc23e623..c4c1e115 100644 --- a/apps/docs/src/components/docs/hero.tsx +++ b/apps/docs/src/components/docs/hero.tsx @@ -16,7 +16,7 @@ export function Hero() { >

ghost

- a model always generates the likeliest thing. ghost makes your brand the + a model generates the likeliest thing. ghost makes your brand the likeliest thing.

diff --git a/apps/docs/src/content/docs/authoring.mdx b/apps/docs/src/content/docs/authoring.mdx index 034d977b..0c6836a6 100644 --- a/apps/docs/src/content/docs/authoring.mdx +++ b/apps/docs/src/content/docs/authoring.mdx @@ -1,6 +1,6 @@ --- title: Authoring -description: Co-author ghost brand guidance through elicitation, human intent, shown material, agent synthesis, and Git review. +description: Turn your intent, examples, and source material into brand guidance an agent can use. kicker: Docs section: guide order: 20 @@ -9,32 +9,26 @@ slug: authoring -A ghost package is not a scan dump. It is durable brand guidance that a -human and agent shape together — through **elicitation, not scanning**. +A human and agent author a ghost package together. The agent asks about the +brand's intent, audience, conditions, and boundaries. The human answers with +words, screenshots, links, shipped work, brand docs, and copy they love or +hate. The agent drafts guidance from that evidence. Git review approves the +result. -The human names the intent: what the brand should feel like, who it serves, -which guidance is universal, which guidance holds only under a condition, and what should -not drift. The evidence is what the human says, shows, and points at — words, -screenshots, links, exemplar products, brand docs, copy they love or hate. The -agent synthesizes drafts, but ordinary Git review is where ghost package edits -become canonical. - -Repo-bound reality — components, tokens, paths, building blocks — can be -recorded as `materials` on the node whose prose explains their purpose. What a -codebase does is not what a brand means; what it repeats may be legacy, not -stance. +Add components, tokens, paths, and other building blocks as `materials` on the +node that explains their purpose. A repeated pattern in the codebase may be +legacy rather than stance, so treat it as evidence to curate. - + -Start by classifying the authoring scenario. The scenario determines what the -human can articulate, show, or point at — and how the interview should run. +Use the material the human can explain, show, or point at. | Scenario | Authoring posture | | --- | --- | -| Net new brand | Stance-first. Elicit feel, audience, and early anti-goals before anything else exists to point at. | -| Established brand, first ghost package | Artifact-rich interview. The human can show a lot — shipped surfaces, brand docs, campaigns. Elicit which of it is intentional. | +| Net new brand | Stance-first. Elicit feel, audience, and anti-goals before anything else exists to point at. | +| Established brand, first ghost package | Artifact-rich interview. Review shipped surfaces, brand docs, and campaigns. Ask which choices are intentional. | | Strong opinions, weak articulation | Example-led. Work from exemplars and counter-exemplars ("this feels like us, this never would") toward the stance underneath. | | Brand doc or design-language deck exists | Distillation. Treat the document as evidence: pull the claims that steer decisions, drop the filler, and have the human ratify each survivor. | | Rebrand, redesign, or migration | Transition-led. Capture current, target, and what must not be lost in between. | @@ -50,35 +44,30 @@ Ask for it in plain English: Set up ghost for this repo. ``` -1. **Interview** - ask what the brand should feel like, what it should never - become, who it serves, and which surfaces or campaigns show it at its best. - Counter-exemplars are as valuable as exemplars: "we would never ship this" - usually yields a sharper decision than an hour of affirmative description. -2. **Work the material** - ask for what the human can show: screenshots, - links, exemplar products, brand docs, past campaigns, a rejected design and - why it was rejected. Treat every artifact as evidence to curate, not guidance - to copy. -3. **Draft** - write the smallest useful node prose from what the human said - or showed, asking of each node why, with what, and how it is assembled. -4. **Curate** - have the human keep, soften, reject, restate at a different - altitude, or record important claims before treating them as durable. -5. **Validate** - run ghost validation and use Git review as the approval - boundary. +1. **Interview:** ask how the brand should feel, who it serves, what it should + avoid, and which work shows it at its best. Ask for examples the team would + reject too; they often expose a sharper boundary. +2. **Review the material:** collect screenshots, links, brand docs, campaigns, + and rejected work. Ask which choices were intentional. +3. **Draft:** write the smallest useful node from what the human said or showed. + Ask why the guidance exists, which materials support it, and how agents + should apply it. +4. **Curate:** ask the human to keep, revise, narrow, or reject each claim. +5. **Validate:** run `ghost validate`, then review the package changes in Git. ```bash ghost validate ``` -Artifacts and brand docs are testimony only. They can seed a draft, but they do -not establish brand guidance by themselves — the human ratifies what is -intentional. +Artifacts and brand docs provide evidence for a draft. The human decides which +choices become guidance. -A `.ghost/` package is a **flat set of prose nodes** — no hierarchy, no inheritance, -no edges. A node's identity is its file path with `.md` dropped +A `.ghost/` package is a **flat set of prose nodes**. It has no hierarchy, +inheritance, or edges. A node's identity is its file path with `.md` dropped (`marketing.email.md` is the node `marketing.email`). Its **kind** is the filename's first dotted segment (`principle.trust.md` → kind `principle`), and must be a kind declared in `glossary.md`; a bare name (`voice.md`) has @@ -92,7 +81,7 @@ Node frontmatter carries only descriptive properties: | Property | What it does | | --- | --- | -| `description` | The retrieval payload the menu shows: what the node governs, the observable condition under which it applies, and what it contributes where useful. | +| `description` | The text an agent selects against: what the node governs, the observable condition under which it applies, and what it contributes where useful. | | `materials` | Optional list of locators for the concrete materials the guidance is about: repo-relative paths/globs or absolute HTTPS URLs. | | _passthrough_ | Free-form keys (`audience`, `stage`, …) are preserved for host tooling. | @@ -100,74 +89,56 @@ Node frontmatter carries only descriptive properties: files, illustrations, and external asset libraries all use it. Guidance stays in prose; `materials` only says where the material is. Repo-relative locators also power `ghost review`: a diff that touches a matched path surfaces the node and -its checks — see [Checks And Review](/docs/checks-and-review). +its checks. See [Checks And Review](/docs/checks-and-review). -The **glossary** (`glossary.md`) declares the kind vocabulary and what each -kind means — its normative weight, so an agent knows how to read a node of -that kind. Kinds are the author's choice; ghost ships no fixed vocabulary. +The **glossary** (`glossary.md`) declares the kind vocabulary and explains how +an agent should treat each kind. Kinds are the author's choice; ghost ships no +fixed vocabulary. -State guidance at the altitude where it applies. Universal guidance is stated -plainly. Narrower guidance names its **condition** in the prose — the *situation* -where it applies ("when a surface must show many items at once, carry hierarchy with -weight, not color"), never a destination or filing bucket ("for dashboards:"). -The agent reads the condition and decides when the guidance applies; that -interpretation is the agent's job, not the filename's. Avoid broad universal -imperatives unless universal retrieval is truly intended. +State guidance at the altitude where it applies. Write universal guidance +without a condition. For narrower guidance, name the **condition** in the prose: +"when a surface must show many items at once, carry hierarchy with weight, not +color." Do not file it under a destination such as "for dashboards." The agent +uses the condition to decide when the guidance applies. -While drafting a node's prose body, ask three questions of every node: *why* -(the stance — audience, goals, anti-goals, principles), *with what* (the -materials — building blocks, files, routes, assets, exemplars, source links), -and *how it is assembled* (the patterns — layouts, flows, states, content -patterns, behavior, visual arrangements). These are drafting prompts, never -frontmatter fields, and a node may answer only one. Each answer lands as a -steering dimension: stance prose is read first — weakest at moving output form -on its own, but the yardstick every later selection is measured against; -materials provide concrete payload and support review matching; patterns and -Skeletons steer strongest, and Skeleton fences are extracted and emitted dead -last so generation starts from them. +While drafting a node, ask why the guidance exists, which materials support +it, and how agents should assemble the result. Put those answers in the prose +body. They are not frontmatter fields, and a node does not need to answer all +three. + +Stance helps the agent judge later choices. Materials give it concrete work to +inspect or use. Patterns provide a repeatable assembly. -Useful brand guidance should help future agents choose, restrain, anchor, and -manifest. It should not only describe what exists or collect every available -style detail. - -Write node prose so generation decisions become explicit: - -- Name what generated work should preserve. -- Block plausible defaults that would make the output feel generic or wrong. -- Say which value wins when choices conflict. -- State the condition under which a narrower guidance applies. -- Capture broad brand intent. -- Turn taste, trust, recovery, or disclosure into obligations. -- Give repeatable layout, flow, state, content, behavior, or visual rules. -- Anchor the guidance in concrete exemplars an agent can inspect. +Useful brand guidance changes what an agent does. Write node prose that: -Write less like a brand book and more like a decision engine. +- names what the generated work should preserve; +- blocks plausible defaults that would make the output feel generic or wrong; +- states which value wins when choices conflict; +- gives the condition for narrower guidance; +- provides repeatable layout, flow, content, or behavior rules; and +- points to concrete exemplars the agent can inspect. - - -One contract per package: a repo's `.ghost/` is the whole ghost package. Reach for -a second package only when a distinct product owns its own ghost package — a -separate app in a monorepo — and target it with `ghost --package `. - - + - +A repo's `.ghost/` is the whole ghost package. Use a second package only for a +distinct product, such as a separate app in a monorepo, and target it with +`ghost --package `. -Uncommitted or unmerged ghost package edits are drafts. Checked-in ghost node prose -is canonical. +Treat uncommitted or unmerged package edits as drafts. Checked-in node prose is +canonical. diff --git a/apps/docs/src/content/docs/checks-and-review.mdx b/apps/docs/src/content/docs/checks-and-review.mdx index 2a781e90..ba4196a2 100644 --- a/apps/docs/src/content/docs/checks-and-review.mdx +++ b/apps/docs/src/content/docs/checks-and-review.mdx @@ -1,22 +1,21 @@ --- title: Checks And Review -description: Opt in to review assertions with the checks directory, bind them to nodes, and assemble advisory review packets from a diff. +description: Add review assertions to brand guidance, then ask your agent to review a diff against them. kicker: Docs section: guide order: 25 slug: checks-and-review --- - + -`gather` and `pull` are feed-forward: they ground your agent **before** it -builds. Checks and `ghost review` are the optional feed-back half: **after** a -change exists, they help the same agent review it against the guidance you wrote -down. +`gather` and `pull` give your agent guidance **before** it builds. Checks and +`ghost review` are optional tools for **after** a change exists. They help the +same agent review the change against the guidance you wrote down. -The division of labor stays the same. The CLI assembles deterministically — it -reads a diff, matches touched files to node `materials`, and offers the relevant -checks. Your agent weighs the packet and renders findings. ghost grades nothing. +Your agent performs the review. `ghost review` supplies repeatable evidence by +reading a diff, matching touched files to node `materials`, and offering the +relevant checks. The agent decides which checks apply and reports its findings. This guide assumes you have a `.ghost/` package; if not, start with [Getting Started](/docs/getting-started). Exact flags and exit codes live in @@ -30,13 +29,9 @@ Review this diff against the ghost checks. -Checks live in a flat `.ghost/checks/` directory. Two properties keep them -honest: - -- **Opt-in.** Core `ghost init` ships no checks. You add them explicitly. -- **Feed-back only.** Nothing under `checks/` is a node. `ghost gather` and - `ghost pull` never emit check content, so checks cannot leak into generation - context. +Checks live in a flat `.ghost/checks/` directory. `ghost init` does not add +checks unless you request them. Files under `checks/` are review assertions, +not nodes, so `ghost gather` and `ghost pull` do not emit their content. ```bash ghost checks init # scaffold .ghost/checks/ @@ -72,18 +67,16 @@ glyph is used when the full lockup is required. | --- | --- | | `name` | The check's identity. | | `description` | What the check asserts, in one line. | -| `severity` | `high`, `medium`, or `low` — advice for the reviewing agent, not an enforcement level. | +| `severity` | `high`, `medium`, or `low`. This advises the reviewing agent; it does not enforce a result. | | `references` | Node ids this check is grounded in, optionally scoped to one section with a heading anchor (`asset.logo > Clearspace`). | -The prose body is the instruction the reviewing agent follows. Write it like a -review comment you would trust another engineer to act on: what to grade, what -to flag, and what counts as preserved. +The prose body instructs the reviewing agent. State what to grade, what to +flag, and what counts as preserved. -`references` does double duty. It grounds the check — `ghost validate` verifies -every reference resolves to a real node — and it routes the check: when a diff -touches a file matched by a referenced node's `materials`, that check is -offered. A check that references no material-backed node is **always offered**, -which is the right shape for cross-cutting assertions like voice or tone. +`references` grounds and routes the check. `ghost validate` verifies each +reference. During review, a check is offered when the diff touches a path in a +referenced node's `materials`. A check with no material-backed reference is +**always offered**, which suits cross-cutting assertions such as voice or tone. @@ -103,29 +96,27 @@ ghost review --format json # raw packet for host tooling The advisory packet contains: - **Touched files** parsed from the diff. -- **Matched material-backed nodes** — every node whose repo-relative `materials` - locators match a touched file, with its full prose and which locators matched. - (HTTPS locators identify external material; they never match diff paths.) -- **Offered checks** — checks routed via a matched node (`matched`) plus checks - with no material-backed references (`always`), each with its prose and the - referenced nodes' baseline prose. -- **Coverage gaps** — touched files no node's materials claim, and - material-backed nodes no check guards. - -The host agent takes it from there: weigh which checks apply, cite the -brand guidance, and do not invent obligations the package never states. +- **Matched material-backed nodes:** nodes whose repo-relative `materials` + locators match a touched file, including their prose and matched locators. + HTTPS locators identify external material and do not match diff paths. +- **Offered checks:** checks routed through a matched node (`matched`) and checks + with no material-backed references (`always`). +- **Coverage gaps:** touched files no node claims and material-backed nodes no + check guards. + +The agent weighs which checks apply, cites the brand guidance, and reports its +findings without inventing obligations the package never states. - + -Coverage gaps are the review-side analog of `ghost pulse`: signals for the -package's author, not errors. +Coverage gaps show where the package may need work: -- **Unmatched files** — the diff touched something no node claims. Maybe that - surface deserves guidance, or maybe it is genuinely out of scope. -- **Unguarded materials** — a node points at concrete material, but no check - guards it. The node feeds generation but nothing asserts it survives review. +- **Unmatched files:** the diff touched a file no node claims. Decide whether + that surface needs guidance. +- **Unguarded materials:** a node points at concrete material, but no check + guards it during review. Grow coverage the same way you grow the brand guidance: one decision at a time, starting with the drift you keep catching by hand. diff --git a/apps/docs/src/content/docs/cli-reference.mdx b/apps/docs/src/content/docs/cli-reference.mdx index 464192c6..b403e254 100644 --- a/apps/docs/src/content/docs/cli-reference.mdx +++ b/apps/docs/src/content/docs/cli-reference.mdx @@ -1,6 +1,6 @@ --- title: CLI Reference -description: The deterministic commands around the flat ghost package lifecycle. Your agent does the reading, writing, and interpreting. +description: Commands that support how agents author, gather, apply, and review brand guidance from a ghost package. kicker: Docs section: guide order: 30 @@ -9,9 +9,11 @@ slug: cli -ghost is the product; the `.ghost/` package is what the CLI carries. It scaffolds and -validates the package, emits the complete menu of guidance, delivers selected -nodes, and assembles review evidence. Your agent reads, writes, and decides. +A `.ghost/` package holds the brand guidance your agent uses while making. +Your agent authors the guidance, selects what applies, interprets it, and uses +it in the work. The CLI handles the repeatable operations: scaffolding and +validation, the complete guidance menu, selected-node delivery, and review +evidence. `ghost --help` shows the short core workflow. `ghost --help --all` shows the complete command index, and `ghost --help` shows flags for one @@ -38,13 +40,13 @@ Every command follows one contract, so an agent can branch on the exit code: | Code | Meaning | | --- | --- | | `0` | Success. | -| `1` | The command ran but the result is unhappy: `validate` found issues, or an unexpected error was thrown. | +| `1` | The command ran but reported problems: `validate` found issues, or an unexpected error was thrown. | | `2` | The command was called wrong: a bad flag or argument, a missing package for a command that needs one (`gather`), a `pull` with no known ids, or `review` without a checks directory. | | `3` | A command-specific refusal, currently only `skill install` when a skill is already present (pass `--force`). | -A missing package is reported, not treated as a crash: `validate` records it as -a finding (exit `1`). Getting a code you don't understand, or a command that -refuses to run at all? See [Troubleshooting](/docs/troubleshooting). +`validate` records a missing package as a finding and exits `1`. See +[Troubleshooting](/docs/troubleshooting) for commands that exit with an +unexpected code. @@ -52,28 +54,27 @@ refuses to run at all? See [Troubleshooting](/docs/troubleshooting). ### Initialize: `init` -Scaffold a `.ghost/` package. The skeleton starter includes a manifest, a glossary, a `brand.md` cover, foundation chapters with open questions, a conversation context, and the model cliche floor. The cover and foundations tell the agent to ask rather than invent brand values. Use `--template minimal` for a smaller cover-led starter. Add guidance by adding files (`principle.density.md`). Use -`--package ` for an exact directory, or set `GHOST_PACKAGE_DIR` when a host -wrapper stores ghost files outside the default `.ghost`. Core `init` scaffolds -the package only; pass `--with checks` to also add the checks directory in -one move. +Scaffold a `.ghost/` package. The default starter includes a manifest, +glossary, `brand.md` cover, foundation nodes with open questions, and context +for the authoring conversation. These prompts tell the agent to ask rather than +invent brand values. Use `--template minimal` for a smaller starter. Add +guidance with files such as `principle.density.md`. Use `--package ` for an +exact directory, or set `GHOST_PACKAGE_DIR` when a host wrapper stores ghost +files outside `.ghost`. Pass `--with checks` to add the checks directory. -Templates are shapes of emptiness — structures awaiting your guidance. A -**body** is the same anatomy inhabited: answered signature dials, a -`materials/` tree (tokens, fonts, worked reference compositions), and its own -`checks/`. Install one with `--body`: +A template provides files and prompts for guidance you still need to author. A +**body** provides a worked ghost package with guidance, a `materials/` tree, +reference compositions, and its own `checks/`. Install one with `--body`: ```bash ghost init --body vessel-light ``` -`vessel-light` installs Vessel's design language at full strength — the -fork-axis corpus, `materials/tokens.css` owning every literal value, embedded -fonts, six annotated refs, and five checks, included automatically (no -`--with checks` needed; combining them is an error). The manifest id stays -`vessel-light` until you adapt the starter: renaming it is deliberately the -first step of making the brand yours. `--body` and `--template` are mutually -exclusive. +`vessel-light` includes guidance, `materials/tokens.css`, embedded fonts, six +annotated references, and five checks. The checks install automatically, so do +not combine `--body vessel-light` with `--with checks`. The manifest id stays +`vessel-light` until you adapt the starter; rename it when the package becomes +your own. `--body` and `--template` are mutually exclusive. @@ -88,9 +89,9 @@ GHOST_PACKAGE_DIR=.agents/ghost ghost init ### Manage checks: `checks` **Checks** are optional review assertions in a flat `.ghost/checks/` -directory. They are feed-back only — they never leak into generation context, -and they are never emitted by `gather` or `pull`. They are consumed by -`ghost review`. +directory. They are used only after a change exists. `gather` and `pull` never +emit them as generation context; `ghost review` uses them when assembling +review evidence. @@ -127,11 +128,10 @@ ghost validate --format json ### Emit the menu: `gather` -`gather` emits **Available guidance** from the ghost package: every -node's id, kind, and description. It does no selection, filtering, or ranking. -The agent reads the ask against applicability and pulls every node whose stated -situation governs the work. There is no slice, no anchor, and no inheritance -between nodes; the package is flat. +`gather` emits **Available guidance** from the ghost package: each node's id, +kind, and description. The command returns the complete menu without selecting, +filtering, or ranking nodes. The agent reads the ask and pulls each node whose +stated situation governs the work. Nodes do not inherit from one another. @@ -140,25 +140,21 @@ ghost gather "checkout confirmation email" ghost gather "checkout confirmation email" --format json ``` -This is the pre-generation step, and "the right context at the right time": the -agent selects just-in-time against the actual task, then applies the guidance -to whatever it is building: product UI, marketing, email, a landing page. If -nothing applies, the agent should name the package's silence and avoid -inventing ghost-backed guidance. +Run this before generation. The agent selects against the actual task, then +applies the guidance to whatever it is building: product UI, marketing, email, +or a landing page. If nothing applies, say that the package has no guidance +for the task. Do not invent ghost-backed guidance. ### Read the selected nodes: `pull` -`pull` emits the named nodes' full prose bodies and materials. It is how an -agent reads the guidance it selected from the menu, preferred over opening the -files directly because it inlines small materials, emits inspect-pointers, -extracts Skeletons last, and appends structured JSONL events to -`.ghost/.events`. +`pull` emits the selected nodes' prose and materials. It inlines small local +materials, emits pointers for files the agent must inspect separately, and +appends structured JSONL events to `.ghost/.events`. -That file is an events tape for the package's author: tweak a node's -description, re-run the task, and `ghost pulse` shows whether the node appeared -on the menu, got pulled, was missed by name, or stayed cold. It is a dotfile -(never a node), gitignored by the scaffold, and safe to delete at any time. The -events tape is local tuning signal, not canonical ghost package state. +The local log helps package authors tune retrieval. Change a description, +re-run the task, and use `ghost pulse` to see whether the agent saw and selected +the node. The scaffold gitignores `.ghost/.events`, and you can delete it at +any time. @@ -170,7 +166,7 @@ ghost pull voice --no-events ### Read the local signal: `pulse` -`pulse` summarizes the events tape: gathers, pulls, abandoned gathers, +`pulse` summarizes the local log: gathers, pulls, abandoned gathers, exposure-normalized hit rates, cold nodes, and unknown ids the agent tried to pull. @@ -189,15 +185,15 @@ A mixed pull partially succeeds: known ids are emitted, unknown ids warn with cl ### Assemble a review packet: `review` -Where `gather` and `pull` are feed-forward, `review` is feed-back. It reads a -diff, matches touched files to node `materials` (repo-relative locators only), -offers the relevant checks, and emits an **advisory packet** for the host agent -to weigh. The command grades nothing; the agent renders findings. +`gather` and `pull` provide guidance before the agent makes. `review` supports +the agent after a change exists. It reads a diff, matches touched files to node +`materials` (repo-relative locators only), offers the relevant checks, and emits +review evidence for the agent to weigh. The command grades nothing; the agent +renders findings. `review` requires the checks directory (`ghost checks init`). Without it, the command exits `2` with a pointer to add it. For the authoring model behind -checks — how to write them and how routing works — see -[Checks And Review](/docs/checks-and-review). +checks and routing, see [Checks And Review](/docs/checks-and-review). @@ -211,14 +207,12 @@ ghost review --format json # raw JSON packet for host tooling The packet contains: - **Touched files** parsed from the diff. -- **Matched material-backed nodes**: every node whose `materials` locators match - a touched file, with its full prose and which locators matched. -- **Offered checks**: checks whose `references` point at a matched node - (`matched`), plus checks that reference no material-backed node at all and are - therefore always offered (`always`). Each check carries its prose and the - referenced nodes' baseline prose. -- **Coverage gaps**: touched files no node's materials claim, and material-backed - nodes no check guards — signals for tuning the package, not failures. +- **Matched material-backed nodes:** nodes whose `materials` locators match a + touched file, including their prose and matched locators. +- **Offered checks:** checks routed through a matched node (`matched`) and checks + with no material-backed references (`always`). +- **Coverage gaps:** touched files no node claims and material-backed nodes no + check guards. Each check is one markdown file under `.ghost/checks/`: @@ -236,8 +230,8 @@ Grade whether the change preserves the logo guidance in `asset.logo`. `references` binds the check to node ids, optionally scoped to one section with a heading anchor (`asset.logo > Clearspace`); `validate` verifies -every reference resolves. `severity` is one of `high`, `medium`, or `low` — -advice for the reviewing agent, not an enforcement level. +every reference resolves. `severity` is one of `high`, `medium`, or `low`. It +advises the reviewing agent but does not enforce a result. @@ -245,16 +239,15 @@ advice for the reviewing agent, not an enforcement level. ### Package a portable artifact: `export` -`export` packages the `.ghost/` package as a portable brand artifact: a tarball of -the `.ghost/` directory plus a stamped `export.yml` (schema, package id, CLI -version, export time). The events tape stays out of the archive; pass -`--no-checks` to leave the checks directory out too. +`export` creates a tarball of the `.ghost/` directory plus a stamped +`export.yml` with the schema, package id, CLI version, and export time. The +local log stays out of the archive. Pass `--no-checks` to omit the checks +directory. -The export also audits every node's `materials` entry and reports which ones -travel with the archive (bundled files and URLs) and which are **stranded**: -repo-relative paths that point outside the package and won't exist wherever -the archive lands. Stranded entries are reported, not fatal; pass `--strict` -to exit `2` when any exist. +The export audits every node's `materials` entries. Bundled files and URLs +travel with the archive. Repo-relative paths outside the package are +**stranded** because the destination may not contain them. Pass `--strict` to +exit `2` when any are stranded. `export` refuses to package a ghost package with validation errors. Run `ghost validate` and fix them first. diff --git a/apps/docs/src/content/docs/getting-started.mdx b/apps/docs/src/content/docs/getting-started.mdx index b241f3c5..6e96644f 100644 --- a/apps/docs/src/content/docs/getting-started.mdx +++ b/apps/docs/src/content/docs/getting-started.mdx @@ -1,6 +1,6 @@ --- title: Getting Started -description: Install ghost, write your brand guidance as prose, and give your agent the right context before it builds. +description: Install ghost, record one brand decision, and give it to your agent before the next task. kicker: Docs section: guide order: 10 @@ -30,9 +30,8 @@ Near the moment of payment, reduce felt risk. Keep reassurance close to the action; proximity beats completeness. Do not introduce a new visual system here. ``` -The file is the product: a decision checked into the repo where your agent can -read it before building. Brief the next checkout task from ghost and the review -comment becomes context the agent has up front. +The decision now lives in the repo. Brief the next checkout task from ghost so +the agent receives that context before starting work. One decision can steer checkout, a receipt, a decline message, or another surface where payment creates risk. Grow the ghost package one repeated decision at a @@ -59,10 +58,11 @@ Set up ghost for this repo. Brief this work from ghost. ``` -The skill tells the agent what to read, what to write, and how to gather. ghost -never calls an LLM itself, so there is no API key or agent lock-in. +The skill teaches the agent how to author the package, gather applicable +guidance, and use it while making. The CLI handles the repeatable work without +calling an LLM, so there is no API key or agent lock-in. -The packet is the product. The CLI carries it through the loop: +The commands support that agent workflow: ```bash ghost init # scaffold the .ghost package @@ -79,29 +79,24 @@ a diff. -Start with one decision, not the whole brand. The hardest part is picking the first -one, so pick the decision whose review feedback you keep repeating: the checkout -you flag for trust, the voice you re-tone, the empty state you -rewrite. Write that one down as a node, and grow the brand guidance -node by node. One well-grounded node beats an empty catalog. +Start with the decision whose review feedback you keep repeating: the checkout +you flag for trust, the voice you re-tone, or the empty state you rewrite. Write +it as one node, then add guidance when another repeated decision appears. -The CLI scaffolds; your agent authors: interviewing you, working with the -material you show it, drafting node prose, and asking you to curate the claims. -`ghost init` writes a starter you adapt: a glossary, a `brand.md` cover, -foundation chapters with open questions, and the model cliche floor. Answering -the questions is how the starter becomes your brand. Prefer a bare start? -`ghost init --template minimal` writes a smaller cover-led starter. The full -starter anatomy is in the [CLI reference](/docs/cli). +Your agent interviews you, works with the material you show it, drafts node +prose, and asks you to curate the claims. `ghost init` handles the repeatable +setup by writing a starter you adapt: a glossary, a `brand.md` cover, and +foundation nodes with open questions. Answer the questions with your brand's +decisions. Use `ghost init --template minimal` for a smaller starter. The +[CLI reference](/docs/cli) lists the generated files. ```bash ghost init ghost validate ``` -Prefer starting from a working brand instead of an empty anatomy? Install a -**body**, the same skeleton with a real brand's values plugged in: answered -dials, a `materials/` tree with tokens and fonts, worked reference -compositions, and its own checks: +To start from a worked package, install a **body**. It includes brand guidance, +a `materials/` tree with tokens and fonts, reference compositions, and checks: ```bash ghost init --body vessel-light @@ -129,10 +124,10 @@ applies in, never a filename destination. For a fuller human-agent workflow, rea -Before generating, gather Available guidance from the ghost package. -`gather` does no selection: it emits the complete, unfiltered, unranked list of -node ids, kinds, and descriptions. Your agent reads the ask against -applicability and pulls every node whose stated situation governs the work: +Before generating, ask your agent to gather guidance from the ghost package. +`gather` emits the complete, unfiltered, unranked list of node ids, kinds, and +descriptions. The agent reads the ask against that menu and pulls every node +whose stated situation governs the work: ```bash ghost gather "checkout settings" @@ -140,28 +135,23 @@ ghost gather "checkout settings" --format json ghost pull principle.trust voice # pull selected nodes' full bodies ``` -This is "the right context at the right time": the agent selects just-in-time -against the actual task. Topic overlap alone is not applicability; if nothing -applies, it should name the package's silence and avoid inventing -ghost-backed guidance. Run it before generation, so the agent builds with the -brand in hand rather than discovering the gaps in review. +The agent selects against the task before starting work. Topic overlap alone +does not make a node applicable. If no node applies, say that the package has +no guidance for the task. Do not invent ghost-backed guidance. -Each `gather` and `pull` also adds a line to a private local log at -`.ghost/.events`; it stays on your machine and never enters version control. -That one is for you: tweak a node's description, re-run the task, and `ghost -pulse` shows whether the node appeared on the menu, got pulled, was missed by -name, or stayed cold. +Each `gather` and `pull` adds a line to `.ghost/.events`, a local log ignored by +Git. Change a description, re-run the task, and use `ghost pulse` to see whether +the agent saw and selected the node. ```bash ghost pulse ``` -Read `pulse` as a tuning signal, not a score. A cold node may need a sharper -`description`; a repeated miss may mean the id or glossary vocabulary is not -what agents expect; an abandoned gather means the menu did not lead to a useful -selection. The log is local tuning signal, not canonical ghost package state. +Use `pulse` to tune retrieval. A cold node may need a sharper `description`; a +repeated miss may mean the id or glossary vocabulary differs from what agents +expect. An abandoned gather means the menu did not lead to a pull. @@ -180,18 +170,14 @@ An undeclared kind prefix is a warning with a "did you mean" suggestion, not a failure. Treat ghost package edits as ordinary Git-reviewed changes: uncommitted edits are drafts; checked-in nodes are canonical. -Hit something the loop above doesn't explain — an empty `gather`, a `review` -that refuses to run, a stale example? See -[Troubleshooting](/docs/troubleshooting). +See [Troubleshooting](/docs/troubleshooting) for an empty `gather`, a `review` +that refuses to run, or a stale example. - + -You can run the whole loop above without this section; your agent handles the -format. Read on when you want to understand how the folder of files is wired. - -The `.ghost/` package is a **flat set of prose nodes**: +A `.ghost/` package is a **flat set of prose nodes**: ```text .ghost/ @@ -205,7 +191,7 @@ The `.ghost/` package is a **flat set of prose nodes**: checks/ # optional review assertions; never a node ``` -There is no hierarchy, no inheritance, and no edges between nodes. The +Nodes do not form a hierarchy, inherit from one another, or carry edges. The optional `cover:` in `manifest.yml` may name any node; `ghost gather` inlines it before the menu. The default skeleton calls that node `brand`, but the filename is not reserved. A node's identity is its file path with `.md` dropped @@ -215,14 +201,14 @@ be a kind declared in `glossary.md`; a bare name (`voice.md`) has no kind. Folders are a human-browsing convenience only; the model reads a flat menu. -Altitude lives in the prose. Universal guidance is stated plainly. Narrower -guidance names the *situation* where it applies. The agent reads the condition -and decides whether to use the node. Reserved at the package root: `manifest.yml`, -`glossary.md`, `materials/`, and `checks/`; every other `*.md` is a node. +Write universal guidance without a condition. Narrower guidance names the +situation where it applies, which the agent uses to decide whether to select the +node. `manifest.yml`, `glossary.md`, `materials/`, and `checks/` are reserved at +the package root; every other `*.md` is a node. -A node may carry `materials`: repo-relative paths/globs or HTTPS -URLs for the concrete materials the prose governs. `ghost gather` emits the menu; -the agent selects and manifests. Checks are used by `ghost review`, never by +A node may carry `materials`: repo-relative paths/globs or HTTPS URLs for the +concrete materials the prose governs. `ghost gather` emits the menu; the agent +selects and applies the guidance. Checks are used by `ghost review`, never by generation. One contract per package: a repo's `.ghost/` is the whole ghost package. diff --git a/apps/docs/src/content/docs/troubleshooting.mdx b/apps/docs/src/content/docs/troubleshooting.mdx index b7c14456..6822d821 100644 --- a/apps/docs/src/content/docs/troubleshooting.mdx +++ b/apps/docs/src/content/docs/troubleshooting.mdx @@ -1,6 +1,6 @@ --- title: Troubleshooting -description: What to do when gather comes back empty, validate warns about a kind, review refuses to run, or a pull misses an id. +description: Fix empty gathers, validation warnings, review refusals, and unknown node ids. kicker: Docs section: guide order: 35 @@ -15,36 +15,32 @@ code before reading the message: | Code | Meaning | | --- | --- | | `0` | Success. | -| `1` | The command ran but the result is unhappy: `validate` found issues, or an unexpected error was thrown. | +| `1` | The command ran but reported problems: `validate` found issues, or an unexpected error was thrown. | | `2` | The command was called wrong: a bad flag or argument, a missing package for a command that needs one, a `pull` with no known ids, or `review` without a checks directory. | | `3` | A command-specific refusal, currently only `skill install` when a skill is already present (pass `--force`). | -The sections below are the specific failures behind those codes, and what to -do about each. +Use the sections below to diagnose each code. -`gather` does no filtering or ranking; it always emits the whole guidance menu. +`gather` emits the complete guidance menu without filtering or ranking. If your agent reads the menu and pulls nothing useful, the problem may be retrieval, applicability, or true package silence. -- **Check the description, not the body.** An agent selects against a node's - `description` alone. A vague description (`Our trust principles.`) is - invisible at selection time no matter how good the prose underneath is. - Rewrite it as a task-shaped phrase: what the node governs, what observable - condition makes it apply, and what it contributes where useful. -- **Check `ghost pulse`.** It shows whether a node appeared on the menu, got - pulled, was missed by an id the agent tried and failed, or stayed cold - every time. A cold node with real content is a description problem, not a - content problem. -- **Don't add more nodes to fix a retrieval miss.** A weak description with a - strong body is worse than a strong description with a thin body; the agent - never reaches the body. -- **Respect silence.** If no description indicates an applicable situation, name - the package's silence, follow the cover silence posture, and do not claim - provisional local reasoning as ghost-backed. +- **Check the description.** An agent selects against a node's `description`. + A vague description (`Our trust principles.`) gives it no applicable + situation. Rewrite the description to name what the node governs, when it + applies, and what it contributes. +- **Check `ghost pulse`.** It shows whether the agent saw and selected a node or + tried an unknown id. A node that appears but stays unselected may need a + sharper description. +- **Sharpen the existing description.** The agent never reaches a body when its + description does not make the node applicable. +- **Handle missing guidance.** If no description indicates an applicable + situation, say that the package has no guidance for the task. Follow any + instructions in the cover, and label local reasoning as provisional. ```bash ghost gather "checkout settings" --format json @@ -58,14 +54,15 @@ ghost pulse A node's kind is the filename prefix before the first dot (`principle.trust.md` → kind `principle`), and it must be declared in `glossary.md`. An undeclared kind is a warning with a "did you mean" -suggestion, not a failure — `validate` still exits `0` unless something else +suggestion, not a failure; `validate` still exits `0` unless something else is wrong. - If the suggestion is right, it's usually a typo in the filename (`pinciple.trust.md`) or a stale glossary that never picked up a kind you started using. - If you meant to introduce a new kind, add it to `glossary.md`'s `kinds` - list along with a `#` section explaining its meaning and normative weight. + list along with a `#` section explaining what it means and how an agent should + treat it. Declaring a kind with one user is fine; ghost ships no fixed vocabulary. - If the file was never meant to carry a kind, drop the dotted prefix; a bare name (`voice.md`) has no kind and needs no glossary entry. @@ -85,9 +82,9 @@ silently emitting an empty packet: ghost checks init ``` -If checks already exist and `review` still exits oddly, check that you're -running it against a real diff — with no base and no staged/unstaged changes, -there's nothing to match against materials. Point it explicitly: +If checks already exist, confirm that `review` has a diff to read. With no base +and no staged or unstaged changes, it has no files to match against materials. +Pass a base or a diff: ```bash ghost review --base main @@ -114,17 +111,13 @@ closest-id hints, and the miss is recorded for `ghost pulse`. A pull with `materials` locators are repo-relative paths/globs or absolute HTTPS URLs. -When a path moves or a component gets deleted, the node still validates (path -resolution is checked, but drift after a refactor can slip through between -validate runs) and `ghost review` will simply stop matching that node to the -files that touch it. - -- Run `ghost export --strict` periodically; it audits every `materials` entry - and fails on any repo-relative path that resolves outside the package or - doesn't exist, which is exactly the "stranded" case a refactor produces. -- If `ghost review`'s coverage gaps list a touched file no node claims, that's - the same signal at review time: either the file needs a `materials` owner, - or it's genuinely out of scope. +After a path moves or a component is deleted, `ghost review` stops matching the +node to changed files. A validation run catches dead local paths. + +- Run `ghost export --strict` to find repo-relative paths that resolve outside + the package or no longer exist. +- If review coverage lists a touched file no node claims, decide whether to add + it to a node's `materials`. ```bash ghost export --strict @@ -134,9 +127,8 @@ ghost export --strict -The CLI reference and its `--help` output are generated from the CLI source, -not hand-maintained. If you changed a command or flag and the docs didn't -follow: +The CLI source generates the reference and `--help` output. After changing a +command or flag, run: ```bash pnpm dump:cli-help @@ -149,23 +141,19 @@ drift check declines a PR that changes a command surface without this step. -Uncommitted or unmerged ghost package edits are drafts; ghost's tooling reads -whatever is on disk in your working tree, so a draft node does show up in -`gather` immediately. What it means for edits to "take effect" is usually one -of: +ghost commands read the files in your working tree, so uncommitted changes +appear in `gather`. -- **The agent already had a stale menu in context.** Re-run `ghost gather` - after any edit; nothing caches the menu between calls. -- **The team disagrees on what's canonical.** Ordinary Git review is the - approval boundary for ghost package edits, same as any other code change. - Treat an unmerged branch's nodes as provisional until they land. +- **The agent has an old menu in context.** Re-run `ghost gather` after editing + the package. +- **The team disagrees on the approved guidance.** Treat unmerged nodes as + drafts. Git review determines which changes become canonical. -If none of the above matches what you're seeing, the fastest path is usually -to ask your agent directly, since it already has the skill bundle installed: +Ask your agent to diagnose the package with the installed skill: ```text Run ghost validate and ghost pulse, and tell me what's wrong with this ghost package. diff --git a/packages/ghost/README.md b/packages/ghost/README.md index 600bd9b3..b260d18a 100644 --- a/packages/ghost/README.md +++ b/packages/ghost/README.md @@ -1,13 +1,12 @@ # @design-intelligence/ghost -**ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose -guidance (your stance, your voice, your trust moves) checked into the repo and -read by any agent before it makes anything: a screen, an email, an empty -state, a sentence. +Use ghost to give agents applicable brand guidance before they start work. A +`.ghost/` package stores your stance, voice, trust moves, and concrete materials +in the repo. Agents select and read that guidance while working on a screen, +email, empty state, or sentence. -Today those decisions live in reviewers' heads: "that's not our voice," again, -on every surface. The agent that built the thing never saw them. ghost writes -them down once, where the agent looks first. +Reviewers repeat the same feedback on every surface: "that's not our voice." +Write the decision in `.ghost/` so the next agent has it before starting work. [Documentation](https://block.github.io/ghost/) · [Repo](https://github.com/block/ghost) @@ -32,22 +31,29 @@ Brief this work from the ghost package. Review this diff against the ghost checks. ``` -ghost never calls an LLM itself; your agent does the thinking. No API key, -no lock-in. +Your agent decides what applies and interprets the guidance. The CLI handles +repeatable work without calling an LLM, so ghost needs no API key and does not +lock you into one agent. -## The Loop +## Use Guidance While Making + +Your agent works with the package through a small set of commands: ```bash ghost init # scaffold .ghost/ with the skeleton starter ghost checks init # opt in to review assertions ghost validate # make sure the package is well-formed -ghost gather # before building: list all available guidance +ghost gather [ask] # before building: show the complete guidance menu ghost pull # read the picked nodes' full bodies ghost review # during review: match a diff to guidance and checks ghost export # bundle the guidance as a portable artifact ghost pulse # while tuning: see what agents reached for ``` +For a task-specific gather, your agent reads the complete, unfiltered menu and +pulls every node whose stated situation applies. Bare `ghost gather` inspects +the catalog without grounding a task. + Run `ghost --help` for the core workflow and `ghost --help` for flags. The [CLI reference](https://block.github.io/ghost/docs/cli) covers every command; [Getting Started](https://block.github.io/ghost/docs/getting-started) diff --git a/packages/vessel-light/README.md b/packages/vessel-light/README.md index bfe78552..0e1696ae 100644 --- a/packages/vessel-light/README.md +++ b/packages/vessel-light/README.md @@ -1,6 +1,6 @@ # vessel-light -vessel-light is Vessel's design language as a locked-and-loaded `.ghost/` package for agents writing raw HTML/CSS. No install. No build. No dependencies. Copy the package into a repo, hand it to `ghost`, and make the agent style with the same tokens, primitives, and worked compositions Vessel uses. +vessel-light packages Vessel's design language for agents writing raw HTML and CSS. It has no install, build, or dependencies. Copy the `.ghost/` package into a repo so your agent can select and apply Vessel's guidance, tokens, primitives, and worked compositions. ## Copy