fix(metadata): add reindexing prompts for inheritance settings and improve augmentor sync

- Add confirmation dialogs when file metadata inheritance settings change
- Sync inheritance settings to Augmentor on initialization and updates
- Fix formatting consistency across multiple modules
- Ensure immediate task index rebuild when inheritance behavior changes
This commit is contained in:
Quorafind 2025-09-08 22:48:37 +08:00
parent ff2d304157
commit 6c51119b36
8 changed files with 782 additions and 686 deletions

View file

@ -164,17 +164,17 @@ export function renderIndexSettingsTab(
});
const exampleFormats = [
{ format: "yyyy-MM-dd", example: "2025-08-16" },
{ format: "dd/MM/yyyy", example: "16/08/2025" },
{ format: "MM-dd-yyyy", example: "08-16-2025" },
{ format: "yyyy.MM.dd", example: "2025.08.16" },
{ format: "yyyyMMdd", example: "20250816" },
{ format: "yyyyMMdd_HHmmss", example: "20250816_144403" },
{ format: "yyyyMMddHHmmss", example: "20250816144403" },
{ format: "yyyy-MM-dd'T'HH:mm", example: "2025-08-16T14:44" },
{ format: "dd MMM yyyy", example: "16 Aug 2025" },
{ format: "MMM dd, yyyy", example: "Aug 16, 2025" },
{ format: "yyyy年MM月dd日", example: "2025年08月16日" },
{format: "yyyy-MM-dd", example: "2025-08-16"},
{format: "dd/MM/yyyy", example: "16/08/2025"},
{format: "MM-dd-yyyy", example: "08-16-2025"},
{format: "yyyy.MM.dd", example: "2025.08.16"},
{format: "yyyyMMdd", example: "20250816"},
{format: "yyyyMMdd_HHmmss", example: "20250816_144403"},
{format: "yyyyMMddHHmmss", example: "20250816144403"},
{format: "yyyy-MM-dd'T'HH:mm", example: "2025-08-16T14:44"},
{format: "dd MMM yyyy", example: "16 Aug 2025"},
{format: "MMM dd, yyyy", example: "Aug 16, 2025"},
{format: "yyyy年MM月dd日", example: "2025年08月16日"},
];
const table = examplesContainer.createEl("table", {
@ -182,13 +182,13 @@ export function renderIndexSettingsTab(
});
const headerRow = table.createEl("tr");
headerRow.createEl("th", { text: t("Format Pattern") });
headerRow.createEl("th", { text: t("Example") });
headerRow.createEl("th", {text: t("Format Pattern")});
headerRow.createEl("th", {text: t("Example")});
exampleFormats.forEach(({ format, example }) => {
exampleFormats.forEach(({format, example}) => {
const row = table.createEl("tr");
row.createEl("td", { text: format });
row.createEl("td", { text: example });
row.createEl("td", {text: format});
row.createEl("td", {text: example});
});
}
@ -202,23 +202,23 @@ export function renderIndexSettingsTab(
.setDesc(
isDataviewFormat
? t(
"Customize the prefix used for project tags in dataview format (e.g., 'project' for [project:: myproject]). Changes require reindexing."
)
"Customize the prefix used for project tags in dataview format (e.g., 'project' for [project:: myproject]). Changes require reindexing."
)
: t(
"Customize the prefix used for project tags (e.g., 'project' for #project/myproject). Changes require reindexing."
)
"Customize the prefix used for project tags (e.g., 'project' for #project/myproject). Changes require reindexing."
)
)
.addText((text) => {
text.setPlaceholder("project")
.setValue(
settingTab.plugin.settings.projectTagPrefix[
settingTab.plugin.settings.preferMetadataFormat
]
]
)
.onChange(async (value) => {
settingTab.plugin.settings.projectTagPrefix[
settingTab.plugin.settings.preferMetadataFormat
] = value || "project";
] = value || "project";
settingTab.applySettingsUpdate();
});
});
@ -229,23 +229,23 @@ export function renderIndexSettingsTab(
.setDesc(
isDataviewFormat
? t(
"Customize the prefix used for context tags in dataview format (e.g., 'context' for [context:: home]). Changes require reindexing."
)
"Customize the prefix used for context tags in dataview format (e.g., 'context' for [context:: home]). Changes require reindexing."
)
: t(
"Customize the prefix used for context tags (e.g., '@home' for @home). Changes require reindexing."
)
"Customize the prefix used for context tags (e.g., '@home' for @home). Changes require reindexing."
)
)
.addText((text) => {
text.setPlaceholder("context")
.setValue(
settingTab.plugin.settings.contextTagPrefix[
settingTab.plugin.settings.preferMetadataFormat
]
]
)
.onChange(async (value) => {
settingTab.plugin.settings.contextTagPrefix[
settingTab.plugin.settings.preferMetadataFormat
] = value || (isDataviewFormat ? "context" : "@");
] = value || (isDataviewFormat ? "context" : "@");
settingTab.applySettingsUpdate();
});
});
@ -464,10 +464,27 @@ export function renderIndexSettingsTab(
settingTab.plugin.settings.fileMetadataInheritance.enabled
)
.onChange(async (value) => {
settingTab.plugin.settings.fileMetadataInheritance.enabled =
value;
settingTab.plugin.settings.fileMetadataInheritance.enabled = value;
settingTab.applySettingsUpdate();
new ConfirmModal(settingTab.plugin, {
title: t("Reindex"),
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
confirmText: t("Reindex"),
cancelText: t("Cancel"),
onConfirm: async (confirmed: boolean) => {
if (!confirmed) return;
try {
new Notice(t("Clearing task cache and rebuilding index..."));
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
new Notice(t("Task index completely rebuilt"));
} catch (error) {
console.error("Failed to reindex after inheritance setting change:", error);
new Notice(t("Failed to reindex tasks"));
}
},
}).open();
setTimeout(() => {
settingTab.display();
}, 200);
@ -489,9 +506,26 @@ export function renderIndexSettingsTab(
.inheritFromFrontmatter
)
.onChange(async (value) => {
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatter =
value;
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatter = value;
settingTab.applySettingsUpdate();
new ConfirmModal(settingTab.plugin, {
title: t("Reindex"),
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
confirmText: t("Reindex"),
cancelText: t("Cancel"),
onConfirm: async (confirmed: boolean) => {
if (!confirmed) return;
try {
new Notice(t("Clearing task cache and rebuilding index..."));
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
new Notice(t("Task index completely rebuilt"));
} catch (error) {
console.error("Failed to reindex after inheritance setting change:", error);
new Notice(t("Failed to reindex tasks"));
}
},
}).open();
})
);
@ -509,9 +543,26 @@ export function renderIndexSettingsTab(
.inheritFromFrontmatterForSubtasks
)
.onChange(async (value) => {
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatterForSubtasks =
value;
settingTab.plugin.settings.fileMetadataInheritance.inheritFromFrontmatterForSubtasks = value;
settingTab.applySettingsUpdate();
new ConfirmModal(settingTab.plugin, {
title: t("Reindex"),
message: t("This change affects how tasks inherit metadata from files. Rebuild the index now so changes take effect immediately?"),
confirmText: t("Reindex"),
cancelText: t("Cancel"),
onConfirm: async (confirmed: boolean) => {
if (!confirmed) return;
try {
new Notice(t("Clearing task cache and rebuilding index..."));
await settingTab.plugin.dataflowOrchestrator?.onSettingsChange(["parser"]);
new Notice(t("Task index completely rebuilt"));
} catch (error) {
console.error("Failed to reindex after inheritance setting change:", error);
new Notice(t("Failed to reindex tasks"));
}
},
}).open();
})
);
}

View file

