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/log-md-broken-links-exempt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkeep/open-knowledge": patch
---

The append-only change log (`log.md`) is no longer reported as having broken outbound links. An append-only audit trail legitimately references documents that were later moved or deleted, so a link in it that no longer resolves is expected history, not an authoring defect. Until now, appending an entry re-surfaced a broken-link warning on every write; the reserved log is now exempt from broken-link reporting. Matching is by basename and extension-insensitive, so the root `log.md`, a nested `wiki/log.md`, and `.mdx` variants all qualify, while ordinary documents that merely contain "log" in their name (`changelog`, `blog`, `logs/2026-01`) are unaffected.
28 changes: 28 additions & 0 deletions packages/server/src/backlink-index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type ExtractedWikiLink,
extractMarkdownLinksFromMarkdown,
extractWikiLinksFromMarkdown,
isAppendOnlyLogDoc,
resolveMarkdownHref,
} from './backlink-index.ts';
import { _resetDocExtensionsForTests } from './doc-extensions.ts';
Expand Down Expand Up @@ -1600,4 +1601,31 @@ describe('computeBrokenOutboundLinks', () => {
].join('\n');
expect(computeBrokenOutboundLinks(md, 'notes/a', new Set(), fileOracle([]))).toEqual([]);
});

test('the append-only log is exempt — never reports broken links', () => {
// Same markdown from a content doc would flag both as `no-such-doc`.
const md = 'See [[captures/gone]] and [old note](./external-sources/removed.md).';
expect(computeBrokenOutboundLinks(md, 'log', new Set())).toEqual([]);
expect(computeBrokenOutboundLinks(md, 'wiki/log', new Set())).toEqual([]);
expect(computeBrokenOutboundLinks(md, 'log.md', new Set())).toEqual([]);
});
});

describe('isAppendOnlyLogDoc', () => {
test('matches the reserved log by basename, extension-insensitive', () => {
expect(isAppendOnlyLogDoc('log')).toBe(true);
expect(isAppendOnlyLogDoc('log.md')).toBe(true);
expect(isAppendOnlyLogDoc('log.mdx')).toBe(true);
expect(isAppendOnlyLogDoc('wiki/log')).toBe(true);
expect(isAppendOnlyLogDoc('wiki/log.md')).toBe(true);
expect(isAppendOnlyLogDoc('LOG.md')).toBe(true);
});

test('does not match content docs that merely contain "log"', () => {
expect(isAppendOnlyLogDoc('articles/changelog')).toBe(false);
expect(isAppendOnlyLogDoc('blog')).toBe(false);
expect(isAppendOnlyLogDoc('logs/2026-01')).toBe(false);
expect(isAppendOnlyLogDoc('log/entry')).toBe(false);
expect(isAppendOnlyLogDoc('index')).toBe(false);
});
});
19 changes: 19 additions & 0 deletions packages/server/src/backlink-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,23 @@ export interface BrokenOutboundLink {
reason: BrokenLinkReason;
}

/**
* True for the OKF reserved change-history file (`log.md`).
*
* An append-only log legitimately references docs that were later moved or
* deleted, so a broken outbound link in it is expected, not an authoring
* defect — callers exempt it from broken-link reporting.
*
* Basename match, extension-insensitive: the log is scaffolded at varying
* paths (root `log.md`, nested `wiki/log.md`) and the docName may arrive with
* or without a `.md`/`.mdx` extension.
*/
export function isAppendOnlyLogDoc(sourceDocName: string): boolean {
const withoutExt = sourceDocName.replace(/\.mdx?$/i, '');
const base = withoutExt.slice(withoutExt.lastIndexOf('/') + 1);
return base.toLowerCase() === 'log';
}

/**
* Resolve a just-written doc's outbound internal links against the live set of
* docs that exist, and return the ones that don't resolve. This is the
Expand Down Expand Up @@ -766,6 +783,8 @@ export function computeBrokenOutboundLinks(
admittedDocs: Iterable<string>,
fileExists?: (contentRootRelativePath: string) => boolean,
): BrokenOutboundLink[] {
if (isAppendOnlyLogDoc(sourceDocName)) return [];

const admitted = admittedDocs instanceof Set ? admittedDocs : new Set(admittedDocs);

let body: string;
Expand Down