fix(sources-footer): use canonical [N]: form per Lossless citation spec

Previous fix used [N] without the colon. Lossless-Citation-Spec.md mandates
the colon for reference-section identifiers (cite-wide reminders/spec line
68: "always use a `: ` after the citation identifier"). Cite-wide's parser
accepts both shapes, but the colon form is canonical and what other
Lossless tooling expects to see in checked-in files.

Verified the parser flow against cite-wide:
- REFDEF_NUM_RE (llmCitationParserService.ts:91) matches both `[N]` and `[N]:`
- reformatRefDefBodyAsMarkdownLink (line 547) skips wrapping when the body
  already contains a markdown link, so `[N]: [Title](URL)` passes through
  untouched into the hex-conversion stage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mpstaton 2026-05-09 16:35:32 -05:00
parent 3575dc168f
commit 22b26a271e

View file

@ -63,14 +63,17 @@ function wrapThinkBlocks(text: string): string {
function buildSourcesFooter(sources: PerplexitySource[]): string {
if (!sources.length) return '';
// Format matches cite-wide's REFDEF_NUM_RE (`[N] <content>`) so the
// existing "convert to hex citations" command can process this footer.
// See cite-wide/src/services/llmCitationParserService.ts.
// Canonical Lossless reference-section format per
// cite-wide/context-v/reminders/Lossless-Citation-Spec.md:
// "always use a `: ` after the citation identifier".
// cite-wide's parser (REFDEF_NUM_RE, line 91 of llmCitationParserService.ts)
// accepts both `[N]` and `[N]:` thanks to the `(:?)` group, but the colon
// form matches the spec, so emit it.
const lines = sources.map((s, i) => {
const n = i + 1;
const title = (typeof s.title === 'string' && s.title) ? s.title : (s.url ?? 'Source');
const url = s.url ?? '';
return url ? `[${n.toString()}] [${title}](${url})` : `[${n.toString()}] ${title}`;
return url ? `[${n.toString()}]: [${title}](${url})` : `[${n.toString()}]: ${title}`;
});
return '\n\n## Sources\n\n' + lines.join('\n') + '\n';
}