@bpmnkit/docspack 0.0.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/.llms/chunks/cli.casen.aikit-skills.md +19 -0
- package/.llms/chunks/cli.casen.bpmn-generation-pipeline.md +32 -0
- package/.llms/chunks/cli.casen.connection-profiles.md +62 -0
- package/.llms/chunks/cli.casen.generate-bpmn-files.md +28 -0
- package/.llms/chunks/cli.casen.md +80 -0
- package/.llms/chunks/cli.casen.plugins.md +56 -0
- package/.llms/chunks/cli.casen.worker-commands.md +45 -0
- package/.llms/chunks/cli.connector.all-flags.md +29 -0
- package/.llms/chunks/cli.connector.browse-the-bundled-ootb-catalog.md +32 -0
- package/.llms/chunks/cli.connector.built-in-catalog.md +41 -0
- package/.llms/chunks/cli.connector.common-workflows.md +50 -0
- package/.llms/chunks/cli.connector.generate-from-the-openapi-catalog.md +30 -0
- package/.llms/chunks/cli.connector.md +20 -0
- package/.llms/chunks/cli.connector.programmatic-usage.md +7 -0
- package/.llms/chunks/cli.connector.what-gets-generated.md +20 -0
- package/.llms/chunks/cli.generate.definition-mode-ai-scripting-path.md +89 -0
- package/.llms/chunks/cli.generate.md +52 -0
- package/.llms/chunks/cli.generate.modify-existing-mode-input.md +59 -0
- package/.llms/chunks/cli.generate.typical-ai-workflow.md +18 -0
- package/.llms/chunks/cli.plugin-authoring.md +59 -0
- package/.llms/chunks/cli.plugin-authoring.plugin-naming-conventions.md +11 -0
- package/.llms/chunks/cli.plugin-authoring.test-locally.md +41 -0
- package/.llms/chunks/cli.plugin-authoring.the-bpmnkit-cli-sdk-sdk.md +82 -0
- package/.llms/chunks/cli.plugin-authoring.what-gets-generated.md +69 -0
- package/.llms/chunks/cli.plugin-authoring.writing-commands.md +84 -0
- package/.llms/chunks/cli.plugins.bpmnkit-casen-report.md +39 -0
- package/.llms/chunks/cli.plugins.bpmnkit-casen-worker-http.md +23 -0
- package/.llms/chunks/cli.plugins.md +25 -0
- package/.llms/chunks/cli.skills.deploy.md +24 -0
- package/.llms/chunks/cli.skills.implement.md +36 -0
- package/.llms/chunks/cli.skills.md +29 -0
- package/.llms/chunks/cli.skills.no-mcp-server-required.md +6 -0
- package/.llms/chunks/cli.skills.review.md +33 -0
- package/.llms/chunks/cli.skills.test.md +33 -0
- package/.llms/chunks/cli.view.folder-support.md +31 -0
- package/.llms/chunks/cli.view.md +70 -0
- package/.llms/chunks/cli.view.tabbed-navigation.md +28 -0
- package/.llms/chunks/getting-started.concepts.ai-compact-format.md +23 -0
- package/.llms/chunks/getting-started.concepts.auto-layout.md +24 -0
- package/.llms/chunks/getting-started.concepts.parsing-and-serializing.md +26 -0
- package/.llms/chunks/getting-started.concepts.the-fluent-builder.md +41 -0
- package/.llms/chunks/getting-started.concepts.zeebe-extensions.md +25 -0
- package/.llms/chunks/getting-started.installation.md +38 -0
- package/.llms/chunks/getting-started.installation.runtime-requirements.md +13 -0
- package/.llms/chunks/getting-started.installation.svg-canvas-viewer.md +41 -0
- package/.llms/chunks/getting-started.installation.worker-client.md +40 -0
- package/.llms/chunks/getting-started.quick-start.md +31 -0
- package/.llms/chunks/getting-started.quick-start.step-2-simulate-locally.md +28 -0
- package/.llms/chunks/getting-started.quick-start.step-3-deploy-to-camunda-8.md +33 -0
- package/.llms/chunks/getting-started.quick-start.what-s-next.md +14 -0
- package/.llms/chunks/guides.ai-agents.deploy-grade-validation.md +19 -0
- package/.llms/chunks/guides.ai-agents.how-it-s-modeled.md +11 -0
- package/.llms/chunks/guides.ai-agents.md +18 -0
- package/.llms/chunks/guides.ai-agents.see-also.md +7 -0
- package/.llms/chunks/guides.ai-agents.testing-without-a-real-llm-call.md +26 -0
- package/.llms/chunks/guides.ai-agents.the-aiagent-plan-step.md +62 -0
- package/.llms/chunks/guides.ai-implement.deploying.md +49 -0
- package/.llms/chunks/guides.ai-implement.extending-an-existing-process.md +10 -0
- package/.llms/chunks/guides.ai-implement.lightweight-alternative-casen-skills-install.md +28 -0
- package/.llms/chunks/guides.ai-implement.md +32 -0
- package/.llms/chunks/guides.ai-implement.the-claude-code-plugin.md +21 -0
- package/.llms/chunks/guides.ai.claude-api-example.md +41 -0
- package/.llms/chunks/guides.ai.mcp-server.md +20 -0
- package/.llms/chunks/guides.ai.md +32 -0
- package/.llms/chunks/guides.ai.minimal-empty-diagram.md +32 -0
- package/.llms/chunks/guides.ai.openai-function-calling.md +40 -0
- package/.llms/chunks/guides.building-processes.boundary-events.md +47 -0
- package/.llms/chunks/guides.building-processes.call-activities.md +47 -0
- package/.llms/chunks/guides.building-processes.events.md +57 -0
- package/.llms/chunks/guides.building-processes.multi-instance.md +20 -0
- package/.llms/chunks/guides.building-processes.service-tasks.md +39 -0
- package/.llms/chunks/guides.building-processes.sub-processes.md +40 -0
- package/.llms/chunks/guides.building-processes.task-defaults.md +18 -0
- package/.llms/chunks/guides.claude-code-plugin.ambient-hooks.md +24 -0
- package/.llms/chunks/guides.claude-code-plugin.configuration.md +126 -0
- package/.llms/chunks/guides.claude-code-plugin.md +47 -0
- package/.llms/chunks/guides.claude-code-plugin.reference-docs.md +61 -0
- package/.llms/chunks/guides.deployment.ci-cd-deploy-on-push.md +47 -0
- package/.llms/chunks/guides.deployment.deploying-a-process.md +52 -0
- package/.llms/chunks/guides.deployment.handling-jobs.md +50 -0
- package/.llms/chunks/guides.deployment.managing-incidents.md +33 -0
- package/.llms/chunks/guides.deployment.md +49 -0
- package/.llms/chunks/guides.gateways.branching-inside-sub-processes.md +27 -0
- package/.llms/chunks/guides.gateways.exclusive-gateway-xor.md +41 -0
- package/.llms/chunks/guides.gateways.inclusive-gateway-or.md +43 -0
- package/.llms/chunks/guides.gateways.nested-branching.md +27 -0
- package/.llms/chunks/guides.gateways.parallel-gateway-and.md +26 -0
- package/.llms/chunks/guides.patterns.how-matching-works.md +17 -0
- package/.llms/chunks/guides.patterns.md +26 -0
- package/.llms/chunks/guides.patterns.pattern-schema.md +38 -0
- package/.llms/chunks/guides.patterns.see-also.md +7 -0
- package/.llms/chunks/guides.patterns.using-patterns-from-the-cli.md +30 -0
- package/.llms/chunks/guides.patterns.what-a-pattern-contains.md +18 -0
- package/.llms/chunks/guides.simulation.dmn-decision-evaluation.md +51 -0
- package/.llms/chunks/guides.simulation.instance-state.md +29 -0
- package/.llms/chunks/guides.simulation.md +51 -0
- package/.llms/chunks/guides.simulation.message-correlation.md +30 -0
- package/.llms/chunks/guides.simulation.multiple-deployments.md +14 -0
- package/.llms/chunks/guides.workers-standalone.anatomy-of-a-worker.md +42 -0
- package/.llms/chunks/guides.workers-standalone.camunda-saas.md +43 -0
- package/.llms/chunks/guides.workers-standalone.environment-variables.md +14 -0
- package/.llms/chunks/guides.workers-standalone.md +35 -0
- package/.llms/chunks/guides.workers-standalone.running-workers.md +61 -0
- package/.llms/chunks/guides.workers-standalone.see-also.md +7 -0
- package/.llms/chunks/packages.api.incident-resolution.md +31 -0
- package/.llms/chunks/packages.api.observability-events.md +13 -0
- package/.llms/chunks/packages.api.overview.md +49 -0
- package/.llms/chunks/packages.api.process-operations.md +60 -0
- package/.llms/chunks/packages.api.resource-namespaces.md +20 -0
- package/.llms/chunks/packages.canvas.events.md +56 -0
- package/.llms/chunks/packages.canvas.options.md +24 -0
- package/.llms/chunks/packages.canvas.overview.md +35 -0
- package/.llms/chunks/packages.connector-gen.body-expansion.md +18 -0
- package/.llms/chunks/packages.connector-gen.catalog-reference.md +54 -0
- package/.llms/chunks/packages.connector-gen.catalogentry-type.md +21 -0
- package/.llms/chunks/packages.connector-gen.generating-from-the-catalog.md +44 -0
- package/.llms/chunks/packages.connector-gen.installation.md +42 -0
- package/.llms/chunks/packages.connector-gen.lower-level-api.md +31 -0
- package/.llms/chunks/packages.connector-gen.overview.md +16 -0
- package/.llms/chunks/packages.connector-gen.writeoptions.md +26 -0
- package/.llms/chunks/packages.core.dmn-support.md +36 -0
- package/.llms/chunks/packages.core.installation-bpmn.creatediagram-id.md +18 -0
- package/.llms/chunks/packages.core.installation-bpmn.createprocess-id-name.md +10 -0
- package/.llms/chunks/packages.core.installation-bpmn.export-definitions.md +10 -0
- package/.llms/chunks/packages.core.installation-bpmn.makeempty-processid-processname.md +11 -0
- package/.llms/chunks/packages.core.installation-bpmn.parse-xml.md +10 -0
- package/.llms/chunks/packages.core.installation-bpmn.sample_xml.md +7 -0
- package/.llms/chunks/packages.core.installation-compactify-definitions.md +13 -0
- package/.llms/chunks/packages.core.installation-expand-compact.md +13 -0
- package/.llms/chunks/packages.core.installation-layoutprocess-process.md +15 -0
- package/.llms/chunks/packages.core.installation-processbuilder-methods.md +29 -0
- package/.llms/chunks/packages.core.installation.md +11 -0
- package/.llms/chunks/packages.core.overview.md +15 -0
- package/.llms/chunks/packages.docspack.installation.md +45 -0
- package/.llms/chunks/packages.docspack.overview.md +23 -0
- package/.llms/chunks/packages.docspack.reading-it-yourself.md +34 -0
- package/.llms/chunks/packages.docspack.trust.md +13 -0
- package/.llms/chunks/packages.docspack.using-the-index-directly.md +52 -0
- package/.llms/chunks/packages.editor.basic-setup.md +41 -0
- package/.llms/chunks/packages.editor.full-editor-with-all-plugins.md +30 -0
- package/.llms/chunks/packages.editor.hud-options.md +41 -0
- package/.llms/chunks/packages.editor.overview.md +21 -0
- package/.llms/chunks/packages.engine.installation.md +77 -0
- package/.llms/chunks/packages.engine.overview.md +19 -0
- package/.llms/chunks/packages.engine.processinstance.md +17 -0
- package/.llms/chunks/packages.engine.variable-scoping.md +27 -0
- package/.llms/chunks/packages.worker-client.client.poll-jobtype-options.md +40 -0
- package/.llms/chunks/packages.worker-client.createworkerclient-options.md +32 -0
- package/.llms/chunks/packages.worker-client.job.complete-variables.md +29 -0
- package/.llms/chunks/packages.worker-client.md +33 -0
- package/.llms/chunks/packages.worker-client.oauth2-camunda-saas.md +26 -0
- package/.llms/chunks/packages.worker-client.see-also.md +7 -0
- package/.llms/manifest.json +2083 -0
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.js +89 -0
- package/dist/chunk.d.ts +41 -0
- package/dist/chunk.js +242 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/load.d.ts +27 -0
- package/dist/load.js +144 -0
- package/dist/search.d.ts +53 -0
- package/dist/search.js +104 -0
- package/dist/text.d.ts +22 -0
- package/dist/text.js +204 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.js +3 -0
- package/llms.txt +174 -0
- package/package.json +76 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `bpmnkit-docs` — read the installed documentation packages and answer from them.
|
|
4
|
+
*
|
|
5
|
+
* Every command reads the filesystem only. There is no server, no network call
|
|
6
|
+
* and nothing resident between questions.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join, resolve } from "node:path";
|
|
10
|
+
import { buildPack } from "./build.js";
|
|
11
|
+
import { discoverPacks, indexPacks } from "./load.js";
|
|
12
|
+
import { answer, search } from "./search.js";
|
|
13
|
+
const USAGE = `bpmnkit-docs — offline documentation search for AI agents
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
bpmnkit-docs ask <question> Answer from the installed docs packages
|
|
17
|
+
bpmnkit-docs search <query> Rank matching chunks, for reading in a terminal
|
|
18
|
+
bpmnkit-docs list Show the docs packages found and their state
|
|
19
|
+
bpmnkit-docs build Regenerate this package's .llms/ payload
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--limit <n> Chunks to return (default 3)
|
|
23
|
+
--max-tokens <n> Token ceiling for an answer (default 3000)
|
|
24
|
+
--pack <name> Restrict to one documentation package
|
|
25
|
+
--cwd <dir> Directory to resolve packages from (default: current)
|
|
26
|
+
`;
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const options = new Map();
|
|
29
|
+
const words = [];
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const arg = argv[i] ?? "";
|
|
32
|
+
if (arg.startsWith("--"))
|
|
33
|
+
options.set(arg.slice(2), argv[++i] ?? "");
|
|
34
|
+
else
|
|
35
|
+
words.push(arg);
|
|
36
|
+
}
|
|
37
|
+
return { command: words[0] ?? "", rest: words.slice(1), options };
|
|
38
|
+
}
|
|
39
|
+
function number(args, name, fallback) {
|
|
40
|
+
const raw = args.options.get(name);
|
|
41
|
+
if (raw === undefined)
|
|
42
|
+
return fallback;
|
|
43
|
+
const value = Number(raw);
|
|
44
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
45
|
+
throw new Error(`--${name} must be a positive integer (got ${JSON.stringify(raw)})`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function main(argv) {
|
|
50
|
+
const args = parseArgs(argv);
|
|
51
|
+
const cwd = resolve(args.options.get("cwd") ?? process.cwd());
|
|
52
|
+
if (args.command === "help" || args.options.has("help")) {
|
|
53
|
+
process.stdout.write(USAGE);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
if (args.command === "") {
|
|
57
|
+
process.stderr.write(USAGE);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (args.command === "build")
|
|
61
|
+
return runBuild(cwd);
|
|
62
|
+
const packs = discoverPacks(cwd);
|
|
63
|
+
if (packs.length === 0) {
|
|
64
|
+
process.stderr.write(`No documentation packages found from ${cwd}.\nInstall one, e.g. npm i -D @bpmnkit/docspack\n`);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
if (args.command === "list")
|
|
68
|
+
return runList(packs);
|
|
69
|
+
const query = args.rest.join(" ").trim();
|
|
70
|
+
if (query === "") {
|
|
71
|
+
process.stderr.write(`${args.command} needs a query — bpmnkit-docs ${args.command} "..."\n`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
const selected = args.options.get("pack");
|
|
75
|
+
const scope = selected ? { packs: [selected] } : {};
|
|
76
|
+
const index = indexPacks(packs);
|
|
77
|
+
if (args.command === "search") {
|
|
78
|
+
const hits = search(index, query, { ...scope, limit: number(args, "limit", 10) });
|
|
79
|
+
process.stdout.write(formatSearch(query, hits));
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
if (args.command === "ask") {
|
|
83
|
+
const result = answer(index, query, {
|
|
84
|
+
...scope,
|
|
85
|
+
limit: number(args, "limit", 3),
|
|
86
|
+
maxTokens: number(args, "max-tokens", 3000),
|
|
87
|
+
});
|
|
88
|
+
process.stdout.write(formatAnswer(query, result.hits, result.tokens, result.maxTokens));
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
process.stderr.write(`Unknown command "${args.command}".\n\n${USAGE}`);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
function runBuild(cwd) {
|
|
95
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
96
|
+
const config = pkg.docspack ?? {};
|
|
97
|
+
if (typeof config.source !== "string") {
|
|
98
|
+
process.stderr.write(`${cwd}/package.json: "docspack.source" must be a path to Markdown docs\n`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const result = buildPack({
|
|
102
|
+
source: resolve(cwd, config.source),
|
|
103
|
+
packDir: cwd,
|
|
104
|
+
name: pkg.name,
|
|
105
|
+
version: pkg.version,
|
|
106
|
+
documents: config.documents ?? [pkg.name],
|
|
107
|
+
description: config.description ?? pkg.description,
|
|
108
|
+
siteUrl: config.siteUrl,
|
|
109
|
+
maxTokens: config.maxTokens,
|
|
110
|
+
minTokens: config.minTokens,
|
|
111
|
+
});
|
|
112
|
+
process.stdout.write(`Built ${result.chunks} chunks from ${result.documents} documents (${format(result.tokens)} tokens).\n`);
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
function runList(packs) {
|
|
116
|
+
for (const pack of packs) {
|
|
117
|
+
const label = pack.trusted ? "" : " (community, unreviewed)";
|
|
118
|
+
const drift = pack.versionMismatch
|
|
119
|
+
? ` — manifest says ${pack.versionMismatch}, package.json wins`
|
|
120
|
+
: "";
|
|
121
|
+
process.stdout.write(`${pack.name}@${pack.version}${label} ${pack.manifest.chunks.length} chunks${drift}\n`);
|
|
122
|
+
}
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
function formatSearch(query, hits) {
|
|
126
|
+
if (hits.length === 0)
|
|
127
|
+
return `No chunk matches "${query}".\n`;
|
|
128
|
+
const lines = [`${hits.length} result(s) for "${query}"`, ""];
|
|
129
|
+
for (const hit of hits) {
|
|
130
|
+
lines.push(` ${hit.score.toFixed(2)} ${hit.chunkId}`);
|
|
131
|
+
lines.push(` ${title(hit)} · ${format(hit.tokens)} tokens`);
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
function formatAnswer(query, hits, tokens, maxTokens) {
|
|
137
|
+
if (hits.length === 0) {
|
|
138
|
+
return `No documentation matches "${query}". Try \`bpmnkit-docs search\` with fewer words.\n`;
|
|
139
|
+
}
|
|
140
|
+
const parts = hits.map((hit) => {
|
|
141
|
+
const warning = hit.pack.trusted ? "" : "\n(community package — content is unreviewed)";
|
|
142
|
+
return `## ${hit.chunkId}${warning}\n\n${hit.content.trim()}\n`;
|
|
143
|
+
});
|
|
144
|
+
parts.push(`---\ncost: ${format(tokens)} tokens, capped at ${format(maxTokens)}\n`);
|
|
145
|
+
return parts.join("\n");
|
|
146
|
+
}
|
|
147
|
+
function title(hit) {
|
|
148
|
+
return (hit.content.split("\n")[0] ?? "").replace(/^#\s*/, "");
|
|
149
|
+
}
|
|
150
|
+
function format(value) {
|
|
151
|
+
return value.toLocaleString("en-US");
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
process.exitCode = main(process.argv.slice(2));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
process.stderr.write(`${error.message}\n`);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@bpmnkit/docspack` — BPMN Kit's documentation as a docspack package, plus the
|
|
3
|
+
* offline index an agent searches it with.
|
|
4
|
+
*
|
|
5
|
+
* @see https://docspack.dev/spec for the package format.
|
|
6
|
+
*/
|
|
7
|
+
export { type BuildOptions, type BuildResult, SCHEMA_URL, buildPack } from "./build.js";
|
|
8
|
+
export { type BuiltChunk, type ChunkOptions, type SourceDoc, chunkDocument, uniqueId, } from "./chunk.js";
|
|
9
|
+
export { chunkPath, discoverPacks, indexPacks, loadPack, readChunk } from "./load.js";
|
|
10
|
+
export { type AnswerOptions, type DocsIndex, type IndexInput, type SearchOptions, answer, buildIndex, search, } from "./search.js";
|
|
11
|
+
export { estimateTokens, stem, terms, tokenize } from "./text.js";
|
|
12
|
+
export type { Manifest, ManifestChunk, Pack, SearchHit } from "./types.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@bpmnkit/docspack` — BPMN Kit's documentation as a docspack package, plus the
|
|
3
|
+
* offline index an agent searches it with.
|
|
4
|
+
*
|
|
5
|
+
* @see https://docspack.dev/spec for the package format.
|
|
6
|
+
*/
|
|
7
|
+
export { SCHEMA_URL, buildPack } from "./build.js";
|
|
8
|
+
export { chunkDocument, uniqueId, } from "./chunk.js";
|
|
9
|
+
export { chunkPath, discoverPacks, indexPacks, loadPack, readChunk } from "./load.js";
|
|
10
|
+
export { answer, buildIndex, search, } from "./search.js";
|
|
11
|
+
export { estimateTokens, stem, terms, tokenize } from "./text.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
package/dist/load.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding and reading documentation packages on disk.
|
|
3
|
+
*
|
|
4
|
+
* A manifest is third-party input that ends up in a model's context, so it is
|
|
5
|
+
* validated as a security boundary: a chunk path that leaves `.llms/` is refused,
|
|
6
|
+
* and the installed `package.json` version supersedes whatever the manifest claims.
|
|
7
|
+
*/
|
|
8
|
+
import { type DocsIndex } from "./search.js";
|
|
9
|
+
import type { ManifestChunk, Pack } from "./types.js";
|
|
10
|
+
/** Read one documentation package from its root directory. */
|
|
11
|
+
export declare function loadPack(dir: string): Pack;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a chunk's file inside `.llms/`, refusing any path that escapes it.
|
|
14
|
+
* The manifest is untrusted, so this is the only way a chunk path is turned
|
|
15
|
+
* into a real one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function chunkPath(packDir: string, chunk: ManifestChunk): string;
|
|
18
|
+
export declare function readChunk(pack: Pack, chunk: ManifestChunk): string;
|
|
19
|
+
/**
|
|
20
|
+
* Every documentation package reachable from `cwd`: the vendor-scoped
|
|
21
|
+
* `@<vendor>/docspack` and community `@docspack-community/*` packages installed
|
|
22
|
+
* in any `node_modules` up the tree, plus `cwd` itself when it is a pack.
|
|
23
|
+
*/
|
|
24
|
+
export declare function discoverPacks(cwd?: string): Pack[];
|
|
25
|
+
/** Load every chunk of every pack into a searchable index. */
|
|
26
|
+
export declare function indexPacks(packs: readonly Pack[]): DocsIndex;
|
|
27
|
+
//# sourceMappingURL=load.d.ts.map
|
package/dist/load.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding and reading documentation packages on disk.
|
|
3
|
+
*
|
|
4
|
+
* A manifest is third-party input that ends up in a model's context, so it is
|
|
5
|
+
* validated as a security boundary: a chunk path that leaves `.llms/` is refused,
|
|
6
|
+
* and the installed `package.json` version supersedes whatever the manifest claims.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { buildIndex } from "./search.js";
|
|
11
|
+
import { estimateTokens } from "./text.js";
|
|
12
|
+
const CHUNK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13
|
+
const COMMUNITY_SCOPE = "@docspack-community";
|
|
14
|
+
/** Read one documentation package from its root directory. */
|
|
15
|
+
export function loadPack(dir) {
|
|
16
|
+
const root = resolve(dir);
|
|
17
|
+
const pkg = readJson(join(root, "package.json"), "package.json");
|
|
18
|
+
const manifest = readJson(join(root, ".llms", "manifest.json"), "manifest");
|
|
19
|
+
if (typeof pkg.name !== "string" || typeof pkg.version !== "string") {
|
|
20
|
+
throw new Error(`${root}: package.json needs a "name" and a "version"`);
|
|
21
|
+
}
|
|
22
|
+
if (!Array.isArray(manifest.chunks)) {
|
|
23
|
+
throw new Error(`${root}: .llms/manifest.json needs a "chunks" array`);
|
|
24
|
+
}
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const chunk of manifest.chunks) {
|
|
27
|
+
if (!CHUNK_ID.test(chunk.id ?? "")) {
|
|
28
|
+
throw new Error(`${pkg.name}: chunk id ${JSON.stringify(chunk.id)} is not a valid id`);
|
|
29
|
+
}
|
|
30
|
+
if (seen.has(chunk.id))
|
|
31
|
+
throw new Error(`${pkg.name}: duplicate chunk id "${chunk.id}"`);
|
|
32
|
+
seen.add(chunk.id);
|
|
33
|
+
if (chunk.tokens !== undefined && (!Number.isInteger(chunk.tokens) || chunk.tokens < 1)) {
|
|
34
|
+
throw new Error(`${pkg.name}/${chunk.id}: "tokens" must be an integer of at least 1`);
|
|
35
|
+
}
|
|
36
|
+
chunkPath(root, chunk);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
name: pkg.name,
|
|
40
|
+
version: pkg.version,
|
|
41
|
+
dir: root,
|
|
42
|
+
trusted: !pkg.name.startsWith(`${COMMUNITY_SCOPE}/`),
|
|
43
|
+
manifest,
|
|
44
|
+
...(manifest.version && manifest.version !== pkg.version
|
|
45
|
+
? { versionMismatch: manifest.version }
|
|
46
|
+
: {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a chunk's file inside `.llms/`, refusing any path that escapes it.
|
|
51
|
+
* The manifest is untrusted, so this is the only way a chunk path is turned
|
|
52
|
+
* into a real one.
|
|
53
|
+
*/
|
|
54
|
+
export function chunkPath(packDir, chunk) {
|
|
55
|
+
const base = resolve(packDir, ".llms");
|
|
56
|
+
const target = resolve(base, chunk.file ?? "");
|
|
57
|
+
const inside = relative(base, target);
|
|
58
|
+
if (inside === "" || inside.startsWith("..") || inside.startsWith(`..${sep}`)) {
|
|
59
|
+
throw new Error(`${chunk.id}: chunk file "${chunk.file}" escapes .llms/`);
|
|
60
|
+
}
|
|
61
|
+
return target;
|
|
62
|
+
}
|
|
63
|
+
export function readChunk(pack, chunk) {
|
|
64
|
+
return readFileSync(chunkPath(pack.dir, chunk), "utf8");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Every documentation package reachable from `cwd`: the vendor-scoped
|
|
68
|
+
* `@<vendor>/docspack` and community `@docspack-community/*` packages installed
|
|
69
|
+
* in any `node_modules` up the tree, plus `cwd` itself when it is a pack.
|
|
70
|
+
*/
|
|
71
|
+
export function discoverPacks(cwd = process.cwd()) {
|
|
72
|
+
const found = new Map();
|
|
73
|
+
const offer = (dir) => {
|
|
74
|
+
try {
|
|
75
|
+
const pack = loadPack(dir);
|
|
76
|
+
if (!found.has(pack.name))
|
|
77
|
+
found.set(pack.name, pack);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Not a documentation package, or one we must not read. Skip it.
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
offer(cwd);
|
|
84
|
+
for (const modules of nodeModulesChain(resolve(cwd))) {
|
|
85
|
+
for (const scope of listDirectory(modules)) {
|
|
86
|
+
if (!scope.startsWith("@"))
|
|
87
|
+
continue;
|
|
88
|
+
if (scope === COMMUNITY_SCOPE) {
|
|
89
|
+
for (const name of listDirectory(join(modules, scope)))
|
|
90
|
+
offer(join(modules, scope, name));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
offer(join(modules, scope, "docspack"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [...found.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
98
|
+
}
|
|
99
|
+
/** Load every chunk of every pack into a searchable index. */
|
|
100
|
+
export function indexPacks(packs) {
|
|
101
|
+
const inputs = [];
|
|
102
|
+
for (const pack of packs) {
|
|
103
|
+
for (const chunk of pack.manifest.chunks) {
|
|
104
|
+
const content = readChunk(pack, chunk);
|
|
105
|
+
inputs.push({ pack, chunk, content, tokens: chunk.tokens ?? estimateTokens(content) });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return buildIndex(inputs);
|
|
109
|
+
}
|
|
110
|
+
function nodeModulesChain(from) {
|
|
111
|
+
const dirs = [];
|
|
112
|
+
let current = from;
|
|
113
|
+
for (;;) {
|
|
114
|
+
dirs.push(join(current, "node_modules"));
|
|
115
|
+
const parent = dirname(current);
|
|
116
|
+
if (parent === current)
|
|
117
|
+
return dirs;
|
|
118
|
+
current = parent;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function listDirectory(dir) {
|
|
122
|
+
try {
|
|
123
|
+
return readdirSync(dir).filter((name) => statSync(join(dir, name)).isDirectory());
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readJson(path, label) {
|
|
130
|
+
let raw;
|
|
131
|
+
try {
|
|
132
|
+
raw = readFileSync(path, "utf8");
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new Error(`${path}: ${label} not found`);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(raw);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw new Error(`${path}: ${label} is not valid JSON — ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=load.js.map
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The retrieval side: a BM25 index over chunk text, with tags and entities
|
|
3
|
+
* weighted above prose. Built in memory from the manifests — the corpus is a
|
|
4
|
+
* project's own documentation, not the whole registry, so there is nothing to
|
|
5
|
+
* persist and no database to keep in step.
|
|
6
|
+
*/
|
|
7
|
+
import type { ManifestChunk, Pack, SearchHit } from "./types.js";
|
|
8
|
+
interface IndexedChunk {
|
|
9
|
+
chunkId: string;
|
|
10
|
+
pack: Pack;
|
|
11
|
+
chunk: ManifestChunk;
|
|
12
|
+
tokens: number;
|
|
13
|
+
content: string;
|
|
14
|
+
frequencies: Map<string, number>;
|
|
15
|
+
length: number;
|
|
16
|
+
}
|
|
17
|
+
export interface DocsIndex {
|
|
18
|
+
chunks: IndexedChunk[];
|
|
19
|
+
documentFrequency: Map<string, number>;
|
|
20
|
+
averageLength: number;
|
|
21
|
+
}
|
|
22
|
+
/** One chunk's content, paired with the pack it came from. */
|
|
23
|
+
export interface IndexInput {
|
|
24
|
+
pack: Pack;
|
|
25
|
+
chunk: ManifestChunk;
|
|
26
|
+
content: string;
|
|
27
|
+
tokens: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildIndex(inputs: IndexInput[]): DocsIndex;
|
|
30
|
+
export interface SearchOptions {
|
|
31
|
+
/** Maximum hits to return. */
|
|
32
|
+
limit?: number;
|
|
33
|
+
/** Restrict to these package names — a project asks only about what it installed. */
|
|
34
|
+
packs?: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
/** Rank every chunk that shares a term with the query. Ties break on chunk id. */
|
|
37
|
+
export declare function search(index: DocsIndex, query: string, options?: SearchOptions): SearchHit[];
|
|
38
|
+
export interface AnswerOptions extends SearchOptions {
|
|
39
|
+
/** Hard ceiling on the tokens an answer may spend, counted before content. */
|
|
40
|
+
maxTokens?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The hits an agent gets back: the top matches that fit the token budget.
|
|
44
|
+
* The budget is spent from the manifest counts, so a chunk that would overrun
|
|
45
|
+
* it is dropped rather than truncated mid-sentence.
|
|
46
|
+
*/
|
|
47
|
+
export declare function answer(index: DocsIndex, query: string, options?: AnswerOptions): {
|
|
48
|
+
hits: SearchHit[];
|
|
49
|
+
tokens: number;
|
|
50
|
+
maxTokens: number;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=search.d.ts.map
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The retrieval side: a BM25 index over chunk text, with tags and entities
|
|
3
|
+
* weighted above prose. Built in memory from the manifests — the corpus is a
|
|
4
|
+
* project's own documentation, not the whole registry, so there is nothing to
|
|
5
|
+
* persist and no database to keep in step.
|
|
6
|
+
*/
|
|
7
|
+
import { terms } from "./text.js";
|
|
8
|
+
/** BM25 saturation and length-normalisation, at their conventional values. */
|
|
9
|
+
const K1 = 1.2;
|
|
10
|
+
const B = 0.75;
|
|
11
|
+
/** A tag is a deliberate index term; a word in a paragraph is incidental. */
|
|
12
|
+
const TAG_WEIGHT = 3;
|
|
13
|
+
const ENTITY_WEIGHT = 3;
|
|
14
|
+
export function buildIndex(inputs) {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
const documentFrequency = new Map();
|
|
17
|
+
for (const input of inputs) {
|
|
18
|
+
const frequencies = new Map();
|
|
19
|
+
const add = (values, weight) => {
|
|
20
|
+
for (const term of values)
|
|
21
|
+
frequencies.set(term, (frequencies.get(term) ?? 0) + weight);
|
|
22
|
+
};
|
|
23
|
+
const contentTerms = terms(input.content);
|
|
24
|
+
add(contentTerms, 1);
|
|
25
|
+
add(terms((input.chunk.tags ?? []).join(" ")), TAG_WEIGHT);
|
|
26
|
+
add(terms((input.chunk.entities ?? []).join(" ")), ENTITY_WEIGHT);
|
|
27
|
+
for (const term of frequencies.keys()) {
|
|
28
|
+
documentFrequency.set(term, (documentFrequency.get(term) ?? 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
chunks.push({
|
|
31
|
+
chunkId: `${input.pack.name}@${input.pack.version}/${input.chunk.id}`,
|
|
32
|
+
pack: input.pack,
|
|
33
|
+
chunk: input.chunk,
|
|
34
|
+
tokens: input.tokens,
|
|
35
|
+
content: input.content,
|
|
36
|
+
frequencies,
|
|
37
|
+
length: contentTerms.length,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const total = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
41
|
+
return {
|
|
42
|
+
chunks,
|
|
43
|
+
documentFrequency,
|
|
44
|
+
averageLength: chunks.length === 0 ? 1 : Math.max(1, total / chunks.length),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Rank every chunk that shares a term with the query. Ties break on chunk id. */
|
|
48
|
+
export function search(index, query, options = {}) {
|
|
49
|
+
const limit = options.limit ?? 3;
|
|
50
|
+
const queryTerms = terms(query);
|
|
51
|
+
if (queryTerms.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
const allowed = options.packs ? new Set(options.packs) : null;
|
|
54
|
+
const total = index.chunks.length;
|
|
55
|
+
const hits = [];
|
|
56
|
+
for (const candidate of index.chunks) {
|
|
57
|
+
if (allowed && !allowed.has(candidate.pack.name))
|
|
58
|
+
continue;
|
|
59
|
+
let score = 0;
|
|
60
|
+
for (const term of new Set(queryTerms)) {
|
|
61
|
+
const frequency = candidate.frequencies.get(term);
|
|
62
|
+
if (!frequency)
|
|
63
|
+
continue;
|
|
64
|
+
const documents = index.documentFrequency.get(term) ?? 0;
|
|
65
|
+
const idf = Math.log(1 + (total - documents + 0.5) / (documents + 0.5));
|
|
66
|
+
const norm = K1 * (1 - B + (B * candidate.length) / index.averageLength);
|
|
67
|
+
score += idf * ((frequency * (K1 + 1)) / (frequency + norm));
|
|
68
|
+
}
|
|
69
|
+
if (score <= 0)
|
|
70
|
+
continue;
|
|
71
|
+
hits.push({
|
|
72
|
+
chunkId: candidate.chunkId,
|
|
73
|
+
pack: candidate.pack,
|
|
74
|
+
chunk: candidate.chunk,
|
|
75
|
+
tokens: candidate.tokens,
|
|
76
|
+
content: candidate.content,
|
|
77
|
+
score,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
hits.sort((a, b) => b.score - a.score || a.chunkId.localeCompare(b.chunkId));
|
|
81
|
+
return hits.slice(0, limit);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The hits an agent gets back: the top matches that fit the token budget.
|
|
85
|
+
* The budget is spent from the manifest counts, so a chunk that would overrun
|
|
86
|
+
* it is dropped rather than truncated mid-sentence.
|
|
87
|
+
*/
|
|
88
|
+
export function answer(index, query, options = {}) {
|
|
89
|
+
const maxTokens = options.maxTokens ?? 3000;
|
|
90
|
+
const limit = options.limit ?? 3;
|
|
91
|
+
const ranked = search(index, query, { ...options, limit: Math.max(limit * 4, limit) });
|
|
92
|
+
const selected = [];
|
|
93
|
+
let spent = 0;
|
|
94
|
+
for (const hit of ranked) {
|
|
95
|
+
if (selected.length >= limit)
|
|
96
|
+
break;
|
|
97
|
+
if (spent + hit.tokens > maxTokens)
|
|
98
|
+
continue;
|
|
99
|
+
selected.push(hit);
|
|
100
|
+
spent += hit.tokens;
|
|
101
|
+
}
|
|
102
|
+
return { hits: selected, tokens: spent, maxTokens };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=search.js.map
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Tokenising, stemming and token counting — shared by the builder and the index. */
|
|
2
|
+
/**
|
|
3
|
+
* The Porter (1980) stemmer, so a query for `authenticate` reaches a chunk that
|
|
4
|
+
* only ever says `authentication`. Both reduce to `authent`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stem(input: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Words a query and a chunk share no meaning through: indexing them costs space
|
|
9
|
+
* and matching on them ranks every chunk equally.
|
|
10
|
+
*/
|
|
11
|
+
export declare const STOP_WORDS: ReadonlySet<string>;
|
|
12
|
+
/** Split text into lowercase word tokens, keeping the dots inside `a.b` identifiers. */
|
|
13
|
+
export declare function tokenize(text: string): string[];
|
|
14
|
+
/** Tokenize, drop stop words, stem — the form both the index and a query use. */
|
|
15
|
+
export declare function terms(text: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Approximate the token count of a string. Deterministic and close enough to
|
|
18
|
+
* budget an answer against — docspack counts from the manifest before it reads
|
|
19
|
+
* any content, so this must not need the content to be tokenised twice.
|
|
20
|
+
*/
|
|
21
|
+
export declare function estimateTokens(text: string): number;
|
|
22
|
+
//# sourceMappingURL=text.d.ts.map
|