diff --git a/.changeset/agent-first-docs-framing.md b/.changeset/agent-first-docs-framing.md new file mode 100644 index 00000000..1927f355 --- /dev/null +++ b/.changeset/agent-first-docs-framing.md @@ -0,0 +1,5 @@ +--- +"@design-intelligence/ghost": patch +--- + +Center the public documentation on how agents find and apply brand guidance, with the CLI in a supporting role. diff --git a/README.md b/README.md index a0f5ce6c..14135fa0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # ghost -**ghost is your brand, packed for agents.** A `.ghost/` folder of plain-prose -guidance (your stance, your voice, your trust moves) checked into the repo and -read by any agent before it makes anything: a screen, an email, an empty -state, a sentence. +Use ghost to give agents applicable brand guidance before they start work. A +`.ghost/` package stores your stance, voice, trust moves, and concrete materials +in the repo. Agents select and read that guidance while working on a screen, +email, empty state, or sentence. ```text .ghost/ @@ -13,12 +13,10 @@ state, a sentence. asset.logo.md # points at the actual SVGs ``` -Today those decisions live in reviewers' heads: "that's not our voice," again, -on every surface. The agent that built the thing never saw them. ghost writes -them down once, where the agent looks first. +Reviewers repeat the same feedback on every surface: "that's not our voice." +Write the decision in `.ghost/` so the next agent has it before starting work. -One portable packet; Claude Code, Codex, Cursor, and Goose all read the same -one. One package, `@design-intelligence/ghost`. One CLI, `ghost`. +Claude Code, Codex, Cursor, and Goose can all use the same guidance. [Documentation](https://block.github.io/ghost/) · [npm](https://www.npmjs.com/package/@design-intelligence/ghost) @@ -42,25 +40,33 @@ Brief this work from the ghost package. Review this diff against the ghost checks. ``` -ghost never calls an LLM itself; your agent does the thinking. No API key, -no lock-in. +Your agent selects, interprets, and applies the guidance. The CLI handles the +repeatable work without calling an LLM, so ghost needs no API key and does not +lock you into one agent. -## The Loop +## Use Guidance While Making + +Your agent works with the package through a small set of commands: ```bash ghost init # scaffold .ghost/ with the skeleton starter ghost checks init # opt in to review assertions ghost validate # make sure the package is well-formed -ghost gather # before building: list all available guidance +ghost gather [ask] # before building: show the complete guidance menu ghost pull # read the picked nodes' full bodies ghost review # during review: match a diff to guidance and checks ghost export # bundle the guidance as a portable artifact ghost pulse # while tuning: see what agents reached for ``` -ghost keeps a private local log of what agents reached for; `ghost pulse` -reads it so you can tune descriptions. It stays on your machine and never -enters version control. +For a task-specific gather, your agent reads the complete, unfiltered menu and +pulls every node whose stated situation applies. Bare `ghost gather` inspects +the catalog without grounding a task. Because only selected nodes enter the +working context, the agent can see the shape of the brand without loading the +whole package. + +`gather` and `pull` write a Git-ignored local log. Use `ghost pulse` to inspect +it and tune node descriptions. Run `ghost --help` for the core workflow and `ghost --help` for flags; the [CLI reference](https://block.github.io/ghost/docs/cli) covers @@ -68,22 +74,21 @@ every command. ## Thesis -Agents changed the unit of design work. When they make the screens, the -emails, and the sentences, polishing any one of them moves nothing; the next -generation starts from the model's average again. The work that compounds is -architectural: decide where that average serves, decide where the brand must -win, and put those decisions where the agent reads before it makes. +Agents now make screens, emails, and sentences. Polishing one output does not +help the next generation. Record where the model's default is good enough and +where the brand must differ, then give those decisions to the agent before it +starts. -ghost is that artifact: a `.ghost/` package checked into the repo, carrying the -guidance, the materials they point at, and the conditions they hold under. -Buttons stay buttons. The moments that carry your brand get your stance -instead of the default. The few author it once. Every agent it travels to -builds from it. +A `.ghost/` package keeps that guidance in the repo with the materials it +points at and the conditions where it applies. Buttons stay buttons. The +moments that carry your brand get your stance instead of the default. Write the +decision once, and each agent can use it when the same situation returns. ## How It Works -A ghost package is a small folder of prose. The CLI computes; your agent reads, -writes, and decides. +A ghost package is a small folder of prose. Your agent finds the guidance that +applies, reads it, and uses it while making. The CLI supports that work with +repeatable commands for scaffolding, validation, retrieval, and review. ```text .ghost/ @@ -99,7 +104,7 @@ The package is a **flat set of nodes**. The optional `cover:` in `manifest.yml` may name any node; `ghost gather` inlines it before the menu. The default skeleton calls that node `brand`, but the filename is not reserved. A node is one markdown file: a `description` in frontmatter, optional -`materials`, and a brand guidance in the prose body. +`materials`, and brand guidance in the prose body. ```markdown --- @@ -140,28 +145,22 @@ references: Grade whether the change preserves the logo guidance in `asset.logo`. ``` -`gather` and `pull` run before your agent builds. `review` runs after: it -reads a diff, matches touched files to node `materials`, offers relevant -checks, and emits an advisory packet for the host agent to weigh. Review -output never enters generation context. - -The packet is the product; the CLI is the courier. Everything above -(gather, pull, review, checks, the local log) is machinery around the -brand guidance, and the guidance outlives all of it. +`gather` and `pull` give your agent applicable guidance before it builds. +`review` supports the same agent after a change exists: the CLI reads a diff, +matches touched files to node `materials`, and offers relevant checks for the +agent to weigh. Review output never enters generation context. -## Portable by Design +## The Package Travels -The package travels. It is agent-agnostic (every host agent reads the same -packet), medium-agnostic (the same guidance steers a screen, a page, an email, a -sentence), and repo-native (it moves with a clone, a fork, a new hire's first -checkout). When you need it as a standalone artifact: +Different agents can read the same guidance and apply it to a screen, page, +email, or sentence. The package moves with the repo when someone clones or +forks it. To move the package on its own: ```bash ghost export ``` -The export audits every `materials` entry so the packet doesn't silently -point at things that moved. +The export audits `materials` entries and reports paths that moved. ## Project Status: Beta diff --git a/apps/docs/.ghost/anti-goal.generated-docs-site.md b/apps/docs/.ghost/anti-goal.generated-docs-site.md index e5134c6b..4096d470 100644 --- a/apps/docs/.ghost/anti-goal.generated-docs-site.md +++ b/apps/docs/.ghost/anti-goal.generated-docs-site.md @@ -6,7 +6,7 @@ materials: - apps/docs/src/components/docs/doc-prose.tsx --- -Ghost is not the generated AI SaaS docs site. +ghost is not the generated AI SaaS docs site. Reject these defaults even when they are easy to build: @@ -16,13 +16,13 @@ Reject these defaults even when they are easy to build: dashboard screenshot. - Mascots, emoji headings, playful empty-state illustrations, or robot metaphors. - Overpromising copy: "ship on-brand every time," "brand autopilot," "AI that - understands your design system," or any claim that makes Ghost sound like it + understands your design system," or any claim that makes ghost sound like it replaces taste, review, or human curation. - Decorative motion that exists only because the page felt static. - Dense nav chrome, sidebars on landing pages, or tool-index pages that feel like enterprise documentation portals. A technically correct output can still be off-brand if it looks like any AI tool -could ship it. Ghost's docs win by being severe, specific, and a little haunted: +could ship it. ghost's docs win by being severe, specific, and a little haunted: black-and-white, exact language, spectral geometry, and enough restraint that the one strange gesture actually lands. diff --git a/apps/docs/.ghost/asset.docs-materials.md b/apps/docs/.ghost/asset.docs-materials.md index 3994fc9a..6d65626b 100644 --- a/apps/docs/.ghost/asset.docs-materials.md +++ b/apps/docs/.ghost/asset.docs-materials.md @@ -3,7 +3,8 @@ description: Concrete docs-site materials — inspect before inventing tokens, c materials: - apps/docs/src/App.tsx - apps/docs/src/main.tsx - - apps/docs/src/styles/dev-fonts.css + - apps/docs/src/styles/docs.css + - apps/docs/src/styles/marked-doc.css - apps/docs/src/components/docs/** - apps/docs/src/app/**/*.tsx - apps/docs/src/content/docs/*.mdx @@ -23,15 +24,13 @@ Material contract: `bg-card`) and the named docs typography variables (`--heading-*`, `--label-*`). Avoid raw palette utilities unless they are already part of a documented semantic exception. -- **Fonts** are overridden for the docs dev surface in - `apps/docs/src/styles/dev-fonts.css`; `font-display` and `font-sans` resolve to - Cash Sans locally. Preserve the uppercase label and black display-type rhythm. +- **Typography** uses the docs styles and Vessel's semantic type roles. Preserve + the compact monospace labels, lowercase headings, and restrained body rhythm. - **Navigation** is the bottom floating `Dock`. New top nav, side nav, or secondary persistent chrome should be treated as a product decision, not a routine addition. -- **Page structure** lives in `AnimatedPageHeader`, `SectionWrapper`, - `DocsPageLayout`, `DocSection`, and `DocProse`. Use these before making a new - layout primitive. +- **Page structure** lives in `PageHeader`, `SectionWrapper`, `DocsPageLayout`, + `DocSection`, and `DocProse`. Use these before making a new layout primitive. - **Docs content** is MDX under `apps/docs/src/content/docs/` with frontmatter wired through the docs manifest and routes. Do not hard-code a one-off article route unless the content model cannot express it. @@ -39,5 +38,5 @@ Material contract: or regenerated with `pnpm dump:cli-help` after CLI command/flag changes. The materials locate facts; this node does not make every existing choice a -brand truth. If a component repeats a weak or legacy pattern, fix the pattern +brand guidance. If a component repeats a weak or legacy pattern, fix the pattern instead of canonizing it here. diff --git a/apps/docs/.ghost/checks/cli-examples-match-manifest.md b/apps/docs/.ghost/checks/cli-examples-match-manifest.md index bc938bcd..2a81e81e 100644 --- a/apps/docs/.ghost/checks/cli-examples-match-manifest.md +++ b/apps/docs/.ghost/checks/cli-examples-match-manifest.md @@ -1,6 +1,6 @@ --- name: cli-examples-match-manifest -description: CLI examples and generated docs stay synchronized with the real Ghost command surface. +description: CLI examples and generated docs stay synchronized with the real ghost command surface. severity: high references: - condition.cli-reference-exactness diff --git a/apps/docs/.ghost/checks/no-generic-ai-ornament.md b/apps/docs/.ghost/checks/no-generic-ai-ornament.md index f8bafddb..0e1057e1 100644 --- a/apps/docs/.ghost/checks/no-generic-ai-ornament.md +++ b/apps/docs/.ghost/checks/no-generic-ai-ornament.md @@ -1,6 +1,6 @@ --- name: no-generic-ai-ornament -description: Docs UI avoids generic AI SaaS ornament and preserves Ghost's restrained monochrome visual stance. +description: Docs UI avoids generic AI SaaS ornament and preserves ghost's restrained monochrome visual stance. severity: medium references: - anti-goal.generated-docs-site @@ -15,4 +15,4 @@ state. This check should not block legitimate semantic color in docs prose or existing Vessel component behavior. The issue is generic ornament that weakens the severe, -monochrome, typographic Ghost stance. +monochrome, typographic ghost stance. diff --git a/apps/docs/.ghost/checks/review-stays-advisory.md b/apps/docs/.ghost/checks/review-stays-advisory.md index 66f16490..53f53324 100644 --- a/apps/docs/.ghost/checks/review-stays-advisory.md +++ b/apps/docs/.ghost/checks/review-stays-advisory.md @@ -1,16 +1,16 @@ --- name: review-stays-advisory -description: Docs keep checks and review positioned as feed-back advisory context, never deterministic brand verdict or generation input. +description: Docs keep checks and review advisory and after generation, never a brand verdict or generation input. severity: high references: - principle.product-model - decision.feed-forward-over-review --- -Review docs copy for claims that Ghost review grades, approves, blocks, judges, +Review docs copy for claims that ghost review grades, approves, blocks, judges, or guarantees brand fit. Flag wording that treats checks as generation input or turns advisory review packets into CI gates. -Accept wording that says checks are optional deterministic assertions, review -assembles packets, host agents judge, and teams may choose to enforce separate -CI around their own deterministic checks. +Accept wording that says checks are optional review assertions, `ghost review` +supplies evidence, agents judge, and teams may enforce separate CI around +their own automated checks. diff --git a/apps/docs/.ghost/decision.feed-forward-over-review.md b/apps/docs/.ghost/decision.feed-forward-over-review.md index ff0feae8..67d06f07 100644 --- a/apps/docs/.ghost/decision.feed-forward-over-review.md +++ b/apps/docs/.ghost/decision.feed-forward-over-review.md @@ -3,29 +3,28 @@ description: Tradeoff behind leading with gather/pull before review — gather w materials: - apps/docs/src/content/docs/getting-started.mdx - apps/docs/src/content/docs/checks-and-review.mdx - - apps/docs/src/app/tools/scan/page.tsx - - apps/docs/src/app/tools/drift/page.tsx + - apps/docs/src/content/docs/cli-reference.mdx --- -Decision trace: Ghost could have led with review because drift detection is easy -to understand. We choose feed-forward authoring and recall as the center because -that is the product's real leverage: the agent should receive the decision before -it builds. +Decision trace: ghost could have led with review because drift detection is easy +to understand. We lead with authoring, gather, and pull because the agent should +receive the decision before it builds. What review is good for: - It makes drift visible after a diff. - It routes changed files to material-backed nodes and checks. -- It gives a host agent an advisory packet for critique. +- It gives the agent grounded evidence for critique. Why review does not lead: -- If the right truth was never gathered, review catches the failure late. -- Calling review a gate makes Ghost sound like it judges brand fit +- If the right guidance was never gathered, review catches the failure late. +- Calling review a gate makes ghost sound like it judges brand fit deterministically, which it does not. - Teams adopt faster when the first win is one repeated decision written down and reused before generation. The decision reverses only for a page whose explicit job is `ghost review`, the -checks directory, or CI-style integration. Even there, repeat the boundary: review is -feed-back and advisory; checks do not leak into generation context. +checks directory, or CI-style integration. Even there, repeat the boundary: +review happens after a change exists and remains advisory; checks do not enter +generation context. diff --git a/apps/docs/.ghost/exemplar.home-thesis.md b/apps/docs/.ghost/exemplar.home-thesis.md index 4446c24b..d9bac5e6 100644 --- a/apps/docs/.ghost/exemplar.home-thesis.md +++ b/apps/docs/.ghost/exemplar.home-thesis.md @@ -1,5 +1,5 @@ --- -description: Annotated home-page thesis exemplar — match this form when writing high-level Ghost narrative or landing copy. +description: Annotated home-page thesis exemplar — match this form when writing high-level ghost narrative or landing copy. materials: - apps/docs/src/app/page.tsx - apps/docs/src/components/docs/hero.tsx @@ -11,17 +11,17 @@ pace. What to match: - **Tiny hero, huge word.** The hero does not explain the whole product. It says - "Ghost" at wordmark scale and gives one sentence of context. That restraint is + "ghost" at wordmark scale and gives one sentence of context. That restraint is the brand move. - **Editorial thesis cadence.** The thesis unfolds in paragraphs, not a grid of benefits. It trusts the reader to follow a serious idea. -- **Mechanics over adjectives.** The copy names concrete mechanics — flat corpus, - `gather`, `pull`, `review`, Git approval — instead of claiming the product is - simple, powerful, or magical. +- **Interaction over architecture.** The copy shows the agent finding and using + applicable guidance. Name commands when they clarify that interaction, not to + make the CLI the protagonist. - **Selective emphasis.** Foreground text highlights only the load-bearing model phrase. Do not bold every important sentence. - **Haunted restraint.** The concentric circles are spectral and low-opacity; - they make the page feel like Ghost without becoming decoration the prose must + they make the page feel like ghost without becoming decoration the prose must fight. Do not copy incidentals: the exact paragraph count, current CLI ordering, and the diff --git a/apps/docs/.ghost/glossary.md b/apps/docs/.ghost/glossary.md index cad2d372..5459ea02 100644 --- a/apps/docs/.ghost/glossary.md +++ b/apps/docs/.ghost/glossary.md @@ -18,7 +18,7 @@ loosen them. # condition -Situational truth. Use only when the stated situation holds, and do not treat the +Situational guidance. Use only when the stated situation holds, and do not treat the condition as a destination bucket. # pattern @@ -39,7 +39,7 @@ from. # asset -Material truth about concrete tokens, components, type, motion, canvases, docs, +Material guidance about concrete tokens, components, type, motion, canvases, docs, copy, code, or files. `materials` locates; prose explains what the files mean. # decision @@ -49,5 +49,5 @@ choice would reverse. # voice -Language and naming truth: the product vocabulary, cadence, metaphor, and copy -boundaries that make the docs sound like Ghost rather than generic SaaS docs. +Language and naming guidance: the product vocabulary, cadence, metaphor, and copy +boundaries that make the docs sound like ghost rather than generic SaaS docs. diff --git a/apps/docs/.ghost/index.md b/apps/docs/.ghost/index.md index cd5150e4..5d2cfe2d 100644 --- a/apps/docs/.ghost/index.md +++ b/apps/docs/.ghost/index.md @@ -1,23 +1,24 @@ --- -description: Always read first for docs-site work — non-negotiables, coverage, and silence posture for the Ghost docs surface. +description: Always read first for docs-site work — non-negotiables, coverage, and silence posture for the ghost docs surface. materials: - apps/docs/src/app/page.tsx - apps/docs/src/content/docs/*.mdx - apps/docs/src/components/docs/** --- -This fingerprint governs the Ghost docs site: the landing page, documentation -index, tool landings, MDX docs pages, and the small components that carry them. +This ghost package governs ghost's public explanation: the root and package +READMEs, landing page, documentation index, MDX docs pages, and the small +components that carry them. It does not govern Vessel as a reference component registry; Vessel has its own -fingerprint under `packages/vessel-react/.ghost/`. When the docs consume Vessel -materials, this fingerprint decides the docs' product stance and Vessel decides +ghost package under `packages/vessel-react/.ghost/`. When the docs consume Vessel +materials, this package decides the docs' product stance and Vessel decides the component-system contract. ## Non-negotiables -- Ghost is **brand context for agents**, not a component library, lifecycle +- ghost is **brand context for agents**, not a component library, lifecycle manager, design archive, or autonomous judge. Keep the docs centered on the - feed-forward act: the agent reads repo-local brand truth before it builds. + main interaction: the agent reads repo-local brand guidance before it builds. - Preserve the flat corpus model: `.ghost/` is a package of prose nodes; `manifest.yml`, `glossary.md`, and `checks/` are reserved; everything else is a node whose id comes from its filename. No hierarchy, inheritance, graph, or @@ -29,8 +30,9 @@ the component-system contract. `ghost gather`, `ghost pull`, `ghost pulse`, `ghost checks init`, and `ghost review` exactly unless the CLI changes and the generated manifest has been updated. -- Do not let optional review language obscure the boundary: checks and review - are feed-back, advisory, and never generation input. +- Do not let optional review language obscure the main interaction: checks and + review happen after a change exists, remain advisory, and never become + generation input. ## How to read the corpus @@ -47,7 +49,7 @@ pulls off the stance; match the annotated qualities, not every literal detail. ## Silence posture -When this fingerprint is silent, proceed provisionally from nearby docs pages and +When this package is silent, proceed provisionally from nearby docs pages and Vessel's token/component contract for routine implementation. Ask before changing product model vocabulary, introducing new visual metaphors, adding a new agent workflow, or making the docs sound more like marketing than instruction. diff --git a/apps/docs/.ghost/pattern.docs-index-card-grid.md b/apps/docs/.ghost/pattern.docs-index-card-grid.md index 946fa03b..c97d9565 100644 --- a/apps/docs/.ghost/pattern.docs-index-card-grid.md +++ b/apps/docs/.ghost/pattern.docs-index-card-grid.md @@ -1,39 +1,29 @@ --- -description: Docs and tool index card grids — use for /docs, /tools, and per-tool landing pages that route users to a small set of next reads. +description: Docs index rows — use for /docs and other routing pages that point users to a small set of next reads. materials: - apps/docs/src/app/docs/page.tsx - - apps/docs/src/app/tools/page.tsx - - apps/docs/src/app/tools/scan/page.tsx - - apps/docs/src/app/tools/drift/page.tsx - - apps/docs/src/components/docs/animated-page-header.tsx + - apps/docs/src/components/docs/doc-index.tsx + - apps/docs/src/components/docs/page-header.tsx --- -This pattern applies when a page is a routing surface: it names a section of the -product and offers a small set of next reads or tools. +This pattern applies when a page routes readers to a small set of next steps. **Bound:** -- Start with `AnimatedPageHeader`: kicker, blunt title, one sentence of routing - context, and the short horizontal rule. -- Use a low-count grid. Two or three columns are enough; if the page needs more - than five cards, reconsider the information architecture before adding visual - density. -- Cards stay quiet by default: border-card, card background, muted icon, compact - title, one short description. The hover state can darken the border and invert - a title underline, but it should not become a whole animated tile. -- Card copy explains the job of the destination, not the feature in abstract. - Prefer "Emit Available guidance with gather" over "Powerful context - discovery." -- Icons are thin-line Lucide symbols at the existing sizes and stroke widths. - They support scanning; they are not illustrations. +- Start with `PageHeader`: a blunt title and only the description needed to + orient the reader. +- Use low-count `DocIndex` rows with numbered names and one short description. +- Route copy explains what the reader can do at the destination. Prefer "Exact + commands, flags, outputs, and exit behavior" over feature language. +- Keep rows typographic. Do not add icons, screenshots, badges, or decorative + cards to make a short index feel fuller. **Open:** -- Grid cardinality and card ordering may change with the docs IA. -- The exact icon can change when it makes the destination easier to recognize. -- A tool page may use a tighter chip strip instead of large cards when the - choices are secondary. +- Row count and ordering may change with the docs IA. +- Sections may split the index when they reflect a real difference in reader + intent, such as learning versus reference. -**Refines:** `principle.visual-composition` and `voice.docs-language`. If a -proposed card grid needs bright accents, screenshots, badges, or paragraph-long -blurbs to feel useful, the content structure is wrong. +**Refines:** `principle.visual-composition` and `voice.docs-language`. If the +index needs ornament or paragraph-long descriptions to feel useful, improve the +information structure. diff --git a/apps/docs/.ghost/pattern.landing-thesis.md b/apps/docs/.ghost/pattern.landing-thesis.md index 57fb5a8e..9d71f33f 100644 --- a/apps/docs/.ghost/pattern.landing-thesis.md +++ b/apps/docs/.ghost/pattern.landing-thesis.md @@ -7,7 +7,7 @@ materials: --- The home page is an argument, not a feature tour. It should make one memorable -claim about why Ghost exists and then let the docs carry the details. +claim about why ghost exists and then let the docs carry the details. **Bound:** @@ -17,11 +17,11 @@ claim about why Ghost exists and then let the docs carry the details. few foreground highlights for key model words. The rhythm is editorial, not sales-led. - The story moves from problem to model to loop to payoff: agents can assemble - UI, but not preserve brand truth; Ghost captures flat prose nodes; the agent + UI, but not preserve brand guidance; ghost captures flat prose nodes; the agent gathers and pulls them; review makes drift visible. - Lists should clarify the model. If a list reads like benefits marketing, rewrite it as artifacts and mechanics. -- End with a transfer claim: a brand truth that cannot be recalled or reviewed +- End with a transfer claim: a brand guidance that cannot be recalled or reviewed against cannot be delegated. **Open:** diff --git a/apps/docs/.ghost/principle.product-model.md b/apps/docs/.ghost/principle.product-model.md index 33d1ab02..f904f9db 100644 --- a/apps/docs/.ghost/principle.product-model.md +++ b/apps/docs/.ghost/principle.product-model.md @@ -1,31 +1,35 @@ --- -description: Core Ghost product model — gather for docs copy, IA, onboarding, diagrams, workflow explanations, or any statement of what Ghost is. +description: Core ghost product model — gather for docs copy, IA, onboarding, diagrams, workflow explanations, or any statement of what ghost is. materials: + - README.md + - packages/ghost/README.md - apps/docs/src/app/page.tsx - apps/docs/src/content/docs/getting-started.mdx - - apps/docs/src/content/docs/fingerprint-authoring.mdx + - apps/docs/src/content/docs/authoring.mdx - apps/docs/src/content/docs/checks-and-review.mdx - packages/ghost/src/commands/** --- -Ghost is a small deterministic layer around an interpretive human-agent loop. -The docs should keep that model crisp: +ghost gives agents applicable, repo-local brand guidance before they make. The +docs should keep three roles clear: -- **Feed-forward first.** Ghost helps the agent read the right repo-local brand - truths before it builds. Review is useful, but it is the second half of the - loop, not the headline. -- **BYOA, not an autonomous designer.** The host agent reads, selects, writes, - and judges. The CLI performs repeatable work: scaffold, validate, gather the - menu, pull selected nodes, summarize local events, and assemble advisory - review packets. -- **Flat node corpus.** A fingerprint is a flat `.ghost/` package of markdown +- **The `.ghost/` package holds the guidance.** It keeps brand decisions and + concrete materials with the work. +- **The agent authors and uses it.** The agent reads, selects, interprets, + applies, and judges. ghost is BYOA, not an autonomous designer. +- **The CLI supports the interaction.** It performs repeatable work: scaffold, + validate, gather the menu, pull selected nodes, summarize local events, and + assemble review evidence. +- **Guidance before review.** Give the agent applicable guidance before it + builds. Review is useful after a change exists, but it is not the headline. +- **Flat node corpus.** A ghost package is a flat `.ghost/` package of markdown prose nodes. Kinds come from filename prefixes declared in `glossary.md`. - Altitude lives in prose; narrower truths name their condition. Do not describe + Altitude lives in prose; narrower guidance names their condition. Do not describe folders, inheritance, edge traversal, or schema fields as the conceptual model. -- **Prose is executable context only through use.** A node steers because an - agent can find it, read it, and manifest it. Keep retrieval handles and command - examples close to abstract claims. -- **Git is the approval boundary.** Uncommitted fingerprint edits are drafts; +- **Guidance matters through use.** A node helps only when an agent can find it, + read it, and apply it. Keep descriptions and command examples close to + abstract claims. +- **Git is the approval boundary.** Uncommitted ghost package edits are drafts; checked-in node prose is canonical through normal review. When the docs must choose between theoretical completeness and a simple first diff --git a/apps/docs/.ghost/principle.visual-composition.md b/apps/docs/.ghost/principle.visual-composition.md index 3fb9c6a7..f338d2bc 100644 --- a/apps/docs/.ghost/principle.visual-composition.md +++ b/apps/docs/.ghost/principle.visual-composition.md @@ -3,12 +3,11 @@ description: Visual composition floor for the docs site — gather before changi materials: - apps/docs/src/app/page.tsx - apps/docs/src/app/docs/page.tsx - - apps/docs/src/app/tools/**/*.tsx - apps/docs/src/components/docs/** - packages/vessel-react/src/styles/main.css --- -The Ghost docs site should render like a stark technical artifact with one +The ghost docs site should render like a stark technical artifact with one controlled spectral move, not a generic docs template. Hard floor: @@ -35,4 +34,4 @@ Hard floor: When a pattern conflicts with this floor, the floor wins and the pattern is wrong. When Vessel tokens make a choice available but this floor rejects the -output, choose the docs fingerprint. +output, choose the docs package. diff --git a/apps/docs/.ghost/voice.docs-language.md b/apps/docs/.ghost/voice.docs-language.md index fd590929..f0c239f7 100644 --- a/apps/docs/.ghost/voice.docs-language.md +++ b/apps/docs/.ghost/voice.docs-language.md @@ -1,41 +1,54 @@ --- -description: Ghost docs voice and vocabulary — gather before writing docs copy, headings, onboarding text, CLI examples, or product explanations. +description: ghost docs voice and vocabulary — gather before writing docs copy, headings, onboarding text, CLI examples, or product explanations. materials: - apps/docs/src/content/docs/*.mdx - apps/docs/src/app/page.tsx - apps/docs/src/app/docs/page.tsx - - apps/docs/src/app/tools/**/*.tsx + - README.md + - packages/ghost/README.md --- -Ghost docs should sound like an opinionated tool builder explaining a sharp +ghost docs should sound like an opinionated tool builder explaining a sharp model, not like a launch page selling a platform. Voice rules: - Lead with the user's repeated pain: the same review comment, the same drift, - the same missing decision. Then show the small move Ghost makes possible. -- Prefer short declarative sentences around the model: "The CLI does the - deterministic work; your agent does the interpretation." Do not bury the - boundary in hedging. + the same missing decision. Then show the small move ghost makes possible. +- Put the agent interaction first: the `.ghost/` package holds guidance, the + agent selects and applies it, and the CLI handles repeatable support work. + Name CLI mechanics only when they help someone use or understand a command. - Use concrete artifact names: `.ghost/`, `manifest.yml`, `glossary.md`, the manifest-declared cover, `checks/`, `ghost gather`, `ghost pull`. The docs earn trust by naming the files and commands agents actually touch. -- Say **brand truth**, **fingerprint**, **node**, **kind**, **materials**, - **feed-forward**, **feed-back**, and **advisory review packet** - consistently. Do not introduce synonyms such as brand DNA, style cache, - design brain, magic layer, or guardrail engine. -- Explain by contrast: feed-forward versus feed-back, deterministic CLI versus - interpretive agent, flat corpus versus traversal, testimony versus truth, - draft versus canonical. -- Keep the claim plain when the idea is abstract. A good Ghost sentence should +- Say **ghost**, **`.ghost/` package**, **brand guidance**, **node**, + **kind**, and **materials** consistently. Use **review evidence** when the + packet format itself is not relevant. Do not introduce synonyms such as brand + DNA, style cache, design brain, magic layer, or guardrail engine. +- Prefer direct explanations of what happens before and after making. Reserve + terms such as **feed-forward**, **feed-back**, and **deterministic** for + technical passages where the distinction changes behavior. +- Keep the claim plain when the idea is abstract. A good ghost sentence should make a wrong implementation less likely, not merely sound clever. +- Use contrast only when it prevents a real misunderstanding. Do not stack + "X, not Y" constructions or end sections by restating the point as an + aphorism. +- Name the actor. Humans author and approve guidance; agents interpret and make; + CLI commands return menus, nodes, validation results, and review evidence. +- Do not use em dashes. Copy anti-patterns: +- Do not turn architecture into a slogan. Avoid declaring that the package, + packet, CLI, or ghost itself "is the product," and avoid courier, machinery, + engine, or layer metaphors in user-facing explanations. +- Do not leak implementation names such as Skeletons, event tapes, cliche floors, + or signature dials before the docs define them. - No "unlock," "supercharge," "seamless," "delightful," "AI-powered," or "world-class" filler. - No exclamation points in instructional copy. -- No anthropomorphizing Ghost as the judge or designer. The agent judges; Ghost - assembles context and packets. -- No broad promises that Ghost preserves a whole brand automatically. It only - preserves what the fingerprint makes selectable, readable, and reviewable. +- No anthropomorphizing ghost as the judge, designer, or author. The agent + interprets the guidance and makes the work; CLI commands supply menus, nodes, + validation results, and review evidence. +- No broad promises that ghost preserves a whole brand automatically. It only + preserves what the selected guidance makes readable and reviewable. diff --git a/apps/docs/README.md b/apps/docs/README.md index 062e28e6..4bd9d53e 100644 --- a/apps/docs/README.md +++ b/apps/docs/README.md @@ -1,8 +1,8 @@ # ghost-docs -**Documentation site for the Ghost project.** +**Documentation site for the ghost project.** -`ghost-docs` is the deployed docs for everything in this monorepo: the `ghost` CLI, the fingerprint package format, the generation loop, and the live `vessel` component catalogue. A Vite + MDX app that consumes [`vessel-react`](../../packages/vessel-react) as a workspace dependency. +`ghost-docs` is the deployed documentation for ghost: how agents author and use brand guidance from a `.ghost/` package, plus the supporting CLI reference. It is a Vite + MDX app that consumes [`vessel-react`](../../packages/vessel-react) as a workspace dependency. ## Run diff --git a/apps/docs/index.html b/apps/docs/index.html index fbca18e9..0eb53a49 100644 --- a/apps/docs/index.html +++ b/apps/docs/index.html @@ -3,8 +3,11 @@ - Ghost UI - + ghost — brand steering for agents +
diff --git a/apps/docs/package.json b/apps/docs/package.json index d8e61593..3dd89f71 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -53,11 +53,9 @@ "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", - "gsap": "^3.14.2", "input-otp": "^1.4.2", "lucide-react": "^1.7.0", "media-chrome": "^4.18.3", - "motion": "^12.38.0", "nanoid": "^5.1.7", "react-day-picker": "9.14.0", "react-hook-form": "^7.72.0", diff --git a/apps/docs/src/App.tsx b/apps/docs/src/App.tsx index e66db5df..d51e8c16 100644 --- a/apps/docs/src/App.tsx +++ b/apps/docs/src/App.tsx @@ -3,12 +3,11 @@ import { useEffect } from "react"; import { Navigate, Route, Routes, useLocation } from "react-router"; import DocsIndex from "@/app/docs/page"; import HomePage from "@/app/page"; -import GhostDriftLanding from "@/app/tools/drift/page"; -import ToolsIndex from "@/app/tools/page"; -import GhostScanLanding from "@/app/tools/scan/page"; import { Dock } from "@/components/docs/dock"; import { mdxDocsRoutes } from "@/routes/docs-routes"; +const legacyAuthoringPath = `docs/${["finger", "print-authoring"].join("")}`; + function ScrollToHash() { const { hash, pathname } = useLocation(); @@ -38,48 +37,12 @@ export function App() { } /> - {/* Tools: four-card index plus per-tool landings */} - } /> - } - /> - } /> - } /> - - {/* Cross-tool docs hub */} } /> - } + path={legacyAuthoringPath} + element={} /> - - {/* MDX-authored doc pages under /docs/* */} {mdxDocsRoutes()} - - {/* Redirects from the previous /tools/drift/{getting-started,cli} URLs */} - } - /> - } - /> - - } - /> - - } - /> diff --git a/apps/docs/src/app/docs/page.tsx b/apps/docs/src/app/docs/page.tsx index 2ffae40e..743b0f87 100644 --- a/apps/docs/src/app/docs/page.tsx +++ b/apps/docs/src/app/docs/page.tsx @@ -1,95 +1,59 @@ -"use client"; - -import { useStaggerReveal } from "@design-intelligence/vessel-react"; -import { BookOpen, FileText, Rocket, ShieldCheck, Zap } from "lucide-react"; -import type { ReactNode } from "react"; -import { Link } from "react-router"; -import { AnimatedPageHeader } from "@/components/docs/animated-page-header"; +import { BetaWarning } from "@/components/docs/beta-warning"; +import { DocIndex, type DocIndexItem } from "@/components/docs/doc-index"; +import { PageHeader } from "@/components/docs/page-header"; import { SectionWrapper } from "@/components/docs/wrappers"; -const sections: { - name: string; - href: string; - description: string; - icon: ReactNode; -}[] = [ - { - name: "Five-Minute Ghost", - href: "/docs/quickstart", - description: - "Write down the one decision you keep repeating, once, and let your agent read it before it builds.", - icon: , - }, +const learn: DocIndexItem[] = [ { - name: "Getting Started", + name: "Getting started", href: "/docs/getting-started", description: - "Install Ghost, set up the repo fingerprint, and learn the loop around .ghost.", - icon: , + "Record one repeated decision and give it to an agent before the next task.", }, { - name: "Fingerprint Authoring", - href: "/docs/fingerprint-authoring", + name: "Authoring", + href: "/docs/authoring", description: - "Co-author flat prose nodes that steer generation, with kinds from the glossary.", - icon: , + "Turn intent, shown material, and repeated decisions into guidance an agent can use.", }, { - name: "Checks And Review", + name: "Checks and review", href: "/docs/checks-and-review", description: - "Opt in to review checks, bind review assertions to nodes, and assemble advisory packets from a diff.", - icon: , + "Attach review assertions to brand guidance and inspect changed work after generation.", }, +]; + +const reference: DocIndexItem[] = [ { - name: "CLI Reference", + name: "CLI reference", href: "/docs/cli", description: - "Every command around the flat fingerprint: init, checks, validate, gather, pull, pulse, and review.", - icon: , + "Exact commands, flags, outputs, and exit behavior for the ghost CLI.", + }, + { + name: "Troubleshooting", + href: "/docs/troubleshooting", + description: + "Diagnose package discovery, validation, context gathering, review, and installation problems.", }, ]; export default function DocsIndex() { - const ref = useStaggerReveal(".doc-card", { - stagger: 0.06, - y: 30, - duration: 0.7, - }); - return ( - + + + +
+

learn

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

- {item.description} -

- - ))} -
+
+

