Enhance note adoption process with confidence evaluation and settings
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run

- Introduced a new function to evaluate adoption confidence based on file creation time and content criteria.
- Updated the adoption confirmation logic to determine when to show confirmation based on user settings.
- Added new settings for adoption confirmation mode and high-confidence time window in the settings interface.
- Improved the user experience by providing clearer options for managing note adoption behavior.
This commit is contained in:
Lars 2026-01-17 10:11:30 +01:00
parent 90eafb62f4
commit a6ce268b04
5 changed files with 255 additions and 6 deletions

View File

@ -39,6 +39,7 @@ import {
isAdoptCandidate, isAdoptCandidate,
matchesPendingHint, matchesPendingHint,
mergeFrontmatter, mergeFrontmatter,
evaluateAdoptionConfidence,
type PendingCreateHint, type PendingCreateHint,
} from "./unresolvedLink/adoptHelpers"; } from "./unresolvedLink/adoptHelpers";
import { AdoptNoteModal } from "./ui/AdoptNoteModal"; import { AdoptNoteModal } from "./ui/AdoptNoteModal";
@ -745,21 +746,31 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
return; return;
} }
// Check if matches pending hint (high confidence) // Evaluate confidence level
const highConfidence = matchesPendingHint(file, this.pendingCreateHint); const confidence = evaluateAdoptionConfidence(
file,
content,
this.settings.adoptMaxChars,
this.pendingCreateHint,
this.settings.highConfidenceWindowMs
);
if (this.settings.debugLogging) { if (this.settings.debugLogging) {
console.log(`[Mindnet] Adopt candidate detected: ${file.path}`, { console.log(`[Mindnet] Adopt candidate detected: ${file.path}`, {
highConfidence, confidence,
pendingHint: this.pendingCreateHint, pendingHint: this.pendingCreateHint,
fileCtime: file.stat.ctime,
timeSinceCreation: Date.now() - file.stat.ctime,
}); });
} }
// Clear pending hint (used once) // Clear pending hint (used once)
this.pendingCreateHint = null; this.pendingCreateHint = null;
// Show confirm modal if not high confidence // Determine if we need to show confirmation
if (!highConfidence) { const shouldShowConfirm = this.shouldShowAdoptionConfirm(confidence);
if (shouldShowConfirm) {
const adoptModal = new AdoptNoteModal(this.app, file.basename); const adoptModal = new AdoptNoteModal(this.app, file.basename);
const result = await adoptModal.show(); const result = await adoptModal.show();
if (!result.adopt) { if (!result.adopt) {
@ -797,6 +808,20 @@ export default class MindnetCausalAssistantPlugin extends Plugin {
} }
} }
/**
* Determine if adoption confirmation should be shown based on mode and confidence.
*/
private shouldShowAdoptionConfirm(confidence: "high" | "low"): boolean {
if (this.settings.adoptConfirmMode === "always") {
return true;
}
if (this.settings.adoptConfirmMode === "never") {
return false;
}
// onlyLowConfidence: show only for low confidence
return confidence === "low";
}
/** /**
* Adopt a note: convert to Mindnet format and start wizard. * Adopt a note: convert to Mindnet format and start wizard.
*/ */

View File

@ -16,6 +16,8 @@ export interface MindnetSettings {
debugLogging: boolean; // Enable debug logging for unresolved link handling debugLogging: boolean; // Enable debug logging for unresolved link handling
adoptNewNotesInEditor: boolean; // Auto-adopt newly created notes in editor adoptNewNotesInEditor: boolean; // Auto-adopt newly created notes in editor
adoptMaxChars: number; // Max content length to consider note as adopt-candidate adoptMaxChars: number; // Max content length to consider note as adopt-candidate
adoptConfirmMode: "always" | "onlyLowConfidence" | "never"; // When to show adoption confirmation
highConfidenceWindowMs: number; // Time window in ms for high-confidence adoption
// Semantic mapping builder settings // Semantic mapping builder settings
mappingWrapperCalloutType: string; // default: "abstract" mappingWrapperCalloutType: string; // default: "abstract"
mappingWrapperTitle: string; // default: "🕸️ Semantic Mapping" mappingWrapperTitle: string; // default: "🕸️ Semantic Mapping"
@ -44,6 +46,8 @@ export interface MindnetSettings {
debugLogging: false, debugLogging: false,
adoptNewNotesInEditor: true, adoptNewNotesInEditor: true,
adoptMaxChars: 200, adoptMaxChars: 200,
adoptConfirmMode: "onlyLowConfidence",
highConfidenceWindowMs: 3000,
mappingWrapperCalloutType: "abstract", mappingWrapperCalloutType: "abstract",
mappingWrapperTitle: "🕸️ Semantic Mapping", mappingWrapperTitle: "🕸️ Semantic Mapping",
mappingWrapperFolded: true, mappingWrapperFolded: true,

View File

@ -0,0 +1,145 @@
import { describe, it, expect } from "vitest";
import {
evaluateAdoptionConfidence,
type PendingCreateHint,
} from "../../unresolvedLink/adoptHelpers";
import { TFile } from "obsidian";
describe("evaluateAdoptionConfidence", () => {
it("returns high confidence for recent file with matching hint", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 1000 }, // 1 second ago
} as TFile;
const content = "Some content";
const hint: PendingCreateHint = {
basename: "test-note",
sourcePath: "source.md",
timestamp: Date.now() - 500,
};
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
expect(result).toBe("high");
});
it("returns low confidence for old file", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 10000 }, // 10 seconds ago
} as TFile;
const content = "Some content";
const hint: PendingCreateHint = {
basename: "test-note",
sourcePath: "source.md",
timestamp: Date.now() - 500,
};
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
expect(result).toBe("low");
});
it("returns low confidence for file with id", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 1000 },
} as TFile;
const content = `---
id: note_123
---
Body`;
const hint: PendingCreateHint = {
basename: "test-note",
sourcePath: "source.md",
timestamp: Date.now() - 500,
};
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
expect(result).toBe("low");
});
it("returns low confidence for file exceeding maxChars", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 1000 },
} as TFile;
const content = "x".repeat(300); // Exceeds maxChars
const hint: PendingCreateHint = {
basename: "test-note",
sourcePath: "source.md",
timestamp: Date.now() - 500,
};
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
expect(result).toBe("low");
});
it("returns high confidence for very recent file even without hint", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 500 }, // Very recent (0.5 seconds)
} as TFile;
const content = "Some content";
const result = evaluateAdoptionConfidence(file, content, 200, null, 3000);
expect(result).toBe("high");
});
it("returns low confidence for recent file without hint if not very recent", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 2500 }, // 2.5 seconds ago (not very recent)
} as TFile;
const content = "Some content";
const result = evaluateAdoptionConfidence(file, content, 200, null, 3000);
expect(result).toBe("low");
});
it("returns high confidence when hint matches even if file is slightly older", () => {
const file = {
basename: "test-note",
stat: { ctime: Date.now() - 2500 }, // 2.5 seconds ago
} as TFile;
const content = "Some content";
const hint: PendingCreateHint = {
basename: "test-note",
sourcePath: "source.md",
timestamp: Date.now() - 2000, // Hint is 2 seconds ago, within window
};
const result = evaluateAdoptionConfidence(file, content, 200, hint, 3000);
expect(result).toBe("high");
});
});
describe("adoptConfirmMode behavior", () => {
// Pure function to test confirm mode logic
function shouldShowAdoptionConfirm(
mode: "always" | "onlyLowConfidence" | "never",
confidence: "high" | "low"
): boolean {
if (mode === "always") {
return true;
}
if (mode === "never") {
return false;
}
// onlyLowConfidence: show only for low confidence
return confidence === "low";
}
it("always mode shows confirm for both high and low confidence", () => {
expect(shouldShowAdoptionConfirm("always", "high")).toBe(true);
expect(shouldShowAdoptionConfirm("always", "low")).toBe(true);
});
it("never mode never shows confirm", () => {
expect(shouldShowAdoptionConfirm("never", "high")).toBe(false);
expect(shouldShowAdoptionConfirm("never", "low")).toBe(false);
});
it("onlyLowConfidence mode shows confirm only for low confidence", () => {
expect(shouldShowAdoptionConfirm("onlyLowConfidence", "high")).toBe(false);
expect(shouldShowAdoptionConfirm("onlyLowConfidence", "low")).toBe(true);
});
});

