Skip to content
Merged
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/lucky-pugs-repeat.md
Original file line number Diff line number Diff line change
@@ -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.
55 changes: 54 additions & 1 deletion packages/context/src/git.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -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"]);
});
});
14 changes: 12 additions & 2 deletions packages/context/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<string>();

Expand Down
Loading