@md2do/core 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/coverage/coverage-final.json +6 -5
  3. package/coverage/index.html +52 -37
  4. package/coverage/lcov-report/index.html +52 -37
  5. package/coverage/lcov-report/src/filters/index.html +1 -1
  6. package/coverage/lcov-report/src/filters/index.ts.html +1 -1
  7. package/coverage/lcov-report/src/index.html +5 -5
  8. package/coverage/lcov-report/src/index.ts.html +13 -4
  9. package/coverage/lcov-report/src/parser/index.html +17 -17
  10. package/coverage/lcov-report/src/parser/index.ts.html +181 -13
  11. package/coverage/lcov-report/src/parser/patterns.ts.html +18 -9
  12. package/coverage/lcov-report/src/scanner/index.html +15 -15
  13. package/coverage/lcov-report/src/scanner/index.ts.html +83 -8
  14. package/coverage/lcov-report/src/sorting/index.html +1 -1
  15. package/coverage/lcov-report/src/sorting/index.ts.html +1 -1
  16. package/coverage/lcov-report/src/utils/dates.ts.html +169 -25
  17. package/coverage/lcov-report/src/utils/id.ts.html +1 -1
  18. package/coverage/lcov-report/src/utils/index.html +19 -19
  19. package/coverage/lcov-report/src/warnings/filter.ts.html +364 -0
  20. package/coverage/lcov-report/src/warnings/index.html +116 -0
  21. package/coverage/lcov-report/src/writer/index.html +1 -1
  22. package/coverage/lcov-report/src/writer/index.ts.html +1 -1
  23. package/coverage/lcov.info +760 -512
  24. package/coverage/src/filters/index.html +1 -1
  25. package/coverage/src/filters/index.ts.html +1 -1
  26. package/coverage/src/index.html +5 -5
  27. package/coverage/src/index.ts.html +13 -4
  28. package/coverage/src/parser/index.html +17 -17
  29. package/coverage/src/parser/index.ts.html +181 -13
  30. package/coverage/src/parser/patterns.ts.html +18 -9
  31. package/coverage/src/scanner/index.html +15 -15
  32. package/coverage/src/scanner/index.ts.html +83 -8
  33. package/coverage/src/sorting/index.html +1 -1
  34. package/coverage/src/sorting/index.ts.html +1 -1
  35. package/coverage/src/utils/dates.ts.html +169 -25
  36. package/coverage/src/utils/id.ts.html +1 -1
  37. package/coverage/src/utils/index.html +19 -19
  38. package/coverage/src/warnings/filter.ts.html +364 -0
  39. package/coverage/src/warnings/index.html +116 -0
  40. package/coverage/src/writer/index.html +1 -1
  41. package/coverage/src/writer/index.ts.html +1 -1
  42. package/dist/index.d.mts +103 -12
  43. package/dist/index.d.ts +103 -12
  44. package/dist/index.js +125 -11
  45. package/dist/index.mjs +125 -12
  46. package/package.json +1 -1
  47. package/src/index.ts +3 -0
  48. package/src/parser/index.ts +61 -5
  49. package/src/parser/patterns.ts +8 -5
  50. package/src/scanner/index.ts +25 -0
  51. package/src/types/index.ts +35 -2
  52. package/src/utils/dates.ts +58 -10
  53. package/src/warnings/filter.ts +93 -0
package/dist/index.d.mts CHANGED
@@ -21,6 +21,9 @@ interface ParsingContext {
21
21
  person?: string;
22
22
  currentDate?: Date;
23
23
  currentHeading?: string;
24
+ workdayStartTime?: string;
25
+ workdayEndTime?: string;
26
+ defaultDueTime?: 'start' | 'end';
24
27
  }
25
28
  interface TaskFilterCriteria {
26
29
  assignee?: string | string[];
@@ -47,11 +50,20 @@ interface ScanResult {
47
50
  parseErrors: number;
48
51
  };
49
52
  }
53
+ type WarningSeverity = 'info' | 'warning' | 'error';
54
+ type WarningCode = 'unsupported-bullet' | 'malformed-checkbox' | 'missing-space-after' | 'missing-space-before' | 'relative-date-no-context' | 'missing-due-date' | 'missing-completed-date' | 'duplicate-todoist-id' | 'file-read-error';
50
55
  interface Warning {
51
56
  file: string;
52
57
  line: number;
53
- text: string;
54
- reason: string;
58
+ column?: number;
59
+ severity: WarningSeverity;
60
+ source: 'md2do';
61
+ ruleId: WarningCode;
62
+ message: string;
63
+ text?: string;
64
+ url?: string;
65
+ /** @deprecated Use message instead */
66
+ reason?: string;
55
67
  }
