docs(coding-agent): synced omp docs coverage
Added root omp docs for memory_edit, learn, manage_skill, generate_image, and tts, plus package-level coverage for user-facing README-only CLIs. Added a docs-index freshness check to package check and made gen:bundle generate and reset the docs embed itself. Fixes #3934
This commit is contained in:
@@ -1,40 +1,51 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Populate (or reset) the embedded harness documentation index for `omp://`.
|
||||
* Populate, check, or reset the embedded harness documentation index for `omp://`.
|
||||
*
|
||||
* `--generate` writes `src/internal-urls/docs-index.generated.txt` as two lines:
|
||||
* a plain JSON array of the sorted `docs/**\/*.md` file names, then a base64
|
||||
* gzip blob of the index-aligned doc bodies (`string[]`). Keeping the filename
|
||||
* list out of the blob lets the loader list docs without inflating it.
|
||||
* Compiled binaries and the prepacked npm bundle inline this (~0.5MB) instead of
|
||||
* the ~1.6MB raw map; `--reset` restores the checked-in empty placeholder so the
|
||||
* gzip blob of the index-aligned doc bodies (`string[]`). `--check` rebuilds
|
||||
* that payload from the real docs corpus and compares it to the embed when
|
||||
* present; the checked-in empty placeholder is accepted after verifying that a
|
||||
* fresh generated payload round-trips. `--reset` restores the placeholder so the
|
||||
* dev tree reads `docs/` from disk. Mirrors the stats / model-catalog embeds.
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
import { gzipSync } from "node:zlib";
|
||||
import { gunzipSync, gzipSync } from "node:zlib";
|
||||
import { Glob } from "bun";
|
||||
|
||||
const docsDir = path.resolve(import.meta.dir, "../../../docs");
|
||||
const outputPath = path.resolve(import.meta.dir, "../src/internal-urls/docs-index.generated.txt");
|
||||
const GENERATE_FLAG = "--generate";
|
||||
const RESET_FLAG = "--reset";
|
||||
const CHECK_FLAG = "--check";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const rel = path.relative(process.cwd(), outputPath);
|
||||
export interface DocsIndexPayload {
|
||||
/** Sorted `docs/**\/*.md` file names plus index-aligned bodies and embed text. */
|
||||
readonly files: readonly string[];
|
||||
readonly bodies: readonly string[];
|
||||
readonly payload: string;
|
||||
}
|
||||
|
||||
if (process.argv.includes(RESET_FLAG)) {
|
||||
await Bun.write(outputPath, "");
|
||||
console.log(`Reset ${rel}`);
|
||||
return;
|
||||
}
|
||||
export interface DecodedDocsIndexPayload {
|
||||
/** Sorted `docs/**\/*.md` file names decoded from an embed payload. */
|
||||
readonly files: readonly string[];
|
||||
/** Index-aligned Markdown bodies decoded from an embed payload. */
|
||||
readonly bodies: readonly string[];
|
||||
}
|
||||
|
||||
if (!process.argv.includes(GENERATE_FLAG)) {
|
||||
console.log(`Skipping ${rel}; pass ${GENERATE_FLAG} to embed docs (the dev tree reads docs/ from disk)`);
|
||||
return;
|
||||
}
|
||||
function isStringArray(value: unknown): value is string[] {
|
||||
return Array.isArray(value) && value.every(item => typeof item === "string");
|
||||
}
|
||||
|
||||
function fail(message: string): never {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
/** Build the exact two-line `omp://` docs embed from the source `docs/**\/*.md` corpus. */
|
||||
export async function buildDocsIndexPayload(): Promise<DocsIndexPayload> {
|
||||
const glob = new Glob("**/*.md");
|
||||
const files: string[] = [];
|
||||
for await (const relativePath of glob.scan(docsDir)) {
|
||||
@@ -42,15 +53,94 @@ async function main(): Promise<void> {
|
||||
}
|
||||
files.sort();
|
||||
|
||||
// Index-aligned bodies (Promise.all preserves order), kept separate from the
|
||||
// filename list so the loader can list docs without inflating the blob.
|
||||
const bodies = await Promise.all(files.map(file => Bun.file(path.join(docsDir, file)).text()));
|
||||
|
||||
const bodiesB64 = Buffer.from(gzipSync(Buffer.from(JSON.stringify(bodies)), { level: 9 })).toString("base64");
|
||||
// Two lines: plain filename array, then the base64 gzip blob.
|
||||
const payload = `${JSON.stringify(files)}\n${bodiesB64}`;
|
||||
await Bun.write(outputPath, payload);
|
||||
console.log(`Generated ${rel} (${files.length} docs, ${payload.length} bytes)`);
|
||||
return {
|
||||
files,
|
||||
bodies,
|
||||
payload: `${JSON.stringify(files)}\n${bodiesB64}`,
|
||||
};
|
||||
}
|
||||
|
||||
await main();
|
||||
/** Decode a populated docs embed payload into filenames and index-aligned Markdown bodies. */
|
||||
export function decodeDocsIndexPayload(embed: string): DecodedDocsIndexPayload | null {
|
||||
const newline = embed.indexOf("\n");
|
||||
if (newline === -1) return null;
|
||||
|
||||
const filenames: unknown = JSON.parse(embed.slice(0, newline));
|
||||
if (!isStringArray(filenames)) fail("Embedded docs index filename line is not a JSON string array.");
|
||||
|
||||
const inflated = gunzipSync(Buffer.from(embed.slice(newline + 1), "base64"));
|
||||
const bodies: unknown = JSON.parse(inflated.toString("utf8"));
|
||||
if (!isStringArray(bodies)) fail("Embedded docs index body blob is not a JSON string array.");
|
||||
|
||||
return { files: filenames, bodies };
|
||||
}
|
||||
|
||||
/** Assert that an embed payload is fresh against the current source docs payload. */
|
||||
export function assertDocsIndexFresh(embed: string, expected: DecodedDocsIndexPayload): void {
|
||||
const decoded =
|
||||
embed.length === 0 ? decodeDocsIndexPayload(buildPayloadText(expected)) : decodeDocsIndexPayload(embed);
|
||||
if (decoded === null) fail("Embedded docs index is malformed: missing newline separator.");
|
||||
if (decoded.files.length !== expected.files.length) {
|
||||
fail(`Embedded docs index has ${decoded.files.length} docs; source corpus has ${expected.files.length}.`);
|
||||
}
|
||||
if (decoded.bodies.length !== expected.bodies.length) {
|
||||
fail(`Embedded docs index has ${decoded.bodies.length} bodies; source corpus has ${expected.bodies.length}.`);
|
||||
}
|
||||
for (let i = 0; i < expected.files.length; i++) {
|
||||
if (decoded.files[i] !== expected.files[i]) {
|
||||
fail(
|
||||
`Embedded docs index filename mismatch at ${i}: ${decoded.files[i] ?? "<missing>"} !== ${expected.files[i]}.`,
|
||||
);
|
||||
}
|
||||
if (decoded.bodies[i] !== expected.bodies[i]) {
|
||||
fail(`Embedded docs index body mismatch for ${expected.files[i]}. Run \`bun run gen:docs\`.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildPayloadText(payload: DecodedDocsIndexPayload): string {
|
||||
const bodiesB64 = Buffer.from(gzipSync(Buffer.from(JSON.stringify(payload.bodies)), { level: 9 })).toString(
|
||||
"base64",
|
||||
);
|
||||
return `${JSON.stringify(payload.files)}\n${bodiesB64}`;
|
||||
}
|
||||
|
||||
async function checkDocsIndexFreshness(rel: string): Promise<void> {
|
||||
const current = await buildDocsIndexPayload();
|
||||
const embed = await Bun.file(outputPath).text();
|
||||
assertDocsIndexFresh(embed, current);
|
||||
process.stdout.write(`Docs index fresh for ${current.files.length} docs (${rel})\n`);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const rel = path.relative(process.cwd(), outputPath);
|
||||
|
||||
if (process.argv.includes(RESET_FLAG)) {
|
||||
await Bun.write(outputPath, "");
|
||||
process.stdout.write(`Reset ${rel}\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.argv.includes(CHECK_FLAG)) {
|
||||
await checkDocsIndexFreshness(rel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.argv.includes(GENERATE_FLAG)) {
|
||||
process.stdout.write(
|
||||
`Skipping ${rel}; pass ${GENERATE_FLAG} to embed docs (the dev tree reads docs/ from disk)\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const current = await buildDocsIndexPayload();
|
||||
assertDocsIndexFresh(current.payload, current);
|
||||
await Bun.write(outputPath, current.payload);
|
||||
process.stdout.write(`Generated ${rel} (${current.files.length} docs, ${current.payload.length} bytes)\n`);
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
await main();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user