Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/checks-v2-grounded-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@design-intelligence/ghost": minor
---

Break check format compatibility with `ghost.check/v2`: checks now require `for` (the situation in which the check applies, matching the node retrieval field) and resolving `references`, remove `context`, `name`, `description`, `source`, `tools`, and `turn_limit`, end `.agents/checks` shape compatibility, make unresolved check references validation errors so check-first authoring is retired, reshape `ghost review` into one grounded packet with routing, offered checks, and gaps removed plus a new JSON shape, and rename `parseSourceRef` to `parseGuidanceRef`. Add `ghost review [...checkIds]` filtering with unknown-id suggestions and exit 2 when no requested ids resolve. Add the `ghost review --no-materials` flag for locator-only material output. Refuse review with exit 1 when checks are invalid or cite unresolvable guidance, matching `ghost validate`. Break `/core` typed consumers of the v1 check frontmatter shape because `GhostCheckFrontmatter` removes legacy fields and now requires `references`.
20 changes: 10 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ ghost hands it the brand as a portable steering packet: a repo-local `.ghost/`
package, a flat corpus of prose nodes read before anything is made. The public npm shape is one package,
`@design-intelligence/ghost`, with one user-facing bin, `ghost`. The CLI
validates the corpus, emits the guidance menu, pulls selected nodes, records
local selection events, and assembles advisory review packets from checks. Optional
review checks attach under `.ghost/checks/`. The host agent does all
local selection events, and assembles one-shot grounded review packets from checks,
cited guidance, materials, and diffs. Optional review checks attach under `.ghost/checks/`. The host agent does all
selection and interpretive BYOA work through the installed `ghost` skill.

