refactor(completions): centralize content scanning

Add a browser-safe ContentRegistry owning path and inline content
stores, then derive completions from it. Remove the old block-processor,
directive-scanner, and link-source modules in favor of scanDoc,
deriveCompletions, and injectSparkDirectives, and register an inline
resolver for the frontend file index.
This commit is contained in:
hyper
2026-08-07 12:17:35 +08:00
parent 9e081891df
commit e8aa7165cb
13 changed files with 985 additions and 848 deletions
+8 -42
View File
@@ -16,49 +16,21 @@
*/
import { parse } from "csv-parse/browser/esm/sync";
import { FENCED_BLOCK_RE, parseBlockAttrs } from "./block-scanner.js";
/**
* Scan markdown content for ```csv role=declare blocks and return
* parsed declarations and tag modifiers. Shared between CLI and client.
*/
export function scanDeclareBlocks(content: string, filePath: string): DeclareResult {
const variables: VarDeclaration[] = [];
const tagModifiers: TagModifier[] = [];
FENCED_BLOCK_RE.lastIndex = 0;
let m: RegExpExecArray | null;
while ((m = FENCED_BLOCK_RE.exec(content)) !== null) {
const [, , infoString, body] = m;
const attrs = parseBlockAttrs(infoString);
if (attrs.role !== "declare") continue;
try {
const result = parseDeclareCsv(body, filePath);
variables.push(...result.variables);
tagModifiers.push(...result.tagModifiers);
} catch (e) {
console.warn(`[declare-parser] ${filePath}: ${e}`);
}
}
return { variables, tagModifiers };
}
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface VarDeclaration {
key: string; // "$hp" (always starts with $)
key: string; // "$hp" (always starts with $)
expression: string; // "$con*5+$mod_hp"
}
export interface TagModifier {
tag: string; // "#warrior"
target: string; // "$mod_hp"
tag: string; // "#warrior"
target: string; // "$mod_hp"
expression: string; // "20"
threshold: number; // minimum tagmap count to activate (default 1)
threshold: number; // minimum tagmap count to activate (default 1)
}
export interface DeclareResult {
@@ -104,14 +76,10 @@ export function parseDeclareCsv(csv: string, source: string): DeclareResult {
if (tag) {
// Tag modifier
if (!tag.startsWith("#")) {
throw new Error(
`${source}: tag must start with #, got "${tag}"`,
);
throw new Error(`${source}: tag must start with #, got "${tag}"`);
}
if (!key.startsWith("$")) {
throw new Error(
`${source}: key must start with $, got "${key}"`,
);
throw new Error(`${source}: key must start with $, got "${key}"`);
}
const thresholdRaw = row.threshold?.trim() ?? "";
const threshold = thresholdRaw ? parseInt(thresholdRaw, 10) : 1;
@@ -124,13 +92,11 @@ export function parseDeclareCsv(csv: string, source: string): DeclareResult {
} else {
// Variable declaration
if (!key.startsWith("$")) {
throw new Error(
`${source}: key must start with $, got "${key}"`,
);
throw new Error(`${source}: key must start with $, got "${key}"`);
}
variables.push({ key, expression: expr });
}
}
return { variables, tagModifiers };
}
}