From 0ba295a92516e0c65cdf685e5d9794297be8f2c4 Mon Sep 17 00:00:00 2001 From: Nahiyan Khan Date: Tue, 19 May 2026 10:45:57 -0400 Subject: [PATCH] Reframe Ghost fingerprint capture --- .changeset/fingerprint-capture-surface.md | 5 + .changeset/scan-readiness.md | 5 + README.md | 56 +++-- apps/docs/src/content/docs/cli-reference.mdx | 49 ++-- .../docs/src/content/docs/getting-started.mdx | 32 +-- apps/docs/src/generated/cli-manifest.json | 4 +- docs/fingerprint-format.md | 12 +- docs/generation-loop.md | 33 ++- packages/ghost/README.md | 16 +- packages/ghost/package.json | 2 +- packages/ghost/src/scan-commands.ts | 66 +++++- packages/ghost/src/scan/scan-status.ts | 224 +++++++++++++++++- packages/ghost/src/skill-bundle/SKILL.md | 26 +- .../src/skill-bundle/references/brief.md | 12 +- .../src/skill-bundle/references/capture.md | 159 ++++++++++--- .../src/skill-bundle/references/critique.md | 12 +- .../ghost/src/skill-bundle/references/map.md | 22 +- .../src/skill-bundle/references/patterns.md | 6 + .../src/skill-bundle/references/promote.md | 4 +- .../src/skill-bundle/references/propose.md | 40 ++++ .../src/skill-bundle/references/recall.md | 10 +- .../ghost/src/skill-bundle/references/scan.md | 121 ---------- .../src/skill-bundle/references/survey.md | 17 +- packages/ghost/test/cli.test.ts | 42 +++- packages/ghost/test/scan-status.test.ts | 184 ++++++++++++++ 25 files changed, 849 insertions(+), 310 deletions(-) create mode 100644 .changeset/fingerprint-capture-surface.md create mode 100644 .changeset/scan-readiness.md create mode 100644 packages/ghost/src/skill-bundle/references/propose.md delete mode 100644 packages/ghost/src/skill-bundle/references/scan.md create mode 100644 packages/ghost/test/scan-status.test.ts diff --git a/.changeset/fingerprint-capture-surface.md b/.changeset/fingerprint-capture-surface.md new file mode 100644 index 0000000..568f6fb --- /dev/null +++ b/.changeset/fingerprint-capture-surface.md @@ -0,0 +1,5 @@ +--- +"@anarchitecture/ghost": patch +--- + +Reframe the installed skill and docs around agent-led Ghost Fingerprint Capture. diff --git a/.changeset/scan-readiness.md b/.changeset/scan-readiness.md new file mode 100644 index 0000000..e604a05 --- /dev/null +++ b/.changeset/scan-readiness.md @@ -0,0 +1,5 @@ +--- +"@anarchitecture/ghost": minor +--- + +Report scan evidence readiness so agents can distinguish product-observed, component-demo, substrate-only, and unobservable Ghost bundles. diff --git a/README.md b/README.md index 92e567d..7ddbece 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ghost -**Ghost gives agents repo-local design memory for product experience.** +**Ghost captures a repo-local product fingerprint for agents.** Agents can write UI. What they cannot reliably preserve is the thought behind the product experience they are changing: hierarchy, density, restraint, @@ -17,7 +17,7 @@ The bundle is evidence-first: - **`.ghost/checks.yml`** optionally stores human-promoted deterministic gates. - **`.ghost/intent.md`** optionally records human-authored or human-approved product intent. - **`.ghost/decisions/*.yml`** optionally records accepted/rejected product-experience rationale. -- **`.ghost/proposals/*.yml`** optionally stages candidate memory changes before promotion. +- **`.ghost/proposals/*.yml`** optionally stages candidate fingerprint updates before promotion. ## Install @@ -40,64 +40,68 @@ npx ghost skill install --dest ~/.codex/skills/ghost After that, ask your agent in plain English: ```text -Scan this project with Ghost. +Capture a Ghost fingerprint for this repo. Review this PR for Ghost drift. Compare these two Ghost bundles. -Brief this work from Ghost memory. +Brief this work from the Ghost fingerprint. ``` -## Core Workflow +## Fingerprint Capture + +Fingerprint Capture is a BYOA workflow. Your agent reads, interprets, and writes +the fingerprint artifacts; the CLI supplies deterministic status, validation, +derivation, and review helpers. + +Ask your agent: + +```text +Capture a Ghost fingerprint for this repo. +``` + +During capture, the agent checkpoints with commands like: ```bash -# 0. Create the bundle skeleton ghost init --with-intent - -# 1. Ask your agent to map the repo, then validate +ghost scan --format json ghost inventory ghost lint .ghost - -# 2. Ask your agent to survey values and composition evidence ghost survey fix-ids .ghost/survey.json -o .ghost/survey.json -ghost lint .ghost - -# 3. Derive patterns and verify evidence ghost survey patterns .ghost/survey.json -o .ghost/patterns.yml ghost verify .ghost --root . ghost lint .ghost +``` + +## Drift Workflow -# 4. Check or review changes +```bash ghost check --base main ghost review --base main --include-memory - -# 5. Compare fingerprints or bundles ghost compare market/.ghost dashboard/.ghost ghost compare a.md b.md --semantic ghost compare before.md after.md --temporal ghost compare */.ghost - -# 6. Record intentional drift ghost ack --stance aligned --reason "Initial baseline" ghost track new-tracked.fingerprint.md ghost diverge typography --reason "Editorial product uses a different type scale" - -# 7. Emit derived context ghost emit review-command ghost emit context-bundle ``` -`ghost scan --format json` emits deterministic BYOA state: which artifacts are -present, which stage is next, and enough structure for the host agent to choose -the next recipe. It does not call an LLM. +`ghost scan --format json` emits deterministic Fingerprint Capture state: which +artifacts are present, which stage is next, and evidence readiness. Readiness +distinguishes a product-observed bundle from component-demo, substrate-only, or +unobservable evidence, so agents know when tokens/components are available but +product composition has not been earned yet. It does not call an LLM. ## CLI Commands | Command | Description | | --- | --- | | `ghost init` | Create `.ghost/{resources.yml,map.md,survey.json,patterns.yml,checks.yml}`. | -| `ghost scan` | Report scan state and emit the next BYOA handoff. | +| `ghost scan` | Report fingerprint capture progress and emit the next BYOA handoff. | | `ghost inventory` | Emit raw repo signals as JSON for map authoring. | | `ghost lint` | Validate a bundle or individual artifact. | -| `ghost verify` | Validate resource reachability, pattern evidence, checks, and optional memory. | +| `ghost verify` | Validate resource reachability, pattern evidence, checks, and optional decisions/proposals. | | `ghost describe` | Print optional `intent.md` or direct markdown section ranges + token estimates. | | `ghost diff` | Structural prose-level diff between direct fingerprints. | | `ghost survey ` | Operate on `ghost.survey/v2` files: `merge`, `fix-ids`, `summarize`, `catalog`, `patterns`. | @@ -117,7 +121,7 @@ workspace packages remain only for historical/development context. | Path | Role | Published? | | ---- | ---- | --- | -| [`packages/ghost`](./packages/ghost) | Unified public package. Ships the `ghost` CLI, scan/memory authoring, deterministic checks, advisory review packets, comparison, stance tracking, and the unified skill bundle. | yes: `@anarchitecture/ghost` | +| [`packages/ghost`](./packages/ghost) | Unified public package. Ships the `ghost` CLI, fingerprint capture helpers, deterministic checks, advisory review packets, comparison, stance tracking, and the unified skill bundle. | yes: `@anarchitecture/ghost` | | [`packages/ghost-core`](./packages/ghost-core) | Private historical shared library. Runtime code is folded into `packages/ghost` for publishing. | no | | [`packages/ghost-scan`](./packages/ghost-scan) | Private historical scan package. Runtime code is folded into `packages/ghost` for publishing. | no | | [`packages/ghost-fleet`](./packages/ghost-fleet) | Private fleet view across many members. | no | diff --git a/apps/docs/src/content/docs/cli-reference.mdx b/apps/docs/src/content/docs/cli-reference.mdx index a724085..060d8a8 100644 --- a/apps/docs/src/content/docs/cli-reference.mdx +++ b/apps/docs/src/content/docs/cli-reference.mdx @@ -9,11 +9,12 @@ slug: cli -The CLI does the repeatable parts: initialize bundles, validate files, transform -survey data, compare bundles, check diffs, emit handoff packets, and record -intent. Your agent does the reading, writing, and reviewing. +The CLI does the repeatable parts: initialize bundles, report Fingerprint +Capture progress, validate files, transform survey data, compare bundles, check +diffs, emit handoff packets, and record intent. Your agent does the reading, +writing, and reviewing. -A scan follows one path: +Fingerprint Capture follows one path: ```text resources.yml -> map.md -> survey.json -> patterns.yml @@ -26,6 +27,12 @@ npm install -D @anarchitecture/ghost npx ghost skill install ``` +Ask your agent to run the workflow: + +```text +Capture a Ghost fingerprint for this repo. +``` + Commands are grouped by job: - **Create and check the fingerprint bundle**: `init`, `scan`, `inventory`, `lint`, `verify`, `describe`, `diff`, `survey `, `emit`. @@ -33,8 +40,8 @@ Commands are grouped by job: - **Install BYOA recipes**: `skill install`. - **View many projects**: `ghost-fleet members`, `ghost-fleet view`, `ghost-fleet emit skill`. -Workflows like _scan_, _map_, _survey_, _patterns_, _recall_, _brief_, -_critique_, _capture_, _promote_, _review_, _verify_, _compare_, and +Workflows like _capture_, _map_, _survey_, _patterns_, _recall_, _brief_, +_critique_, _propose_, _promote_, _review_, _verify_, _compare_, and _remediate_ are skill recipes your host agent runs. They are installed by `ghost skill install`; they are not LLM-running CLI verbs. @@ -44,11 +51,12 @@ reference. - + -`ghost` owns the root bundle scan. It inventories the repo, validates each file, -verifies cross-artifact fidelity, describes markdown, diffs direct fingerprint -files, runs survey ops, and emits agent-ready outputs. +`ghost` supports Fingerprint Capture. It inventories the repo, reports capture +progress, validates each file, verifies cross-artifact fidelity, describes +markdown, diffs direct fingerprint files, runs survey ops, and emits +agent-ready outputs. ### Initialize - `init` @@ -62,10 +70,11 @@ ghost init ghost init --with-intent ``` -### Pipeline progress - `scan` +### Capture progress - `scan` -Report which scan stages already exist in a directory and which stage the -agent should run next. `--format json` is the deterministic BYOA handoff. +Report which fingerprint capture stages already exist in a directory, evidence +readiness, and which stage the agent should run next. `--format json` is the +deterministic BYOA handoff. @@ -195,8 +204,8 @@ ghost check --diff patch.diff --format json ### Advisory packet - `review` Emit an evidence-routed advisory review packet grounded in `.ghost/patterns.yml`, -`.ghost/survey.json`, optional `.ghost/intent.md`, optional memory, checks, and -the diff. +`.ghost/survey.json`, optional `.ghost/intent.md`, accepted decisions, checks, +and the diff. @@ -248,9 +257,9 @@ ghost diverge palette --reason "Dark-mode-first palette for this product" ### Skill bundle - `skill install` -Install the unified `ghost` skill bundle into a host agent. It contains scan, -map, survey, patterns, schema, recall, brief, critique, capture, promote, -review, verify, compare, and remediate recipes. +Install the unified `ghost` skill bundle into a host agent. It contains +Fingerprint Capture, map, survey, patterns, schema, recall, brief, critique, +propose, promote, review, verify, compare, and remediate recipes. @@ -297,11 +306,11 @@ then ask your agent in plain English. | Recipe | Bundle | Trigger | | --- | --- | --- | -| `scan` | `ghost` | "scan this project" / "go end-to-end" | +| `capture` | `ghost` | "Capture a Ghost fingerprint for this repo" | | `map` | `ghost` | "map this repo" / "write map.md" | | `survey` | `ghost` | "survey design values" / "extract tokens" | | `patterns` | `ghost` | "write patterns.yml" / "codify composition patterns" | -| `recall` / `brief` / `critique` / `capture` / `promote` | `ghost` | "what does Ghost remember?" / "brief this work" / "capture this decision" | +| `recall` / `brief` / `critique` / `propose` / `promote` | `ghost` | "brief this work" / "propose this decision" / "promote this proposal" | | `review` | `ghost` | "review this PR for drift" | | `verify` | `ghost` | "verify generated UI against the bundle" | | `compare` | `ghost` | "why did these two fingerprints drift?" | diff --git a/apps/docs/src/content/docs/getting-started.mdx b/apps/docs/src/content/docs/getting-started.mdx index cbd726d..0bbdf69 100644 --- a/apps/docs/src/content/docs/getting-started.mdx +++ b/apps/docs/src/content/docs/getting-started.mdx @@ -1,6 +1,6 @@ --- title: Getting Started -description: Install Ghost, scan a repo, and review generated UI against a root fingerprint bundle. +description: Install Ghost, capture a repo fingerprint, and review generated UI against it. kicker: Docs section: guide order: 10 @@ -9,9 +9,9 @@ slug: getting-started -Ghost keeps a project's product-experience memory in the repo, where your agent -can use it. The public package is `@anarchitecture/ghost`, and it installs one -CLI: `ghost`. +Ghost captures a project's product identity in the repo, where your agent can +use it. The public package is `@anarchitecture/ghost`, and it installs one CLI: +`ghost`. The fingerprint is the root `.ghost/` bundle: @@ -22,7 +22,7 @@ what to read where UI lives what exists composition grammar `checks.yml` is optional deterministic enforcement. `intent.md`, `decisions/*.yml`, and `proposals/*.yml` are optional human-approved or -human-staged product-experience memory. +human-staged context around the fingerprint. | Surface | Job | Verbs | | --- | --- | --- | @@ -40,19 +40,19 @@ npx ghost --help npx ghost skill install ``` -Once the skill is installed, ask your agent in plain English: "scan this -project with Ghost" or "review this PR for drift". The recipe tells the agent -what to read, what to write, and which CLI checks to run. +Once the skill is installed, ask your agent in plain English: "Capture a Ghost +fingerprint for this repo" or "review this PR for drift". The recipe tells the +agent what to read, what to write, and which CLI checks to run. - + ```text -Scan this project with Ghost +Capture a Ghost fingerprint for this repo. ``` -The `scan` recipe checkpoints between stages: +Fingerprint Capture checkpoints between stages: 1. **Resources (`resources.yml`)** - declare the primary target, design-system references, canonical surfaces, screenshots, docs, resolvers, and path @@ -105,18 +105,18 @@ advisory packet. - + Use the installed `ghost` skill when work reveals a decision Ghost should -remember: +consider adding to the fingerprint: ```text -Brief this work with Ghost memory -Capture this product-experience decision as a proposal +Brief this work from the Ghost fingerprint +Propose this product-experience decision as a fingerprint update Promote this accepted proposal ``` -The recipes write proposals first. Humans promote durable memory into accepted +The recipes write proposals first. Humans promote durable context into accepted decisions, `patterns.yml`, `checks.yml`, or `intent.md`. diff --git a/apps/docs/src/generated/cli-manifest.json b/apps/docs/src/generated/cli-manifest.json index fa32630..0b2b25a 100644 --- a/apps/docs/src/generated/cli-manifest.json +++ b/apps/docs/src/generated/cli-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-05-18T18:58:16.311Z", + "generatedAt": "2026-05-19T14:36:11.941Z", "tools": [ { "tool": "ghost", @@ -72,7 +72,7 @@ "tool": "ghost", "name": "scan", "rawName": "scan [dir]", - "description": "Report which root fingerprint bundle stages have produced artifacts: resources.yml, map.md, survey.json, patterns.yml, and optional checks.yml/intent.md.", + "description": "Report fingerprint capture progress: produced artifacts, evidence readiness, and the next BYOA step.", "options": [ { "rawName": "--include-scopes", diff --git a/docs/fingerprint-format.md b/docs/fingerprint-format.md index 76f9dcd..08e867f 100644 --- a/docs/fingerprint-format.md +++ b/docs/fingerprint-format.md @@ -12,14 +12,15 @@ not a single prose file. The canonical on-disk shape is: checks.yml intent.md # optional decisions/ # optional ghost.decision/v1 product-experience rationale - proposals/ # optional ghost.proposal/v1 candidate memory changes + proposals/ # optional ghost.proposal/v1 candidate fingerprint updates ``` `survey.json` is the evidence ledger. `patterns.yml` is the operational composition grammar. `checks.yml` is the deterministic gate layer. `intent.md` is optional human-authored or human-approved intent. `decisions/` and -`proposals/` are optional product-experience memory: accepted/rejected rationale -and candidate changes that may later be promoted. +`proposals/` are optional product-experience context: accepted/rejected +rationale and candidate changes that may later be promoted into the +fingerprint. ## Package Artifacts @@ -150,8 +151,9 @@ decided_at: "2026-05-17T00:00:00.000Z" ### `proposals/*.yml` -Candidate memory changes use `ghost.proposal/v1`. They are working memory until -a human promotes them into decisions, patterns, checks, or intent. +Candidate fingerprint updates use `ghost.proposal/v1`. They remain unresolved +proposals until a human promotes them into decisions, patterns, checks, or +intent. ```yaml schema: ghost.proposal/v1 diff --git a/docs/generation-loop.md b/docs/generation-loop.md index e134fbc..8e6e340 100644 --- a/docs/generation-loop.md +++ b/docs/generation-loop.md @@ -1,8 +1,7 @@ -# Product-Experience Memory Loop +# Product Fingerprint Loop -Ghost gives UI generators and product-development agents local, auditable memory -for product experience. The canonical input is the root `.ghost/` fingerprint -bundle: +Ghost gives UI generators and product-development agents a local, auditable +product fingerprint. The canonical input is the root `.ghost/` bundle: ```text .ghost/ @@ -17,12 +16,12 @@ bundle: ``` `patterns.yml` is the operational composition grammar, `survey.json` is the -evidence ledger, `resources.yml` says what the scan is grounded in, `checks.yml` -contains deterministic gates, `intent.md` is optional human authority, -`decisions/` records optional product-experience rationale, and `proposals/` -stages candidate memory changes. The generator can work without optional memory; -when it is present, treat accepted decisions as advisory context and proposals as -working memory. +evidence ledger, `resources.yml` says what the capture is grounded in, +`checks.yml` contains deterministic gates, `intent.md` is optional human +authority, `decisions/` records optional product-experience rationale, and +`proposals/` stages candidate fingerprint updates. The generator can work +without optional decisions; when they are present, treat accepted decisions as +advisory context and proposals as unresolved candidates. ## Pipeline Shape @@ -100,8 +99,8 @@ Accepted decisions can be included in advisory review with ### `.ghost/proposals/*.yml` -Proposals are optional `ghost.proposal/v1` files. They capture candidate memory -changes from design reviews, generated UI, QA findings, or PM/engineering +Proposals are optional `ghost.proposal/v1` files. They record candidate changes +from design reviews, generated UI, QA findings, or PM/engineering discussion. They are never canonical until a human promotes them. ## Review Loop @@ -143,12 +142,12 @@ agent, generate the requested UI, then run `ghost review` and review packets when generated or changed UI appears to drift from `patterns.yml`. -**Fingerprint maintenance:** scan in order: +**Fingerprint Capture:** ask your agent to capture the fingerprint, then move in order: `resources -> map -> survey -> patterns`. Keep `survey.json` factual, promote repeated composition observations into `patterns.yml`, and add `intent.md` only when a human has supplied or approved the intent. -**Product-experience memory:** use `ghost` recipes to recall, brief, -critique, capture, and promote optional decisions/proposals. Keep promotion -deliberate: proposals are working memory; accepted decisions are advisory -memory; active checks are the only blocking mechanism. +**Fingerprint updates:** use `ghost` recipes to recall, brief, critique, +propose, and promote optional decisions/proposals. Keep promotion deliberate: +proposals are unresolved candidates; accepted decisions are advisory context; +active checks are the only blocking mechanism. diff --git a/packages/ghost/README.md b/packages/ghost/README.md index a7d6ed7..f83d653 100644 --- a/packages/ghost/README.md +++ b/packages/ghost/README.md @@ -1,8 +1,8 @@ # @anarchitecture/ghost -**Unified Ghost CLI for repo-local design memory.** +**Unified Ghost CLI for repo-local product fingerprints.** -Ghost creates and maintains a root `.ghost/` fingerprint bundle, checks diffs +Ghost supports Fingerprint Capture for a root `.ghost/` bundle, checks diffs against deterministic gates, emits advisory review packets, compares bundles, and records intentional drift. It ships one CLI: `ghost`. @@ -54,14 +54,20 @@ import { compareFingerprints } from "@anarchitecture/ghost/core"; Ghost is bring-your-own-agent. The CLI performs deterministic work: inventory, lint, verify, compare, check, and handoff packet generation. The installed -`ghost` skill teaches your host agent how to scan, map, survey, codify patterns, -review drift, verify generated UI, remediate, and capture product-experience -memory. +`ghost` skill teaches your host agent how to capture a product fingerprint: map +the repo, survey evidence, codify patterns, review drift, verify generated UI, +remediate issues, and propose accepted fingerprint updates. ```bash ghost skill install ``` +Then ask your agent: + +```text +Capture a Ghost fingerprint for this repo. +``` + ## License Apache-2.0 diff --git a/packages/ghost/package.json b/packages/ghost/package.json index c391706..645c3d7 100644 --- a/packages/ghost/package.json +++ b/packages/ghost/package.json @@ -1,7 +1,7 @@ { "name": "@anarchitecture/ghost", "version": "0.0.0", - "description": "Unified Ghost CLI for repo-local product-experience memory, scan workflows, deterministic checks, advisory review, and drift tracking", + "description": "Unified Ghost CLI for repo-local product fingerprints, deterministic checks, advisory review, and drift tracking", "license": "Apache-2.0", "author": "Block, Inc.", "repository": { diff --git a/packages/ghost/src/scan-commands.ts b/packages/ghost/src/scan-commands.ts index 10e8985..480b741 100644 --- a/packages/ghost/src/scan-commands.ts +++ b/packages/ghost/src/scan-commands.ts @@ -37,7 +37,7 @@ import { import { registerEmitCommand } from "./scan-emit-command.js"; /** - * Register scan and fingerprint-bundle commands on the unified Ghost CLI. + * Register fingerprint-bundle commands on the unified Ghost CLI. * * Verbs author and validate the root `.ghost/` fingerprint bundle: * `lint` (schema check, auto-detects file kind), `verify` (cross-artifact @@ -194,7 +194,7 @@ export function registerScanCommands(cli: CAC): void { cli .command( "scan [dir]", - "Report which root fingerprint bundle stages have produced artifacts: resources.yml, map.md, survey.json, patterns.yml, and optional checks.yml/intent.md.", + "Report fingerprint capture progress: produced artifacts, evidence readiness, and the next BYOA step.", ) .option( "--include-scopes", @@ -212,7 +212,7 @@ export function registerScanCommands(cli: CAC): void { } else { const fmt = (state: string) => state === "present" ? "present" : "missing"; - process.stdout.write(`scan dir: ${status.dir}\n\n`); + process.stdout.write(`capture dir: ${status.dir}\n\n`); process.stdout.write( ` resources (resources.yml): ${fmt(status.resources.state)}\n`, ); @@ -236,7 +236,23 @@ export function registerScanCommands(cli: CAC): void { `next: run the ${status.recommended_next} stage\n`, ); } else { - process.stdout.write("next: scan complete — all stages present\n"); + process.stdout.write( + "next: fingerprint capture complete - all stages present\n", + ); + } + process.stdout.write(`readiness: ${status.readiness.state}\n`); + if (status.readiness.can_review.length > 0) { + process.stdout.write( + ` can review: ${status.readiness.can_review.join(", ")}\n`, + ); + } + if (status.readiness.cannot_review.length > 0) { + process.stdout.write( + ` cannot review: ${status.readiness.cannot_review.join(", ")}\n`, + ); + } + if (status.readiness.reasons[0]) { + process.stdout.write(` reason: ${status.readiness.reasons[0]}\n`); } if (status.scope_error) { process.stdout.write(`\nscopes: error — ${status.scope_error}\n`); @@ -785,15 +801,47 @@ function summarizeSurveyPatterns(survey: Survey): GhostPatternsDocument { ], })), advisory: { - review_expectations: [ - "Identify the surface type before judging composition.", - "Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.", - "Treat intent.md as human authority when present.", - ], + review_expectations: surveyPatternReviewExpectations(survey), }, }; } +function surveyPatternReviewExpectations(survey: Survey): string[] { + if (survey.ui_surfaces.length === 0) { + return [ + "No UI surface evidence is present; do not infer product composition patterns from values, tokens, or components alone.", + "Use survey values, tokens, and components as substrate evidence until implemented surfaces are observed.", + "Treat intent.md as human authority when present.", + ]; + } + + const hasProductSurface = survey.ui_surfaces.some((surface) => + isProductSurfaceKind(surface.kind), + ); + if (!hasProductSurface) { + return [ + "Treat story, fixture, and doc-example rows as component demonstration evidence, not product composition authority.", + "Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.", + "Treat intent.md as human authority when present.", + ]; + } + + return [ + "Identify the surface type before judging composition.", + "Cite matching composition_patterns[].evidence and survey.ui_surfaces evidence for advisory findings.", + "Treat intent.md as human authority when present.", + ]; +} + +function isProductSurfaceKind(kind: string): boolean { + return ( + kind === "route" || + kind === "screen" || + kind === "screenshot" || + kind === "source" + ); +} + interface PatternAccumulator { count: number; examples: string[]; diff --git a/packages/ghost/src/scan/scan-status.ts b/packages/ghost/src/scan/scan-status.ts index c25e755..1c25e7c 100644 --- a/packages/ghost/src/scan/scan-status.ts +++ b/packages/ghost/src/scan/scan-status.ts @@ -10,6 +10,7 @@ import { type MapFrontmatter, MapFrontmatterSchema, SURVEY_FILENAME, + type Survey, } from "#ghost-core"; import { FINGERPRINTS_DIRNAME, @@ -18,7 +19,7 @@ import { } from "./constants.js"; /** - * Per-stage state in a scan directory. + * Per-stage state in a fingerprint capture directory. * * `missing` — the artifact doesn't exist yet. * `present` — the artifact exists. Existence is the only signal this @@ -44,12 +45,34 @@ export interface ScanScopeReport { fingerprint: ScanStageReport; } +export type ScanReadinessState = + | "pending" + | "product-observed" + | "component-demo" + | "substrate-only" + | "unobservable" + | "unknown"; + +export interface ScanReadinessReport { + state: ScanReadinessState; + product_surface_count: number; + demo_surface_count: number; + substrate_rows: { + values: number; + tokens: number; + components: number; + }; + can_review: string[]; + cannot_review: string[]; + reasons: string[]; +} + export interface ScanStatusOptions { includeScopes?: boolean; } export interface ScanStatus { - /** Absolute path to the scan directory. */ + /** Absolute path to the fingerprint capture directory. */ dir: string; resources: ScanStageReport; map: ScanStageReport; @@ -59,6 +82,11 @@ export interface ScanStatus { intent: ScanStageReport; scopes?: ScanScopeReport[]; scope_error?: string; + /** + * Best-effort evidence maturity derived from map.md + survey.json. This is + * advisory: invalid or missing artifacts still surface through lint/verify. + */ + readiness: ScanReadinessReport; /** * The next stage an orchestrator should run, or `null` if every required * stage is `present`. Stages run in order: @@ -69,7 +97,8 @@ export interface ScanStatus { } /** - * Inspect a scan directory and report which stages have produced artifacts. + * Inspect a fingerprint capture directory and report which stages have produced + * artifacts. * * Existence-only check today. The artifacts checked are: * @@ -144,6 +173,13 @@ export async function scanStatus( else if (survey.state === "missing") recommended_next = "survey"; else if (patterns.state === "missing") recommended_next = "patterns"; + const readiness = await scanReadiness({ + mapPath, + mapPresent, + surveyPath, + surveyPresent, + }); + const status: ScanStatus = { dir, resources, @@ -152,6 +188,7 @@ export async function scanStatus( patterns, checks, intent, + readiness, recommended_next, }; @@ -167,6 +204,187 @@ export async function scanStatus( return status; } +async function scanReadiness(options: { + mapPath: string; + mapPresent: boolean; + surveyPath: string; + surveyPresent: boolean; +}): Promise { + const reasons: string[] = []; + + if (!options.mapPresent || !options.surveyPresent) { + if (!options.mapPresent) { + reasons.push("map.md is missing, so observation paths are unknown."); + } + if (!options.surveyPresent) { + reasons.push("survey.json is missing, so evidence rows are unknown."); + } + return readinessReport("pending", { + reasons, + can_review: [], + cannot_review: [], + }); + } + + let map: MapFrontmatter | undefined; + try { + map = await readMapFrontmatter(options.mapPath); + } catch (err) { + reasons.push( + `map.md could not be read for readiness: ${ + err instanceof Error ? err.message : String(err) + }`, + ); + } + + let survey: Pick; + try { + survey = await readSurveyEvidence(options.surveyPath); + } catch (err) { + return readinessReport("unknown", { + reasons: [ + `survey.json could not be read for readiness: ${ + err instanceof Error ? err.message : String(err) + }`, + ], + can_review: [], + cannot_review: [ + "product composition", + "surface flow", + "tokens", + "components", + ], + }); + } + + const productSurfaceCount = survey.ui_surfaces.filter((surface) => + isProductSurfaceKind(surface.kind), + ).length; + const demoSurfaceCount = survey.ui_surfaces.filter((surface) => + isDemoSurfaceKind(surface.kind), + ).length; + const substrateRows = { + values: survey.values.length, + tokens: survey.tokens.length, + components: survey.components.length, + }; + const substrateRowCount = + substrateRows.values + substrateRows.tokens + substrateRows.components; + + if (productSurfaceCount > 0) { + reasons.push( + `${productSurfaceCount} product surface(s) were observed in survey.ui_surfaces.`, + ); + return readinessReport("product-observed", { + product_surface_count: productSurfaceCount, + demo_surface_count: demoSurfaceCount, + substrate_rows: substrateRows, + reasons, + can_review: [ + "product composition", + "surface flow", + "tokens", + "components", + "design values", + ], + cannot_review: [], + }); + } + + if (demoSurfaceCount > 0) { + reasons.push( + `${demoSurfaceCount} demo surface(s) were observed, but no product route, screen, screenshot, or source surface is present.`, + ); + return readinessReport("component-demo", { + demo_surface_count: demoSurfaceCount, + substrate_rows: substrateRows, + reasons, + can_review: [ + "tokens", + "components", + "design values", + "component demonstration composition", + ], + cannot_review: ["product composition", "surface flow"], + }); + } + + if (substrateRowCount > 0) { + reasons.push( + `survey.json has ${substrateRowCount} value/token/component row(s), but no UI surfaces.`, + ); + if (map?.surface_sources.render_strategy === "unknown") { + reasons.push("map.md declares surface_sources.render_strategy: unknown."); + } + return readinessReport("substrate-only", { + substrate_rows: substrateRows, + reasons, + can_review: ["tokens", "components", "design values"], + cannot_review: [ + "product composition", + "surface flow", + "surface hierarchy", + ], + }); + } + + reasons.push( + "survey.json has no values, tokens, components, or UI surfaces to support product-experience judgment.", + ); + return readinessReport("unobservable", { + reasons, + can_review: [], + cannot_review: [ + "product composition", + "surface flow", + "surface hierarchy", + "tokens", + "components", + ], + }); +} + +function readinessReport( + state: ScanReadinessState, + overrides: Partial> = {}, +): ScanReadinessReport { + return { + state, + product_surface_count: 0, + demo_surface_count: 0, + substrate_rows: { values: 0, tokens: 0, components: 0 }, + can_review: [], + cannot_review: [], + reasons: [], + ...overrides, + }; +} + +async function readSurveyEvidence( + path: string, +): Promise> { + const raw = JSON.parse(await readFile(path, "utf-8")) as Partial; + return { + values: Array.isArray(raw.values) ? raw.values : [], + tokens: Array.isArray(raw.tokens) ? raw.tokens : [], + components: Array.isArray(raw.components) ? raw.components : [], + ui_surfaces: Array.isArray(raw.ui_surfaces) ? raw.ui_surfaces : [], + }; +} + +function isProductSurfaceKind(kind: string): boolean { + return ( + kind === "route" || + kind === "screen" || + kind === "screenshot" || + kind === "source" + ); +} + +function isDemoSurfaceKind(kind: string): boolean { + return kind === "story" || kind === "doc-example" || kind === "fixture"; +} + async function pathExists(path: string): Promise { try { const s = await stat(path); diff --git a/packages/ghost/src/skill-bundle/SKILL.md b/packages/ghost/src/skill-bundle/SKILL.md index 8ecbbdc..171669b 100644 --- a/packages/ghost/src/skill-bundle/SKILL.md +++ b/packages/ghost/src/skill-bundle/SKILL.md @@ -1,15 +1,15 @@ --- name: ghost -description: Create, validate, recall, review, and evolve a repo-local Ghost fingerprint bundle. Use when the user wants to scan a product, update .ghost, brief work from product-experience memory, review drift, verify generated UI, compare fingerprints, or record accepted divergence. +description: Capture, validate, review, and evolve a repo-local Ghost fingerprint. Use when the user wants to capture a product fingerprint, update .ghost, brief work from accepted product-experience context, review drift, verify generated UI, compare fingerprints, or record accepted divergence. license: Apache-2.0 metadata: homepage: https://github.com/block/ghost cli: ghost --- -# Ghost — Sovereign Design Memory +# Ghost - Product Fingerprints -Ghost keeps product-experience memory local to the repository: +Ghost captures product identity in a repo-local fingerprint bundle: ```text .ghost/ @@ -19,24 +19,24 @@ Ghost keeps product-experience memory local to the repository: patterns.yml checks.yml # optional deterministic gates intent.md # optional human-approved intent - decisions/ # optional accepted/rejected memory - proposals/ # optional candidate memory + decisions/ # optional accepted/rejected rationale + proposals/ # optional candidate updates ``` Survey grounds the bundle. Patterns make composition operational. Checks are deterministic gates. Intent and decisions preserve human-approved product -experience context. The host agent reads and writes the memory; the CLI provides -deterministic validation, comparison, routing, and handoff packets. +experience context. The host agent reads and writes the fingerprint; the CLI +provides deterministic validation, comparison, routing, and handoff packets. ## CLI Verbs | Verb | Purpose | |---|---| | `ghost init [dir] [--with-intent]` | Create the root `.ghost` bundle skeleton. | -| `ghost scan [dir] [--format json]` | Report scan state and the next BYOA step. | +| `ghost scan [dir] [--format json]` | Report fingerprint capture progress and the next BYOA step. | | `ghost inventory [path]` | Emit raw repo signals for map authoring. | | `ghost lint [file-or-dir]` | Validate a bundle or individual artifact. | -| `ghost verify [dir] --root ` | Validate resources, pattern evidence, checks, and optional memory. | +| `ghost verify [dir] --root ` | Validate resources, pattern evidence, checks, and optional decisions/proposals. | | `ghost survey ` | Survey ops: `merge`, `fix-ids`, `summarize`, `catalog`, `patterns`. | | `ghost check --base ` | Run active deterministic gates against a diff. | | `ghost review --base ` | Emit an advisory review packet grounded in bundle evidence. | @@ -47,24 +47,24 @@ deterministic validation, comparison, routing, and handoff packets. ## Workflows -- Full scan: follow [references/scan.md](references/scan.md). +- Fingerprint Capture: follow [references/capture.md](references/capture.md). - Map the repo: follow [references/map.md](references/map.md). - Survey design evidence: follow [references/survey.md](references/survey.md). - Derive composition grammar: follow [references/patterns.md](references/patterns.md). -- Recall product-experience memory: follow [references/recall.md](references/recall.md). +- Recall accepted product-experience context: follow [references/recall.md](references/recall.md). - Shape a pre-generation brief: follow [references/brief.md](references/brief.md). - Critique generated or changed work: follow [references/critique.md](references/critique.md). - Review drift: follow [references/review.md](references/review.md). - Verify generation: follow [references/verify.md](references/verify.md). - Compare bundles: follow [references/compare.md](references/compare.md). - Remediate drift: follow [references/remediate.md](references/remediate.md). -- Capture a candidate memory update: follow [references/capture.md](references/capture.md). +- Propose a candidate fingerprint update: follow [references/propose.md](references/propose.md). - Promote a human-approved proposal: follow [references/promote.md](references/promote.md). ## Always - Treat `.ghost/` as the source of truth. -- Validate with `ghost lint` and `ghost verify --root ` before declaring a scan complete. +- Validate with `ghost lint` and `ghost verify --root ` before declaring Fingerprint Capture complete. - Run `ghost check` for deterministic gates and `ghost review` for advisory critique. - Include accepted decisions with `ghost review --include-memory` when product-experience rationale matters. diff --git a/packages/ghost/src/skill-bundle/references/brief.md b/packages/ghost/src/skill-bundle/references/brief.md index 6158cb9..1b34d36 100644 --- a/packages/ghost/src/skill-bundle/references/brief.md +++ b/packages/ghost/src/skill-bundle/references/brief.md @@ -1,9 +1,9 @@ --- name: brief -description: Shape a pre-generation brief from Ghost product-experience memory. +description: Shape a pre-generation brief from the Ghost fingerprint. --- -# Brief From Product-Experience Memory +# Brief From The Ghost Fingerprint Use this before generating or implementing UI. The goal is to help the human and agent understand the experience problem, not just load style constraints. @@ -13,7 +13,7 @@ agent understand the experience problem, not just load style constraints. 1. Run the recall workflow for the requested task. 2. Identify the likely map scope, surface type, and composition pattern. 3. Name the product-experience decisions at stake. -4. Call out risks: accidental drift, incomplete memory, or intentional change. +4. Call out risks: accidental drift, incomplete fingerprint context, or intentional change. 5. Write prompt material for the generator or implementation agent. ## Output @@ -21,10 +21,10 @@ agent understand the experience problem, not just load style constraints. Produce: - Task framing. -- Relevant memory with citations. +- Relevant fingerprint context with citations. - Decisions the human should make before generation. - Product-native generation guidance. - Drift checks to run afterward. -Do not invent memory. If memory is missing, say what proposal should be captured -after the work. +Do not invent fingerprint context. If context is missing, say what proposal +should be recorded after the work. diff --git a/packages/ghost/src/skill-bundle/references/capture.md b/packages/ghost/src/skill-bundle/references/capture.md index 7076f37..3925ca3 100644 --- a/packages/ghost/src/skill-bundle/references/capture.md +++ b/packages/ghost/src/skill-bundle/references/capture.md @@ -1,40 +1,135 @@ --- name: capture -description: Write a candidate ghost.proposal/v1 memory artifact from a session. +description: Drive Ghost Fingerprint Capture to produce .ghost/{resources.yml,map.md,survey.json,patterns.yml} plus optional checks.yml and intent.md. +handoffs: + - label: Inspect stage status + command: ghost scan + prompt: What fingerprint capture stage should I run next? + - label: Run deterministic drift checks + command: ghost check + prompt: Run ghost check against this bundle --- -# Capture A Memory Proposal +# Recipe: Capture A Ghost Fingerprint -Use this when a design review, implementation, QA finding, or PM discussion -reveals a product-experience decision that Ghost should remember. +**Goal:** capture the product's repo-local Ghost fingerprint: + +```text +.ghost/ + resources.yml + map.md + survey.json + patterns.yml + checks.yml + intent.md # optional +``` + +## Overview + +```text +resources -> map -> survey -> patterns +``` + +- `resources.yml`: references that define the product. +- `map.md`: topology and routing. +- `survey.json`: observed factual evidence. +- `patterns.yml`: operational composition grammar backed by survey evidence. +- `checks.yml`: optional human-promoted deterministic gates. +- `intent.md`: optional human-authored or human-approved product intent. + +Fingerprint Capture is a BYOA workflow: the host agent reads, interprets, and +writes the fingerprint artifacts; the `ghost` CLI supplies deterministic status, +validation, derivation, and review helpers. + +`ghost scan --format json` reports capture progress and `readiness.state`. A +capture can be structurally complete while still being `substrate-only` or +`component-demo`; in those states, use tokens, components, and demo evidence, +but do not treat product composition, hierarchy, surface flow, or product +rhythm as observed. ## Steps -1. Confirm the observation is about product experience: perceived, used, - trusted, understood, or safely changed. -2. Check whether accepted decisions, patterns, checks, or intent already cover it. -3. If it is new, write `.ghost/proposals/.yml`. -4. Use schema `ghost.proposal/v1`. -5. Run `ghost lint .ghost`. - -## Proposal Shape - -```yaml -schema: ghost.proposal/v1 -id: saved-payment-empty-state -status: open -kind: decision -title: Saved payment empty state should teach recovery -claim: Empty states for saved payment methods should prioritize recovery over education. -rationale: The user is blocked from paying, not browsing product concepts. -scope: - roles: [design, pm, qa] - surface_types: [empty-state] -evidence: - - path: apps/payments/empty-state.tsx -proposed_action: - target: decisions - summary: Promote into an accepted product-experience decision if repeated. -``` - -Do not write accepted decisions without human approval. +### 0. Initialize + +```bash +ghost init +ghost scan +``` + +Use `--with-intent` only when you have human-authored or human-approved intent +to record. + +### 1. Resources + +Run when `ghost scan` recommends `resources`. + +Author `.ghost/resources.yml` from the target, any design-system repositories, +canonical screenshots, docs, resolver sources, and include/exclude boundaries. + +### 2. Map + +Run when `ghost scan` recommends `map`. + +Follow [map.md](map.md). Write `.ghost/map.md`, then validate: + +```bash +ghost lint .ghost +``` + +### 3. Survey + +Run when `ghost scan` recommends `survey`. + +Follow [survey.md](survey.md). Write `.ghost/survey.json`, then finalize and +validate: + +```bash +ghost survey fix-ids .ghost/survey.json -o .ghost/survey.json +ghost lint .ghost +``` + +### 4. Patterns + +Run when `ghost scan` recommends `patterns`. + +Follow [patterns.md](patterns.md). Start from the derived pattern draft: + +```bash +ghost survey patterns .ghost/survey.json -o .ghost/patterns.yml +``` + +Curate names, anatomy, variants, anti-patterns, confidence, and evidence. Then: + +```bash +ghost verify .ghost --root +ghost lint .ghost +``` + +If readiness is `substrate-only` or `component-demo`, keep `patterns.yml` +honest about that limited authority. Empty `composition_patterns` is acceptable +when no implemented product surfaces exist. + +### Optional. Checks + +First captures may leave `checks.yml` with `checks: []`. Candidate checks belong +in your response or capture notes until a human curator promotes them. + +When checks are promoted, validate and smoke-test: + +```bash +ghost lint .ghost +ghost check --base HEAD +``` + +## Resumability + +Run `ghost scan` between stages. To force a stage rerun, delete or replace that +artifact and re-run status. Do not move forward from a failed lint or verify +result. + +## Never + +- Never describe root-level `fingerprint.md` as canonical. +- Never invent values or composition observations absent from `survey.json`. +- Never promote subjective composition prose directly into `checks.yml`; make it + deterministic or keep it advisory. diff --git a/packages/ghost/src/skill-bundle/references/critique.md b/packages/ghost/src/skill-bundle/references/critique.md index 56da109..6378d32 100644 --- a/packages/ghost/src/skill-bundle/references/critique.md +++ b/packages/ghost/src/skill-bundle/references/critique.md @@ -1,12 +1,12 @@ --- name: critique -description: Critique generated or changed work using Ghost memory and ghost. +description: Critique generated or changed work using the Ghost fingerprint and CLI. --- -# Critique With Ghost Scan +# Critique With The Ghost Fingerprint -Use this after generated or changed UI exists. `ghost` remains the judge; -Ghost Scan adds role-aware interpretation. +Use this after generated or changed UI exists. `ghost` emits deterministic +checks and advisory packets; the fingerprint supplies role-aware interpretation. ## Steps @@ -18,11 +18,11 @@ Ghost Scan adds role-aware interpretation. - engineering: implementation choices that preserve experience - pm: product promise and tradeoffs - qa: experience invariants and edge states -5. Classify each issue as fix, intentional divergence, or missing memory. +5. Classify each issue as fix, intentional divergence, or missing fingerprint context. ## Output Lead with actionable findings. Cite diff locations, patterns, survey evidence, intent, accepted decisions, and repairs where relevant. -Never fail a build on advisory-only memory. Only active `checks.yml` gates block. +Never fail a build on advisory-only context. Only active `checks.yml` gates block. diff --git a/packages/ghost/src/skill-bundle/references/map.md b/packages/ghost/src/skill-bundle/references/map.md index 02a5aa9..831c059 100644 --- a/packages/ghost/src/skill-bundle/references/map.md +++ b/packages/ghost/src/skill-bundle/references/map.md @@ -1,6 +1,6 @@ --- name: map -description: Author the map.md for a target — Ghost's topology card. The first stage of a scan. +description: Author the map.md for a target - Ghost's topology card. The first stage of Fingerprint Capture. handoffs: - label: Survey values into survey.json command: (next stage — survey recipe) @@ -12,7 +12,7 @@ handoffs: # Recipe: Author a target's map.md -**Goal:** produce a valid `.ghost/map.md` (`ghost.map/v2`) that captures the *topology* of the target — what platform it ships on, what it builds with, where the design system lives, and where implemented UI can actually be observed. `map.md` is the topology stage of a package scan: later stages (`survey.json`, `patterns.yml`, and optional `checks.yml`) read it to skip rediscovery and route changes. +**Goal:** produce a valid `.ghost/map.md` (`ghost.map/v2`) that captures the *topology* of the target - what platform it ships on, what it builds with, where the design system lives, and where implemented UI can actually be observed. `map.md` is the topology stage of Fingerprint Capture: later stages (`survey.json`, `patterns.yml`, and optional `checks.yml`) read it to skip rediscovery and route changes. This recipe is *your* job. Ghost's CLI provides `ghost inventory` (deterministic raw signals) and `ghost lint ` (validation), but you do the synthesis. @@ -44,8 +44,8 @@ The `ghost.map/v2` frontmatter requires: - **`schema: ghost.map/v2`** (literal) - **`id`** — slug (lowercase alphanumeric plus `.` `_` `-`, leading alphanumeric). For fleet scans, this is the fleet target id. - **`repo`** — GitHub `org/repo`, or any source identifier that uniquely names this target. -- **`subject`** — optional `{id, target}` that names the single thing this bundle will describe. Use it when the scan needs multiple sources; `subject` stays the primary claim. -- **`sources`** — optional scan source graph. Each source is `{id?, role, target, resolves?, paths?}` where `role` is `primary` or `resolver`. `primary` supplies usage/salience; `resolver` supplies concrete meaning for imported symbols. Declare exactly one primary when `sources[]` is present. +- **`subject`** — optional `{id, target}` that names the single thing this bundle will describe. Use it when capture needs multiple sources; `subject` stays the primary claim. +- **`sources`** — optional capture source graph. Each source is `{id?, role, target, resolves?, paths?}` where `role` is `primary` or `resolver`. `primary` supplies usage/salience; `resolver` supplies concrete meaning for imported symbols. Declare exactly one primary when `sources[]` is present. - **`mapped_at`** — current ISO date (`YYYY-MM-DD`) or full datetime. - **`platform`** — one of `web`, `ios`, `android`, `desktop`, `flutter`, `mixed`, `other`, or an array spanning multiple. The inventory's `platform_hints` is your starting point — accept it when consistent, override when you have evidence. - **`languages`** — array of `{name, files, share}` from the inventory histogram. `share` is fraction in [0,1]. @@ -55,10 +55,10 @@ The `ghost.map/v2` frontmatter requires: - **`composition.rendering`** — short slug (`react-spa`, `next-app-router`, `swiftui`, `compose`, `static`, `mixed`, …). - **`composition.styling`** — array (e.g. `["tailwindcss"]`, `["scss-modules"]`, `["styled-components"]`). - **`composition.navigation`** — optional short slug (`next-router`, `react-router`, `swiftui-navigation`, …). -- **`registry`** — optional `{path, components}` if a shadcn-style registry exists. -- **`design_system`** — `{paths[], entry_files?, derived_files?, path_patterns?, token_source?, upstream?, status}`. `token_source` is `inline` / `external` / `mixed`. `status` is `active` / `mixed` / `unclear`. Set `upstream` when `token_source` is `external` or `mixed`, and prefer representing each upstream as a `sources[]` resolver when the scan author can inspect it. +- **`registry`** — optional `{path, components}` if a shadcn-style registry exists. Component registries are substrate evidence; they do not imply product surfaces by themselves. +- **`design_system`** — `{paths[], entry_files?, derived_files?, path_patterns?, token_source?, upstream?, status}`. `token_source` is `inline` / `external` / `mixed`. `status` is `active` / `mixed` / `unclear`. Set `upstream` when `token_source` is `external` or `mixed`, and prefer representing each upstream as a `sources[]` resolver when the capture author can inspect it. - **`surface_sources`** — `{render_strategy, include[], exclude[], coverage_gaps?}` — globs for implemented UI plus how the surveyor can observe it. `render_strategy` is one of `browser`, `storybook`, `docs`, `native-screenshot`, `static-source`, `mixed`, or `unknown`. -- **`feature_areas`** — array of `{name, paths[], sub_areas?[]}` describing sampling clusters for implemented surfaces. These are product or documentation surfaces, not just folders. 3–8 areas is typical; fewer is fine for small repos. +- **`feature_areas`** — array of `{name, paths[], sub_areas?[]}` describing sampling clusters for implemented surfaces. These are product or documentation surfaces, not just folders. For component-library-only repos, use areas like `components`, `tokens`, `stories`, or `docs` so the survey can record substrate/demo evidence without pretending product flows exist. - **`orientation_files`** — array of files an agent should read first to understand the target. ### 3. Use a manifest if one is provided @@ -72,18 +72,18 @@ If no manifest is provided, derive `feature_areas` and `surface_sources` from th Choose the strongest observation path the target supports: - `browser` — app routes can be launched and inspected in a browser. -- `storybook` — Storybook or equivalent component stories are the primary observable surface. -- `docs` — docs/catalogue examples are the primary observable surface. +- `storybook` — Storybook or equivalent component stories are the primary observable demo surface. +- `docs` — docs/catalogue examples are the primary observable demo surface. - `native-screenshot` — native UI is represented by screenshot fixtures or simulator captures. - `static-source` — no renderer is available; surveyor must infer from source files. - `mixed` — more than one strategy is materially needed. - `unknown` — no trustworthy implemented UI surface is discoverable. Use only with `coverage_gaps` explaining what is missing. -`coverage_gaps` should list important blind spots ("native screens require simulator access", "marketing routes are generated from CMS", "no screenshots checked in"). Empty or absent means the scan author believes the declared sources are enough for the survey stage. +`coverage_gaps` should list important blind spots ("no product screens exist yet", "native screens require simulator access", "marketing routes are generated from CMS", "no screenshots checked in"). Empty or absent means the capture author believes the declared sources are enough for the survey stage. ### 3a. Source graph for split repos -Use a source graph when the target's design language is only observable through dependencies (apps consuming token packages, native apps importing design-system modules, wrappers over upstream registries). The bundle still has one subject; the scan may have many sources. +Use a source graph when the target's design language is only observable through dependencies (apps consuming token packages, native apps importing design-system modules, wrappers over upstream registries). The bundle still has one subject; Fingerprint Capture may have many sources. Rule: diff --git a/packages/ghost/src/skill-bundle/references/patterns.md b/packages/ghost/src/skill-bundle/references/patterns.md index dbf1b06..e20db3f 100644 --- a/packages/ghost/src/skill-bundle/references/patterns.md +++ b/packages/ghost/src/skill-bundle/references/patterns.md @@ -25,6 +25,12 @@ ghost survey patterns .ghost/survey.json -o .ghost/patterns.yml Then curate the draft. Keep every pattern evidence-backed. +If `ghost scan --format json` reports `readiness.state: substrate-only`, leave +`composition_patterns` empty and keep advisory expectations focused on +tokens/components/values. If readiness is `component-demo`, mark patterns as +demo or component-anatomy evidence; do not present them as product composition, +surface flow, or hierarchy. + ## Authoring Rules - Use stable slugs: `resource-index`, `dense-resource-index`, `settings-stack`. diff --git a/packages/ghost/src/skill-bundle/references/promote.md b/packages/ghost/src/skill-bundle/references/promote.md index 9e8b65e..fd1e2d1 100644 --- a/packages/ghost/src/skill-bundle/references/promote.md +++ b/packages/ghost/src/skill-bundle/references/promote.md @@ -1,9 +1,9 @@ --- name: promote -description: Promote a human-approved proposal into canonical Ghost memory. +description: Promote a human-approved proposal into the Ghost fingerprint. --- -# Promote A Memory Proposal +# Promote A Fingerprint Proposal Use this only when a human has accepted a proposal or explicitly asks to record the decision. diff --git a/packages/ghost/src/skill-bundle/references/propose.md b/packages/ghost/src/skill-bundle/references/propose.md new file mode 100644 index 0000000..63ac15f --- /dev/null +++ b/packages/ghost/src/skill-bundle/references/propose.md @@ -0,0 +1,40 @@ +--- +name: propose +description: Write a candidate ghost.proposal/v1 artifact from a session. +--- + +# Propose A Fingerprint Update + +Use this when a design review, implementation, QA finding, or PM discussion +reveals a product-experience decision that may belong in the Ghost fingerprint. + +## Steps + +1. Confirm the observation is about product experience: perceived, used, + trusted, understood, or safely changed. +2. Check whether accepted decisions, patterns, checks, or intent already cover it. +3. If it is new, write `.ghost/proposals/.yml`. +4. Use schema `ghost.proposal/v1`. +5. Run `ghost lint .ghost`. + +## Proposal Shape + +```yaml +schema: ghost.proposal/v1 +id: saved-payment-empty-state +status: open +kind: decision +title: Saved payment empty state should teach recovery +claim: Empty states for saved payment methods should prioritize recovery over education. +rationale: The user is blocked from paying, not browsing product concepts. +scope: + roles: [design, pm, qa] + surface_types: [empty-state] +evidence: + - path: apps/payments/empty-state.tsx +proposed_action: + target: decisions + summary: Promote into an accepted product-experience decision if repeated. +``` + +Do not write accepted decisions without human approval. diff --git a/packages/ghost/src/skill-bundle/references/recall.md b/packages/ghost/src/skill-bundle/references/recall.md index e744b20..cf308fc 100644 --- a/packages/ghost/src/skill-bundle/references/recall.md +++ b/packages/ghost/src/skill-bundle/references/recall.md @@ -1,12 +1,12 @@ --- name: recall -description: Summarize relevant Ghost product-experience memory for a task. +description: Summarize relevant Ghost fingerprint context for a task. --- -# Recall Product-Experience Memory +# Recall Fingerprint Context -Use this when the user asks what Ghost remembers, how a product usually handles -a surface, or what constraints matter before work begins. +Use this when the user asks what the fingerprint says, how a product usually +handles a surface, or what constraints matter before work begins. ## Steps @@ -15,7 +15,7 @@ a surface, or what constraints matter before work begins. 3. Read `.ghost/patterns.yml` for matching surface and composition patterns. 4. Read `.ghost/checks.yml` for active or proposed deterministic gates. 5. Read `.ghost/decisions/*.yml`; include only `status: accepted` as canonical. -6. Skim `.ghost/proposals/*.yml` separately as unresolved working memory. +6. Skim `.ghost/proposals/*.yml` separately as unresolved candidate updates. ## Output diff --git a/packages/ghost/src/skill-bundle/references/scan.md b/packages/ghost/src/skill-bundle/references/scan.md deleted file mode 100644 index 97b0cf4..0000000 --- a/packages/ghost/src/skill-bundle/references/scan.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -name: scan -description: Drive a full Ghost scan to produce .ghost/{resources.yml,map.md,survey.json,patterns.yml} plus optional checks.yml and intent.md. -handoffs: - - label: Inspect stage status - command: ghost scan - prompt: What fingerprint bundle stage should I run next? - - label: Run deterministic drift checks - command: ghost check - prompt: Run ghost check against this bundle ---- - -# Recipe: Scan A Target End-To-End - -**Goal:** produce a complete root fingerprint bundle: - -```text -.ghost/ - resources.yml - map.md - survey.json - patterns.yml - checks.yml - intent.md # optional -``` - -## Overview - -```text -resources -> map -> survey -> patterns -``` - -- `resources.yml`: references that define the product. -- `map.md`: topology and routing. -- `survey.json`: observed factual evidence. -- `patterns.yml`: operational composition grammar backed by survey evidence. -- `checks.yml`: optional human-promoted deterministic gates. -- `intent.md`: optional human-authored or human-approved product intent. - -## Steps - -### 0. Initialize - -```bash -ghost init -ghost scan -``` - -Use `--with-intent` only when you have human-authored or human-approved intent -to record. - -### 1. Resources - -Run when `scan` recommends `resources`. - -Author `.ghost/resources.yml` from the target, any design-system repositories, -canonical screenshots, docs, resolver sources, and include/exclude boundaries. - -### 2. Map - -Run when `scan` recommends `map`. - -Follow [map.md](map.md). Write `.ghost/map.md`, then validate: - -```bash -ghost lint .ghost -``` - -### 3. Survey - -Run when `scan` recommends `survey`. - -Follow [survey.md](survey.md). Write `.ghost/survey.json`, then finalize and -validate: - -```bash -ghost survey fix-ids .ghost/survey.json -o .ghost/survey.json -ghost lint .ghost -``` - -### 4. Patterns - -Run when `scan` recommends `patterns`. - -Follow [patterns.md](patterns.md). Start from the derived pattern draft: - -```bash -ghost survey patterns .ghost/survey.json -o .ghost/patterns.yml -``` - -Curate names, anatomy, variants, anti-patterns, confidence, and evidence. Then: - -```bash -ghost verify .ghost --root -ghost lint .ghost -``` - -### Optional. Checks - -First scans may leave `checks.yml` with `checks: []`. Candidate checks belong in -your response or scan notes until a human curator promotes them. - -When checks are promoted, validate and smoke-test: - -```bash -ghost lint .ghost -ghost check --base HEAD -``` - -## Resumability - -Run `ghost scan` between stages. To force a stage rerun, -delete or replace that artifact and re-run status. Do not move forward from a -failed lint or verify result. - -## Never - -- Never describe root-level `fingerprint.md` as canonical. -- Never invent values or composition observations absent from `survey.json`. -- Never promote subjective composition prose directly into `checks.yml`; make it - deterministic or keep it advisory. diff --git a/packages/ghost/src/skill-bundle/references/survey.md b/packages/ghost/src/skill-bundle/references/survey.md index dab1a05..e5c235f 100644 --- a/packages/ghost/src/skill-bundle/references/survey.md +++ b/packages/ghost/src/skill-bundle/references/survey.md @@ -14,7 +14,7 @@ handoffs: **Goal:** produce a valid `survey.json` (`ghost.survey/v2`) that catalogues every concrete design value and implemented UI surface the target ships, with structured specs, occurrence counts, and surface evidence. **You are the surveyor, not the interpreter.** Record what is there. Do not assign meaning. Do not write prose. Do not invent. -`survey.json` is the evidence artifact in the package scan: resources (`resources.yml`) → map (`map.md`) → survey (`survey.json`) → patterns (`patterns.yml`). The interpreter reads your survey as evidence and writes operational composition grammar. If you skip values or fabricate them here, the package downstream is wrong. +`survey.json` is the evidence artifact in Fingerprint Capture: resources (`resources.yml`) -> map (`map.md`) -> survey (`survey.json`) -> patterns (`patterns.yml`). The interpreter reads your survey as evidence and writes operational composition grammar. If you skip values or fabricate them here, the package downstream is wrong. The survey is an evidence ledger, not prompt context. It should be large enough to justify patterns, checks, and advisory review; the patterns stage decides what becomes generation-facing composition guidance and check candidates. @@ -42,11 +42,11 @@ Each row carries an `id` (deterministic SHA-256 prefix you do **not** compute by - **`values[]`** — every concrete literal that ships in the design language. `kind` is open; recommended values: `color`, `spacing`, `typography`, `radius`, `shadow`, `breakpoint`, `motion`, `layout-primitive`. Other kinds (`z-index`, `opacity`, `cursor`, `gradient`, `iconography`, `aspect-ratio`) get a `value-kind-unknown` warning but are accepted — emit them when they matter. - **`tokens[]`** — every named token declared in source (CSS variables, theme keys, design-token entries). Each row has `name`, `alias_chain` (path through any indirection — `["--button-bg", "--color-brand-primary"]` for a two-step chain; `[]` for a leaf defined inline), `resolved_value` (end-of-chain literal), optional `by_theme` for light/dark variants. - **`components[]`** — every named component you can confidently identify (registry entries, exported PascalCase components with variants/sizes). Loose schema: `name`, `discovered_via` (`registry.json` / `heuristic` / etc.), optional `variants[]` and `sizes[]`. -- **`ui_surfaces[]`** — implemented UI specimens the design language can be inferred from. Each row has `name`, `kind` (`route`, `story`, `screen`, `fixture`, `doc-example`, `screenshot`, `source`), `locator`, `renderability` (`rendered`, `screenshot`, `source-only`, `unknown`), `files[]`, optional soft `classification`, and factual `signals`. +- **`ui_surfaces[]`** — implemented UI specimens the design language can be inferred from. Each row has `name`, `kind` (`route`, `story`, `screen`, `fixture`, `doc-example`, `screenshot`, `source`), `locator`, `renderability` (`rendered`, `screenshot`, `source-only`, `unknown`), `files[]`, optional soft `classification`, and factual `signals`. For net-new component libraries, `story`, `doc-example`, and `fixture` rows are useful demo evidence; do not classify them as product routes/screens. External libraries (icon sets, primitive collections, motion libs, charting, etc.) are intentionally *not* a survey section. Whether a system uses Radix or hand-rolls primitives doesn't change what its design language *is*. When a library matters to the design language (icon family, font sourcing), surface it in the interpreter stage as prose evidence under the relevant decision dimension instead. -Every row needs `occurrences` (total count across the scan) and (for values) `files_count` (distinct files that contain the value). Optional `usage` breaks down by context: `{className: 30, css_var: 17}`. Optional `role_hypothesis` is a single tentative role tag (`brand-primary`, `surface-elevated`); **leave it empty if you are not sure** — the interpreter does role assignment, not you. +Every row needs `occurrences` (total count across the capture) and (for values) `files_count` (distinct files that contain the value). Optional `usage` breaks down by context: `{className: 30, css_var: 17}`. Optional `role_hypothesis` is a single tentative role tag (`brand-primary`, `surface-elevated`); **leave it empty if you are not sure** — the interpreter does role assignment, not you. `ui_surfaces[].classification` is a retrieval lens, not design truth. `intent` and `surface_type` are open strings; `density`, `layout_shape`, and `confidence` should be omitted unless evidence is clear. `ui_surfaces[].signals` contains observed facts only: `dominant_components`, `layout_patterns`, `breakpoint_behavior`, `value_refs`, and short factual `notes`. @@ -78,7 +78,7 @@ Open `map.md`. Note: - `composition.styling` — Tailwind, CSS modules, styled-components, scss, swift-tokens, etc. Drives your extraction strategy. - `composition.frameworks` — react, next, swiftui, compose, … - `design_system.entry_files` — start here. These declare the canonical token set. -- `sources[]` — scan source graph when the target needs upstream packages to resolve symbols. `primary` supplies usage; `resolver` supplies values. +- `sources[]` — capture source graph when the target needs upstream packages to resolve symbols. `primary` supplies usage; `resolver` supplies values. - `design_system.paths` — directories where the design system lives. - `surface_sources.render_strategy` — how implemented UI can be observed. - `surface_sources.include` / `.exclude` — globs that bound surface discovery. @@ -88,7 +88,7 @@ Decide your extraction strategy from these signals — see Step 2. ## The exhaustiveness rule -Recall is the failure mode and the only one. A survey missing 90% of a section's rows is a failed scan, even if every row that *is* there is well-formed — the interpreter downstream cannot recover what you didn't record. +Recall is the failure mode and the only one. A survey missing 90% of a section's rows is a failed capture, even if every row that *is* there is well-formed — the interpreter downstream cannot recover what you didn't record. For every section (`values[]`, `tokens[]`, `components[]`, `ui_surfaces[]`): @@ -114,7 +114,7 @@ If the repo mixes dialects (e.g. `swiftui` + `arcade`), run extraction per diale ## Resolver pass for source graphs -For split repos, a local app scan is not complete until symbolic usage has been resolved through the declared resolver sources where possible. This is still an app scan: the primary source decides salience, resolver sources only supply meaning. +For split repos, a local app capture is not complete until symbolic usage has been resolved through the declared resolver sources where possible. This is still an app capture: the primary source decides salience, resolver sources only supply meaning. Run this pass when `map.md` has `sources[]` with a `resolver` role, or when `design_system.token_source` is `external` / `mixed`. @@ -165,7 +165,7 @@ For components: ### 4. Record implemented UI surfaces -Use `surface_sources` and `feature_areas[]` from `map.md` to enumerate representative implemented surfaces. This section is required in `ghost.survey/v2`. If no implemented surface can be observed, write `ui_surfaces: []`, ensure `map.md` uses `surface_sources.render_strategy: unknown`, and carry the coverage gap in your scratchpad for the interpreter. +Use `surface_sources` and `feature_areas[]` from `map.md` to enumerate representative implemented surfaces. This section is required in `ghost.survey/v2`. If only component demos exist, record them honestly as `story`, `doc-example`, or `fixture` rows. If no implemented or demo surface can be observed, write `ui_surfaces: []`, ensure `map.md` uses `surface_sources.render_strategy: unknown`, and carry the coverage gap in your scratchpad for the interpreter. Surface rows are evidence, not exemplars and not prose. Record facts that the later patterns authoring pass can cluster: @@ -257,9 +257,10 @@ Before declaring the survey done, walk each section and confirm exhaustiveness: - **`components[]`** — what's the canonical signal in this repo? Count it independently. If your row count is below that count, you've under-recorded. Either add the missing rows or, if the section truly isn't enumerable here, leave the array empty. - **`ui_surfaces[]`** — compare rows against `surface_sources` and `feature_areas[]`. Every major implemented surface family should have at least one row, or the coverage gap should be explicit. +- **Readiness** — after validation, run `ghost scan --format json`. If readiness is `substrate-only`, the survey can support token/component/value guidance but not product composition. If readiness is `component-demo`, story/doc/example evidence can support component anatomy and demo composition, not product flow or hierarchy. - **`tokens[]`** — count the named-token declarations in the canonical token source(s) named in `map.md`. Your row count should match. - **`values[]`** — frequency-cluster again with a fresh grep. New top-N entries that aren't in your survey = missed. - For resolver-backed scans, also check unresolved symbols by kind; top unresolved symbols should either be resolved or explicitly surfaced as coverage gaps. + For resolver-backed captures, also check unresolved symbols by kind; top unresolved symbols should either be resolved or explicitly surfaced as coverage gaps. The survey is **saturated** when another exhaustiveness pass adds fewer than ~2 new rows across all sections AND your component/token row counts match (or come very close to) an independent count of the canonical signal. If exhaustiveness disagrees with what you have, exhaustiveness wins — re-pass. Hard stop conditions: diff --git a/packages/ghost/test/cli.test.ts b/packages/ghost/test/cli.test.ts index b6fc8bc..c96f5dc 100644 --- a/packages/ghost/test/cli.test.ts +++ b/packages/ghost/test/cli.test.ts @@ -223,7 +223,7 @@ describe("ghost CLI", () => { expect(manifest.dimensions.typography.reason).toBe("editorial"); }); - it("initializes a bundle and reports scan state as json", async () => { + it("initializes a bundle and reports fingerprint capture state as json", async () => { const init = await runCli(["init", "--with-intent"], dir); const scan = await runCli(["scan", "--format", "json"], dir); @@ -238,6 +238,7 @@ describe("ghost CLI", () => { const status = JSON.parse(scan.stdout); expect(status.resources.state).toBe("present"); expect(status.map.state).toBe("present"); + expect(status.readiness.state).toBe("unobservable"); }); it("runs inventory, lint, and verify from the unified cli", async () => { @@ -280,6 +281,42 @@ describe("ghost CLI", () => { expect(JSON.parse(patterns.stdout).schema).toBe("ghost.patterns/v1"); }); + it("keeps derived patterns substrate-aware when no UI surfaces exist", async () => { + await mkdir(join(dir, ".ghost"), { recursive: true }); + await writeFile( + join(dir, ".ghost", "survey.json"), + JSON.stringify({ + schema: "ghost.survey/v2", + sources: [ + { id: "library", target: ".", scanned_at: "2026-05-19T00:00:00Z" }, + ], + values: [], + tokens: [], + components: [ + { + id: "component_button", + source: { target: ".", scanned_at: "2026-05-19T00:00:00Z" }, + name: "Button", + discovered_via: "registry.json", + }, + ], + ui_surfaces: [], + }), + ); + + const result = await runCli( + ["survey", "patterns", ".ghost/survey.json", "--format", "json"], + dir, + ); + + expect(result.code).toBe(0); + const patterns = JSON.parse(result.stdout); + expect(patterns.composition_patterns).toHaveLength(0); + expect(patterns.advisory.review_expectations[0]).toContain( + "No UI surface evidence", + ); + }); + it("runs survey fix-ids from the unified cli", async () => { await writeComparableBundle(join(dir, ".ghost"), "sectioned-form"); @@ -327,7 +364,8 @@ describe("ghost CLI", () => { expect(result.code).toBe(0); for (const path of [ "SKILL.md", - "references/scan.md", + "references/capture.md", + "references/propose.md", "references/review.md", "references/remediate.md", "references/brief.md", diff --git a/packages/ghost/test/scan-status.test.ts b/packages/ghost/test/scan-status.test.ts new file mode 100644 index 0000000..2620463 --- /dev/null +++ b/packages/ghost/test/scan-status.test.ts @@ -0,0 +1,184 @@ +import { mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { scanStatus } from "../src/scan/scan-status.js"; + +describe("scanStatus readiness", () => { + let dir: string; + + beforeEach(async () => { + dir = join( + tmpdir(), + `ghost-readiness-${Date.now()}-${Math.random().toString(36).slice(2)}`, + ); + await mkdir(dir, { recursive: true }); + }); + + afterEach(async () => { + await rm(dir, { recursive: true, force: true }); + }); + + it("reports pending readiness before map and survey exist", async () => { + const status = await scanStatus(dir); + + expect(status.readiness.state).toBe("pending"); + expect(status.readiness.reasons.join(" ")).toContain("map.md is missing"); + expect(status.readiness.reasons.join(" ")).toContain( + "survey.json is missing", + ); + }); + + it("reports substrate-only when only values, tokens, or components exist", async () => { + await writeFile(join(dir, "map.md"), mapFile("unknown")); + await writeFile( + join(dir, "survey.json"), + JSON.stringify( + surveyFile({ + components: [ + { + id: "component_button", + source: source(), + name: "Button", + discovered_via: "registry.json", + }, + ], + ui_surfaces: [], + }), + ), + ); + + const status = await scanStatus(dir); + + expect(status.readiness.state).toBe("substrate-only"); + expect(status.readiness.substrate_rows.components).toBe(1); + expect(status.readiness.can_review).toContain("components"); + expect(status.readiness.cannot_review).toContain("product composition"); + }); + + it("reports component-demo when only stories or docs examples exist", async () => { + await writeFile(join(dir, "map.md"), mapFile("storybook")); + await writeFile( + join(dir, "survey.json"), + JSON.stringify( + surveyFile({ + ui_surfaces: [ + { + id: "surface_button_story", + source: source(), + name: "Button story", + kind: "story", + locator: "button--default", + renderability: "rendered", + files: ["src/button.stories.tsx"], + signals: { layout_patterns: ["button-demo"] }, + }, + ], + }), + ), + ); + + const status = await scanStatus(dir); + + expect(status.readiness.state).toBe("component-demo"); + expect(status.readiness.demo_surface_count).toBe(1); + expect(status.readiness.cannot_review).toContain("surface flow"); + }); + + it("reports product-observed when route or screen evidence exists", async () => { + await writeFile(join(dir, "map.md"), mapFile("static-source")); + await writeFile( + join(dir, "survey.json"), + JSON.stringify( + surveyFile({ + ui_surfaces: [ + { + id: "surface_settings", + source: source(), + name: "Settings", + kind: "route", + locator: "/settings", + renderability: "source-only", + files: ["src/routes/settings.tsx"], + signals: { layout_patterns: ["settings-stack"] }, + }, + ], + }), + ), + ); + + const status = await scanStatus(dir); + + expect(status.readiness.state).toBe("product-observed"); + expect(status.readiness.product_surface_count).toBe(1); + expect(status.readiness.can_review).toContain("product composition"); + }); +}); + +function source() { + return { target: ".", scanned_at: "2026-05-19T00:00:00.000Z" }; +} + +function surveyFile(overrides: Record) { + return { + schema: "ghost.survey/v2", + sources: [source()], + values: [], + tokens: [], + components: [], + ui_surfaces: [], + ...overrides, + }; +} + +function mapFile(renderStrategy: string): string { + return `--- +schema: ghost.map/v2 +id: local +repo: local +mapped_at: 2026-05-19 +platform: web +languages: + - { name: typescript, files: 1, share: 1 } +build_system: pnpm +package_manifests: + - package.json +composition: + frameworks: + - { name: react } + rendering: react-spa + styling: + - css +design_system: + paths: + - src/components + status: active +surface_sources: + render_strategy: ${renderStrategy} + include: + - src/** + exclude: + - node_modules/** + coverage_gaps: + - no product screens exist yet +feature_areas: + - name: components + paths: + - src/components +orientation_files: + - README.md +--- + +## Identity + +Local test target. + +## Topology + +Components live under src/components. + +## Conventions + +Tests use a compact map fixture. +`; +}