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.
Files changed (77) hide show
  1. package/README.md +25 -1
  2. package/dist/build.d.ts +5 -0
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +68 -5
  5. package/dist/build.js.map +1 -1
  6. package/dist/cli-spec.d.ts +3 -0
  7. package/dist/cli-spec.d.ts.map +1 -0
  8. package/dist/cli-spec.js +667 -0
  9. package/dist/cli-spec.js.map +1 -0
  10. package/dist/cli.d.ts +146 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +80 -14
  13. package/dist/cli.js.map +1 -1
  14. package/dist/cmdspec.json +1219 -0
  15. package/dist/commands.d.ts +78 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +231 -0
  18. package/dist/commands.js.map +1 -0
  19. package/dist/config.d.ts +1 -0
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +2 -0
  22. package/dist/config.js.map +1 -1
  23. package/dist/db.d.ts +6 -1
  24. package/dist/db.d.ts.map +1 -1
  25. package/dist/db.js +7 -1
  26. package/dist/db.js.map +1 -1
  27. package/dist/discovery.d.ts +22 -3
  28. package/dist/discovery.d.ts.map +1 -1
  29. package/dist/discovery.js +91 -12
  30. package/dist/discovery.js.map +1 -1
  31. package/dist/doctor.d.ts.map +1 -1
  32. package/dist/doctor.js +66 -1
  33. package/dist/doctor.js.map +1 -1
  34. package/dist/help.d.ts +10 -6
  35. package/dist/help.d.ts.map +1 -1
  36. package/dist/help.js +118 -371
  37. package/dist/help.js.map +1 -1
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/init/plan.js +4 -3
  43. package/dist/init/plan.js.map +1 -1
  44. package/dist/init/templates.d.ts.map +1 -1
  45. package/dist/init/templates.js +4 -2
  46. package/dist/init/templates.js.map +1 -1
  47. package/dist/preview.d.ts.map +1 -1
  48. package/dist/preview.js +12 -6
  49. package/dist/preview.js.map +1 -1
  50. package/dist/search.d.ts +2 -0
  51. package/dist/search.d.ts.map +1 -1
  52. package/dist/search.js +66 -22
  53. package/dist/search.js.map +1 -1
  54. package/dist/spec.d.ts +21 -1
  55. package/dist/spec.d.ts.map +1 -1
  56. package/dist/spec.js +24 -2
  57. package/dist/spec.js.map +1 -1
  58. package/dist/sync.d.ts.map +1 -1
  59. package/dist/sync.js +99 -1
  60. package/dist/sync.js.map +1 -1
  61. package/package.json +8 -5
  62. package/src/build.ts +86 -7
  63. package/src/cli-spec.ts +688 -0
  64. package/src/cli.ts +90 -17
  65. package/src/commands.ts +298 -0
  66. package/src/config.ts +4 -1
  67. package/src/db.ts +9 -2
  68. package/src/discovery.ts +113 -12
  69. package/src/doctor.ts +67 -0
  70. package/src/help.ts +138 -380
  71. package/src/index.ts +1 -0
  72. package/src/init/plan.ts +4 -3
  73. package/src/init/templates.ts +4 -2
  74. package/src/preview.ts +14 -13
  75. package/src/search.ts +79 -22
  76. package/src/spec.ts +28 -2
  77. package/src/sync.ts +120 -1
