diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index e8ffb56..ace04fa 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -27,10 +27,14 @@ const kbWritePathEnabled = ref(true);
// code embeddings sit on a much higher similarity floor than prose, so 0.55 let
// unrelated code through (#2223). Shares top-k, not the threshold.
const kbWritePathThreshold = ref("0.68");
-// Near-duplicate report floor. Deliberately looser than the 0.90 write-time
-// gate: that one BLOCKS a create and must be unforgiving of noise, this one only
-// suggests a merge the operator reviews (services/dedup.py).
-const kbDuplicateThreshold = ref("0.82");
+// Near-duplicate report floors, one per record kind (services/dedup.py).
+// Snippets are single-chunk, so their floor sits below the 0.90 write-time
+// gate and catches what it lets through. Notes/tasks are scored at chunk
+// grain (#280) — related families clear 0.90 easily — so their floor sits
+// above the gate to keep the report pointed at genuinely-alike records.
+const kbDupThresholdSnippet = ref("0.82");
+const kbDupThresholdNote = ref("0.93");
+const kbDupThresholdTask = ref("0.93");
const savingKbInject = ref(false);
const kbInjectSaved = ref(false);
@@ -76,16 +80,20 @@ async function saveRetention() {
async function saveKbInject() {
const t = Math.min(1, Math.max(0, Number(kbInjectThreshold.value) || 0));
const k = Math.min(10, Math.max(1, Math.floor(Number(kbInjectTopK.value) || 1)));
- // `|| 0.82` not `|| 0`: an unparseable value here should fall back to the
- // default, not to 0 — a 0 floor would report every snippet as a duplicate of
- // every other one.
- const dupT = Math.min(1, Math.max(0, Number(kbDuplicateThreshold.value) || 0.82));
- // Same `|| default` reasoning as dupT: falling back to 0 would surface every
+ // `|| default` not `|| 0`: an unparseable value here should fall back to the
+ // per-kind default, not to 0 — a 0 floor would report every record as a
+ // duplicate of every other one.
+ const dupSnip = Math.min(1, Math.max(0, Number(kbDupThresholdSnippet.value) || 0.82));
+ const dupNote = Math.min(1, Math.max(0, Number(kbDupThresholdNote.value) || 0.93));
+ const dupTask = Math.min(1, Math.max(0, Number(kbDupThresholdTask.value) || 0.93));
+ // Same `|| default` reasoning: falling back to 0 would surface every
// snippet in the corpus on every edit, which is the failure this knob fixes.
const wpT = Math.min(1, Math.max(0, Number(kbWritePathThreshold.value) || 0.68));
kbInjectThreshold.value = String(t);
kbInjectTopK.value = String(k);
- kbDuplicateThreshold.value = String(dupT);
+ kbDupThresholdSnippet.value = String(dupSnip);
+ kbDupThresholdNote.value = String(dupNote);
+ kbDupThresholdTask.value = String(dupTask);
kbWritePathThreshold.value = String(wpT);
savingKbInject.value = true;
kbInjectSaved.value = false;
@@ -99,7 +107,9 @@ async function saveKbInject() {
// measurements that split them.
kb_writepath_enabled: kbWritePathEnabled.value ? 'true' : 'false',
kb_writepath_threshold: String(wpT),
- kb_duplicate_threshold: String(dupT),
+ kb_duplicate_threshold_snippet: String(dupSnip),
+ kb_duplicate_threshold_note: String(dupNote),
+ kb_duplicate_threshold_task: String(dupTask),
});
kbInjectSaved.value = true;
setTimeout(() => (kbInjectSaved.value = false), 2000);
@@ -487,8 +497,14 @@ onMounted(async () => {
if (allSettings.kb_writepath_threshold !== undefined) {
kbWritePathThreshold.value = allSettings.kb_writepath_threshold;
}
- if (allSettings.kb_duplicate_threshold !== undefined) {
- kbDuplicateThreshold.value = allSettings.kb_duplicate_threshold;
+ if (allSettings.kb_duplicate_threshold_snippet !== undefined) {
+ kbDupThresholdSnippet.value = allSettings.kb_duplicate_threshold_snippet;
+ }
+ if (allSettings.kb_duplicate_threshold_note !== undefined) {
+ kbDupThresholdNote.value = allSettings.kb_duplicate_threshold_note;
+ }
+ if (allSettings.kb_duplicate_threshold_task !== undefined) {
+ kbDupThresholdTask.value = allSettings.kb_duplicate_threshold_task;
}
if (allSettings.notify_task_reminders !== undefined) {
notifyTaskReminders.value = allSettings.notify_task_reminders !== "false";
@@ -1267,10 +1283,10 @@ function formatUserDate(iso: string): string {
is for (#274). -->
-
+
+
+
+
+
+
+ The floor for the Knowledge page's note report. Notes are compared
+ section by section, so related records — a run of dev-logs, notes on
+ one topic — score high without being duplicates. Stricter than the
+ snippet floor on purpose; lower it to browse related families rather
+ than hunt true duplicates.
+
+
+
+
+
+
+
+ Same as the note floor, for the task report. Step tasks from different
+ milestones ("Verify on CI") legitimately resemble each other, so this
+ stays strict to keep the report pointed at work opened twice.
+