From 43eb0052937b228fcb6358fd7d405b399d08f07d Mon Sep 17 00:00:00 2001 From: Nahiyan Khan Date: Mon, 27 Apr 2026 12:44:33 -0400 Subject: [PATCH] docs: align docs site IA with five-tool decomposition Rewrites the cross-cutting docs to match the post-PR-#64 reality where Ghost is five tools (ghost-map, ghost-expression, ghost-drift, ghost-fleet, ghost-ui) instead of one. CLI verbs are scoped to the tool that owns the artifact; per-tool skill bundles replace the single ghost-drift bundle. Content: - CLAUDE.md, root README, and ghost-drift README rewritten around the five-tool surface; new packages/ghost-expression/README for the intended-public sibling. - docs/generation-loop.md updates context-bundle invocations to ghost-expression; expression-format.md drops the dropped-from-scope generate recipe. Docs site: - /tools is now a five-card index. Each tool gets a /tools/ landing (hooking ghost-ui through to its existing /ui catalogue). - Cross-tool guides (Getting Started, CLI Reference) move from /tools/drift/* to /docs/* via a new "guide" frontmatter section. - Old /tools/drift/{getting-started,cli} URLs redirect to /docs/*. - Dock command palette restructured into Tools + Docs groups. - Home thesis softened from drift-centric framing; teases the elevation lens / world-model concept that ghost-fleet operationalizes. Tooling: - scripts/dump-cli-help.mjs walks all four CLIs (drift, expression, map, fleet); manifest now keyed by tool with each command carrying its tool prefix. - takes a `tool` prop and renders the correct prefix. - check-docs-frontmatter accepts the new "guide" section and updated known-routes list. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/drift-readme-post-decompose.md | 5 + CLAUDE.md | 144 +++-- README.md | 165 +++-- apps/docs/src/App.tsx | 37 +- apps/docs/src/app/docs/page.tsx | 28 +- apps/docs/src/app/docs/workflow/page.tsx | 23 +- apps/docs/src/app/page.tsx | 17 +- apps/docs/src/app/tools/drift/page.tsx | 81 +++ apps/docs/src/app/tools/expression/page.tsx | 81 +++ apps/docs/src/app/tools/fleet/page.tsx | 81 +++ apps/docs/src/app/tools/map/page.tsx | 81 +++ apps/docs/src/app/tools/page.tsx | 44 +- apps/docs/src/app/tools/ui/page.tsx | 84 +++ apps/docs/src/components/docs/cli-help.tsx | 33 +- apps/docs/src/components/docs/dock.tsx | 49 +- apps/docs/src/content/docs-frontmatter.ts | 9 +- apps/docs/src/content/docs/cli-reference.mdx | 270 +++++--- .../docs/src/content/docs/getting-started.mdx | 140 +++-- apps/docs/src/generated/cli-manifest.json | 592 +++++++++++++----- docs/expression-format.md | 2 +- docs/generation-loop.md | 63 +- packages/ghost-drift/README.md | 61 +- packages/ghost-expression/README.md | 83 +++ scripts/check-docs-frontmatter.mjs | 15 +- scripts/dump-cli-help.mjs | 84 +-- 25 files changed, 1708 insertions(+), 564 deletions(-) create mode 100644 .changeset/drift-readme-post-decompose.md create mode 100644 apps/docs/src/app/tools/drift/page.tsx create mode 100644 apps/docs/src/app/tools/expression/page.tsx create mode 100644 apps/docs/src/app/tools/fleet/page.tsx create mode 100644 apps/docs/src/app/tools/map/page.tsx create mode 100644 apps/docs/src/app/tools/ui/page.tsx create mode 100644 packages/ghost-expression/README.md diff --git a/.changeset/drift-readme-post-decompose.md b/.changeset/drift-readme-post-decompose.md new file mode 100644 index 0000000..5f8a15e --- /dev/null +++ b/.changeset/drift-readme-post-decompose.md @@ -0,0 +1,5 @@ +--- +"ghost-drift": patch +--- + +Rewrite README to reflect the five-tool decomposition: drift now lists five verbs (compare, ack, track, diverge, emit skill) and points users at `ghost-expression` for the moved authoring verbs (lint, describe, diff, emit review-command, emit context-bundle). diff --git a/CLAUDE.md b/CLAUDE.md index b09afaf..beef989 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,32 +6,36 @@ ```bash pnpm install # install dependencies (pnpm 10+, Node 18+) -pnpm build # build all packages (tsc --build) +pnpm build # build all packages (tsc --build, copies skill-bundle to dist) ``` -Run the CLI after building: +Run any tool's CLI after building: ```bash node packages/ghost-drift/dist/bin.js -# or -pnpm --filter ghost-drift exec ghost +node packages/ghost-expression/dist/bin.js +node packages/ghost-map/dist/bin.js +node packages/ghost-fleet/dist/bin.js +# or via the workspace +pnpm --filter ghost-drift exec ghost-drift +pnpm --filter ghost-expression exec ghost-expression ``` ## Environment Variables -Ghost's CLI is deterministic — no API key required for any verb. +Every CLI verb across every Ghost tool is deterministic — no API key required. -- `OPENAI_API_KEY` / `VOYAGE_API_KEY` — optional, consumed only by `computeSemanticEmbedding` (library function; used when a host writes an expression.md and wants an enriched 49-dim vector for paraphrase-robust comparison). -- `GITHUB_TOKEN` — optional, for `resolveTrackedExpression` fetching a tracked expression from GitHub (avoids rate limits). +- `OPENAI_API_KEY` / `VOYAGE_API_KEY` — optional, consumed only by `computeSemanticEmbedding` (library function in `@ghost/core`; used when a host writes an `expression.md` and wants an enriched 49-dim vector for paraphrase-robust comparison). +- `GITHUB_TOKEN` — optional, used by `resolveTrackedExpression` when fetching a tracked expression from GitHub (avoids rate limits). -The CLI auto-loads `.env` and `.env.local` from the working directory. +Each CLI auto-loads `.env` and `.env.local` from the working directory. ## Test & Lint ```bash -pnpm test # vitest run +pnpm test # vitest run (across all packages) pnpm test:watch # vitest watch mode -pnpm check # biome check + typecheck + file-size check +pnpm check # biome check + typecheck + file-size check + cli-manifest drift check pnpm fmt # biome format --write pnpm lint # biome lint ``` @@ -45,53 +49,77 @@ Run `just` to list all recipes. Key ones: `setup`, `build`, `check`, `fmt`, `tes ## Architecture -Ghost is **BYOA (bring-your-own-agent)**. The CLI is a set of **deterministic primitives**. It never calls an LLM. Judgement work (profile, review, verify, generate, discover) belongs to the host agent harness (Claude Code, Codex, Cursor, Goose, etc.), which drives the primitives via an [agentskills.io](https://agentskills.io)-compatible skill bundle. Ghost ships that bundle via `ghost-drift emit skill`. +Ghost is **BYOA (bring-your-own-agent)**. Every CLI is a set of **deterministic primitives** — none of them call an LLM. Judgement work (profile, review, verify, remediate) lives in [agentskills.io](https://agentskills.io)-compatible skill bundles the host agent (Claude Code, Codex, Cursor, Goose, …) executes. -Engine layout (lives under `packages/ghost-drift/src/core/`): +The repo decomposes into **five tools plus a reference design system**, each with a single responsibility: -- `core/compare.ts` — embedding-based comparison (pairwise + composite) -- `core/embedding/` — 49-dim vector computation, optional semantic embedding via OpenAI/Voyage -- `core/expression/` — parse/compose/diff/lint `expression.md` -- `core/evolution/` — history, ack manifest, composite analysis, tracked-expression resolution -- `core/context/` — artifact generators (review-command, context-bundle, tokens.css) -- `core/reporters/` — output formatters for compare/composite/temporal/expression +``` +@ghost/core library only — embedding math, types, target resolver, skill loader +ghost-map topology (map.md) — inventory, lint +ghost-expression authoring (expression.md) — lint, describe, diff, emit +ghost-drift drift (.ghost/*) — compare, ack, track, diverge, emit skill +ghost-fleet elevation (fleet.md) — members, view, emit skill +ghost-ui reference design system — 97 shadcn components + MCP server +``` + +Dependency flow: `@ghost/core` ← everyone. `ghost-expression` ← `ghost-drift`, `ghost-fleet`. `ghost-map` ← `ghost-fleet`. No cycles. -CLI glue sits alongside under `packages/ghost-drift/src/` (`bin.ts`, `cli.ts`, `emit-command.ts`, `evolution-commands.ts`, `target-resolver.ts`, `skill-bundle.ts`). The `./core/index.js` barrel is the single library export; `./cli` is the CLI subpath export. +Each tool lives under `packages//` with the same shape: -What was removed in the BYOA migration: the Claude Agent SDK profiling loop (`src/agents/`), the LLM-driven review pipeline (`src/review/`), the LLM generate/verify loops (`src/generate/`, `src/verify/`), the Anthropic/OpenAI provider plumbing (`src/llm/`), and the GitHub Action that wrapped them. Profile, review, verify, generate, and discover are now skill recipes the host agent executes. +- `src/bin.ts` — shebang entry +- `src/cli.ts` — `buildCli()` builder (cac) +- `src/core/` — deterministic library surface, public via `src/core/index.ts` +- `src/skill-bundle/` — `SKILL.md` + `references/*.md` (only tools that ship recipes) +- `test/` — vitest, with `test/fixtures/` for sample data ## Packages | Package | Published? | Description | |---------|-----------|-------------| -| `packages/ghost-drift` | ✅ `ghost-drift` on npm | Merged engine + CLI — deterministic primitives (compare, embedding, expression parse/lint, evolution, reporters) plus the cac-based CLI and the `ghost-drift` agentskills.io skill bundle under `src/skill-bundle/` | +| `packages/ghost-core` | ❌ private (`@ghost/core`) | Workspace-only library. Embedding math, shared types, target resolution, skill-bundle loader. No CLI. Consumed by every other tool. | +| `packages/ghost-drift` | ✅ `ghost-drift` on npm (v0.2+) | Drift detection. CLI verbs: `compare`, `ack`, `track`, `diverge`, `emit skill`. Skill recipes: `compare.md`, `review.md`, `verify.md`, `remediate.md`. Old `lint`/`describe`/`emit review-command`/`emit context-bundle` stay registered as stub commands that point users to `ghost-expression`. | +| `packages/ghost-expression` | ✅ intended-public (`publishConfig.access: public`, currently v0.0.0) | Authoring & validating `expression.md`. CLI verbs: `lint`, `describe`, `diff`, `emit` (kinds: `review-command`, `context-bundle`, `skill`). Skill recipes: `profile.md`, `schema.md`. Owns the canonical artifact. | +| `packages/ghost-map` | ❌ private (currently) | Generates `map.md` — the navigation card every Ghost tool reads. CLI verbs: `inventory` (raw signals as JSON), `lint`. Will eventually publish; gated on the `describe` and `emit skill` follow-ups. | +| `packages/ghost-fleet` | ❌ private | Read-only elevation across many `(map.md, expression.md)` members. CLI verbs: `members`, `view`, `emit skill`. Skill recipes: `target.md`. | | `packages/ghost-ui` | ❌ private | Reference component library — 49 UI primitives + 48 AI elements + theme + hooks, distributed via the shadcn `registry.json`, not npm. Also ships the `ghost-mcp` bin (`src/mcp/`, built via `tsconfig.mcp.json` → `dist-mcp/`) — an MCP server re-exposing the registry to AI assistants (5 tools, 2 resources). | | `apps/docs` | ❌ private | The deployed docs site (`ghost-docs`) — home, drift tooling docs, design language foundations, live component catalogue. Consumes `ghost-ui`. | ## CLI Commands -Seven deterministic primitives. Everything else (profile, review, verify, generate, discover) is a skill recipe the host agent executes. - -| Command | Description | -|---------|-------------| -| `ghost-drift compare [...expressions]` | Pairwise (N=2) or composite (N≥3: pairwise matrix, centroid, clusters) over expression embeddings. `--semantic`, `--temporal`. | -| `ghost-drift lint [expression.md]` | Validate schema + body/frontmatter coherence | -| `ghost-drift describe [expression.md]` | Print section ranges and token estimates for selective loading | -| `ghost-drift ack` | Acknowledge drift; records stance in `.ghost-sync.json` (reads local `expression.md`) | -| `ghost-drift track ` | Track another expression as this repo's reference | -| `ghost-drift diverge ` | Declare intentional divergence on a dimension | -| `ghost-drift emit ` | Derive artifacts from `expression.md` — `review-command`, `context-bundle`, or `skill` (the agentskills.io bundle). Run `ghost-drift emit skill` to install the `ghost-drift` skill into your host agent. | - -**Workflows the CLI does not do** — these are recipes the host agent follows (all under `packages/ghost-drift/src/skill-bundle/references/`): -- **Profile** (write `expression.md` from a project) — `profile.md` -- **Review** (flag drift in PR changes) — `review.md` -- **Verify** (generate → review loop) — `verify.md` -- **Generate** (produce UI from expression) — `generate.md` -- **Discover** (find public design languages) — `discover.md` +Verbs are scoped to the tool that owns the artifact. The full surface across all four tools: + +| Tool | Command | Description | +|------|---------|-------------| +| `ghost-map` | `inventory [path]` | Emit deterministic raw repo signals (manifests, language histogram, registry, top-level tree, git remote) as JSON. | +| `ghost-map` | `lint [map]` | Validate `map.md` against `ghost.map/v1`. | +| `ghost-expression` | `lint [expression]` | Validate `expression.md` schema + body/frontmatter coherence. | +| `ghost-expression` | `describe [expression]` | Print section ranges + token estimates (so agents can selectively load). | +| `ghost-expression` | `diff ` | Structural prose-level diff (decisions + palette roles). **Not** vector distance. | +| `ghost-expression` | `emit ` | Derive an artifact from `expression.md`: `review-command`, `context-bundle`, or `skill`. | +| `ghost-drift` | `compare [...expressions]` | Pairwise (N=2) or composite (N≥3) over expression embeddings. `--semantic`, `--temporal`. | +| `ghost-drift` | `ack` | Record a stance toward the tracked expression in `.ghost-sync.json`. | +| `ghost-drift` | `track ` | Shift the tracked expression. | +| `ghost-drift` | `diverge ` | Declare intentional divergence on a dimension. | +| `ghost-drift` | `emit skill` | Install the `ghost-drift` agentskills.io bundle. | +| `ghost-fleet` | `members [dir]` | List registered fleet members + freshness. | +| `ghost-fleet` | `view [dir]` | Compute pairwise distances + group-by tables; emit `fleet.md` + `fleet.json`. | +| `ghost-fleet` | `emit skill` | Install the `ghost-fleet` agentskills.io bundle. | + +**Workflows the CLI does not do** — these are recipes the host agent follows. Each tool ships its own under `packages//src/skill-bundle/references/`: + +- **Profile** (write `expression.md` from a project) — `ghost-expression/.../profile.md` +- **Map** (write `map.md` from a repo) — `ghost-map/.../map.md` +- **Review** (flag drift in PR changes) — `ghost-drift/.../review.md` +- **Verify** (generate → review loop) — `ghost-drift/.../verify.md` +- **Compare interpretation** — `ghost-drift/.../compare.md` +- **Remediate** (suggest minimal fixes for drift) — `ghost-drift/.../remediate.md` +- **Fleet narrative** (synthesize `fleet.md` prose from CLI output) — `ghost-fleet/.../target.md` + +`discover.md` and `generate.md` are dropped from scope in the decomposition (per `docs/ideas/phase-0-decisions.md`); they are not migrated to any tool. ## Target Types -The `resolveTarget()` function in `packages/ghost-drift/src/core/config.ts` accepts: +The `resolveTarget()` function in `@ghost/core` (`packages/ghost-core/src/target-resolver.ts`) accepts: - `github:owner/repo` — GitHub repository - `npm:package-name` — npm package @@ -100,17 +128,22 @@ The `resolveTarget()` function in `packages/ghost-drift/src/core/config.ts` acce - `https://...` — URL - `.` — current directory -Used by `resolveTrackedExpression` (tracked expression resolution) and legacy library consumers. The profile flow itself no longer consumes targets — the host agent explores whatever directory is relevant. +Used by `resolveTrackedExpression` (in `ghost-drift`) and legacy library consumers. Profile and map flows don't consume targets directly — the host agent explores whatever directory is relevant. -## Expression format +## Canonical artifacts -The canonical expression artifact is **`expression.md`** — a human-readable, LLM-editable Markdown file with YAML frontmatter (machine layer) and a three-section prose body (Character → Signature → Decisions). See `docs/expression-format.md` for the full spec; a condensed reference ships inside the skill bundle at `packages/ghost-drift/src/skill-bundle/references/schema.md`. +Two canonical Markdown artifacts, each owned by one tool: + +- **`expression.md`** — the design language. Owned by `ghost-expression`. Human-readable, LLM-editable, with YAML frontmatter (machine layer: 49-dim embedding + palette/spacing/typography/surfaces/roles) and a three-section prose body (Character → Signature → Decisions). See `docs/expression-format.md` for the full spec; the condensed reference ships at `packages/ghost-expression/src/skill-bundle/references/schema.md`. +- **`map.md`** — the topology card. Owned by `ghost-map`. Human-readable answer to "where is the design system, which folders matter?" Schema is `ghost.map/v1`, validated by `ghost-map lint`. The condensed reference ships at `packages/ghost-map/src/skill-bundle/references/schema.md`. The repo's own `map.md` lives at the root. ## Releasing & Changesets -`ghost-drift` is the single published package. Releases go through [Changesets](https://github.com/changesets/changesets); the `.github/workflows/release.yml` workflow opens a "Version Packages" PR whenever pending changesets are on `main`, and publishes to npm when that PR merges. +`ghost-drift` is the only currently-published package. `ghost-expression` is set up to publish (`publishConfig.access: public`); `ghost-map` and `ghost-fleet` are private workspace-only for now. Releases go through [Changesets](https://github.com/changesets/changesets); the `.github/workflows/release.yml` workflow opens a "Version Packages" PR whenever pending changesets are on `main`, and publishes to npm when that PR merges. + +The Changesets config ignores private packages (`@ghost/core`, `ghost-fleet`, `ghost-map`, `ghost-ui`, `apps/docs`) — they don't appear in version PRs. -**When you (the agent) complete a user-visible change to `packages/ghost-drift/`, write a changeset file yourself instead of asking the user to run `pnpm changeset`.** Create `.changeset/.md` with this shape: +**When you (the agent) complete a user-visible change to a published package, write a changeset file yourself instead of asking the user to run `pnpm changeset`.** Create `.changeset/.md` with this shape: ```markdown --- @@ -120,19 +153,30 @@ The canonical expression artifact is **`expression.md`** — a human-readable, L One sentence, user-facing, present tense. What changed from the user's POV — not "refactor the X module." ``` +Multiple packages can be bumped in one changeset: + +```markdown +--- +"ghost-drift": patch +"ghost-expression": minor +--- +``` + Guidance on the bump level: - **`patch`** — bug fixes, doc fixes, non-breaking internal refactors. The default; when in doubt, pick this. - **`minor`** — new CLI verb, new flag, new library export, new capability. Anything a user might want to reach for. - **`major`** — removed/renamed CLI verb, removed/renamed library export, changed default behavior, breaking expression schema change, changed exit codes. **Always flag this explicitly in the PR description and ask the user to confirm — do not `major`-bump unreviewed.** -Skip the changeset entirely for: CI/workflow-only changes, test-only changes, changes scoped to `packages/ghost-ui` or `apps/docs` (both private — not published). The Changesets config ignores those packages. +Skip the changeset entirely for: CI/workflow-only changes, test-only changes, changes scoped to private packages. The slug should be short and descriptive: `add-temporal-flag.md`, `fix-palette-lint-crash.md`. Avoid dates or PR numbers — Changesets consumes and deletes the file at version time. ## Key Conventions -- Each expression carries a 49-dimensional embedding vector (palette [0–20], spacing [21–30], typography [31–40], surfaces [41–48]; see `packages/ghost-drift/src/core/embedding/embedding.ts`). The canonical on-disk form is `expression.md`. -- `compare` takes **file paths** to `expression.md`, not target strings. Mode auto-detects from N and flags: `--semantic` / `--temporal` require N=2; N≥3 returns a composite expression. -- `ack` / `track` / `diverge` read the local `expression.md`. The host agent is responsible for regenerating `expression.md` (via the profile recipe) before acknowledging drift. -- `lint` takes a single expression.md and reports schema/partition violations. Use as the success gate when writing an expression. +- Each `expression.md` carries a 49-dimensional embedding vector (palette [0–20], spacing [21–30], typography [31–40], surfaces [41–48]; see `packages/ghost-core/src/embedding/embedding.ts`). The canonical on-disk form is the Markdown file itself — there is no parallel JSON/DTCG representation (see [`INVARIANTS.md`](./INVARIANTS.md) §2). +- `ghost-drift compare` takes **file paths** to `expression.md`, not target strings. Mode auto-detects from N and flags: `--semantic` / `--temporal` require N=2; N≥3 returns a composite expression. +- `ghost-drift ack` / `track` / `diverge` read the local `expression.md`. The host agent is responsible for regenerating `expression.md` (via the `profile` recipe) before acknowledging drift. +- `ghost-expression lint` takes a single `expression.md` and reports schema/partition violations. Use as the success gate when authoring an expression. +- `ghost-map lint` takes a single `map.md` and validates against `ghost.map/v1`. Use as the success gate when authoring a map. +- The CLI manifest at `apps/docs/src/generated/cli-manifest.json` is auto-generated by `pnpm dump:cli-help`. CI guards drift via `pnpm check:cli-manifest`. Re-run `pnpm dump:cli-help` after adding/removing flags or verbs to any tool. diff --git a/README.md b/README.md index 09956c7..f645f99 100644 --- a/README.md +++ b/README.md @@ -4,38 +4,56 @@ AI is becoming the primary author of shipped code. Humans sit in fewer diffs; the harness (guardrails, reviewers, verifiers) catches drift before it lands. In that world, ensuring every generation reflects a brand's voice is paramount. Fonts and spacing are the easy half. The hard half is character: the posture a product takes, what it refuses to do. That's where generations drift first. -Ghost closes that loop. It captures a brand as an **expression**: a human-readable `expression.md` encoding character, signature traits, and concrete decisions. It gives any agent the primitives to author against it, detect drift the moment it happens, and record the right stance: **acknowledge**, **track**, or **intentionally diverge**. Each repo owns its expression, its trajectory, and its stance. The fleet of expressions drifts in the open. Nothing gets enforced; nothing drifts silently. Deterministic arithmetic lives in Ghost's CLI; judgment lives in whatever agent you already use. +Ghost closes that loop. It captures a brand as an **expression**: a human-readable `expression.md` encoding character, signature traits, and concrete decisions. It gives any agent the primitives to author against it, detect drift the moment it happens, and record the right stance: **acknowledge**, **track**, or **intentionally diverge**. Each repo owns its expression, its trajectory, and its stance. The fleet of expressions drifts in the open. Nothing gets enforced; nothing drifts silently. Deterministic arithmetic lives in Ghost's CLIs; judgment lives in whatever agent you already use. ## BYOA: bring your own agent Ghost splits the work the way agents need it split: **judgement in the agent, arithmetic in the CLI**. -- **The CLI**: a set of **deterministic primitives**. Seven verbs. It never calls an LLM. It does vector distance, schema validation, and manifest writes. Same answer every time. -- **A skill bundle**: [agentskills.io](https://agentskills.io)-compatible recipes for the interpretive work (profile, review, verify, generate, discover). The host agent (Claude Code, Codex, Cursor, Goose, …) runs the recipes and calls the CLI for the arithmetic. +- **The CLIs**: a set of **deterministic primitives** distributed across five small tools. None of them ever call an LLM. They do vector distance, schema validation, manifest writes. Same answer every time. +- **The skill bundles**: [agentskills.io](https://agentskills.io)-compatible recipes for the interpretive work (profile, review, verify, remediate, fleet narrative). Each tool ships its own; the host agent (Claude Code, Codex, Cursor, Goose, …) runs the recipes and calls the relevant CLI for the arithmetic. -No API key is required to use any CLI verb. Judgment work lives in whichever agent you already use; `ghost-drift emit skill` installs the recipes there. +No API key is required to use any CLI verb. Judgment work lives in whichever agent you already use; each tool's `emit skill` verb installs the recipes there. + +## The five tools + +Ghost is split into one responsibility per tool, around two canonical Markdown artifacts. + +| Tool | Owns | Verbs | +| --- | --- | --- | +| **`ghost-map`** | `map.md` — the topology card answering "where is the design system, which folders matter?" | `inventory`, `lint` | +| **`ghost-expression`** | `expression.md` — the canonical design language artifact | `lint`, `describe`, `diff`, `emit` | +| **`ghost-drift`** | `.ghost/history.jsonl` + `.ghost-sync.json` — drift detection and stance | `compare`, `ack`, `track`, `diverge`, `emit skill` | +| **`ghost-fleet`** | `fleet.md` — read-only elevation across many `(map.md, expression.md)` members | `members`, `view`, `emit skill` | +| **`ghost-ui`** | A reference design system Ghost dogfoods — 97 shadcn components + an MCP server | (no verbs) | + +`@ghost/core` underneath is a workspace-only library with the embedding math, target resolver, and skill-bundle loader the four CLIs share. ## Why Ghost? Ghost gives agents four capabilities the design-at-scale problem actually needs: -- **Author against a real quality bar**: `ghost-drift emit context-bundle` and the `generate` recipe turn a design language into grounding an agent can actually follow. The expression is the bar; the agent authors to it. -- **Self-govern at author time**: the `review` and `verify` recipes run an agent's output against the expression *before* a human sees it. Drift gets caught where it's cheap to fix, not after it ships. -- **Detect drift at the right time**: PR-time (via `review`), generation-time (via `verify`), or org-time (via `compare` on N≥3 expressions — the composite view). Timing is load-bearing: the same drift surfaced a month later is noise; surfaced inline, it's action. +- **Author against a real quality bar**: `ghost-expression emit context-bundle` and the `generate` recipe turn a design language into grounding an agent can actually follow. The expression is the bar; the agent authors to it. +- **Self-govern at author time**: the `review` and `verify` recipes (in the `ghost-drift` skill bundle) run an agent's output against the expression *before* a human sees it. Drift gets caught where it's cheap to fix, not after it ships. +- **Detect drift at the right time**: PR-time (via `review`), generation-time (via `verify`), or org-time (via `ghost-drift compare` on N≥3 expressions, or `ghost-fleet view` for the full elevation). Timing is load-bearing: the same drift surfaced a month later is noise; surfaced inline, it's action. - **Remediate with structured intent**: `ack`, `track`, `diverge` are the three moves. Every stance is published with reasoning and full lineage. Drift without intent is noise; drift with intent becomes useful evidence. -- **Human-readable, diff-friendly**: `expression.md` is Markdown with YAML frontmatter (machine layer) plus a three-layer prose body (Character, Signature, Decisions). Humans read it, agents consume it, deterministic tools diff it. No DSL to learn. +- **Human-readable, diff-friendly**: `expression.md` is Markdown with YAML frontmatter (machine layer) plus a three-layer prose body (Character, Signature, Decisions). `map.md` is the same shape for topology. Humans read them, agents consume them, deterministic tools diff them. No DSL to learn. ## Repo layout -Ghost is a monorepo. One main tool, one reference design system, one docs site — with room for more tools to land alongside `ghost-drift` over time. +Ghost is a pnpm monorepo. Five tools, one reference design system, one docs site. -| Path | Role | -| ---- | ---- | -| [`packages/ghost-drift`](./packages/ghost-drift) | **Main tool.** The deterministic CLI and skill bundle. The only published package (`ghost-drift` on npm). | -| [`packages/ghost-ui`](./packages/ghost-ui) | **Reference design system.** 97 components distributed via a shadcn registry. Also ships the `ghost-mcp` bin — an MCP server that re-exposes the registry to AI assistants. The system Ghost dogfoods its expression against. Private. | -| [`apps/docs`](./apps/docs) | **Docs site.** `ghost-docs`, the deployed documentation for the project. Consumes `ghost-ui`. Private. | +| Path | Role | Published? | +| ---- | ---- | --- | +| [`packages/ghost-core`](./packages/ghost-core) | Workspace-only shared library — embedding math, types, target resolver, skill loader. | ❌ private (`@ghost/core`) | +| [`packages/ghost-map`](./packages/ghost-map) | `map.md` topology generator + linter. | ❌ private (today) | +| [`packages/ghost-expression`](./packages/ghost-expression) | `expression.md` authoring + emit pipeline. | ✅ intended-public on npm | +| [`packages/ghost-drift`](./packages/ghost-drift) | Drift detection + governance verbs. | ✅ `ghost-drift` on npm | +| [`packages/ghost-fleet`](./packages/ghost-fleet) | Fleet elevation across many members. | ❌ private | +| [`packages/ghost-ui`](./packages/ghost-ui) | Reference design system: 97 shadcn components + the `ghost-mcp` MCP server. | ❌ private (distributed via shadcn registry, not npm) | +| [`apps/docs`](./apps/docs) | Deployed docs site (`ghost-docs`). | ❌ private | -`ghost-drift` is the product; the rest is how the expression stays concrete. +Dependency flow: `@ghost/core` ← everyone. `ghost-expression` ← `ghost-drift`, `ghost-fleet`. `ghost-map` ← `ghost-fleet`. No cycles. ## Getting Started @@ -51,24 +69,32 @@ pnpm install pnpm build ``` -### Install the skill into your host agent +### Install the skill bundles into your host agent + +Each tool ships its own bundle. Install whichever you need. ```bash -# Writes the ghost-drift skill bundle to ./.claude/skills/ghost-drift -ghost-drift emit skill +ghost-drift emit skill # → ./.claude/skills/ghost-drift +ghost-expression emit skill # → ./.claude/skills/ghost-expression +ghost-fleet emit skill # → ./.claude/skills/ghost-fleet ``` -Once the skill is installed, ask your agent to "profile this design language" or "review this PR for design drift" and it will follow the recipe, calling `ghost-drift` for any deterministic step. +Once a skill is installed, ask your agent in plain English ("profile this design language", "review this PR for drift", "compute the fleet view") and it'll follow the recipe, calling the relevant CLI for any deterministic step. ### Quick start -**1. Profile your system**: ask your host agent (Claude Code, Cursor, etc.) to write an `expression.md`. It'll follow the `profile` recipe and validate with `ghost-drift lint` at the end. +**1. Map the repo** (optional but speeds up everything that follows). Ask your host agent to write `map.md`, then validate: -**2. Validate the result:** +```bash +ghost-map inventory # raw signals as JSON (the agent reads this to author map.md) +ghost-map lint # validate ./map.md against ghost.map/v1 +``` + +**2. Profile your design system** — ask your host agent to write `expression.md`. It'll follow the `profile` recipe and validate at the end. You validate manually with: ```bash -ghost-drift lint # defaults to ./expression.md -ghost-drift lint path/to/expression.md --format json +ghost-expression lint # defaults to ./expression.md +ghost-expression lint path/to/expression.md --format json ``` **3. Compare expressions:** @@ -95,12 +121,19 @@ ghost-drift track new-tracked.expression.md ghost-drift diverge typography --reason "Editorial product uses a different type scale" ``` -**5. Emit derived artifacts:** +**5. Emit derived artifacts** (these all live in `ghost-expression` now — they read your `expression.md`): ```bash -ghost-drift emit review-command # .claude/commands/design-review.md (per-project slash command) -ghost-drift emit context-bundle # ghost-context/ (SKILL.md + tokens.css + prompt.md) -ghost-drift emit skill # .claude/skills/ghost-drift (the agentskills.io skill bundle) +ghost-expression emit review-command # .claude/commands/design-review.md (per-project slash command) +ghost-expression emit context-bundle # ghost-context/ (SKILL.md + tokens.css + prompt.md) +ghost-expression emit skill # .claude/skills/ghost-expression (the agentskills.io bundle) +``` + +**6. Take the fleet elevation** (when you have ≥2 members each with their own `map.md` and `expression.md`): + +```bash +ghost-fleet members ./fleet # list registered members + freshness +ghost-fleet view ./fleet # emit fleet.md + fleet.json with pairwise matrix, centroid, clusters ``` **Run the docs site locally:** @@ -112,56 +145,70 @@ just dev ## CLI Commands -Seven deterministic primitives, grouped by the loop: **author** (`emit`), **detect** (`compare`, `lint`, `describe`), **remediate** (`ack`, `track`, `diverge`). Everything interpretive is a skill recipe the host agent runs. - -| Command | Description | -| ---------------- | ----------------------------------------------------------------------------------- | -| `ghost-drift compare` | Pairwise distance (N=2) or composite expression (N≥3: pairwise matrix, centroid, clusters) over embeddings. `--semantic` and `--temporal` add qualitative enrichment for N=2. | -| `ghost-drift lint` | Validate `expression.md` schema + body/frontmatter coherence. Use before declaring an expression valid. | -| `ghost-drift describe` | Print a section map for `expression.md` so agents can selectively load it. | -| `ghost-drift ack` | Record a stance toward the tracked expression (aligned / accepted / diverging) in `.ghost-sync.json`. | -| `ghost-drift track` | Shift tracked expression to a new expression. | -| `ghost-drift diverge` | Declare intentional divergence on a dimension with reasoning. | -| `ghost-drift emit` | Derive an artifact from `expression.md`: `review-command`, `context-bundle`, or `skill`. | +Every verb is a deterministic primitive — pure inputs → pure outputs, no LLM in the loop. Verbs are scoped to the tool that owns the artifact. + +| Tool | Command | Description | +| --- | --- | --- | +| `ghost-map` | `inventory` | Emit raw repo signals (manifests, language histogram, registry presence, top-level tree, git remote) as JSON. | +| `ghost-map` | `lint` | Validate `map.md` against `ghost.map/v1`. | +| `ghost-expression` | `lint` | Validate `expression.md` schema + body/frontmatter coherence. | +| `ghost-expression` | `describe` | Print section ranges + token estimates so agents can selectively load. | +| `ghost-expression` | `diff` | Structural prose-level diff between two expressions (NOT vector distance — for that, use `ghost-drift compare`). | +| `ghost-expression` | `emit` | Derive an artifact from `expression.md`: `review-command`, `context-bundle`, or `skill`. | +| `ghost-drift` | `compare` | Pairwise (N=2) or composite (N≥3) over expression embeddings. `--semantic` / `--temporal` add qualitative enrichment. | +| `ghost-drift` | `ack` | Record stance toward the tracked expression in `.ghost-sync.json`. | +| `ghost-drift` | `track` | Shift the tracked expression. | +| `ghost-drift` | `diverge` | Declare intentional divergence on a dimension. | +| `ghost-drift` | `emit skill` | Install the `ghost-drift` agentskills.io bundle. | +| `ghost-fleet` | `members` | List registered fleet members + freshness. | +| `ghost-fleet` | `view` | Compute pairwise distances + group-by tables; emit `fleet.md` + `fleet.json`. | +| `ghost-fleet` | `emit skill` | Install the `ghost-fleet` agentskills.io bundle. | ### Skill recipes: run by the host agent -Install once with `ghost-drift emit skill`. Each recipe gives the agent a specific capability from the pitch (*author, self-govern, detect, remediate*): +The interpretive verbs from the pitch (*author, self-govern, detect, remediate*) are recipes the agent runs. Install the relevant bundle once; ask in plain English. Each tool ships its own. -| Recipe | Capability | Triggered by | -| ---------- | ---------------------------------- | ---------------------------------------------- | -| `profile` | Author the quality bar | "profile this", "write expression.md" | -| `generate` | Author *against* the quality bar | "generate a component matching our design" | -| `review` | Self-govern at PR time | "review this PR for drift" | -| `verify` | Self-govern at generation time | "verify generated UI against the expression" | -| `compare` | Detect drift across the org | "why did these two expressions drift?" | -| `discover` | Find quality bars worth borrowing | "find design languages like X" | +| Recipe | Bundle | Capability | Triggered by | +| --- | --- | --- | --- | +| `map` | `ghost-map` | Author the topology card | "map this repo", "write map.md" | +| `profile` | `ghost-expression` | Author the quality bar | "profile this design language", "write expression.md" | +| `review` | `ghost-drift` | Self-govern at PR time | "review this PR for drift" | +| `verify` | `ghost-drift` | Self-govern at generation time | "verify generated UI against the expression" | +| `compare` | `ghost-drift` | Detect drift across the org | "why did these two expressions drift?" | +| `remediate` | `ghost-drift` | Suggest minimal fixes for drift | "fix this drift" | +| `target` | `ghost-fleet` | Synthesize fleet narrative | "describe this fleet" | -These are instructions, not code. The agent executes them using its normal tools (file search, reading, editing) plus `ghost-drift` for the deterministic steps. +These are instructions, not code. The agent executes them using its normal tools (file search, reading, editing) plus the relevant Ghost CLI for any deterministic step. (`discover` and `generate` are intentionally not migrated — see [`docs/ideas/phase-0-decisions.md`](./docs/ideas/phase-0-decisions.md).) ## Configuration -`ghost.config.ts` is optional — only `ack` and `diverge` consult it (to locate the tracked expression). Everything else is zero-config. +`ghost.config.ts` is optional — only `ghost-drift ack` and `ghost-drift diverge` consult it (to locate the tracked expression). Everything else is zero-config. ### Environment variables -- `OPENAI_API_KEY` / `VOYAGE_API_KEY`: optional, consumed by `computeSemanticEmbedding` when a host writes an expression.md and wants the enriched 49-dim vector. +- `OPENAI_API_KEY` / `VOYAGE_API_KEY`: optional, consumed by `computeSemanticEmbedding` (a `@ghost/core` library function) when a host writes an `expression.md` and wants the enriched 49-dim vector. - `GITHUB_TOKEN`: optional, used by `resolveTrackedExpression` when fetching a tracked expression from GitHub (avoids rate limits). -The CLI auto-loads `.env` and `.env.local` from the working directory. +Each CLI auto-loads `.env` and `.env.local` from the working directory. ## How It Works ### The expression -What the agent reads when it authors, reviews, or remediates. The canonical artifact is **`expression.md`**: a Markdown document with YAML frontmatter (machine layer) plus a three-layer prose body. Human-readable, LLM-consumable, diff-friendly: +What the agent reads when it authors, reviews, or remediates. The canonical artifact is **`expression.md`** (owned by `ghost-expression`): a Markdown document with YAML frontmatter (machine layer) plus a three-layer prose body. Human-readable, LLM-consumable, diff-friendly: - **Frontmatter**: 49-dimensional embedding, palette, spacing, typography, surfaces, roles, provenance. What deterministic tools read. - **`# Character`**: the opening atmosphere read, evocative not technical. What an agent quotes to stay on-brand. - **`# Signature`**: 3–7 distinctive traits that make _this_ system unlike its peers. The drift-sensitive moves. -- **`# Decisions`**: abstract, implementation-agnostic choices with evidence. Each decision is embedded so `compare --semantic` can match semantically. +- **`# Decisions`**: abstract, implementation-agnostic choices with evidence. Each decision is embedded so `ghost-drift compare --semantic` can match semantically. -Generate one with the `profile` recipe. See [`docs/expression-format.md`](./docs/expression-format.md) for the full spec, including the 49-dim machine-vector breakdown. +Generate one with the `profile` recipe (in the `ghost-expression` skill bundle). See [`docs/expression-format.md`](./docs/expression-format.md) for the full spec, including the 49-dim machine-vector breakdown. + +### The map + +What every Ghost tool reads to learn the topology of a repo. The canonical artifact is **`map.md`** (owned by `ghost-map`): YAML frontmatter against the `ghost.map/v1` schema (languages, build system, package manifests, registry, design-system paths, UI surface globs, feature areas) plus a short prose body (Identity, Topology, Conventions). The repo's own `map.md` lives at the root. + +Generate one with the `map` recipe (in the `ghost-map` skill bundle). The agent reads `ghost-map inventory` (deterministic raw signals) and synthesizes the prose layer. ### Author + self-govern loop @@ -172,17 +219,21 @@ The literal loop the pitch describes: the agent authors UI, Ghost detects drift Three responses, each with recorded reasoning and full lineage, so a year from now you know whether a divergence was meant or missed: - **`expression.md`**: The canonical expression artifact. -- **`.ghost-sync.json`**: Per-dimension stances toward the tracked expression (aligned, accepted, or diverging), each with recorded reasoning. Written by `ack` / `track` / `diverge`. -- **`.ghost/history.jsonl`**: Append-only expression history for temporal analysis. Read by `compare --temporal`. +- **`.ghost-sync.json`**: Per-dimension stances toward the tracked expression (aligned, accepted, or diverging), each with recorded reasoning. Written by `ghost-drift ack` / `track` / `diverge`. +- **`.ghost/history.jsonl`**: Append-only expression history for temporal analysis. Read by `ghost-drift compare --temporal`. ### Org-scale observability -Drift at scale: the fleet view. Run `ghost-drift compare` with three or more expressions and Ghost returns the **composite expression** — pairwise distances, a centroid, and similarity clusters. Which expressions cluster tightly, which are far apart, and where the gaps are. An expression of expressions. +Drift at scale: the fleet view. Two routes, depending on what you have: + +- **Many expressions, no map**: run `ghost-drift compare` with three or more `expression.md` files. Returns the **composite expression** — pairwise distances, a centroid, similarity clusters. An expression of expressions. +- **A registered fleet** (members each with `map.md` + `expression.md`): run `ghost-fleet view`. Adds group-by axes (platform, build system, design-system status) on top of the composite. The map metadata is the orthogonal axis pure expression-comparison can't see. ## Project Resources | Resource | Description | | ------------------------------------ | --------------------------- | +| [INVARIANTS.md](./INVARIANTS.md) | Hard constraints — read before non-trivial changes | | [CODEOWNERS](./CODEOWNERS) | Project lead(s) | | [CONTRIBUTING.md](./CONTRIBUTING.md) | How to contribute | | [GOVERNANCE.md](./GOVERNANCE.md) | Project governance | diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index 53002c4..96e8d5c 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -1,9 +1,14 @@ import { ThemeProvider } from "ghost-ui"; import { Navigate, Route, Routes, useParams } from "react-router"; -import DriftEngineIndex from "@/app/docs/page"; +import DocsIndex from "@/app/docs/page"; import WorkflowPage from "@/app/docs/workflow/page"; import HomePage from "@/app/page"; +import GhostDriftLanding from "@/app/tools/drift/page"; +import GhostExpressionLanding from "@/app/tools/expression/page"; +import GhostFleetLanding from "@/app/tools/fleet/page"; +import GhostMapLanding from "@/app/tools/map/page"; import ToolsIndex from "@/app/tools/page"; +import GhostUiLanding from "@/app/tools/ui/page"; import ComponentPage from "@/app/ui/components/[name]/page"; import ComponentsIndex from "@/app/ui/components/page"; import ColorsPage from "@/app/ui/foundations/colors/page"; @@ -31,15 +36,22 @@ export function App() { } /> - {/* Tools */} + {/* Tools — five-card index plus per-tool landings */} } /> - } /> + } /> + } /> + } /> } /> + } /> + } /> - {/* MDX-authored doc pages */} + {/* Cross-tool docs hub */} + } /> + + {/* MDX-authored doc pages (getting-started + cli reference under /docs/*) */} {mdxDocsRoutes()} - {/* Design Language (ghost-ui catalogue — not linked from home/dock) */} + {/* Design Language (ghost-ui catalogue) */} } /> } /> } /> @@ -50,19 +62,14 @@ export function App() { } /> } /> - {/* Redirects from old /docs/* URLs */} - } /> - } - /> + {/* Redirects from the previous /tools/drift/{getting-started,cli} URLs */} } + path="tools/drift/getting-started" + element={} /> } + path="tools/drift/cli" + element={} /> , - }, { name: "Getting Started", - href: "/tools/drift/getting-started", + href: "/docs/getting-started", description: - "Install the skill bundle, write your first expression.md, and track drift against another expression — in under five minutes.", + "Install the CLIs, write your first map.md and expression.md, and track drift across the org — in under five minutes.", icon: , }, { name: "CLI Reference", - href: "/tools/drift/cli", + href: "/docs/cli", description: - "Seven deterministic primitives — compare, lint, describe, ack, track, diverge, emit. Plus the skill recipes the host agent runs.", + "Sixteen deterministic primitives across four tools — ghost-map, ghost-expression, ghost-drift, ghost-fleet. Plus the skill recipes the host agent runs.", icon: , }, + { + name: "Drift Workflow", + href: "/tools/drift/workflow", + description: + "The five moves: profile, compare, review, evolve, and zoom out to the org expression — with examples for each.", + icon: , + }, ]; export default function DocsIndex() { @@ -46,9 +46,9 @@ export default function DocsIndex() { return (
Step 01 · Profile Write an expression.md - Open your project in a host agent with the ghost-drift{" "} - skill installed and ask it to profile this design language. - The recipe walks the agent through your theme CSS, tailwind config, - and component primitives, resolves variable chains, and writes a - single expression.md at the repo root — YAML frontmatter - for machines, Markdown body for humans. + Open your project in a host agent with the{" "} + ghost-expression skill installed and ask it to{" "} + profile this design language. The recipe walks the agent + through your theme CSS, tailwind config, and component primitives, + resolves variable chains, and writes a single{" "} + expression.md at the repo root — YAML frontmatter for + machines, Markdown body for humans.
@@ -886,8 +887,8 @@ export default function WorkflowPage() {

Ghost never calls an LLM itself. The agent writes the expression; the CLI lints, compares, and diffs it deterministically. The final step of - every profile is ghost-drift lint — which validates the - schema and flags body/frontmatter incoherence before anything else + every profile is ghost-expression lint — which validates + the schema and flags body/frontmatter incoherence before anything else touches it.

@@ -958,11 +959,11 @@ export default function WorkflowPage() { {" "} for distance,{" "} - ghost-drift lint + ghost-expression lint {" "} for validation. For a per-project, pre-baked review command, run{" "} - ghost-drift emit review-command + ghost-expression emit review-command {" "} and the agent will pick it up the next time you open a PR.

@@ -1086,7 +1087,7 @@ export default function WorkflowPage() {
{[ { - step: "ghost-drift emit context-bundle", + step: "ghost-expression emit context-bundle", name: "Ground", desc: "Write SKILL.md + tokens.css + prompt.md from expression.md. Whatever the generator consumes.", }, diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index e0b66ce..55873b1 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -33,8 +33,8 @@ export default function Home() { driven by a human operator through an agentic workflow; sometimes it runs fully autonomously. The economics have shifted with it. We no longer assume humans will sit in every diff; we assume the - harness (the guardrails, the reviewers, the verifiers) catches - drift and course-corrects before it lands. + harness — the guardrails, the reviewers, the verifiers — catches + divergence before it lands.

In that world, ensuring every generation reflects a brand's voice @@ -64,7 +64,8 @@ export default function Home() { in the repo, versioned with the code, edited in the same PRs as the features it shapes. And it has to evolve. Brand isn't locked in at the start; it shifts as the product ships, as taste - sharpens, as new surfaces appear. + sharpens, as new surfaces appear, as the org grows new products + around it.

Which raises the governance question. The reflex is to centralize: @@ -72,10 +73,12 @@ export default function Home() { from above. Ghost takes the opposite approach. Each repo owns its expression, its trajectory, and its stance. Decentralization without intent is entropy, so stances (aligned,{" "} - accepted, diverging) turn drift into signal. The - fleet of expressions drifts in the open; every divergence carries - reasoning. Nothing is prescriptive. Nothing drifts silently. - Everything is transparent. + accepted, diverging) turn divergence into + signal. The fleet of expressions drifts in the open; every + divergence carries reasoning. And read from above, the fleet + becomes a world model — the shape of the org's design language, + drawn from the languages inside it. Nothing is prescriptive. + Nothing drifts silently. Everything is transparent.

diff --git a/apps/docs/src/app/tools/drift/page.tsx b/apps/docs/src/app/tools/drift/page.tsx new file mode 100644 index 0000000..6679516 --- /dev/null +++ b/apps/docs/src/app/tools/drift/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useStaggerReveal } from "ghost-ui"; +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: "Workflow", + href: "/tools/drift/workflow", + description: + "The five moves: profile, compare, review, evolve, and zoom out to the org expression — with examples for each.", + icon: , + }, + { + name: "Get started", + href: "/docs/getting-started", + description: + "Install the ghost-drift skill bundle and start tracking drift against a reference expression.", + icon: , + }, + { + name: "CLI reference", + href: "/docs/cli#ghost-drift--drift-detection--governance", + description: + "compare, ack, track, diverge, and emit skill — plus the review / verify / remediate recipes.", + 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/expression/page.tsx b/apps/docs/src/app/tools/expression/page.tsx new file mode 100644 index 0000000..478cd5d --- /dev/null +++ b/apps/docs/src/app/tools/expression/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useStaggerReveal } from "ghost-ui"; +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 ghost-expression skill bundle and ask your agent to profile a design language.", + icon: , + }, + { + name: "CLI reference", + href: "/docs/cli#ghost-expression--authoring--validation", + description: + "lint, describe, diff, and emit (review-command, context-bundle, skill).", + icon: , + }, + { + name: "Format spec", + href: "https://github.com/block/ghost/blob/main/docs/expression-format.md", + description: + "The full expression.md spec — frontmatter schema, three-layer body, 49-dim machine vector.", + icon: , + }, +]; + +export default function GhostExpressionLanding() { + 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/fleet/page.tsx b/apps/docs/src/app/tools/fleet/page.tsx new file mode 100644 index 0000000..8cad1d1 --- /dev/null +++ b/apps/docs/src/app/tools/fleet/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useStaggerReveal } from "ghost-ui"; +import { BookOpen, Network, 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 ghost-fleet skill bundle and run members + view across a directory of registered members.", + icon: , + }, + { + name: "CLI reference", + href: "/docs/cli#ghost-fleet--elevation-across-members", + description: + "members (list + freshness), view (pairwise distances + group-by tables, emits fleet.md + fleet.json), emit skill.", + icon: , + }, + { + name: "Skill bundle", + href: "https://github.com/block/ghost/tree/main/packages/ghost-fleet/src/skill-bundle", + description: + "The target recipe — synthesize the fleet.md prose narrative from the deterministic view output.", + icon: , + }, +]; + +export default function GhostFleetLanding() { + 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/map/page.tsx b/apps/docs/src/app/tools/map/page.tsx new file mode 100644 index 0000000..a18ef47 --- /dev/null +++ b/apps/docs/src/app/tools/map/page.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useStaggerReveal } from "ghost-ui"; +import { BookOpen, Compass, 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 CLIs and write your first map.md alongside expression.md.", + icon: , + }, + { + name: "CLI reference", + href: "/docs/cli#ghost-map--topology", + description: + "inventory (raw repo signals as JSON) and lint (validate map.md against ghost.map/v1).", + icon: , + }, + { + name: "Skill bundle", + href: "https://github.com/block/ghost/tree/main/packages/ghost-map/src/skill-bundle", + description: + "The map recipe — the agent reads inventory and synthesizes the prose layer of map.md.", + icon: , + }, +]; + +export default function GhostMapLanding() { + 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 index 52ef9cd..b97610a 100644 --- a/apps/docs/src/app/tools/page.tsx +++ b/apps/docs/src/app/tools/page.tsx @@ -1,19 +1,53 @@ "use client"; import { useStaggerReveal } from "ghost-ui"; -import { Orbit } from "lucide-react"; +import { Compass, FileText, Network, Orbit, Palette } 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 = [ +const tools: { + name: string; + href: string; + description: string; + icon: ReactNode; +}[] = [ { - name: "Drift", + name: "ghost-map", + href: "/tools/map", + description: + "Topology. Generates map.md — the navigation card every other Ghost tool reads to learn where the design implementation lives.", + icon: , + }, + { + name: "ghost-expression", + href: "/tools/expression", + description: + "Authoring. Owns expression.md — the canonical design-language artifact. Lint, describe, diff, and emit grounding bundles for any generator.", + icon: , + }, + { + name: "ghost-drift", href: "/tools/drift", description: - "Express design languages, track their evolution, and surface divergence before it compounds.", + "Detection. Compares expressions, tracks stance (ack / track / diverge), and ships the review / verify / remediate recipes.", icon: , }, + { + name: "ghost-fleet", + href: "/tools/fleet", + description: + "Elevation. Reads many (map.md, expression.md) members and emits fleet.md — pairwise distances, group-by tables, tracks-graph.", + icon: , + }, + { + name: "ghost-ui", + href: "/tools/ui", + description: + "Reference UI library. 97 shadcn-distributed components + an MCP server. The system Ghost dogfoods its expression against.", + icon: , + }, ]; export default function ToolsIndex() { @@ -28,7 +62,7 @@ export default function ToolsIndex() {
, + }, + { + name: `Components (${componentCount})`, + href: "/ui/components", + description: + "Production-ready primitives + AI elements. Distributed via the shadcn registry.json — installed component-by-component, never wholesale.", + icon: , + }, + { + name: "MCP server", + href: "https://github.com/block/ghost/tree/main/packages/ghost-ui#mcp-server", + description: + "ghost-mcp re-exposes the registry to AI assistants — five tools, two resources, so an agent can search components and pull source.", + icon: , + }, +]; + +export default function GhostUiLanding() { + 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/cli-help.tsx b/apps/docs/src/components/docs/cli-help.tsx index 0a84986..ce328b7 100644 --- a/apps/docs/src/components/docs/cli-help.tsx +++ b/apps/docs/src/components/docs/cli-help.tsx @@ -1,7 +1,14 @@ import manifest from "@/generated/cli-manifest.json"; +type ToolName = + | "ghost-drift" + | "ghost-expression" + | "ghost-map" + | "ghost-fleet"; + interface CliHelpProps { command: string; + tool?: ToolName; show?: "all" | "signature" | "options"; hideDescription?: boolean; } @@ -16,29 +23,41 @@ interface CliOption { } interface CliCommand { + tool: ToolName; name: string; rawName: string; description: string; options: CliOption[]; } -const commands = (manifest as { commands: CliCommand[] }).commands; +interface ToolEntry { + tool: ToolName; + commands: CliCommand[]; +} + +const tools = (manifest as { tools: ToolEntry[] }).tools; -function findCommand(name: string): CliCommand | undefined { - return commands.find((c) => c.name === name || c.rawName.startsWith(name)); +function findCommand(tool: ToolName, name: string): CliCommand | undefined { + const entry = tools.find((t) => t.tool === tool); + if (!entry) return undefined; + return entry.commands.find( + (c) => c.name === name || c.rawName.startsWith(name), + ); } export function CliHelp({ command, + tool = "ghost-drift", show = "all", hideDescription = false, }: CliHelpProps) { - const cmd = findCommand(command); + const cmd = findCommand(tool, command); if (!cmd) { + const knownTools = tools.map((t) => t.tool).join(", "); return (
- Unknown CLI command: {command}. Commands in manifest:{" "} - {commands.map((c) => c.name).join(", ")} + Unknown CLI command: {`${tool} ${command}`}. Tools in + manifest: {knownTools}
); } @@ -48,7 +67,7 @@ export function CliHelp({ {(show === "all" || show === "signature") && (
- ghost-drift {cmd.rawName} + {tool} {cmd.rawName}
{!hideDescription && cmd.description && (
diff --git a/apps/docs/src/components/docs/dock.tsx b/apps/docs/src/components/docs/dock.tsx index bb2693c..689cab8 100644 --- a/apps/docs/src/components/docs/dock.tsx +++ b/apps/docs/src/components/docs/dock.tsx @@ -176,7 +176,25 @@ export function Dock() { - + + { + navigate("/tools/map"); + setSearchOpen(false); + }} + > + + ghost-map + + { + navigate("/tools/expression"); + setSearchOpen(false); + }} + > + + ghost-expression + { navigate("/tools/drift"); @@ -184,11 +202,32 @@ export function Dock() { }} > - Drift + ghost-drift + + { + navigate("/tools/fleet"); + setSearchOpen(false); + }} + > + + ghost-fleet { - navigate("/tools/drift/getting-started"); + navigate("/tools/ui"); + setSearchOpen(false); + }} + > + + ghost-ui + + + + + { + navigate("/docs/getting-started"); setSearchOpen(false); }} > @@ -197,7 +236,7 @@ export function Dock() { { - navigate("/tools/drift/cli"); + navigate("/docs/cli"); setSearchOpen(false); }} > @@ -211,7 +250,7 @@ export function Dock() { }} > - Workflow + Drift Workflow diff --git a/apps/docs/src/content/docs-frontmatter.ts b/apps/docs/src/content/docs-frontmatter.ts index 2204d3f..0b17339 100644 --- a/apps/docs/src/content/docs-frontmatter.ts +++ b/apps/docs/src/content/docs-frontmatter.ts @@ -4,7 +4,7 @@ export const DocsFrontmatterSchema = z.object({ title: z.string().min(1), description: z.string().min(1), kicker: z.string().default("Docs"), - section: z.enum(["drift", "ui"]).default("drift"), + section: z.enum(["guide", "drift", "ui"]).default("guide"), order: z.number(), slug: z.string().min(1), route: z.string().optional(), @@ -16,6 +16,11 @@ export type DocsFrontmatter = z.infer; export function routeFor(fm: DocsFrontmatter): string { if (fm.route) return fm.route; - const prefix = fm.section === "drift" ? "/tools/drift" : "/ui"; + const prefix = + fm.section === "guide" + ? "/docs" + : fm.section === "drift" + ? "/tools/drift" + : "/ui"; return `${prefix}/${fm.slug}`; } diff --git a/apps/docs/src/content/docs/cli-reference.mdx b/apps/docs/src/content/docs/cli-reference.mdx index b0bb71e..ed526ef 100644 --- a/apps/docs/src/content/docs/cli-reference.mdx +++ b/apps/docs/src/content/docs/cli-reference.mdx @@ -1,75 +1,90 @@ --- title: CLI Reference -description: Seven deterministic primitives. Everything interpretive lives in the skill bundle. +description: Sixteen deterministic primitives across four tools. Everything interpretive lives in the skill bundles. kicker: Docs -section: drift +section: guide order: 20 slug: cli --- -Ghost's CLI is a set of deterministic primitives. It never calls an LLM. -The canonical artifact is `expression.md` — a Markdown file with two -layers: a machine layer (YAML frontmatter: 49-dim vector + palette, -spacing, typography, surfaces, roles) and a prose body with three -sections (Character, Signature, Decisions). Most commands accept a path -to an `expression.md`; they default to `./expression.md` in the current -directory. +Ghost's CLIs are deterministic primitives. None of them call an LLM. The +canonical artifacts are **`expression.md`** (owned by `ghost-expression`) +and **`map.md`** (owned by `ghost-map`) — Markdown files with YAML +frontmatter (machine layer) plus a prose body. Most commands accept a path +to the relevant artifact; they default to `./expression.md` or `./map.md` +in the current directory. -Workflows like _profile_, _review_, _verify_, _generate_, and _discover_ -are skill recipes your host agent runs — not CLI verbs. Install them once -with `ghost-drift emit skill`. +Verbs are scoped to the tool that owns the artifact: -The tables below are generated from the CLI source at build time. If a -flag changes in `bin.ts`, the next `pnpm dump:cli-help` run regenerates -this reference — so these docs can't drift from the binary. +- **`ghost-map`** — topology: `inventory`, `lint` +- **`ghost-expression`** — design language: `lint`, `describe`, `diff`, `emit` +- **`ghost-drift`** — drift detection + governance: `compare`, `ack`, `track`, `diverge`, `emit skill` +- **`ghost-fleet`** — elevation across many members: `members`, `view`, `emit skill` + +Workflows like _profile_, _review_, _verify_, and _remediate_ are skill +recipes your host agent runs — not CLI verbs. Install them with each +tool's `emit skill` verb. + +The tables below are generated from each CLI's source at build time. If a +flag changes in any `cli.ts`, the next `pnpm dump:cli-help` run regenerates +this reference — so these docs can't drift from the binaries. - + -Pairwise distance (N=2) or fleet analysis (N≥3) over expression -embeddings. Pure math. Exits non-zero when drift exceeds 0.5. `--semantic` -and `--temporal` add qualitative enrichment for N=2. +`map.md` is the navigation card every Ghost tool reads to learn the +topology of a frontend repo. `ghost-map` ships two verbs: `inventory` +(deterministic raw signals as JSON, the input the agent reads to author +`map.md`) and `lint` (validate against `ghost.map/v1`). - + ```bash -# Pairwise (N=2) -ghost-drift compare market.expression.md dashboard.expression.md +# Inventory the current directory +ghost-map inventory -# Qualitative diff of decisions + palette -ghost-drift compare a.expression.md b.expression.md --semantic +# Inventory a different repo +ghost-map inventory ../other-repo +``` -# Velocity + trajectory -ghost-drift compare before.expression.md after.expression.md --temporal + -# Fleet (N≥3) — pairwise matrix + centroid -ghost-drift compare *.expression.md +```bash +# Default — reads ./map.md +ghost-map lint + +# Specific file, JSON output +ghost-map lint packages/ghost-ui/map.md --format json ``` - + + +`expression.md` is the canonical design-language artifact. `ghost-expression` +owns authoring (lint + describe + diff + emit) — drift detection lives +separately in `ghost-drift`. + +### Validation — `lint` Validate `expression.md` schema + body/frontmatter coherence. Use this -before declaring an expression valid — the profile recipe ends by calling -it. +before declaring an expression valid — the `profile` recipe ends by +calling it. - + ```bash # Default — reads ./expression.md -ghost-drift lint +ghost-expression lint # Specific file, JSON output -ghost-drift lint path/to/expression.md --format json +ghost-expression lint path/to/expression.md --format json ``` - - - +### Inspection — `describe` Print a section map of `expression.md` — frontmatter range, body sections (`# Character`, `# Signature`, `# Decisions`, `# Fragments`), and each @@ -81,20 +96,20 @@ A typical `expression.md` runs 3–5k tokens. The `# Decisions` block alone is usually 60–80% of that, and an agent reviewing a single component change rarely needs every dimension. `describe` is the deterministic answer to "what's in this file and where" — the recall safety rule (when in doubt, -load the whole `# Decisions` block) lives in the review/generate skill +load the whole `# Decisions` block) lives in the review/remediate skill recipes. - + ```bash # Default — reads ./expression.md -ghost-drift describe +ghost-expression describe # Specific file -ghost-drift describe path/to/expression.md +ghost-expression describe path/to/expression.md # Machine-readable for agents -ghost-drift describe --format json +ghost-expression describe --format json ``` Sample output (against `packages/ghost-ui/expression.md`): @@ -117,9 +132,73 @@ Line ranges are 1-indexed and inclusive — they plug directly into a Read tool's `offset` / `limit = end - start + 1`. Token counts are a `chars / 4` approximation, sufficient for context budgeting. +### Structural diff — `diff` + +Diff two expressions at the prose / structural level — what decisions, +palette roles, and tokens changed. **Not** vector distance; for that, use +`ghost-drift compare`. + + + +```bash +ghost-expression diff a/expression.md b/expression.md +ghost-expression diff a.md b.md --format json +``` + +### Emit — derive artifacts from `expression.md` + +Derive an artifact from `expression.md`. Kinds: `review-command` (a +per-project slash command at `.claude/commands/design-review.md`), +`context-bundle` (SKILL.md + tokens.css + optional prompt.md for any +generator), or `skill` (the `ghost-expression` agentskills.io bundle — +install this into your host agent for the `profile` recipe). + + + +```bash +# Install the ghost-expression skill bundle +ghost-expression emit skill + +# Emit a per-project design-review slash command +ghost-expression emit review-command + +# Emit a grounding bundle any generator can consume +ghost-expression emit context-bundle + +# Single prompt.md for plain-text LLM context +ghost-expression emit context-bundle --prompt-only + +# Custom output directory +ghost-expression emit context-bundle --out dist/context +``` + - + + +### Comparison — `compare` + +Pairwise distance (N=2) or composite analysis (N≥3) over expression +embeddings. Pure math. Exits non-zero when drift exceeds 0.5. `--semantic` +and `--temporal` add qualitative enrichment for N=2. + + + +```bash +# Pairwise (N=2) +ghost-drift compare market.expression.md dashboard.expression.md + +# Qualitative diff of decisions + palette +ghost-drift compare a.expression.md b.expression.md --semantic + +# Velocity + trajectory +ghost-drift compare before.expression.md after.expression.md --temporal + +# Composite (N≥3) — pairwise matrix + centroid + clusters +ghost-drift compare *.expression.md +``` + +### Intent — `ack` / `track` / `diverge` These three verbs write per-dimension stances to `.ghost-sync.json`. `ack` and `diverge` need a tracked expression declared in `ghost.config.ts`; @@ -129,7 +208,7 @@ Acknowledge current drift — record your intentional stance (aligned, accepted, or diverging). Reads the tracked expression from `ghost.config.ts` and the local `expression.md`. - + ```bash # Acknowledge all dimensions as aligned @@ -142,7 +221,7 @@ ghost-drift ack -d typography --stance diverging --reason "Brand refresh require Shift the tracked expression to a new expression. Use this when you want to re-anchor your drift measurements against a different reference expression. - + ```bash # Track a new reference expression @@ -152,72 +231,101 @@ ghost-drift track new-tracked.expression.md Mark a specific dimension as intentionally diverging. Shorthand for `ack --stance diverging` that also records a reason. - + ```bash ghost-drift diverge palette --reason "Dark-mode-first palette for this product" ``` +### Emit — `emit skill` + +`ghost-drift emit` ships a single kind: `skill`, the agentskills.io bundle +that teaches your host agent the review / verify / compare / remediate +recipes. (Authoring artifacts — `review-command`, `context-bundle` — moved +to `ghost-expression`.) + + + +```bash +# Install the ghost-drift skill bundle (default: .claude/skills/ghost-drift) +ghost-drift emit skill + +# Custom location +ghost-drift emit skill --out ~/.my-agent/skills/ghost-drift +``` + - + -Derive an artifact from `expression.md`. Kinds: `review-command` (a -per-project slash command at `.claude/commands/design-review.md`), -`context-bundle` (SKILL.md + tokens.css + optional prompt.md for any -generator), or `skill` (the `ghost-drift` agentskills.io bundle — install -this into your host agent). +A fleet is a directory containing many `(map.md, expression.md)` members. +`ghost-fleet` reads the fleet, emits per-fleet outputs (`fleet.md` + +`fleet.json`), and ships the agentskills.io bundle for the narrative +recipe. - + ```bash -# Install the ghost-drift skill into your host agent -ghost-drift emit skill +# List members in the current fleet directory +ghost-fleet members -# Emit a per-project design-review slash command -ghost-drift emit review-command +# JSON output for scripting +ghost-fleet members --json +``` -# Emit a grounding bundle any generator can consume -ghost-drift emit context-bundle + -# Single prompt.md for plain-text LLM context -ghost-drift emit context-bundle --prompt-only +```bash +# Compute pairwise distances + group-by tables; write fleet.md + fleet.json +ghost-fleet view ./fleet -# Custom output directory -ghost-drift emit context-bundle --out dist/context +# Override the fleet id and reports directory +ghost-fleet view ./fleet --id cash-mobile --out reports/cash +``` + + + +```bash +ghost-fleet emit skill ``` -These aren't CLI verbs — they're instruction files inside the skill bundle -that your host agent follows. Install the bundle with `ghost-drift emit skill`, -then ask your agent in plain English: +These aren't CLI verbs — they're instruction files inside the per-tool +skill bundles that your host agent follows. Install the relevant bundle +once, then ask your agent in plain English: -| Recipe | Trigger | -| ---------- | ------------------------------------------------------ | -| `profile` | "profile this design language" / "write expression.md" | -| `review` | "review this PR for drift" | -| `verify` | "verify generated UI against the expression" | -| `generate` | "generate a component matching our design" | -| `discover` | "find design languages like Linear" | -| `compare` | "why did these two expressions drift?" | +| Recipe | Bundle | Trigger | +| ---------- | ------------------ | ------------------------------------------------------ | +| `map` | `ghost-map` | "map this repo" / "write map.md" | +| `profile` | `ghost-expression` | "profile this design language" / "write expression.md" | +| `review` | `ghost-drift` | "review this PR for drift" | +| `verify` | `ghost-drift` | "verify generated UI against the expression" | +| `compare` | `ghost-drift` | "why did these two expressions drift?" | +| `remediate`| `ghost-drift` | "fix this drift" | +| `target` | `ghost-fleet` | "describe this fleet" | Source for each recipe lives under -`packages/ghost-drift/src/skill-bundle/references/`. The agent executes the -steps with its normal tools and calls `ghost-drift` for any deterministic work. +`packages//src/skill-bundle/references/`. The agent executes the +steps with its normal tools and calls the relevant CLI for any +deterministic work. Each recipe also declares `handoffs` in its frontmatter — structured next-step suggestions that point at another skill (e.g. `profile` → `compare`) or a CLI invocation (e.g. `profile` → -`ghost-drift emit review-command`). Hosts that read the field can surface the -next action inline; hosts that ignore unknown frontmatter see no change. +`ghost-expression emit review-command`). Hosts that read the field can +surface the next action inline; hosts that ignore unknown frontmatter +see no change. + +`discover` and `generate` from earlier Ghost iterations are intentionally +not migrated to any tool (see [`docs/ideas/phase-0-decisions.md`](https://github.com/block/ghost/blob/main/docs/ideas/phase-0-decisions.md)). --- -See the [Workflow](/tools/drift/workflow) walkthrough for the five-move -picture these primitives fit into, or [Getting Started](/tools/drift/getting-started) -for an installation-first guide. +See the [Drift Workflow](/tools/drift/workflow) walkthrough for the +five-move picture these primitives fit into, or +[Getting Started](/docs/getting-started) for an installation-first guide. diff --git a/apps/docs/src/content/docs/getting-started.mdx b/apps/docs/src/content/docs/getting-started.mdx index 152eaa7..ff6c8e9 100644 --- a/apps/docs/src/content/docs/getting-started.mdx +++ b/apps/docs/src/content/docs/getting-started.mdx @@ -1,76 +1,91 @@ --- title: Getting Started -description: Install the skill bundle, write your first expression.md, and track drift against another expression — in under five minutes. +description: Install the skill bundles, write your first map.md and expression.md, and track drift across the org — in under five minutes. kicker: Docs -section: drift +section: guide order: 10 slug: getting-started --- -Ghost is split across two surfaces. The **CLI** is a set of deterministic -primitives — seven verbs that never call an LLM. The **skill bundle** is a set -of [agentskills.io](https://agentskills.io)-compatible recipes your host -agent (Claude Code, Cursor, Goose, Codex, …) follows for anything -interpretive: profile, review, verify, generate, discover. +Ghost is split into **five small tools**, each with one responsibility, plus a +shared library underneath. Every CLI is a set of deterministic primitives — +none of them ever call an LLM. Each tool ships its own +[agentskills.io](https://agentskills.io)-compatible recipe bundle for the +interpretive work your host agent (Claude Code, Cursor, Goose, Codex, …) +runs: profile, review, verify, remediate. -You install Ghost in two steps: add the CLI, then emit the skill bundle into -your agent. +| Tool | Owns | Verbs | +| --- | --- | --- | +| `ghost-map` | `map.md` (topology) | `inventory`, `lint` | +| `ghost-expression` | `expression.md` (design language) | `lint`, `describe`, `diff`, `emit` | +| `ghost-drift` | drift detection + governance | `compare`, `ack`, `track`, `diverge`, `emit skill` | +| `ghost-fleet` | `fleet.md` (elevation across members) | `members`, `view`, `emit skill` | +| `ghost-ui` | reference design system (97 shadcn components) | — | + +You install Ghost in two steps: add whichever CLI(s) you need, then emit the +matching skill bundle into your agent. -Add the core library and CLI to your project: +`ghost-drift` and `ghost-expression` are the published tools today. Add +either or both: ```bash -pnpm add -D ghost-drift +pnpm add -D ghost-drift ghost-expression ``` -Or install globally to use `ghost-drift` from anywhere: +Or install globally: ```bash -pnpm add -g ghost-drift +pnpm add -g ghost-drift ghost-expression ``` +`ghost-map` and `ghost-fleet` are workspace-only for now — clone the repo +and `pnpm build` to use them. + No API key is required for any CLI verb. If your host agent uses Anthropic -or OpenAI models, it'll handle auth itself. Ghost auto-loads `.env` and +or OpenAI models, it'll handle auth itself. Each CLI auto-loads `.env` and `.env.local` from the working directory for a couple of optional variables (`OPENAI_API_KEY` / `VOYAGE_API_KEY` for paraphrase-robust semantic embeddings, `GITHUB_TOKEN` for fetching tracked expressions). - + -`ghost-drift emit skill` writes the `ghost-drift` skill bundle into your host -agent's skill directory. Defaults to `.claude/skills/ghost-drift/`; use -`--out` to target another location. +Each tool ships its own bundle. Install whichever capability you want +your agent to gain: ```bash -# Install into Claude Code (default) -ghost-drift emit skill - -# Or into a custom path for another agent -ghost-drift emit skill --out ~/.my-agent/skills/ghost-drift +ghost-drift emit skill # → .claude/skills/ghost-drift +ghost-expression emit skill # → .claude/skills/ghost-expression +# ghost-fleet emit skill # → .claude/skills/ghost-fleet (workspace tool today) ``` -The bundle contains a top-level `SKILL.md` entry point plus recipes under -`references/` (`profile.md`, `review.md`, `verify.md`, `generate.md`, -`discover.md`, `compare.md`) plus a schema reference (`schema.md`). Each -recipe declares `handoffs` in its frontmatter so hosts can surface the -next step inline — e.g. after `profile`, a prompt to `compare` against another -expression, or to run `ghost-drift emit review-command`. Once installed, ask your -agent to "profile this design language" and it'll follow the recipe, -calling `ghost-drift` for any deterministic step. +Each bundle ships its own `SKILL.md` plus recipes under `references/`: + +- **`ghost-expression`** — `profile.md` (write `expression.md` from a project) + `schema.md` (condensed format reference). +- **`ghost-drift`** — `compare.md` (interpretation), `review.md` (PR review), `verify.md` (generation→review loop), `remediate.md` (suggest minimal fixes). +- **`ghost-fleet`** — `target.md` (synthesize fleet narrative from `view` output). + +Each recipe declares `handoffs` in its frontmatter so hosts can surface the +next step inline — e.g. after `profile`, a prompt to `compare` against +another expression, or to run `ghost-expression emit review-command`. Once +installed, ask your agent in plain English ("profile this design language", +"review this PR for drift") and it'll follow the recipe, calling the right +CLI for any deterministic step. -Profiling is a skill recipe, not a CLI verb. Open your host agent in the -project you want to profile and ask it something like: +Profiling is a skill recipe shipped in `ghost-expression`'s bundle, not a +CLI verb. Open your host agent in the project you want to profile and ask +it something like: ```text Profile this design language into expression.md @@ -78,8 +93,9 @@ Profile this design language into expression.md The `profile` recipe walks the agent through finding design sources (tailwind config, theme CSS, token files, component primitives), resolving -variable chains end-to-end, and writing the expression. The final step -is always `ghost-drift lint` — which the CLI runs for you, deterministically. +variable chains end-to-end, and writing the expression. The final step is +always `ghost-expression lint` — which the CLI runs for you, +deterministically. An **expression** is a two-layer Markdown file: YAML frontmatter is the machine layer (49-dim vector + palette, spacing, typography, surfaces, @@ -90,18 +106,19 @@ in this repo for a full real-world example. ```bash # Validate the result (zero-config — reads ./expression.md) -ghost-drift lint +ghost-expression lint # Or validate a specific file -ghost-drift lint path/to/expression.md --format json +ghost-expression lint path/to/expression.md --format json ``` -Once you have two expressions, `compare` returns a scalar distance, -per-dimension deltas, and (optionally) qualitative or temporal enrichment: +Once you have two expressions, `ghost-drift compare` returns a scalar +distance, per-dimension deltas, and (optionally) qualitative or temporal +enrichment: ```bash # Pairwise (N=2) — distance + per-dimension delta @@ -113,7 +130,7 @@ ghost-drift compare a.expression.md b.expression.md --semantic # Velocity + trajectory (reads .ghost/history.jsonl) ghost-drift compare before.expression.md after.expression.md --temporal -# Fleet (N≥3) — pairwise matrix + centroid +# Composite (N≥3) — pairwise matrix + centroid + clusters ghost-drift compare *.expression.md ``` @@ -121,12 +138,15 @@ ghost-drift compare *.expression.md decisions. Decisions are matched semantically above a cosine-similarity threshold so wording doesn't have to line up. +For a structural prose-level diff (what decisions and palette roles +changed, ignoring vector distance), use `ghost-expression diff` instead. + -Reviewing PRs is a skill recipe. Once the skill is installed, open a PR -branch in your host agent and ask: +Reviewing PRs is a skill recipe shipped in `ghost-drift`'s bundle. Once +installed, open a PR branch in your host agent and ask: ```text Review this PR for design drift @@ -134,17 +154,19 @@ Review this PR for design drift The `review` recipe diffs changed files against the local `expression.md` and flags hardcoded colors off the palette, spacing off the scale, and type -choices that violate decisions. For a project-level check, ask the agent to -compare a fresh expression against another expression. +choices that violate decisions. -If you want a repeatable, per-project slash command for your agent, emit one -from the expression itself: +If you want a repeatable, per-project slash command for your agent, emit +one from the expression itself: ```bash # Writes .claude/commands/design-review.md -ghost-drift emit review-command +ghost-expression emit review-command ``` +(`emit review-command` lives in `ghost-expression` because it derives from +`expression.md`. `ghost-drift` ships only `emit skill`.) + @@ -173,9 +195,9 @@ ghost-drift diverge typography --reason "Editorial product uses a different type Ghost doubles as pipeline infrastructure for AI-generated UI. The expression grounds the generator; the `review` recipe gates the output. -1. `ghost-drift emit context-bundle` — emit a grounding bundle from an expression - (SKILL.md + tokens.css + optional prompt.md) that any generator can - consume. +1. `ghost-expression emit context-bundle` — emit a grounding bundle from an + expression (SKILL.md + tokens.css + optional prompt.md) that any + generator can consume. 2. Run any generator — your host agent, Cursor, v0, or an in-house tool — with the bundle in context. 3. Use the `review` recipe to gate the output. Use the `verify` recipe to @@ -184,21 +206,21 @@ expression grounds the generator; the `review` recipe gates the output. ```bash # Emit a grounding bundle — default output: ./ghost-context/ -ghost-drift emit context-bundle +ghost-expression emit context-bundle # Single prompt.md for plain-text LLM context -ghost-drift emit context-bundle --prompt-only +ghost-expression emit context-bundle --prompt-only # Custom output directory -ghost-drift emit context-bundle --out dist/context +ghost-expression emit context-bundle --out dist/context ``` -`ack` and `diverge` need a tracked expression declared. Most other verbs are -zero-config. +`ack` and `diverge` need a tracked expression declared. Most other verbs +are zero-config. ```ts import { defineConfig } from "ghost-drift"; @@ -225,9 +247,9 @@ export default defineConfig({ --- -Next: [Workflow](/tools/drift/workflow) for the five-move walkthrough — -profile, compare, review, evolve, org — with richer examples for each. -Or jump to the [CLI Reference](/tools/drift/cli) for every deterministic -verb and flag. +Next: [Drift Workflow](/tools/drift/workflow) for the five-move +walkthrough — profile, compare, review, evolve, org — with richer examples +for each. Or jump to the [CLI Reference](/docs/cli) for every deterministic +verb across all four tools. diff --git a/apps/docs/src/generated/cli-manifest.json b/apps/docs/src/generated/cli-manifest.json index 6f8fd42..1052a3d 100644 --- a/apps/docs/src/generated/cli-manifest.json +++ b/apps/docs/src/generated/cli-manifest.json @@ -1,186 +1,446 @@ { - "generatedAt": "2026-04-27T12:04:59.221Z", - "commands": [ + "generatedAt": "2026-04-27T15:50:16.473Z", + "tools": [ { - "name": "compare", - "rawName": "compare [...expressions]", - "description": "Compare two or more expressions. N=2 returns a pairwise delta; N≥3 returns a composite expression (pairwise matrix, centroid, spread, clusters).", - "options": [ - { - "rawName": "--semantic", - "name": "semantic", - "description": "Qualitative diff of decisions + palette (N=2 only)", - "default": null, - "takesValue": false, - "negated": false - }, - { - "rawName": "--temporal", - "name": "temporal", - "description": "Add velocity, trajectory, and ack bounds (N=2, reads .ghost/history.jsonl)", - "default": null, - "takesValue": false, - "negated": false - }, - { - "rawName": "--history-dir ", - "name": "historyDir", - "description": "Directory containing .ghost/history.jsonl (for --temporal, defaults to cwd)", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "--format ", - "name": "format", - "description": "Output format: cli or json", - "default": "cli", - "takesValue": true, - "negated": false + "tool": "ghost-drift", + "commands": [ + { + "tool": "ghost-drift", + "name": "compare", + "rawName": "compare [...expressions]", + "description": "Compare two or more expressions. N=2 returns a pairwise delta; N≥3 returns a composite expression (pairwise matrix, centroid, spread, clusters).", + "options": [ + { + "rawName": "--semantic", + "name": "semantic", + "description": "Qualitative diff of decisions + palette (N=2 only)", + "default": null, + "takesValue": false, + "negated": false + }, + { + "rawName": "--temporal", + "name": "temporal", + "description": "Add velocity, trajectory, and ack bounds (N=2, reads .ghost/history.jsonl)", + "default": null, + "takesValue": false, + "negated": false + }, + { + "rawName": "--history-dir ", + "name": "historyDir", + "description": "Directory containing .ghost/history.jsonl (for --temporal, defaults to cwd)", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-drift", + "name": "ack", + "rawName": "ack", + "description": "Acknowledge current drift — record intentional stance toward the tracked expression", + "options": [ + { + "rawName": "-c, --config ", + "name": "config", + "description": "Path to ghost config file", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "-d, --dimension ", + "name": "dimension", + "description": "Acknowledge a specific dimension only", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--stance ", + "name": "stance", + "description": "Stance: aligned, accepted, or diverging", + "default": "accepted", + "takesValue": true, + "negated": false + }, + { + "rawName": "--reason ", + "name": "reason", + "description": "Reason for this acknowledgment", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-drift", + "name": "track", + "rawName": "track ", + "description": "Track another expression as this repo's reference", + "options": [ + { + "rawName": "-d, --dimension ", + "name": "dimension", + "description": "Track only for a specific dimension", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-drift", + "name": "diverge", + "rawName": "diverge ", + "description": "Declare intentional divergence on a dimension", + "options": [ + { + "rawName": "-c, --config ", + "name": "config", + "description": "Path to ghost config file", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "-r, --reason ", + "name": "reason", + "description": "Why this dimension is intentionally diverging", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-drift", + "name": "emit", + "rawName": "emit ", + "description": "Emit the ghost-drift agentskills.io bundle (kind: skill). For review-command and context-bundle, use `ghost-expression emit`.", + "options": [ + { + "rawName": "-o, --out ", + "name": "out", + "description": "Output directory (default: .claude/skills/ghost-drift)", + "default": null, + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-drift", + "name": "lint", + "rawName": "lint [...args]", + "description": "Moved to `ghost-expression lint`. Install ghost-expression and re-run.", + "options": [] + }, + { + "tool": "ghost-drift", + "name": "describe", + "rawName": "describe [...args]", + "description": "Moved to `ghost-expression describe`. Install ghost-expression and re-run.", + "options": [] } - ] - }, - { - "name": "ack", - "rawName": "ack", - "description": "Acknowledge current drift — record intentional stance toward the tracked expression", - "options": [ - { - "rawName": "-c, --config ", - "name": "config", - "description": "Path to ghost config file", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "-d, --dimension ", - "name": "dimension", - "description": "Acknowledge a specific dimension only", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "--stance ", - "name": "stance", - "description": "Stance: aligned, accepted, or diverging", - "default": "accepted", - "takesValue": true, - "negated": false - }, - { - "rawName": "--reason ", - "name": "reason", - "description": "Reason for this acknowledgment", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "--format ", - "name": "format", - "description": "Output format: cli or json", - "default": "cli", - "takesValue": true, - "negated": false + ], + "globalOptions": [ + { + "rawName": "-h, --help", + "name": "help", + "description": "Display this message", + "default": null + }, + { + "rawName": "-v, --version", + "name": "version", + "description": "Display version number", + "default": null } ] }, { - "name": "track", - "rawName": "track ", - "description": "Track another expression as this repo's reference", - "options": [ - { - "rawName": "-d, --dimension ", - "name": "dimension", - "description": "Track only for a specific dimension", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "--format ", - "name": "format", - "description": "Output format: cli or json", - "default": "cli", - "takesValue": true, - "negated": false + "tool": "ghost-expression", + "commands": [ + { + "tool": "ghost-expression", + "name": "lint", + "rawName": "lint [expression]", + "description": "Validate expression.md schema and body/frontmatter coherence", + "options": [ + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-expression", + "name": "describe", + "rawName": "describe [expression]", + "description": "Print a section map of expression.md (line ranges + token estimates) so agents can selectively load only the sections they need.", + "options": [ + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-expression", + "name": "diff", + "rawName": "diff ", + "description": "Structural diff between two expression.md files — what decisions, palette roles, and tokens changed (text-level, NOT embedding distance; for that, use `ghost-drift compare`).", + "options": [ + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-expression", + "name": "emit", + "rawName": "emit ", + "description": "Emit a derived artifact from expression.md (kinds: review-command, context-bundle, skill)", + "options": [ + { + "rawName": "-e, --expression ", + "name": "expression", + "description": "Source expression file (default: expression.md)", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "-o, --out ", + "name": "out", + "description": "Output path (review-command → .claude/commands/design-review.md; context-bundle → ghost-context/; skill → .claude/skills/ghost-expression/)", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--stdout", + "name": "stdout", + "description": "Write to stdout instead of a file (review-command only)", + "default": null, + "takesValue": false, + "negated": false + }, + { + "rawName": "--no-tokens", + "name": "tokens", + "description": "Skip tokens.css output (context-bundle)", + "default": true, + "takesValue": false, + "negated": true + }, + { + "rawName": "--readme", + "name": "readme", + "description": "Include README.md (context-bundle)", + "default": null, + "takesValue": false, + "negated": false + }, + { + "rawName": "--prompt-only", + "name": "promptOnly", + "description": "Emit only prompt.md — skips SKILL.md / expression.md / tokens.css (context-bundle)", + "default": null, + "takesValue": false, + "negated": false + }, + { + "rawName": "--name ", + "name": "name", + "description": "Override the skill name (default: expression id) (context-bundle)", + "default": null, + "takesValue": true, + "negated": false + } + ] + } + ], + "globalOptions": [ + { + "rawName": "-h, --help", + "name": "help", + "description": "Display this message", + "default": null + }, + { + "rawName": "-v, --version", + "name": "version", + "description": "Display version number", + "default": null } ] }, { - "name": "diverge", - "rawName": "diverge ", - "description": "Declare intentional divergence on a dimension", - "options": [ - { - "rawName": "-c, --config ", - "name": "config", - "description": "Path to ghost config file", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "-r, --reason ", - "name": "reason", - "description": "Why this dimension is intentionally diverging", - "default": null, - "takesValue": true, - "negated": false - }, - { - "rawName": "--format ", - "name": "format", - "description": "Output format: cli or json", - "default": "cli", - "takesValue": true, - "negated": false + "tool": "ghost-map", + "commands": [ + { + "tool": "ghost-map", + "name": "inventory", + "rawName": "inventory [path]", + "description": "Emit deterministic raw signals about a frontend repo as JSON: package manifests, language histogram, candidate config files, registry presence, top-level tree, git remote.", + "options": [] + }, + { + "tool": "ghost-map", + "name": "lint", + "rawName": "lint [map]", + "description": "Validate map.md against ghost.map/v1", + "options": [ + { + "rawName": "--format ", + "name": "format", + "description": "Output format: cli or json", + "default": "cli", + "takesValue": true, + "negated": false + } + ] + } + ], + "globalOptions": [ + { + "rawName": "-h, --help", + "name": "help", + "description": "Display this message", + "default": null + }, + { + "rawName": "-v, --version", + "name": "version", + "description": "Display version number", + "default": null } ] }, { - "name": "emit", - "rawName": "emit ", - "description": "Emit the ghost-drift agentskills.io bundle (kind: skill). For review-command and context-bundle, use `ghost-expression emit`.", - "options": [ - { - "rawName": "-o, --out ", - "name": "out", - "description": "Output directory (default: .claude/skills/ghost-drift)", - "default": null, - "takesValue": true, - "negated": false + "tool": "ghost-fleet", + "commands": [ + { + "tool": "ghost-fleet", + "name": "members", + "rawName": "members [dir]", + "description": "List registered fleet members and their freshness — one row per (map.md, expression.md) subdirectory.", + "options": [ + { + "rawName": "--json", + "name": "json", + "description": "Output JSON (one object per member) instead of a table", + "default": null, + "takesValue": false, + "negated": false + } + ] + }, + { + "tool": "ghost-fleet", + "name": "view", + "rawName": "view [dir]", + "description": "Compute the fleet's pairwise distances, group-by tables, and tracks-graph; emit fleet.md + fleet.json into /reports/.", + "options": [ + { + "rawName": "--id ", + "name": "id", + "description": "Override the fleet id (default: directory basename slug)", + "default": null, + "takesValue": true, + "negated": false + }, + { + "rawName": "--out ", + "name": "out", + "description": "Reports directory (default: /reports)", + "default": null, + "takesValue": true, + "negated": false + } + ] + }, + { + "tool": "ghost-fleet", + "name": "emit", + "rawName": "emit ", + "description": "Emit the ghost-fleet agentskills.io bundle (kind: skill).", + "options": [ + { + "rawName": "-o, --out ", + "name": "out", + "description": "Output directory (default: .claude/skills/ghost-fleet)", + "default": null, + "takesValue": true, + "negated": false + } + ] + } + ], + "globalOptions": [ + { + "rawName": "-h, --help", + "name": "help", + "description": "Display this message", + "default": null + }, + { + "rawName": "-v, --version", + "name": "version", + "description": "Display version number", + "default": null } ] - }, - { - "name": "lint", - "rawName": "lint [...args]", - "description": "Moved to `ghost-expression lint`. Install ghost-expression and re-run.", - "options": [] - }, - { - "name": "describe", - "rawName": "describe [...args]", - "description": "Moved to `ghost-expression describe`. Install ghost-expression and re-run.", - "options": [] - } - ], - "globalOptions": [ - { - "rawName": "-h, --help", - "name": "help", - "description": "Display this message", - "default": null - }, - { - "rawName": "-v, --version", - "name": "version", - "description": "Display version number", - "default": null } ] } diff --git a/docs/expression-format.md b/docs/expression-format.md index c3e8ecf..9f7842d 100644 --- a/docs/expression-format.md +++ b/docs/expression-format.md @@ -1,6 +1,6 @@ # The `expression.md` format -A Ghost **expression** is a single Markdown file that captures what a design language is trying to say — readable and editable by humans, natively consumable by LLMs, with a structured machine layer for `ghost-drift compare`, `ghost-expression lint`, and the skill recipes the host agent runs (profile, review, verify, generate). +A Ghost **expression** is a single Markdown file that captures what a design language is trying to say — readable and editable by humans, natively consumable by LLMs, with a structured machine layer for `ghost-drift compare`, `ghost-expression lint`, and the skill recipes the host agent runs (profile, review, verify, remediate). The file has two parts, and each owns **different data**: diff --git a/docs/generation-loop.md b/docs/generation-loop.md index 574e661..3729deb 100644 --- a/docs/generation-loop.md +++ b/docs/generation-loop.md @@ -5,14 +5,19 @@ Ghost sits as pipeline infrastructure for AI-driven UI generation. The post-generation gate; the *verify* recipe drives the loop over a prompt suite to expose where the expression leaks. -Only the grounding step is a deterministic CLI verb (`ghost-drift emit -context-bundle`). *Generate*, *review*, and *verify* are skill recipes +Only the grounding step is a deterministic CLI verb (`ghost-expression +emit context-bundle`). *Review*, *verify*, and *remediate* are skill recipes the host agent follows — installed with `ghost-drift emit skill`. +(Note: `generate.md` was dropped from scope in the five-tool decomposition. +Use any generator — the host agent itself, Cursor, v0, or an in-house tool — +with the emitted context bundle in its prompt; Ghost's job is grounding the +input and gating the output, not running the generator.) + ## Pipeline shape ``` -expression.md ──► [ghost-drift emit context-bundle] ──► SKILL.md / tokens.css / prompt.md +expression.md ──► [ghost-expression emit context-bundle] ──► SKILL.md / tokens.css / prompt.md │ ▼ any generator @@ -20,14 +25,14 @@ expression.md ──► [ghost-drift emit context-bundle] ──► SKILL.md in-house tool) │ ▼ HTML / JSX - [review recipe] ──► drift disposition - (block / annotate - / ack / track) + [review recipe — ghost-drift] ──► drift disposition + (block / annotate + / ack / track) ``` ## Pieces -### `ghost-drift emit context-bundle [flags]` — the one CLI verb +### `ghost-expression emit context-bundle [flags]` — the one CLI verb Emit a grounding bundle any generator can consume. Default output writes `SKILL.md` + `expression.md` + `tokens.css` into `./ghost-context/`. @@ -42,25 +47,25 @@ Flags: Point a Claude Code or MCP client at the output directory and the agent reads `SKILL.md`. -### The `generate` recipe - -Driven by the host agent. Loads the expression, builds a system prompt -from Character/Signature/Decisions + tokens, asks the underlying model, -extracts the artifact (HTML/JSX/etc.), and hands it to the `review` recipe -for self-check. Retries with drift feedback until it passes or the agent -gives up. +### Driving the generator -Not a replacement for Cursor / v0 / in-house tools. It exists so the loop -is provable end-to-end, and so the `verify` recipe has something to drive. +Driven by the host agent. Loads the expression (the agent typically pulls +just the sections it needs via `ghost-expression describe`), builds a system +prompt from Character/Signature/Decisions + tokens, asks the underlying +model, extracts the artifact (HTML/JSX/etc.), and hands it to the `review` +recipe for self-check. Retries with drift feedback until it passes or the +agent gives up. -Source: `packages/ghost-drift/src/skill-bundle/references/generate.md`. +This isn't a recipe Ghost ships — `generate.md` was dropped. The agent's +own driver code (or whatever generator it shells out to) owns this step. +Ghost's job is the bundle that goes in and the review that gates the output. ### The `review` recipe The agent diffs generated output against the expression. Flags hardcoded colors outside the palette, spacing off the scale, and type choices that violate decisions. For pre-baked, per-project review commands use -`ghost-drift emit review-command` (which writes a slash command at +`ghost-expression emit review-command` (which writes a slash command at `.claude/commands/design-review.md`). Source: `packages/ghost-drift/src/skill-bundle/references/review.md`. @@ -80,6 +85,16 @@ grounding. Source: `packages/ghost-drift/src/skill-bundle/references/verify.md`. +### The `remediate` recipe + +Once `review` flags drift, `remediate` walks the agent through the smallest +correction that lands the output back inside the expression. The output is +either a fix proposal (the agent applies it) or — when the drift turns out +to be intentional — a recommendation to record stance with `ghost-drift ack` +or `ghost-drift diverge` instead of correcting the code. + +Source: `packages/ghost-drift/src/skill-bundle/references/remediate.md`. + ## The standard prompt suite A versioned set of UI-construction tasks, each tagged with the expression @@ -104,13 +119,13 @@ over-specified. The `verify` recipe is the schema-discipline mechanism. ## Integration patterns **CI**: a per-project `design-review` slash command emitted from -`ghost-drift emit review-command`, invoked by the host agent as a required -check on PRs that touch UI files. +`ghost-expression emit review-command`, invoked by the host agent as a +required check on PRs that touch UI files. -**In a generation pipeline**: `ghost-drift emit context-bundle` writes the -skill bundle into the generator's context; the generator produces; the -`review` recipe gates the output. Drift disposition belongs to the -pipeline owner (block, annotate, require `ghost-drift ack`). +**In a generation pipeline**: `ghost-expression emit context-bundle` writes +the skill bundle into the generator's context; the generator produces; the +`review` recipe gates the output. Drift disposition belongs to the pipeline +owner (block, annotate, require `ghost-drift ack`). **Expression maintenance**: run `verify` periodically. When a dimension shows up consistently leaky, the expression needs more Decisions for diff --git a/packages/ghost-drift/README.md b/packages/ghost-drift/README.md index e48c992..0b708ca 100644 --- a/packages/ghost-drift/README.md +++ b/packages/ghost-drift/README.md @@ -1,8 +1,8 @@ # ghost-drift -**Deterministic design drift detection. Seven verbs. No LLM calls.** +**Deterministic design drift detection. Five verbs. No LLM calls.** -`ghost-drift` captures a design language as a human-readable `expression.md` (49-dim embedding + three-layer prose body) and gives any agent the primitives to detect drift against it. Judgement lives in whatever agent you already use; arithmetic lives here. +`ghost-drift` compares design-language expressions, records intent across drift, and ships the agentskills.io recipes a host agent uses to review, verify, and remediate. It pairs with **[`ghost-expression`](../ghost-expression)** — the package that owns authoring `expression.md` (the canonical 49-dim embedding + three-layer prose artifact this tool consumes). ## Requirements @@ -14,10 +14,10 @@ ```bash # latest release -npm install https://github.com/block/ghost/releases/download/ghost-drift%400.1.1/ghost-drift-0.1.1.tgz +npm install https://github.com/block/ghost/releases/download/ghost-drift%400.2.0/ghost-drift-0.2.0.tgz # pnpm / yarn work the same -pnpm add https://github.com/block/ghost/releases/download/ghost-drift%400.1.1/ghost-drift-0.1.1.tgz +pnpm add https://github.com/block/ghost/releases/download/ghost-drift%400.2.0/ghost-drift-0.2.0.tgz ``` Or pin in `package.json`: @@ -25,49 +25,62 @@ Or pin in `package.json`: ```json { "dependencies": { - "ghost-drift": "https://github.com/block/ghost/releases/download/ghost-drift%400.1.1/ghost-drift-0.1.1.tgz" + "ghost-drift": "https://github.com/block/ghost/releases/download/ghost-drift%400.2.0/ghost-drift-0.2.0.tgz" } } ``` -Once npm publishing is unblocked this will move to the registry — swap the URL for a plain `^0.1.1`. +Once npm publishing is unblocked this will move to the registry — swap the URL for a plain `^0.2.0`. ## Use ```bash -ghost-drift lint expression.md # validate schema + partition -ghost-drift compare a/expression.md b/expression.md # pairwise distance (N=2) -ghost-drift compare ./*/expression.md # composite, N≥3 -ghost-drift ack # acknowledge drift against the tracked expression -ghost-drift track path/to/new-tracked.md # track another expression -ghost-drift diverge # declare intentional divergence -ghost-drift emit skill # install the agent recipe bundle -ghost-drift emit review-command # emit a per-project review slash command -ghost-drift emit context-bundle # emit a generation context bundle +ghost-drift compare a/expression.md b/expression.md # pairwise distance (N=2) +ghost-drift compare ./*/expression.md # composite, N≥3 +ghost-drift compare a.md b.md --semantic # add qualitative diff +ghost-drift compare a.md b.md --temporal # add velocity / trajectory +ghost-drift ack # acknowledge drift against the tracked expression +ghost-drift track path/to/new-tracked.expression.md # track another expression +ghost-drift diverge # declare intentional divergence +ghost-drift emit skill # install the agent recipe bundle ``` Zero config for every verb. No API key needed. `OPENAI_API_KEY` / `VOYAGE_API_KEY` are optional and only consumed if you ask for a semantic-enriched embedding via the library. +### Authoring `expression.md`? + +Authoring lives in **[`ghost-expression`](../ghost-expression)**. Install it for `lint`, `describe`, `diff`, and `emit review-command` / `emit context-bundle`: + +```bash +ghost-expression lint # validate ./expression.md +ghost-expression describe # section ranges + token estimates +ghost-expression diff a.md b.md # structural prose-level diff +ghost-expression emit review-command # per-project slash command +ghost-expression emit context-bundle # generation context bundle +``` + +These verbs used to live under `ghost-drift`. They were moved in v0.2.0 — running them on `ghost-drift` now prints a deprecation message pointing here. + ## As a library ```ts import { - parseExpression, - lintExpression, - diffExpressions, compareExpressions, + loadExpression, + readHistory, + readSyncManifest, } from "ghost-drift"; -const { expression } = parseExpression(await readFile("expression.md", "utf8")); -const report = lintExpression(source); +const { expression: a } = await loadExpression("a/expression.md"); +const { expression: b } = await loadExpression("b/expression.md"); const distance = compareExpressions(a, b); ``` -All exports are browser-safe except the ones that read from disk (history, sync manifest, tracked-expression resolution). +Parsing, linting, layout, and diff utilities live in `ghost-expression` (re-exported from there). The shared embedding math lives in `@ghost/core`. All exports are browser-safe except the ones that read from disk (history, sync manifest, tracked-expression resolution). ## BYOA — bring your own agent -Ghost ships an [agentskills.io](https://agentskills.io)-compatible skill bundle that teaches your host agent (Claude Code, Codex, Cursor, Goose, …) how to **profile**, **review**, **verify**, **generate**, and **discover**. Install it with: +Ghost ships per-tool [agentskills.io](https://agentskills.io)-compatible bundles. The `ghost-drift` bundle teaches your host agent (Claude Code, Codex, Cursor, Goose, …) how to **review** drift in a PR, **verify** generated UI, **interpret** comparison output, and **remediate** with `ack` / `track` / `diverge`. Install it with: ```bash ghost-drift emit skill @@ -75,9 +88,11 @@ ghost-drift emit skill The agent runs the recipes; the CLI runs the arithmetic. The CLI never calls an LLM. +(Authoring recipes — `profile` for `expression.md` — ship in `ghost-expression`'s skill bundle. Topology and fleet recipes ship in `ghost-map` and `ghost-fleet` respectively.) + ## Full story -See the [project README](https://github.com/block/ghost#readme) for the philosophy, the expression format spec, composite comparison, and the reference design language (Ghost UI). +See the [project README](https://github.com/block/ghost#readme) for the philosophy, the five-tool decomposition, the expression format spec, composite comparison, and the reference design language (Ghost UI). ## License diff --git a/packages/ghost-expression/README.md b/packages/ghost-expression/README.md new file mode 100644 index 0000000..08ffa3d --- /dev/null +++ b/packages/ghost-expression/README.md @@ -0,0 +1,83 @@ +# ghost-expression + +**Author and validate `expression.md` — Ghost's canonical design-language artifact. Four verbs. No LLM calls.** + +`ghost-expression` owns the on-disk format every other Ghost tool reads. It parses, lints, lays out (section ranges + token estimates for selective loading), structurally diffs, and emits derived artifacts (per-project review slash commands, generation context bundles, agentskills.io skill bundles). + +The actual *writing* of an `expression.md` is a host-agent recipe — `profile.md` ships in this package's skill bundle and walks an agent through resolving design sources end-to-end. The CLI here is the deterministic gate at the end of that loop. + +For drift detection, comparison, and stance recording (`compare`, `ack`, `track`, `diverge`), see **[`ghost-drift`](../ghost-drift)**. + +## Requirements + +- Node.js **18+** + +## Install + +> `ghost-expression` is part of the same release as `ghost-drift`. Install from the GitHub release tarball until npm registration is unblocked: + +```bash +pnpm add https://github.com/block/ghost/releases/download/ghost-expression%400.0.0/ghost-expression-0.0.0.tgz +``` + +## Use + +```bash +ghost-expression lint # validate ./expression.md +ghost-expression lint path/to/expression.md # validate a specific file +ghost-expression lint expression.md --format json # machine-readable output + +ghost-expression describe # section ranges + token estimates for ./expression.md +ghost-expression describe expression.md --format json + +ghost-expression diff a/expression.md b/expression.md # structural prose-level diff + # (NOT vector distance — for that, use `ghost-drift compare`) + +ghost-expression emit review-command # → .claude/commands/design-review.md +ghost-expression emit context-bundle # → ghost-context/ (SKILL.md + tokens.css + prompt.md) +ghost-expression emit context-bundle --prompt-only # single prompt.md +ghost-expression emit skill # install the agent recipe bundle +``` + +Zero config for every verb. No API key needed. + +## As a library + +```ts +import { + parseExpression, + lintExpression, + layoutExpression, + diffExpressions, +} from "ghost-expression"; + +const { expression } = parseExpression(await readFile("expression.md", "utf8")); +const report = lintExpression(source); +const layout = layoutExpression(source); // section ranges + token estimates +const diff = diffExpressions(a, b); // structural prose diff +``` + +All exports are browser-safe. + +## BYOA — bring your own agent + +Install the skill bundle so your agent can author against the schema: + +```bash +ghost-expression emit skill +``` + +The bundle ships: + +- `profile.md` — recipe for writing `expression.md` from a project (mode-branched: `target` / `module` / `rollup`). +- `schema.md` — condensed reference to the frontmatter schema and three-layer body. + +Once installed, ask your agent to "profile this design language" and it'll follow the recipe, ending at `ghost-expression lint` for the deterministic success gate. + +## Canonical artifact + +See [`docs/expression-format.md`](https://github.com/block/ghost/blob/main/docs/expression-format.md) for the full spec, including the 49-dim machine-vector breakdown (palette [0–20], spacing [21–30], typography [31–40], surfaces [41–48]). + +## License + +Apache-2.0 diff --git a/scripts/check-docs-frontmatter.mjs b/scripts/check-docs-frontmatter.mjs index 26ac4e6..4152ac4 100644 --- a/scripts/check-docs-frontmatter.mjs +++ b/scripts/check-docs-frontmatter.mjs @@ -11,7 +11,7 @@ const Schema = z.object({ title: z.string().min(1), description: z.string().min(1), kicker: z.string().optional(), - section: z.enum(["drift", "ui"]).optional(), + section: z.enum(["guide", "drift", "ui"]).optional(), order: z.number(), slug: z.string().min(1), route: z.string().optional(), @@ -32,7 +32,13 @@ function walk(dir) { function routeFor(fm) { if (fm.route) return fm.route; - const prefix = (fm.section ?? "drift") === "drift" ? "/tools/drift" : "/ui"; + const section = fm.section ?? "guide"; + const prefix = + section === "guide" + ? "/docs" + : section === "drift" + ? "/tools/drift" + : "/ui"; return `${prefix}/${fm.slug}`; } @@ -69,8 +75,13 @@ for (const { file, fm: _fm } of parsed) { const KNOWN = [ "/", "/tools", + "/tools/map", + "/tools/expression", "/tools/drift", "/tools/drift/workflow", + "/tools/fleet", + "/tools/ui", + "/docs", "/ui", "/ui/foundations", "/ui/foundations/colors", diff --git a/scripts/dump-cli-help.mjs b/scripts/dump-cli-help.mjs index 89c6355..821cd2d 100644 --- a/scripts/dump-cli-help.mjs +++ b/scripts/dump-cli-help.mjs @@ -1,65 +1,78 @@ #!/usr/bin/env node -// Walk the cac command registry from the built ghost-drift package and -// emit a JSON manifest of commands, flags, and descriptions. The docs -// site renders this manifest via , so the CLI -// is the single source of truth — adding a flag to bin.ts (via cli.ts) -// propagates to the docs on the next build. +// Walk the cac command registry from each built Ghost tool and emit a +// JSON manifest of commands, flags, and descriptions per tool. The docs +// site renders this manifest via , so +// the CLIs are the single source of truth — adding a flag to a bin.ts +// (via cli.ts) propagates to the docs on the next build. import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { pathToFileURL } from "node:url"; const ROOT = process.cwd(); -const CLI_DIST = resolve(ROOT, "packages/ghost-drift/dist/cli.js"); -if (!existsSync(CLI_DIST)) { - console.error( - `ghost-drift dist not built. Run \`pnpm --filter ghost-drift build\` first.`, - ); - process.exit(1); -} -const { buildCli } = await import(pathToFileURL(CLI_DIST).href); + +const TOOLS = [ + { name: "ghost-drift", dist: "packages/ghost-drift/dist/cli.js" }, + { name: "ghost-expression", dist: "packages/ghost-expression/dist/cli.js" }, + { name: "ghost-map", dist: "packages/ghost-map/dist/cli.js" }, + { name: "ghost-fleet", dist: "packages/ghost-fleet/dist/cli.js" }, +]; + const OUT = resolve(ROOT, "apps/docs/src/generated/cli-manifest.json"); -const cli = buildCli(); +const tools = []; +for (const tool of TOOLS) { + const cliDist = resolve(ROOT, tool.dist); + if (!existsSync(cliDist)) { + console.error( + `${tool.name} dist not built. Run \`pnpm --filter ${tool.name} build\` first (or \`pnpm build\`).`, + ); + process.exit(1); + } + const { buildCli } = await import(pathToFileURL(cliDist).href); + const cli = buildCli(); -const commands = cli.commands.map((cmd) => ({ - name: cmd.name, - rawName: cmd.rawName, - description: cmd.description, - options: cmd.options.map((o) => ({ + const commands = cli.commands.map((cmd) => ({ + tool: tool.name, + name: cmd.name, + rawName: cmd.rawName, + description: cmd.description, + options: cmd.options.map((o) => ({ + rawName: o.rawName, + name: o.name, + description: o.description, + default: o.config?.default ?? null, + takesValue: /<[^>]+>/.test(o.rawName), + negated: Boolean(o.negated), + })), + })); + + const globalOptions = cli.globalCommand.options.map((o) => ({ rawName: o.rawName, name: o.name, description: o.description, default: o.config?.default ?? null, - takesValue: /<[^>]+>/.test(o.rawName), - negated: Boolean(o.negated), - })), -})); + })); -const globalOptions = cli.globalCommand.options.map((o) => ({ - rawName: o.rawName, - name: o.name, - description: o.description, - default: o.config?.default ?? null, -})); + tools.push({ tool: tool.name, commands, globalOptions }); +} -// Intentionally omits `version`: the CLI reads its version from +// Intentionally omits `version`: each CLI reads its version from // package.json at runtime, so baking it in here would make every // Changesets-driven version bump drift the committed manifest and // fail CI. Command/flag shape is what the docs render — that's the // only signal the drift check needs to guard. const manifest = { generatedAt: new Date().toISOString(), - commands, - globalOptions, + tools, }; mkdirSync(dirname(OUT), { recursive: true }); const serialized = `${JSON.stringify(manifest, null, 2)}\n`; -// Drift check: bail if the committed manifest differs from what the CLI -// currently emits. This keeps the docs and the CLI in lockstep under CI. +// Drift check: bail if the committed manifest differs from what the CLIs +// currently emit. This keeps the docs and the CLIs in lockstep under CI. if (process.argv.includes("--check")) { if (!existsSync(OUT)) { console.error( @@ -82,6 +95,7 @@ if (process.argv.includes("--check")) { } writeFileSync(OUT, serialized); +const totalCommands = tools.reduce((sum, t) => sum + t.commands.length, 0); console.log( - `dump-cli-help: wrote ${commands.length} commands -> ${OUT.replace(`${ROOT}/`, "")}`, + `dump-cli-help: wrote ${totalCommands} command${totalCommands === 1 ? "" : "s"} across ${tools.length} tool${tools.length === 1 ? "" : "s"} -> ${OUT.replace(`${ROOT}/`, "")}`, );