docspack 1.1.0 → 1.3.0
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 +25 -1
- package/dist/build.d.ts +5 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +68 -5
- package/dist/build.js.map +1 -1
- package/dist/cli-spec.d.ts +3 -0
- package/dist/cli-spec.d.ts.map +1 -0
- package/dist/cli-spec.js +667 -0
- package/dist/cli-spec.js.map +1 -0
- package/dist/cli.d.ts +146 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +80 -14
- package/dist/cli.js.map +1 -1
- package/dist/cmdspec.json +1219 -0
- package/dist/commands.d.ts +78 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +231 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/db.d.ts +6 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +7 -1
- package/dist/db.js.map +1 -1
- package/dist/discovery.d.ts +22 -3
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +91 -12
- package/dist/discovery.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +66 -1
- package/dist/doctor.js.map +1 -1
- package/dist/help.d.ts +10 -6
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +118 -371
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/init/plan.js +4 -3
- package/dist/init/plan.js.map +1 -1
- package/dist/init/templates.d.ts.map +1 -1
- package/dist/init/templates.js +4 -2
- package/dist/init/templates.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +12 -6
- package/dist/preview.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +66 -22
- package/dist/search.js.map +1 -1
- package/dist/spec.d.ts +21 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +24 -2
- package/dist/spec.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +99 -1
- package/dist/sync.js.map +1 -1
- package/package.json +8 -5
- package/src/build.ts +86 -7
- package/src/cli-spec.ts +688 -0
- package/src/cli.ts +90 -17
- package/src/commands.ts +298 -0
- package/src/config.ts +4 -1
- package/src/db.ts +9 -2
- package/src/discovery.ts +113 -12
- package/src/doctor.ts +67 -0
- package/src/help.ts +138 -380
- package/src/index.ts +1 -0
- package/src/init/plan.ts +4 -3
- package/src/init/templates.ts +4 -2
- package/src/preview.ts +14 -13
- package/src/search.ts +79 -22
- package/src/spec.ts +28 -2
- package/src/sync.ts +120 -1
package/src/help.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CmdspecDocument,
|
|
3
|
+
commandPath,
|
|
4
|
+
commandSummary,
|
|
5
|
+
type ResolvedCommand,
|
|
6
|
+
type ResolvedOption,
|
|
7
|
+
resolveDocument,
|
|
8
|
+
synopsis,
|
|
9
|
+
} from "@docspack/cmdspec";
|
|
10
|
+
import { CLI } from "./cli-spec.js";
|
|
1
11
|
import { AGENTS_SNIPPET, FEEDBACK_SNIPPET } from "./snippet.js";
|
|
2
12
|
|
|
3
13
|
/**
|
|
4
|
-
* The help text,
|
|
14
|
+
* The help text, rendered from the CLI's cmdspec document (`cli-spec.ts`).
|
|
5
15
|
*
|
|
6
|
-
* `docspack <command> --help`
|
|
7
|
-
* at all
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
16
|
+
* `docspack <command> --help` used to print the global help, so no flag was discoverable from the
|
|
17
|
+
* CLI at all; then it printed a hand-kept list that drifted from the parser — `build` accepted
|
|
18
|
+
* `--from-json` and `--local` and listed neither. Now the parser, this help and `docspack
|
|
19
|
+
* --cmdspec` read the same document, so a flag cannot be accepted without being listed, or listed
|
|
20
|
+
* without being accepted. The global page's prose is still written here; its lists are not.
|
|
11
21
|
*/
|
|
12
22
|
export interface CommandHelp {
|
|
13
23
|
readonly name: string;
|
|
@@ -21,393 +31,47 @@ export interface CommandHelp {
|
|
|
21
31
|
readonly examples: readonly string[];
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
usage: "docspack sync [options]",
|
|
40
|
-
options: [
|
|
41
|
-
["--force", "re-index packages already in the store"],
|
|
42
|
-
["--no-artifacts", "skip the declarations derived from installed libraries"],
|
|
43
|
-
],
|
|
44
|
-
detail: [
|
|
45
|
-
"Reads node_modules and indexes every @vendor/docspack and @docspack-community/<name>",
|
|
46
|
-
"package this project declares. Makes no network requests.",
|
|
47
|
-
"",
|
|
48
|
-
"It also reads each installed library's own type declarations and indexes one entry per",
|
|
49
|
-
"exported name. Half of a well-documented library's exports are mentioned in no",
|
|
50
|
-
"documentation anyone published, and those declarations are the only local answer for",
|
|
51
|
-
"them. They are looked up by name, never ranked against prose, so they cannot crowd out",
|
|
52
|
-
"the documentation that does exist.",
|
|
53
|
-
],
|
|
54
|
-
examples: ["docspack sync", "docspack sync --force", "docspack sync --no-artifacts"],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
name: "ask",
|
|
58
|
-
summary: "Answer from the local index — the command to give an agent",
|
|
59
|
-
group: "core",
|
|
60
|
-
usage: 'docspack ask "<question>"',
|
|
61
|
-
options: QUERY_OPTIONS,
|
|
62
|
-
detail: [
|
|
63
|
-
"Answers from the installed versions, in the Markdown an agent should read. Exits 0 when",
|
|
64
|
-
"chunks were returned, 3 when a docs package is installed but not indexed, and 4 when",
|
|
65
|
-
"everything installed is indexed and nothing matched.",
|
|
66
|
-
"",
|
|
67
|
-
"When the question names something an installed library exports and no documentation",
|
|
68
|
-
"mentions, the answer leads with that name's declaration from the installed build and says",
|
|
69
|
-
"the documentation does not cover it. Ranking alone cannot tell that apart from a match.",
|
|
70
|
-
],
|
|
71
|
-
examples: [
|
|
72
|
-
'docspack ask "how do I verify a webhook signature"',
|
|
73
|
-
'docspack ask "webhook signature" --package stripe --limit 5',
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: "index",
|
|
78
|
-
summary: "Index this project's own sources, so an agent can ask them instead of reading them",
|
|
79
|
-
group: "core",
|
|
80
|
-
usage: "docspack index [--from <dir>] [--from-json <file|->]",
|
|
81
|
-
options: [
|
|
82
|
-
["--from <dir>", "directory of Markdown to index"],
|
|
83
|
-
["--from-json <f>", "JSON records to index, or `-` for standard input"],
|
|
84
|
-
["--name <s>", "name for the corpus (default: derived from the source)"],
|
|
85
|
-
["--force", "re-index even when no source has changed"],
|
|
86
|
-
],
|
|
87
|
-
detail: [
|
|
88
|
-
"For a corpus this project already has rather than one somebody published: notes, an",
|
|
89
|
-
"export, rows out of a query. The payload is built in a temporary directory and thrown",
|
|
90
|
-
"away; what is kept is the index, in `.docspack/local.db`, which is a plaintext copy of",
|
|
91
|
-
"whatever was indexed and is gitignored on the tool's behalf.",
|
|
92
|
-
"",
|
|
93
|
-
"Records arrive as JSON so no database driver is needed: `sqlite3 -json … | docspack index",
|
|
94
|
-
"--from-json -`. A record with an `id` becomes one chunk under that id, because a row's",
|
|
95
|
-
"identity is its key.",
|
|
96
|
-
"",
|
|
97
|
-
"What was indexed is recorded with each source's size, mtime and hash, so a re-run does",
|
|
98
|
-
"nothing when nothing has changed and `recall` can say when an answer may be superseded.",
|
|
99
|
-
],
|
|
100
|
-
examples: [
|
|
101
|
-
"docspack index --from ./notes",
|
|
102
|
-
"sqlite3 -json shop.db 'select id, title, body as text from posts' | docspack index --from-json -",
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: "recall",
|
|
107
|
-
summary: "Answer from this project's indexed corpus, not from its dependencies",
|
|
108
|
-
group: "core",
|
|
109
|
-
usage: 'docspack recall "<question>"',
|
|
110
|
-
options: [
|
|
111
|
-
["--limit <n>", `maximum chunks to return (default ${LIMIT})`],
|
|
112
|
-
["--max-tokens <n>", `token ceiling for the result set (default ${MAX_TOKENS})`],
|
|
113
|
-
],
|
|
114
|
-
detail: [
|
|
115
|
-
"Separate from `ask` on purpose. `ask` answers from the versions this project installed,",
|
|
116
|
-
"and a working corpus is never one of them — so a corpus cannot reach an answer about a",
|
|
117
|
-
"dependency, and a dependency cannot reach an answer about your notes.",
|
|
118
|
-
"",
|
|
119
|
-
"An answer leads with a warning when a source has changed since it was indexed. Exits 1",
|
|
120
|
-
"when nothing matched.",
|
|
121
|
-
],
|
|
122
|
-
examples: ['docspack recall "what did we decide about retries"'],
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "search",
|
|
126
|
-
summary: "Same index, formatted for a human reading the terminal",
|
|
127
|
-
group: "core",
|
|
128
|
-
usage: "docspack search <query>",
|
|
129
|
-
options: QUERY_OPTIONS,
|
|
130
|
-
detail: ["The same index and ranking as `ask`, printed as a list of hits with a preview."],
|
|
131
|
-
examples: ["docspack search webhook signature"],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: "list",
|
|
135
|
-
summary: "Show this project's docs packages and their index state",
|
|
136
|
-
group: "core",
|
|
137
|
-
usage: "docspack list [options]",
|
|
138
|
-
options: [["--coverage", "how much of each documented library's exports the prose mentions"]],
|
|
139
|
-
detail: [
|
|
140
|
-
"Coverage is mechanical: the exported names a library declares, against the names its",
|
|
141
|
-
"documentation mentions anywhere. It is reported, never gated on — a page listing every",
|
|
142
|
-
"export and explaining none would score full marks.",
|
|
143
|
-
],
|
|
144
|
-
examples: ["docspack list", "docspack list --coverage", "docspack list --json"],
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: "agent",
|
|
148
|
-
summary: "Wire docspack into the agent tooling this project already uses",
|
|
149
|
-
group: "core",
|
|
150
|
-
usage: "docspack agent <install|check> [options]",
|
|
151
|
-
options: [
|
|
152
|
-
["--feedback", "also include recording documentation problems"],
|
|
153
|
-
["--hooks", "add a SessionStart hook that keeps the index in step"],
|
|
154
|
-
["--mcp", "add the MCP server to .mcp.json"],
|
|
155
|
-
["--dry-run", "print what would be written and write nothing"],
|
|
156
|
-
],
|
|
157
|
-
detail: [
|
|
158
|
-
"`install` writes a marked block into AGENTS.md or CLAUDE.md — whichever the project",
|
|
159
|
-
"already has — and a skill into .claude/skills/docspack/ when the project uses Claude",
|
|
160
|
-
"Code. Everything outside the markers is left alone, and re-running rewrites the block",
|
|
161
|
-
"rather than appending a second copy.",
|
|
162
|
-
"",
|
|
163
|
-
"`check` writes nothing and exits non-zero when the wiring is missing or out of date, so",
|
|
164
|
-
"CI notices a pasted instruction that has drifted from what the tool now does.",
|
|
165
|
-
],
|
|
166
|
-
examples: [
|
|
167
|
-
"docspack agent install",
|
|
168
|
-
"docspack agent install --feedback --hooks",
|
|
169
|
-
"docspack agent check",
|
|
170
|
-
],
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "changed",
|
|
174
|
-
summary: "What a library's exports gained and lost between two versions",
|
|
175
|
-
group: "core",
|
|
176
|
-
usage: "docspack changed <library>[@version]",
|
|
177
|
-
options: [],
|
|
178
|
-
detail: [
|
|
179
|
-
"Compares two versions already in the global store, which is shared by every project on",
|
|
180
|
-
"this machine, so nothing is fetched. Without a version it compares what is installed here",
|
|
181
|
-
"against the most recently indexed other version.",
|
|
182
|
-
"",
|
|
183
|
-
"Upgrades are overwhelmingly additive: the useful answer is what exists now that an older",
|
|
184
|
-
"release did not have, and which of those names no documentation here mentions — the ones",
|
|
185
|
-
"a model can know from neither its training data nor the vendor's pages.",
|
|
186
|
-
],
|
|
187
|
-
examples: ["docspack changed hono", "docspack changed hono@4.0.0"],
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: "verify",
|
|
191
|
-
summary: "Check the docs still describe the code you installed",
|
|
192
|
-
group: "core",
|
|
193
|
-
usage: "docspack verify [options]",
|
|
194
|
-
options: [["--package-dir <d>", "verify one package directory instead of what is installed"]],
|
|
195
|
-
detail: [
|
|
196
|
-
"Compares the identifiers the chunks name against what the documented libraries declare,",
|
|
197
|
-
"read from their .d.ts files. Nothing you depend on is imported or executed. The libraries",
|
|
198
|
-
'come from the manifest\'s "documents", or from the docspack key when the manifest has none.',
|
|
199
|
-
"Exits 1 when anything was reported.",
|
|
200
|
-
],
|
|
201
|
-
examples: ["docspack verify", "docspack verify --json"],
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
name: "feedback",
|
|
205
|
-
summary: "Record documentation problems: add, list, submit, remove",
|
|
206
|
-
group: "core",
|
|
207
|
-
usage: "docspack feedback <add|list|submit|remove> [options]",
|
|
208
|
-
options: [
|
|
209
|
-
["--chunk <id>", "add: the chunk the problem is in"],
|
|
210
|
-
["--kind <k>", "add: drift, incorrect or missing"],
|
|
211
|
-
["--evidence <text>", "add: the claim, in one line"],
|
|
212
|
-
["--expected <text>", "add: what the documentation led you to expect"],
|
|
213
|
-
["--actual <text>", "add: what happened instead"],
|
|
214
|
-
["--repro <code>", "add: code that demonstrates it"],
|
|
215
|
-
["-p, --package <s>", "submit: only findings from packages matching this text"],
|
|
216
|
-
["--all", "remove: every recorded finding"],
|
|
217
|
-
],
|
|
218
|
-
detail: [
|
|
219
|
-
"Writes to .docspack/feedback.jsonl in this project. Nothing is transmitted: docspack",
|
|
220
|
-
"contains no code that can send a report anywhere.",
|
|
221
|
-
"",
|
|
222
|
-
"`drift` must name the identifier that drifted. `incorrect` and `missing` must carry",
|
|
223
|
-
"--expected, --actual and --repro, so a claim that cannot show its work cannot be",
|
|
224
|
-
"recorded. `submit` prints a prefilled GitHub issue URL for a human to open and file.",
|
|
225
|
-
],
|
|
226
|
-
examples: [
|
|
227
|
-
'docspack feedback add --chunk @acme/docspack@1.4.0/api-auth --kind drift --evidence "client.setKey is not exported; setApiKey is"',
|
|
228
|
-
"docspack feedback list",
|
|
229
|
-
"docspack feedback submit",
|
|
230
|
-
"docspack feedback remove <fingerprint>",
|
|
231
|
-
],
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: "mcp",
|
|
235
|
-
summary: "Serve the index over MCP instead, as a long-lived process",
|
|
236
|
-
group: "core",
|
|
237
|
-
usage: "docspack mcp",
|
|
238
|
-
options: [],
|
|
239
|
-
detail: [
|
|
240
|
-
"Serves the same index over the Model Context Protocol, for clients that prefer a tool",
|
|
241
|
-
"definition to a shell command. `query_local_docs` returns exactly what `ask` prints.",
|
|
242
|
-
"stdout is the protocol channel, so nothing else is written to it.",
|
|
243
|
-
],
|
|
244
|
-
examples: ["docspack mcp"],
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: "sources",
|
|
248
|
-
summary: "List curated sources that `docspack build` can fetch",
|
|
249
|
-
group: "core",
|
|
250
|
-
usage: "docspack sources",
|
|
251
|
-
options: [],
|
|
252
|
-
examples: ["docspack sources", "docspack build hono --name @docspack-community/hono"],
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
name: "init",
|
|
256
|
-
summary: "Scaffold a documentation package, then build and check it",
|
|
257
|
-
group: "authoring",
|
|
258
|
-
usage: "docspack init [name] [options]",
|
|
259
|
-
options: [
|
|
260
|
-
["-y, --yes", "skip the prompts and use flags plus detected defaults"],
|
|
261
|
-
["--dry-run", "print the file tree and write nothing"],
|
|
262
|
-
["--mirror <id|url>", "bootstrap from a published llms.txt"],
|
|
263
|
-
["--community", "scaffold under @docspack-community"],
|
|
264
|
-
["--template <t>", "full (default) or minimal"],
|
|
265
|
-
["--mode <m>", "standalone or in-repo"],
|
|
266
|
-
["--no-workflow", "skip the release workflow"],
|
|
267
|
-
["--no-build", "scaffold only"],
|
|
268
|
-
["--name <name>", "package name, e.g. @acme/docspack"],
|
|
269
|
-
["--pkg-version <v>", "package version"],
|
|
270
|
-
["--from <dir>", "directory of Markdown to package"],
|
|
271
|
-
["--openapi <file>", "OpenAPI JSON to package"],
|
|
272
|
-
["--out <dir>", "where to scaffold (default: ./docspack)"],
|
|
273
|
-
["--force", "overwrite files that already exist"],
|
|
274
|
-
],
|
|
275
|
-
detail: [
|
|
276
|
-
"Reads the surrounding project first — name, version, a docs directory, an OpenAPI",
|
|
277
|
-
"document, the git remote, the license — and proposes a package built from what it found.",
|
|
278
|
-
],
|
|
279
|
-
examples: [
|
|
280
|
-
"docspack init",
|
|
281
|
-
"docspack init --name @acme/docspack --from ./docs --yes",
|
|
282
|
-
"docspack init --mirror hono --community --yes",
|
|
283
|
-
],
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
name: "build",
|
|
287
|
-
summary: "Generate the .llms/ payload for publishing",
|
|
288
|
-
group: "authoring",
|
|
289
|
-
usage: "docspack build [source] [options]",
|
|
290
|
-
options: [
|
|
291
|
-
["--from <dir>", "directory of Markdown to package"],
|
|
292
|
-
["--openapi <file>", "OpenAPI 3 document (JSON or YAML), one chunk per operation"],
|
|
293
|
-
["--name <name>", "package name, e.g. @acme/docspack"],
|
|
294
|
-
["--pkg-version <v>", "package version"],
|
|
295
|
-
["--out <dir>", "output directory (default: the current directory)"],
|
|
296
|
-
["--pages <n>", "maximum documents to fetch from a remote source"],
|
|
297
|
-
["--max-chunk-tokens <n>", "split a section above this size (default 800)"],
|
|
298
|
-
["--min-chunk-tokens <n>", "pack adjacent sections up to this size before splitting"],
|
|
299
|
-
["--documents <p>", "library this package documents, repeatable: acme@1.4.0"],
|
|
300
|
-
],
|
|
301
|
-
detail: [
|
|
302
|
-
"Splits each document at ## headings, then ###, then paragraphs, until every chunk fits",
|
|
303
|
-
"the budget. --min-chunk-tokens packs the other way first, merging adjacent sections up to",
|
|
304
|
-
"the budget: use it for generated reference, where a heading is a field name and one chunk",
|
|
305
|
-
"per heading is hundreds of chunks too small to answer anything.",
|
|
306
|
-
"",
|
|
307
|
-
"--openapi writes one chunk per operation: the base URL, the credential, the inputs with",
|
|
308
|
-
"their types, the response, the failures and a runnable curl call, in LAPIS notation. Each",
|
|
309
|
-
"chunk answers to both `POST /v1/charges` and the operationId, so `docspack ask` can be",
|
|
310
|
-
"given either. Combine it with --from to publish prose and an API in one package.",
|
|
311
|
-
"",
|
|
312
|
-
"With no flags, settings are read from the docspack key of the package.json in --out.",
|
|
313
|
-
],
|
|
314
|
-
examples: [
|
|
315
|
-
"docspack build",
|
|
316
|
-
"docspack build --from ./docs --name @acme/docspack --pkg-version 1.4.0",
|
|
317
|
-
"docspack build --from ./reference --min-chunk-tokens 400 --max-chunk-tokens 900",
|
|
318
|
-
"docspack build --openapi ./openapi.json",
|
|
319
|
-
"docspack build --from ./docs --openapi ./openapi.json",
|
|
320
|
-
],
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: "doctor",
|
|
324
|
-
summary: "Check a package the way the indexer and a reviewer would",
|
|
325
|
-
group: "authoring",
|
|
326
|
-
usage: "docspack doctor [options]",
|
|
327
|
-
options: [
|
|
328
|
-
["--strict", "treat structural warnings as failures"],
|
|
329
|
-
["--pedantic", "--strict, and fail on prose style as well"],
|
|
330
|
-
["--package-dir <d>", "the package to inspect (default: .)"],
|
|
331
|
-
],
|
|
332
|
-
detail: [
|
|
333
|
-
"Errors are packages that will not index. Warnings are packages that will index and",
|
|
334
|
-
"retrieve badly. Prose style is a note unless --pedantic, because writing by hand is not a",
|
|
335
|
-
"reason to block a first publish. Exits 1 when the package is not ready to publish.",
|
|
336
|
-
],
|
|
337
|
-
examples: ["docspack doctor", "docspack doctor --strict", "docspack doctor --json"],
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: "preview",
|
|
341
|
-
summary: "Answer a query from the local package, as an agent would",
|
|
342
|
-
group: "authoring",
|
|
343
|
-
usage: "docspack preview <query> [options]",
|
|
344
|
-
options: [
|
|
345
|
-
["--package-dir <d>", "the package to read (default: .)"],
|
|
346
|
-
["--limit <n>", `maximum chunks to return (default ${LIMIT})`],
|
|
347
|
-
["--max-tokens <n>", `token ceiling for the result set (default ${MAX_TOKENS})`],
|
|
348
|
-
],
|
|
349
|
-
detail: [
|
|
350
|
-
"Indexes the package in memory and answers through the same ranking and token budget an",
|
|
351
|
-
"agent gets. Nothing is published, installed, or written to the global store.",
|
|
352
|
-
],
|
|
353
|
-
examples: ['docspack preview "how do I authenticate"'],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
name: "eval",
|
|
357
|
-
summary: "Measure retrieval against a set of questions",
|
|
358
|
-
group: "authoring",
|
|
359
|
-
usage: "docspack eval <queries.json> [options]",
|
|
360
|
-
options: [
|
|
361
|
-
["--package-dir <d>", "the package to read (default: .)"],
|
|
362
|
-
["--limit <n>", `chunks per answer, the window a hit must fall in (default ${LIMIT})`],
|
|
363
|
-
["--max-tokens <n>", `token ceiling for each answer (default ${MAX_TOKENS})`],
|
|
364
|
-
["--min-hit-rate <n>", "exit 1 below this percentage of questions answered"],
|
|
365
|
-
],
|
|
366
|
-
detail: [
|
|
367
|
-
"The evaluation set is a JSON array of questions and the chunk ids that would answer them:",
|
|
368
|
-
"",
|
|
369
|
-
' [{ "query": "how do I verify a webhook signature", "expect": "webhooks-signing" },',
|
|
370
|
-
' { "query": "rate limits", "expect": ["rate-limits", "errors"] }]',
|
|
371
|
-
"",
|
|
372
|
-
"Reports hit rate, top-1 rate and mean answer size — the numbers that settle the chunk",
|
|
373
|
-
"budget, which no structural check can see. With --min-hit-rate it gates a publish on",
|
|
374
|
-
"retrieval quality the way `doctor --strict` gates it on structure.",
|
|
375
|
-
],
|
|
376
|
-
examples: [
|
|
377
|
-
"docspack eval ./eval/queries.json",
|
|
378
|
-
"docspack eval ./eval/queries.json --min-hit-rate 90",
|
|
379
|
-
"docspack eval ./eval/queries.json --limit 1 --json",
|
|
380
|
-
],
|
|
381
|
-
},
|
|
382
|
-
];
|
|
383
|
-
|
|
384
|
-
const GLOBAL_OPTIONS: readonly (readonly [string, string])[] = [
|
|
385
|
-
["--store <path>", "Use a different index"],
|
|
386
|
-
["--cwd <dir>", "Run in a different directory"],
|
|
387
|
-
["--json", "Machine-readable output"],
|
|
388
|
-
["-q, --quiet", "Only print errors"],
|
|
389
|
-
["-h, --help", "Show this help, or a command's help"],
|
|
390
|
-
["-v, --version", "Show the version"],
|
|
391
|
-
];
|
|
34
|
+
const WIDTH = 21;
|
|
35
|
+
/** The width prose is wrapped to, which is what the hand-written help used. */
|
|
36
|
+
const TEXT_WIDTH = 92;
|
|
37
|
+
|
|
38
|
+
const RESOLVED = resolveDocument(CLI);
|
|
39
|
+
|
|
40
|
+
/** The program's commands as a caller types them, in the order the document declares them. */
|
|
41
|
+
export const COMMANDS: readonly CommandHelp[] = RESOLVED.commands
|
|
42
|
+
.filter((command) => command.path.length === 2 && !command.hidden)
|
|
43
|
+
.map(toHelp);
|
|
44
|
+
|
|
45
|
+
const ROOT = RESOLVED.commands[0] as ResolvedCommand;
|
|
46
|
+
|
|
47
|
+
/** Every option the root declares — the inherited ones every command takes, and its own. */
|
|
48
|
+
const GLOBAL_OPTIONS: readonly (readonly [string, string])[] = ROOT.options.map(optionRow);
|
|
392
49
|
|
|
393
50
|
export function findCommandHelp(name: string): CommandHelp | undefined {
|
|
394
51
|
return COMMANDS.find((command) => command.name === name);
|
|
395
52
|
}
|
|
396
53
|
|
|
397
|
-
|
|
54
|
+
/** The document this help is rendered from, for `docspack --cmdspec`. */
|
|
55
|
+
export function cliDocument(version: string): CmdspecDocument {
|
|
56
|
+
return { ...CLI, info: { ...CLI.info, version } };
|
|
57
|
+
}
|
|
398
58
|
|
|
399
59
|
function row([flag, description]: readonly [string, string]): string {
|
|
400
|
-
return ` ${flag.padEnd(WIDTH)} ${description}`;
|
|
60
|
+
return description.length === 0 ? ` ${flag}` : ` ${flag.padEnd(WIDTH)} ${description}`;
|
|
401
61
|
}
|
|
402
62
|
|
|
403
63
|
/** The page one command prints for `docspack <command> --help`. */
|
|
404
64
|
export function commandHelpText(command: CommandHelp): string {
|
|
65
|
+
const resolved = RESOLVED.commands.find(
|
|
66
|
+
(candidate) => commandPath(candidate) === `docspack ${command.name}`,
|
|
67
|
+
);
|
|
405
68
|
const blocks: string[] = [
|
|
406
69
|
`docspack ${command.name} — ${command.summary}`,
|
|
407
70
|
"",
|
|
408
71
|
"Usage",
|
|
409
72
|
` ${command.usage}`,
|
|
410
73
|
];
|
|
74
|
+
for (const sub of subcommandsOf(resolved)) blocks.push(` ${synopsis(sub)}`);
|
|
411
75
|
|
|
412
76
|
if (command.detail !== undefined) {
|
|
413
77
|
blocks.push("", ...command.detail.map((line) => (line.length === 0 ? "" : ` ${line}`)));
|
|
@@ -415,6 +79,13 @@ export function commandHelpText(command: CommandHelp): string {
|
|
|
415
79
|
if (command.options.length > 0) {
|
|
416
80
|
blocks.push("", "Options", ...command.options.map(row));
|
|
417
81
|
}
|
|
82
|
+
for (const sub of subcommandsOf(resolved)) {
|
|
83
|
+
const rows = sub.options.filter((option) => option.hidden !== true).map(optionRow);
|
|
84
|
+
blocks.push("", `${commandPath(sub)} — ${commandSummary(sub) ?? ""}`);
|
|
85
|
+
if (rows.length > 0) blocks.push(...rows.map(row));
|
|
86
|
+
}
|
|
87
|
+
const exits = exitRows(resolved);
|
|
88
|
+
if (exits.length > 0) blocks.push("", "Exit status", ...exits.map(row));
|
|
418
89
|
blocks.push(
|
|
419
90
|
"",
|
|
420
91
|
"Global options",
|
|
@@ -427,6 +98,92 @@ export function commandHelpText(command: CommandHelp): string {
|
|
|
427
98
|
return blocks.join("\n");
|
|
428
99
|
}
|
|
429
100
|
|
|
101
|
+
function toHelp(command: ResolvedCommand): CommandHelp {
|
|
102
|
+
const description = command.declared.description;
|
|
103
|
+
return {
|
|
104
|
+
name: command.path[1] as string,
|
|
105
|
+
summary: commandSummary(command) ?? "",
|
|
106
|
+
group: command.declared.group === "authoring" ? "authoring" : "core",
|
|
107
|
+
usage: synopsis(command),
|
|
108
|
+
options: command.options.filter((option) => option.hidden !== true).map(optionRow),
|
|
109
|
+
...(description === undefined ? {} : { detail: wrap(description) }),
|
|
110
|
+
examples: [command, ...subcommandsOf(command)]
|
|
111
|
+
.flatMap((each) => (each.declared.examples ?? []).map((example) => example.run))
|
|
112
|
+
.filter((run, index, all) => all.indexOf(run) === index),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function subcommandsOf(command: ResolvedCommand | undefined): ResolvedCommand[] {
|
|
117
|
+
if (command === undefined) return [];
|
|
118
|
+
return RESOLVED.commands.filter(
|
|
119
|
+
(candidate) =>
|
|
120
|
+
!candidate.hidden &&
|
|
121
|
+
candidate.path.length === command.path.length + 1 &&
|
|
122
|
+
commandPath(candidate).startsWith(`${commandPath(command)} `),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One option as a help row: its spellings and value, then what it does and its default. */
|
|
127
|
+
function optionRow(option: ResolvedOption): readonly [string, string] {
|
|
128
|
+
const spellings = [option.name, ...(option.aliases ?? [])]
|
|
129
|
+
.sort((a, b) => a.length - b.length)
|
|
130
|
+
.join(", ");
|
|
131
|
+
const negation = option.negation === undefined ? "" : `, ${option.negation.join(", ")}`;
|
|
132
|
+
const placeholder = option.value === undefined ? "" : ` <${option.value.name ?? "value"}>`;
|
|
133
|
+
const value = option.value;
|
|
134
|
+
const notes = [
|
|
135
|
+
value?.enum === undefined ? undefined : value.enum.join(", "),
|
|
136
|
+
value?.default !== undefined
|
|
137
|
+
? `default ${String(value.default)}`
|
|
138
|
+
: value?.defaultDescription === undefined
|
|
139
|
+
? undefined
|
|
140
|
+
: `default: ${value.defaultDescription}`,
|
|
141
|
+
option.repeat === "list" ? "repeatable" : undefined,
|
|
142
|
+
option.required === true ? "required" : undefined,
|
|
143
|
+
option.env === undefined ? undefined : `env ${option.env.join(", ")}`,
|
|
144
|
+
].filter((note) => note !== undefined);
|
|
145
|
+
const summary = option.summary ?? "";
|
|
146
|
+
const text =
|
|
147
|
+
notes.length === 0
|
|
148
|
+
? summary
|
|
149
|
+
: `${summary}${summary.length > 0 ? " " : ""}(${notes.join("; ")})`;
|
|
150
|
+
return [`${spellings}${placeholder}${negation}`, text];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The statuses a command adds to the program's 0, 1 and 2, with its subcommands'. */
|
|
154
|
+
function exitRows(command: ResolvedCommand | undefined): (readonly [string, string])[] {
|
|
155
|
+
if (command === undefined) return [];
|
|
156
|
+
const rows: (readonly [string, string])[] = [];
|
|
157
|
+
for (const each of [command, ...subcommandsOf(command)]) {
|
|
158
|
+
for (const [code, exit] of Object.entries(each.declared.exits ?? {})) {
|
|
159
|
+
const who = each === command ? "" : ` (${each.path.slice(2).join(" ")})`;
|
|
160
|
+
rows.push([code, `${exit.meaning}${who}`]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return rows;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A description as help lines: paragraphs wrapped, indented blocks kept as written. */
|
|
167
|
+
function wrap(text: string): string[] {
|
|
168
|
+
const lines: string[] = [];
|
|
169
|
+
for (const paragraph of text.split("\n\n")) {
|
|
170
|
+
if (lines.length > 0) lines.push("");
|
|
171
|
+
if (paragraph.startsWith(" ")) {
|
|
172
|
+
lines.push(...paragraph.split("\n").map((line) => line.slice(2)));
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
let current = "";
|
|
176
|
+
for (const word of paragraph.replace(/\s+/g, " ").trim().split(" ")) {
|
|
177
|
+
if (current.length > 0 && current.length + 1 + word.length > TEXT_WIDTH) {
|
|
178
|
+
lines.push(current);
|
|
179
|
+
current = word;
|
|
180
|
+
} else current = current.length === 0 ? word : `${current} ${word}`;
|
|
181
|
+
}
|
|
182
|
+
if (current.length > 0) lines.push(current);
|
|
183
|
+
}
|
|
184
|
+
return lines;
|
|
185
|
+
}
|
|
186
|
+
|
|
430
187
|
/**
|
|
431
188
|
* The global page. It lists the commands and the options that apply to all of them, and points
|
|
432
189
|
* at the per-command pages for the rest — one list of every flag of every command was what made
|
|
@@ -451,10 +208,11 @@ Authoring
|
|
|
451
208
|
${group("authoring")}
|
|
452
209
|
|
|
453
210
|
How it works
|
|
454
|
-
Documentation ships as npm packages named @vendor/docspack
|
|
455
|
-
@docspack-community/<name>. Add them to
|
|
456
|
-
every chunk is indexed into a shared
|
|
457
|
-
that index locally: no network, no
|
|
211
|
+
Documentation ships as npm packages named @vendor/docspack,
|
|
212
|
+
@vendor/<name>-docspack or @docspack-community/<name>. Add them to
|
|
213
|
+
package.json, run \`docspack sync\`, and every chunk is indexed into a shared
|
|
214
|
+
SQLite database with FTS5. Agents query that index locally: no network, no
|
|
215
|
+
scraping, always the installed version.
|
|
458
216
|
|
|
459
217
|
Giving an agent access
|
|
460
218
|
Any agent with a shell can run \`docspack ask\`, so one line in AGENTS.md or
|
package/src/index.ts
CHANGED
package/src/init/plan.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
import { DocspackError } from "../errors.js";
|
|
3
|
-
import { isCommunityPackage, isDocsPackage } from "../spec.js";
|
|
3
|
+
import { DISCOVERABLE_NAMES, isCommunityPackage, isDocsPackage } from "../spec.js";
|
|
4
4
|
import type { Detected } from "./detect.js";
|
|
5
5
|
import { docSeeds, gitignore, packageJson, readme, workflow } from "./templates.js";
|
|
6
6
|
|
|
@@ -135,7 +135,7 @@ export function proposePackageName(detected: Detected, community: boolean): stri
|
|
|
135
135
|
function assertPackageName(name: string): void {
|
|
136
136
|
if (!isDocsPackage(name)) {
|
|
137
137
|
throw new DocspackError(`"${name}" is not a name docspack will discover`, {
|
|
138
|
-
hint:
|
|
138
|
+
hint: `Use one of ${DISCOVERABLE_NAMES}.`,
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -156,7 +156,8 @@ function resolveInput(
|
|
|
156
156
|
if (options.mirror !== undefined) {
|
|
157
157
|
if (!isCommunityPackage(options.name ?? "")) {
|
|
158
158
|
notes.push(
|
|
159
|
-
"A mirror republishes someone else's documentation; publish it under @docspack-community
|
|
159
|
+
"A mirror republishes someone else's documentation; publish it under @docspack-community, " +
|
|
160
|
+
"or as @yourscope/<name>-docspack if you own the scope — not as the vendor's own @vendor/docspack.",
|
|
160
161
|
);
|
|
161
162
|
}
|
|
162
163
|
return { kind: "mirror", value: options.mirror };
|
package/src/init/templates.ts
CHANGED
|
@@ -154,8 +154,10 @@ ${PLACEHOLDER} same shape as above.
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
export function workflow(name: string, buildArgs: string): string {
|
|
157
|
-
return `# Publishes ${name} on every release, so the documentation version
|
|
158
|
-
#
|
|
157
|
+
return `# Publishes ${name} on every release, so the documentation version matches the
|
|
158
|
+
# library version by default. That is a convention, not a constraint: to correct published
|
|
159
|
+
# documentation between two releases, dispatch this workflow with a version of its own and
|
|
160
|
+
# declare \`documents\` in the package. Needs an NPM_TOKEN secret with publish rights.
|
|
159
161
|
|
|
160
162
|
name: Publish documentation package
|
|
161
163
|
|