tasknotes-spec 0.2.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/00-overview.md +172 -0
  2. package/01-terminology.md +156 -0
  3. package/02-model-and-mapping.md +288 -0
  4. package/03-temporal-semantics.md +290 -0
  5. package/04-recurrence.md +398 -0
  6. package/05-operations.md +968 -0
  7. package/06-validation.md +267 -0
  8. package/07-conformance.md +292 -0
  9. package/08-compatibility-and-migrations.md +188 -0
  10. package/09-configuration.md +837 -0
  11. package/10-dependencies-and-reminders.md +266 -0
  12. package/11-links.md +373 -0
  13. package/CHANGELOG.md +25 -0
  14. package/README.md +80 -0
  15. package/conformance/README.md +31 -0
  16. package/conformance/adapters/mdbase-tasknotes.adapter.mjs +141 -0
  17. package/conformance/adapters/tasknotes-core/conformance.ts +2498 -0
  18. package/conformance/adapters/tasknotes-core/create-compat.ts +1 -0
  19. package/conformance/adapters/tasknotes-core/date.ts +12 -0
  20. package/conformance/adapters/tasknotes-core/field-mapping.ts +14 -0
  21. package/conformance/adapters/tasknotes-core/recurrence.ts +10 -0
  22. package/conformance/adapters/tasknotes-date-bridge.ts +20 -0
  23. package/conformance/adapters/tasknotes-runtime-bridge.ts +1107 -0
  24. package/conformance/adapters/tasknotes-runtime-obsidian-shim.ts +84 -0
  25. package/conformance/adapters/tasknotes-templating-bridge.ts +13 -0
  26. package/conformance/adapters/tasknotes.adapter.mjs +485 -0
  27. package/conformance/docs/ADAPTER_CONTRACT.md +245 -0
  28. package/conformance/docs/FIXTURE_FORMAT.md +247 -0
  29. package/conformance/docs/RUNNER_GUIDE.md +393 -0
  30. package/conformance/fixtures/config-schema.json +634 -0
  31. package/conformance/fixtures/config.json +18984 -0
  32. package/conformance/fixtures/conformance.json +444 -0
  33. package/conformance/fixtures/create-compat.json +18639 -0
  34. package/conformance/fixtures/date.json +25612 -0
  35. package/conformance/fixtures/dependencies.json +8647 -0
  36. package/conformance/fixtures/field-mapping.json +5406 -0
  37. package/conformance/fixtures/links.json +1127 -0
  38. package/conformance/fixtures/migrations.json +668 -0
  39. package/conformance/fixtures/operations.json +2761 -0
  40. package/conformance/fixtures/recurrence.json +22958 -0
  41. package/conformance/fixtures/reminders.json +13333 -0
  42. package/conformance/fixtures/templating.json +497 -0
  43. package/conformance/fixtures/validation.json +5308 -0
  44. package/conformance/lib/adapter-loader.mjs +23 -0
  45. package/conformance/lib/load-fixtures.mjs +43 -0
  46. package/conformance/lib/matchers.mjs +200 -0
  47. package/conformance/manifest.json +232 -0
  48. package/conformance/scripts/generate-fixtures.mjs +5239 -0
  49. package/conformance/scripts/package-fixtures.mjs +101 -0
  50. package/conformance/tests/coverage.test.mjs +213 -0
  51. package/conformance/tests/runner.test.mjs +102 -0
  52. package/conformance/tests/tasknotes-runtime-routing.test.mjs +64 -0
  53. package/package.json +49 -0
