Skip to content
Closed
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/restore-handoff-grounding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@design-intelligence/ghost": minor
---

Add `ghost pull --with-cover` to restore the cover with the same selected guidance after a session handoff.
15 changes: 14 additions & 1 deletion packages/ghost/src/commands/pull-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export function registerPullCommand(cli: CAC): void {
"--no-materials",
"Emit material locators only; do not inline files",
)
.option(
"--with-cover",
"Also pull the package's resolved cover without reopening selection",
)
.option("--order <mode>", "Output order: steering or given", {
default: "steering",
})
Expand All @@ -49,9 +53,18 @@ export function registerPullCommand(cli: CAC): void {

const paths = resolveGhostPackage(opts.package, process.cwd());
const snapshot = await loadGhostSnapshot(paths);
const pullIds =
opts.withCover && snapshot.cover.state === "resolved"
? [snapshot.cover.id, ...ids]
: ids;
if (opts.withCover && snapshot.cover.state !== "resolved") {
console.error(
`Warning: --with-cover added nothing because the package cover is ${snapshot.cover.state}.`,
);
}
const repoRoot = await resolveGitRoot(process.cwd());
const result = await pullGhostNodes(snapshot, {
ids,
ids: pullIds,
repoRoot,
inlineMaterials: opts.materials !== false,
order,
Expand Down
8 changes: 6 additions & 2 deletions packages/ghost/src/skill-bundle/references/ground.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ rewriting it into a brief or checklist. Inspect any material the output tells
you to inspect before generating; [making.md](making.md) covers unavailable or
external material.

`ghost pull` records the pulled ids. After compaction or a session handoff,
re-run it with the same ids to restore the guidance.
`ghost pull` records the pulled ids. Before compaction or a session handoff,
preserve those ids. Restore the same grounding with
`ghost pull --with-cover <same ids>`; this reloads the resolved cover and
selected guidance without exposing the menu or reopening selection. If
`gather` supplied a no-guidance instruction that is not in the cover body,
preserve that instruction too.

Then make the requested artifact. When the output includes a matching starting
structure, begin from it verbatim and fill it with task content.
55 changes: 55 additions & 0 deletions packages/ghost/test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,51 @@ describe("ghost CLI", () => {
);
});

it("pull --with-cover restores the cover without exposing the menu", async () => {
await runCli(["init"], dir);

const pull = await runCli(
["pull", "foundation.layout", "--with-cover"],
dir,
);

expect(pull.code).toBe(0);
expect(pull.stdout).toContain("# `brand`");
expect(pull.stdout).toContain("# `foundation.layout`");
expect(pull.stdout.indexOf("# `brand`")).toBeLessThan(
pull.stdout.indexOf("# `foundation.layout`"),
);
expect(pull.stdout).not.toContain("## Available guidance");

const events = (await readFile(join(dir, ".ghost", ".events"), "utf-8"))
.trim()
.split("\n")
.map((line) => JSON.parse(line));
expect(events.at(-1)).toMatchObject({
event: "pull",
ids: ["brand", "foundation.layout"],
});
});

it("pull --with-cover reports when no cover resolves", async () => {
await writeBareTestPackage(dir);
await writeFile(
join(dir, ".ghost", "manifest.yml"),
"schema: ghost.package/v1\nid: local\n",
);

const pull = await runCli(
["pull", "standard.model-defaults", "--with-cover"],
dir,
);

expect(pull.code).toBe(0);
expect(pull.stdout).toContain("# `standard.model-defaults`");
expect(pull.stderr).toContain(
"--with-cover added nothing because the package cover is absent",
);
});

it("keeps glossary kind purposes in JSON and only headings in Markdown", async () => {
await runCli(["init"], dir);

Expand Down Expand Up @@ -1745,6 +1790,16 @@ describe("ghost CLI", () => {
await expect(
readFile(join(dir, "skills", "ghost", "SKILL.md"), "utf-8"),
).resolves.toContain("When the package is silent");
const installedGround = await readFile(
join(dir, "skills", "ghost", "references", "ground.md"),
"utf-8",
);
expect(installedGround).toContain("ghost pull --with-cover <same ids>");
expect(installedGround).toContain(
"without exposing the menu or reopening selection",
);
expect(installedGround).toContain("preserve that instruction too");
expect(installedGround).not.toContain("ghost gather <same ask>");

const collision = await runCli(
["skill", "install", "--dest", "skills/ghost"],
Expand Down
Loading