@mono-labs/tracker 0.1.269

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 (126) hide show
  1. package/README.md +1196 -0
  2. package/bin/tracker.js +2 -0
  3. package/dist/dashboard/cli.d.ts +2 -0
  4. package/dist/dashboard/cli.d.ts.map +1 -0
  5. package/dist/dashboard/cli.js +38 -0
  6. package/dist/dashboard/index.d.ts +3 -0
  7. package/dist/dashboard/index.d.ts.map +1 -0
  8. package/dist/dashboard/index.js +5 -0
  9. package/dist/dashboard/server.d.ts +3 -0
  10. package/dist/dashboard/server.d.ts.map +1 -0
  11. package/dist/dashboard/server.js +130 -0
  12. package/dist/dashboard/types.d.ts +13 -0
  13. package/dist/dashboard/types.d.ts.map +1 -0
  14. package/dist/dashboard/types.js +2 -0
  15. package/dist/dashboard/watcher.d.ts +7 -0
  16. package/dist/dashboard/watcher.d.ts.map +1 -0
  17. package/dist/dashboard/watcher.js +44 -0
  18. package/dist/executor/action-executor.d.ts +10 -0
  19. package/dist/executor/action-executor.d.ts.map +1 -0
  20. package/dist/executor/action-executor.js +19 -0
  21. package/dist/executor/actions/index.d.ts +4 -0
  22. package/dist/executor/actions/index.d.ts.map +1 -0
  23. package/dist/executor/actions/index.js +9 -0
  24. package/dist/executor/actions/remove-action.d.ts +4 -0
  25. package/dist/executor/actions/remove-action.d.ts.map +1 -0
  26. package/dist/executor/actions/remove-action.js +10 -0
  27. package/dist/executor/actions/rename-action.d.ts +4 -0
  28. package/dist/executor/actions/rename-action.d.ts.map +1 -0
  29. package/dist/executor/actions/rename-action.js +10 -0
  30. package/dist/executor/actions/replace-action.d.ts +4 -0
  31. package/dist/executor/actions/replace-action.d.ts.map +1 -0
  32. package/dist/executor/actions/replace-action.js +10 -0
  33. package/dist/executor/index.d.ts +4 -0
  34. package/dist/executor/index.d.ts.map +1 -0
  35. package/dist/executor/index.js +10 -0
  36. package/dist/index.d.ts +20 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +58 -0
  39. package/dist/manager/index.d.ts +7 -0
  40. package/dist/manager/index.d.ts.map +1 -0
  41. package/dist/manager/index.js +19 -0
  42. package/dist/manager/notation-manager.d.ts +18 -0
  43. package/dist/manager/notation-manager.d.ts.map +1 -0
  44. package/dist/manager/notation-manager.js +137 -0
  45. package/dist/manager/notation-manager.test.d.ts +2 -0
  46. package/dist/manager/notation-manager.test.d.ts.map +1 -0
  47. package/dist/manager/notation-manager.test.js +211 -0
  48. package/dist/manager/notation-updater.d.ts +6 -0
  49. package/dist/manager/notation-updater.d.ts.map +1 -0
  50. package/dist/manager/notation-updater.js +20 -0
  51. package/dist/manager/relationship-manager.d.ts +5 -0
  52. package/dist/manager/relationship-manager.d.ts.map +1 -0
  53. package/dist/manager/relationship-manager.js +46 -0
  54. package/dist/manager/stats.d.ts +3 -0
  55. package/dist/manager/stats.d.ts.map +1 -0
  56. package/dist/manager/stats.js +41 -0
  57. package/dist/manager/validator.d.ts +9 -0
  58. package/dist/manager/validator.d.ts.map +1 -0
  59. package/dist/manager/validator.js +62 -0
  60. package/dist/scanner/action-parser.d.ts +3 -0
  61. package/dist/scanner/action-parser.d.ts.map +1 -0
  62. package/dist/scanner/action-parser.js +97 -0
  63. package/dist/scanner/action-parser.test.d.ts +2 -0
  64. package/dist/scanner/action-parser.test.d.ts.map +1 -0
  65. package/dist/scanner/action-parser.test.js +94 -0
  66. package/dist/scanner/attribute-parser.d.ts +15 -0
  67. package/dist/scanner/attribute-parser.d.ts.map +1 -0
  68. package/dist/scanner/attribute-parser.js +183 -0
  69. package/dist/scanner/attribute-parser.test.d.ts +2 -0
  70. package/dist/scanner/attribute-parser.test.d.ts.map +1 -0
  71. package/dist/scanner/attribute-parser.test.js +93 -0
  72. package/dist/scanner/file-scanner.d.ts +3 -0
  73. package/dist/scanner/file-scanner.d.ts.map +1 -0
  74. package/dist/scanner/file-scanner.js +58 -0
  75. package/dist/scanner/index.d.ts +6 -0
  76. package/dist/scanner/index.d.ts.map +1 -0
  77. package/dist/scanner/index.js +11 -0
  78. package/dist/scanner/notation-parser.d.ts +3 -0
  79. package/dist/scanner/notation-parser.d.ts.map +1 -0
  80. package/dist/scanner/notation-parser.js +88 -0
  81. package/dist/scanner/notation-parser.test.d.ts +2 -0
  82. package/dist/scanner/notation-parser.test.d.ts.map +1 -0
  83. package/dist/scanner/notation-parser.test.js +153 -0
  84. package/dist/storage/config-loader.d.ts +3 -0
  85. package/dist/storage/config-loader.d.ts.map +1 -0
  86. package/dist/storage/config-loader.js +55 -0
  87. package/dist/storage/index.d.ts +3 -0
  88. package/dist/storage/index.d.ts.map +1 -0
  89. package/dist/storage/index.js +7 -0
  90. package/dist/storage/jsonl-storage.d.ts +11 -0
  91. package/dist/storage/jsonl-storage.d.ts.map +1 -0
  92. package/dist/storage/jsonl-storage.js +88 -0
  93. package/dist/storage/jsonl-storage.test.d.ts +2 -0
  94. package/dist/storage/jsonl-storage.test.d.ts.map +1 -0
  95. package/dist/storage/jsonl-storage.test.js +126 -0
  96. package/dist/types/action.d.ts +57 -0
  97. package/dist/types/action.d.ts.map +1 -0
  98. package/dist/types/action.js +13 -0
  99. package/dist/types/config.d.ts +11 -0
  100. package/dist/types/config.d.ts.map +1 -0
  101. package/dist/types/config.js +11 -0
  102. package/dist/types/enums.d.ts +40 -0
  103. package/dist/types/enums.d.ts.map +1 -0
  104. package/dist/types/enums.js +37 -0
  105. package/dist/types/index.d.ts +7 -0
  106. package/dist/types/index.d.ts.map +1 -0
  107. package/dist/types/index.js +13 -0
  108. package/dist/types/notation.d.ts +64 -0
  109. package/dist/types/notation.d.ts.map +1 -0
  110. package/dist/types/notation.js +2 -0
  111. package/dist/utils/date-parser.d.ts +3 -0
  112. package/dist/utils/date-parser.d.ts.map +1 -0
  113. package/dist/utils/date-parser.js +51 -0
  114. package/dist/utils/date-parser.test.d.ts +2 -0
  115. package/dist/utils/date-parser.test.d.ts.map +1 -0
  116. package/dist/utils/date-parser.test.js +34 -0
  117. package/dist/utils/id-generator.d.ts +3 -0
  118. package/dist/utils/id-generator.d.ts.map +1 -0
  119. package/dist/utils/id-generator.js +12 -0
  120. package/dist/utils/id-generator.test.d.ts +2 -0
  121. package/dist/utils/id-generator.test.d.ts.map +1 -0
  122. package/dist/utils/id-generator.test.js +30 -0
  123. package/dist/utils/index.d.ts +3 -0
  124. package/dist/utils/index.d.ts.map +1 -0
  125. package/dist/utils/index.js +9 -0
  126. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,1196 @@
