Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-documents-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neuledge/context": minor
---

Report documentation ingestion outcomes with relative paths and reasons, distinguishing exclusions, duplicate content, read/parse failures, empty documents, and indexed documents. Add JSON reports and strict validation to documentation builds, preserving installed packages when validation rejects unexpected document loss.
31 changes: 31 additions & 0 deletions packages/context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,37 @@ context add ./my-project --name my-lib --pkg-version 2.0 --save ./packages/
context add ./packages/my-lib@2.0.db
```

Builds print an ingestion summary: discovered documentation files, selected files,
indexed documents and sections, plus exclusions, duplicates, read/parse failures,
and empty documents. To inspect paths and reasons, write a JSON report:

```bash
context add ./my-project --diagnostics ingestion.json
context add ./my-project --strict --diagnostics ingestion.json
```

`--strict` rejects unreadable files/directories, parser failures, and documents
that produce no sections before changing the installed package. Intentional
exclusions and duplicate content remain acceptable. The JSON report is also
written when strict validation fails. These options apply to documentation
builds, not installation of prebuilt `.db` files.

The report has `schemaVersion`, `summary`, and per-path `entries`. Discovered
files are documentation candidates actually visited within the selected source;
selected files exclude intentional exclusions and duplicates. Indexed sections
count only sections stored after deduplication. Pruned directories appear once
with their exclusion reason; their contents are not scanned or counted. Unrelated
source-code files are omitted. Git/local paths are relative to the repository
root, including an explicit docs path.

Library callers receive the report as `BuildResult.diagnostics`; the existing
`skippedFiles` field still counts only split/parse failures. To include source
selection and read outcomes, pass the same `IngestionDiagnostic[]` to
`readLocalDocsFiles(..., { diagnostics })` and
`buildPackage(..., { name, version, diagnostics, strict })`. Strict failures throw
`IngestionError`, whose `diagnostics` property contains the report. Library code
does not log diagnostics.

---

## :whale: Docker
Expand Down
94 changes: 84 additions & 10 deletions packages/context/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
renameSync,
statSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { createRequire } from "node:module";
import { homedir } from "node:os";
Expand Down Expand Up @@ -49,6 +50,11 @@ import {
NO_DOCUMENTATION_FOUND_MESSAGE,
SEARCH_PACKAGES_NAME_DESCRIPTION,
} from "./guidance.js";
import {
formatIngestionSummary,
type IngestionDiagnostic,
IngestionError,
} from "./ingestion.js";
import { fetchLinkedDocs } from "./llms-txt.js";
import {
type BuildResult,
Expand Down Expand Up @@ -353,6 +359,7 @@ async function addFromWebsite(

console.log(`Building package...`);
const result = buildPackage(outputPath, files, {
strict: options.strict,
name: packageName,
version: versionLabel,
sourceUrl: source,
Expand All @@ -364,7 +371,13 @@ async function addFromWebsite(
);
}

reportBuilt(result, packageName, versionLabel, outputPath);
reportBuilt(
result,
packageName,
versionLabel,
outputPath,
options.diagnostics,
);

if (options.save) {
savePackageCopy(outputPath, options.save, packageName, versionLabel);
Expand Down Expand Up @@ -414,6 +427,7 @@ async function addFromWebsite(

console.log(`Building package...`);
const result = buildPackage(outputPath, files, {
strict: options.strict,
name: packageName,
version: versionLabel,
sourceUrl: source,
Expand All @@ -425,7 +439,13 @@ async function addFromWebsite(
);
}

reportBuilt(result, packageName, versionLabel, outputPath);
reportBuilt(
result,
packageName,
versionLabel,
outputPath,
options.diagnostics,
);

// Save to custom path if specified
if (options.save) {
Expand Down Expand Up @@ -540,9 +560,16 @@ function reportBuilt(
name: string,
version: string,
outputPath: string,
diagnosticsPath?: string,
): void {
console.log(`✓ Built package: ${name}@${version}`);
console.log(`✓ Saved to ${outputPath}`);
console.log(formatIngestionSummary(result.diagnostics));
if (diagnosticsPath)
writeFileSync(
resolve(diagnosticsPath),
`${JSON.stringify(result.diagnostics, null, 2)}\n`,
);

if (result.skippedFiles > 0) {
console.log(
Expand Down Expand Up @@ -719,6 +746,8 @@ export interface AddFromGitOptions {
name?: string;
save?: string;
lang?: string;
strict?: boolean;
diagnostics?: string;
}

/**
Expand Down Expand Up @@ -895,17 +924,20 @@ async function addFromGitClone(
}

// Read all markdown files (filtered by language)
const diagnostics: IngestionDiagnostic[] = [];
const files = readLocalDocsFiles(tempDir, {
diagnostics,
path: docsPath,
lang: options.lang,
});
if (files.length === 0) {
throw new Error(
`No markdown files found${docsPath ? ` in ${docsPath}` : ""}. Use --path to specify or --lang all to include all languages.`,
throw new IngestionError(
`No documentation files found${docsPath ? ` in ${docsPath}` : ""}. Use --path to specify or --lang all to include all languages.`,
diagnostics,
);
}
console.log(
`✓ Found ${files.length} markdown files${options.lang ? ` (lang: ${options.lang})` : ""}`,
`✓ Found ${files.length} documentation files${options.lang ? ` (lang: ${options.lang})` : ""}`,
);

// Build the package
Expand All @@ -917,12 +949,20 @@ async function addFromGitClone(

console.log(`Building package...`);
const result = buildPackage(outputPath, files, {
strict: options.strict,
name: packageName,
version: versionLabel,
sourceUrl: url,
diagnostics,
});

reportBuilt(result, packageName, versionLabel, outputPath);
reportBuilt(
result,
packageName,
versionLabel,
outputPath,
options.diagnostics,
);

// Save to custom path if specified
if (options.save) {
Expand Down Expand Up @@ -974,17 +1014,20 @@ async function addFromLocalDir(
}

// Read all markdown files (filtered by language)
const diagnostics: IngestionDiagnostic[] = [];
const files = readLocalDocsFiles(dirPath, {
diagnostics,
path: docsPath,
lang: options.lang,
});
if (files.length === 0) {
throw new Error(
`No markdown files found${docsPath ? ` in ${docsPath}` : ""}. Use --path to specify or --lang all to include all languages.`,
throw new IngestionError(
`No documentation files found${docsPath ? ` in ${docsPath}` : ""}. Use --path to specify or --lang all to include all languages.`,
diagnostics,
);
}
console.log(
`✓ Found ${files.length} markdown files${options.lang ? ` (lang: ${options.lang})` : ""}`,
`✓ Found ${files.length} documentation files${options.lang ? ` (lang: ${options.lang})` : ""}`,
);

// Build the package
Expand All @@ -996,12 +1039,20 @@ async function addFromLocalDir(

console.log(`Building package...`);
const result = buildPackage(outputPath, files, {
strict: options.strict,
name: packageName,
version: versionLabel,
sourceUrl: dirPath,
diagnostics,
});

reportBuilt(result, packageName, versionLabel, outputPath);
reportBuilt(
result,
packageName,
versionLabel,
outputPath,
options.diagnostics,
);

// Save to custom path if specified
if (options.save) {
Expand Down Expand Up @@ -1029,6 +1080,11 @@ program
"<source>",
"Package source: local .db file, URL (.db), GitHub URL, git URL, website URL (auto-fetches llms.txt), or local directory",
)
.option("--strict", "Fail on unreadable, unparseable, or empty documentation")
.option(
"--diagnostics <file>",
"Write detailed ingestion diagnostics as JSON",
)
.option("--tag <tag>", "Git tag to checkout (for git repos)")
.option("--pkg-version <version>", "Custom version label")
.option("--path <path>", "Path to docs folder in repo/directory")
Expand All @@ -1048,10 +1104,20 @@ program
name?: string;
save?: string;
lang?: string;
strict?: boolean;
diagnostics?: string;
},
) => {
try {
const sourceType = detectSourceType(source);
if (
(options.strict || options.diagnostics) &&
(sourceType === "file" || sourceType === "url")
) {
throw new Error(
"Ingestion diagnostics and strict validation require a documentation source, not a prebuilt .db package.",
);
}

// Map pkgVersion to version for internal use
const internalOptions = {
Expand All @@ -1077,6 +1143,14 @@ program
break;
}
} catch (err) {
if (err instanceof IngestionError) {
console.error(formatIngestionSummary(err.diagnostics));
if (options.diagnostics)
writeFileSync(
resolve(options.diagnostics),
`${JSON.stringify(err.diagnostics, null, 2)}\n`,
);
}
console.error(`Error: ${err instanceof Error ? err.message : err}`);
process.exit(1);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/context/src/fixtures/ingestion/guide.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!doctype html>
<html><body>
<h1>HTML Guide</h1>
<h2>Configure HTML</h2>
<p>Configure the HTML service with this example:</p>
<pre><code class="language-sh">run-html-example --port 8081</code></pre>
</body></html>
9 changes: 9 additions & 0 deletions packages/context/src/fixtures/ingestion/guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Markdown Guide

## Configure Markdown

Configure the Markdown service with this example:

```sh
run-markdown-example --port 8080
```
11 changes: 11 additions & 0 deletions packages/context/src/fixtures/ingestion/guide.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
RST Guide
=========

Configure RST
-------------

Configure the RST service with this example:

.. code-block:: sh

run-rst-example --port 8082
Loading
Loading