diff --git a/.changeset/lucky-pugs-repeat.md b/.changeset/lucky-pugs-repeat.md new file mode 100644 index 0000000..5d124b8 --- /dev/null +++ b/.changeset/lucky-pugs-repeat.md @@ -0,0 +1,5 @@ +--- +"@neuledge/context": patch +--- + +Only skip repo-meta filenames (`security`, `license`, `changelog`, `contributing`, and the rest) when the scan starts at the repository root. They were matched by basename at every depth, and also at the top of a `docs_path` folder, so an ordinary documentation page that happens to share one of those names was dropped without a word. `context add` on the forgejo docs lost `docs/admin/actions/security.md`, the only source in that repo for `container.valid_volumes`, and still reported success. `docker/docs` and `excalidraw/excalidraw` lose pages to the same rule. diff --git a/packages/context/src/git.test.ts b/packages/context/src/git.test.ts index 925b4fd..e21d61d 100644 --- a/packages/context/src/git.test.ts +++ b/packages/context/src/git.test.ts @@ -1,9 +1,13 @@ -import { describe, expect, it } from "vitest"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { findLatestStableVersion, isMissingRefError, isTransientGitError, parseMonorepoTag, + readLocalDocsFiles, } from "./git.js"; describe("isTransientGitError", () => { @@ -189,3 +193,52 @@ describe("findLatestStableVersion", () => { }); }); }); + +describe("readLocalDocsFiles — repo-meta filenames", () => { + let dir: string; + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), "ctx-localdocs-")); + }); + + afterEach(() => { + rmSync(dir, { recursive: true, force: true }); + }); + + const write = (rel: string, body: string): void => { + const full = join(dir, rel); + mkdirSync(join(full, ".."), { recursive: true }); + writeFileSync(full, body); + }; + + it("skips repo-meta files at the scan root", () => { + write("security.md", "# Security policy\n\nReport issues to us.\n"); + write("license.md", "# License\n\nMIT.\n"); + write("guide.md", "# Guide\n\nContent.\n"); + + const paths = readLocalDocsFiles(dir).map((f) => f.path); + + expect(paths).toEqual(["guide.md"]); + }); + + it("keeps a documentation page that merely shares a repo-meta filename", () => { + write( + "admin/actions/security.md", + "# Securing Actions\n\nThe default value of valid_volumes is an empty array.\n", + ); + + const paths = readLocalDocsFiles(dir).map((f) => f.path); + + expect(paths).toEqual(["admin/actions/security.md"]); + }); + + it("keeps a repo-meta filename at a docs root that is not the repo root", () => { + write("SECURITY.md", "# Security policy\n\nReport issues to us.\n"); + write("docs/security.md", "# Securing deployments\n\nSet valid_volumes.\n"); + write("docs/guide.md", "# Guide\n\nContent.\n"); + + const paths = readLocalDocsFiles(dir, { path: "docs" }).map((f) => f.path); + + expect(paths.sort()).toEqual(["docs/guide.md", "docs/security.md"]); + }); +}); diff --git a/packages/context/src/git.ts b/packages/context/src/git.ts index fa764d1..79b75a8 100755 --- a/packages/context/src/git.ts +++ b/packages/context/src/git.ts @@ -407,6 +407,8 @@ function loadGitignore(basePath: string): Ignore { export interface FindMarkdownOptions { /** Language filter: "all" includes everything, specific code (e.g., "en") includes only that locale */ lang?: string; + /** True when the scan starts at the repo root rather than inside a docs folder */ + atRepoRoot?: boolean; } /** @@ -474,7 +476,12 @@ function findMarkdownFiles( const matchingExt = DOCUMENTATION_EXTENSIONS.find((ext) => lowerName.endsWith(ext), ); - if (matchingExt) { + // Only at the repo root: these names mean repo housekeeping there, + // but anywhere in a docs tree they are ordinary pages — forgejo's + // docs/admin/actions/security.md documents Actions security, and was + // being dropped as if it were a SECURITY.md policy file. A docs + // folder is not the repo root even though the walk starts there. + if (matchingExt && basePath === "" && options.atRepoRoot) { const baseName = lowerName.slice(0, -matchingExt.length); if (IGNORED_FILES.has(baseName)) continue; } @@ -527,7 +534,10 @@ export function readLocalDocsFiles( // Load gitignore from repo root const ig = loadGitignore(basePath); - const markdownFiles = findMarkdownFiles(searchPath, ig, "", { lang }); + const markdownFiles = findMarkdownFiles(searchPath, ig, "", { + lang, + atRepoRoot: !docsPath, + }); const files: Array<{ path: string; content: string }> = []; const seenHashes = new Set();