reference

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

+ {n} + {children} +

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

Teams keep different parts of the brand in different tools.

+

+ Product teams maintain components and tokens. Marketing keeps + campaign systems and asset libraries. By the time a specialist + applies the shared brand guidelines, each discipline has adapted + them to its own work. +

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

+ Specialists bridge the gaps through interpretation and review, + then repeat the work on the next surface. +

+
+
+ +
-

maker +

+

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

+

+ Agents can build a payment screen, write its confirmation email, + produce a launch campaign, and revise the support response. The + agent still receives a component library for one task, a voice + guide for another, and a campaign deck for the next. Different + people provide each source at different times. +

+
+
+ +
+ guidance +
+

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

+

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

+

+ One decision can guide a screen, email, campaign, and support + response. Each medium still keeps its own form. +

+
+ +
- Thesis -

-
-

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

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

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

-

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

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

-

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

+ + + +
+

observable

+

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

-

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

efficiency

+

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

-

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

+ +
+

+ One ghost package can guide product, marketing, support, and + motion. The agent loads only the guidance that applies to its + current task.

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

- {item.description} -

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

- {tool.blurb} -

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

- {item.description} -

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

- {kicker} -

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

- {description} -

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

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

+

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

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

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

+

+

{title}

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

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

+

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

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

- Ghost -

-

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

-
-
- +
+

ghost

+

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

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

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

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

+
+

{title}

+

{kicker}

+ {description ? ( +

{description}

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