1
+ # @mono-labs/tracker
2
+
3
+ Code notation tracker for scanning, parsing, and managing structured comment markers across your codebase.
4
+
5
+ <!-- Badges -->
6
+ ![npm version](https://img.shields.io/npm/v/@mono-labs/tracker)
7
+ ![license](https://img.shields.io/npm/l/@mono-labs/tracker)
8
+ ![tests](https://img.shields.io/badge/tests-102%20passing-brightgreen)
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ - [Quick Start](#quick-start)
15
+ - [What is Tracker?](#what-is-tracker)
16
+ - [Notation Syntax Guide](#notation-syntax-guide)
17
+ - [Basic Syntax](#basic-syntax)
18
+ - [Inline IDs](#inline-ids)
19
+ - [Multi-line Notations](#multi-line-notations)
20
+ - [Code Context Capture](#code-context-capture)
21
+ - [Attribute Styles](#attribute-styles)
22
+ - [Actions](#actions)
23
+ - [Relationships](#relationships)
24
+ - [Performance Impact](#performance-impact)
25
+ - [Technical Debt](#technical-debt)
26
+ - [Priority Shorthand](#priority-shorthand)
27
+ - [Risk Shorthand](#risk-shorthand)
28
+ - [Configuration](#configuration)
29
+ - [Core API Walkthrough](#core-api-walkthrough)
30
+ - [Scanning Files](#scanning-files)
31
+ - [NotationManager](#notationmanager)
32
+ - [Querying](#querying)
33
+ - [Storage](#storage)
34
+ - [Utilities](#utilities)
35
+ - [Relationships & Validation](#relationships--validation)
36
+ - [Mutation Helpers](#mutation-helpers)
37
+ - [Executor Framework](#executor-framework)
38
+ - [Full Type Reference](#full-type-reference)
39
+ - [Architecture](#architecture)
40
+ - [Contributor Guide](#contributor-guide)
41
+ - [License](#license)
42
+
43
+ ---
44
+
45
+ ## Quick Start
46
+
47
+ Install the package:
48
+
49
+ ```bash
50
+ yarn add @mono-labs/tracker
51
+ ```
52
+
53
+ Scan your project and view results in three steps:
54
+
55
+ ```ts
56
+ import { loadConfig, scanFiles, NotationManager } from '@mono-labs/tracker'
57
+
58
+ // 1. Load config (reads tracker.config.json or uses defaults)
59
+ const config = loadConfig(process.cwd())
60
+
61
+ // 2. Scan source files for notations
62
+ const notations = await scanFiles(config)
63
+
64
+ // 3. Manage and query results
65
+ const manager = new NotationManager(config)
66
+ manager.setAll(notations)
67
+ await manager.save()
68
+
69
+ console.log(manager.stats())
70
+ console.log(manager.query({ type: 'TODO', priority: 'high' }))
71
+ ```
72
+
73
+ ---
74
+
75
+ ## What is Tracker?
76
+
77
+ Codebases accumulate structured comments — `TODO`, `FIXME`, `BUG`, `HACK`, `NOTE`, `OPTIMIZE`, `SECURITY` — scattered across hundreds of files. These carry intent, ownership, deadlines, and technical debt information, but they're invisible to your tooling.
78
+
79
+ **Tracker** solves this by providing a pipeline to:
80
+
81
+ 1. **Scan** — Discover notation comments via configurable glob patterns
82
+ 2. **Parse** — Extract structured data: priority, assignee, tags, due dates, actions, relationships, performance impact, and technical debt
83
+ 3. **Persist** — Store results in append-friendly JSONL format with atomic writes
84
+ 4. **Query** — Filter notations by any combination of fields
85
+ 5. **Validate** — Check for missing fields, duplicate IDs, broken references, and circular dependencies
86
+ 6. **Report** — Compute aggregate statistics across your notations
87
+ 7. **Execute** — Dispatch parsed actions to registered handler functions
88
+
89
+ ### Supported Marker Types
90
+
91
+ | Marker | Purpose |
92
+ |------------|--------------------------------------|
93
+ | `TODO` | Planned work |
94
+ | `FIXME` | Known issue needing a fix |
95
+ | `BUG` | Confirmed defect |
96
+ | `HACK` | Temporary workaround |
97
+ | `NOTE` | Informational annotation |
98
+ | `OPTIMIZE` | Performance improvement opportunity |
99
+ | `SECURITY` | Security-related concern |
100
+
101
+ ---
102
+
103
+ ## Notation Syntax Guide
104
+
105
+ ### Basic Syntax
106
+
107
+ Single-line notation with a marker and description:
108
+
109
+ ```ts
110
+ // TODO: Refactor this function to use async/await
111
+ ```
112
+
113
+ The colon after the marker is optional:
114
+
115
+ ```ts
116
+ // FIXME Broken on empty input
117
+ ```
118
+
119
+ ### Inline IDs
120
+
121
+ Assign a stable external ID using square brackets:
122
+
123
+ ```ts
124
+ // TODO [TASK-123] Migrate to the new API
125
+ ```
126
+
127
+ When no inline ID is provided, Tracker generates a deterministic ID from the file path and line number using SHA-256.
128
+
129
+ ### Multi-line Notations
130
+
131
+ Continuation comment lines immediately following a marker are collected as the notation body:
132
+
133
+ ```ts
134
+ // TODO: Refactor authentication module
135
+ // This function has grown too complex and handles
136
+ // both session management and token refresh.
137
+ // @author: Alice
138
+ // @priority: high
139
+ ```
140
+
141
+ Continuation stops at blank lines, non-comment lines, or a new marker.
142
+
143
+ ### Code Context Capture
144
+
145
+ Non-comment, non-empty lines immediately following the notation block are captured as code context:
146
+
147
+ ```ts
148
+ // TODO: This query is too slow
149
+ // @priority: high
150
+ // Performance: 2000ms->100ms
151
+ const results = db.query('SELECT * FROM users')
152
+ const filtered = results.filter(u => u.active)
153
+ ```
154
+
155
+ Here `results` and `filtered` lines are captured in the `codeContext` array.
156
+
157
+ ### Attribute Styles
158
+
159
+ Tracker supports three attribute formats. All can be mixed within the same notation body.
160
+
161
+ #### 1. `@` Prefix Style
162
+
163
+ ```ts
164
+ // TODO: Implement caching layer
165
+ // @author: Alice
166
+ // @assignee: Bob
167
+ // @priority: high
168
+ // @tags: performance, api
169
+ // @due: +2w
170
+ // @risk: moderate
171
+ ```
172
+
173
+ #### 2. Compact Bracket Style
174
+
175
+ Pack multiple attributes into a single bracketed line. Segments are separated by `|`.
176
+
177
+ ```ts
178
+ // TODO: Fix login redirect
179
+ // [Alice → Bob | high | 3d | due: 2/24/2026 | tags: auth, ui]
180
+ ```
181
+
182
+ - **Arrow assignment** (`→` or `->`): sets `author` and `assignee`
183
+ - **Duration shorthand** (`3d`, `2w`, `1m`): sets due date relative to today (hours like `8h` set debt instead)
184
+ - **Key-value** (`due: 2/24/2026`): same as `@` prefix keys
185
+ - **Bare words** (`high`, `critical`): matched against priority/risk maps
186
+
187
+ #### 3. Key-Value Style
188
+
189
+ Capitalized key followed by colon and value:
190
+
191
+ ```ts
192
+ // TODO: Update error handling
193
+ // Priority: critical
194
+ // Tags: ui, api
195
+ // Assignee: Charlie
196
+ // Debt: 8h | compounding: high
197
+ ```
198
+
199
+ ### Actions
200
+
201
+ Declare code transformation intentions with `Action:` lines:
202
+
203
+ ```ts
204
+ // TODO: Clean up legacy code
205
+ // Action: replace(oldFunction, newFunction)
206
+ ```
207
+
208
+ Chained calls for positional actions:
209
+
210
+ ```ts
211
+ // TODO: Add error boundary
212
+ // Action: insert(ErrorBoundary).before(App)
213
+ ```
214
+
215
+ Supported action verbs: `replace`, `remove`, `rename`, `insert`, `extract`, `move`, `wrapIn`. Unrecognized verbs are parsed as `generic`.
216
+
217
+ ```ts
218
+ // Action: remove(legacyHelper)
219
+ // Action: rename(fetchData, loadData)
220
+ // Action: extract(validateInput).to(validators.ts)
221
+ // Action: move(utils).to(shared/utils.ts)
222
+ // Action: wrapIn(rawQuery, sanitize)
223
+ ```
224
+
225
+ ### Relationships
226
+
227
+ Declare dependencies between notations:
228
+
229
+ ```ts
230
+ // TODO: Implement logout
231
+ // Blocks: N-abc12345
232
+ // Depends on: N-def45678
233
+ // Related: N-ghi78901, N-jkl01234
234
+ ```
235
+
236
+ Relationship keys: `Blocks`, `Blocked by`, `Depends on`, `Related`. All populate the `relationships` array with referenced IDs. Notation is considered _blocked_ if any related notation has a non-resolved status.
237
+
238
+ ### Performance Impact
239
+
240
+ Track measured or expected performance changes:
241
+
242
+ ```ts
243
+ // OPTIMIZE: Replace N+1 query with batch load
244
+ // Performance: 2000ms->100ms
245
+ ```
246
+
247
+ Parsed into a `PerformanceImpact` object with `before`, `after`, and `unit` fields. Supported units: `ms`, `s`, `us`.
248
+
249
+ ### Technical Debt
250
+
251
+ Estimate and track accumulated debt:
252
+
253
+ ```ts
254
+ // HACK: Hardcoded timeout
255
+ // Debt: 8h | compounding: high
256
+ ```
257
+
258
+ The hours value must use the `h` suffix. Compounding rate is `low`, `medium`, or `high` (defaults to `low`).
259
+
260
+ ### Priority Shorthand
261
+
262
+ | Shorthand | Full Value |
263
+ |-----------|-----------|
264
+ | `m1` | `minimal` |
265
+ | `l2` | `low` |
266
+ | `m3`, `med` | `medium` |
267
+ | `h4` | `high` |
268
+ | `c5` | `critical`|
269
+
270
+ ### Risk Shorthand
271
+
272
+ | Shorthand | Full Value |
273
+ |-----------|------------|
274
+ | `m1` | `minimal` |
275
+ | `l2` | `low` |
276
+ | `m3`, `mod` | `moderate` |
277
+ | `s2` | `severe` |
278
+ | `c3` | `critical` |
279
+
280
+ ---
281
+
282
+ ## Configuration
283
+
284
+ Create a `tracker.config.json` in your project root:
285
+
286
+ ```json
287
+ {
288
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
289
+ "exclude": ["**/node_modules/**", "**/dist/**", "**/*.test.ts"],
290
+ "markers": ["TODO", "FIXME", "BUG"],
291
+ "storagePath": ".tracker/notations.jsonl",
292
+ "idPrefix": "N"
293
+ }
294
+ ```
295
+
296
+ ### Default Configuration
297
+
298
+ If no config file is found, or for any omitted fields, these defaults apply:
299
+
300
+ ```ts
301
+ const DEFAULT_CONFIG: TrackerConfig = {
302
+ rootDir: '.',
303
+ include: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
304
+ exclude: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
305
+ markers: ['TODO', 'FIXME', 'BUG', 'HACK', 'NOTE', 'OPTIMIZE', 'SECURITY'],
306
+ storagePath: '.tracker/notations.jsonl',
307
+ idPrefix: 'N',
308
+ }
309
+ ```
310
+
311
+ ### `loadConfig(projectRoot: string): TrackerConfig`
312
+
313
+ Reads `tracker.config.json` from `projectRoot`, merges with `DEFAULT_CONFIG`, and sets `rootDir` to `projectRoot`. If the file is missing or contains invalid JSON, defaults are used silently.
314
+
315
+ ### `TrackerConfig` Interface
316
+
317
+ | Field | Type | Description |
318
+ |---------------|--------------|--------------------------------------------------|
319
+ | `rootDir` | `string` | Absolute root directory (set by `loadConfig`) |
320
+ | `include` | `string[]` | Glob patterns for files to scan |
321
+ | `exclude` | `string[]` | Glob patterns for files to skip |
322
+ | `markers` | `MarkerType[]` | Which marker types to recognize |
323
+ | `storagePath` | `string` | Path to the JSONL storage file (relative to root) |
324
+ | `idPrefix` | `string` | Prefix for generated notation IDs |
325
+
326
+ ---
327
+
328
+ ## Core API Walkthrough
329
+
330
+ ### Scanning Files
331
+
332
+ #### `scanFiles(config: TrackerConfig, rootDir?: string): Promise<Notation[]>`
333
+
334
+ Discovers files matching `config.include` (excluding `config.exclude`) using [fast-glob](https://github.com/mrmlnc/fast-glob), reads each file, and parses all notations.
335
+
336
+ ```ts
337
+ import { loadConfig, scanFiles } from '@mono-labs/tracker'
338
+
339
+ const config = loadConfig('/path/to/project')
340
+ const notations = await scanFiles(config)
341
+ console.log(`Found ${notations.length} notations`)
342
+ ```
343
+
344
+ The optional `rootDir` parameter overrides `config.rootDir` for the scan.
345
+
346
+ #### `parseFileContent(filePath: string, content: string, idPrefix?: string): Notation[]`
347
+
348
+ Parses notation markers from a raw file string. Useful when you already have file content in memory.
349
+
350
+ ```ts
351
+ import { parseFileContent } from '@mono-labs/tracker'
352
+
353
+ const source = `
354
+ // TODO: Implement validation
355
+ // @priority: high
356
+ function validate() {}
357
+ `
358
+
359
+ const notations = parseFileContent('src/validate.ts', source, 'N')
360
+ // notations[0].description === 'Implement validation'
361
+ // notations[0].priority === 'high'
362
+ // notations[0].codeContext === ['function validate() {}']
363
+ ```
364
+
365
+ #### `parseAttributes(bodyLines: string[]): ParsedAttributes`
366
+
367
+ Parses attribute lines from a notation body independently. Returns a `ParsedAttributes` object.
368
+
369
+ ```ts
370
+ import { parseAttributes } from '@mono-labs/tracker'
371
+
372
+ const attrs = parseAttributes([
373
+ '@author: Alice',
374
+ '@priority: high',
375
+ '@tags: ui, perf',
376
+ ])
377
+ // attrs.author === 'Alice'
378
+ // attrs.priority === 'high'
379
+ // attrs.tags === ['ui', 'perf']
380
+ ```
381
+
382
+ #### `parseActions(bodyLines: string[]): NotationAction[]`
383
+
384
+ Parses `Action:` lines from a notation body independently.
385
+
386
+ ```ts
387
+ import { parseActions } from '@mono-labs/tracker'
388
+
389
+ const actions = parseActions([
390
+ 'Action: replace(oldFn, newFn)',
391
+ 'Action: insert(guard).before(handler)',
392
+ ])
393
+ // actions[0].args.verb === 'replace'
394
+ // actions[1].args.verb === 'insert'
395
+ // actions[1].args.position === 'before'
396
+ ```
397
+
398
+ ### NotationManager
399
+
400
+ The `NotationManager` class is the primary facade for loading, querying, mutating, and persisting notations.
401
+
402
+ #### Constructor
403
+
404
+ ```ts
405
+ import { NotationManager, loadConfig } from '@mono-labs/tracker'
406
+
407
+ const config = loadConfig(process.cwd())
408
+ const manager = new NotationManager(config)
409
+ ```
410
+
411
+ The storage file path is resolved from `config.storagePath` (relative paths resolve against `config.rootDir`).
412
+
413
+ #### `load(): Promise<void>`
414
+
415
+ Reads all notations from the JSONL storage file into memory.
416
+
417
+ ```ts
418
+ await manager.load()
419
+ console.log(manager.getAll().length)
420
+ ```
421
+
422
+ #### `save(): Promise<void>`
423
+
424
+ Writes all in-memory notations to the JSONL storage file (atomic write via tmp + rename).
425
+
426
+ ```ts
427
+ manager.setAll(notations)
428
+ await manager.save()
429
+ ```
430
+
431
+ #### `getAll(): Notation[]`
432
+
433
+ Returns a shallow copy of all notations currently in memory.
434
+
435
+ #### `getById(id: string): Notation | undefined`
436
+
437
+ Returns a single notation by its ID, or `undefined` if not found.
438
+
439
+ ```ts
440
+ const notation = manager.getById('N-abc12345')
441
+ ```
442
+
443
+ #### `setAll(notations: Notation[]): void`
444
+
445
+ Replaces all in-memory notations with a new array (shallow copy).
446
+
447
+ #### `query(q: NotationQuery): Notation[]`
448
+
449
+ Filters notations by any combination of query fields. See [Querying](#querying) for full filter reference.
450
+
451
+ ```ts
452
+ const critical = manager.query({ priority: 'critical', status: 'open' })
453
+ ```
454
+
455
+ #### `update(id: string, updates: Partial<Notation>): boolean`
456
+
457
+ Merges `updates` into the notation with the given ID. Returns `true` if the notation was found and updated, `false` otherwise.
458
+
459
+ ```ts
460
+ manager.update('N-abc12345', { status: 'resolved', assignee: 'Bob' })
461
+ ```
462
+
463
+ #### `validate(): ValidationError[]`
464
+
465
+ Runs full validation across all notations: field checks, duplicate IDs, broken references, and circular dependency detection.
466
+
467
+ ```ts
468
+ const errors = manager.validate()
469
+ if (errors.length > 0) {
470
+ console.error('Validation errors:', errors)
471
+ }
472
+ ```
473
+
474
+ #### `stats(): NotationStats`
475
+
476
+ Computes aggregate statistics over all in-memory notations.
477
+
478
+ ```ts
479
+ const s = manager.stats()
480
+ console.log(`Total: ${s.total}, Overdue: ${s.overdue}, Debt: ${s.totalDebtHours}h`)
481
+ ```
482
+
483
+ ### Querying
484
+
485
+ The `query()` method accepts a `NotationQuery` object. All fields are optional; multiple fields combine with AND logic.
486
+
487
+ | Field | Type | Behavior |
488
+ |-------------|--------------------------------|-------------------------------------------------------------|
489
+ | `type` | `MarkerType \| MarkerType[]` | Match one or more marker types |
490
+ | `tags` | `string[]` | Match notations containing _any_ of the specified tags |
491
+ | `priority` | `Priority \| Priority[]` | Match one or more priority levels |
492
+ | `status` | `Status \| Status[]` | Match one or more statuses |
493
+ | `file` | `string` | Substring match against `location.file` |
494
+ | `assignee` | `string` | Exact match against `assignee` |
495
+ | `overdue` | `boolean` | If `true`, return only overdue non-resolved notations |
496
+ | `blocked` | `boolean` | If `true`, return only blocked notations |
497
+ | `search` | `string` | Case-insensitive substring search in description, body, tags|
498
+ | `dueBefore` | `string` | ISO date string — notations due on or before this date |
499
+ | `dueAfter` | `string` | ISO date string — notations due on or after this date |
500
+
501
+ Array values enable multi-select filtering:
502
+
503
+ ```ts
504
+ manager.query({ type: ['TODO', 'BUG'], priority: ['high', 'critical'] })
505
+ ```
506
+
507
+ ### Storage
508
+
509
+ #### `JsonlStorage`
510
+
511
+ Low-level storage engine that persists notations as newline-delimited JSON.
512
+
513
+ ```ts
514
+ import { JsonlStorage } from '@mono-labs/tracker'
515
+
516
+ const storage = new JsonlStorage('.tracker/notations.jsonl')
517
+ ```
518
+
519
+ ##### `readAll(): Promise<Notation[]>`
520
+
521
+ Reads and parses all lines from the JSONL file. Corrupt lines are silently skipped. Returns an empty array if the file doesn't exist.
522
+
523
+ ##### `writeAll(notations: Notation[]): Promise<void>`
524
+
525
+ Atomically writes all notations: writes to a `.tmp` file first, then renames over the target. Creates parent directories if needed.
526
+
527
+ ##### `append(notation: Notation): Promise<void>`
528
+
529
+ Appends a single notation as a new line to the file.
530
+
531
+ ##### `appendBatch(notations: Notation[]): Promise<void>`
532
+
533
+ Appends multiple notations in a single write operation. No-ops on empty arrays.
534
+
535
+ ### Utilities
536
+
537
+ #### `generateId(prefix?: string): string`
538
+
539
+ Generates a random ID using UUID v4. Default prefix is `'N'`.
540
+
541
+ ```ts
542
+ import { generateId } from '@mono-labs/tracker'
543
+
544
+ generateId() // 'N-a1b2c3d4'
545
+ generateId('T') // 'T-e5f6a7b8'
546
+ ```
547
+
548
+ #### `generateStableId(prefix: string, file: string, line: number): string`
549
+
550
+ Generates a deterministic ID from a file path and line number using SHA-256. Re-scanning the same file produces the same IDs, enabling incremental updates.
551
+
552
+ ```ts
553
+ import { generateStableId } from '@mono-labs/tracker'
554
+
555
+ generateStableId('N', 'src/app.ts', 42) // 'N-<8-char hash>'
556
+ ```
557
+
558
+ #### `parseDate(input: string): string | null`
559
+
560
+ Parses date strings in multiple formats. Returns an ISO date string (`YYYY-MM-DD`) or `null`.
561
+
562
+ | Format | Example | Notes |
563
+ |------------------|----------------|--------------------------------|
564
+ | ISO | `2026-02-24` | Returned as-is |
565
+ | US | `2/24/2026` | `MM/DD/YYYY`, zero-padding optional |
566
+ | Relative days | `+3d` | 3 days from today |
567
+ | Relative weeks | `+2w` | 14 days from today |
568
+ | Relative months | `+1m` | 1 month from today |
569
+ | Relative years | `+1y` | 1 year from today |
570
+
571
+ ```ts
572
+ import { parseDate } from '@mono-labs/tracker'
573
+
574
+ parseDate('2026-02-24') // '2026-02-24'
575
+ parseDate('2/24/2026') // '2026-02-24'
576
+ parseDate('+2w') // ISO date 14 days from now
577
+ ```
578
+
579
+ #### `isOverdue(dateStr: string): boolean`
580
+
581
+ Returns `true` if the given ISO date string is in the past (compared at end of day, 23:59:59).
582
+
583
+ ```ts
584
+ import { isOverdue } from '@mono-labs/tracker'
585
+
586
+ isOverdue('2020-01-01') // true
587
+ isOverdue('2099-12-31') // false
588
+ ```
589
+
590
+ ### Relationships & Validation
591
+
592
+ #### `getBlockers(notation: Notation, allNotations: Notation[]): Notation[]`
593
+
594
+ Returns all notations referenced in `notation.relationships` that have a non-resolved status.
595
+
596
+ #### `isBlocked(notation: Notation, allNotations: Notation[]): boolean`
597
+
598
+ Returns `true` if the notation has any unresolved blockers.
599
+
600
+ ```ts
601
+ import { isBlocked, getBlockers } from '@mono-labs/tracker'
602
+
603
+ const blockers = getBlockers(myNotation, allNotations)
604
+ if (isBlocked(myNotation, allNotations)) {
605
+ console.log('Blocked by:', blockers.map(b => b.id))
606
+ }
607
+ ```
608
+
609
+ #### `detectCircularDependencies(notations: Notation[]): string[][]`
610
+
611
+ Uses DFS to detect cycles in the relationship graph. Returns an array of cycles, where each cycle is an array of notation IDs forming the loop.
612
+
613
+ ```ts
614
+ import { detectCircularDependencies } from '@mono-labs/tracker'
615
+
616
+ const cycles = detectCircularDependencies(allNotations)
617
+ // [['N-abc', 'N-def', 'N-abc']]
618
+ ```
619
+
620
+ #### `validateNotation(notation: Notation): ValidationError[]`
621
+
622
+ Validates a single notation for:
623
+ - Missing `id`, `description`, or `location.file`
624
+ - Invalid `type`, `status`, `priority`, or `risk` values
625
+
626
+ #### `validateAll(notations: Notation[]): ValidationError[]`
627
+
628
+ Validates all notations and additionally checks for:
629
+ - Duplicate IDs
630
+ - Broken relationship references (IDs not in the set)
631
+ - Circular dependencies
632
+
633
+ ```ts
634
+ import { validateAll } from '@mono-labs/tracker'
635
+
636
+ const errors = validateAll(notations)
637
+ for (const err of errors) {
638
+ console.error(`${err.notationId} [${err.field}]: ${err.message}`)
639
+ }
640
+ ```
641
+
642
+ #### `computeStats(notations: Notation[]): NotationStats`
643
+
644
+ Standalone function to compute statistics from a notation array (same logic as `manager.stats()`).
645
+
646
+ ### Mutation Helpers
647
+
648
+ Immutable helper functions that return new `Notation` objects:
649
+
650
+ #### `updateStatus(notation: Notation, status: Status): Notation`
651
+
652
+ ```ts
653
+ import { updateStatus, Status } from '@mono-labs/tracker'
654
+
655
+ const resolved = updateStatus(notation, Status.RESOLVED)
656
+ ```
657
+
658
+ #### `addTag(notation: Notation, tag: string): Notation`
659
+
660
+ Adds a tag if not already present. Returns the same object if the tag exists.
661
+
662
+ ```ts
663
+ import { addTag } from '@mono-labs/tracker'
664
+
665
+ const tagged = addTag(notation, 'urgent')
666
+ ```
667
+
668
+ #### `removeTag(notation: Notation, tag: string): Notation`
669
+
670
+ ```ts
671
+ import { removeTag } from '@mono-labs/tracker'
672
+
673
+ const untagged = removeTag(notation, 'stale')
674
+ ```
675
+
676
+ #### `setAssignee(notation: Notation, assignee: string): Notation`
677
+
678
+ ```ts
679
+ import { setAssignee } from '@mono-labs/tracker'
680
+
681
+ const assigned = setAssignee(notation, 'Alice')
682
+ ```
683
+
684
+ ### Executor Framework
685
+
686
+ The executor provides a plugin-based system for dispatching parsed actions to handler functions.
687
+
688
+ #### `registerActionHandler(verb: string, handler: ActionHandler): void`
689
+
690
+ Registers a handler function for a specific action verb.
691
+
692
+ ```ts
693
+ import { registerActionHandler } from '@mono-labs/tracker'
694
+ import type { ActionHandler } from '@mono-labs/tracker'
695
+
696
+ const myHandler: ActionHandler = async (action) => {
697
+ // Implement your logic here
698
+ return { success: true, message: 'Done', verb: action.verb }
699
+ }
700
+
701
+ registerActionHandler('replace', myHandler)
702
+ ```
703
+
704
+ #### `executeAction(action: NotationAction): Promise<ActionResult>`
705
+
706
+ Dispatches an action to the registered handler for its verb. Returns `{ success: false }` if no handler is registered.
707
+
708
+ ```ts
709
+ import { executeAction } from '@mono-labs/tracker'
710
+
711
+ for (const action of notation.actions) {
712
+ const result = await executeAction(action)
713
+ if (!result.success) {
714
+ console.error(`Failed: ${result.message}`)
715
+ }
716
+ }
717
+ ```
718
+
719
+ #### Built-in Stubs
720
+
721
+ Three stub handlers are exported for reference and testing. They return `success: true` with a descriptive message but perform no actual file operations:
722
+
723
+ - `handleReplace` — Stub for `replace` actions
724
+ - `handleRemove` — Stub for `remove` actions
725
+ - `handleRename` — Stub for `rename` actions
726
+
727
+ Register them if you want safe no-op handling:
728
+
729
+ ```ts
730
+ import { registerActionHandler, handleReplace, handleRemove, handleRename } from '@mono-labs/tracker'
731
+
732
+ registerActionHandler('replace', handleReplace)
733
+ registerActionHandler('remove', handleRemove)
734
+ registerActionHandler('rename', handleRename)
735
+ ```
736
+
737
+ #### Writing a Custom Handler
738
+
739
+ ```ts
740
+ import { registerActionHandler } from '@mono-labs/tracker'
741
+ import type { ActionHandler, NotationAction, ActionResult } from '@mono-labs/tracker'
742
+
743
+ const handleExtract: ActionHandler = async (action: NotationAction): Promise<ActionResult> => {
744
+ if (action.args.verb !== 'extract') {
745
+ return { success: false, message: 'Wrong verb', verb: action.verb }
746
+ }
747
+ const { target, destination } = action.args
748
+ // ... perform extraction logic ...
749
+ return { success: true, message: `Extracted ${target} to ${destination}`, verb: 'extract' }
750
+ }
751
+
752
+ registerActionHandler('extract', handleExtract)
753
+ ```
754
+
755
+ ---
756
+
757
+ ## Full Type Reference
758
+
759
+ ### Enums
760
+
761
+ All enums are defined as `const` objects with matching type aliases, enabling both runtime access and type safety.
762
+
763
+ #### `MarkerType`
764
+
765
+ ```ts
766
+ 'TODO' | 'FIXME' | 'BUG' | 'HACK' | 'NOTE' | 'OPTIMIZE' | 'SECURITY'
767
+ ```
768
+
769
+ #### `Priority`
770
+
771
+ ```ts
772
+ 'minimal' | 'low' | 'medium' | 'high' | 'critical'
773
+ ```
774
+
775
+ #### `RiskLevel`
776
+
777
+ ```ts
778
+ 'minimal' | 'low' | 'moderate' | 'severe' | 'critical'
779
+ ```
780
+
781
+ #### `Status`
782
+
783
+ ```ts
784
+ 'open' | 'in_progress' | 'blocked' | 'resolved'
785
+ ```
786
+
787
+ #### `CompoundingRate`
788
+
789
+ ```ts
790
+ 'low' | 'medium' | 'high'
791
+ ```
792
+
793
+ #### `ActionVerb`
794
+
795
+ ```ts
796
+ 'replace' | 'remove' | 'rename' | 'insert' | 'extract' | 'move' | 'wrapIn' | 'generic'
797
+ ```
798
+
799
+ ### Interfaces
800
+
801
+ #### `Notation`
802
+
803
+ | Field | Type | Required | Description |
804
+ |---------------|---------------------|----------|--------------------------------------------|
805
+ | `id` | `string` | yes | Unique identifier (inline or generated) |
806
+ | `type` | `MarkerType` | yes | The marker type |
807
+ | `description` | `string` | yes | First-line description text |
808
+ | `body` | `string[]` | yes | Continuation comment lines |
809
+ | `codeContext` | `string[]` | yes | Code lines following the notation block |
810
+ | `location` | `SourceLocation` | yes | File, line, column, and optional endLine |
811
+ | `author` | `string` | no | Author name from attributes |
812
+ | `assignee` | `string` | no | Assignee name |
813
+ | `priority` | `Priority` | no | Priority level |
814
+ | `risk` | `RiskLevel` | no | Risk level |
815
+ | `status` | `Status` | yes | Current status (default: `'open'`) |
816
+ | `tags` | `string[]` | yes | Tags parsed from attributes |
817
+ | `dueDate` | `string` | no | ISO date string |
818
+ | `createdDate` | `string` | no | ISO date string |
819
+ | `performance` | `PerformanceImpact` | no | Performance before/after measurement |
820
+ | `debt` | `TechnicalDebt` | no | Estimated debt hours and compounding rate |
821
+ | `actions` | `NotationAction[]` | yes | Parsed action instructions |
822
+ | `relationships` | `string[]` | yes | IDs of related notations |
823
+ | `rawBlock` | `string` | yes | Original raw text of the notation block |
824
+ | `scannedAt` | `string` | yes | ISO timestamp of when the notation was scanned |
825
+
826
+ #### `SourceLocation`
827
+
828
+ | Field | Type | Required | Description |
829
+ |-----------|----------|----------|--------------------------------|
830
+ | `file` | `string` | yes | File path |
831
+ | `line` | `number` | yes | Start line (1-indexed) |
832
+ | `column` | `number` | yes | Column offset (1-indexed) |
833
+ | `endLine` | `number` | no | End line of the notation block |
834
+
835
+ #### `PerformanceImpact`
836
+
837
+ | Field | Type | Description |
838
+ |----------|----------|--------------------------------------|
839
+ | `before` | `string` | Value before (e.g., `'2000ms'`) |
840
+ | `after` | `string` | Value after (e.g., `'100ms'`) |
841
+ | `unit` | `string` | Unit from the "after" value (`ms`, `s`, `us`) |
842
+
843
+ #### `TechnicalDebt`
844
+
845
+ | Field | Type | Description |
846
+ |---------------|-------------------|--------------------------------|
847
+ | `hours` | `number` | Estimated debt in hours |
848
+ | `compounding` | `CompoundingRate` | How fast the debt grows |
849
+
850
+ #### `NotationQuery`
851
+
852
+ | Field | Type | Description |
853
+ |-------------|--------------------------------|--------------------------------------|
854
+ | `type` | `MarkerType \| MarkerType[]` | Filter by marker type(s) |
855
+ | `tags` | `string[]` | Filter by tag (OR match) |
856
+ | `priority` | `Priority \| Priority[]` | Filter by priority level(s) |
857
+ | `status` | `Status \| Status[]` | Filter by status(es) |
858
+ | `file` | `string` | Substring match on file path |
859
+ | `assignee` | `string` | Exact assignee match |
860
+ | `overdue` | `boolean` | Only overdue, non-resolved notations |
861
+ | `blocked` | `boolean` | Only blocked notations |
862
+ | `search` | `string` | Full-text search (description, body, tags) |
863
+ | `dueBefore` | `string` | Due on or before this ISO date |
864
+ | `dueAfter` | `string` | Due on or after this ISO date |
865
+
866
+ #### `NotationStats`
867
+
868
+ | Field | Type | Description |
869
+ |------------------|-------------------------|----------------------------------|
870
+ | `total` | `number` | Total notation count |
871
+ | `byType` | `Record<string, number>`| Count per marker type |
872
+ | `byPriority` | `Record<string, number>`| Count per priority level |
873
+ | `byStatus` | `Record<string, number>`| Count per status |
874
+ | `byTag` | `Record<string, number>`| Count per tag |
875
+ | `byAssignee` | `Record<string, number>`| Count per assignee |
876
+ | `overdue` | `number` | Number of overdue notations |
877
+ | `blocked` | `number` | Number of blocked notations |
878
+ | `totalDebtHours` | `number` | Sum of all debt hours |
879
+
880
+ #### `NotationAction`
881
+
882
+ | Field | Type | Description |
883
+ |--------|--------------|--------------------------------|
884
+ | `verb` | `ActionVerb` | The action verb |
885
+ | `raw` | `string` | Original raw action string |
886
+ | `args` | `ActionArgs` | Parsed arguments (discriminated union) |
887
+
888
+ #### `ActionArgs` Variants
889
+
890
+ | Variant | Fields |
891
+ |---------------|-------------------------------------------------|
892
+ | `ReplaceArgs` | `verb: 'replace'`, `target`, `replacement` |
893
+ | `RemoveArgs` | `verb: 'remove'`, `target` |
894
+ | `RenameArgs` | `verb: 'rename'`, `from`, `to` |
895
+ | `InsertArgs` | `verb: 'insert'`, `content`, `position` (`'before' \| 'after'`), `anchor` |
896
+ | `ExtractArgs` | `verb: 'extract'`, `target`, `destination` |
897
+ | `MoveArgs` | `verb: 'move'`, `target`, `destination` |
898
+ | `WrapInArgs` | `verb: 'wrapIn'`, `target`, `wrapper` |
899
+ | `GenericArgs` | `verb: 'generic'`, `description` |
900
+
901
+ #### `TrackerConfig`
902
+
903
+ | Field | Type | Description |
904
+ |---------------|----------------|-----------------------------------|
905
+ | `rootDir` | `string` | Project root directory |
906
+ | `include` | `string[]` | Glob patterns to include |
907
+ | `exclude` | `string[]` | Glob patterns to exclude |
908
+ | `markers` | `MarkerType[]` | Marker types to recognize |
909
+ | `storagePath` | `string` | JSONL file path |
910
+ | `idPrefix` | `string` | Prefix for generated IDs |
911
+
912
+ #### `ValidationError`
913
+
914
+ | Field | Type | Description |
915
+ |--------------|----------|-------------------------------------|
916
+ | `notationId` | `string` | ID of the notation with the error |
917
+ | `field` | `string` | Field name that failed validation |
918
+ | `message` | `string` | Human-readable error message |
919
+
920
+ #### `ActionResult`
921
+
922
+ | Field | Type | Description |
923
+ |-----------|-----------|----------------------------------|
924
+ | `success` | `boolean` | Whether the action succeeded |
925
+ | `message` | `string` | Result or error message |
926
+ | `verb` | `string` | The action verb that was executed|
927
+
928
+ #### `ActionHandler`
929
+
930
+ ```ts
931
+ type ActionHandler = (action: NotationAction) => Promise<ActionResult>
932
+ ```
933
+
934
+ #### `ParsedAttributes`
935
+
936
+ | Field | Type | Description |
937
+ |----------------|---------------------|--------------------------------|
938
+ | `author` | `string \| undefined` | Parsed author |
939
+ | `assignee` | `string \| undefined` | Parsed assignee |
940
+ | `priority` | `Priority \| undefined` | Parsed priority |
941
+ | `risk` | `RiskLevel \| undefined` | Parsed risk level |
942
+ | `tags` | `string[]` | Parsed tags |
943
+ | `dueDate` | `string \| undefined` | Parsed due date (ISO) |
944
+ | `createdDate` | `string \| undefined` | Parsed created date (ISO) |
945
+ | `performance` | `PerformanceImpact \| undefined` | Parsed performance |
946
+ | `debt` | `TechnicalDebt \| undefined` | Parsed debt |
947
+ | `relationships`| `string[]` | Parsed relationship IDs |
948
+
949
+ ---
950
+
951
+ ## Architecture
952
+
953
+ ### Layer Diagram
954
+
955
+ ```
956
+ ┌─────────────────────────────────────────────────────────┐
957
+ │ Executor │
958
+ │ registerActionHandler · executeAction │
959
+ ├─────────────────────────────────────────────────────────┤
960
+ │ Manager │
961
+ │ NotationManager · query · validate · stats · updaters │
962
+ ├─────────────────────────────────────────────────────────┤
963
+ │ Scanner │
964
+ │ scanFiles · parseFileContent · parseAttributes │
965
+ ├─────────────────────────────────────────────────────────┤
966
+ │ Storage │
967
+ │ JsonlStorage · loadConfig │
968
+ ├─────────────────────────────────────────────────────────┤
969
+ │ Utils │
970
+ │ generateId · generateStableId · parseDate │
971
+ ├─────────────────────────────────────────────────────────┤
972
+ │ Types │
973
+ │ enums · Notation · Action · Config · Query · Stats │
974
+ └─────────────────────────────────────────────────────────┘
975
+ ```
976
+
977
+ Each layer depends only on layers below it.
978
+
979
+ ### Source Tree
980
+
981
+ ```
982
+ packages/tracker/
983
+ ├── src/
984
+ │ ├── index.ts # Public barrel export
985
+ │ ├── types/
986
+ │ │ ├── index.ts # Type barrel
987
+ │ │ ├── enums.ts # MarkerType, Priority, RiskLevel, Status, CompoundingRate
988
+ │ │ ├── notation.ts # Notation, SourceLocation, NotationQuery, NotationStats
989
+ │ │ ├── action.ts # ActionVerb, NotationAction, all ActionArgs variants
990
+ │ │ └── config.ts # TrackerConfig, DEFAULT_CONFIG
991
+ │ ├── utils/
992
+ │ │ ├── index.ts # Utils barrel
993
+ │ │ ├── id-generator.ts # generateId, generateStableId
994
+ │ │ ├── id-generator.test.ts
995
+ │ │ ├── date-parser.ts # parseDate, isOverdue
996
+ │ │ └── date-parser.test.ts
997
+ │ ├── storage/
998
+ │ │ ├── index.ts # Storage barrel
999
+ │ │ ├── jsonl-storage.ts # JsonlStorage class
1000
+ │ │ ├── jsonl-storage.test.ts
1001
+ │ │ └── config-loader.ts # loadConfig
1002
+ │ ├── scanner/
1003
+ │ │ ├── index.ts # Scanner barrel
1004
+ │ │ ├── file-scanner.ts # scanFiles (glob + read + parse)
1005
+ │ │ ├── notation-parser.ts # parseFileContent
1006
+ │ │ ├── notation-parser.test.ts
1007
+ │ │ ├── attribute-parser.ts # parseAttributes (3 style strategies)
1008
+ │ │ ├── attribute-parser.test.ts
1009
+ │ │ ├── action-parser.ts # parseActions (chained call parser)
1010
+ │ │ └── action-parser.test.ts
1011
+ │ ├── manager/
1012
+ │ │ ├── index.ts # Manager barrel
1013
+ │ │ ├── notation-manager.ts # NotationManager class
1014
+ │ │ ├── notation-manager.test.ts
1015
+ │ │ ├── notation-updater.ts # updateStatus, addTag, removeTag, setAssignee
1016
+ │ │ ├── relationship-manager.ts # getBlockers, isBlocked, detectCircularDependencies
1017
+ │ │ ├── validator.ts # validateNotation, validateAll
1018
+ │ │ └── stats.ts # computeStats
1019
+ │ └── executor/
1020
+ │ ├── index.ts # Executor barrel
1021
+ │ ├── action-executor.ts # registerActionHandler, executeAction
1022
+ │ └── actions/
1023
+ │ ├── index.ts # Action stubs barrel
1024
+ │ ├── replace-action.ts # handleReplace stub
1025
+ │ ├── remove-action.ts # handleRemove stub
1026
+ │ └── rename-action.ts # handleRename stub
1027
+ ├── package.json
1028
+ ├── tsconfig.json
1029
+ └── vitest.config.ts
1030
+ ```
1031
+
1032
+ ### Design Decisions
1033
+
1034
+ - **JSONL storage** — Append-friendly format; each line is an independent JSON object, making it resilient to partial writes and easy to stream
1035
+ - **Atomic writes** — `writeAll` writes to a `.tmp` file then renames, preventing data corruption on crash
1036
+ - **Stable IDs** — `generateStableId` uses SHA-256 of `file:line`, so re-scanning produces the same IDs for unchanged notations
1037
+ - **Corrupt line resilience** — `readAll` silently skips unparseable lines, so a single corrupt entry doesn't break the entire store
1038
+ - **Immutable updaters** — Mutation helpers (`updateStatus`, `addTag`, etc.) return new objects, leaving originals unchanged
1039
+ - **Plugin executor** — The handler registry decouples action parsing from execution, allowing consumers to implement their own file-modification logic
1040
+
1041
+ ---
1042
+
1043
+ ## Contributor Guide
1044
+
1045
+ ### Prerequisites
1046
+
1047
+ - **Node.js** 20+
1048
+ - **Yarn** 1.x (classic)
1049
+ - **TypeScript** 5.9+
1050
+
1051
+ ### Setup
1052
+
1053
+ ```bash
1054
+ git clone <repo-url>
1055
+ cd mono-labs-cli
1056
+ yarn install
1057
+ yarn workspace @mono-labs/tracker build
1058
+ ```
1059
+
1060
+ ### Project Structure
1061
+
1062
+ | Directory | Purpose |
1063
+ |----------------|--------------------------------------------------------|
1064
+ | `src/types/` | All TypeScript types, enums, and config defaults |
1065
+ | `src/utils/` | Pure utility functions (ID generation, date parsing) |
1066
+ | `src/storage/` | JSONL persistence and config file loading |
1067
+ | `src/scanner/` | File discovery, notation parsing, attribute/action extraction |
1068
+ | `src/manager/` | High-level facade, querying, validation, stats, mutation helpers |
1069
+ | `src/executor/`| Action dispatch registry and built-in handler stubs |
1070
+
1071
+ ### Development Workflow
1072
+
1073
+ ```bash
1074
+ # Edit source files in src/
1075
+ # Build the package
1076
+ yarn workspace @mono-labs/tracker build
1077
+
1078
+ # Run tests
1079
+ yarn workspace @mono-labs/tracker test
1080
+ ```
1081
+
1082
+ ### Testing
1083
+
1084
+ Tests use [Vitest](https://vitest.dev/) with colocated `.test.ts` files. Run them with:
1085
+
1086
+ ```bash
1087
+ yarn workspace @mono-labs/tracker test
1088
+ ```
1089
+
1090
+ #### Test Files
1091
+
1092
+ | File | Covers |
1093
+ |-------------------------------------|------------------------------------------------------------|
1094
+ | `utils/id-generator.test.ts` | `generateId`, `generateStableId` — format, uniqueness, determinism |
1095
+ | `utils/date-parser.test.ts` | `parseDate`, `isOverdue` — ISO, US, relative formats |
1096
+ | `scanner/notation-parser.test.ts` | `parseFileContent` — marker extraction, multi-line, code context, inline IDs |
1097
+ | `scanner/attribute-parser.test.ts` | `parseAttributes` — all 3 styles, priority/risk maps, relationships |
1098
+ | `scanner/action-parser.test.ts` | `parseActions` — all verbs, chained calls, edge cases |
1099
+ | `storage/jsonl-storage.test.ts` | `JsonlStorage` — read/write/append, atomic writes, corrupt line handling |
1100
+ | `manager/notation-manager.test.ts` | `NotationManager`, `computeStats`, relationships, updaters, validation |
1101
+
1102
+ #### Writing Tests
1103
+
1104
+ Tests follow these patterns:
1105
+
1106
+ ```ts
1107
+ // Helper factory — override only what you need
1108
+ function makeNotation(overrides: Partial<Notation> = {}): Notation {
1109
+ return {
1110
+ id: 'N-test001',
1111
+ type: 'TODO',
1112
+ description: 'Test notation',
1113
+ body: [],
1114
+ codeContext: [],
1115
+ location: { file: 'test.ts', line: 1, column: 1 },
1116
+ status: 'open',
1117
+ tags: [],
1118
+ actions: [],
1119
+ relationships: [],
1120
+ rawBlock: '// TODO: Test',
1121
+ scannedAt: '2026-01-01T00:00:00.000Z',
1122
+ ...overrides,
1123
+ } as Notation
1124
+ }
1125
+
1126
+ // Temp directory pattern for storage tests
1127
+ let tmpDir: string
1128
+ beforeEach(() => {
1129
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tracker-test-'))
1130
+ })
1131
+ afterEach(() => {
1132
+ fs.rmSync(tmpDir, { recursive: true, force: true })
1133
+ })
1134
+ ```
1135
+
1136
+ ### Adding a New Marker Type
1137
+
1138
+ 1. **`src/types/enums.ts`** — Add the new value to the `MarkerType` const object
1139
+ 2. **`src/types/config.ts`** — Add it to `DEFAULT_CONFIG.markers`
1140
+ 3. **`src/scanner/notation-parser.ts`** — Add it to the `MARKER_REGEX` alternation
1141
+ 4. **Tests** — Add test cases in `notation-parser.test.ts`
1142
+
1143
+ ### Adding a New Attribute
1144
+
1145
+ 1. **`src/scanner/attribute-parser.ts`** — Add a case in `applyKeyValue()` for the new key
1146
+ 2. **`src/scanner/attribute-parser.ts`** — Update `ParsedAttributes` interface if a new field is needed
1147
+ 3. **`src/types/notation.ts`** — Add the field to `Notation` if it's a new top-level field
1148
+ 4. **`src/scanner/notation-parser.ts`** — Map the parsed attribute to the `Notation` object
1149
+ 5. **Tests** — Add test cases in `attribute-parser.test.ts`
1150
+
1151
+ ### Adding a New Action Verb
1152
+
1153
+ 1. **`src/types/action.ts`** — Add the verb to `ActionVerb` and create a new `*Args` interface; add it to the `ActionArgs` union
1154
+ 2. **`src/scanner/action-parser.ts`** — Add a case in `buildActionArgs()` for the new verb
1155
+ 3. **`src/executor/actions/`** — Create a stub handler file
1156
+ 4. **`src/executor/actions/index.ts`** — Export the new stub
1157
+ 5. **`src/executor/index.ts`** — Re-export the new stub
1158
+ 6. **`src/index.ts`** — Export the new type and handler
1159
+ 7. **Tests** — Add test cases in `action-parser.test.ts`
1160
+
1161
+ ### Adding a New Query Filter
1162
+
1163
+ 1. **`src/types/notation.ts`** — Add the field to `NotationQuery`
1164
+ 2. **`src/manager/notation-manager.ts`** — Add the filter logic in the `query()` method's filter chain
1165
+ 3. **Tests** — Add test cases in `notation-manager.test.ts`
1166
+
1167
+ ### Code Style
1168
+
1169
+ - **Tabs** for indentation
1170
+ - **Single quotes** for strings
1171
+ - **Trailing commas** in multi-line constructs
1172
+ - **No semicolons**
1173
+ - Match the existing style — when in doubt, look at surrounding code
1174
+
1175
+ ### Monorepo Integration
1176
+
1177
+ The tracker package lives within the `mono-labs-cli` monorepo:
1178
+
1179
+ - **`scripts/bump-version.js`** — Bumps version across all packages (root, shared, project, expo, cli, dev, tracker) in lockstep. Usage: `node scripts/bump-version.js [patch|minor|major]`
1180
+ - **Deploy** — `yarn workspace @mono-labs/tracker deploy` builds and publishes to npm
1181
+ - **Release scripts** — `release:patch`, `release:minor`, `release:major` handle version bump + publish in one command
1182
+
1183
+ ### PR Checklist
1184
+
1185
+ - [ ] `yarn workspace @mono-labs/tracker build` passes with no errors
1186
+ - [ ] `yarn workspace @mono-labs/tracker test` passes (all 102+ tests green)
1187
+ - [ ] New types are exported from barrel files (`src/*/index.ts` and `src/index.ts`)
1188
+ - [ ] No regressions in existing tests
1189
+ - [ ] Code follows existing style (tabs, single quotes, no semicolons, trailing commas)
1190
+ - [ ] Tests added for new functionality
1191
+
1192
+ ---
1193
+
1194
+ ## License
1195
+
1196
+ MIT