@@ -0,0 +1,837 @@
1
+ # 9. Configuration
2
+
3
+ ## 9.1 Purpose
4
+
5
+ This section defines the normative configuration model for collections using `tasknotes-spec`.
6
+
7
+ ## 9.2 Configuration provider model
8
+
9
+ Implementations MUST derive an **effective configuration** from one or more configuration providers.
10
+
11
+ A provider is a source adapter that returns a partial configuration normalized to this section's schema.
12
+
13
+ Implementations MUST:
14
+
15
+ - support at least one provider,
16
+ - document supported providers,
17
+ - document provider precedence order,
18
+ - disclose active providers and fallback behavior in conformance output (§7).
19
+
20
+ ### 9.2.1 Common provider types
21
+
22
+ Common providers include:
23
+
24
+ - `yaml_file`: a `tasknotes.yaml` document at collection root,
25
+ - `tasknotes_plugin_data_json`: `.obsidian/plugins/tasknotes/data.json`,
26
+ - `built_in_defaults`: implementation defaults,
27
+ - optional runtime/CLI/env overrides.
28
+
29
+ This list is not exhaustive.
30
+
31
+ ### 9.2.2 Deterministic precedence and merge
32
+
33
+ Provider precedence MUST be deterministic.
34
+
35
+ Unless explicitly documented otherwise, implementations MUST resolve configuration per top-level key:
36
+
37
+ - for each key in §9.3/§9.4, the highest-precedence provider that supplies that key wins,
38
+ - winning object values replace lower-precedence object values as whole objects (no implicit deep merge),
39
+ - after top-level selection, implementations MUST apply documented schema defaults for missing nested keys before exposing effective configuration.
40
+
41
+ If an implementation supports deep-merge behavior, it MUST be explicitly documented and deterministic.
42
+
43
+ ### 9.2.3 Strict vs permissive provider behavior
44
+
45
+ In strict mode:
46
+
47
+ - failure to resolve required effective keys (`spec_version`, `mapping`) MUST be a configuration error.
48
+
49
+ If permissive mode is implemented, then in permissive mode:
50
+
51
+ - implementations MAY continue with defaults when providers are missing/unreadable,
52
+ - implementations MUST emit configuration warnings,
53
+ - implementations MUST disclose that effective configuration is partial/default-derived.
54
+
55
+ ### 9.2.4 TaskNotes plugin provider (`data.json`)
56
+
57
+ The TaskNotes plugin stores settings in `.obsidian/plugins/tasknotes/data.json` within the vault root. When using this file as a configuration provider, normalization MUST apply the field mapping in the table below.
58
+
59
+ **Key mapping table** (`data.json` key → spec effective config):
60
+
61
+ | `data.json` key | Spec key | Notes |
62
+ |---|---|---|
63
+ | `fieldMapping` | `mapping` | Normalize role names: `dateCreated`→`date_created`, `completedDate`→`completed_date`, `recurrenceAnchor`→`recurrence_anchor`, `completeInstances`→`complete_instances`, `skippedInstances`→`skipped_instances`, `recurrenceParent`→`recurrence_parent`, `occurrenceDate`→`occurrence_date`, `occurrenceMaterialization`→`occurrence_materialization`, `occurrenceNextTrigger`→`occurrence_next_trigger`, `occurrenceTemplate`→`occurrence_template`, `occurrencePastHorizon`→`occurrence_past_horizon`, `occurrenceFutureHorizon`→`occurrence_future_horizon`, `blockedBy`→`blocked_by`, `timeEntries`→`time_entries`, `timeEstimate`→`time_estimate` |
64
+ | `storeTitleInFilename` | `title.storage` | `true` → `"filename"`, `false` → `"frontmatter"` |
65
+ | `taskFilenameFormat` | `title.filename_format` | Values: `"title"`, `"zettel"`, `"timestamp"`, `"custom"`; used only when `title.storage=frontmatter` for canonical writes, but preserved as compatibility input when present |
66
+ | `customFilenameTemplate` | `title.custom_filename_template` | Template string, e.g. `"{title}"`; used only when `title.storage=frontmatter` and `filename_format=custom` |
67
+ | `taskCreationDefaults.useBodyTemplate` | `templating.enabled` | boolean |
68
+ | `taskCreationDefaults.bodyTemplate` | `templating.template_path` | Path to template file |
69
+ | `customStatuses[*].value` | `status.values` | Ordered array of status value strings |
70
+ | `defaultTaskStatus` | `status.default` | Default status string |
71
+ | `customStatuses[isCompleted=true][*].value` | `status.completed_values` | Statuses where `isCompleted: true` |
72
+ | `defaultTaskPriority` | `defaults.priority` | Default priority string |
73
+ | `autoStopTimeTrackingOnComplete` | `time_tracking.auto_stop_on_complete` | boolean |
74
+ | `autoStopTimeTrackingNotification` | `time_tracking.auto_stop_notification` | boolean |
75
+ | `taskIdentificationMethod` | `task_detection.method` | `"tag"` or `"property"` |
76
+ | `taskTag` | `task_detection.tag` | Tag string used when `method="tag"` |
77
+ | `taskPropertyName` | `task_detection.property_name` | Frontmatter key used when `method="property"` |
78
+ | `taskPropertyValue` | `task_detection.property_value` | Expected value; empty means key must exist |
79
+ | `tasksFolder` | `task_detection.default_folder` | Default folder for new tasks |
80
+ | `excludedFolders` | `task_detection.excluded_folders` | Comma-separated folder paths to exclude |
81
+ | `moveArchivedTasks` | `archive.move_on_archive` | boolean |
82
+ | `archiveFolder` | `archive.folder` | Archive destination folder |
83
+ | `useFrontmatterMarkdownLinks` | `links.use_markdown_format` | boolean; see §11 |
84
+
85
+ **Key `data.json` fields with defaults** (for cold-start reading of a vault with no `tasknotes.yaml`):
86
+
87
+ | Field | Type | Default | Meaning |
88
+ |---|---|---|---|
89
+ | `tasksFolder` | string | `"TaskNotes/Tasks"` | Default folder for new task files |
90
+ | `taskIdentificationMethod` | `"tag" \| "property"` | `"tag"` | How task files are identified |
91
+ | `taskTag` | string | `"task"` | Tag that identifies task files (when `method="tag"`) |
92
+ | `taskPropertyName` | string | `""` | Frontmatter key for property-based identification |
93
+ | `taskPropertyValue` | string | `""` | Expected value for property-based identification (empty = key presence) |
94
+ | `storeTitleInFilename` | boolean | `true` | Title storage mode |
95
+ | `taskFilenameFormat` | `"title" \| "zettel" \| "timestamp" \| "custom"` | `"title"` | Filename generation format |
96
+ | `customFilenameTemplate` | string | `"{title}"` | Template for `"custom"` filename format |
97
+ | `defaultTaskStatus` | string | `"open"` | Default status on create |
98
+ | `defaultTaskPriority` | string | `"normal"` | Default priority on create |
99
+ | `customStatuses` | StatusConfig[] | see object shape below | Ordered status definitions |
100
+ | `autoStopTimeTrackingOnComplete` | boolean | `true` | Auto-stop active session on completion |
101
+ | `autoStopTimeTrackingNotification` | boolean | `false` | Show notification when auto-stopping |
102
+ | `useFrontmatterMarkdownLinks` | boolean | `false` | Use markdown links in frontmatter (requires obsidian-frontmatter-markdown-links plugin) |
103
+ | `moveArchivedTasks` | boolean | `false` | Move task file to archive folder on archive |
104
+ | `archiveFolder` | string | `"TaskNotes/Archive"` | Archive destination folder |
105
+ | `excludedFolders` | string | `""` | Comma-separated folders to exclude from task indexing |
106
+
107
+ **StatusConfig object shape:**
108
+
109
+ ```json
110
+ {
111
+ "id": "done",
112
+ "value": "done",
113
+ "label": "Done",
114
+ "color": "#00aa00",
115
+ "isCompleted": true,
116
+ "order": 3,
117
+ "autoArchive": false,
118
+ "autoArchiveDelay": 5
119
+ }
120
+ ```
121
+
122
+ `spec_version` is not present in `data.json`. Implementations MUST synthesize an effective `spec_version` as described in §9.5.
123
+
124
+ Provider keys not listed above MAY be ignored by `tasknotes-spec` consumers.
125
+
126
+ ## 9.3 Required top-level keys
127
+
128
+ Required keys apply to the effective configuration after provider resolution.
129
+
130
+ | Key | Type | Required | Description |
131
+ |---|---|---|---|
132
+ | `spec_version` | string | yes | target spec version (provider-supplied or synthesized per §9.5) |
133
+ | `mapping` | object | yes | semantic role to storage key mapping |
134
+
135
+ ## 9.4 Recommended top-level keys
136
+
137
+ | Key | Type | Description |
138
+ |---|---|---|
139
+ | `runtime_timezone` | string | IANA timezone for day-level semantics |
140
+ | `task_detection` | object | task file identification rules |
141
+ | `defaults` | object | role defaults used during create |
142
+ | `status` | object | status set and completed-value semantics |
143
+ | `validation` | object | validation behavior (`strict` required, `permissive` optional) |
144
+ | `links` | object | link parsing/resolution behavior |
145
+ | `title` | object | title source and filename behavior policy |
146
+ | `templating` | object | optional create-time templating behavior policy |
147
+ | `dependencies` | object | dependency behavior policy |
148
+ | `reminders` | object | reminder behavior policy |
149
+ | `time_tracking` | object | time-tracking management behavior policy |
150
+ | `occurrences` | object | recurrence materialization defaults and bounds |
151
+ | `compatibility` | object | legacy alias/behavior switches |
152
+
153
+ ## 9.5 spec_version behavior
154
+
155
+ `spec_version` MUST be a semantic version string.
156
+ Pre-release identifiers are valid (for example `0.2.0-draft`).
157
+
158
+ Implementations in strict mode MUST reject unsupported major versions.
159
+
160
+ If permissive mode is implemented, permissive mode MAY proceed with warning when major version differs.
161
+
162
+ If a provider does not supply `spec_version` (for example TaskNotes `data.json`), implementations MUST synthesize an effective `spec_version` matching their target `tasknotes-spec` version and SHOULD disclose that it was synthesized.
163
+
164
+ ### 9.5.1 runtime_timezone behavior
165
+
166
+ If `runtime_timezone` is configured, it MUST be a valid IANA timezone identifier and MUST be used for day-level semantics.
167
+
168
+ If `runtime_timezone` is absent, implementations MUST use system local timezone.
169
+ The effective timezone MUST be discoverable.
170
+
171
+ ## 9.6 mapping schema
172
+
173
+ `mapping` MUST map semantic roles (from §2) to string storage keys.
174
+
175
+ Minimum required roles in mapping:
176
+
177
+ - `title`
178
+ - `status`
179
+ - `completed_date`
180
+ - `date_created`
181
+ - `date_modified`
182
+
183
+ If semantic role `id` is supported (§2.6.5), mapping SHOULD include `id`.
184
+ If profile `materialized-occurrences` is claimed, mapping MUST include `recurrence_parent` and `occurrence_date`, and SHOULD include parent policy roles (`occurrence_materialization`, `occurrence_next_trigger`, `occurrence_template`, `occurrence_past_horizon`, `occurrence_future_horizon`).
185
+
186
+ Example:
187
+
188
+ ```yaml
189
+ mapping:
190
+ title: title
191
+ status: status
192
+ date_created: dateCreated
193
+ date_modified: dateModified
194
+ completed_date: completedDate
195
+ recurrence: recurrence
196
+ recurrence_anchor: recurrenceAnchor
197
+ complete_instances: completeInstances
198
+ skipped_instances: skippedInstances
199
+ recurrence_parent: recurrence_parent
200
+ occurrence_date: occurrence_date
201
+ occurrence_materialization: occurrence_materialization
202
+ occurrence_next_trigger: occurrence_next_trigger
203
+ occurrence_template: occurrence_template
204
+ occurrence_past_horizon: occurrence_past_horizon
205
+ occurrence_future_horizon: occurrence_future_horizon
206
+ time_entries: timeEntries
207
+ blocked_by: blockedBy
208
+ reminders: reminders
209
+ ```
210
+
211
+ ## 9.7 task_detection schema
212
+
213
+ `task_detection` controls how task files are identified within the vault.
214
+
215
+ ### Detection methods
216
+
217
+ The tasknotes plugin natively supports two identification methods, controlled by `taskIdentificationMethod` in `data.json` (mapped to `task_detection.method`):
218
+
219
+ **`tag` (default)** — A markdown file is a task if it contains the configured tag (default `#task`) in its frontmatter `tags` array or inline in the body. Configured by `task_detection.tag`.
220
+
221
+ **`property`** — A markdown file is a task if a specified frontmatter property has a specified value (or exists, if value is empty). Configured by `task_detection.property_name` and `task_detection.property_value`.
222
+
223
+ Schema and selection rules:
224
+
225
+ 1. `task_detection` MAY specify either:
226
+ - `method` (single-method form), or
227
+ - `methods` (multi-method form; non-empty list).
228
+ 2. If both are present, `methods` MUST take precedence and implementations SHOULD emit a configuration warning.
229
+ 3. If neither is present, effective detection method MUST default to `tag`.
230
+ 4. `method` MUST be `tag` or `property`.
231
+ 5. `methods` entries MUST be unique and MUST be from:
232
+ - `tag`
233
+ - `property`
234
+ - `field_presence`
235
+ - `field_match`
236
+ 6. Method-specific required keys:
237
+ - when `tag` is enabled, effective `task_detection.tag` MUST be non-empty (default `task` when absent);
238
+ - when `property` is enabled, `task_detection.property_name` MUST be provided and non-empty; `task_detection.property_value` MAY be empty (empty means key-exists semantics).
239
+ 7. If `methods` has more than one entry, `task_detection.combine` controls combination semantics (§9.7.3). If omitted, it MUST default to `or`.
240
+
241
+ ### 9.7.1 Tag matching semantics
242
+
243
+ When `task_detection.method=tag`, matching MUST follow these rules:
244
+
245
+ 1. Normalize configured `task_detection.tag` by stripping one leading `#` when present.
246
+ 2. Compare tag names case-insensitively after normalization.
247
+ 3. Frontmatter matching:
248
+ - `tags` MAY be a list of strings or a single string.
249
+ - each tag value is normalized by trimming surrounding whitespace and stripping one leading `#`.
250
+ - match requires exact normalized equality (for example `task` matches `#task`; `task` does not match `tasking`).
251
+ 4. Body matching:
252
+ - only markdown text outside fenced code blocks and inline code spans is considered,
253
+ - a match requires a hashtag token with exact normalized name (for example `#task`),
254
+ - partial-word matches MUST NOT count (for example `#tasking` does not match configured `task`).
255
+
256
+ If either frontmatter or body matches, the file is identified as a task.
257
+
258
+ ### 9.7.2 Property matching semantics
259
+
260
+ When property detection is enabled (`method=property` or `methods` includes `property`):
261
+
262
+ 1. `task_detection.property_name` MUST name the frontmatter key to inspect.
263
+ 2. If `task_detection.property_value` is non-empty, match requires semantic equality with the configured value.
264
+ 3. If `task_detection.property_value` is empty or absent, match requires key presence only.
265
+
266
+ ### 9.7.3 Multi-method extension semantics
267
+
268
+ `tasknotes.yaml`-level providers MAY additionally support:
269
+
270
+ - `field_presence` (example: required key `status`)
271
+ - `field_match` (example: `type == "task"`)
272
+
273
+ Extension-key shape:
274
+
275
+ - `field_presence`: string or list<string> of required frontmatter keys.
276
+ - `field_match`: object map of `key -> expected scalar value` for exact semantic equality checks.
277
+
278
+ If multiple methods are configured in `tasknotes.yaml`, `task_detection.combine` MUST define combinator semantics:
279
+
280
+ - `or` (default): file is a task if any enabled method matches.
281
+ - `and`: file is a task only if all enabled methods match.
282
+
283
+ If `task_detection.combine` is absent, implementations MUST default to `or`.
284
+
285
+ Implementations MUST exclude folders listed in `task_detection.excluded_folders` from task indexing.
286
+
287
+ Executable conformance: adapters participating in the fixture suite SHOULD expose `config.detect_task_file` to evaluate these detection semantics against fixture inputs.
288
+
289
+ Example (`tasknotes.yaml`, equivalent to tag-based default):
290
+
291
+ ```yaml
292
+ task_detection:
293
+ method: tag
294
+ tag: task
295
+ default_folder: TaskNotes/Tasks
296
+ ```
297
+
298
+ Example (property-based):
299
+
300
+ ```yaml
301
+ task_detection:
302
+ method: property
303
+ property_name: type
304
+ property_value: task
305
+ ```
306
+
307
+ ## 9.8 defaults schema
308
+
309
+ `defaults` defines values used at create time when caller input omits a role.
310
+
311
+ Defaults MUST NOT override explicit user input.
312
+
313
+ Example:
314
+
315
+ ```yaml
316
+ defaults:
317
+ status: open
318
+ priority: normal
319
+ recurrence_anchor: scheduled
320
+ reminders:
321
+ - id: due_minus_1d
322
+ type: relative
323
+ relatedTo: due
324
+ offset: -P1D
325
+ ```
326
+
327
+ `defaults.reminders` behavior is defined in §10.3.9.
328
+
329
+ ## 9.9 status schema
330
+
331
+ `status` defines known status values and completion semantics.
332
+
333
+ Example:
334
+
335
+ ```yaml
336
+ status:
337
+ values: [open, in-progress, done, cancelled]
338
+ default: open
339
+ completed_values: [done]
340
+ skipped_values: [cancelled]
341
+ default_skipped: cancelled
342
+ ```
343
+
344
+ Rules:
345
+
346
+ - `default` MUST be one of `values`.
347
+ - `completed_values` MUST be a non-empty list.
348
+ - Each `completed_values` entry MUST be in `values`.
349
+ - `skipped_values`, when present, MUST be a list whose entries are in `values`.
350
+ - `default_skipped`, when present, MUST be in `skipped_values`.
351
+ - For implementations claiming `materialized-occurrences`, `skipped_values` MUST NOT overlap `completed_values`.
352
+ - Implementations claiming `materialized-occurrences` MUST provide at least one skipped value or fail skip of materialized occurrence notes deterministically (§5.20.3).
353
+ - Non-recurring completion MUST use this list, not hardcoded literals.
354
+ - When a complete operation does not provide an explicit target status, writers MUST use the first entry of `completed_values`.
355
+ - When a materialized occurrence skip operation does not provide an explicit skipped status, writers MUST use `default_skipped` when present, otherwise the first entry of `skipped_values`.
356
+
357
+ ## 9.10 validation schema
358
+
359
+ Example:
360
+
361
+ ```yaml
362
+ validation:
363
+ mode: strict
364
+ reject_unknown_fields: false
365
+ ```
366
+
367
+ Rules:
368
+
369
+ - `mode` MUST be `strict` or `permissive` when present.
370
+ - If `mode` is absent, effective mode MUST default to `strict`.
371
+ - Conforming implementations MUST support `strict`.
372
+ - Implementations MAY support `permissive`.
373
+ - If `mode=permissive` is configured but not implemented, configuration MUST fail in strict mode and SHOULD emit warning/fallback in permissive mode.
374
+ - `reject_unknown_fields=true` enforces closed-schema behavior for mapped roles.
375
+
376
+ ## 9.11 dependencies schema
377
+
378
+ Example:
379
+
380
+ ```yaml
381
+ dependencies:
382
+ default_reltype: FINISHTOSTART
383
+ treat_missing_target_as_blocked: true
384
+ enforce_unique_uid: true
385
+ unresolved_target_severity: warning
386
+ require_resolved_uid_on_write: false
387
+ ```
388
+
389
+ Rules:
390
+
391
+ - `default_reltype` MUST be one of the allowed reltype values in §10.2.
392
+ - `treat_missing_target_as_blocked` controls whether missing/unresolvable dependency targets contribute to blocked-state evaluation (§10.2.5).
393
+ - If `treat_missing_target_as_blocked` is absent, effective value MUST default to `true`.
394
+ - `enforce_unique_uid=true` enforces uniqueness at validation/write time.
395
+ - If `enforce_unique_uid` is absent, effective value MUST default to `true`.
396
+ - `unresolved_target_severity` MUST be `warning` or `error`.
397
+ - `require_resolved_uid_on_write=true` requires dependency UID resolution success for add/update operations.
398
+
399
+ ## 9.12 links schema
400
+
401
+ Example:
402
+
403
+ ```yaml
404
+ links:
405
+ extensions: [".md"]
406
+ unresolved_default_severity: warning
407
+ update_references_on_rename: true
408
+ ```
409
+
410
+ Rules:
411
+
412
+ - `extensions` defines extension trial order for extensionless targets.
413
+ - `unresolved_default_severity` MUST be `warning` or `error`.
414
+ - `update_references_on_rename=true` enables rename-time link rewrite behavior when supported.
415
+
416
+ ## 9.13 title schema
417
+
418
+ Example (`storage=frontmatter`):
419
+
420
+ ```yaml
421
+ title:
422
+ storage: frontmatter
423
+ filename_format: custom
424
+ custom_filename_template: "{{date}} {{title}}"
425
+ ```
426
+
427
+ Example (`storage=filename`):
428
+
429
+ ```yaml
430
+ title:
431
+ storage: filename
432
+ ```
433
+
434
+ Rules:
435
+
436
+ - `storage` MUST be `frontmatter` or `filename`.
437
+ - when `storage=frontmatter`, `filename_format` MUST be `title`, `zettel`, `timestamp`, or `custom`.
438
+ - when `storage=frontmatter` and `filename_format=custom`, `custom_filename_template` MUST be provided and non-empty.
439
+ - when `storage=filename`, `filename_format` and `custom_filename_template` MAY be present for compatibility input but MUST be ignored for canonical write behavior.
440
+ - Read precedence MUST be storage-mode-aware:
441
+ 1. when `storage=frontmatter`, use mapped `title` key when present and non-empty, then file basename fallback;
442
+ 2. when `storage=filename`, use file basename first, then mapped `title` fallback when basename is unavailable.
443
+ - If frontmatter and basename titles both exist and differ, the source authoritative for active `storage` MUST win.
444
+ - If `storage=filename`, canonical writes MUST treat filename as title source, MUST rename on title change, and MUST ignore `filename_format` and `custom_filename_template`.
445
+ - If `storage=frontmatter`, canonical writes MUST persist mapped title key; `filename_format` and `custom_filename_template` govern create-time filename generation.
446
+
447
+ Informative mapping to TaskNotes settings:
448
+
449
+ - `title.storage=filename` corresponds to `storeTitleInFilename=true`.
450
+ - `title.filename_format` corresponds to `taskFilenameFormat`.
451
+ - `title.custom_filename_template` corresponds to `customFilenameTemplate`.
452
+
453
+ ## 9.14 templating schema
454
+
455
+ `templating` defines optional create-time template behavior.
456
+ This schema is required only for implementations claiming profile `templating` (§7.3.3).
457
+
458
+ Example:
459
+
460
+ ```yaml
461
+ templating:
462
+ enabled: true
463
+ template_path: Templates/Task.md
464
+ failure_mode: warning_fallback
465
+ unknown_variable_policy: preserve
466
+ ```
467
+
468
+ Rules:
469
+
470
+ - `enabled` MUST be boolean.
471
+ - If `enabled=true`, `template_path` MUST be provided and non-empty.
472
+ - `failure_mode` MUST be `error` or `warning_fallback` when present.
473
+ - `unknown_variable_policy` MUST be `preserve` or `empty` when present.
474
+ - If `failure_mode` is absent, effective value MUST default to `warning_fallback`.
475
+ - If `unknown_variable_policy` is absent, effective value MUST default to `preserve`.
476
+ - Implementations claiming `templating` MUST support portable double-brace variables from §5.3.5.
477
+ - Implementations not claiming `templating` MAY ignore `templating` settings.
478
+
479
+ ## 9.15 reminders schema
480
+
481
+ Example:
482
+
483
+ ```yaml
484
+ reminders:
485
+ date_only_anchor_time: "00:00"
486
+ apply_defaults_when_explicit: false
487
+ ```
488
+
489
+ Rules:
490
+
491
+ - `date_only_anchor_time` MUST be `HH:MM` 24-hour local time format.
492
+ - `date_only_anchor_time` is used by §10.3.4 for relative reminders against date-only bases.
493
+ - `apply_defaults_when_explicit` MUST be boolean when present.
494
+ - if `apply_defaults_when_explicit` is absent, effective value MUST default to `false`.
495
+ - `apply_defaults_when_explicit=false` means explicit input reminders replace default-reminder application at create time.
496
+ - `apply_defaults_when_explicit=true` means explicit create-time reminders are merged with defaults using §10.3.9 deterministic merge rules.
497
+
498
+ ## 9.16 time_tracking schema
499
+
500
+ Example:
501
+
502
+ ```yaml
503
+ time_tracking:
504
+ auto_stop_on_complete: true
505
+ auto_stop_notification: false
506
+ ```
507
+
508
+ Rules:
509
+
510
+ - `auto_stop_on_complete` MUST be boolean when present.
511
+ - `auto_stop_notification` MUST be boolean when present.
512
+ - if `auto_stop_on_complete=true`, completion-triggered stop behavior MUST follow §5.19.5.
513
+ - if `auto_stop_notification` is absent, effective value MUST default to `false`.
514
+
515
+ ## 9.17 occurrences schema
516
+
517
+ `occurrences` defines collection-level defaults for recurrence materialization.
518
+ This schema is required only for implementations claiming profile `materialized-occurrences` (§7.3.4).
519
+
520
+ Example:
521
+
522
+ ```yaml
523
+ occurrences:
524
+ default_materialization: manual
525
+ default_next_trigger: completion
526
+ past_horizon: P0D
527
+ future_horizon: P14D
528
+ ```
529
+
530
+ Rules:
531
+
532
+ - `default_materialization` MUST be one of `manual`, `on_completion`, or `rolling`.
533
+ - If `default_materialization` is absent, effective value MUST default to `manual`.
534
+ - `default_next_trigger` MUST be `completion` or `completion_or_skip`.
535
+ - If `default_next_trigger` is absent, effective value MUST default to `completion`.
536
+ - `past_horizon` and `future_horizon`, when present, MUST be ISO 8601 duration strings.
537
+ - If `default_materialization=rolling`, implementations MUST use finite rolling bounds. If bounds are absent, documented finite defaults MUST be used.
538
+ - Parent task fields override collection defaults: `occurrence_materialization` overrides `occurrences.default_materialization`; `occurrence_next_trigger` overrides `occurrences.default_next_trigger`; `occurrence_past_horizon` and `occurrence_future_horizon` override `occurrences.past_horizon` and `occurrences.future_horizon`.
539
+ - If `occurrences` is absent, the effective behavior is `manual` materialization and `completion` next trigger.
540
+
541
+ ## 9.18 compatibility schema
542
+
543
+ Example:
544
+
545
+ ```yaml
546
+ compatibility:
547
+ read_aliases: true
548
+ legacy_duration_field: true
549
+ legacy_local_datetime_input: false
550
+ ```
551
+
552
+ Rules:
553
+
554
+ - Compatibility flags MUST default to conservative behavior in new collections.
555
+ - `legacy_local_datetime_input=true` MAY relax strict parsing for offset-less datetimes only in permissive mode; strict mode parsing rules in §3.4.2 still apply.
556
+ - Enabled compatibility flags SHOULD be disclosed in conformance output (§7).
557
+
558
+ ## 9.19 Complete configuration example (`yaml_file` provider)
559
+
560
+ ```yaml
561
+ spec_version: 0.2.0-draft
562
+ runtime_timezone: America/Los_Angeles
563
+
564
+ mapping:
565
+ title: title
566
+ status: status
567
+ priority: priority
568
+ due: due
569
+ scheduled: scheduled
570
+ completed_date: completedDate
571
+ date_created: dateCreated
572
+ date_modified: dateModified
573
+ recurrence: recurrence
574
+ recurrence_anchor: recurrenceAnchor
575
+ complete_instances: completeInstances
576
+ skipped_instances: skippedInstances
577
+ recurrence_parent: recurrence_parent
578
+ occurrence_date: occurrence_date
579
+ occurrence_materialization: occurrence_materialization
580
+ occurrence_next_trigger: occurrence_next_trigger
581
+ occurrence_template: occurrence_template
582
+ occurrence_past_horizon: occurrence_past_horizon
583
+ occurrence_future_horizon: occurrence_future_horizon
584
+ time_entries: timeEntries
585
+ blocked_by: blockedBy
586
+ reminders: reminders
587
+
588
+ task_detection:
589
+ method: tag
590
+ tag: task
591
+ default_folder: TaskNotes/Tasks
592
+
593
+ defaults:
594
+ status: open
595
+ priority: normal
596
+ recurrence_anchor: scheduled
597
+ reminders:
598
+ - id: due_minus_1d
599
+ type: relative
600
+ relatedTo: due
601
+ offset: -P1D
602
+
603
+ status:
604
+ values: [open, in-progress, done, cancelled]
605
+ default: open
606
+ completed_values: [done]
607
+ skipped_values: [cancelled]
608
+ default_skipped: cancelled
609
+
610
+ validation:
611
+ mode: strict
612
+ reject_unknown_fields: false
613
+
614
+ dependencies:
615
+ default_reltype: FINISHTOSTART
616
+ treat_missing_target_as_blocked: true
617
+ enforce_unique_uid: true
618
+ unresolved_target_severity: warning
619
+ require_resolved_uid_on_write: false
620
+
621
+ links:
622
+ extensions: [".md"]
623
+ unresolved_default_severity: warning
624
+ update_references_on_rename: true
625
+
626
+ title:
627
+ storage: filename
628
+
629
+ templating:
630
+ enabled: false
631
+ failure_mode: warning_fallback
632
+ unknown_variable_policy: preserve
633
+
634
+ reminders:
635
+ date_only_anchor_time: "00:00"
636
+ apply_defaults_when_explicit: false
637
+
638
+ time_tracking:
639
+ auto_stop_on_complete: true
640
+ auto_stop_notification: false
641
+
642
+ occurrences:
643
+ default_materialization: manual
644
+ default_next_trigger: completion
645
+ past_horizon: P0D
646
+ future_horizon: P14D
647
+
648
+ compatibility:
649
+ read_aliases: true
650
+ legacy_duration_field: true
651
+ ```
652
+
653
+ ## 9.20 Configuration errors
654
+
655
+ Configuration validation MUST report structured errors with key path context.
656
+
657
+ Examples:
658
+
659
+ - no readable configuration provider available in strict mode
660
+ - required effective key unresolved after provider resolution (`spec_version` or `mapping`)
661
+ - missing `mapping.title`
662
+ - `status.default` not present in `status.values`
663
+ - `status.completed_values` contains a value not present in `status.values`
664
+ - `status.skipped_values` contains a value not present in `status.values`
665
+ - `status.default_skipped` not present in `status.skipped_values`
666
+ - `status.skipped_values` overlaps `status.completed_values` when `materialized-occurrences` is claimed
667
+ - unsupported `validation.mode`
668
+ - `validation.mode=permissive` but permissive mode is not implemented
669
+ - invalid `task_detection.method`
670
+ - invalid `task_detection.methods` entry
671
+ - empty `task_detection.tag` when `tag` method is enabled
672
+ - missing `task_detection.property_name` when `property` method is enabled
673
+ - invalid `task_detection.combine`
674
+ - invalid `dependencies.default_reltype`
675
+ - invalid `dependencies.unresolved_target_severity`
676
+ - invalid `links.unresolved_default_severity`
677
+ - invalid `title.storage`
678
+ - invalid `title.filename_format`
679
+ - missing `title.filename_format` when `title.storage=frontmatter`
680
+ - missing `title.custom_filename_template` when `title.storage=frontmatter` and `title.filename_format=custom`
681
+ - missing `templating.template_path` when `templating.enabled=true`
682
+ - invalid `templating.failure_mode`
683
+ - invalid `templating.unknown_variable_policy`
684
+ - invalid `reminders.date_only_anchor_time`
685
+ - invalid `reminders.apply_defaults_when_explicit`
686
+ - invalid `time_tracking.auto_stop_on_complete`
687
+ - invalid `time_tracking.auto_stop_notification`
688
+ - invalid `occurrences.default_materialization`
689
+ - invalid `occurrences.default_next_trigger`
690
+ - invalid `occurrences.past_horizon`
691
+ - invalid `occurrences.future_horizon`
692
+
693
+ ## 9.21 Default collection state
694
+
695
+ This section describes what a fresh tasknotes vault looks like before any user customization. Implementations that read an existing tasknotes vault without a `tasknotes.yaml` SHOULD apply these defaults when `data.json` is absent or fields are missing.
696
+
697
+ ### Folder layout
698
+
699
+ ```text
700
+ MyVault/
701
+ ├── TaskNotes/
702
+ │ ├── Tasks/ ← new tasks created here by default
703
+ │ └── Archive/ ← archived tasks moved here (if moveArchivedTasks=true)
704
+ └── .obsidian/
705
+ └── plugins/
706
+ └── tasknotes/
707
+ └── data.json
708
+ ```
709
+
710
+ ### Task detection
711
+
712
+ Default method: `tag`. A file is a task if it contains the tag `#task` in its frontmatter `tags` array or inline in the body.
713
+
714
+ ### Default field mapping
715
+
716
+ | Semantic role | Default storage key |
717
+ |---|---|
718
+ | `title` | `title` |
719
+ | `status` | `status` |
720
+ | `priority` | `priority` |
721
+ | `due` | `due` |
722
+ | `scheduled` | `scheduled` |
723
+ | `contexts` | `contexts` |
724
+ | `projects` | `projects` |
725
+ | `time_estimate` | `timeEstimate` |
726
+ | `completed_date` | `completedDate` |
727
+ | `date_created` | `dateCreated` |
728
+ | `date_modified` | `dateModified` |
729
+ | `recurrence` | `recurrence` |
730
+ | `recurrence_anchor` | `recurrence_anchor` |
731
+ | `complete_instances` | `complete_instances` |
732
+ | `skipped_instances` | `skipped_instances` |
733
+ | `recurrence_parent` | `recurrence_parent` |
734
+ | `occurrence_date` | `occurrence_date` |
735
+ | `occurrence_materialization` | `occurrence_materialization` |
736
+ | `occurrence_next_trigger` | `occurrence_next_trigger` |
737
+ | `occurrence_template` | `occurrence_template` |
738
+ | `occurrence_past_horizon` | `occurrence_past_horizon` |
739
+ | `occurrence_future_horizon` | `occurrence_future_horizon` |
740
+ | `time_entries` | `timeEntries` |
741
+ | `blocked_by` | `blockedBy` |
742
+ | `reminders` | `reminders` |
743
+
744
+ Note: `recurrenceAnchor`, `completeInstances`, and `skippedInstances` are accepted as aliases on read (§2.5). The mixed camelCase/snake_case defaults are intentional for compatibility with historical TaskNotes frontmatter.
745
+
746
+ ### Default statuses
747
+
748
+ | Value | Label | `isCompleted` | Order |
749
+ |---|---|---|---|
750
+ | `none` | None | false | 0 |
751
+ | `open` | Open | false | 1 |
752
+ | `in-progress` | In progress | false | 2 |
753
+ | `done` | Done | true | 3 |
754
+
755
+ Default status on create: `open`. Default completed status (first `isCompleted=true`): `done`.
756
+ No skipped status is configured by default; implementations that support materialized occurrence skips MUST require configuration or expose a documented default before writing skipped occurrence state.
757
+
758
+ ### Default priorities
759
+
760
+ | Value | Label | Weight |
761
+ |---|---|---|
762
+ | `none` | None | 0 |
763
+ | `low` | Low | 1 |
764
+ | `normal` | Normal | 2 |
765
+ | `high` | High | 3 |
766
+
767
+ Default priority on create: `normal`.
768
+
769
+ ### Title and filename defaults
770
+
771
+ - `title.storage`: `filename` (filename/path is the authoritative title source)
772
+ - default basename generation under `title.storage=filename` derives from semantic title using filename-safe sanitization
773
+ - implementations MAY also keep mapped `title` in frontmatter as a synchronized compatibility mirror
774
+ - TaskNotes `data.json` may still contain legacy `taskFilenameFormat` values such as `zettel`, but these are compatibility inputs rather than the forward default when `title.storage=filename`
775
+ - title is read from filename first; frontmatter `title` is compatibility fallback when filename-derived title is unavailable (§2.2.2)
776
+
777
+ ### Time tracking defaults
778
+
779
+ - `auto_stop_on_complete`: `true`
780
+ - `auto_stop_notification`: `false`
781
+
782
+ ### Link format defaults
783
+
784
+ - `useFrontmatterMarkdownLinks`: `false` — wikilinks used by default (e.g. `[[task-name]]`)
785
+ - To use markdown link format, the `obsidian-frontmatter-markdown-links` plugin must also be installed
786
+
787
+ ### Effective configuration for a minimal fresh vault
788
+
789
+ When neither `data.json` nor `tasknotes.yaml` is present, implementations MUST use the following effective configuration:
790
+
791
+ ```yaml
792
+ spec_version: 0.2.0-draft # synthesized
793
+ mapping:
794
+ title: title
795
+ status: status
796
+ priority: priority
797
+ due: due
798
+ scheduled: scheduled
799
+ contexts: contexts
800
+ projects: projects
801
+ time_estimate: timeEstimate
802
+ completed_date: completedDate
803
+ date_created: dateCreated
804
+ date_modified: dateModified
805
+ recurrence: recurrence
806
+ recurrence_anchor: recurrence_anchor
807
+ complete_instances: complete_instances
808
+ skipped_instances: skipped_instances
809
+ recurrence_parent: recurrence_parent
810
+ occurrence_date: occurrence_date
811
+ occurrence_materialization: occurrence_materialization
812
+ occurrence_next_trigger: occurrence_next_trigger
813
+ occurrence_template: occurrence_template
814
+ occurrence_past_horizon: occurrence_past_horizon
815
+ occurrence_future_horizon: occurrence_future_horizon
816
+ time_entries: timeEntries
817
+ blocked_by: blockedBy
818
+ reminders: reminders
819
+ task_detection:
820
+ method: tag
821
+ tag: task
822
+ default_folder: TaskNotes/Tasks
823
+ status:
824
+ values: [none, open, in-progress, done]
825
+ default: open
826
+ completed_values: [done]
827
+ title:
828
+ storage: filename
829
+ time_tracking:
830
+ auto_stop_on_complete: true
831
+ auto_stop_notification: false
832
+ links:
833
+ use_markdown_format: false
834
+ extensions: [".md"]
835
+ unresolved_default_severity: warning
836
+ update_references_on_rename: true
837
+ ```