@design-intelligence/ghost 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -16
- package/dist/commands/gather-command.d.ts.map +1 -1
- package/dist/commands/gather-command.js +39 -117
- package/dist/commands/gather-command.js.map +1 -1
- package/dist/commands/pull-command.d.ts.map +1 -1
- package/dist/commands/pull-command.js +72 -135
- package/dist/commands/pull-command.js.map +1 -1
- package/dist/embed/gather.d.ts +9 -0
- package/dist/embed/gather.d.ts.map +1 -0
- package/dist/embed/gather.js +80 -0
- package/dist/embed/gather.js.map +1 -0
- package/dist/embed/index.d.ts +9 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +6 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/inspect.d.ts +3 -0
- package/dist/embed/inspect.d.ts.map +1 -0
- package/dist/embed/inspect.js +155 -0
- package/dist/embed/inspect.js.map +1 -0
- package/dist/embed/pull.d.ts +8 -0
- package/dist/embed/pull.d.ts.map +1 -0
- package/dist/embed/pull.js +96 -0
- package/dist/embed/pull.js.map +1 -0
- package/dist/embed/readonly-map.d.ts +3 -0
- package/dist/embed/readonly-map.d.ts.map +1 -0
- package/dist/embed/readonly-map.js +33 -0
- package/dist/embed/readonly-map.js.map +1 -0
- package/dist/embed/snapshot.d.ts +4 -0
- package/dist/embed/snapshot.d.ts.map +1 -0
- package/dist/embed/snapshot.js +105 -0
- package/dist/embed/snapshot.js.map +1 -0
- package/dist/embed/types.d.ts +156 -0
- package/dist/embed/types.d.ts.map +1 -0
- package/dist/embed/types.js +2 -0
- package/dist/embed/types.js.map +1 -0
- package/dist/ghost-core/index.d.ts +1 -1
- package/dist/ghost-core/index.d.ts.map +1 -1
- package/dist/ghost-core/index.js +1 -1
- package/dist/ghost-core/index.js.map +1 -1
- package/dist/ghost-core/material-transport.d.ts +11 -0
- package/dist/ghost-core/material-transport.d.ts.map +1 -1
- package/dist/ghost-core/material-transport.js +80 -8
- package/dist/ghost-core/material-transport.js.map +1 -1
- package/dist/observability-events.d.ts +1 -0
- package/dist/observability-events.d.ts.map +1 -1
- package/dist/observability-events.js +4 -1
- package/dist/observability-events.js.map +1 -1
- package/dist/scan/templates.d.ts.map +1 -1
- package/dist/scan/templates.js +0 -280
- package/dist/scan/templates.js.map +1 -1
- package/dist/skill-bundle/SKILL.md +1 -1
- package/dist/skill-bundle/references/capture.md +1 -2
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ email, empty state, or sentence.
|
|
|
8
8
|
Reviewers repeat the same feedback on every surface: "that's not our voice."
|
|
9
9
|
Write the decision in `.ghost/` so the next agent has it before starting work.
|
|
10
10
|
|
|
11
|
-
[
|
|
11
|
+
[Project site](https://block.github.io/ghost/) ·
|
|
12
12
|
[Repo](https://github.com/block/ghost)
|
|
13
13
|
|
|
14
14
|
## Install
|
|
@@ -55,37 +55,54 @@ pulls every node whose stated situation applies. Bare `ghost gather` inspects
|
|
|
55
55
|
the catalog without grounding a task.
|
|
56
56
|
|
|
57
57
|
Run `ghost --help` for the core workflow and `ghost <command> --help` for
|
|
58
|
-
flags
|
|
59
|
-
every command; [Getting Started](https://block.github.io/ghost/docs/getting-started)
|
|
60
|
-
covers the full model. Stuck? See
|
|
61
|
-
[Troubleshooting](https://block.github.io/ghost/docs/troubleshooting).
|
|
58
|
+
current flags and command behavior.
|
|
62
59
|
|
|
63
60
|
## Library
|
|
64
61
|
|
|
65
62
|
```ts
|
|
66
63
|
import {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
initGhostPackage,
|
|
65
|
+
lintGhostPackage,
|
|
66
|
+
loadGhostPackage,
|
|
70
67
|
} from "@design-intelligence/ghost/package";
|
|
71
68
|
import { buildCatalogMenu } from "@design-intelligence/ghost/core";
|
|
69
|
+
import {
|
|
70
|
+
gatherGhostPackage,
|
|
71
|
+
inspectGhostMaterial,
|
|
72
|
+
loadGhostSnapshot,
|
|
73
|
+
pullGhostNodes,
|
|
74
|
+
} from "@design-intelligence/ghost/embed";
|
|
72
75
|
import { buildCli } from "@design-intelligence/ghost/cli";
|
|
73
76
|
```
|
|
74
77
|
|
|
78
|
+
Embedded hosts can use `@design-intelligence/ghost/embed` for the same semantic
|
|
79
|
+
contract as CLI `gather` and `pull` without CLI-only presentation fields or event
|
|
80
|
+
side effects. `loadGhostSnapshot` reads the package, resolved/absent/dangling
|
|
81
|
+
cover state, glossary kinds, and checks. `gatherGhostPackage` returns the
|
|
82
|
+
complete unfiltered menu with the resolved cover separated from selectable
|
|
83
|
+
nodes; checks stay separate. `pullGhostNodes` validates and de-duplicates ids,
|
|
84
|
+
returns misses with suggestions, stable cover/concrete/prose ordering, stripped
|
|
85
|
+
node bodies, extracted Skeletons, and material transport packets. Use
|
|
86
|
+
`inspectGhostMaterial` only for materials declared by a pulled node; it is local
|
|
87
|
+
and bundled-only by default, with explicit host policy required for referenced
|
|
88
|
+
files. HTTPS inspection is always rejected. Returned text is source data, not
|
|
89
|
+
render-safe markup. Embedded operations do not write `.ghost/.events`; hosts may
|
|
90
|
+
persist exported observability events in their own telemetry.
|
|
91
|
+
|
|
75
92
|
Available subpath exports: `@design-intelligence/ghost`,
|
|
76
93
|
`@design-intelligence/ghost/package`,
|
|
77
|
-
`@design-intelligence/ghost/core`,
|
|
94
|
+
`@design-intelligence/ghost/core`,
|
|
95
|
+
`@design-intelligence/ghost/embed`, and
|
|
78
96
|
`@design-intelligence/ghost/cli`. The former package API remains available as
|
|
79
97
|
an explicitly deprecated compatibility alias.
|
|
80
98
|
|
|
81
|
-
## Project Status:
|
|
99
|
+
## Project Status: Development Preview
|
|
82
100
|
|
|
83
|
-
ghost is
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
upgrading.
|
|
101
|
+
ghost is being built in public, but it is not ready for adoption and the
|
|
102
|
+
project is not seeking external testers yet. The CLI, package schema, on-disk
|
|
103
|
+
`.ghost/` package shape, and public JavaScript exports may change without
|
|
104
|
+
migration support. Breaking changes may ship in minor versions; patch versions
|
|
105
|
+
are reserved for fixes that should not require migration.
|
|
89
106
|
|
|
90
107
|
Every `ghost` command is also available as `ghost-fingerprint` for when
|
|
91
108
|
another tool owns the `ghost` bin.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gather-command.d.ts","sourceRoot":"","sources":["../../src/commands/gather-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gather-command.d.ts","sourceRoot":"","sources":["../../src/commands/gather-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAQ/B,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAoDpD"}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildCatalogMenu, parseGlossary, } from "#ghost-core";
|
|
3
|
-
import { isMissingPathError } from "../internal/fs.js";
|
|
1
|
+
import { gatherGhostPackage, loadGhostSnapshot } from "../embed/index.js";
|
|
4
2
|
import { appendGhostEvent, resolveRunId } from "../observability-events.js";
|
|
5
3
|
import { resolveGhostPackage } from "../package.js";
|
|
6
|
-
import { loadGhostPackage } from "../scan/fingerprint-package.js";
|
|
7
4
|
import { failFromError } from "./errors.js";
|
|
8
5
|
export function registerGatherCommand(cli) {
|
|
9
6
|
cli
|
|
@@ -20,60 +17,25 @@ export function registerGatherCommand(cli) {
|
|
|
20
17
|
process.exit(2);
|
|
21
18
|
return;
|
|
22
19
|
}
|
|
23
|
-
const ask =
|
|
20
|
+
const ask = normalizeAskParts(askParts);
|
|
24
21
|
const paths = resolveGhostPackage(opts.package, process.cwd());
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const coverNode = coverId
|
|
28
|
-
? loaded.catalog.nodes.get(coverId)
|
|
29
|
-
: undefined;
|
|
30
|
-
const menu = buildCatalogMenu(loaded.catalog).filter((entry) => entry.id !== coverNode?.id);
|
|
31
|
-
const kinds = await readMenuKinds(paths.glossary);
|
|
22
|
+
const snapshot = await loadGhostSnapshot(paths);
|
|
23
|
+
const menu = gatherGhostPackage(snapshot, { ask });
|
|
32
24
|
const runId = resolveRunId(opts.run);
|
|
33
25
|
await appendGhostEvent(paths.packageDir, {
|
|
34
26
|
event: "gather",
|
|
35
27
|
...(runId ? { run: runId } : {}),
|
|
36
|
-
...(ask ? { ask } : {}),
|
|
37
|
-
menu: menu.map((entry) => entry.id),
|
|
28
|
+
...(menu.ask ? { ask: menu.ask } : {}),
|
|
29
|
+
menu: menu.nodes.map((entry) => entry.id),
|
|
38
30
|
});
|
|
39
31
|
// ghost does no selection. It emits the complete catalog; the agent
|
|
40
32
|
// reads the ask against it and pulls the nodes whose described
|
|
41
33
|
// conditions apply.
|
|
42
34
|
if (opts.format === "json") {
|
|
43
|
-
process.stdout.write(`${JSON.stringify(
|
|
44
|
-
kind: "menu",
|
|
45
|
-
...(ask ? { ask } : {}),
|
|
46
|
-
source: {
|
|
47
|
-
artifact: "ghost package",
|
|
48
|
-
list: "Available guidance",
|
|
49
|
-
},
|
|
50
|
-
contract: gatherContract(ask),
|
|
51
|
-
...(coverNode
|
|
52
|
-
? {
|
|
53
|
-
cover: {
|
|
54
|
-
id: coverNode.id,
|
|
55
|
-
body: coverNode.body,
|
|
56
|
-
inContext: true,
|
|
57
|
-
selectable: false,
|
|
58
|
-
},
|
|
59
|
-
}
|
|
60
|
-
: {}),
|
|
61
|
-
next: { command: "ghost pull <id> [<id>…]" },
|
|
62
|
-
silence: {
|
|
63
|
-
ifNoneApply: "Name the package's silence, follow the cover silence posture when present, and do not invent ghost-backed guidance.",
|
|
64
|
-
},
|
|
65
|
-
coverage: menuCoverage(menu),
|
|
66
|
-
...(kinds.length > 0 ? { kinds } : {}),
|
|
67
|
-
nodes: menu,
|
|
68
|
-
}, null, 2)}\n`);
|
|
35
|
+
process.stdout.write(`${JSON.stringify(formatGatherJson(menu), null, 2)}\n`);
|
|
69
36
|
}
|
|
70
37
|
else {
|
|
71
|
-
process.stdout.write(formatMenuMarkdown(menu
|
|
72
|
-
ask,
|
|
73
|
-
cover: coverNode
|
|
74
|
-
? { id: coverNode.id, body: coverNode.body }
|
|
75
|
-
: undefined,
|
|
76
|
-
}));
|
|
38
|
+
process.stdout.write(formatMenuMarkdown(menu));
|
|
77
39
|
}
|
|
78
40
|
process.exit(0);
|
|
79
41
|
}
|
|
@@ -82,75 +44,35 @@ export function registerGatherCommand(cli) {
|
|
|
82
44
|
}
|
|
83
45
|
});
|
|
84
46
|
}
|
|
85
|
-
function
|
|
47
|
+
function normalizeAskParts(askParts) {
|
|
86
48
|
const ask = (askParts ?? []).join(" ").trim();
|
|
87
49
|
return ask.length > 0 ? ask : undefined;
|
|
88
50
|
}
|
|
89
|
-
|
|
90
|
-
* Read the glossary's declared kinds and their prose purposes for the menu
|
|
91
|
-
* header. Missing or malformed glossaries degrade to no legend — the menu
|
|
92
|
-
* itself never depends on the glossary being present.
|
|
93
|
-
*/
|
|
94
|
-
async function readMenuKinds(glossaryPath) {
|
|
95
|
-
let raw;
|
|
96
|
-
try {
|
|
97
|
-
raw = await readFile(glossaryPath, "utf-8");
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
if (isMissingPathError(err))
|
|
101
|
-
return [];
|
|
102
|
-
throw err;
|
|
103
|
-
}
|
|
104
|
-
const result = parseGlossary(raw);
|
|
105
|
-
if (result.glossary === null)
|
|
106
|
-
return [];
|
|
107
|
-
return result.glossary.kinds
|
|
108
|
-
.filter((kind) => kind.purpose.length > 0)
|
|
109
|
-
.map((kind) => ({
|
|
110
|
-
name: kind.name,
|
|
111
|
-
// Legend entries are one line each: keep the section's first paragraph
|
|
112
|
-
// and collapse internal wrapping.
|
|
113
|
-
purpose: (kind.purpose.split(/\n\s*\n/, 1)[0] ?? "")
|
|
114
|
-
.replace(/\s+/g, " ")
|
|
115
|
-
.trim(),
|
|
116
|
-
}));
|
|
117
|
-
}
|
|
118
|
-
function gatherContract(ask) {
|
|
51
|
+
function formatGatherJson(menu) {
|
|
119
52
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
nodes: menu.length,
|
|
141
|
-
concrete: menu.filter((entry) => entry.concrete).length,
|
|
142
|
-
payloads: {
|
|
143
|
-
materials: menu.filter((entry) => entry.materials !== undefined).length,
|
|
144
|
-
fencedExamples: menu.filter((entry) => entry.hasFencedExample).length,
|
|
145
|
-
skeletons: menu.filter((entry) => entry.hasSkeleton).length,
|
|
146
|
-
},
|
|
147
|
-
// A node without a description is a bare id the agent cannot select
|
|
148
|
-
// against; surface the count where selection happens.
|
|
149
|
-
undescribed: menu.filter((entry) => !entry.description || entry.description.trim().length === 0).length,
|
|
53
|
+
kind: menu.kind,
|
|
54
|
+
...(menu.ask ? { ask: menu.ask } : {}),
|
|
55
|
+
source: menu.source,
|
|
56
|
+
contract: menu.contract,
|
|
57
|
+
...(menu.cover.state === "resolved"
|
|
58
|
+
? {
|
|
59
|
+
cover: {
|
|
60
|
+
id: menu.cover.id,
|
|
61
|
+
body: menu.cover.node.body,
|
|
62
|
+
inContext: true,
|
|
63
|
+
selectable: false,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
: {}),
|
|
67
|
+
next: { command: "ghost pull <id> [<id>…]" },
|
|
68
|
+
silence: menu.silence,
|
|
69
|
+
coverage: menu.coverage,
|
|
70
|
+
...(menu.kinds !== undefined ? { kinds: menu.kinds } : {}),
|
|
71
|
+
nodes: menu.nodes,
|
|
150
72
|
};
|
|
151
73
|
}
|
|
152
74
|
function menuCoverageLine(menu) {
|
|
153
|
-
const coverage =
|
|
75
|
+
const coverage = menu.coverage;
|
|
154
76
|
const payloadParts = [
|
|
155
77
|
`${coverage.payloads.materials} with materials`,
|
|
156
78
|
`${coverage.payloads.fencedExamples} with substantial fenced examples`,
|
|
@@ -165,28 +87,28 @@ function menuCoverageLine(menu) {
|
|
|
165
87
|
}
|
|
166
88
|
return parts.join(" · ");
|
|
167
89
|
}
|
|
168
|
-
function formatMenuMarkdown(menu
|
|
90
|
+
function formatMenuMarkdown(menu) {
|
|
169
91
|
const lines = ["# ghost package", ""];
|
|
170
|
-
if (
|
|
171
|
-
lines.push(`Ask: ${
|
|
172
|
-
if (
|
|
173
|
-
lines.push(`## Cover in context: \`${
|
|
92
|
+
if (menu.ask)
|
|
93
|
+
lines.push(`Ask: ${menu.ask}`, "");
|
|
94
|
+
if (menu.cover.state === "resolved") {
|
|
95
|
+
lines.push(`## Cover in context: \`${menu.cover.id}\``, "", menu.cover.node.body, "", "Cover status: already in context; outside selection; do not pull again.", "", "---", "");
|
|
174
96
|
}
|
|
175
97
|
lines.push("## Available guidance", "", menuCoverageLine(menu), "");
|
|
176
|
-
if (
|
|
98
|
+
if (menu.ask) {
|
|
177
99
|
lines.push("Complete, unfiltered, unranked list from the ghost package. ghost has not selected nodes for this ask.", "Pull every node whose description indicates its stated situation applies and whose guidance, material, structure, or refusal governs the work. Skip inapplicable nodes. Topic overlap alone is not applicability. Do not add nodes for completeness or omit applicable nodes to meet a count.", "Next: `ghost pull <id> [<id>…]`.", "If nothing applies, name the package's silence, follow the cover silence posture, and do not invent ghost-backed guidance.", "");
|
|
178
100
|
}
|
|
179
101
|
else {
|
|
180
102
|
lines.push("Complete, unfiltered, unranked list from the ghost package. Bare gather is catalog inspection; ghost has not grounded a task or selected nodes.", "When grounding an ask, pull every applicable node with `ghost pull <id> [<id>…]`. Skip inapplicable nodes and do not invent ghost-backed guidance when the ghost package is silent.", "");
|
|
181
103
|
}
|
|
182
|
-
if (kinds.length > 0) {
|
|
104
|
+
if (menu.kinds !== undefined && menu.kinds.length > 0) {
|
|
183
105
|
lines.push("Kinds:", "");
|
|
184
|
-
for (const kind of kinds) {
|
|
106
|
+
for (const kind of menu.kinds) {
|
|
185
107
|
lines.push(`- **${kind.name}** — ${kind.purpose}`);
|
|
186
108
|
}
|
|
187
109
|
lines.push("");
|
|
188
110
|
}
|
|
189
|
-
for (const entry of menu) {
|
|
111
|
+
for (const entry of menu.nodes) {
|
|
190
112
|
const kind = entry.kind ? ` _(${entry.kind})_` : "";
|
|
191
113
|
lines.push(`- \`${entry.id}\`${kind}`);
|
|
192
114
|
if (entry.description)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gather-command.js","sourceRoot":"","sources":["../../src/commands/gather-command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gather-command.js","sourceRoot":"","sources":["../../src/commands/gather-command.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,UAAU,qBAAqB,CAAC,GAAQ;IAC5C,GAAG;SACA,OAAO,CACN,iBAAiB,EACjB,mFAAmF,CACpF;SACA,MAAM,CACL,iBAAiB,EACjB,sDAAsD,CACvD;SACA,MAAM,CAAC,gBAAgB,EAAE,iCAAiC,EAAE;QAC3D,OAAO,EAAE,UAAU;KACpB,CAAC;SACD,MAAM,CACL,YAAY,EACZ,iEAAiE,CAClE;SACA,MAAM,CAAC,KAAK,EAAE,QAA8B,EAAE,IAAI,EAAE,EAAE;QACrD,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBACzD,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,OAAO;YACT,CAAC;YAED,MAAM,GAAG,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACnD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE;gBACvC,KAAK,EAAE,QAAQ;gBACf,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;aAC1C,CAAC,CAAC;YAEH,oEAAoE;YACpE,+DAA+D;YAC/D,oBAAoB;YACpB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACvD,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,aAAa,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,iBAAiB,CAAC,QAA8B;IACvD,MAAM,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAuB;IAC/C,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,UAAU;YACjC,CAAC,CAAC;gBACE,KAAK,EAAE;oBACL,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE;oBACjB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI;oBAC1B,SAAS,EAAE,IAAI;oBACf,UAAU,EAAE,KAAK;iBAClB;aACF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,EAAE,EAAE,OAAO,EAAE,yBAAyB,EAAE;QAC5C,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAuB;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,MAAM,YAAY,GAAG;QACnB,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,iBAAiB;QAC/C,GAAG,QAAQ,CAAC,QAAQ,CAAC,cAAc,mCAAmC;QACtE,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,iBAAiB;KAChD,CAAC;IACF,MAAM,KAAK,GAAG;QACZ,GAAG,QAAQ,CAAC,KAAK,QAAQ;QACzB,GAAG,QAAQ,CAAC,QAAQ,oBAAoB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KACnE,CAAC;IACF,IAAI,QAAQ,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,WAAW,oBAAoB,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAuB;IACjD,MAAM,KAAK,GAAa,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,IAAI,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CACR,0BAA0B,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAC3C,EAAE,EACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EACpB,EAAE,EACF,yEAAyE,EACzE,EAAE,EACF,KAAK,EACL,EAAE,CACH,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACpE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CACR,wGAAwG,EACxG,+RAA+R,EAC/R,kCAAkC,EAClC,4HAA4H,EAC5H,EAAE,CACH,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,iJAAiJ,EACjJ,qLAAqL,EACrL,EAAE,CACH,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAC9D,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC,kBAAkB,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,MAAM,YAAY,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC/C,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,iBAAiB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAuB;IACjD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3D,IAAI,KAAK,CAAC,gBAAgB;QAAE,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IACrE,IAAI,KAAK,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pull-command.d.ts","sourceRoot":"","sources":["../../src/commands/pull-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"pull-command.d.ts","sourceRoot":"","sources":["../../src/commands/pull-command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAU/B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAwFlD"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { appendGhostEvent, resolveRunId
|
|
1
|
+
import { loadGhostSnapshot, pullGhostNodes } from "../embed/index.js";
|
|
2
|
+
import { appendGhostEvent, resolveRunId } from "../observability-events.js";
|
|
3
3
|
import { resolveGhostPackage } from "../package.js";
|
|
4
|
-
import { GHOST_EVENTS_FILENAME
|
|
5
|
-
import { loadGhostPackage } from "../scan/fingerprint-package.js";
|
|
4
|
+
import { GHOST_EVENTS_FILENAME } from "../scan/constants.js";
|
|
6
5
|
import { resolveGitRoot } from "../scan/package-paths.js";
|
|
7
6
|
import { failFromError } from "./errors.js";
|
|
8
7
|
export function registerPullCommand(cli) {
|
|
@@ -31,68 +30,43 @@ export function registerPullCommand(cli) {
|
|
|
31
30
|
return;
|
|
32
31
|
}
|
|
33
32
|
const paths = resolveGhostPackage(opts.package, process.cwd());
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
for (const miss of missed) {
|
|
33
|
+
const snapshot = await loadGhostSnapshot(paths);
|
|
34
|
+
const repoRoot = await resolveGitRoot(process.cwd());
|
|
35
|
+
const result = await pullGhostNodes(snapshot, {
|
|
36
|
+
ids,
|
|
37
|
+
repoRoot,
|
|
38
|
+
inlineMaterials: opts.materials !== false,
|
|
39
|
+
order: opts.order,
|
|
40
|
+
});
|
|
41
|
+
for (const miss of result.missed) {
|
|
43
42
|
const hint = miss.suggested.length > 0
|
|
44
43
|
? ` (did you mean ${miss.suggested.map((s) => `\`${s}\``).join(", ")}?)`
|
|
45
44
|
: "";
|
|
46
45
|
console.error(`Warning: unknown node \`${miss.requested}\`${hint}`);
|
|
47
46
|
}
|
|
48
|
-
if (missed.length > 0) {
|
|
47
|
+
if (result.missed.length > 0) {
|
|
49
48
|
console.error("Run `ghost gather` to list every node.");
|
|
50
49
|
}
|
|
51
|
-
const givenNodes = known.map((id) => catalog.nodes.get(id));
|
|
52
|
-
const nodes = opts.order === "given"
|
|
53
|
-
? givenNodes
|
|
54
|
-
: orderPulledNodes(givenNodes, loaded.manifest.cover);
|
|
55
|
-
const pulledNodes = await resolvePulledNodes(nodes, paths.packageDir, opts.materials !== false);
|
|
56
|
-
const counts = sumMaterialCounts(pulledNodes);
|
|
57
50
|
if (opts.events !== false) {
|
|
58
51
|
const runId = resolveRunId(opts.run);
|
|
59
52
|
await appendGhostEvent(paths.packageDir, {
|
|
60
53
|
event: "pull",
|
|
61
54
|
...(runId ? { run: runId } : {}),
|
|
62
|
-
ids:
|
|
63
|
-
inlinedMaterials:
|
|
64
|
-
omittedMaterials:
|
|
65
|
-
...(missed.length > 0 ? { missed } : {}),
|
|
55
|
+
ids: [...result.ids],
|
|
56
|
+
inlinedMaterials: result.materialCounts.inlined,
|
|
57
|
+
omittedMaterials: result.materialCounts.omitted,
|
|
58
|
+
...(result.missed.length > 0 ? { missed: [...result.missed] } : {}),
|
|
66
59
|
});
|
|
67
60
|
}
|
|
68
|
-
if (
|
|
61
|
+
if (result.ids.length === 0) {
|
|
69
62
|
process.exit(2);
|
|
70
63
|
return;
|
|
71
64
|
}
|
|
72
65
|
if (opts.format === "json") {
|
|
73
|
-
process.stdout.write(`${JSON.stringify(
|
|
74
|
-
kind: "pull",
|
|
75
|
-
...(missed.length > 0 ? { missed } : {}),
|
|
76
|
-
nodes: pulledNodes.map(({ node, materials }) => ({
|
|
77
|
-
id: node.id,
|
|
78
|
-
...(node.kind !== undefined ? { kind: node.kind } : {}),
|
|
79
|
-
...(node.description
|
|
80
|
-
? { description: node.description }
|
|
81
|
-
: {}),
|
|
82
|
-
...(node.materials !== undefined
|
|
83
|
-
? {
|
|
84
|
-
materials: opts.materials === false
|
|
85
|
-
? node.materials
|
|
86
|
-
: materials.materials.map(formatJsonMaterial),
|
|
87
|
-
}
|
|
88
|
-
: {}),
|
|
89
|
-
body: stripSkeletonSections(node.body),
|
|
90
|
-
})),
|
|
91
|
-
skeletons: pulledSkeletons(pulledNodes),
|
|
92
|
-
}, null, 2)}\n`);
|
|
66
|
+
process.stdout.write(`${JSON.stringify(formatPullJson(result, opts.materials !== false), null, 2)}\n`);
|
|
93
67
|
}
|
|
94
68
|
else {
|
|
95
|
-
process.stdout.write(formatPullMarkdown(
|
|
69
|
+
process.stdout.write(formatPullMarkdown(result));
|
|
96
70
|
}
|
|
97
71
|
process.exit(0);
|
|
98
72
|
}
|
|
@@ -101,114 +75,49 @@ export function registerPullCommand(cli) {
|
|
|
101
75
|
}
|
|
102
76
|
});
|
|
103
77
|
}
|
|
104
|
-
|
|
105
|
-
const repoRoot = await resolveGitRoot(process.cwd());
|
|
106
|
-
return Promise.all(nodes.map(async (node) => ({
|
|
107
|
-
node,
|
|
108
|
-
materials: inlineMaterials
|
|
109
|
-
? await transportMaterials(node.materials, {
|
|
110
|
-
repoRoot,
|
|
111
|
-
packageDir,
|
|
112
|
-
materialsDir: GHOST_MATERIALS_DIR,
|
|
113
|
-
})
|
|
114
|
-
: locatorOnlyMaterials(node.materials, repoRoot, packageDir),
|
|
115
|
-
})));
|
|
116
|
-
}
|
|
117
|
-
function orderPulledNodes(nodes, coverId) {
|
|
118
|
-
return nodes
|
|
119
|
-
.map((node, index) => ({
|
|
120
|
-
node,
|
|
121
|
-
index,
|
|
122
|
-
bucket: steeringBucket(node, coverId),
|
|
123
|
-
}))
|
|
124
|
-
.sort((a, b) => a.bucket - b.bucket || a.index - b.index)
|
|
125
|
-
.map((entry) => entry.node);
|
|
126
|
-
}
|
|
127
|
-
function steeringBucket(node, coverId) {
|
|
128
|
-
if (coverId !== undefined && node.id === coverId)
|
|
129
|
-
return 0;
|
|
130
|
-
if (node.concrete)
|
|
131
|
-
return 1;
|
|
132
|
-
return 2;
|
|
133
|
-
}
|
|
134
|
-
function locatorOnlyMaterials(locators, repoRoot, packageDir) {
|
|
78
|
+
function formatPullJson(result, inlineMaterials) {
|
|
135
79
|
return {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
80
|
+
kind: "pull",
|
|
81
|
+
...(result.missed.length > 0 ? { missed: result.missed } : {}),
|
|
82
|
+
nodes: result.nodes.map((node) => ({
|
|
83
|
+
id: node.id,
|
|
84
|
+
...(node.kind !== undefined ? { kind: node.kind } : {}),
|
|
85
|
+
...(node.description ? { description: node.description } : {}),
|
|
86
|
+
...(node.declaredMaterials !== undefined
|
|
87
|
+
? {
|
|
88
|
+
materials: inlineMaterials
|
|
89
|
+
? (node.materials ?? []).map(formatJsonMaterial)
|
|
90
|
+
: node.declaredMaterials,
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
93
|
+
body: node.body,
|
|
145
94
|
})),
|
|
146
|
-
|
|
147
|
-
omitted: 0,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function sumMaterialCounts(nodes) {
|
|
151
|
-
return nodes.reduce((sum, { materials }) => ({
|
|
152
|
-
inlined: sum.inlined + materials.inlined,
|
|
153
|
-
omitted: sum.omitted + materials.omitted,
|
|
154
|
-
}), { inlined: 0, omitted: 0 });
|
|
155
|
-
}
|
|
156
|
-
function pulledSkeletons(nodes) {
|
|
157
|
-
return nodes.flatMap(({ node }) => extractSkeletonFences(node.body).map((fence) => ({
|
|
158
|
-
nodeId: node.id,
|
|
159
|
-
...(fence.info ? { info: fence.info } : {}),
|
|
160
|
-
content: fence.content,
|
|
161
|
-
})));
|
|
162
|
-
}
|
|
163
|
-
function formatJsonMaterial(material) {
|
|
164
|
-
return {
|
|
165
|
-
locator: material.locator,
|
|
166
|
-
tier: material.tier,
|
|
167
|
-
...(material.inlined !== undefined ? { inlined: material.inlined } : {}),
|
|
168
|
-
...(material.omitted
|
|
169
|
-
? { omitted: true, reason: material.reason ?? "not inlined" }
|
|
170
|
-
: {}),
|
|
171
|
-
...(material.reason === "binary inspect-pointer"
|
|
172
|
-
? { inspect: material.path ?? material.locator }
|
|
173
|
-
: {}),
|
|
95
|
+
skeletons: result.skeletons,
|
|
174
96
|
};
|
|
175
97
|
}
|
|
176
|
-
function formatPullMarkdown(
|
|
98
|
+
function formatPullMarkdown(result) {
|
|
177
99
|
const sections = [];
|
|
178
|
-
for (const
|
|
100
|
+
for (const node of result.nodes) {
|
|
179
101
|
const kind = node.kind ? ` _(${node.kind})_` : "";
|
|
180
102
|
const lines = [`# \`${node.id}\`${kind}`];
|
|
181
103
|
if (node.description)
|
|
182
104
|
lines.push("", `> ${node.description}`);
|
|
183
|
-
lines.push("",
|
|
184
|
-
if (materials.materials.length > 0) {
|
|
105
|
+
lines.push("", node.body.trim());
|
|
106
|
+
if (node.materials !== undefined && node.materials.length > 0) {
|
|
185
107
|
lines.push("", "Materials:");
|
|
186
|
-
for (const material of
|
|
187
|
-
|
|
188
|
-
const info = material.path ?? material.locator;
|
|
189
|
-
lines.push("", fencedMarkdown(material.inlined.trimEnd(), info));
|
|
190
|
-
}
|
|
191
|
-
else if (material.reason === "binary inspect-pointer") {
|
|
192
|
-
lines.push(`- inspect: ${material.path ?? material.locator} — view this image before generating`);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
const reason = material.omitted
|
|
196
|
-
? ` — ${material.reason ?? "not inlined"}`
|
|
197
|
-
: "";
|
|
198
|
-
lines.push(`- ${material.locator}${reason}`);
|
|
199
|
-
}
|
|
108
|
+
for (const material of node.materials) {
|
|
109
|
+
appendMaterialMarkdown(lines, material);
|
|
200
110
|
}
|
|
201
111
|
}
|
|
202
112
|
sections.push(lines.join("\n"));
|
|
203
113
|
}
|
|
204
|
-
|
|
205
|
-
if (skeletons.length > 0) {
|
|
114
|
+
if (result.skeletons.length > 0) {
|
|
206
115
|
const lines = [
|
|
207
116
|
"# Skeletons — begin the artifact from this structure",
|
|
208
117
|
"",
|
|
209
118
|
"Begin the artifact from the matching structure below verbatim, then fill it.",
|
|
210
119
|
];
|
|
211
|
-
for (const skeleton of skeletons) {
|
|
120
|
+
for (const skeleton of result.skeletons) {
|
|
212
121
|
lines.push("", `## From \`${skeleton.nodeId}\``, "");
|
|
213
122
|
lines.push(fencedMarkdown(skeleton.content.trimEnd(), skeleton.info));
|
|
214
123
|
}
|
|
@@ -216,6 +125,34 @@ function formatPullMarkdown(nodes) {
|
|
|
216
125
|
}
|
|
217
126
|
return `${sections.join("\n\n---\n\n")}\n`;
|
|
218
127
|
}
|
|
128
|
+
function appendMaterialMarkdown(lines, material) {
|
|
129
|
+
if (material.inlined !== undefined) {
|
|
130
|
+
const info = material.path ?? material.locator;
|
|
131
|
+
lines.push("", fencedMarkdown(material.inlined.trimEnd(), info));
|
|
132
|
+
}
|
|
133
|
+
else if (material.reason === "binary inspect-pointer") {
|
|
134
|
+
lines.push(`- inspect: ${material.path ?? material.locator} — view this image before generating`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const reason = material.omitted
|
|
138
|
+
? ` — ${material.reason ?? "not inlined"}`
|
|
139
|
+
: "";
|
|
140
|
+
lines.push(`- ${material.locator}${reason}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function formatJsonMaterial(material) {
|
|
144
|
+
return {
|
|
145
|
+
locator: material.locator,
|
|
146
|
+
tier: material.tier,
|
|
147
|
+
...(material.inlined !== undefined ? { inlined: material.inlined } : {}),
|
|
148
|
+
...(material.omitted
|
|
149
|
+
? { omitted: true, reason: material.reason ?? "not inlined" }
|
|
150
|
+
: {}),
|
|
151
|
+
...(material.reason === "binary inspect-pointer"
|
|
152
|
+
? { inspect: material.path ?? material.locator }
|
|
153
|
+
: {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
219
156
|
function fencedMarkdown(content, info) {
|
|
220
157
|
const fence = "`".repeat(Math.max(3, longestBacktickRun(content) + 1));
|
|
221
158
|
return `${fence}${info ?? ""}\n${content}\n${fence}`;
|