docspack 0.3.0 → 1.0.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/dist/agent.d.ts +48 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +243 -0
- package/dist/agent.js.map +1 -0
- package/dist/artifact.d.ts +32 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +78 -0
- package/dist/artifact.js.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +75 -9
- package/dist/build.js.map +1 -1
- package/dist/changed.d.ts +31 -0
- package/dist/changed.d.ts.map +1 -0
- package/dist/changed.js +71 -0
- package/dist/changed.js.map +1 -0
- package/dist/cli.js +131 -10
- package/dist/cli.js.map +1 -1
- package/dist/coverage.d.ts +35 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +64 -0
- package/dist/coverage.js.map +1 -0
- package/dist/db.d.ts +38 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +109 -6
- package/dist/db.js.map +1 -1
- package/dist/discovery.d.ts +14 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +31 -6
- package/dist/discovery.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +90 -3
- package/dist/doctor.js.map +1 -1
- package/dist/document.d.ts +2 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +7 -3
- package/dist/document.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +65 -4
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +3 -0
- package/dist/mcp.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +1 -0
- package/dist/preview.js.map +1 -1
- package/dist/search.d.ts +17 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +116 -21
- package/dist/search.js.map +1 -1
- package/dist/spec.d.ts +6 -0
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +12 -3
- package/dist/spec.js.map +1 -1
- package/dist/surface.d.ts +45 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +208 -0
- package/dist/surface.js.map +1 -0
- package/dist/sync.d.ts +8 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +50 -1
- package/dist/sync.js.map +1 -1
- package/dist/verify.d.ts +9 -0
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +55 -27
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +308 -0
- package/src/artifact.ts +110 -0
- package/src/build.ts +103 -10
- package/src/changed.ts +99 -0
- package/src/cli.ts +167 -10
- package/src/coverage.ts +96 -0
- package/src/db.ts +168 -7
- package/src/discovery.ts +40 -5
- package/src/doctor.ts +100 -4
- package/src/document.ts +13 -4
- package/src/help.ts +65 -4
- package/src/index.ts +30 -0
- package/src/mcp.ts +3 -0
- package/src/preview.ts +1 -0
- package/src/search.ts +158 -24
- package/src/spec.ts +21 -3
- package/src/surface.ts +265 -0
- package/src/sync.ts +66 -2
- package/src/verify.ts +57 -27
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring docspack into the agent tooling a project already uses.
|
|
3
|
+
*
|
|
4
|
+
* The documented setup is "paste two lines into AGENTS.md", which is manual, easy to forget, and
|
|
5
|
+
* drifts: a line pasted once says whatever docspack did at the time. This writes the same thing
|
|
6
|
+
* from the code that defines it, into a marked block that can be rewritten in place.
|
|
7
|
+
*/
|
|
8
|
+
export type SurfaceKind = "instructions" | "skill" | "hook" | "mcp";
|
|
9
|
+
export interface AgentFile {
|
|
10
|
+
/** Path relative to the project root. */
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly kind: SurfaceKind;
|
|
13
|
+
readonly contents: string;
|
|
14
|
+
readonly status: "create" | "update" | "unchanged";
|
|
15
|
+
/** Why this file is in the plan, for the line the CLI prints. */
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AgentPlan {
|
|
19
|
+
readonly files: readonly AgentFile[];
|
|
20
|
+
}
|
|
21
|
+
export interface AgentOptions {
|
|
22
|
+
readonly cwd: string;
|
|
23
|
+
/**
|
|
24
|
+
* The three surfaces beyond the instruction block. Left unset, each is inferred from what is
|
|
25
|
+
* already on disk, so `docspack agent check` needs no flags to say whether what a project
|
|
26
|
+
* chose is still current — which is the only form of the check CI can run.
|
|
27
|
+
*/
|
|
28
|
+
readonly feedback?: boolean;
|
|
29
|
+
/** Write a SessionStart hook that keeps the index in step with the lockfile. */
|
|
30
|
+
readonly hooks?: boolean;
|
|
31
|
+
/** Write the MCP server into `.mcp.json`. */
|
|
32
|
+
readonly mcp?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What to write, and where. Nothing is written for a tool the project does not use: the presence
|
|
36
|
+
* of `.claude/` is what says a Claude Code user works here, and a project with neither an
|
|
37
|
+
* AGENTS.md nor a CLAUDE.md gets AGENTS.md, which is the cross-tool convention.
|
|
38
|
+
*/
|
|
39
|
+
export declare function planAgentSetup(options: AgentOptions): Promise<AgentPlan>;
|
|
40
|
+
/** Writes a plan. A file that already carries the current text is left alone. */
|
|
41
|
+
export declare function applyAgentSetup(options: AgentOptions, plan: AgentPlan): Promise<readonly AgentFile[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Replaces the marked block, or appends one. Everything outside the markers is left exactly as
|
|
44
|
+
* it was: these files are written by people, and a tool that rewrites them wholesale is a tool
|
|
45
|
+
* nobody runs twice.
|
|
46
|
+
*/
|
|
47
|
+
export declare function withBlock(document: string, block: string): string;
|
|
48
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IACnD,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,6CAA6C;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAQD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAqD9E;AAED,iFAAiF;AACjF,wBAAsB,eAAe,CACnC,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAa/B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjE"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { DocspackError } from "./errors.js";
|
|
5
|
+
import { AGENTS_SNIPPET, FEEDBACK_SNIPPET } from "./snippet.js";
|
|
6
|
+
const START = "<!-- docspack:start -->";
|
|
7
|
+
const END = "<!-- docspack:end -->";
|
|
8
|
+
/** Files an agent reads as standing instructions, in the order they are checked. */
|
|
9
|
+
const INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
10
|
+
/**
|
|
11
|
+
* What to write, and where. Nothing is written for a tool the project does not use: the presence
|
|
12
|
+
* of `.claude/` is what says a Claude Code user works here, and a project with neither an
|
|
13
|
+
* AGENTS.md nor a CLAUDE.md gets AGENTS.md, which is the cross-tool convention.
|
|
14
|
+
*/
|
|
15
|
+
export async function planAgentSetup(options) {
|
|
16
|
+
const root = resolve(options.cwd);
|
|
17
|
+
if (!existsSync(join(root, "package.json"))) {
|
|
18
|
+
throw new DocspackError(`No package.json found in ${root}`, {
|
|
19
|
+
hint: "Run docspack from a project directory, or pass --cwd <dir>.",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const existing = INSTRUCTION_FILES.filter((name) => existsSync(join(root, name)));
|
|
23
|
+
const feedback = options.feedback ?? (await wantsFeedback(root, existing));
|
|
24
|
+
const files = [];
|
|
25
|
+
const block = instructionBlock(feedback);
|
|
26
|
+
const targets = existing.length > 0 ? existing : ["AGENTS.md"];
|
|
27
|
+
for (const name of targets) {
|
|
28
|
+
const before = await read(join(root, name));
|
|
29
|
+
const after = withBlock(before ?? "", block);
|
|
30
|
+
files.push({
|
|
31
|
+
path: name,
|
|
32
|
+
kind: "instructions",
|
|
33
|
+
contents: after,
|
|
34
|
+
status: before === undefined ? "create" : after === before ? "unchanged" : "update",
|
|
35
|
+
reason: before === undefined
|
|
36
|
+
? "the cross-tool convention for standing agent instructions"
|
|
37
|
+
: "already read by the agents working here",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (existsSync(join(root, ".claude"))) {
|
|
41
|
+
const path = join(".claude", "skills", "docspack", "SKILL.md");
|
|
42
|
+
const before = await read(join(root, path));
|
|
43
|
+
const contents = skill(feedback);
|
|
44
|
+
files.push({
|
|
45
|
+
path,
|
|
46
|
+
kind: "skill",
|
|
47
|
+
contents,
|
|
48
|
+
status: before === undefined ? "create" : before === contents ? "unchanged" : "update",
|
|
49
|
+
// A skill's body is loaded when it is used; instructions are loaded every session. Both
|
|
50
|
+
// are written, because only one of them survives a session that never asks a question.
|
|
51
|
+
reason: "loaded on demand, so it costs nothing in a session that asks no questions",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const settings = await read(join(root, ".claude", "settings.json"));
|
|
55
|
+
const wantsHook = options.hooks ?? (settings ?? "").includes("docspack sync");
|
|
56
|
+
if (wantsHook)
|
|
57
|
+
files.push(await hook(root));
|
|
58
|
+
const mcpConfig = await read(join(root, ".mcp.json"));
|
|
59
|
+
const wantsMcp = options.mcp ?? /"docspack"\s*:/.test(mcpConfig ?? "");
|
|
60
|
+
if (wantsMcp)
|
|
61
|
+
files.push(await mcpEntry(root));
|
|
62
|
+
return { files };
|
|
63
|
+
}
|
|
64
|
+
/** Writes a plan. A file that already carries the current text is left alone. */
|
|
65
|
+
export async function applyAgentSetup(options, plan) {
|
|
66
|
+
const root = resolve(options.cwd);
|
|
67
|
+
const written = [];
|
|
68
|
+
for (const file of plan.files) {
|
|
69
|
+
if (file.status === "unchanged")
|
|
70
|
+
continue;
|
|
71
|
+
const target = join(root, file.path);
|
|
72
|
+
await mkdir(dirname(target), { recursive: true });
|
|
73
|
+
await writeFile(target, file.contents, "utf8");
|
|
74
|
+
written.push(file);
|
|
75
|
+
}
|
|
76
|
+
return written;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Replaces the marked block, or appends one. Everything outside the markers is left exactly as
|
|
80
|
+
* it was: these files are written by people, and a tool that rewrites them wholesale is a tool
|
|
81
|
+
* nobody runs twice.
|
|
82
|
+
*/
|
|
83
|
+
export function withBlock(document, block) {
|
|
84
|
+
const start = document.indexOf(START);
|
|
85
|
+
const end = document.indexOf(END);
|
|
86
|
+
if (start !== -1 && end > start) {
|
|
87
|
+
return `${document.slice(0, start)}${block}${document.slice(end + END.length)}`;
|
|
88
|
+
}
|
|
89
|
+
const body = document.trimEnd();
|
|
90
|
+
return body.length === 0 ? `${block}\n` : `${body}\n\n${block}\n`;
|
|
91
|
+
}
|
|
92
|
+
/** Whether a project already asked for the feedback half, read from what it has on disk. */
|
|
93
|
+
async function wantsFeedback(root, files) {
|
|
94
|
+
for (const name of files) {
|
|
95
|
+
if ((await read(join(root, name)))?.includes("docspack feedback add") === true)
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const skillFile = await read(join(root, ".claude", "skills", "docspack", "SKILL.md"));
|
|
99
|
+
return skillFile?.includes("docspack feedback add") === true;
|
|
100
|
+
}
|
|
101
|
+
function instructionBlock(feedback) {
|
|
102
|
+
const lines = [
|
|
103
|
+
START,
|
|
104
|
+
"## Documentation for this project's dependencies",
|
|
105
|
+
"",
|
|
106
|
+
...AGENTS_SNIPPET,
|
|
107
|
+
...(feedback ? ["", ...FEEDBACK_SNIPPET] : []),
|
|
108
|
+
"",
|
|
109
|
+
"<!-- Written by `docspack agent install`. Re-run it to update this block. -->",
|
|
110
|
+
END,
|
|
111
|
+
];
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The Claude Code skill. Its description is what decides whether the skill is reached for, and
|
|
116
|
+
* the body is loaded only once it is — so the description names the situations rather than the
|
|
117
|
+
* tool, and the body carries what the two-line instruction cannot afford to.
|
|
118
|
+
*/
|
|
119
|
+
function skill(feedback) {
|
|
120
|
+
return [
|
|
121
|
+
"---",
|
|
122
|
+
"name: docspack",
|
|
123
|
+
"description: >-",
|
|
124
|
+
" Answer questions about this project's installed dependencies from local, version-matched",
|
|
125
|
+
" documentation instead of memory. Use before writing or reviewing code that calls a",
|
|
126
|
+
" dependency, when an API's shape is uncertain, when an import or a signature may have",
|
|
127
|
+
" changed between versions, or when a name is not in any documentation at all.",
|
|
128
|
+
"allowed-tools: Bash",
|
|
129
|
+
"---",
|
|
130
|
+
"",
|
|
131
|
+
"# Documentation for installed dependencies",
|
|
132
|
+
"",
|
|
133
|
+
'Run `docspack ask "<question>"` for anything about a dependency of this project. It answers',
|
|
134
|
+
"from the versions in `node_modules`, offline, in about 100ms, and bounded to 3,000 tokens.",
|
|
135
|
+
"",
|
|
136
|
+
"Prefer it over recalling an API from training data. The installed version is usually newer",
|
|
137
|
+
"than the training data and the answer states which release it came from.",
|
|
138
|
+
"",
|
|
139
|
+
"## What the answer can tell you",
|
|
140
|
+
"",
|
|
141
|
+
"- A passage from a documentation package, headed by its chunk id and the version it documents.",
|
|
142
|
+
"- A declaration read from the installed build, when the question names something the",
|
|
143
|
+
" documentation never mentions. It carries the import to write and the signature as installed.",
|
|
144
|
+
"- A note saying the name is exported but documented nowhere. That is a real answer: it means",
|
|
145
|
+
" no prose exists, not that the search failed.",
|
|
146
|
+
"",
|
|
147
|
+
"## Other commands",
|
|
148
|
+
"",
|
|
149
|
+
"- `docspack changed <library>` — what a library's exports gained and lost between two",
|
|
150
|
+
" installed versions. Use it when an upgrade broke something, or before writing code against",
|
|
151
|
+
" a version newer than what you remember.",
|
|
152
|
+
"- `docspack list` — which packages are indexed here.",
|
|
153
|
+
"- `docspack sync` — index them. Run it if `ask` reports something installed but not indexed.",
|
|
154
|
+
...(feedback
|
|
155
|
+
? [
|
|
156
|
+
"",
|
|
157
|
+
"## Recording a problem",
|
|
158
|
+
"",
|
|
159
|
+
"If the documentation contradicts the installed package, record it:",
|
|
160
|
+
"",
|
|
161
|
+
"```bash",
|
|
162
|
+
'docspack feedback add --chunk <id> --kind <drift|incorrect|missing> --evidence "<claim>"',
|
|
163
|
+
"```",
|
|
164
|
+
"",
|
|
165
|
+
"Only claims that can be shown false. `incorrect` and `missing` also need `--expected`,",
|
|
166
|
+
"`--actual` and `--repro`, so record those only after running something. It appends to a",
|
|
167
|
+
"local file for a human to review and sends nothing.",
|
|
168
|
+
]
|
|
169
|
+
: []),
|
|
170
|
+
"",
|
|
171
|
+
].join("\n");
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A SessionStart hook, so the index follows the lockfile without anyone remembering to sync.
|
|
175
|
+
*
|
|
176
|
+
* `--quiet` keeps it silent: a hook that prints on every session start spends context on a
|
|
177
|
+
* message saying nothing changed.
|
|
178
|
+
*/
|
|
179
|
+
async function hook(root) {
|
|
180
|
+
const path = join(".claude", "settings.json");
|
|
181
|
+
const before = await read(join(root, path));
|
|
182
|
+
const settings = parseJson(before, path);
|
|
183
|
+
// Silent and never failing: a session must not break, or print, because an index is stale.
|
|
184
|
+
const command = "npx --no-install docspack sync --quiet >/dev/null 2>&1 || true";
|
|
185
|
+
const hooks = (settings.hooks ?? {});
|
|
186
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
187
|
+
const already = JSON.stringify(sessionStart).includes("docspack sync");
|
|
188
|
+
if (!already)
|
|
189
|
+
sessionStart.push({ hooks: [{ type: "command", command }] });
|
|
190
|
+
const contents = `${JSON.stringify({ ...settings, hooks: { ...hooks, SessionStart: sessionStart } }, null, 2)}\n`;
|
|
191
|
+
return {
|
|
192
|
+
path,
|
|
193
|
+
kind: "hook",
|
|
194
|
+
contents,
|
|
195
|
+
status: before === undefined ? "create" : before === contents ? "unchanged" : "update",
|
|
196
|
+
reason: "keeps the index in step with the lockfile without anyone remembering to sync",
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function mcpEntry(root) {
|
|
200
|
+
const path = ".mcp.json";
|
|
201
|
+
const before = await read(join(root, path));
|
|
202
|
+
const config = parseJson(before, path);
|
|
203
|
+
const servers = (config.mcpServers ?? {});
|
|
204
|
+
const contents = `${JSON.stringify({
|
|
205
|
+
...config,
|
|
206
|
+
mcpServers: {
|
|
207
|
+
...servers,
|
|
208
|
+
docspack: { command: "npx", args: ["-y", "docspack", "mcp"] },
|
|
209
|
+
},
|
|
210
|
+
}, null, 2)}\n`;
|
|
211
|
+
return {
|
|
212
|
+
path,
|
|
213
|
+
kind: "mcp",
|
|
214
|
+
contents,
|
|
215
|
+
status: before === undefined ? "create" : before === contents ? "unchanged" : "update",
|
|
216
|
+
reason: "a declared tool, for clients that would rather have one than a shell command",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function parseJson(raw, path) {
|
|
220
|
+
if (raw === undefined || raw.trim().length === 0)
|
|
221
|
+
return {};
|
|
222
|
+
try {
|
|
223
|
+
const parsed = JSON.parse(raw);
|
|
224
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
225
|
+
throw new Error("not an object");
|
|
226
|
+
return parsed;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new DocspackError(`${path} is not valid JSON`, {
|
|
230
|
+
hint: "Fix it, or move it aside and run this again.",
|
|
231
|
+
cause: error,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function read(path) {
|
|
236
|
+
try {
|
|
237
|
+
return await readFile(path, "utf8");
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IndexedChunk } from "./db.js";
|
|
2
|
+
/**
|
|
3
|
+
* A package derived from an installed library rather than published by its author.
|
|
4
|
+
*
|
|
5
|
+
* Half of a well-documented library's exported names are mentioned nowhere in its own
|
|
6
|
+
* documentation (see `docs/design/beyond-retrieval.md`), and almost all of them are declared in
|
|
7
|
+
* the type declarations sitting in `node_modules`. Those declarations are the answer a
|
|
8
|
+
* documentation site cannot serve, because the site is not the artifact.
|
|
9
|
+
*/
|
|
10
|
+
export interface ArtifactPackage {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly version: string;
|
|
14
|
+
readonly chunks: readonly IndexedChunk[];
|
|
15
|
+
/**
|
|
16
|
+
* Every exported name, mapped to the chunk carrying its declaration — or to an empty string
|
|
17
|
+
* when the package exports the name without declaring it anywhere this can read.
|
|
18
|
+
*
|
|
19
|
+
* Recording those too is what keeps `changed` honest: a name re-exported from a file with no
|
|
20
|
+
* declaration in it would otherwise look like a name the release removed.
|
|
21
|
+
*/
|
|
22
|
+
readonly symbols: ReadonlyMap<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Derives chunks from an installed library: one per exported name, carrying the import a caller
|
|
26
|
+
* writes and the declaration as the installed build states it.
|
|
27
|
+
*
|
|
28
|
+
* Returns nothing when the package ships no type declarations. That is the common case across a
|
|
29
|
+
* dependency tree and is not a problem worth reporting.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readArtifact(dir: string): Promise<ArtifactPackage | undefined>;
|
|
32
|
+
//# sourceMappingURL=artifact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact.d.ts","sourceRoot":"","sources":["../src/artifact.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAQD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAgDpF"}
|
package/dist/artifact.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { chunkId, estimateTokens, packageId } from "./spec.js";
|
|
4
|
+
import { declarationsIn, readPublicSurface } from "./surface.js";
|
|
5
|
+
/** Beyond this a package is generated machinery, and indexing it would drown the prose. */
|
|
6
|
+
const MAX_SYMBOLS = 2000;
|
|
7
|
+
/** How much of a declaration is carried. Long enough for a signature, short enough to rank. */
|
|
8
|
+
const DECLARATION_LIMIT = 400;
|
|
9
|
+
/**
|
|
10
|
+
* Derives chunks from an installed library: one per exported name, carrying the import a caller
|
|
11
|
+
* writes and the declaration as the installed build states it.
|
|
12
|
+
*
|
|
13
|
+
* Returns nothing when the package ships no type declarations. That is the common case across a
|
|
14
|
+
* dependency tree and is not a problem worth reporting.
|
|
15
|
+
*/
|
|
16
|
+
export async function readArtifact(dir) {
|
|
17
|
+
const surface = await readPublicSurface(dir);
|
|
18
|
+
if (surface === undefined || surface.name.length === 0 || surface.version.length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const id = packageId(surface.name, surface.version);
|
|
22
|
+
const declared = new Map();
|
|
23
|
+
const chunks = [];
|
|
24
|
+
const symbols = new Map();
|
|
25
|
+
for (const symbol of surface.symbols.values()) {
|
|
26
|
+
symbols.set(symbol.name, "");
|
|
27
|
+
if (chunks.length >= MAX_SYMBOLS)
|
|
28
|
+
continue;
|
|
29
|
+
let declarations = declared.get(symbol.file);
|
|
30
|
+
if (declarations === undefined) {
|
|
31
|
+
try {
|
|
32
|
+
declarations = declarationsIn(await readFile(join(dir, symbol.file), "utf8"), DECLARATION_LIMIT);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
declarations = new Map();
|
|
36
|
+
}
|
|
37
|
+
declared.set(symbol.file, declarations);
|
|
38
|
+
}
|
|
39
|
+
const declaration = declarations.get(symbol.name);
|
|
40
|
+
// A name that only appears in an `export { … }` list has no declaration to show. The import
|
|
41
|
+
// line alone would say nothing the name did not already say, so it is left out.
|
|
42
|
+
if (declaration === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
const content = render(symbol, declaration);
|
|
45
|
+
const chunk = chunkId(id, symbol.name);
|
|
46
|
+
chunks.push({
|
|
47
|
+
chunkId: chunk,
|
|
48
|
+
filePath: symbol.file,
|
|
49
|
+
tokens: estimateTokens(content),
|
|
50
|
+
content,
|
|
51
|
+
// The name is the tag, and tags are weighted 3× against prose in the ranking.
|
|
52
|
+
tags: [symbol.name, symbol.from],
|
|
53
|
+
});
|
|
54
|
+
symbols.set(symbol.name, chunk);
|
|
55
|
+
}
|
|
56
|
+
if (symbols.size === 0)
|
|
57
|
+
return undefined;
|
|
58
|
+
return { id, name: surface.name, version: surface.version, chunks, symbols };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What an agent receives. The import line is first because it is the part a caller gets wrong
|
|
62
|
+
* most often — a name that exists at a subpath nobody guessed is the same failure as a name that
|
|
63
|
+
* does not exist.
|
|
64
|
+
*/
|
|
65
|
+
function render(symbol, declaration) {
|
|
66
|
+
return [
|
|
67
|
+
`# ${symbol.name}`,
|
|
68
|
+
"",
|
|
69
|
+
`Declared by \`${symbol.from}\`. This is the installed build's own declaration, not prose.`,
|
|
70
|
+
"",
|
|
71
|
+
"```ts",
|
|
72
|
+
`import { ${symbol.name} } from "${symbol.from}";`,
|
|
73
|
+
"",
|
|
74
|
+
declaration.trim(),
|
|
75
|
+
"```",
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=artifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact.js","sourceRoot":"","sources":["../src/artifact.ts"],"names":[],"mappings":"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"}
|
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAgBvC,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,iGAAiG;IACjG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAoBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAsE5E;AAqrBD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBzD"}
|
package/dist/build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { join, relative, sep } from "node:path";
|
|
3
4
|
import { findEntry } from "@docspack/registry";
|
|
@@ -7,7 +8,7 @@ import { DocspackError } from "./errors.js";
|
|
|
7
8
|
import { htmlToMarkdown, isHtml } from "./html.js";
|
|
8
9
|
import { HttpClient } from "./http.js";
|
|
9
10
|
import { fetchableLinks, parseLlmsTxt } from "./llms-txt.js";
|
|
10
|
-
import { CHUNKS_DIR, estimateTokens, LLMS_DIR, MANIFEST_FILE, parseDocumentedLibrary, serializeManifest, } from "./spec.js";
|
|
11
|
+
import { CHUNKS_DIR, estimateTokens, LLMS_DIR, MANIFEST_FILE, parseDocumentedLibrary, parseManifest, serializeManifest, } from "./spec.js";
|
|
11
12
|
import { STOPWORDS } from "./stopwords.js";
|
|
12
13
|
const DEFAULT_MAX_CHUNK_TOKENS = 800;
|
|
13
14
|
const MARKDOWN = /\.(md|mdx|markdown)$/i;
|
|
@@ -31,8 +32,11 @@ export async function buildPackage(input) {
|
|
|
31
32
|
if (minTokens !== undefined && minTokens > maxTokens) {
|
|
32
33
|
throw new DocspackError(`minChunkTokens (${minTokens}) is larger than maxChunkTokens (${maxTokens})`, { hint: "A chunk cannot be required to be bigger than it is allowed to be." });
|
|
33
34
|
}
|
|
34
|
-
const { chunks, files } = chunkDocuments(documents, maxTokens, minTokens);
|
|
35
|
+
const { chunks, files, collisions } = chunkDocuments(documents, maxTokens, minTokens);
|
|
35
36
|
const llmsDir = join(options.out, LLMS_DIR);
|
|
37
|
+
// Read before the payload is removed: a chunk id is what `feedback add --chunk` pins and what
|
|
38
|
+
// an answer is headed with, so a rebuild that renames one silently orphans both.
|
|
39
|
+
warnings.push(...idWarnings(collisions, await departedChunkIds(llmsDir, chunks)));
|
|
36
40
|
await rm(llmsDir, { recursive: true, force: true });
|
|
37
41
|
await mkdir(join(llmsDir, CHUNKS_DIR), { recursive: true });
|
|
38
42
|
for (const file of files) {
|
|
@@ -155,6 +159,7 @@ async function collectFromDirectory(dir) {
|
|
|
155
159
|
origin: relativePath,
|
|
156
160
|
text: cleaned.text,
|
|
157
161
|
...(cleaned.tags.length === 0 ? {} : { tags: cleaned.tags }),
|
|
162
|
+
...(cleaned.documents.length === 0 ? {} : { documents: cleaned.documents }),
|
|
158
163
|
});
|
|
159
164
|
}
|
|
160
165
|
return documents;
|
|
@@ -174,6 +179,13 @@ async function collectFromSource(options, warnings) {
|
|
|
174
179
|
const parsed = parseLlmsTxt(index.text);
|
|
175
180
|
const documents = [];
|
|
176
181
|
const links = fetchableLinks(parsed).slice(0, options.pages ?? 50);
|
|
182
|
+
// Two links can name the same document. `fetchableLinks` already drops a repeated URL and a
|
|
183
|
+
// repeated fragment, but a site whose index links anchors as a query — `/v4?id=codecs` — gets
|
|
184
|
+
// one entry per anchor, and each one fetches the whole page again. Left alone that packages
|
|
185
|
+
// the same text dozens of times: an index mostly made of duplicates, which is slower to search
|
|
186
|
+
// and ranks worse, because copies of one page crowd out the rest of the corpus.
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
let duplicates = 0;
|
|
177
189
|
for (const link of links) {
|
|
178
190
|
options.onProgress?.(`fetching ${link.url}`);
|
|
179
191
|
try {
|
|
@@ -183,6 +195,12 @@ async function collectFromSource(options, warnings) {
|
|
|
183
195
|
warnings.push(`skipped ${link.url}: document was empty`);
|
|
184
196
|
continue;
|
|
185
197
|
}
|
|
198
|
+
const fingerprint = createHash("sha256").update(text).digest("hex");
|
|
199
|
+
if (seen.has(fingerprint)) {
|
|
200
|
+
duplicates += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
seen.add(fingerprint);
|
|
186
204
|
documents.push({
|
|
187
205
|
title: link.title.length > 0 ? link.title : (firstHeading(text) ?? link.url),
|
|
188
206
|
origin: link.url,
|
|
@@ -194,6 +212,9 @@ async function collectFromSource(options, warnings) {
|
|
|
194
212
|
warnings.push(`skipped ${link.url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
195
213
|
}
|
|
196
214
|
}
|
|
215
|
+
if (duplicates > 0) {
|
|
216
|
+
warnings.push(`skipped ${duplicates} of ${links.length} linked documents whose text repeated one already packaged`);
|
|
217
|
+
}
|
|
197
218
|
if (fetchableLinks(parsed).length > links.length) {
|
|
198
219
|
warnings.push(`packaged ${links.length} of ${fetchableLinks(parsed).length} linked documents (raise with --pages)`);
|
|
199
220
|
}
|
|
@@ -301,6 +322,7 @@ function renderOperation(method, path, operation, file) {
|
|
|
301
322
|
function chunkDocuments(documents, maxTokens, minTokens) {
|
|
302
323
|
const chunks = [];
|
|
303
324
|
const files = [];
|
|
325
|
+
const collisions = [];
|
|
304
326
|
const taken = new Set();
|
|
305
327
|
for (const document of documents) {
|
|
306
328
|
const text = collapseTablePadding(document.text);
|
|
@@ -310,7 +332,11 @@ function chunkDocuments(documents, maxTokens, minTokens) {
|
|
|
310
332
|
for (const section of sections) {
|
|
311
333
|
const directives = readDirectives(section.body);
|
|
312
334
|
const body = withoutDuplicateHeading(directives.body, section.heading);
|
|
313
|
-
const
|
|
335
|
+
const derived = chunkSlug(document.title, section.heading);
|
|
336
|
+
const id = uniqueId(derived, taken);
|
|
337
|
+
if (id !== derived)
|
|
338
|
+
collisions.push(derived);
|
|
339
|
+
const libraries = directives.documents.length > 0 ? directives.documents : document.documents;
|
|
314
340
|
const contents = `# ${section.heading}\n\n<!-- docspack: from ${document.origin} -->\n\n${body}\n`;
|
|
315
341
|
const file = `${CHUNKS_DIR}/${id}.md`;
|
|
316
342
|
files.push({ path: file, contents });
|
|
@@ -330,10 +356,46 @@ function chunkDocuments(documents, maxTokens, minTokens) {
|
|
|
330
356
|
...extractEntities(body),
|
|
331
357
|
]),
|
|
332
358
|
],
|
|
359
|
+
...(libraries === undefined || libraries.length === 0
|
|
360
|
+
? {}
|
|
361
|
+
: { documents: libraries.map(parseDocumentedLibrary) }),
|
|
333
362
|
});
|
|
334
363
|
}
|
|
335
364
|
}
|
|
336
|
-
return { chunks, files };
|
|
365
|
+
return { chunks, files, collisions };
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The two ways a chunk id moves without anyone deciding it should. Both are reported as one
|
|
369
|
+
* line each, however many ids are involved: a large package collides in dozens of places, and a
|
|
370
|
+
* warning per id is a wall of text nobody reads.
|
|
371
|
+
*/
|
|
372
|
+
function idWarnings(collisions, gone) {
|
|
373
|
+
const warnings = [];
|
|
374
|
+
if (collisions.length > 0) {
|
|
375
|
+
warnings.push(`${plural(collisions.length, "derived chunk id")} collided and ${collisions.length === 1 ? "was" : "were"} given a numeric suffix: ${list(collisions)} — which section keeps the bare id depends on the order the sources were read in`);
|
|
376
|
+
}
|
|
377
|
+
if (gone.length > 0) {
|
|
378
|
+
warnings.push(`${plural(gone.length, "chunk id")} the previous build published ${gone.length === 1 ? "is" : "are"} gone: ${list(gone)} — recorded feedback and published links pinned to ${gone.length === 1 ? "it" : "them"} no longer resolve`);
|
|
379
|
+
}
|
|
380
|
+
return warnings;
|
|
381
|
+
}
|
|
382
|
+
const plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
383
|
+
const list = (ids) => `${ids.slice(0, 5).join(", ")}${ids.length > 5 ? `, and ${ids.length - 5} more` : ""}`;
|
|
384
|
+
/**
|
|
385
|
+
* Chunk ids the payload on disk carries that this build does not, or nothing when there is no
|
|
386
|
+
* previous payload to compare with. An unreadable manifest is not a finding: `build` is what
|
|
387
|
+
* replaces it.
|
|
388
|
+
*/
|
|
389
|
+
async function departedChunkIds(llmsDir, chunks) {
|
|
390
|
+
let previous;
|
|
391
|
+
try {
|
|
392
|
+
previous = parseManifest(JSON.parse(await readFile(join(llmsDir, MANIFEST_FILE), "utf8")), `${LLMS_DIR}/${MANIFEST_FILE}`);
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
const current = new Set(chunks.map((chunk) => chunk.id));
|
|
398
|
+
return previous.chunks.map((chunk) => chunk.id).filter((id) => !current.has(id));
|
|
337
399
|
}
|
|
338
400
|
/**
|
|
339
401
|
* Splits Markdown at `##` headings, then `###`, then paragraphs, until every piece fits the token
|
|
@@ -487,23 +549,27 @@ function matchAll(text, pattern) {
|
|
|
487
549
|
return found;
|
|
488
550
|
}
|
|
489
551
|
/** One authored override under a heading: `<!-- docspack: tags=grid,columns -->`. */
|
|
490
|
-
const DIRECTIVE = /^[ \t]*<!--[ \t]*docspack:[ \t]*(tags|entities)[ \t]*=([^>]*?)-->[ \t]*$/gim;
|
|
552
|
+
const DIRECTIVE = /^[ \t]*<!--[ \t]*docspack:[ \t]*(tags|entities|documents)[ \t]*=([^>]*?)-->[ \t]*$/gim;
|
|
491
553
|
/**
|
|
492
554
|
* Reads per-section directives and removes them from the body. Front matter belongs to a whole
|
|
493
555
|
* document, so a page of eighty sections cannot aim any one of them; this is that lever.
|
|
494
556
|
*/
|
|
495
557
|
function readDirectives(body) {
|
|
496
|
-
const
|
|
497
|
-
const entities = [];
|
|
558
|
+
const collected = { tags: [], entities: [], documents: [] };
|
|
498
559
|
const text = body.replace(DIRECTIVE, (_line, key, value) => {
|
|
499
560
|
const values = value
|
|
500
561
|
.split(",")
|
|
501
562
|
.map((item) => item.trim())
|
|
502
563
|
.filter((item) => item.length > 0);
|
|
503
|
-
|
|
564
|
+
collected[key.toLowerCase()]?.push(...values);
|
|
504
565
|
return "";
|
|
505
566
|
});
|
|
506
|
-
return {
|
|
567
|
+
return {
|
|
568
|
+
body: text.replace(/\n{3,}/g, "\n\n").trim(),
|
|
569
|
+
tags: collected.tags ?? [],
|
|
570
|
+
entities: collected.entities ?? [],
|
|
571
|
+
documents: collected.documents ?? [],
|
|
572
|
+
};
|
|
507
573
|
}
|
|
508
574
|
/**
|
|
509
575
|
* Drops a source `# Title` that repeats the heading being written above it. A single-`H1`
|