View File

@ -347,6 +347,41 @@ export class MindnetSettingTab extends PluginSettingTab {
}) })
); );
// Adopt confirm mode
new Setting(containerEl)
.setName("Adopt confirm mode")
.setDesc("When to show adoption confirmation: always, onlyLowConfidence (skip for high-confidence), or never")
.addDropdown((dropdown) =>
dropdown
.addOption("always", "Always ask")
.addOption("onlyLowConfidence", "Only for low confidence")
.addOption("never", "Never ask")
.setValue(this.plugin.settings.adoptConfirmMode)
.onChange(async (value) => {
if (value === "always" || value === "onlyLowConfidence" || value === "never") {
this.plugin.settings.adoptConfirmMode = value;
await this.plugin.saveSettings();
}
})
);
// High confidence window
new Setting(containerEl)
.setName("High confidence window (ms)")
.setDesc("Time window in milliseconds for high-confidence adoption (default: 3000)")
.addText((text) =>
text
.setPlaceholder("3000")
.setValue(String(this.plugin.settings.highConfidenceWindowMs))
.onChange(async (value) => {
const numValue = parseInt(value, 10);
if (!isNaN(numValue) && numValue > 0) {
this.plugin.settings.highConfidenceWindowMs = numValue;
await this.plugin.saveSettings();
}
})
);
// Semantic Mapping Builder section // Semantic Mapping Builder section
containerEl.createEl("h2", { text: "Semantic Mapping Builder" }); containerEl.createEl("h2", { text: "Semantic Mapping Builder" });

