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:
roboomp
2026-06-30 23:53:42 +00:00
parent ebdc7280cb
commit 3c3cb2a76b
14 changed files with 537 additions and 38 deletions
@@ -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();
}