@@ -0,0 +1,688 @@
1
+ import type { CmdspecDocument, Option } from "@docspack/cmdspec";
2
+ import { DEFAULT_LIMIT, DEFAULT_MAX_TOKENS } from "./search.js";
3
+
4
+ /**
5
+ * The docspack CLI, described in cmdspec (`packages/cmdspec/SPEC.md`).
6
+ *
7
+ * This is the CLI's definition, not documentation of it. `cli.ts` builds each command's parser
8
+ * from it, so a flag one command reads is an error on another; `help.ts` renders `--help` from it;
9
+ * `docspack --cmdspec` prints it, and the build writes it to `dist/cmdspec.json`, which
10
+ * `package.json`'s `"cmdspec"` field names and the site imports. Before this there were three
11
+ * lists — the parser's, the help's and the site's — and they had drifted: `build` accepted
12
+ * `--from-json` and `--local`, and its help mentioned neither.
13
+ *
14
+ * A TypeScript constant rather than a YAML file, because the CLI reads it on every invocation and
15
+ * `docspack ask` pays for nothing it does not use; the compiler checks it against cmdspec's types,
16
+ * and `tests/cli-spec.test.ts` validates it against the schema and its rules.
17
+ *
18
+ * `info.version` is a placeholder: the CLI reports `package.json`'s, which is the one a release
19
+ * bumps.
20
+ */
21
+
22
+ const option = (definition: Option): Option => definition;
23
+
24
+ const components = {
25
+ package: option({
26
+ name: "--package",
27
+ aliases: ["-p"],
28
+ summary: "only packages whose name contains this text",
29
+ value: { name: "s" },
30
+ }),
31
+ limit: option({
32
+ name: "--limit",
33
+ summary: "maximum chunks to return",
34
+ value: { name: "n", type: "integer", minimum: 1, default: DEFAULT_LIMIT },
35
+ }),
36
+ "max-tokens": option({
37
+ name: "--max-tokens",
38
+ summary: "token ceiling for the result set",
39
+ value: { name: "n", type: "integer", minimum: 1, default: DEFAULT_MAX_TOKENS },
40
+ }),
41
+ all: option({ name: "--all", summary: "search the whole store, not just this project" }),
42
+ "package-dir": option({
43
+ name: "--package-dir",
44
+ summary: "the package to read",
45
+ value: { name: "d", format: "directory", default: "." },
46
+ }),
47
+ from: option({
48
+ name: "--from",
49
+ summary: "directory of Markdown to package",
50
+ value: { name: "dir", format: "directory" },
51
+ }),
52
+ openapi: option({
53
+ name: "--openapi",
54
+ summary: "OpenAPI 3 document (JSON or YAML), one chunk per operation",
55
+ value: { name: "file", format: "file" },
56
+ }),
57
+ name: option({
58
+ name: "--name",
59
+ summary: "package name, e.g. @acme/docspack",
60
+ value: { name: "name" },
61
+ }),
62
+ "pkg-version": option({
63
+ name: "--pkg-version",
64
+ summary: "package version",
65
+ value: { name: "v" },
66
+ }),
67
+ };
68
+
69
+ const ref = (key: keyof typeof components) => ({ $ref: `#/components/options/${key}` });
70
+
71
+ export const CLI: CmdspecDocument = {
72
+ $schema: "https://docspack.dev/cmdspec/schema/v0.1.json",
73
+ cmdspec: "0.1",
74
+ info: {
75
+ name: "docspack",
76
+ version: "0.0.0",
77
+ summary: "Local, version-locked documentation for AI agents",
78
+ description:
79
+ "Documentation ships as npm packages named @vendor/docspack, @vendor/<name>-docspack or @docspack-community/<name>. Add them to package.json, run `docspack sync`, and every chunk is indexed into a shared SQLite database with FTS5. Agents query that index locally: no network, no scraping, always the installed version.",
80
+ homepage: "https://docspack.dev",
81
+ license: { identifier: "MIT" },
82
+ },
83
+ subcommandRequired: true,
84
+ options: [
85
+ {
86
+ name: "--store",
87
+ summary: "Use a different index",
88
+ value: { name: "path", format: "file", defaultDescription: "~/.docspack/store.db" },
89
+ env: ["DOCSPACK_STORE"],
90
+ inherited: true,
91
+ },
92
+ {
93
+ name: "--cwd",
94
+ summary: "Run in a different directory",
95
+ value: { name: "dir", format: "directory", defaultDescription: "the current directory" },
96
+ inherited: true,
97
+ },
98
+ { name: "--json", summary: "Machine-readable output", inherited: true },
99
+ { name: "--quiet", aliases: ["-q"], summary: "Only print errors", inherited: true },
100
+ {
101
+ name: "--help",
102
+ aliases: ["-h"],
103
+ summary: "Show this help, or a command's help",
104
+ inherited: true,
105
+ },
106
+ { name: "--version", aliases: ["-v"], summary: "Show the version" },
107
+ { name: "--cmdspec", summary: "Print this program's own cmdspec document, as JSON" },
108
+ ],
109
+ environment: [{ name: "NO_COLOR", summary: "Print without colour when set to anything" }],
110
+ files: [
111
+ {
112
+ path: "~/.docspack/store.db",
113
+ role: "data",
114
+ format: "sqlite",
115
+ summary: "The global index, shared by every project on this machine",
116
+ },
117
+ {
118
+ path: ".docspack/local.db",
119
+ role: "data",
120
+ format: "sqlite",
121
+ summary: "This project's own corpus, written by `index`",
122
+ },
123
+ {
124
+ path: ".docspack/feedback.jsonl",
125
+ role: "state",
126
+ format: "jsonl",
127
+ summary: "Documentation problems recorded by `feedback add`",
128
+ },
129
+ ],
130
+ exits: {
131
+ "0": { meaning: "success" },
132
+ "1": { meaning: "the command failed" },
133
+ "2": { meaning: "the command was used wrongly" },
134
+ },
135
+ components: { options: components },
136
+ commands: [
137
+ {
138
+ name: "sync",
139
+ group: "core",
140
+ summary: "Index the docs packages this project depends on",
141
+ description:
142
+ "Reads node_modules and indexes every @vendor/docspack and @docspack-community/<name> package this project declares. Makes no network requests.\n\nIt also reads each installed library's own type declarations and indexes one entry per exported name. Half of a well-documented library's exports are mentioned in no documentation anyone published, and those declarations are the only local answer for them. They are looked up by name, never ranked against prose, so they cannot crowd out the documentation that does exist.\n\nA library that describes its own command line, with `\"cmdspec\"` in its package.json, has that description indexed as well, for the version installed.",
143
+ effects: ["read", "write"],
144
+ idempotent: true,
145
+ options: [
146
+ { name: "--force", summary: "re-index packages already in the store" },
147
+ {
148
+ name: "--no-artifacts",
149
+ summary: "skip the declarations derived from installed libraries",
150
+ },
151
+ ],
152
+ examples: [
153
+ { run: "docspack sync" },
154
+ { run: "docspack sync --force" },
155
+ { run: "docspack sync --no-artifacts" },
156
+ ],
157
+ },
158
+ {
159
+ name: "ask",
160
+ group: "core",
161
+ summary: "Answer from the local index — the command to give an agent",
162
+ description:
163
+ "Answers from the installed versions, in the Markdown an agent should read.\n\nWhen the question names something an installed library exports and no documentation mentions, the answer leads with that name's declaration from the installed build and says the documentation does not cover it. Ranking alone cannot tell that apart from a match. A question that names an endpoint (`POST /v1/charges`) or a command (`git remote add`) gets that operation or command first.",
164
+ effects: ["read"],
165
+ idempotent: true,
166
+ arguments: [
167
+ {
168
+ name: "question",
169
+ required: true,
170
+ variadic: true,
171
+ summary: "words are joined with spaces",
172
+ },
173
+ ],
174
+ options: [ref("package"), ref("limit"), ref("max-tokens"), ref("all")],
175
+ io: {
176
+ stdout: [
177
+ { mediaType: "text/markdown", summary: "the answer" },
178
+ {
179
+ mediaType: "application/json",
180
+ selectedBy: "--json",
181
+ summary: "the chunks and their scores",
182
+ },
183
+ ],
184
+ },
185
+ exits: {
186
+ "3": {
187
+ meaning: "nothing matched, and a docs package is installed but not indexed",
188
+ description: "Run `docspack sync`, then ask again.",
189
+ },
190
+ "4": { meaning: "nothing matched, and everything installed is already indexed" },
191
+ },
192
+ examples: [
193
+ { run: 'docspack ask "how do I verify a webhook signature"' },
194
+ {
195
+ run: 'docspack ask "webhook signature" --package stripe --limit 5',
196
+ summary: "Look in one package only, and return more",
197
+ },
198
+ ],
199
+ },
200
+ {
201
+ name: "index",
202
+ group: "core",
203
+ summary: "Index this project's own sources, so an agent can ask them instead of reading them",
204
+ description:
205
+ "For a corpus this project already has rather than one somebody published: notes, an export, rows out of a query. The payload is built in a temporary directory and thrown away; what is kept is the index, in `.docspack/local.db`, which is a plaintext copy of whatever was indexed and is gitignored on the tool's behalf.\n\nRecords arrive as JSON so no database driver is needed: `sqlite3 -json … | docspack index --from-json -`. A record with an `id` becomes one chunk under that id, because a row's identity is its key.\n\nWhat was indexed is recorded with each source's size, mtime and hash, so a re-run does nothing when nothing has changed and `recall` can say when an answer may be superseded.",
206
+ effects: ["read", "write"],
207
+ idempotent: true,
208
+ options: [
209
+ ref("from"),
210
+ {
211
+ name: "--from-json",
212
+ summary: "JSON records to index, or `-` for standard input",
213
+ value: { name: "file", format: "file", stdio: true },
214
+ },
215
+ {
216
+ name: "--name",
217
+ summary: "name for the corpus",
218
+ value: { name: "s", defaultDescription: "derived from the source" },
219
+ },
220
+ { name: "--force", summary: "re-index even when no source has changed" },
221
+ ],
222
+ io: {
223
+ stdin: [
224
+ {
225
+ mediaType: "application/json",
226
+ selectedBy: "--from-json=-",
227
+ summary: "an array of records, each with text and optionally an id and a title",
228
+ },
229
+ ],
230
+ },
231
+ examples: [
232
+ { run: "docspack index --from ./notes" },
233
+ {
234
+ run: "sqlite3 -json shop.db 'select id, title, body as text from posts' | docspack index --from-json -",
235
+ summary: "Index rows straight out of a database",
236
+ },
237
+ ],
238
+ },
239
+ {
240
+ name: "recall",
241
+ group: "core",
242
+ summary: "Answer from this project's indexed corpus, not from its dependencies",
243
+ description:
244
+ "Separate from `ask` on purpose. `ask` answers from the versions this project installed, and a working corpus is never one of them — so a corpus cannot reach an answer about a dependency, and a dependency cannot reach an answer about your notes.\n\nAn answer leads with a warning when a source has changed since it was indexed.",
245
+ effects: ["read"],
246
+ idempotent: true,
247
+ arguments: [{ name: "question", required: true, variadic: true }],
248
+ options: [ref("limit"), ref("max-tokens")],
249
+ exits: { "1": { meaning: "nothing matched" } },
250
+ examples: [{ run: 'docspack recall "what did we decide about retries"' }],
251
+ },
252
+ {
253
+ name: "search",
254
+ group: "core",
255
+ summary: "Same index, formatted for a human reading the terminal",
256
+ description: "The same index and ranking as `ask`, printed as a list of hits with a preview.",
257
+ effects: ["read"],
258
+ idempotent: true,
259
+ arguments: [{ name: "query", required: true, variadic: true }],
260
+ options: [ref("package"), ref("limit"), ref("max-tokens"), ref("all")],
261
+ exits: {
262
+ "3": { meaning: "nothing matched, and a docs package is installed but not indexed" },
263
+ "4": { meaning: "nothing matched, and everything installed is already indexed" },
264
+ },
265
+ examples: [{ run: "docspack search webhook signature" }],
266
+ },
267
+ {
268
+ name: "list",
269
+ group: "core",
270
+ summary: "Show this project's docs packages and their index state",
271
+ description:
272
+ "Coverage is mechanical: the exported names a library declares, against the names its documentation mentions anywhere. It is reported, never gated on — a page listing every export and explaining none would score full marks.",
273
+ effects: ["read"],
274
+ idempotent: true,
275
+ options: [
276
+ {
277
+ name: "--coverage",
278
+ summary: "how much of each documented library's exports the prose mentions",
279
+ },
280
+ ],
281
+ examples: [
282
+ { run: "docspack list" },
283
+ { run: "docspack list --coverage" },
284
+ { run: "docspack list --json" },
285
+ ],
286
+ },
287
+ {
288
+ name: "agent",
289
+ group: "core",
290
+ summary: "Wire docspack into the agent tooling this project already uses",
291
+ description:
292
+ "`install` writes a marked block into AGENTS.md or CLAUDE.md — whichever the project already has — and a skill into .claude/skills/docspack/ when the project uses Claude Code. Everything outside the markers is left alone, and re-running rewrites the block rather than appending a second copy. With no subcommand, `agent` installs.\n\n`check` writes nothing and exits non-zero when the wiring is missing or out of date, so CI notices a pasted instruction that has drifted from what the tool now does.",
293
+ effects: ["read", "write"],
294
+ idempotent: true,
295
+ options: [
296
+ {
297
+ name: "--feedback",
298
+ summary: "also include recording documentation problems",
299
+ inherited: true,
300
+ },
301
+ {
302
+ name: "--hooks",
303
+ summary: "add a SessionStart hook that keeps the index in step",
304
+ inherited: true,
305
+ },
306
+ { name: "--mcp", summary: "add the MCP server to .mcp.json", inherited: true },
307
+ {
308
+ name: "--dry-run",
309
+ summary: "print what would be written and write nothing",
310
+ effects: ["read"],
311
+ },
312
+ ],
313
+ commands: [
314
+ {
315
+ name: "install",
316
+ summary: "Write the docspack block into AGENTS.md or CLAUDE.md",
317
+ effects: ["read", "write"],
318
+ idempotent: true,
319
+ options: [
320
+ {
321
+ name: "--dry-run",
322
+ summary: "print what would be written and write nothing",
323
+ effects: ["read"],
324
+ },
325
+ ],
326
+ examples: [
327
+ { run: "docspack agent install" },
328
+ { run: "docspack agent install --feedback --hooks" },
329
+ ],
330
+ },
331
+ {
332
+ name: "check",
333
+ summary: "Fail when the wiring is missing or out of date",
334
+ effects: ["read"],
335
+ idempotent: true,
336
+ exits: { "1": { meaning: "the wiring is missing or out of date" } },
337
+ examples: [{ run: "docspack agent check" }],
338
+ },
339
+ ],
340
+ examples: [{ run: "docspack agent install --feedback --hooks" }],
341
+ },
342
+ {
343
+ name: "changed",
344
+ group: "core",
345
+ summary: "What a library's exports gained and lost between two versions",
346
+ description:
347
+ "Compares two versions already in the global store, which is shared by every project on this machine, so nothing is fetched. Without a version it compares what is installed here against the most recently indexed other version.\n\nUpgrades are overwhelmingly additive: the useful answer is what exists now that an older release did not have, and which of those names no documentation here mentions — the ones a model can know from neither its training data nor the vendor's pages.",
348
+ effects: ["read"],
349
+ idempotent: true,
350
+ arguments: [{ name: "library", required: true, value: { name: "library[@version]" } }],
351
+ examples: [{ run: "docspack changed hono" }, { run: "docspack changed hono@4.0.0" }],
352
+ },
353
+ {
354
+ name: "verify",
355
+ group: "core",
356
+ summary: "Check the docs still describe the code you installed",
357
+ description:
358
+ 'Compares the identifiers the chunks name against what the documented libraries declare, read from their .d.ts files. Nothing you depend on is imported or executed. The libraries come from the manifest\'s "documents", or from the docspack key when the manifest has none.',
359
+ effects: ["read"],
360
+ idempotent: true,
361
+ options: [
362
+ {
363
+ name: "--package-dir",
364
+ summary: "verify one package directory instead of what is installed",
365
+ value: { name: "d", format: "directory" },
366
+ },
367
+ ],
368
+ exits: { "1": { meaning: "anything was reported" } },
369
+ examples: [{ run: "docspack verify" }, { run: "docspack verify --json" }],
370
+ },
371
+ {
372
+ name: "feedback",
373
+ group: "core",
374
+ summary: "Record documentation problems: add, list, submit, remove",
375
+ description:
376
+ "Writes to .docspack/feedback.jsonl in this project. Nothing is transmitted: docspack contains no code that can send a report anywhere.\n\n`drift` must name the identifier that drifted. `incorrect` and `missing` must carry --expected, --actual and --repro, so a claim that cannot show its work cannot be recorded. `submit` prints a prefilled GitHub issue URL for a human to open and file.",
377
+ subcommandRequired: true,
378
+ commands: [
379
+ {
380
+ name: "add",
381
+ summary: "Record one problem",
382
+ effects: ["write"],
383
+ options: [
384
+ {
385
+ name: "--chunk",
386
+ required: true,
387
+ summary: "the chunk the problem is in",
388
+ value: { name: "id" },
389
+ },
390
+ {
391
+ name: "--kind",
392
+ value: { name: "k", enum: ["drift", "incorrect", "missing"], default: "drift" },
393
+ },
394
+ {
395
+ name: "--evidence",
396
+ required: true,
397
+ summary: "the claim, in one line",
398
+ value: { name: "text" },
399
+ },
400
+ {
401
+ name: "--expected",
402
+ summary: "what the documentation led you to expect",
403
+ value: { name: "text" },
404
+ },
405
+ { name: "--actual", summary: "what happened instead", value: { name: "text" } },
406
+ { name: "--repro", summary: "code that demonstrates it", value: { name: "code" } },
407
+ ],
408
+ constraints: [
409
+ { when: "--kind=incorrect", requires: ["--expected", "--actual", "--repro"] },
410
+ { when: "--kind=missing", requires: ["--expected", "--actual", "--repro"] },
411
+ ],
412
+ examples: [
413
+ {
414
+ run: 'docspack feedback add --chunk @acme/docspack@1.4.0/api-auth --kind drift --evidence "client.setKey is not exported; setApiKey is"',
415
+ },
416
+ ],
417
+ },
418
+ {
419
+ name: "list",
420
+ summary: "Show recorded problems",
421
+ effects: ["read"],
422
+ idempotent: true,
423
+ examples: [{ run: "docspack feedback list" }],
424
+ },
425
+ {
426
+ name: "submit",
427
+ summary: "Print a prefilled GitHub issue URL for a human to open",
428
+ effects: ["read"],
429
+ idempotent: true,
430
+ options: [
431
+ {
432
+ name: "--package",
433
+ aliases: ["-p"],
434
+ summary: "only findings from packages matching this text",
435
+ value: { name: "s" },
436
+ },
437
+ ],
438
+ examples: [{ run: "docspack feedback submit" }],
439
+ },
440
+ {
441
+ name: "remove",
442
+ summary: "Delete recorded problems",
443
+ effects: ["destructive"],
444
+ idempotent: true,
445
+ arguments: [{ name: "fingerprint", variadic: true }],
446
+ options: [{ name: "--all", summary: "every recorded finding" }],
447
+ constraints: [{ oneOf: ["fingerprint", "--all"] }],
448
+ examples: [{ run: "docspack feedback remove <fingerprint>" }],
449
+ },
450
+ ],
451
+ },
452
+ {
453
+ name: "mcp",
454
+ group: "core",
455
+ summary: "Serve the index over MCP instead, as a long-lived process",
456
+ description:
457
+ "Serves the same index over the Model Context Protocol, for clients that prefer a tool definition to a shell command. `query_local_docs` returns exactly what `ask` prints. stdout is the protocol channel, so nothing else is written to it.",
458
+ effects: ["read"],
459
+ longRunning: true,
460
+ io: {
461
+ stdin: [{ mediaType: "application/jsonl", summary: "MCP requests" }],
462
+ stdout: [{ mediaType: "application/jsonl", summary: "MCP responses" }],
463
+ },
464
+ examples: [{ run: "docspack mcp" }],
465
+ },
466
+ {
467
+ name: "sources",
468
+ group: "core",
469
+ summary: "List curated sources that `docspack build` can fetch",
470
+ effects: [],
471
+ idempotent: true,
472
+ examples: [
473
+ { run: "docspack sources" },
474
+ { run: "docspack build hono --name @docspack-community/hono" },
475
+ ],
476
+ },
477
+ {
478
+ name: "init",
479
+ group: "authoring",
480
+ summary: "Scaffold a documentation package, then build and check it",
481
+ description:
482
+ "Reads the surrounding project first — name, version, a docs directory, an OpenAPI document, the git remote, the license — and proposes a package built from what it found.",
483
+ effects: ["read", "write", "network"],
484
+ interactive: { when: "tty", disable: "--yes" },
485
+ arguments: [{ name: "name", summary: "package name" }],
486
+ options: [
487
+ {
488
+ name: "--yes",
489
+ aliases: ["-y"],
490
+ summary: "skip the prompts and use flags plus detected defaults",
491
+ },
492
+ {
493
+ name: "--dry-run",
494
+ summary: "print the file tree and write nothing",
495
+ effects: ["read"],
496
+ },
497
+ {
498
+ name: "--mirror",
499
+ summary: "bootstrap from a published llms.txt",
500
+ value: { name: "id|url" },
501
+ },
502
+ { name: "--community", summary: "scaffold under @docspack-community" },
503
+ { name: "--template", value: { name: "t", enum: ["full", "minimal"], default: "full" } },
504
+ { name: "--mode", value: { name: "m", enum: ["standalone", "in-repo"] } },
505
+ {
506
+ name: "--workflow",
507
+ negation: ["--no-workflow"],
508
+ summary: "write the release workflow (the default)",
509
+ },
510
+ {
511
+ name: "--build",
512
+ negation: ["--no-build"],
513
+ summary: "build and check after scaffolding (the default)",
514
+ },
515
+ ref("name"),
516
+ ref("pkg-version"),
517
+ ref("from"),
518
+ {
519
+ name: "--openapi",
520
+ summary: "OpenAPI JSON to package",
521
+ value: { name: "file", format: "file" },
522
+ },
523
+ {
524
+ name: "--out",
525
+ summary: "where to scaffold",
526
+ value: { name: "dir", format: "directory", default: "./docspack" },
527
+ },
528
+ { name: "--force", summary: "overwrite files that already exist" },
529
+ ],
530
+ examples: [
531
+ { run: "docspack init" },
532
+ { run: "docspack init --name @acme/docspack --from ./docs --yes" },
533
+ { run: "docspack init --mirror hono --community --yes" },
534
+ ],
535
+ },
536
+ {
537
+ name: "build",
538
+ group: "authoring",
539
+ summary: "Generate the .llms/ payload for publishing",
540
+ description:
541
+ 'Splits each document at ## headings, then ###, then paragraphs, until every chunk fits the budget. --min-chunk-tokens packs the other way first, merging adjacent sections up to the budget: use it for generated reference, where a heading is a field name and one chunk per heading is hundreds of chunks too small to answer anything.\n\n--openapi writes one chunk per operation: the base URL, the credential, the inputs with their types, the response, the failures and a runnable curl call, in LAPIS notation. Each chunk answers to both `POST /v1/charges` and the operationId, so `docspack ask` can be given either. Combine it with --from to publish prose and an API in one package.\n\n--cmdspec writes one chunk per command of a command-line interface described in cmdspec, or in OpenCLI or Usage, converted: the synopsis, the inputs with their types and defaults, what the command changes, what it prints and what its exit status means. Each chunk answers to its command path, so `docspack ask "git remote add"` pins it.\n\nWith no flags, settings are read from the docspack key of the package.json in --out.',
542
+ effects: ["read", "write", "network"],
543
+ idempotent: true,
544
+ arguments: [
545
+ { name: "source", summary: "a curated source to fetch, from `docspack sources`" },
546
+ ],
547
+ options: [
548
+ ref("from"),
549
+ ref("openapi"),
550
+ {
551
+ name: "--cmdspec",
552
+ summary: "CLI description (cmdspec, OpenCLI or Usage), one chunk per command",
553
+ value: { name: "file", format: "file" },
554
+ },
555
+ {
556
+ name: "--from-json",
557
+ summary: "JSON records to package, or `-` for standard input",
558
+ value: { name: "file", format: "file", stdio: true },
559
+ },
560
+ ref("name"),
561
+ ref("pkg-version"),
562
+ {
563
+ name: "--out",
564
+ summary: "output directory",
565
+ value: { name: "dir", format: "directory", defaultDescription: "the current directory" },
566
+ },
567
+ {
568
+ name: "--pages",
569
+ summary: "maximum documents to fetch from a remote source",
570
+ value: { name: "n", type: "integer", minimum: 1 },
571
+ },
572
+ {
573
+ name: "--max-chunk-tokens",
574
+ summary: "split a section above this size",
575
+ value: { name: "n", type: "integer", minimum: 1, default: 800 },
576
+ },
577
+ {
578
+ name: "--min-chunk-tokens",
579
+ summary: "pack adjacent sections up to this size before splitting",
580
+ value: { name: "n", type: "integer", minimum: 1 },
581
+ },
582
+ {
583
+ name: "--documents",
584
+ summary: "library this package documents, such as acme@1.4.0",
585
+ repeat: "list",
586
+ value: { name: "p" },
587
+ },
588
+ {
589
+ name: "--local",
590
+ hidden: true,
591
+ summary: "build a working corpus rather than a publishable package, as `index` does",
592
+ },
593
+ ],
594
+ constraints: [
595
+ { exclusive: ["source", "--from"] },
596
+ { exclusive: ["source", "--openapi"] },
597
+ { exclusive: ["source", "--cmdspec"] },
598
+ { exclusive: ["source", "--from-json"] },
599
+ ],
600
+ examples: [
601
+ { run: "docspack build" },
602
+ { run: "docspack build --from ./docs --name @acme/docspack --pkg-version 1.4.0" },
603
+ { run: "docspack build --from ./reference --min-chunk-tokens 400 --max-chunk-tokens 900" },
604
+ { run: "docspack build --openapi ./openapi.json" },
605
+ {
606
+ run: "docspack build --from ./docs --openapi ./openapi.json",
607
+ summary: "Prose and an HTTP API in one package",
608
+ },
609
+ { run: "docspack build --from ./docs --cmdspec ./cmdspec.yaml" },
610
+ ],
611
+ },
612
+ {
613
+ name: "doctor",
614
+ group: "authoring",
615
+ summary: "Check a package the way the indexer and a reviewer would",
616
+ description:
617
+ "Errors are packages that will not index. Warnings are packages that will index and retrieve badly. Prose style is a note unless --pedantic, because writing by hand is not a reason to block a first publish.",
618
+ effects: ["read"],
619
+ idempotent: true,
620
+ options: [
621
+ { name: "--strict", summary: "treat structural warnings as failures" },
622
+ { name: "--pedantic", summary: "--strict, and fail on prose style as well" },
623
+ ref("package-dir"),
624
+ ],
625
+ exits: { "1": { meaning: "the package is not ready to publish" } },
626
+ examples: [
627
+ { run: "docspack doctor" },
628
+ { run: "docspack doctor --strict" },
629
+ { run: "docspack doctor --json" },
630
+ ],
631
+ },
632
+ {
633
+ name: "preview",
634
+ group: "authoring",
635
+ summary: "Answer a query from the local package, as an agent would",
636
+ description:
637
+ "Indexes the package in memory and answers through the same ranking and token budget an agent gets. Nothing is published, installed, or written to the global store.",
638
+ effects: ["read"],
639
+ idempotent: true,
640
+ arguments: [{ name: "query", required: true, variadic: true }],
641
+ options: [ref("package-dir"), ref("limit"), ref("max-tokens")],
642
+ examples: [{ run: 'docspack preview "how do I authenticate"' }],
643
+ },
644
+ {
645
+ name: "eval",
646
+ group: "authoring",
647
+ summary: "Measure retrieval against a set of questions",
648
+ description:
649
+ 'The evaluation set is a JSON array of questions and the chunk ids that would answer them:\n\n [{ "query": "how do I verify a webhook signature", "expect": "webhooks-signing" },\n { "query": "rate limits", "expect": ["rate-limits", "errors"] }]\n\nReports hit rate, top-1 rate and mean answer size — the numbers that settle the chunk budget, which no structural check can see. With --min-hit-rate it gates a publish on retrieval quality the way `doctor --strict` gates it on structure.',
650
+ effects: ["read"],
651
+ idempotent: true,
652
+ arguments: [
653
+ { name: "queries", required: true, value: { name: "queries.json", format: "file" } },
654
+ ],
655
+ options: [
656
+ ref("package-dir"),
657
+ {
658
+ name: "--limit",
659
+ summary: "chunks per answer, the window a hit must fall in",
660
+ value: { name: "n", type: "integer", minimum: 1, default: DEFAULT_LIMIT },
661
+ },
662
+ {
663
+ name: "--max-tokens",
664
+ summary: "token ceiling for each answer",
665
+ value: { name: "n", type: "integer", minimum: 1, default: DEFAULT_MAX_TOKENS },
666
+ },
667
+ {
668
+ name: "--min-hit-rate",
669
+ summary: "exit 1 below this percentage of questions answered",
670
+ value: { name: "n", type: "number", minimum: 0, maximum: 100 },
671
+ },
672
+ ],
673
+ exits: { "1": { meaning: "the hit rate is below --min-hit-rate" } },
674
+ examples: [
675
+ { run: "docspack eval ./eval/queries.json" },
676
+ { run: "docspack eval ./eval/queries.json --min-hit-rate 90" },
677
+ { run: "docspack eval ./eval/queries.json --limit 1 --json" },
678
+ ],
679
+ },
680
+ {
681
+ name: "help",
682
+ hidden: true,
683
+ summary: "Show the help, or a command's help",
684
+ effects: [],
685
+ arguments: [{ name: "command" }],
686
+ },
687
+ ],
688
+ };