View File

@ -38,7 +38,7 @@ export function isAdoptCandidate(
/** /**
* Check if a created file matches a pending create hint. * Check if a created file matches a pending create hint.
* Returns true if basename matches and within time window (3 seconds). * Returns true if basename matches and within time window.
*/ */
export function matchesPendingHint( export function matchesPendingHint(
file: TFile, file: TFile,
@ -57,6 +57,46 @@ export function matchesPendingHint(
); );
} }
/**
* Evaluate confidence level for adoption.
* Returns "high" if file is recently created and matches criteria, "low" otherwise.
*/
export function evaluateAdoptionConfidence(
file: TFile,
content: string,
maxChars: number,
hint: PendingCreateHint | null,
timeWindowMs: number
): "high" | "low" {
// Check if file is recently created (within time window)
const fileCtime = file.stat.ctime;
const timeSinceCreation = Date.now() - fileCtime;
const isRecent = timeSinceCreation >= 0 && timeSinceCreation <= timeWindowMs;
if (!isRecent) {
return "low";
}
// Check if adopt candidate (no id, content length OK)
if (!isAdoptCandidate(content, maxChars)) {
return "low";
}
// High confidence if pending hint matches (optional but recommended)
if (matchesPendingHint(file, hint, timeWindowMs)) {
return "high";
}
// Also high confidence if file is very recent (within window) and adopt candidate
// This handles cases where pending hint might not be set
if (isRecent && timeSinceCreation <= 2000) {
// Very recent (within 2 seconds) = high confidence
return "high";
}
return "low";
}
/** /**
* Merge new frontmatter into existing content. * Merge new frontmatter into existing content.
* Preserves existing frontmatter fields and body content. * Preserves existing frontmatter fields and body content.