56
68
 
57
69
  /**
@@ -113,10 +125,10 @@ declare function extractCompletedDate(text: string): Date | undefined;
113
125
  * Extract due date from task text with context awareness
114
126
  *
115
127
  * Handles both absolute dates ([due: 2026-01-25]) and relative dates
116
- * ([due: tomorrow]) which require context.
128
+ * ([due: tomorrow]) which require context. Supports optional time ([due: 2026-01-25 17:00]).
117
129
  *
118
130
  * @param text - Task text
119
- * @param context - Parsing context (for relative dates)
131
+ * @param context - Parsing context (for relative dates and workday config)
120
132
  * @returns Object with parsed date and optional warning
121
133
  */
122
134
  declare function extractDueDate(text: string, context: ParsingContext): {
@@ -223,15 +235,17 @@ declare const PRIORITY_HIGH: RegExp;
223
235
  */
224
236
  declare const PRIORITY_NORMAL: RegExp;
225
237
  /**
226
- * Matches absolute due date in ISO format [due: YYYY-MM-DD]
238
+ * Matches absolute due date in ISO format [due: YYYY-MM-DD] with optional time [due: YYYY-MM-DD HH:MM]
227
239
  *
228
240
  * Examples:
229
- * "[due: 2026-01-25]" → "2026-01-25"
230
- * "[due:2026-01-25]" → "2026-01-25" (spaces optional)
231
- * "[due: 2026-01-25 ]" → "2026-01-25" (whitespace allowed)
241
+ * "[due: 2026-01-25]" → "2026-01-25", undefined
242
+ * "[due: 2026-01-25 17:00]" → "2026-01-25", "17:00"
243
+ * "[due: 2026-01-25 9:00]" → "2026-01-25", "9:00"
244
+ * "[due:2026-01-25]" → "2026-01-25", undefined (spaces optional)
232
245
  *
233
246
  * Groups:
234
247
  * [1] - Date string in YYYY-MM-DD format
248
+ * [2] - Optional time string in H:MM or HH:MM format (24-hour)
235
249
  */
236
250
  declare const DUE_DATE_ABSOLUTE: RegExp;
237
251
  /**
@@ -391,9 +405,14 @@ declare class MarkdownScanner {
391
405
  *
392
406
  * @param filePath - Relative file path (for context extraction)
393
407
  * @param content - File content as string
408
+ * @param options - Optional scanner options including workday config
394
409
  * @returns Object containing tasks and warnings
395
410
  */
396
- scanFile(filePath: string, content: string): {
411
+ scanFile(filePath: string, content: string, options?: {
412
+ workdayStartTime?: string;
413
+ workdayEndTime?: string;
414
+ defaultDueTime?: 'start' | 'end';
415
+ }): {
397
416
  tasks: Task[];
398
417
  warnings: Warning[];
399
418
  };
@@ -812,8 +831,75 @@ declare namespace index {
812
831
  export { type index_TaskComparator as TaskComparator, index_byAssignee as byAssignee, index_byCompletionStatus as byCompletionStatus, index_byCreatedDate as byCreatedDate, index_byDueDate as byDueDate, index_byFile as byFile, index_byPerson as byPerson, index_byPriority as byPriority, index_byProject as byProject, index_combineComparators as combineComparators, index_reverse as reverse };
813
832
  }
814
833
 
834
+ interface WarningFilterConfig {
835
+ enabled?: boolean | undefined;
836
+ rules?: Record<string, 'error' | 'warn' | 'info' | 'off'> | undefined;
837
+ }
838
+ /**
839
+ * Filter warnings based on configuration rules
840
+ *
841
+ * This function applies warning configuration rules to filter out disabled warnings
842
+ * and optionally all warnings if globally disabled.
843
+ *
844
+ * @param warnings - Array of warnings to filter
845
+ * @param config - Warning configuration with enabled flag and rule overrides
846
+ * @returns Filtered array of warnings
847
+ *
848
+ * @example
849
+ * ```typescript
850
+ * const config = {
851
+ * enabled: true,
852
+ * rules: {
853
+ * 'missing-due-date': 'off',
854
+ * 'duplicate-todoist-id': 'error',
855
+ * },
856
+ * };
857
+ *
858
+ * const filtered = filterWarnings(allWarnings, config);
859
+ * // Returns warnings except those with ruleId 'missing-due-date'
860
+ * ```
861
+ */
862
+ declare function filterWarnings(warnings: Warning[], config?: WarningFilterConfig): Warning[];
863
+ /**
864
+ * Group warnings by severity
865
+ *
866
+ * Useful for displaying warnings in order of importance or
867
+ * treating errors differently from warnings.
868
+ *
869
+ * @param warnings - Array of warnings to group
870
+ * @returns Object with warnings grouped by severity level
871
+ *
872
+ * @example
873
+ * ```typescript
874
+ * const grouped = groupWarningsBySeverity(warnings);
875
+ * console.log(`${grouped.error.length} errors`);
876
+ * console.log(`${grouped.warning.length} warnings`);
877
+ * console.log(`${grouped.info.length} info messages`);
878
+ * ```
879
+ */
880
+ declare function groupWarningsBySeverity(warnings: Warning[]): {
881
+ error: Warning[];
882
+ warning: Warning[];
883
+ info: Warning[];
884
+ };
885
+
886
+ /**
887
+ * Parse a time string in HH:MM or H:MM format
888
+ *
889
+ * @param timeStr - Time string to parse (e.g., "17:00", "9:00")
890
+ * @returns Object with hours and minutes, or null if invalid
891
+ *
892
+ * @example
893
+ * parseTime("17:00") // => { hours: 17, minutes: 0 }
894
+ * parseTime("9:30") // => { hours: 9, minutes: 30 }
895
+ * parseTime("25:00") // => null (invalid hour)
896
+ */
897
+ declare function parseTime(timeStr: string): {
898
+ hours: number;
899
+ minutes: number;
900
+ } | null;
815
901
  /**
816
- * Parse an absolute date string in various formats
902
+ * Parse an absolute date string in various formats, with optional time
817
903
  *
818
904
  * Supported formats:
819
905
  * - ISO: 2026-01-25
@@ -821,9 +907,14 @@ declare namespace index {
821
907
  * - US full: 1/25/2026
822
908
  *
823
909
  * @param dateStr - Date string to parse
910
+ * @param timeStr - Optional time string in HH:MM or H:MM format
824
911
  * @returns Parsed Date object or null if invalid
912
+ *
913
+ * @example
914
+ * parseAbsoluteDate("2026-01-25") // => Date at midnight
915
+ * parseAbsoluteDate("2026-01-25", "17:00") // => Date at 5 PM
825
916
  */
826
- declare function parseAbsoluteDate(dateStr: string): Date | null;
917
+ declare function parseAbsoluteDate(dateStr: string, timeStr?: string): Date | null;
827
918
  /**
828
919
  * Resolve a relative date keyword against a base date
829
920
  *
@@ -876,4 +967,4 @@ declare function extractDateFromHeading(line: string): Date | null;
876
967
  */
877
968
  declare function generateTaskId(file: string, line: number, text: string): string;
878
969
 
879
- export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, index$1 as filters, generateTaskId, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
970
+ export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WarningCode, type WarningFilterConfig, type WarningSeverity, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, filterWarnings, index$1 as filters, generateTaskId, groupWarningsBySeverity, parseAbsoluteDate, parseTask, parseTime, resolveRelativeDate, index as sorting, updateTask, updateTasks };
package/dist/index.d.ts CHANGED
@@ -21,6 +21,9 @@ interface ParsingContext {
21
21
  person?: string;
22
22
  currentDate?: Date;
23
23
  currentHeading?: string;
24
+ workdayStartTime?: string;
25
+ workdayEndTime?: string;
26
+ defaultDueTime?: 'start' | 'end';
24
27
  }
25
28
  interface TaskFilterCriteria {
26
29
  assignee?: string | string[];
@@ -47,11 +50,20 @@ interface ScanResult {
47
50
  parseErrors: number;
48
51
  };
49
52
  }
53
+ type WarningSeverity = 'info' | 'warning' | 'error';
54
+ type WarningCode = 'unsupported-bullet' | 'malformed-checkbox' | 'missing-space-after' | 'missing-space-before' | 'relative-date-no-context' | 'missing-due-date' | 'missing-completed-date' | 'duplicate-todoist-id' | 'file-read-error';
50
55
  interface Warning {
51
56
  file: string;
52
57
  line: number;
53
- text: string;
54
- reason: string;
58
+ column?: number;
59
+ severity: WarningSeverity;
60
+ source: 'md2do';
61
+ ruleId: WarningCode;
62
+ message: string;
63
+ text?: string;
64
+ url?: string;
65
+ /** @deprecated Use message instead */
66
+ reason?: string;
55
67
  }
56
68
 
57
69
  /**
@@ -113,10 +125,10 @@ declare function extractCompletedDate(text: string): Date | undefined;
113
125
  * Extract due date from task text with context awareness
114
126
  *
115
127
  * Handles both absolute dates ([due: 2026-01-25]) and relative dates
116
- * ([due: tomorrow]) which require context.
128
+ * ([due: tomorrow]) which require context. Supports optional time ([due: 2026-01-25 17:00]).
117
129
  *
118
130
  * @param text - Task text
119
- * @param context - Parsing context (for relative dates)
131
+ * @param context - Parsing context (for relative dates and workday config)
120
132
  * @returns Object with parsed date and optional warning
121
133
  */
122
134
  declare function extractDueDate(text: string, context: ParsingContext): {
@@ -223,15 +235,17 @@ declare const PRIORITY_HIGH: RegExp;
223
235
  */
224
236
  declare const PRIORITY_NORMAL: RegExp;
225
237
  /**
226
- * Matches absolute due date in ISO format [due: YYYY-MM-DD]
238
+ * Matches absolute due date in ISO format [due: YYYY-MM-DD] with optional time [due: YYYY-MM-DD HH:MM]
227
239
  *
228
240
  * Examples:
229
- * "[due: 2026-01-25]" → "2026-01-25"
230
- * "[due:2026-01-25]" → "2026-01-25" (spaces optional)
231
- * "[due: 2026-01-25 ]" → "2026-01-25" (whitespace allowed)
241
+ * "[due: 2026-01-25]" → "2026-01-25", undefined
242
+ * "[due: 2026-01-25 17:00]" → "2026-01-25", "17:00"
243
+ * "[due: 2026-01-25 9:00]" → "2026-01-25", "9:00"
244
+ * "[due:2026-01-25]" → "2026-01-25", undefined (spaces optional)
232
245
  *
233
246
  * Groups:
234
247
  * [1] - Date string in YYYY-MM-DD format
248
+ * [2] - Optional time string in H:MM or HH:MM format (24-hour)
235
249
  */
236
250
  declare const DUE_DATE_ABSOLUTE: RegExp;
237
251
  /**
@@ -391,9 +405,14 @@ declare class MarkdownScanner {
391
405
  *
392
406
  * @param filePath - Relative file path (for context extraction)
393
407
  * @param content - File content as string
408
+ * @param options - Optional scanner options including workday config
394
409
  * @returns Object containing tasks and warnings
395
410
  */
396
- scanFile(filePath: string, content: string): {
411
+ scanFile(filePath: string, content: string, options?: {
412
+ workdayStartTime?: string;
413
+ workdayEndTime?: string;
414
+ defaultDueTime?: 'start' | 'end';
415
+ }): {
397
416
  tasks: Task[];
398
417
  warnings: Warning[];
399
418
  };
@@ -812,8 +831,75 @@ declare namespace index {
812
831
  export { type index_TaskComparator as TaskComparator, index_byAssignee as byAssignee, index_byCompletionStatus as byCompletionStatus, index_byCreatedDate as byCreatedDate, index_byDueDate as byDueDate, index_byFile as byFile, index_byPerson as byPerson, index_byPriority as byPriority, index_byProject as byProject, index_combineComparators as combineComparators, index_reverse as reverse };
813
832
  }
814
833
 
834
+ interface WarningFilterConfig {
835
+ enabled?: boolean | undefined;
836
+ rules?: Record<string, 'error' | 'warn' | 'info' | 'off'> | undefined;
837
+ }
838
+ /**
839
+ * Filter warnings based on configuration rules
840
+ *
841
+ * This function applies warning configuration rules to filter out disabled warnings
842
+ * and optionally all warnings if globally disabled.
843
+ *
844
+ * @param warnings - Array of warnings to filter
845
+ * @param config - Warning configuration with enabled flag and rule overrides
846
+ * @returns Filtered array of warnings
847
+ *
848
+ * @example
849
+ * ```typescript
850
+ * const config = {
851
+ * enabled: true,
852
+ * rules: {
853
+ * 'missing-due-date': 'off',
854
+ * 'duplicate-todoist-id': 'error',
855
+ * },
856
+ * };
857
+ *
858
+ * const filtered = filterWarnings(allWarnings, config);
859
+ * // Returns warnings except those with ruleId 'missing-due-date'
860
+ * ```
861
+ */
862
+ declare function filterWarnings(warnings: Warning[], config?: WarningFilterConfig): Warning[];
863
+ /**
864
+ * Group warnings by severity
865
+ *
866
+ * Useful for displaying warnings in order of importance or
867
+ * treating errors differently from warnings.
868
+ *
869
+ * @param warnings - Array of warnings to group
870
+ * @returns Object with warnings grouped by severity level
871
+ *
872
+ * @example
873
+ * ```typescript
874
+ * const grouped = groupWarningsBySeverity(warnings);
875
+ * console.log(`${grouped.error.length} errors`);
876
+ * console.log(`${grouped.warning.length} warnings`);
877
+ * console.log(`${grouped.info.length} info messages`);
878
+ * ```
879
+ */
880
+ declare function groupWarningsBySeverity(warnings: Warning[]): {
881
+ error: Warning[];
882
+ warning: Warning[];
883
+ info: Warning[];
884
+ };
885
+
886
+ /**
887
+ * Parse a time string in HH:MM or H:MM format
888
+ *
889
+ * @param timeStr - Time string to parse (e.g., "17:00", "9:00")
890
+ * @returns Object with hours and minutes, or null if invalid
891
+ *
892
+ * @example
893
+ * parseTime("17:00") // => { hours: 17, minutes: 0 }
894
+ * parseTime("9:30") // => { hours: 9, minutes: 30 }
895
+ * parseTime("25:00") // => null (invalid hour)
896
+ */
897
+ declare function parseTime(timeStr: string): {
898
+ hours: number;
899
+ minutes: number;
900
+ } | null;
815
901
  /**
816
- * Parse an absolute date string in various formats
902
+ * Parse an absolute date string in various formats, with optional time
817
903
  *
818
904
  * Supported formats:
819
905
  * - ISO: 2026-01-25
@@ -821,9 +907,14 @@ declare namespace index {
821
907
  * - US full: 1/25/2026
822
908
  *
823
909
  * @param dateStr - Date string to parse
910
+ * @param timeStr - Optional time string in HH:MM or H:MM format
824
911
  * @returns Parsed Date object or null if invalid
912
+ *
913
+ * @example
914
+ * parseAbsoluteDate("2026-01-25") // => Date at midnight
915
+ * parseAbsoluteDate("2026-01-25", "17:00") // => Date at 5 PM
825
916
  */
826
- declare function parseAbsoluteDate(dateStr: string): Date | null;
917
+ declare function parseAbsoluteDate(dateStr: string, timeStr?: string): Date | null;
827
918
  /**
828
919
  * Resolve a relative date keyword against a base date
829
920
  *
@@ -876,4 +967,4 @@ declare function extractDateFromHeading(line: string): Date | null;
876
967
  */
877
968
  declare function generateTaskId(file: string, line: number, text: string): string;
878
969
 
879
- export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, index$1 as filters, generateTaskId, parseAbsoluteDate, parseTask, resolveRelativeDate, index as sorting, updateTask, updateTasks };
970
+ export { ASSIGNEE, COMPLETED_DATE, DUE_DATE_ABSOLUTE, DUE_DATE_RELATIVE, DUE_DATE_SHORT, HEADING_DATE_ISO, HEADING_DATE_NATURAL, HEADING_DATE_SLASH, MarkdownScanner, PATTERNS, PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_URGENT, type ParsingContext, type Priority, type ScanResult, TAG, TASK_CHECKBOX, TODOIST_ID, type Task, type TaskFilterCriteria, type UpdateTaskOptions, type Warning, type WarningCode, type WarningFilterConfig, type WarningSeverity, type WriteTaskResult, addTask, cleanTaskText, extractAssignee, extractCompletedDate, extractDateFromHeading, extractDueDate, extractPersonFromFilename, extractPriority, extractProjectFromPath, extractTags, extractTodoistId, filterWarnings, index$1 as filters, generateTaskId, groupWarningsBySeverity, parseAbsoluteDate, parseTask, parseTime, resolveRelativeDate, index as sorting, updateTask, updateTasks };
package/dist/index.js CHANGED
@@ -57,10 +57,13 @@ __export(index_exports, {
57
57
  extractProjectFromPath: () => extractProjectFromPath,
58
58
  extractTags: () => extractTags,
59
59
  extractTodoistId: () => extractTodoistId,
60
+ filterWarnings: () => filterWarnings,
60
61
  filters: () => filters_exports,
61
62
  generateTaskId: () => generateTaskId,
63
+ groupWarningsBySeverity: () => groupWarningsBySeverity,
62
64
  parseAbsoluteDate: () => parseAbsoluteDate,
63
65
  parseTask: () => parseTask,
66
+ parseTime: () => parseTime,
64
67
  resolveRelativeDate: () => resolveRelativeDate,
65
68
  sorting: () => sorting_exports,
66
69
  updateTask: () => updateTask,
@@ -74,7 +77,7 @@ var ASSIGNEE = /@([\w-]+)/;
74
77
  var PRIORITY_URGENT = /!!!/;
75
78
  var PRIORITY_HIGH = /!!/;
76
79
  var PRIORITY_NORMAL = /(?<!!)!(?!!)/;
77
- var DUE_DATE_ABSOLUTE = /\[due:\s*(\d{4}-\d{2}-\d{2})\s*\]/i;
80
+ var DUE_DATE_ABSOLUTE = /\[due:\s*(\d{4}-\d{2}-\d{2})(?:\s+(\d{1,2}:\d{2}))?\s*\]/i;
78
81
  var DUE_DATE_RELATIVE = /\[due:\s*(tomorrow|today|next\s+week|next\s+month)\]/i;
79
82
  var DUE_DATE_SHORT = /\[due:\s*(\d{1,2}\/\d{1,2}(?:\/\d{2,4})?)\]/i;
80
83
  var TAG = /#([\w-]+)/g;
@@ -102,15 +105,34 @@ var PATTERNS = {
102
105
 
103
106
  // src/utils/dates.ts
104
107
  var import_date_fns = require("date-fns");
105
- function parseAbsoluteDate(dateStr) {
108
+ function parseTime(timeStr) {
109
+ const match = timeStr.match(/^(\d{1,2}):(\d{2})$/);
110
+ if (!match || !match[1] || !match[2]) return null;
111
+ const hours = parseInt(match[1], 10);
112
+ const minutes = parseInt(match[2], 10);
113
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
114
+ return null;
115
+ }
116
+ return { hours, minutes };
117
+ }
118
+ function parseAbsoluteDate(dateStr, timeStr) {
106
119
  const referenceDate = /* @__PURE__ */ new Date();
107
120
  let date = (0, import_date_fns.parse)(dateStr, "yyyy-MM-dd", referenceDate);
108
- if ((0, import_date_fns.isValid)(date)) return date;
109
- date = (0, import_date_fns.parse)(dateStr, "M/d/yy", referenceDate);
110
- if ((0, import_date_fns.isValid)(date)) return date;
111
- date = (0, import_date_fns.parse)(dateStr, "M/d/yyyy", referenceDate);
112
- if ((0, import_date_fns.isValid)(date)) return date;
113
- return null;
121
+ if (!(0, import_date_fns.isValid)(date)) {
122
+ date = (0, import_date_fns.parse)(dateStr, "M/d/yy", referenceDate);
123
+ }
124
+ if (!(0, import_date_fns.isValid)(date)) {
125
+ date = (0, import_date_fns.parse)(dateStr, "M/d/yyyy", referenceDate);
126
+ }
127
+ if (!(0, import_date_fns.isValid)(date)) return null;
128
+ if (timeStr) {
129
+ const time = parseTime(timeStr);
130
+ if (time) {
131
+ date = (0, import_date_fns.setHours)(date, time.hours);
132
+ date = (0, import_date_fns.setMinutes)(date, time.minutes);
133
+ }
134
+ }
135
+ return date;
114
136
  }
115
137
  function resolveRelativeDate(relative, baseDate) {
116
138
  const normalized = relative.toLowerCase().trim();
@@ -202,12 +224,29 @@ function extractCompletedDate(text) {
202
224
  function extractDueDate(text, context) {
203
225
  const absoluteMatch = text.match(PATTERNS.DUE_DATE_ABSOLUTE);
204
226
  if (absoluteMatch?.[1]) {
205
- const date = parseAbsoluteDate(absoluteMatch[1]);
227
+ const dateStr = absoluteMatch[1];
228
+ let timeStr = absoluteMatch[2];
229
+ if (!timeStr && context.defaultDueTime) {
230
+ if (context.defaultDueTime === "start" && context.workdayStartTime) {
231
+ timeStr = context.workdayStartTime;
232
+ } else if (context.defaultDueTime === "end" && context.workdayEndTime) {
233
+ timeStr = context.workdayEndTime;
234
+ }
235
+ }
236
+ const date = parseAbsoluteDate(dateStr, timeStr);
206
237
  return { date: date ?? void 0 };
207
238
  }
208
239
  const shortMatch = text.match(PATTERNS.DUE_DATE_SHORT);
209
240
  if (shortMatch?.[1]) {
210
- const date = parseAbsoluteDate(shortMatch[1]);
241
+ let timeStr;
242
+ if (context.defaultDueTime) {
243
+ if (context.defaultDueTime === "start" && context.workdayStartTime) {
244
+ timeStr = context.workdayStartTime;
245
+ } else if (context.defaultDueTime === "end" && context.workdayEndTime) {
246
+ timeStr = context.workdayEndTime;
247
+ }
248
+ }
249
+ const date = parseAbsoluteDate(shortMatch[1], timeStr);
211
250
  return { date: date ?? void 0 };
212
251
  }
213
252
  const relativeMatch = text.match(PATTERNS.DUE_DATE_RELATIVE);
@@ -216,11 +255,15 @@ function extractDueDate(text, context) {
216
255
  return {
217
256
  date: void 0,
218
257
  warning: {
258
+ severity: "warning",
259
+ source: "md2do",
260
+ ruleId: "relative-date-no-context",
219
261
  file: "",
220
262
  // Will be filled in by caller
221
263
  line: 0,
222
264
  // Will be filled in by caller
223
265
  text: text.trim(),
266
+ message: "Relative due date without context date from heading. Add a heading with a date above this task.",
224
267
  reason: "Relative due date without context date from heading. Add a heading with a date above this task."
225
268
  }
226
269
  };
@@ -237,36 +280,52 @@ function parseTask(line, lineNumber, file, context) {
237
280
  const warnings = [];
238
281
  if (/^\s*[*+]\s+\[[ xX]\]/.test(line)) {
239
282
  warnings.push({
283
+ severity: "warning",
284
+ source: "md2do",
285
+ ruleId: "unsupported-bullet",
240
286
  file,
241
287
  line: lineNumber,
242
288
  text: line.trim(),
289
+ message: "Unsupported bullet marker (* or +). Use dash (-) for task lists.",
243
290
  reason: "Unsupported bullet marker (* or +). Use dash (-) for task lists."
244
291
  });
245
292
  return { task: null, warnings };
246
293
  }
247
294
  if (/^\s*-\s+\[[xX]\s+\]/.test(line) || /^\s*-\s+\[\s+[xX]\]/.test(line)) {
248
295
  warnings.push({
296
+ severity: "warning",
297
+ source: "md2do",
298
+ ruleId: "malformed-checkbox",
249
299
  file,
250
300
  line: lineNumber,
251
301
  text: line.trim(),
302
+ message: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces.",
252
303
  reason: "Malformed checkbox with extra spaces. Use [x] or [ ] without extra spaces."
253
304
  });
254
305
  return { task: null, warnings };
255
306
  }
256
307
  if (/^\s*-\s+\[[ xX]\][^\s]/.test(line)) {
257
308
  warnings.push({
309
+ severity: "warning",
310
+ source: "md2do",
311
+ ruleId: "missing-space-after",
258
312
  file,
259
313
  line: lineNumber,
260
314
  text: line.trim(),
315
+ message: 'Missing space after checkbox. Use "- [x] Task" format.',
261
316
  reason: 'Missing space after checkbox. Use "- [x] Task" format.'
262
317
  });
263
318
  return { task: null, warnings };
264
319
  }
265
320
  if (/^\s*-\[[ xX]\]/.test(line)) {
266
321
  warnings.push({
322
+ severity: "warning",
323
+ source: "md2do",
324
+ ruleId: "missing-space-before",
267
325
  file,
268
326
  line: lineNumber,
269
327
  text: line.trim(),
328
+ message: 'Missing space before checkbox. Use "- [x] Task" format.',
270
329
  reason: 'Missing space before checkbox. Use "- [x] Task" format.'
271
330
  });
272
331
  return { task: null, warnings };
@@ -292,17 +351,25 @@ function parseTask(line, lineNumber, file, context) {
292
351
  }
293
352
  if (!completed && !dueDateResult.date && !context.currentDate) {
294
353
  warnings.push({
354
+ severity: "info",
355
+ source: "md2do",
356
+ ruleId: "missing-due-date",
295
357
  file,
296
358
  line: lineNumber,
297
359
  text: fullText.trim(),
360
+ message: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date.",
298
361
  reason: "Task has no due date. Add [due: YYYY-MM-DD] or place under a heading with a date."
299
362
  });
300
363
  }
301
364
  if (completed && !completedDate) {
302
365
  warnings.push({
366
+ severity: "info",
367
+ source: "md2do",
368
+ ruleId: "missing-completed-date",
303
369
  file,
304
370
  line: lineNumber,
305
371
  text: fullText.trim(),
372
+ message: "Completed task missing completion date. Add [completed: YYYY-MM-DD].",
306
373
  reason: "Completed task missing completion date. Add [completed: YYYY-MM-DD]."
307
374
  });
308
375
  }
@@ -346,9 +413,10 @@ var MarkdownScanner = class {
346
413
  *
347
414
  * @param filePath - Relative file path (for context extraction)
348
415
  * @param content - File content as string
416
+ * @param options - Optional scanner options including workday config
349
417
  * @returns Object containing tasks and warnings
350
418
  */
351
- scanFile(filePath, content) {
419
+ scanFile(filePath, content, options) {
352
420
  const tasks = [];
353
421
  const warnings = [];
354
422
  const todoistIds = /* @__PURE__ */ new Map();
@@ -357,6 +425,15 @@ var MarkdownScanner = class {
357
425
  if (project !== void 0) context.project = project;
358
426
  const person = extractPersonFromFilename(filePath);
359
427
  if (person !== void 0) context.person = person;
428
+ if (options?.workdayStartTime) {
429
+ context.workdayStartTime = options.workdayStartTime;
430
+ }
431
+ if (options?.workdayEndTime) {
432
+ context.workdayEndTime = options.workdayEndTime;
433
+ }
434
+ if (options?.defaultDueTime) {
435
+ context.defaultDueTime = options.defaultDueTime;
436
+ }
360
437
  const lines = content.split("\n");
361
438
  for (let i = 0; i < lines.length; i++) {
362
439
  const line = lines[i];
@@ -375,9 +452,13 @@ var MarkdownScanner = class {
375
452
  const existing = todoistIds.get(result.task.todoistId);
376
453
  if (existing) {
377
454
  warnings.push({
455
+ severity: "error",
456
+ source: "md2do",
457
+ ruleId: "duplicate-todoist-id",
378
458
  file: filePath,
379
459
  line: lineNumber,
380
460
  text: result.task.text,
461
+ message: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`,
381
462
  reason: `Duplicate Todoist ID [todoist:${result.task.todoistId}]. Also found at ${existing.file}:${existing.line}.`
382
463
  });
383
464
  } else {
@@ -416,9 +497,13 @@ var MarkdownScanner = class {
416
497
  const existing = todoistIds.get(task.todoistId);
417
498
  if (existing && existing.file !== task.file) {
418
499
  allWarnings.push({
500
+ severity: "error",
501
+ source: "md2do",
502
+ ruleId: "duplicate-todoist-id",
419
503
  file: task.file,
420
504
  line: task.line,
421
505
  text: task.text,
506
+ message: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`,
422
507
  reason: `Duplicate Todoist ID [todoist:${task.todoistId}] across files. Also found at ${existing.file}:${existing.line}.`
423
508
  });
424
509
  } else if (!existing) {
@@ -867,6 +952,32 @@ function combineComparators(comparators) {
867
952
  function reverse(comparator) {
868
953
  return (a, b) => -comparator(a, b);
869
954
  }
955
+
956
+ // src/warnings/filter.ts
957
+ function filterWarnings(warnings, config = {}) {
958
+ if (config.enabled === false) {
959
+ return [];
960
+ }
961
+ if (!config.rules) {
962
+ return warnings;
963
+ }
964
+ return warnings.filter((warning) => {
965
+ const level = config.rules?.[warning.ruleId];
966
+ if (level === void 0) {
967
+ return true;
968
+ }
969
+ return level !== "off";
970
+ });
971
+ }
972
+ function groupWarningsBySeverity(warnings) {
973
+ return warnings.reduce(
974
+ (acc, warning) => {
975
+ acc[warning.severity].push(warning);
976
+ return acc;
977
+ },
978
+ { error: [], warning: [], info: [] }
979
+ );
980
+ }
870
981
  // Annotate the CommonJS export names for ESM import in node:
871
982
  0 && (module.exports = {
872
983
  ASSIGNEE,
@@ -896,10 +1007,13 @@ function reverse(comparator) {
896
1007
  extractProjectFromPath,
897
1008
  extractTags,
898
1009
  extractTodoistId,
1010
+ filterWarnings,
899
1011
  filters,
900
1012
  generateTaskId,
1013
+ groupWarningsBySeverity,
901
1014
  parseAbsoluteDate,
902
1015
  parseTask,
1016
+ parseTime,
903
1017
  resolveRelativeDate,
904
1018
  sorting,
905
1019
  updateTask,