@ -67,7 +67,6 @@ export class DataflowOrchestrator {
constructor(
private app: App,
private vault: Vault,
private metadataCache: MetadataCache,
private plugin: any, // Plugin instance for parser access
@ -87,6 +86,13 @@ export class DataflowOrchestrator {
vault,
metadataCache,
});
// Initial sync of settings to Augmentor to ensure correct inheritance behavior on startup
try {
const initFmi = this.plugin?.settings?.fileMetadataInheritance;
this.augmentor.updateSettings({fileMetadataInheritance: initFmi});
} catch (e) {
console.warn('[DataflowOrchestrator][init] Failed to sync settings to Augmentor', e);
}
this.storage = this.repository.getStorage();
// Initialize FileFilterManager from settings early so sources get it
@ -145,9 +151,9 @@ export class DataflowOrchestrator {
focusHeading: this.plugin.settings.focusHeading || "",
fileParsingConfig: undefined,
fileMetadataInheritance:
this.plugin.settings.fileMetadataInheritance,
this.plugin.settings.fileMetadataInheritance,
enableCustomDateFormats:
this.plugin.settings.enableCustomDateFormats,
this.plugin.settings.enableCustomDateFormats,
customDateFormats: this.plugin.settings.customDateFormats,
// Include tag prefixes for custom dataview field support
projectTagPrefix: this.plugin.settings.projectTagPrefix,
@ -171,7 +177,7 @@ export class DataflowOrchestrator {
this.workerOrchestrator = new WorkerOrchestrator(
taskWorkerManager,
projectWorkerManager,
{ enableWorkerProcessing }
{enableWorkerProcessing}
);
// Initialize Obsidian event source
@ -183,28 +189,28 @@ export class DataflowOrchestrator {
// Initialize TimeParsingService with plugin settings
this.timeParsingService = new TimeParsingService(
this.plugin.settings?.timeParsing ||
({
enabled: true,
supportedLanguages: ["en", "zh"],
dateKeywords: {
start: ["start", "begin", "from"],
due: ["due", "deadline", "by", "until"],
scheduled: ["scheduled", "on", "at"],
},
removeOriginalText: true,
perLineProcessing: true,
realTimeReplacement: true,
timePatterns: {
singleTime: [],
timeRange: [],
rangeSeparators: ["-", "~", ""],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "AM",
midnightCrossing: "next-day",
},
} as EnhancedTimeParsingConfig)
({
enabled: true,
supportedLanguages: ["en", "zh"],
dateKeywords: {
start: ["start", "begin", "from"],
due: ["due", "deadline", "by", "until"],
scheduled: ["scheduled", "on", "at"],
},
removeOriginalText: true,
perLineProcessing: true,
realTimeReplacement: true,
timePatterns: {
singleTime: [],
timeRange: [],
rangeSeparators: ["-", "~", ""],
},
timeDefaults: {
preferredFormat: "24h",
defaultPeriod: "AM",
midnightCrossing: "next-day",
},
} as EnhancedTimeParsingConfig)
);
// Initialize FileSource (conditionally based on settings)
@ -403,7 +409,7 @@ export class DataflowOrchestrator {
// Listen for ICS events updates
this.eventRefs.push(
on(this.app, Events.ICS_EVENTS_UPDATED, async (payload: any) => {
const { events, seq } = payload;
const {events, seq} = payload;
console.log(
`[DataflowOrchestrator] ICS_EVENTS_UPDATED: ${
events?.length || 0
@ -420,7 +426,7 @@ export class DataflowOrchestrator {
// Listen for file updates from ObsidianSource
this.eventRefs.push(
on(this.app, Events.FILE_UPDATED, async (payload: any) => {
const { path, reason } = payload;
const {path, reason} = payload;
console.log(
`[DataflowOrchestrator] FILE_UPDATED event: ${path} (${reason})`
);
@ -444,7 +450,7 @@ export class DataflowOrchestrator {
// ObsidianSource uses FILE_UPDATED events instead
this.eventRefs.push(
on(this.app, Events.TASK_CACHE_UPDATED, async (payload: any) => {
const { changedFiles, sourceSeq } = payload;
const {changedFiles, sourceSeq} = payload;
// Skip if this is our own event (avoid infinite loop)
// Check sourceSeq to identify origin from our own processing
@ -484,7 +490,7 @@ export class DataflowOrchestrator {
this.app,
Events.WRITE_OPERATION_COMPLETE,
async (payload: any) => {
const { path, taskId } = payload;
const {path, taskId} = payload;
console.log(
`[DataflowOrchestrator] WRITE_OPERATION_COMPLETE: ${path}, taskId: ${taskId}`
);
@ -509,7 +515,7 @@ export class DataflowOrchestrator {
// Listen for direct task updates (from inline editing)
this.eventRefs.push(
on(this.app, Events.TASK_UPDATED, async (payload: any) => {
const { task } = payload;
const {task} = payload;
if (task) {
console.log(
`[DataflowOrchestrator] TASK_UPDATED: ${task.id} in ${task.filePath}`
@ -523,7 +529,7 @@ export class DataflowOrchestrator {
// Listen for task deletion events
this.eventRefs.push(
on(this.app, Events.TASK_DELETED, async (payload: any) => {
const { taskId, filePath, deletedTaskIds, mode } = payload;
const {taskId, filePath, deletedTaskIds, mode} = payload;
console.log(
`[DataflowOrchestrator] TASK_DELETED: ${taskId} in ${filePath}, mode: ${mode}, deleted: ${
deletedTaskIds?.length || 1
@ -551,7 +557,7 @@ export class DataflowOrchestrator {
if (this.fileSource) {
this.eventRefs.push(
on(this.app, Events.FILE_TASK_UPDATED, async (payload: any) => {
const { task } = payload;
const {task} = payload;
console.log(
`[DataflowOrchestrator] FILE_TASK_UPDATED: ${task?.filePath}`
);
@ -564,7 +570,7 @@ export class DataflowOrchestrator {
this.eventRefs.push(
on(this.app, Events.FILE_TASK_REMOVED, async (payload: any) => {
const { filePath } = payload;
const {filePath} = payload;
console.log(
`[DataflowOrchestrator] FILE_TASK_REMOVED: ${filePath}`
);
@ -643,13 +649,13 @@ export class DataflowOrchestrator {
// Apply inline filter even when using cached augmented tasks
const includeInlineCached = this.fileFilterManager
? this.fileFilterManager.shouldIncludePath(
filePath,
"inline"
)
filePath,
"inline"
)
: true;
console.log(
"[DataflowOrchestrator] Inline filter decision (cached augmented)",
{ filePath, includeInline: includeInlineCached }
{filePath, includeInline: includeInlineCached}
);
if (!includeInlineCached) {
augmentedTasks = [];
@ -677,13 +683,13 @@ export class DataflowOrchestrator {
);
const includeInlineReaugment = this.fileFilterManager
? this.fileFilterManager.shouldIncludePath(
filePath,
"inline"
)
filePath,
"inline"
)
: true;
console.log(
"[DataflowOrchestrator] Inline filter decision (re-augment cached raw)",
{ filePath, includeInline: includeInlineReaugment }
{filePath, includeInline: includeInlineReaugment}
);
rawTasks = includeInlineReaugment ? rawCached.data : [];
projectData = await this.projectResolver.get(filePath);
@ -706,27 +712,27 @@ export class DataflowOrchestrator {
try {
const taskWorkerManager = this.workerOrchestrator[
"taskWorkerManager"
] as TaskWorkerManager | undefined;
] as TaskWorkerManager | undefined;
if (taskWorkerManager) {
taskWorkerManager.updateSettings({
preferMetadataFormat:
this.plugin.settings.preferMetadataFormat ||
"tasks",
customDateFormats:
this.plugin.settings.customDateFormats,
this.plugin.settings.customDateFormats,
fileMetadataInheritance:
this.plugin.settings
.fileMetadataInheritance,
this.plugin.settings
.fileMetadataInheritance,
ignoreHeading:
this.plugin.settings.ignoreHeading,
this.plugin.settings.ignoreHeading,
focusHeading: this.plugin.settings.focusHeading,
// Include tag prefixes for custom dataview field support
projectTagPrefix:
this.plugin.settings.projectTagPrefix,
this.plugin.settings.projectTagPrefix,
contextTagPrefix:
this.plugin.settings.contextTagPrefix,
this.plugin.settings.contextTagPrefix,
areaTagPrefix:
this.plugin.settings.areaTagPrefix,
this.plugin.settings.areaTagPrefix,
});
}
} catch (e) {
@ -739,13 +745,13 @@ export class DataflowOrchestrator {
// Apply inline filter for parse path
const includeInlineParse = this.fileFilterManager
? this.fileFilterManager.shouldIncludePath(
filePath,
"inline"
)
filePath,
"inline"
)
: true;
console.log(
"[DataflowOrchestrator] Inline filter decision (parse path)",
{ filePath, includeInline: includeInlineParse }
{filePath, includeInline: includeInlineParse}
);
if (includeInlineParse) {
// Parse the file using workers (single-file path)
@ -861,6 +867,16 @@ export class DataflowOrchestrator {
this.timeParsingService.updateConfig(settings.timeParsing);
}
// Sync inheritance toggle to augmentor so it can respect disabling file frontmatter inheritance
try {
console.debug('[DataflowOrchestrator][updateSettings] fileMetadataInheritance =', settings.fileMetadataInheritance);
this.augmentor.updateSettings({
fileMetadataInheritance: settings.fileMetadataInheritance,
});
} catch (e) {
console.warn("[DataflowOrchestrator] Failed to sync settings to Augmentor", e);
}
// Update FileSource if needed
if (settings?.fileSource?.enabled && !this.fileSource) {
// Initialize FileSource if enabled but not yet created
@ -910,7 +926,7 @@ export class DataflowOrchestrator {
try {
const taskWorkerManager = this.workerOrchestrator?.[
"taskWorkerManager"
] as
] as
| import("./workers/TaskWorkerManager").TaskWorkerManager
| undefined;
if (taskWorkerManager) {
@ -954,7 +970,7 @@ export class DataflowOrchestrator {
const newEnabled: boolean = Boolean(settings?.fileFilter?.enabled);
const rulesCount = Array.isArray(settings?.fileFilter?.rules)
? settings.fileFilter.rules.filter((r: any) => r?.enabled)
.length
.length
: 0;
console.log("[TG Index Filter] settingsChange", {
enabled: newEnabled,
@ -1091,7 +1107,7 @@ export class DataflowOrchestrator {
);
console.log(
"[DataflowOrchestrator] restoreByFilter fallback candidates",
{ extra: inlineCandidates.length }
{extra: inlineCandidates.length}
);
} catch (e) {
console.warn(
@ -1124,7 +1140,7 @@ export class DataflowOrchestrator {
path,
augmented.data,
undefined,
{ forceEmit: true }
{forceEmit: true}
);
restoredFromAugmented++;
this.suppressedInline.delete(path);
@ -1183,7 +1199,7 @@ export class DataflowOrchestrator {
console.warn(
"[DataflowOrchestrator] restore inline failed",
{ path, e }
{path, e}
);
}
}
@ -1219,7 +1235,7 @@ export class DataflowOrchestrator {
} catch (e) {
console.warn(
"[DataflowOrchestrator] restore file-task emit failed",
{ path, e }
{path, e}
);
}
}
@ -1243,7 +1259,7 @@ export class DataflowOrchestrator {
*/
getWorkerStatus(): { enabled: boolean; metrics?: any } {
if (!this.workerOrchestrator) {
return { enabled: false };
return {enabled: false};
}
return {
@ -1342,16 +1358,16 @@ export class DataflowOrchestrator {
// Configure worker manager with plugin settings
const taskWorkerManager = this.workerOrchestrator[
"taskWorkerManager"
] as TaskWorkerManager;
] as TaskWorkerManager;
if (taskWorkerManager) {
taskWorkerManager.updateSettings({
preferMetadataFormat:
this.plugin.settings.preferMetadataFormat ||
"tasks",
customDateFormats:
this.plugin.settings.customDateFormats,
this.plugin.settings.customDateFormats,
fileMetadataInheritance:
this.plugin.settings.fileMetadataInheritance,
this.plugin.settings.fileMetadataInheritance,
projectConfig: this.plugin.settings.projectConfig,
ignoreHeading: this.plugin.settings.ignoreHeading,
focusHeading: this.plugin.settings.focusHeading,
@ -1418,9 +1434,9 @@ export class DataflowOrchestrator {
projectName: projectData?.tgProject?.name,
projectMeta: projectData
? {
...(projectData.enhancedMetadata || {}),
tgProject: projectData.tgProject, // Include tgProject in projectMeta
}
...(projectData.enhancedMetadata || {}),
tgProject: projectData.tgProject, // Include tgProject in projectMeta
}
: {},
tasks: rawTasks,
};
@ -1558,13 +1574,13 @@ export class DataflowOrchestrator {
// Apply file filter scope: skip inline parsing when scope === 'file'
const includeInline = this.fileFilterManager
? this.fileFilterManager.shouldIncludePath(
filePath,
"inline"
)
filePath,
"inline"
)
: true;
console.log(
"[DataflowOrchestrator] Inline filter decision",
{ filePath, includeInline }
{filePath, includeInline}
);
const rawTasks = includeInline
? await this.parseFile(file, projectData.tgProject)

View file

@ -101,7 +101,7 @@ export class WriteAPI {
try {
const task = await Promise.resolve(this.getTaskById(args.taskId));
if (!task) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Check if this is a Canvas task
@ -119,14 +119,14 @@ export class WriteAPI {
task.filePath
) as TFile;
if (!file) {
return { success: false, error: "File not found" };
return {success: false, error: "File not found"};
}
const content = await this.vault.read(file);
const lines = content.split("\n");
if (task.line < 0 || task.line >= lines.length) {
return { success: false, error: "Invalid line number" };
return {success: false, error: "Invalid line number"};
}
let taskLine = lines[task.line];
@ -172,17 +172,17 @@ export class WriteAPI {
// Trigger task-completed event if task was just completed
if (args.completed === true && !task.completed) {
const updatedTask = { ...task, completed: true };
const updatedTask = {...task, completed: true};
this.app.workspace.trigger(
"task-genius:task-completed",
updatedTask
);
}
return { success: true };
return {success: true};
} catch (error) {
console.error("WriteAPI: Error updating task status:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -197,7 +197,7 @@ export class WriteAPI {
this.getTaskById(args.taskId)
);
if (!originalTask) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Check if this is a Canvas task
@ -221,17 +221,17 @@ export class WriteAPI {
originalTask.filePath
) as TFile;
if (!file) {
return { success: false, error: "File not found" };
return {success: false, error: "File not found"};
}
const content = await this.vault.read(file);
const lines = content.split("\n");
if (originalTask.line < 0 || originalTask.line >= lines.length) {
return { success: false, error: "Invalid line number" };
return {success: false, error: "Invalid line number"};
}
const updatedTask = { ...originalTask, ...args.updates };
const updatedTask = {...originalTask, ...args.updates};
let taskLine = lines[originalTask.line];
// Update checkbox status or status mark
@ -306,7 +306,7 @@ export class WriteAPI {
};
// Emit task updated event for direct update in dataflow
emit(this.app, Events.TASK_UPDATED, { task: updatedTaskObj });
emit(this.app, Events.TASK_UPDATED, {task: updatedTaskObj});
// Trigger task-completed event if task was just completed
if (args.updates.completed === true && !originalTask.completed) {
@ -322,10 +322,10 @@ export class WriteAPI {
taskId: args.taskId,
});
return { success: true, task: updatedTaskObj };
return {success: true, task: updatedTaskObj};
} catch (error) {
console.error("WriteAPI: Error updating task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -342,7 +342,7 @@ export class WriteAPI {
originalTask.filePath
) as TFile;
if (!file) {
return { success: false, error: "File not found" };
return {success: false, error: "File not found"};
}
let newFilePath = originalTask.filePath;
@ -354,14 +354,6 @@ export class WriteAPI {
this.plugin.settings?.fileSource?.fileTaskProperties as any
)?.customContentField as string | undefined;
console.log("[WriteAPI][FileSource] updateFileSourceTask start", {
taskId,
contentSource,
preferFrontmatterTitle,
customContentField,
updates,
});
// Apply frontmatter updates for non-content fields (status, completed, metadata)
try {
const md = (updates.metadata ?? {}) as any;
@ -410,10 +402,6 @@ export class WriteAPI {
fsMapping.symbolToMetadata?.[statusToWrite];
if (mapped) {
statusToWrite = mapped;
console.log(
"[WriteAPI][FileSource] mapped via symbolToMetadata",
{ mapped }
);
} else {
console.log(
"[WriteAPI][FileSource] fallback mapping from taskStatuses",
@ -456,10 +444,6 @@ export class WriteAPI {
typeToMetadata[entry.type];
if (md) {
statusToWrite = md;
console.log(
"[WriteAPI][FileSource] mapped via taskStatuses",
{ type: entry.type, md }
);
}
break;
}
@ -483,7 +467,7 @@ export class WriteAPI {
(fm as any).priority = md.priority;
console.log(
"[WriteAPI][FileSource] wrote fm.priority",
{ priority: md.priority }
{priority: md.priority}
);
}
if (
@ -494,23 +478,14 @@ export class WriteAPI {
(fm as any).tags = Array.isArray(md.tags)
? md.tags
: typeof md.tags === "string"
? [md.tags]
: md.tags;
console.log("[WriteAPI][FileSource] wrote fm.tags", {
tags: (fm as any).tags,
});
? [md.tags]
: md.tags;
}
if (md.project !== undefined) {
(fm as any).project = md.project;
console.log("[WriteAPI][FileSource] wrote fm.project", {
project: md.project,
});
}
if (md.context !== undefined) {
(fm as any).context = md.context;
console.log("[WriteAPI][FileSource] wrote fm.context", {
context: md.context,
});
}
if (md.area !== undefined) {
(fm as any).area = md.area;
@ -528,7 +503,7 @@ export class WriteAPI {
(fm as any).startDate = formatDate(md.startDate);
console.log(
"[WriteAPI。][FileSource] wrote fm.startDate",
{ startDate: (fm as any).startDate }
{startDate: (fm as any).startDate}
);
}
if (md.scheduledDate !== undefined) {
@ -537,7 +512,7 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] wrote fm.scheduledDate",
{ scheduledDate: (fm as any).scheduledDate }
{scheduledDate: (fm as any).scheduledDate}
);
}
});
@ -553,7 +528,7 @@ export class WriteAPI {
"WriteAPI: Error updating file-source task frontmatter:",
error
);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
// Handle content/title change
@ -587,13 +562,13 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] wrote fm.title (branch: title)",
{ title: updates.content }
{title: updates.content}
);
const cacheAfter =
this.app.metadataCache.getFileCache(file);
console.log(
"[WriteAPI][FileSource] cache fm.title after write (branch: title)",
{ title: cacheAfter?.frontmatter?.title }
{title: cacheAfter?.frontmatter?.title}
);
} else {
newFilePath = await this.renameFile(
@ -602,7 +577,7 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] renamed file (branch: title)",
{ newFilePath }
{newFilePath}
);
}
break;
@ -635,7 +610,7 @@ export class WriteAPI {
field: customContentField,
value: cacheAfter?.frontmatter?.[
customContentField
],
],
}
);
} else if (preferFrontmatterTitle) {
@ -647,13 +622,13 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] wrote fm.title (branch: custom fallback)",
{ title: updates.content }
{title: updates.content}
);
const cacheAfter2 =
this.app.metadataCache.getFileCache(file);
console.log(
"[WriteAPI][FileSource] cache fm.title after write (branch: custom fallback)",
{ title: cacheAfter2?.frontmatter?.title }
{title: cacheAfter2?.frontmatter?.title}
);
} else {
newFilePath = await this.renameFile(
@ -662,7 +637,7 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] renamed file (branch: custom fallback)",
{ newFilePath }
{newFilePath}
);
}
break;
@ -678,13 +653,13 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] wrote fm.title (branch: filename/default)",
{ title: updates.content }
{title: updates.content}
);
const cacheAfter =
this.app.metadataCache.getFileCache(file);
console.log(
"[WriteAPI][FileSource] cache fm.title after write (branch: filename/default)",
{ title: cacheAfter?.frontmatter?.title }
{title: cacheAfter?.frontmatter?.title}
);
} else {
newFilePath = await this.renameFile(
@ -693,7 +668,7 @@ export class WriteAPI {
);
console.log(
"[WriteAPI][FileSource] renamed file (branch: filename/default)",
{ newFilePath }
{newFilePath}
);
}
break;
@ -710,7 +685,7 @@ export class WriteAPI {
"WriteAPI: Error updating file-source task content:",
error
);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -731,9 +706,9 @@ export class WriteAPI {
};
// Emit file-task update so repository updates fileTasks map directly
emit(this.app, Events.FILE_TASK_UPDATED, { task: updatedTaskObj });
emit(this.app, Events.FILE_TASK_UPDATED, {task: updatedTaskObj});
return { success: true, task: updatedTaskObj };
return {success: true, task: updatedTaskObj};
}
private async updateH1Heading(
@ -865,10 +840,10 @@ export class WriteAPI {
});
}
return { success: true };
return {success: true};
} catch (error) {
console.error("WriteAPI: Error creating task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -909,7 +884,7 @@ export class WriteAPI {
try {
const task = await Promise.resolve(this.getTaskById(args.taskId));
if (!task) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Check if this is a Canvas task
@ -921,7 +896,7 @@ export class WriteAPI {
task.filePath
) as TFile;
if (!file) {
return { success: false, error: "File not found" };
return {success: false, error: "File not found"};
}
// Collect all tasks to delete
@ -981,10 +956,10 @@ export class WriteAPI {
mode: args.deleteChildren ? "subtree" : "single",
});
return { success: true };
return {success: true};
} catch (error) {
console.error("WriteAPI: Error deleting task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1019,7 +994,7 @@ export class WriteAPI {
}
}
return { updated, failed };
return {updated, failed};
}
/**
@ -1063,7 +1038,7 @@ export class WriteAPI {
}
}
return { updated, failed };
return {updated, failed};
}
/**
@ -1084,7 +1059,7 @@ export class WriteAPI {
);
const result = await this.updateTask({
taskId,
updates: { content: newContent },
updates: {content: newContent},
});
if (result.success) {
@ -1097,7 +1072,7 @@ export class WriteAPI {
}
}
return { tasks: updatedTasks };
return {tasks: updatedTasks};
}
/**
@ -1110,7 +1085,7 @@ export class WriteAPI {
this.getTaskById(args.parentTaskId)
);
if (!parentTask) {
return { tasks: [] };
return {tasks: []};
}
const createdTasks: Task[] = [];
@ -1127,7 +1102,7 @@ export class WriteAPI {
}
}
return { tasks: createdTasks };
return {tasks: createdTasks};
}
/**
@ -1253,18 +1228,18 @@ export class WriteAPI {
}
// Notify about write operation
emit(this.app, Events.WRITE_OPERATION_START, { path: file.path });
emit(this.app, Events.WRITE_OPERATION_START, {path: file.path});
await this.vault.modify(file, newContent);
emit(this.app, Events.WRITE_OPERATION_COMPLETE, {
path: file.path,
});
return { success: true };
return {success: true};
} catch (error) {
console.error(
"WriteAPI: Error creating task in daily note:",
error
);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1337,13 +1312,13 @@ export class WriteAPI {
dailyNoteSettings: qc.dailyNoteSettings,
});
return { filePath, success: true };
return {filePath, success: true};
} catch (error) {
console.error(
"WriteAPI: Error adding project task to quick capture:",
error
);
return { filePath: "", success: false };
return {filePath: "", success: false};
}
}
@ -1539,7 +1514,7 @@ export class WriteAPI {
): { line: number } | null {
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(taskId)) {
return { line: i };
return {line: i};
}
}
return null;
@ -1602,12 +1577,12 @@ export class WriteAPI {
this.getTaskById(args.taskId)
);
if (!originalTask) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Ensure it's a Canvas task
if (!CanvasTaskUpdater.isCanvasTask(originalTask)) {
return { success: false, error: "Task is not a Canvas task" };
return {success: false, error: "Task is not a Canvas task"};
}
// Create updated task object (deep-merge metadata to preserve unchanged fields)
@ -1628,7 +1603,7 @@ export class WriteAPI {
if (result.success) {
// Emit task updated event for dataflow
emit(this.app, Events.TASK_UPDATED, { task: updatedTask });
emit(this.app, Events.TASK_UPDATED, {task: updatedTask});
// Trigger task-completed event if task was just completed
if (
@ -1641,13 +1616,13 @@ export class WriteAPI {
);
}
return { success: true, task: updatedTask };
return {success: true, task: updatedTask};
} else {
return { success: false, error: result.error };
return {success: false, error: result.error};
}
} catch (error) {
console.error("WriteAPI: Error updating Canvas task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1660,12 +1635,12 @@ export class WriteAPI {
try {
const task = await Promise.resolve(this.getTaskById(args.taskId));
if (!task) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Ensure it's a Canvas task
if (!CanvasTaskUpdater.isCanvasTask(task)) {
return { success: false, error: "Task is not a Canvas task" };
return {success: false, error: "Task is not a Canvas task"};
}
// Collect all tasks to delete
@ -1698,7 +1673,7 @@ export class WriteAPI {
return result;
} catch (error) {
console.error("WriteAPI: Error deleting Canvas task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1714,12 +1689,12 @@ export class WriteAPI {
try {
const task = await Promise.resolve(this.getTaskById(args.taskId));
if (!task) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Ensure it's a Canvas task
if (!CanvasTaskUpdater.isCanvasTask(task)) {
return { success: false, error: "Task is not a Canvas task" };
return {success: false, error: "Task is not a Canvas task"};
}
// Use CanvasTaskUpdater to move the task
@ -1733,7 +1708,7 @@ export class WriteAPI {
return result;
} catch (error) {
console.error("WriteAPI: Error moving Canvas task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1750,12 +1725,12 @@ export class WriteAPI {
try {
const task = await Promise.resolve(this.getTaskById(args.taskId));
if (!task) {
return { success: false, error: "Task not found" };
return {success: false, error: "Task not found"};
}
// Ensure it's a Canvas task
if (!CanvasTaskUpdater.isCanvasTask(task)) {
return { success: false, error: "Task is not a Canvas task" };
return {success: false, error: "Task is not a Canvas task"};
}
// Use CanvasTaskUpdater to duplicate the task
@ -1770,7 +1745,7 @@ export class WriteAPI {
return result;
} catch (error) {
console.error("WriteAPI: Error duplicating Canvas task:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}
@ -1809,7 +1784,7 @@ export class WriteAPI {
return result;
} catch (error) {
console.error("WriteAPI: Error adding task to Canvas node:", error);
return { success: false, error: String(error) };
return {success: false, error: String(error)};
}
}

View file

@ -3,494 +3,545 @@ import { DateInheritanceAugmentor } from "./DateInheritanceAugmentor";
import { App, Vault, MetadataCache } from "obsidian";
export interface AugmentContext {
filePath: string;
fileMeta?: Record<string, any>;
projectName?: string;
projectMeta?: Record<string, any>;
tasks: Task[];
filePath: string;
fileMeta?: Record<string, any>;
projectName?: string;
projectMeta?: Record<string, any>;
tasks: Task[];
}
export interface FileContext {
filePath: string;
fileMeta?: Record<string, any>;
project?: { name?: string; data?: Record<string, any> } | null;
filePath: string;
fileMeta?: Record<string, any>;
project?: { name?: string; data?: Record<string, any> } | null;
}
export interface InheritanceStrategy {
// Priority order: task > file > project > default
scalarPriority: ("task" | "file" | "project" | "default")[];
// For arrays: merge and deduplicate with stable ordering
arrayMergeStrategy: "task-first" | "file-first" | "project-first";
// Special handling for specific fields
statusCompletionSource: "task-only" | "allow-inheritance";
recurrenceSource: "task-explicit" | "allow-inheritance";
// Per-key inheritance control for subtasks
subtaskInheritance: Record<string, boolean>;
// Priority order: task > file > project > default
scalarPriority: ("task" | "file" | "project" | "default")[];
// For arrays: merge and deduplicate with stable ordering
arrayMergeStrategy: "task-first" | "file-first" | "project-first";
// Special handling for specific fields
statusCompletionSource: "task-only" | "allow-inheritance";
recurrenceSource: "task-explicit" | "allow-inheritance";
// Per-key inheritance control for subtasks
subtaskInheritance: Record<string, boolean>;
}
/**
* TaskAugmentor - Complete inheritance and augmentation implementation
*
*
* Implements the full inheritance strategy as specified in the refactor plan:
* - Scalar fields: task explicit > file > project > default
* - Arrays: merge and deduplicate (preserving stable order)
* - Status/completion: only from task level
* - Recurrence: task explicit priority
* - Recurrence: task explicit priority
* - Subtask inheritance: per-key control based on configuration
*/
export class Augmentor {
private strategy: InheritanceStrategy;
private dateInheritanceAugmentor?: DateInheritanceAugmentor;
private strategy: InheritanceStrategy;
private dateInheritanceAugmentor?: DateInheritanceAugmentor;
// Respect plugin setting: file frontmatter inheritance toggle
private fileFrontmatterInheritanceEnabled: boolean = true;
constructor(options?: {
inherit?: Record<string, "task" | "file" | "project" | "merge-array">;
strategy?: Partial<InheritanceStrategy>;
app?: App;
vault?: Vault;
metadataCache?: MetadataCache;
}) {
// Default strategy based on refactor plan requirements
this.strategy = {
scalarPriority: ["task", "file", "project", "default"],
arrayMergeStrategy: "task-first",
statusCompletionSource: "task-only",
recurrenceSource: "task-explicit",
subtaskInheritance: {
// Default: most fields inherit, sensitive fields don't
tags: true,
project: true,
priority: true,
dueDate: false,
startDate: false,
scheduledDate: false,
completed: false,
status: false,
recurrence: false,
onCompletion: false
},
...options?.strategy
};
constructor(options?: {
inherit?: Record<string, "task" | "file" | "project" | "merge-array">;
strategy?: Partial<InheritanceStrategy>;
app?: App;
vault?: Vault;
metadataCache?: MetadataCache;
}) {
// Default strategy based on refactor plan requirements
this.strategy = {
scalarPriority: ["task", "file", "project", "default"],
arrayMergeStrategy: "task-first",
statusCompletionSource: "task-only",
recurrenceSource: "task-explicit",
subtaskInheritance: {
// Default: most fields inherit, sensitive fields don't
tags: true,
project: true,
priority: true,
dueDate: false,
startDate: false,
scheduledDate: false,
completed: false,
status: false,
recurrence: false,
onCompletion: false
},
...options?.strategy
};
// Initialize date inheritance augmentor if Obsidian context is available
if (options?.app && options?.vault && options?.metadataCache) {
this.dateInheritanceAugmentor = new DateInheritanceAugmentor(
options.app,
options.vault,
options.metadataCache
);
}
}
// Initialize date inheritance augmentor if Obsidian context is available
if (options?.app && options?.vault && options?.metadataCache) {
this.dateInheritanceAugmentor = new DateInheritanceAugmentor(
options.app,
options.vault,
options.metadataCache
);
}
}
/**
* Main merge method with enhanced context support
*/
async merge(ctx: AugmentContext): Promise<Task[]> {
// First apply standard augmentation
let augmentedTasks = ctx.tasks.map(task => this.augmentTask(task, ctx));
/**
* Update settings from plugin to control inheritance behavior
*/
public updateSettings(settings: Partial<{
fileMetadataInheritance?: { enabled?: boolean; inheritFromFrontmatter?: boolean }
}>): void {
const f = settings.fileMetadataInheritance;
if (f) {
this.fileFrontmatterInheritanceEnabled = !!(f.enabled && f.inheritFromFrontmatter);
}
}
// Then apply date inheritance for time-only expressions if available
if (this.dateInheritanceAugmentor) {
try {
augmentedTasks = await this.dateInheritanceAugmentor.augmentTasksWithDateInheritance(
augmentedTasks,
ctx.filePath
);
} catch (error) {
console.warn('[Augmentor] Date inheritance augmentation failed:', error);
// Continue with standard augmentation if date inheritance fails
}
}
/**
* Main merge method with enhanced context support
*/
async merge(ctx: AugmentContext): Promise<Task[]> {
// First apply standard augmentation
let augmentedTasks = ctx.tasks.map(task => this.augmentTask(task, ctx));
return augmentedTasks;
}
// Then apply date inheritance for time-only expressions if available
if (this.dateInheritanceAugmentor) {
try {
augmentedTasks = await this.dateInheritanceAugmentor.augmentTasksWithDateInheritance(
augmentedTasks,
ctx.filePath
);
} catch (error) {
console.warn('[Augmentor] Date inheritance augmentation failed:', error);
// Continue with standard augmentation if date inheritance fails
}
}
/**
* Legacy merge method for backward compatibility
*/
mergeCompat(ctx: FileContext, tasks: Task[]): Task[] {
const augmentCtx: AugmentContext = {
filePath: ctx.filePath,
fileMeta: ctx.fileMeta,
projectName: ctx.project?.name,
projectMeta: ctx.project?.data,
tasks
};
return tasks.map(task => this.augmentTask(task, augmentCtx));
}
return augmentedTasks;
}
/**
* Augment a single task with file and project metadata
*/
private augmentTask(task: Task, ctx: AugmentContext): Task {
const originalMetadata = task.metadata || {};
const enhancedMetadata = { ...originalMetadata };
/**
* Legacy merge method for backward compatibility
*/
mergeCompat(ctx: FileContext, tasks: Task[]): Task[] {
const augmentCtx: AugmentContext = {
filePath: ctx.filePath,
fileMeta: ctx.fileMeta,
projectName: ctx.project?.name,
projectMeta: ctx.project?.data,
tasks
};
// Special handling for priority: check both task.priority and metadata.priority
// Priority might be at task root level (from parser) or in metadata
// IMPORTANT: Once priority is set, it should NOT be overridden by inheritance
// Debug logging for priority processing
const debug = process.env.NODE_ENV === 'development';
if (debug && (enhancedMetadata.priority !== undefined || (task as any).priority !== undefined)) {
console.log('[Augmentor] Priority processing:', {
metadataPriority: enhancedMetadata.priority,
taskPriority: (task as any).priority,
filePath: ctx.filePath
});
}
// First, ensure we have the priority from task-level if it exists
if ((enhancedMetadata.priority === undefined || enhancedMetadata.priority === null) &&
(task as any).priority !== undefined && (task as any).priority !== null) {
enhancedMetadata.priority = (task as any).priority;
}
// Ensure priority is properly converted to numeric format if it exists
// Clean up null values to undefined for consistency
if (enhancedMetadata.priority === null) {
enhancedMetadata.priority = undefined;
} else if (enhancedMetadata.priority !== undefined) {
const originalPriority = enhancedMetadata.priority;
enhancedMetadata.priority = this.convertPriorityValue(enhancedMetadata.priority);
if (debug) {
console.log('[Augmentor] Priority conversion:', {
original: originalPriority,
converted: enhancedMetadata.priority,
filePath: ctx.filePath
});
}
}
return tasks.map(task => this.augmentTask(task, augmentCtx));
}
// Apply inheritance for each metadata field
this.applyScalarInheritance(enhancedMetadata, ctx);
this.applyArrayInheritance(enhancedMetadata, ctx);
this.applySpecialFieldRules(enhancedMetadata, ctx);
this.applyProjectReference(enhancedMetadata, ctx);
/**
* Augment a single task with file and project metadata
*/
private augmentTask(task: Task, ctx: AugmentContext): Task {
const originalMetadata = task.metadata || {};
const enhancedMetadata = {...originalMetadata};
// Handle subtask inheritance if this is a parent task
if (originalMetadata.children && Array.isArray(originalMetadata.children)) {
this.applySubtaskInheritance(task, enhancedMetadata, ctx);
}
// Special handling for priority: check both task.priority and metadata.priority
// Priority might be at task root level (from parser) or in metadata
// IMPORTANT: Once priority is set, it should NOT be overridden by inheritance
return {
...task,
metadata: enhancedMetadata
} as Task;
}
// Debug logging for priority processing
const debug = process.env.NODE_ENV === 'development';
if (debug && (enhancedMetadata.priority !== undefined || (task as any).priority !== undefined)) {
console.log('[Augmentor] Priority processing:', {
metadataPriority: enhancedMetadata.priority,
taskPriority: (task as any).priority,
filePath: ctx.filePath
});
}
/**
* Apply scalar field inheritance: task > file > project > default
*/
private applyScalarInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
const scalarFields = [
'priority', 'context', 'area', 'estimatedTime', 'actualTime',
'useAsDateType', 'heading'
];
// First, ensure we have the priority from task-level if it exists
if ((enhancedMetadata.priority === undefined || enhancedMetadata.priority === null) &&
(task as any).priority !== undefined && (task as any).priority !== null) {
enhancedMetadata.priority = (task as any).priority;
}
for (const field of scalarFields) {
// Skip if task already has explicit value
if (metadata[field] !== undefined && metadata[field] !== null) {
continue;
}
// Ensure priority is properly converted to numeric format if it exists
// Clean up null values to undefined for consistency
if (enhancedMetadata.priority === null) {
enhancedMetadata.priority = undefined;
} else if (enhancedMetadata.priority !== undefined) {
const originalPriority = enhancedMetadata.priority;
enhancedMetadata.priority = this.convertPriorityValue(enhancedMetadata.priority);
// Special handling for priority - NEVER apply default value
// Priority should only come from task itself, file, or project
if (field === 'priority') {
// Only check file and project sources, skip default
for (const source of ['file', 'project']) {
let value: any;
if (source === 'file') {
value = ctx.fileMeta?.[field];
} else if (source === 'project') {
value = ctx.projectMeta?.[field];
}
if (debug) {
console.log('[Augmentor] Priority conversion:', {
original: originalPriority,
converted: enhancedMetadata.priority,
filePath: ctx.filePath
});
}
}
if (value !== undefined && value !== null) {
// Convert priority value to numeric format
metadata[field] = this.convertPriorityValue(value);
break;
}
}
// If no priority found, leave it undefined (don't set default)
continue;
}
// Apply inheritance for each metadata field
this.applyScalarInheritance(enhancedMetadata, ctx);
this.applyArrayInheritance(enhancedMetadata, ctx);
this.applySpecialFieldRules(enhancedMetadata, ctx);
this.applyProjectReference(enhancedMetadata, ctx);
// Apply inheritance priority for other fields: file > project > default
for (const source of this.strategy.scalarPriority.slice(1)) { // Skip 'task' since we checked above
let value: any;
switch (source) {
case 'file':
value = ctx.fileMeta?.[field];
break;
case 'project':
value = ctx.projectMeta?.[field];
break;
case 'default':
value = this.getDefaultValue(field);
break;
}
// Handle subtask inheritance if this is a parent task
if (originalMetadata.children && Array.isArray(originalMetadata.children)) {
this.applySubtaskInheritance(task, enhancedMetadata, ctx);
}
if (value !== undefined && value !== null) {
metadata[field] = value;
break;
}
}
}
}
return {
...task,
metadata: enhancedMetadata
} as Task;
}
/**
* Apply array field inheritance with merge and deduplication
*/
private applyArrayInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
const arrayFields = ['tags', 'dependsOn'];
/**
* Apply scalar field inheritance: task > file > project > default
*/
private applyScalarInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
const scalarFields = [
'priority', 'context', 'area', 'estimatedTime', 'actualTime',
'useAsDateType', 'heading'
];
for (const field of arrayFields) {
const taskArray = Array.isArray(metadata[field]) ? metadata[field] : [];
const fileArray = ctx.fileMeta && Array.isArray((ctx.fileMeta as any)[field]) ? (ctx.fileMeta as any)[field] : [];
const projectArray = ctx.projectMeta && Array.isArray((ctx.projectMeta as any)[field]) ? (ctx.projectMeta as any)[field] : [];
for (const field of scalarFields) {
// Skip if task already has explicit value
if (metadata[field] !== undefined && metadata[field] !== null) {
continue;
}
let mergedArray: any[];
// Special handling for priority - NEVER apply default value
// Priority should only come from task itself, file, or project
if (field === 'priority') {
// Only check file and project sources, skip default
for (const source of ['file', 'project']) {
let value: any;
// Merge based on strategy
switch (this.strategy.arrayMergeStrategy) {
case 'task-first':
mergedArray = [...taskArray, ...fileArray, ...projectArray];
break;
case 'file-first':
mergedArray = [...fileArray, ...taskArray, ...projectArray];
break;
case 'project-first':
mergedArray = [...projectArray, ...taskArray, ...fileArray];
break;
default:
mergedArray = [...taskArray, ...fileArray, ...projectArray];
}
if (source === 'file') {
value = ctx.fileMeta?.[field];
} else if (source === 'project') {
value = ctx.projectMeta?.[field];
}
// Deduplicate while preserving order
metadata[field] = Array.from(new Set(mergedArray));
}
}
if (value !== undefined && value !== null) {
// Convert priority value to numeric format
metadata[field] = this.convertPriorityValue(value);
break;
}
}
// If no priority found, leave it undefined (don't set default)
continue;
}
/**
* Apply special field rules for status/completion and recurrence
*/
private applySpecialFieldRules(metadata: Record<string, any>, ctx: AugmentContext): void {
// Status and completion: only from task level (never inherit)
if (this.strategy.statusCompletionSource === 'task-only') {
// These fields should only come from the task itself, never inherit
// (No action needed as we don't override existing task values)
}
// Apply inheritance priority for other fields: file > project > default
for (const source of this.strategy.scalarPriority.slice(1)) { // Skip 'task' since we checked above
let value: any;
// Recurrence: task explicit priority
if (this.strategy.recurrenceSource === 'task-explicit') {
// Only use recurrence if explicitly set on task
if (!metadata.recurrence) {
// Don't inherit recurrence from file or project
delete metadata.recurrence;
}
}
switch (source) {
case 'file':
if (!this.fileFrontmatterInheritanceEnabled) {
continue; // Skip applying file-level values when inheritance is disabled
}
value = ctx.fileMeta?.[field];
break;
case 'project':
value = ctx.projectMeta?.[field];
break;
case 'default':
value = this.getDefaultValue(field);
break;
}
// Date fields: inherit only if not already set
const dateFields = ['dueDate', 'startDate', 'scheduledDate', 'createdDate'];
for (const field of dateFields) {
if (metadata[field] === undefined || metadata[field] === null) {
// Try file first, then project
const fileValue = ctx.fileMeta?.[field];
const projectValue = ctx.projectMeta?.[field];
if (fileValue !== undefined && fileValue !== null) {
metadata[field] = fileValue;
} else if (projectValue !== undefined && projectValue !== null) {
metadata[field] = projectValue;
}
}
}
}
if (value !== undefined && value !== null) {
metadata[field] = value;
break;
}
}
}
}
/**
* Apply TgProject reference
*/
private applyProjectReference(metadata: Record<string, any>, ctx: AugmentContext): void {
// Set project name if not already set
if (!metadata.project && ctx.projectName) {
metadata.project = ctx.projectName;
}
/**
* Apply array field inheritance with merge and deduplication
*/
private applyArrayInheritance(metadata: Record<string, any>, ctx: AugmentContext): void {
const arrayFields = ['tags', 'dependsOn'];
// Set TgProject if project metadata is available
// The tgProject should come from ProjectResolver which includes all necessary fields
if (ctx.projectMeta) {
// Check if we have a tgProject object in the project metadata
if (ctx.projectMeta.tgProject) {
metadata.tgProject = ctx.projectMeta.tgProject;
} else if (ctx.projectName) {
// Fallback: construct from available data
metadata.tgProject = {
type: ctx.projectMeta.type || 'metadata',
name: ctx.projectName,
source: ctx.projectMeta.source || ctx.projectMeta.configSource || 'unknown',
readonly: ctx.projectMeta.readonly || false
} as TgProject;
}
}
}
for (const field of arrayFields) {
const taskArray = Array.isArray(metadata[field]) ? metadata[field] : [];
const fileArrayRaw = ctx.fileMeta && Array.isArray((ctx.fileMeta as any)[field]) ? (ctx.fileMeta as any)[field] : [];
const fileArray = this.fileFrontmatterInheritanceEnabled ? fileArrayRaw : [];
const projectArray = ctx.projectMeta && Array.isArray((ctx.projectMeta as any)[field]) ? (ctx.projectMeta as any)[field] : [];
/**
* Apply subtask inheritance based on per-key control
*/
private applySubtaskInheritance(parentTask: Task, parentMetadata: Record<string, any>, ctx: AugmentContext): void {
// This would typically involve finding child tasks and applying inheritance
// For now, we'll store the inheritance rules on the parent for child processing
parentMetadata._subtaskInheritanceRules = this.strategy.subtaskInheritance;
}
// Normalize tags consistently (ensure leading #) before merging/dedup
const normalizeIfTags = (arr: any[]) => {
if (field !== 'tags') return arr;
return arr
.filter((t) => typeof t === 'string' && t.trim().length > 0)
.map((t: string) => this.normalizeTag(t));
};
/**
* Convert priority value to consistent numeric format
*/
private convertPriorityValue(value: any): number | undefined {
if (value === undefined || value === null || value === "") {
return undefined;
}
// If user disabled file frontmatter inheritance, do not inherit tags from file or project
if (field === 'tags' && !this.fileFrontmatterInheritanceEnabled) {
try {
console.debug('[Augmentor][Tags] Inheritance disabled. Keeping only task tags.', {
taskTags: normalizeIfTags(taskArray)
});
} catch {
}
metadata[field] = normalizeIfTags(taskArray);
continue;
}
// If it's already a number, return it
if (typeof value === "number") {
return value;
}
const taskArrNorm = normalizeIfTags(taskArray);
const fileArrNorm = normalizeIfTags(fileArray);
const projectArrNorm = normalizeIfTags(projectArray);
// If it's a string, try to convert
const strValue = String(value);
// Priority mapping for text and emoji values
const priorityMap: Record<string, number> = {
// Text priorities
highest: 5,
high: 4,
medium: 3,
low: 2,
lowest: 1,
urgent: 5,
critical: 5,
important: 4,
normal: 3,
moderate: 3,
minor: 2,
trivial: 1,
// Emoji priorities (Tasks plugin compatible)
"🔺": 5,
"⏫": 4,
"🔼": 3,
"🔽": 2,
"⏬️": 1,
"⏬": 1,
};
let mergedArray: any[];
// Try numeric conversion first
const numericValue = parseInt(strValue, 10);
if (!isNaN(numericValue) && numericValue >= 1 && numericValue <= 5) {
return numericValue;
}
// Merge based on strategy
switch (this.strategy.arrayMergeStrategy) {
case 'task-first':
mergedArray = [...taskArrNorm, ...fileArrNorm, ...projectArrNorm];
break;
case 'file-first':
mergedArray = [...fileArrNorm, ...taskArrNorm, ...projectArrNorm];
break;
case 'project-first':
mergedArray = [...projectArrNorm, ...taskArrNorm, ...fileArrNorm];
break;
default:
mergedArray = [...taskArrNorm, ...fileArrNorm, ...projectArrNorm];
}
// Try priority mapping (including emojis)
const mappedPriority = priorityMap[strValue.toLowerCase()] || priorityMap[strValue];
if (mappedPriority !== undefined) {
return mappedPriority;
}
// Deduplicate while preserving order
const deduped = Array.from(new Set(mergedArray));
metadata[field] = deduped;
}
}
// If we can't convert, return undefined to avoid setting invalid values
return undefined;
}
// Normalize a tag to include leading # and trim whitespace
private normalizeTag(tag: any): string {
if (typeof tag !== 'string') return tag;
const trimmed = tag.trim();
if (!trimmed) return trimmed;
return trimmed.startsWith('#') ? trimmed : `#${trimmed}`;
}
/**
* Get default value for a field
*/
private getDefaultValue(field: string): any {
const defaults: Record<string, any> = {
// Don't set default priority for now - it should come from parser
// If we need to add it back, we should check if task already has priority elsewhere
tags: [],
dependsOn: [],
estimatedTime: undefined,
actualTime: undefined,
useAsDateType: 'due'
};
/**
* Apply special field rules for status/completion and recurrence
*/
private applySpecialFieldRules(metadata: Record<string, any>, ctx: AugmentContext): void {
// Status and completion: only from task level (never inherit)
if (this.strategy.statusCompletionSource === 'task-only') {
// These fields should only come from the task itself, never inherit
// (No action needed as we don't override existing task values)
}
return defaults[field];
}
// Recurrence: task explicit priority
if (this.strategy.recurrenceSource === 'task-explicit') {
// Only use recurrence if explicitly set on task
if (!metadata.recurrence) {
// Don't inherit recurrence from file or project
delete metadata.recurrence;
}
}
/**
* Update inheritance strategy
*/
updateStrategy(strategy: Partial<InheritanceStrategy>): void {
this.strategy = { ...this.strategy, ...strategy };
}
// Date fields: inherit only if not already set
const dateFields = ['dueDate', 'startDate', 'scheduledDate', 'createdDate'];
for (const field of dateFields) {
if (metadata[field] === undefined || metadata[field] === null) {
// Try file first, then project
const fileValue = ctx.fileMeta?.[field];
const projectValue = ctx.projectMeta?.[field];
/**
* Get current inheritance strategy
*/
getStrategy(): InheritanceStrategy {
return { ...this.strategy };
}
if (fileValue !== undefined && fileValue !== null) {
metadata[field] = fileValue;
} else if (projectValue !== undefined && projectValue !== null) {
metadata[field] = projectValue;
}
}
}
}
/**
* Process inheritance for a specific field type
*/
processFieldInheritance(
field: string,
taskValue: any,
fileValue: any,
projectValue: any
): any {
// Handle arrays specially
if (Array.isArray(taskValue) || Array.isArray(fileValue) || Array.isArray(projectValue)) {
const taskArray = Array.isArray(taskValue) ? taskValue : [];
const fileArray = Array.isArray(fileValue) ? fileValue : [];
const projectArray = Array.isArray(projectValue) ? projectValue : [];
let merged: any[];
switch (this.strategy.arrayMergeStrategy) {
case 'task-first':
merged = [...taskArray, ...fileArray, ...projectArray];
break;
case 'file-first':
merged = [...fileArray, ...taskArray, ...projectArray];
break;
case 'project-first':
merged = [...projectArray, ...taskArray, ...fileArray];
break;
default:
merged = [...taskArray, ...fileArray, ...projectArray];
}
return Array.from(new Set(merged));
}
/**
* Apply TgProject reference
*/
private applyProjectReference(metadata: Record<string, any>, ctx: AugmentContext): void {
// Set project name if not already set
if (!metadata.project && ctx.projectName) {
metadata.project = ctx.projectName;
}
// Handle scalars with priority order
for (const source of this.strategy.scalarPriority) {
let value: any;
switch (source) {
case 'task':
value = taskValue;
break;
case 'file':
value = fileValue;
break;
case 'project':
value = projectValue;
break;
case 'default':
value = this.getDefaultValue(field);
break;
}
// Set TgProject if project metadata is available
// The tgProject should come from ProjectResolver which includes all necessary fields
if (ctx.projectMeta) {
// Check if we have a tgProject object in the project metadata
if (ctx.projectMeta.tgProject) {
metadata.tgProject = ctx.projectMeta.tgProject;
} else if (ctx.projectName) {
// Fallback: construct from available data
metadata.tgProject = {
type: ctx.projectMeta.type || 'metadata',
name: ctx.projectName,
source: ctx.projectMeta.source || ctx.projectMeta.configSource || 'unknown',
readonly: ctx.projectMeta.readonly || false
} as TgProject;
}
}
}
if (value !== undefined && value !== null) {
return value;
}
}
/**
* Apply subtask inheritance based on per-key control
*/
private applySubtaskInheritance(parentTask: Task, parentMetadata: Record<string, any>, ctx: AugmentContext): void {
// This would typically involve finding child tasks and applying inheritance
// For now, we'll store the inheritance rules on the parent for child processing
parentMetadata._subtaskInheritanceRules = this.strategy.subtaskInheritance;
}
return undefined;
}
/**
* Convert priority value to consistent numeric format
*/
private convertPriorityValue(value: any): number | undefined {
if (value === undefined || value === null || value === "") {
return undefined;
}
// If it's already a number, return it
if (typeof value === "number") {
return value;
}
// If it's a string, try to convert
const strValue = String(value);
// Priority mapping for text and emoji values
const priorityMap: Record<string, number> = {
// Text priorities
highest: 5,
high: 4,
medium: 3,
low: 2,
lowest: 1,
urgent: 5,
critical: 5,
important: 4,
normal: 3,
moderate: 3,
minor: 2,
trivial: 1,
// Emoji priorities (Tasks plugin compatible)
"🔺": 5,
"⏫": 4,
"🔼": 3,
"🔽": 2,
"⏬️": 1,
"⏬": 1,
};
// Try numeric conversion first
const numericValue = parseInt(strValue, 10);
if (!isNaN(numericValue) && numericValue >= 1 && numericValue <= 5) {
return numericValue;
}
// Try priority mapping (including emojis)
const mappedPriority = priorityMap[strValue.toLowerCase()] || priorityMap[strValue];
if (mappedPriority !== undefined) {
return mappedPriority;
}
// If we can't convert, return undefined to avoid setting invalid values
return undefined;
}
/**
* Get default value for a field
*/
private getDefaultValue(field: string): any {
const defaults: Record<string, any> = {
// Don't set default priority for now - it should come from parser
// If we need to add it back, we should check if task already has priority elsewhere
tags: [],
dependsOn: [],
estimatedTime: undefined,
actualTime: undefined,
useAsDateType: 'due'
};
return defaults[field];
}
/**
* Update inheritance strategy
*/
updateStrategy(strategy: Partial<InheritanceStrategy>): void {
this.strategy = {...this.strategy, ...strategy};
}
/**
* Get current inheritance strategy
*/
getStrategy(): InheritanceStrategy {
return {...this.strategy};
}
/**
* Process inheritance for a specific field type
*/
processFieldInheritance(
field: string,
taskValue: any,
fileValue: any,
projectValue: any
): any {
// Handle arrays specially
if (Array.isArray(taskValue) || Array.isArray(fileValue) || Array.isArray(projectValue)) {
const taskArray = Array.isArray(taskValue) ? taskValue : [];
const fileArray = Array.isArray(fileValue) ? fileValue : [];
const projectArray = Array.isArray(projectValue) ? projectValue : [];
let merged: any[];
switch (this.strategy.arrayMergeStrategy) {
case 'task-first':
merged = [...taskArray, ...fileArray, ...projectArray];
break;
case 'file-first':
merged = [...fileArray, ...taskArray, ...projectArray];
break;
case 'project-first':
merged = [...projectArray, ...taskArray, ...fileArray];
break;
default:
merged = [...taskArray, ...fileArray, ...projectArray];
}
return Array.from(new Set(merged));
}
// Handle scalars with priority order
for (const source of this.strategy.scalarPriority) {
let value: any;
switch (source) {
case 'task':
value = taskValue;
break;
case 'file':
value = fileValue;
break;
case 'project':
value = projectValue;
break;
case 'default':
value = this.getDefaultValue(field);
break;
}
if (value !== undefined && value !== null) {
return value;
}
}
return undefined;
}
}

View file

@ -59,7 +59,7 @@ export class MarkdownTaskParser {
statusMapping: Record<string, string>,
timeParsingService?: TimeParsingService
): MarkdownTaskParser {
const newConfig = { ...config, statusMapping };
const newConfig = {...config, statusMapping};
return new MarkdownTaskParser(newConfig, timeParsingService);
}
@ -177,10 +177,10 @@ export class MarkdownTaskParser {
const [comment, linesToSkip] =
this.config.parseComments && i + 1 < lines.length
? this.extractMultilineComment(
lines,
i + 1,
actualSpaces
)
lines,
i + 1,
actualSpaces
)
: [undefined, 0];
i += linesToSkip;
@ -471,7 +471,7 @@ export class MarkdownTaskParser {
this.config.specialTagPrefixes[prefix] ??
this.config.specialTagPrefixes[
prefix.toLowerCase()
];
];
console.debug("[TPB] Tag parse", {
tag,
prefix,
@ -481,7 +481,7 @@ export class MarkdownTaskParser {
if (
metadataKey &&
this.config.metadataParseMode !==
MetadataParseMode.None
MetadataParseMode.None
) {
metadata[metadataKey] = value;
} else {
@ -781,7 +781,7 @@ export class MarkdownTaskParser {
specialTagPrefixes: Object.keys(this.config.specialTagPrefixes || {}),
});
}
const before = content.substring(0, start);
const after = content.substring(end + 1);
return [key, value, before + after];
@ -801,7 +801,7 @@ export class MarkdownTaskParser {
const pos = content.indexOf(emoji);
if (pos !== -1) {
if (!earliestEmoji || pos < earliestEmoji.pos) {
earliestEmoji = { pos, emoji, key };
earliestEmoji = {pos, emoji, key};
}
}
}
@ -1211,11 +1211,11 @@ export class MarkdownTaskParser {
this.config.specialTagPrefixes[prefix] ??
this.config.specialTagPrefixes[
prefix.toLowerCase()
];
];
if (
metadataKey &&
this.config.metadataParseMode !==
MetadataParseMode.None
MetadataParseMode.None
) {
metadata[metadataKey] = value;
} else {
@ -1294,7 +1294,7 @@ export class MarkdownTaskParser {
);
}
this.indentStack.push({ taskId, indentLevel, actualSpaces });
this.indentStack.push({taskId, indentLevel, actualSpaces});
}
private getStatusFromMapping(rawStatus: string): string | undefined {
@ -1494,9 +1494,9 @@ export class MarkdownTaskParser {
id: enhancedTask.metadata.id,
dependsOn: enhancedTask.metadata.dependsOn
? enhancedTask.metadata.dependsOn
.split(",")
.map((id) => id.trim())
.filter((id) => id.length > 0)
.split(",")
.map((id) => id.trim())
.filter((id) => id.length > 0)
: undefined,
onCompletion: enhancedTask.metadata.onCompletion,
// Legacy compatibility fields that should remain in metadata
@ -1504,8 +1504,8 @@ export class MarkdownTaskParser {
heading: Array.isArray(enhancedTask.heading)
? enhancedTask.heading
: enhancedTask.heading
? [enhancedTask.heading]
: [],
? [enhancedTask.heading]
: [],
parent: enhancedTask.parentId,
tgProject: enhancedTask.tgProject,
},
@ -1696,7 +1696,13 @@ export class MarkdownTaskParser {
}
/**
* Inherit metadata from file frontmatter and project configuration
* LEGACY (pre-dataflow): Inherit metadata from file frontmatter and project configuration
*
* In the new dataflow architecture, inheritance is handled exclusively by Augmentor.
* This method remains for backward compatibility and is effectively disabled when
* fileMetadataInheritance.enabled is false (returns {}). When enabled, Parser may still
* perform minimal, legacy-compatible merging, but authoritative merging should be done
* in Augmentor.merge().
*/
private inheritFileMetadata(
taskMetadata: Record<string, string>,
@ -1756,7 +1762,7 @@ export class MarkdownTaskParser {
};
// Always convert priority values in task metadata, even if inheritance is disabled
const inherited = { ...taskMetadata };
const inherited = {...taskMetadata};
if (inherited.priority !== undefined) {
inherited.priority = convertPriorityValue(inherited.priority);
}
@ -1764,11 +1770,14 @@ export class MarkdownTaskParser {
// Early return if enhanced project features are disabled
// Check if file metadata inheritance is enabled
if (!this.config.fileMetadataInheritance?.enabled) {
return inherited;
// Parser should not perform file-level inheritance when disabled
// Leave any file/frontmatter merging to Augmentor when enabled
return {};
}
// Check if frontmatter inheritance is enabled
if (!this.config.fileMetadataInheritance?.inheritFromFrontmatter) {
// Legacy behavior: return task-only metadata
return inherited;
}
@ -1778,6 +1787,7 @@ export class MarkdownTaskParser {
!this.config.fileMetadataInheritance
?.inheritFromFrontmatterForSubtasks
) {
// Legacy behavior: do not inherit for subtasks
return inherited;
}
@ -1806,7 +1816,7 @@ export class MarkdownTaskParser {
"metadata", // Prevent recursive metadata inheritance
]);
// Inherit from file metadata (frontmatter) if available
// LEGACY: Inherit from file metadata (frontmatter) if available
if (this.fileMetadata) {
// Map configured frontmatter project key to standard 'project'
try {
@ -1817,7 +1827,7 @@ export class MarkdownTaskParser {
this.fileMetadata[configuredProjectKey] !== undefined &&
this.fileMetadata[configuredProjectKey] !== null &&
String(this.fileMetadata[configuredProjectKey]).trim() !==
""
""
) {
if (
inherited.project === undefined ||
@ -1829,7 +1839,8 @@ export class MarkdownTaskParser {
).trim();
}
}
} catch {}
} catch {
}
for (const [key, value] of Object.entries(this.fileMetadata)) {
// Special handling for tags field
@ -1896,7 +1907,7 @@ export class MarkdownTaskParser {
}
}
// Inherit from project configuration data if available
// LEGACY: Inherit from project configuration data if available
if (this.projectConfigCache) {
for (const [key, value] of Object.entries(
this.projectConfigCache
@ -1980,6 +1991,6 @@ export class ConfigurableTaskParser extends MarkdownTaskParser {
},
};
super({ ...defaultConfig, ...config }, timeParsingService);
super({...defaultConfig, ...config}, timeParsingService);
}
}

View file

@ -11,7 +11,6 @@ import type {
FileSourceConfiguration,
FileSourceTaskMetadata,
FileSourceStats,
UpdateDecision,
FileTaskCache,
RecognitionStrategy,
PathRecognitionConfig,
@ -198,16 +197,6 @@ export class FileSource {
return;
}
// Apply centralized file filter manager for file-scope rules
if (this.fileFilterManager) {
const include = this.fileFilterManager.shouldIncludePath(
filePath,
"file"
);
if (!include) return;
}
const shouldBeTask = await this.shouldCreateFileTask(filePath);
const existingCache = this.fileTaskCache.get(filePath);
const wasTask = existingCache?.fileTaskExists ?? false;
@ -229,6 +218,7 @@ export class FileSource {
* Check if a file should be treated as a task
*/
async shouldCreateFileTask(filePath: string): Promise<boolean> {
// Fast reject non-relevant files before any IO
if (!this.isRelevantFile(filePath)) return false;
const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
@ -261,7 +251,7 @@ export class FileSource {
fileCache: CachedMetadata | null
): boolean {
const config = this.config.getConfig();
const { recognitionStrategies } = config;
const {recognitionStrategies} = config;
// Check metadata strategy
if (recognitionStrategies.metadata.enabled) {
@ -333,7 +323,7 @@ export class FileSource {
): boolean {
if (!fileCache?.frontmatter) return false;
const { taskFields, requireAllFields } = config;
const {taskFields, requireAllFields} = config;
const frontmatter = fileCache.frontmatter;
const matchingFields = taskFields.filter(
@ -360,7 +350,7 @@ export class FileSource {
): boolean {
if (!fileCache?.tags) return false;
const { taskTags, matchMode } = config;
const {taskTags, matchMode} = config;
const fileTags = fileCache.tags.map((tag) => tag.tag);
return taskTags.some((taskTag: string) => {
@ -680,7 +670,7 @@ export class FileSource {
config.recognitionStrategies.metadata
)
) {
return { name: "metadata", criteria: "frontmatter" };
return {name: "metadata", criteria: "frontmatter"};
}
if (
@ -692,7 +682,7 @@ export class FileSource {
config.recognitionStrategies.tags
)
) {
return { name: "tag", criteria: "file-tags" };
return {name: "tag", criteria: "file-tags"};
}
// Check path strategy
@ -1016,19 +1006,7 @@ export class FileSource {
* Check if file is relevant for processing
*/
private isRelevantFile(filePath: string): boolean {
// Use centralized FileFilterManager for 'file' scope filtering if available
if (this.fileFilterManager) {
const include = this.fileFilterManager.shouldIncludePath(
filePath,
"file"
);
console.log(
"[FileSource] isRelevantFile filter check (file-scope)",
{ filePath, include }
);
if (!include) return false;
}
// Fast-path filters first
// Only process markdown files for now (additional file type support can be added later)
if (!filePath.endsWith(".md")) {
return false;
@ -1039,6 +1017,15 @@ export class FileSource {
return false;
}
// Apply centralized FileFilterManager for 'file' scope filtering if available
if (this.fileFilterManager) {
const include = this.fileFilterManager.shouldIncludePath(
filePath,
"file"
);
if (!include) return false;
}
return true;
}
@ -1138,7 +1125,7 @@ export class FileSource {
* Get current statistics
*/
getStats(): FileSourceStats {
return { ...this.stats };
return {...this.stats};
}
/**
@ -1185,8 +1172,6 @@ export class FileSource {
async refresh(): Promise<void> {
if (!this.isInitialized || !this.config.isEnabled()) return;
console.log("[FileSource] Manual refresh triggered");
// Clear cache and re-scan
this.fileTaskCache.clear();
this.stats.trackedFileCount = 0;
@ -1227,7 +1212,7 @@ export class FileSource {
this.stats = {
initialized: false,
trackedFileCount: 0,
recognitionBreakdown: { metadata: 0, tag: 0, template: 0, path: 0 },
recognitionBreakdown: {metadata: 0, tag: 0, template: 0, path: 0},
lastUpdate: 0,
lastUpdateSeq: 0,
};

View file

@ -1622,6 +1622,10 @@ export default class TaskProgressBarPlugin extends Plugin {
async loadSettings() {
const savedData = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData);
try {
console.debug('[Plugin][loadSettings] fileMetadataInheritance (raw):', savedData?.fileMetadataInheritance);
console.debug('[Plugin][loadSettings] fileMetadataInheritance (effective):', this.settings.fileMetadataInheritance);
} catch {}
// Migrate old inheritance settings to new structure
this.migrateInheritanceSettings(savedData);
@ -1658,6 +1662,9 @@ export default class TaskProgressBarPlugin extends Plugin {
}
async saveSettings() {
try {
console.debug('[Plugin][saveSettings] fileMetadataInheritance:', this.settings?.fileMetadataInheritance);
} catch {}
await this.saveData(this.settings);
}

File diff suppressed because one or more lines are too long