Add configurable minimum sentence length for N+1 targets

This commit is contained in:
2026-02-15 18:34:10 -08:00
parent f1b5082801
commit 667bde944c
11 changed files with 180 additions and 9 deletions

View File

@@ -137,6 +137,55 @@ test("accepts valid ankiConnect n+1 behavior values", () => {
assert.equal(config.ankiConnect.nPlusOne.refreshMinutes, 120);
});
test("validates ankiConnect n+1 minimum sentence word count", () => {
const dir = makeTempDir();
fs.writeFileSync(
path.join(dir, "config.jsonc"),
`{
"ankiConnect": {
"nPlusOne": {
"minSentenceWords": 0
}
}
}`,
"utf-8",
);
const service = new ConfigService(dir);
const config = service.getConfig();
const warnings = service.getWarnings();
assert.equal(
config.ankiConnect.nPlusOne.minSentenceWords,
DEFAULT_CONFIG.ankiConnect.nPlusOne.minSentenceWords,
);
assert.ok(
warnings.some(
(warning) => warning.path === "ankiConnect.nPlusOne.minSentenceWords",
),
);
});
test("accepts valid ankiConnect n+1 minimum sentence word count", () => {
const dir = makeTempDir();
fs.writeFileSync(
path.join(dir, "config.jsonc"),
`{
"ankiConnect": {
"nPlusOne": {
"minSentenceWords": 4
}
}
}`,
"utf-8",
);
const service = new ConfigService(dir);
const config = service.getConfig();
assert.equal(config.ankiConnect.nPlusOne.minSentenceWords, 4);
});
test("validates ankiConnect n+1 match mode values", () => {
const dir = makeTempDir();
fs.writeFileSync(
@@ -328,5 +377,6 @@ test("template generator includes known keys", () => {
assert.match(output, /"nPlusOne"\s*:\s*\{/);
assert.match(output, /"nPlusOne": "#c6a0f6"/);
assert.match(output, /"knownWord": "#a6da95"/);
assert.match(output, /"minSentenceWords": 3/);
assert.match(output, /auto-generated from src\/config\/definitions.ts/);
});

View File

@@ -128,6 +128,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
refreshMinutes: 1440,
matchMode: "headword",
decks: [],
minSentenceWords: 3,
nPlusOne: "#c6a0f6",
knownWord: "#a6da95",
},
@@ -333,6 +334,13 @@ export const CONFIG_OPTION_REGISTRY: ConfigOptionRegistryEntry[] = [
defaultValue: DEFAULT_CONFIG.ankiConnect.nPlusOne.refreshMinutes,
description: "Minutes between known-word cache refreshes.",
},
{
path: "ankiConnect.nPlusOne.minSentenceWords",
kind: "number",
defaultValue: DEFAULT_CONFIG.ankiConnect.nPlusOne.minSentenceWords,
description:
"Minimum sentence word count required for N+1 targeting (default: 3).",
},
{
path: "ankiConnect.nPlusOne.decks",
kind: "array",

View File

@@ -698,6 +698,32 @@ export class ConfigService {
DEFAULT_CONFIG.ankiConnect.nPlusOne.refreshMinutes;
}
const nPlusOneMinSentenceWords = asNumber(
nPlusOneConfig.minSentenceWords,
);
const hasValidNPlusOneMinSentenceWords =
nPlusOneMinSentenceWords !== undefined &&
Number.isInteger(nPlusOneMinSentenceWords) &&
nPlusOneMinSentenceWords > 0;
if (nPlusOneMinSentenceWords !== undefined) {
if (hasValidNPlusOneMinSentenceWords) {
resolved.ankiConnect.nPlusOne.minSentenceWords =
nPlusOneMinSentenceWords;
} else {
warn(
"ankiConnect.nPlusOne.minSentenceWords",
nPlusOneConfig.minSentenceWords,
resolved.ankiConnect.nPlusOne.minSentenceWords,
"Expected a positive integer.",
);
resolved.ankiConnect.nPlusOne.minSentenceWords =
DEFAULT_CONFIG.ankiConnect.nPlusOne.minSentenceWords;
}
} else {
resolved.ankiConnect.nPlusOne.minSentenceWords =
DEFAULT_CONFIG.ankiConnect.nPlusOne.minSentenceWords;
}
const nPlusOneMatchMode = asString(nPlusOneConfig.matchMode);
const legacyNPlusOneMatchMode = asString(behavior.nPlusOneMatchMode);
const hasValidNPlusOneMatchMode =