## Build & Run
Expand Down Expand Up @@ -77,15 +77,15 @@ guidance names its **condition**, the situation it applies in, never a filing
destination. `ghost gather` emits the complete menu (every node's id, kind,
context, material count, and payload labels); the agent pulls every node
whose stated situation applies to the actual task. `ghost pull` emits selected node bodies and materials. `ghost
review` reads a diff, matches touched files to node materials, offers relevant
checks, and emits an advisory packet for the host agent to judge.
review` reads a diff and emits a one-shot grounded packet with touched files,
selected checks, cited guidance excerpts, guidance materials, and the diff.

**Checks** (`.ghost/checks/*.md`) are optional review assertions that declare
`references` to guidance node ids (with optional heading anchors) and prose
instructions for the reviewing agent. Checks are feed-back only and never leak
into generation context. Scaffold them with `ghost checks init` or `ghost init
--with checks`. Ordinary Git review is the approval boundary for guidance
edits and checks.
`for`, `severity`, and resolving `references` to guidance node ids (with
optional heading anchors), plus prose instructions for the reviewing agent.
Checks are feed-back only and never leak into generation context. Scaffold them
with `ghost checks init` or `ghost init --with checks`. Ordinary Git review is
the approval boundary for guidance edits and checks.

## Packages

Expand All @@ -108,7 +108,7 @@ Core workflow:
| `ghost validate` | Validate the package: manifest shape, node validity, material locators, check references, and glossary kind prefixes. |
| `ghost gather [ask…]` | Emit the complete guidance menu so the agent can pull applicable nodes. |
| `ghost pull <id> [<id>…]` | Emit selected nodes' bodies and materials; append the selection to the local `.ghost/.events` tape. |
| `ghost review` | Emit an advisory review packet for a diff using material-backed nodes and checks (requires `.ghost/checks/`). |
| `ghost review` | Emit a one-shot grounded review packet for a diff using checks and cited guidance (requires `.ghost/checks/`). |
| `ghost stats` | Summarize local gather/pull events from `.ghost/.events`. |
| `ghost export` | Bundle the guidance as a portable tarball with a materials audit (`--strict` fails on stranded locators). |
| `ghost skill install` | Install the unified `ghost` skill bundle. |
Expand Down
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ghost checks init # opt in to review assertions
ghost validate # make sure the package is well-formed
ghost gather [ask] # before building: show the complete guidance menu
ghost pull <ids> # read the picked nodes' full bodies
ghost review # during review: match a diff to guidance and checks
ghost review # during review: assemble a grounded check packet
ghost export # bundle the guidance as a portable artifact
ghost stats # while tuning: see what agents reached for
```
Expand Down Expand Up @@ -143,20 +143,19 @@ Checks are not nodes. They are review assertions used by `ghost review`:

```markdown
---
name: logo-clearspace-holds
description: Logo usage preserves clearspace, lockup integrity, and glyph rules.
context: Logo usage must preserve clearspace, lockup integrity, and glyph rules.
severity: medium
references:
- asset.logo
- asset.logo > Clearspace
---

Grade whether the change preserves the logo guidance in `asset.logo`.
Grade whether the change preserves the logo guidance in `asset.logo > Clearspace`.
```

`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.
`review` supports the same agent after a change exists: the CLI reads a diff and
assembles selected checks, cited guidance excerpts, guidance materials, and the
diff into one grounded packet. Review output never enters generation context.

## The Package Travels

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ Ink carries content. Signal marks selection. Correction marks review.
<p>
Volume changes the work too. More instructions can flatten each
other until none of them wins. ghost favors compact descriptions,
selective pulls, and material-backed detail so the important
selective pulls, and cited material detail so the important
constraints are visible at the moment of making.
</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion docs/purposes.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Two rules keep the reservation honest:
| **Authoring** | `ghost init`, `ghost validate`, `ghost checks init` | The raw nodes, checks, and glossary for a human or agent writing the guidance. | the package | **No**, this is the model. |
| **Generation** | `ghost gather [ask…]`, `ghost pull <ids>` | The flat menu, then selected node bodies and materials. | nodes only | **No** if selection stays with the agent and checks stay invisible. |
| **Local signal** | `ghost stats` | The gitignored event tape (`.ghost/.events`) written by `gather` and `pull`, used to tune contexts and menu ergonomics. | event ids and miss suggestions | **No**, observability must not become ranking, memory, or canonical state. |
| **Diff review** | `ghost review` | Touched files matched to node `materials`, relevant checks, referenced prose, gaps, and the diff. | nodes, checks, diff | **No** if checks bind by `references` and are not gathered. |
| **Diff review** | `ghost review` | Touched files, selected checks, cited guidance excerpts, guidance materials, and the diff. | nodes, checks, diff | **No** if checks bind by `references` and are not gathered. |
| **Fleet** | (future) | Many ghost packages at once: distances, cohorts, summaries. | many corpora, read-only | **No**, consumes exports read-only. |

## Known leaks
Expand Down
2 changes: 1 addition & 1 deletion packages/ghost/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ ghost checks init # opt in to review assertions
ghost validate # make sure the package is well-formed
ghost gather [ask] # before building: show the complete guidance menu
ghost pull <ids> # read the picked nodes' full bodies
ghost review # during review: match a diff to guidance and checks
ghost review # during review: assemble a grounded check packet
ghost export # bundle the guidance as a portable artifact
ghost stats # while tuning: see what agents reached for
```
Expand Down
4 changes: 2 additions & 2 deletions packages/ghost/src/commands/command-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ const COMMAND_DISCOVERY = [
name: "review",
group: "core",
defaultHelp: true,
compactName: "review",
compactName: "review [checks]",
summary:
"Emit an advisory review packet for a diff (needs .ghost/checks/).",
"Emit a one-shot grounded review packet for a diff (needs .ghost/checks/).",
},
{
name: "export",
Expand Down
61 changes: 57 additions & 4 deletions packages/ghost/src/commands/review-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
buildReviewPacket,
formatReviewPacket,
} from "../review/review-packet.js";
import { lintCheckReferences } from "../scan/check-reference-lint.js";
import { loadGhostPackage } from "../scan/fingerprint-package.js";
import { exitCli, failFromError } from "./errors.js";

Expand All @@ -16,8 +17,8 @@ const execFileAsync = promisify(execFile);
export function registerReviewCommand(cli: CAC): void {
cli
.command(
"review",
"Emit an advisory review packet for a diff using material-backed nodes and checks.",
"review [...checkIds]",
"Emit a one-shot grounded review packet for a diff using checks and cited guidance.",
)
.option(
"--package <dir>",
Expand All @@ -28,7 +29,11 @@ export function registerReviewCommand(cli: CAC): void {
.option("--format <fmt>", "Output format: markdown or json", {
default: "markdown",
})
.action(async (opts) => {
.option(
"--no-materials",
"Emit material locators only; do not inline files",
)
.action(async (checkIds: string[], opts) => {
try {
const format = opts.format;
if (format !== "markdown" && format !== "json") {
Expand All @@ -41,19 +46,67 @@ export function registerReviewCommand(cli: CAC): void {
const ghostPackage = await loadGhostPackage(paths);
if (!ghostPackage.hasChecksDir) {
console.error(
"No checks directory. Run `ghost checks init` to add review assertions.",
"No checks directory. Run `ghost checks init` to add grounded review assertions.",
);
await exitCli(2);
return;
}
if (ghostPackage.invalidChecks.length > 0) {
for (const invalid of ghostPackage.invalidChecks) {
console.error(`${invalid.file}: ${invalid.message}`);
}
console.error("Run `ghost validate` to see every check issue.");
await exitCli(1);
return;
}
const referenceIssues = lintCheckReferences(
ghostPackage.catalog,
ghostPackage.checks,
);
if (referenceIssues.length > 0) {
for (const issue of referenceIssues) {
console.error(
`${issue.file}: ${issue.reference}: ${issue.message}`,
);
}
console.error("Run `ghost validate` to see every check issue.");
await exitCli(1);
return;
}

const diffText = await resolveDiff({
base: opts.base,
diff: opts.diff,
});
const packet = await buildReviewPacket(ghostPackage, diffText, {
packageDir: paths.packageDir,
cwd: process.cwd(),
ids: checkIds,
inlineMaterials: opts.materials !== false,
});

for (const miss of packet.missed ?? []) {
const hint =
miss.suggested.length > 0
? ` (did you mean ${miss.suggested.map((s) => `\`${s}\``).join(", ")}?)`
: "";
console.error(`Warning: unknown check \`${miss.requested}\`${hint}`);
}
if ((packet.missed?.length ?? 0) > 0) {
console.error(
"Run `ghost review` without ids to include every check.",
);
}
if (checkIds.length > 0 && packet.checks.length === 0) {
await exitCli(2);
return;
}
if (ghostPackage.checks.size === 0) {
console.error(
"No checks found in .ghost/checks/; emitting a packet with zero checks.",
);
}

process.stdout.write(
format === "json"
? `${JSON.stringify(packet, null, 2)}\n`
Expand Down
9 changes: 3 additions & 6 deletions packages/ghost/src/embed/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ function cloneCheckMap(
frontmatter: cloneCheckFrontmatter(check.doc.frontmatter),
body: check.doc.body,
},
references: [...check.references],
}),
]),
);
Expand All @@ -80,11 +79,9 @@ function cloneCheckFrontmatter(
frontmatter: GhostCheckFrontmatter,
): GhostCheckFrontmatter {
return {
...frontmatter,
...(frontmatter.tools ? { tools: [...frontmatter.tools] } : {}),
...(frontmatter.references
? { references: [...frontmatter.references] }
: {}),
for: frontmatter.for,
severity: frontmatter.severity,
references: [...frontmatter.references],
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { NodeIdSchema } from "../node/schema.js";

/**
* A parsed `source:` reference: the node path id it points at, plus the
* A parsed guidance reference: the node path id it points at, plus the
* optional heading anchor (`checkout/payment > Confirmation` → nodeId
* `checkout/payment`, heading `Confirmation`).
*/
export interface ParsedSourceRef {
export interface ParsedGuidanceRef {
nodeId: string;
heading?: string;
}

/**
* Parse the check `source:` reference grammar — `<node-id> > <Heading>`.
* Splits on the *first* `>` and trims both parts. Returns `null` when the
* node-id part is not a valid node path id (or the input is empty); the
* heading is present only when a non-empty anchor follows the `>`. This is
* shape validation only — whether the node (or heading) exists is the
* caller's concern: an unresolved ref may name not-yet-written prose.
* Parse the check guidance reference grammar: `<node-id> > <Heading>`.
* Splits on the first `>` and trims both parts. Returns `null` when the
* node-id part is not a valid node path id or the input is empty; the heading
* is present only when a non-empty anchor follows the `>`.
*/
export function parseSourceRef(raw: string): ParsedSourceRef | null {
export function parseGuidanceRef(raw: string): ParsedGuidanceRef | null {
const splitAt = raw.indexOf(">");
const nodePart = (splitAt === -1 ? raw : raw.slice(0, splitAt)).trim();
if (!NodeIdSchema.safeParse(nodePart).success) {
Expand Down
16 changes: 7 additions & 9 deletions packages/ghost/src/ghost-core/check/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
/**
* Public surface for `ghost.check/v1` — markdown + frontmatter checks an agent
* evaluates (ghost never runs them). Every check is offered to the reviewer;
* the agent judges relevance against the diff and the grounded prose. A check's
* optional `source:` names the fingerprint prose it enforces.
* Public surface for `ghost.check/v2`: markdown + frontmatter checks an agent
* evaluates. Checks are grounded review assertions that cite guidance nodes.
*/

export {
type ParsedGuidanceRef,
parseGuidanceRef,
sliceNodeSection,
} from "./guidance-ref.js";
export { lintGhostCheck } from "./lint.js";
export { loadGhostCheck } from "./load.js";
export { type ParsedCheckMarkdown, parseCheckMarkdown } from "./parse.js";
export {
type ParsedSourceRef,
parseSourceRef,
sliceNodeSection,
} from "./source-ref.js";
export {
GHOST_CHECK_SCHEMA,
GHOST_CHECK_SEVERITIES,
Expand Down
Loading
Loading