@mono-labs/tracker
v0.1.288
Published
Code notation tracker for scanning, parsing, and managing structured comment markers
Readme
@mono-labs/tracker
Code notation tracker for scanning, parsing, and managing structured comment markers across your codebase.
Table of Contents
- Quick Start
- What is Tracker?
- Notation Syntax Guide
- Configuration
- Core API Walkthrough
- Full Type Reference
- Architecture
- Contributor Guide
- License
Quick Start
Install the package:
yarn add @mono-labs/trackerScan your project and view results in three steps:
import { loadConfig, scanFiles, NotationManager } from '@mono-labs/tracker'
// 1. Load config (reads tracker.config.json or uses defaults)
const config = loadConfig(process.cwd())
// 2. Scan source files for notations
const notations = await scanFiles(config)
// 3. Manage and query results
const manager = new NotationManager(config)
manager.setAll(notations)
await manager.save()
console.log(manager.stats())
console.log(manager.query({ type: 'TODO', priority: 'high' }))What is Tracker?
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.
Tracker solves this by providing a pipeline to:
- Scan — Discover notation comments via configurable glob patterns
- Parse — Extract structured data: priority, assignee, tags, due dates, actions, relationships, performance impact, and technical debt
- Persist — Store results in append-friendly JSONL format with atomic writes
- Query — Filter notations by any combination of fields
- Validate — Check for missing fields, duplicate IDs, broken references, and circular dependencies
- Report — Compute aggregate statistics across your notations
- Execute — Dispatch parsed actions to registered handler functions
Supported Marker Types
| Marker | Purpose |
|------------|--------------------------------------|
| TODO | Planned work |
| FIXME | Known issue needing a fix |
| BUG | Confirmed defect |
| HACK | Temporary workaround |
| NOTE | Informational annotation |
| OPTIMIZE | Performance improvement opportunity |
| SECURITY | Security-related concern |
Notation Syntax Guide
Basic Syntax
Single-line notation with a marker and description:
// TODO: Refactor this function to use async/awaitThe colon after the marker is optional:
// FIXME Broken on empty inputInline IDs
Assign a stable external ID using square brackets:
// TODO [TASK-123] Migrate to the new APIWhen no inline ID is provided, Tracker generates a deterministic ID from the file path and line number using SHA-256.
Multi-line Notations
Continuation comment lines immediately following a marker are collected as the notation body:
// TODO: Refactor authentication module
// This function has grown too complex and handles
// both session management and token refresh.
// @author: Alice
// @priority: highContinuation stops at blank lines, non-comment lines, or a new marker.
Code Context Capture
Non-comment, non-empty lines immediately following the notation block are captured as code context:
// TODO: This query is too slow
// @priority: high
// Performance: 2000ms->100ms
const results = db.query('SELECT * FROM users')
const filtered = results.filter(u => u.active)Here results and filtered lines are captured in the codeContext array.
Attribute Styles
Tracker supports three attribute formats. All can be mixed within the same notation body.
1. @ Prefix Style
// TODO: Implement caching layer
// @author: Alice
// @assignee: Bob
// @priority: high
// @tags: performance, api
// @due: +2w
// @risk: moderate2. Compact Bracket Style
Pack multiple attributes into a single bracketed line. Segments are separated by |.
// TODO: Fix login redirect
// [Alice → Bob | high | 3d | due: 2/24/2026 | tags: auth, ui]- Arrow assignment (
→or->): setsauthorandassignee - Duration shorthand (
3d,2w,1m): sets due date relative to today (hours like8hset debt instead) - Key-value (
due: 2/24/2026): same as@prefix keys - Bare words (
high,critical): matched against priority/risk maps
3. Key-Value Style
Capitalized key followed by colon and value:
// TODO: Update error handling
// Priority: critical
// Tags: ui, api
// Assignee: Charlie
// Debt: 8h | compounding: highActions
Declare code transformation intentions with Action: lines:
// TODO: Clean up legacy code
// Action: replace(oldFunction, newFunction)Chained calls for positional actions:
// TODO: Add error boundary
// Action: insert(ErrorBoundary).before(App)Supported action verbs: replace, remove, rename, insert, extract, move, wrapIn. Unrecognized verbs are parsed as generic.
// Action: remove(legacyHelper)
// Action: rename(fetchData, loadData)
// Action: extract(validateInput).to(validators.ts)
// Action: move(utils).to(shared/utils.ts)
// Action: wrapIn(rawQuery, sanitize)Relationships
Declare dependencies between notations:
// TODO: Implement logout
// Blocks: N-abc12345
// Depends on: N-def45678
// Related: N-ghi78901, N-jkl01234Relationship 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.
Performance Impact
Track measured or expected performance changes:
// OPTIMIZE: Replace N+1 query with batch load
// Performance: 2000ms->100msParsed into a PerformanceImpact object with before, after, and unit fields. Supported units: ms, s, us.
Technical Debt
Estimate and track accumulated debt:
// HACK: Hardcoded timeout
// Debt: 8h | compounding: highThe hours value must use the h suffix. Compounding rate is low, medium, or high (defaults to low).
Priority Shorthand
| Shorthand | Full Value |
|-----------|-----------|
| m1 | minimal |
| l2 | low |
| m3, med | medium |
| h4 | high |
| c5 | critical|
Risk Shorthand
| Shorthand | Full Value |
|-----------|------------|
| m1 | minimal |
| l2 | low |
| m3, mod | moderate |
| s2 | severe |
| c3 | critical |
Configuration
Create a tracker.config.json in your project root:
{
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["**/node_modules/**", "**/dist/**", "**/*.test.ts"],
"markers": ["TODO", "FIXME", "BUG"],
"storagePath": ".tracker/notations.jsonl",
"idPrefix": "N"
}Default Configuration
If no config file is found, or for any omitted fields, these defaults apply:
const DEFAULT_CONFIG: TrackerConfig = {
rootDir: '.',
include: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
markers: ['TODO', 'FIXME', 'BUG', 'HACK', 'NOTE', 'OPTIMIZE', 'SECURITY'],
storagePath: '.tracker/notations.jsonl',
idPrefix: 'N',
}loadConfig(projectRoot: string): TrackerConfig
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.
TrackerConfig Interface
| Field | Type | Description |
|---------------|--------------|--------------------------------------------------|
| rootDir | string | Absolute root directory (set by loadConfig) |
| include | string[] | Glob patterns for files to scan |
| exclude | string[] | Glob patterns for files to skip |
| markers | MarkerType[] | Which marker types to recognize |
| storagePath | string | Path to the JSONL storage file (relative to root) |
| idPrefix | string | Prefix for generated notation IDs |
Core API Walkthrough
Scanning Files
scanFiles(config: TrackerConfig, rootDir?: string): Promise<Notation[]>
Discovers files matching config.include (excluding config.exclude) using fast-glob, reads each file, and parses all notations.
import { loadConfig, scanFiles } from '@mono-labs/tracker'
const config = loadConfig('/path/to/project')
const notations = await scanFiles(config)
console.log(`Found ${notations.length} notations`)The optional rootDir parameter overrides config.rootDir for the scan.
parseFileContent(filePath: string, content: string, idPrefix?: string): Notation[]
Parses notation markers from a raw file string. Useful when you already have file content in memory.
import { parseFileContent } from '@mono-labs/tracker'
const source = `
// TODO: Implement validation
// @priority: high
function validate() {}
`
const notations = parseFileContent('src/validate.ts', source, 'N')
// notations[0].description === 'Implement validation'
// notations[0].priority === 'high'
// notations[0].codeContext === ['function validate() {}']parseAttributes(bodyLines: string[]): ParsedAttributes
Parses attribute lines from a notation body independently. Returns a ParsedAttributes object.
import { parseAttributes } from '@mono-labs/tracker'
const attrs = parseAttributes([
'@author: Alice',
'@priority: high',
'@tags: ui, perf',
])
// attrs.author === 'Alice'
// attrs.priority === 'high'
// attrs.tags === ['ui', 'perf']parseActions(bodyLines: string[]): NotationAction[]
Parses Action: lines from a notation body independently.
import { parseActions } from '@mono-labs/tracker'
const actions = parseActions([
'Action: replace(oldFn, newFn)',
'Action: insert(guard).before(handler)',
])
// actions[0].args.verb === 'replace'
// actions[1].args.verb === 'insert'
// actions[1].args.position === 'before'NotationManager
The NotationManager class is the primary facade for loading, querying, mutating, and persisting notations.
Constructor
import { NotationManager, loadConfig } from '@mono-labs/tracker'
const config = loadConfig(process.cwd())
const manager = new NotationManager(config)The storage file path is resolved from config.storagePath (relative paths resolve against config.rootDir).
load(): Promise<void>
Reads all notations from the JSONL storage file into memory.
await manager.load()
console.log(manager.getAll().length)save(): Promise<void>
Writes all in-memory notations to the JSONL storage file (atomic write via tmp + rename).
manager.setAll(notations)
await manager.save()getAll(): Notation[]
Returns a shallow copy of all notations currently in memory.
getById(id: string): Notation | undefined
Returns a single notation by its ID, or undefined if not found.
const notation = manager.getById('N-abc12345')setAll(notations: Notation[]): void
Replaces all in-memory notations with a new array (shallow copy).
query(q: NotationQuery): Notation[]
Filters notations by any combination of query fields. See Querying for full filter reference.
const critical = manager.query({ priority: 'critical', status: 'open' })update(id: string, updates: Partial<Notation>): boolean
Merges updates into the notation with the given ID. Returns true if the notation was found and updated, false otherwise.
manager.update('N-abc12345', { status: 'resolved', assignee: 'Bob' })validate(): ValidationError[]
Runs full validation across all notations: field checks, duplicate IDs, broken references, and circular dependency detection.
const errors = manager.validate()
if (errors.length > 0) {
console.error('Validation errors:', errors)
}stats(): NotationStats
Computes aggregate statistics over all in-memory notations.
const s = manager.stats()
console.log(`Total: ${s.total}, Overdue: ${s.overdue}, Debt: ${s.totalDebtHours}h`)Querying
The query() method accepts a NotationQuery object. All fields are optional; multiple fields combine with AND logic.
| Field | Type | Behavior |
|-------------|--------------------------------|-------------------------------------------------------------|
| type | MarkerType \| MarkerType[] | Match one or more marker types |
| tags | string[] | Match notations containing any of the specified tags |
| priority | Priority \| Priority[] | Match one or more priority levels |
| status | Status \| Status[] | Match one or more statuses |
| file | string | Substring match against location.file |
| assignee | string | Exact match against assignee |
| overdue | boolean | If true, return only overdue non-resolved notations |
| blocked | boolean | If true, return only blocked notations |
| search | string | Case-insensitive substring search in description, body, tags|
| dueBefore | string | ISO date string — notations due on or before this date |
| dueAfter | string | ISO date string — notations due on or after this date |
Array values enable multi-select filtering:
manager.query({ type: ['TODO', 'BUG'], priority: ['high', 'critical'] })Storage
JsonlStorage
Low-level storage engine that persists notations as newline-delimited JSON.
import { JsonlStorage } from '@mono-labs/tracker'
const storage = new JsonlStorage('.tracker/notations.jsonl')readAll(): Promise<Notation[]>
Reads and parses all lines from the JSONL file. Corrupt lines are silently skipped. Returns an empty array if the file doesn't exist.
writeAll(notations: Notation[]): Promise<void>
Atomically writes all notations: writes to a .tmp file first, then renames over the target. Creates parent directories if needed.
append(notation: Notation): Promise<void>
Appends a single notation as a new line to the file.
appendBatch(notations: Notation[]): Promise<void>
Appends multiple notations in a single write operation. No-ops on empty arrays.
Utilities
generateId(prefix?: string): string
Generates a random ID using UUID v4. Default prefix is 'N'.
import { generateId } from '@mono-labs/tracker'
generateId() // 'N-a1b2c3d4'
generateId('T') // 'T-e5f6a7b8'generateStableId(prefix: string, file: string, line: number): string
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.
import { generateStableId } from '@mono-labs/tracker'
generateStableId('N', 'src/app.ts', 42) // 'N-<8-char hash>'parseDate(input: string): string | null
Parses date strings in multiple formats. Returns an ISO date string (YYYY-MM-DD) or null.
| Format | Example | Notes |
|------------------|----------------|--------------------------------|
| ISO | 2026-02-24 | Returned as-is |
| US | 2/24/2026 | MM/DD/YYYY, zero-padding optional |
| Relative days | +3d | 3 days from today |
| Relative weeks | +2w | 14 days from today |
| Relative months | +1m | 1 month from today |
| Relative years | +1y | 1 year from today |
import { parseDate } from '@mono-labs/tracker'
parseDate('2026-02-24') // '2026-02-24'
parseDate('2/24/2026') // '2026-02-24'
parseDate('+2w') // ISO date 14 days from nowisOverdue(dateStr: string): boolean
Returns true if the given ISO date string is in the past (compared at end of day, 23:59:59).
import { isOverdue } from '@mono-labs/tracker'
isOverdue('2020-01-01') // true
isOverdue('2099-12-31') // falseRelationships & Validation
getBlockers(notation: Notation, allNotations: Notation[]): Notation[]
Returns all notations referenced in notation.relationships that have a non-resolved status.
isBlocked(notation: Notation, allNotations: Notation[]): boolean
Returns true if the notation has any unresolved blockers.
import { isBlocked, getBlockers } from '@mono-labs/tracker'
const blockers = getBlockers(myNotation, allNotations)
if (isBlocked(myNotation, allNotations)) {
console.log('Blocked by:', blockers.map(b => b.id))
}detectCircularDependencies(notations: Notation[]): string[][]
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.
import { detectCircularDependencies } from '@mono-labs/tracker'
const cycles = detectCircularDependencies(allNotations)
// [['N-abc', 'N-def', 'N-abc']]validateNotation(notation: Notation): ValidationError[]
Validates a single notation for:
- Missing
id,description, orlocation.file - Invalid
type,status,priority, orriskvalues
validateAll(notations: Notation[]): ValidationError[]
Validates all notations and additionally checks for:
- Duplicate IDs
- Broken relationship references (IDs not in the set)
- Circular dependencies
import { validateAll } from '@mono-labs/tracker'
const errors = validateAll(notations)
for (const err of errors) {
console.error(`${err.notationId} [${err.field}]: ${err.message}`)
}computeStats(notations: Notation[]): NotationStats
Standalone function to compute statistics from a notation array (same logic as manager.stats()).
Mutation Helpers
Immutable helper functions that return new Notation objects:
updateStatus(notation: Notation, status: Status): Notation
import { updateStatus, Status } from '@mono-labs/tracker'
const resolved = updateStatus(notation, Status.RESOLVED)addTag(notation: Notation, tag: string): Notation
Adds a tag if not already present. Returns the same object if the tag exists.
import { addTag } from '@mono-labs/tracker'
const tagged = addTag(notation, 'urgent')removeTag(notation: Notation, tag: string): Notation
import { removeTag } from '@mono-labs/tracker'
const untagged = removeTag(notation, 'stale')setAssignee(notation: Notation, assignee: string): Notation
import { setAssignee } from '@mono-labs/tracker'
const assigned = setAssignee(notation, 'Alice')Executor Framework
The executor provides a plugin-based system for dispatching parsed actions to handler functions.
registerActionHandler(verb: string, handler: ActionHandler): void
Registers a handler function for a specific action verb.
import { registerActionHandler } from '@mono-labs/tracker'
import type { ActionHandler } from '@mono-labs/tracker'
const myHandler: ActionHandler = async (action) => {
// Implement your logic here
return { success: true, message: 'Done', verb: action.verb }
}
registerActionHandler('replace', myHandler)executeAction(action: NotationAction): Promise<ActionResult>
Dispatches an action to the registered handler for its verb. Returns { success: false } if no handler is registered.
import { executeAction } from '@mono-labs/tracker'
for (const action of notation.actions) {
const result = await executeAction(action)
if (!result.success) {
console.error(`Failed: ${result.message}`)
}
}Built-in Stubs
Three stub handlers are exported for reference and testing. They return success: true with a descriptive message but perform no actual file operations:
handleReplace— Stub forreplaceactionshandleRemove— Stub forremoveactionshandleRename— Stub forrenameactions
Register them if you want safe no-op handling:
import { registerActionHandler, handleReplace, handleRemove, handleRename } from '@mono-labs/tracker'
registerActionHandler('replace', handleReplace)
registerActionHandler('remove', handleRemove)
registerActionHandler('rename', handleRename)Writing a Custom Handler
import { registerActionHandler } from '@mono-labs/tracker'
import type { ActionHandler, NotationAction, ActionResult } from '@mono-labs/tracker'
const handleExtract: ActionHandler = async (action: NotationAction): Promise<ActionResult> => {
if (action.args.verb !== 'extract') {
return { success: false, message: 'Wrong verb', verb: action.verb }
}
const { target, destination } = action.args
// ... perform extraction logic ...
return { success: true, message: `Extracted ${target} to ${destination}`, verb: 'extract' }
}
registerActionHandler('extract', handleExtract)Full Type Reference
Enums
All enums are defined as const objects with matching type aliases, enabling both runtime access and type safety.
MarkerType
'TODO' | 'FIXME' | 'BUG' | 'HACK' | 'NOTE' | 'OPTIMIZE' | 'SECURITY'Priority
'minimal' | 'low' | 'medium' | 'high' | 'critical'RiskLevel
'minimal' | 'low' | 'moderate' | 'severe' | 'critical'Status
'open' | 'in_progress' | 'blocked' | 'resolved'CompoundingRate
'low' | 'medium' | 'high'ActionVerb
'replace' | 'remove' | 'rename' | 'insert' | 'extract' | 'move' | 'wrapIn' | 'generic'Interfaces
Notation
| Field | Type | Required | Description |
|---------------|---------------------|----------|--------------------------------------------|
| id | string | yes | Unique identifier (inline or generated) |
| type | MarkerType | yes | The marker type |
| description | string | yes | First-line description text |
| body | string[] | yes | Continuation comment lines |
| codeContext | string[] | yes | Code lines following the notation block |
| location | SourceLocation | yes | File, line, column, and optional endLine |
| author | string | no | Author name from attributes |
| assignee | string | no | Assignee name |
| priority | Priority | no | Priority level |
| risk | RiskLevel | no | Risk level |
| status | Status | yes | Current status (default: 'open') |
| tags | string[] | yes | Tags parsed from attributes |
| dueDate | string | no | ISO date string |
| createdDate | string | no | ISO date string |
| performance | PerformanceImpact | no | Performance before/after measurement |
| debt | TechnicalDebt | no | Estimated debt hours and compounding rate |
| actions | NotationAction[] | yes | Parsed action instructions |
| relationships | string[] | yes | IDs of related notations |
| rawBlock | string | yes | Original raw text of the notation block |
| scannedAt | string | yes | ISO timestamp of when the notation was scanned |
SourceLocation
| Field | Type | Required | Description |
|-----------|----------|----------|--------------------------------|
| file | string | yes | File path |
| line | number | yes | Start line (1-indexed) |
| column | number | yes | Column offset (1-indexed) |
| endLine | number | no | End line of the notation block |
PerformanceImpact
| Field | Type | Description |
|----------|----------|--------------------------------------|
| before | string | Value before (e.g., '2000ms') |
| after | string | Value after (e.g., '100ms') |
| unit | string | Unit from the "after" value (ms, s, us) |
TechnicalDebt
| Field | Type | Description |
|---------------|-------------------|--------------------------------|
| hours | number | Estimated debt in hours |
| compounding | CompoundingRate | How fast the debt grows |
NotationQuery
| Field | Type | Description |
|-------------|--------------------------------|--------------------------------------|
| type | MarkerType \| MarkerType[] | Filter by marker type(s) |
| tags | string[] | Filter by tag (OR match) |
| priority | Priority \| Priority[] | Filter by priority level(s) |
| status | Status \| Status[] | Filter by status(es) |
| file | string | Substring match on file path |
| assignee | string | Exact assignee match |
| overdue | boolean | Only overdue, non-resolved notations |
| blocked | boolean | Only blocked notations |
| search | string | Full-text search (description, body, tags) |
| dueBefore | string | Due on or before this ISO date |
| dueAfter | string | Due on or after this ISO date |
NotationStats
| Field | Type | Description |
|------------------|-------------------------|----------------------------------|
| total | number | Total notation count |
| byType | Record<string, number>| Count per marker type |
| byPriority | Record<string, number>| Count per priority level |
| byStatus | Record<string, number>| Count per status |
| byTag | Record<string, number>| Count per tag |
| byAssignee | Record<string, number>| Count per assignee |
| overdue | number | Number of overdue notations |
| blocked | number | Number of blocked notations |
| totalDebtHours | number | Sum of all debt hours |
NotationAction
| Field | Type | Description |
|--------|--------------|--------------------------------|
| verb | ActionVerb | The action verb |
| raw | string | Original raw action string |
| args | ActionArgs | Parsed arguments (discriminated union) |
ActionArgs Variants
| Variant | Fields |
|---------------|-------------------------------------------------|
| ReplaceArgs | verb: 'replace', target, replacement |
| RemoveArgs | verb: 'remove', target |
| RenameArgs | verb: 'rename', from, to |
| InsertArgs | verb: 'insert', content, position ('before' \| 'after'), anchor |
| ExtractArgs | verb: 'extract', target, destination |
| MoveArgs | verb: 'move', target, destination |
| WrapInArgs | verb: 'wrapIn', target, wrapper |
| GenericArgs | verb: 'generic', description |
TrackerConfig
| Field | Type | Description |
|---------------|----------------|-----------------------------------|
| rootDir | string | Project root directory |
| include | string[] | Glob patterns to include |
| exclude | string[] | Glob patterns to exclude |
| markers | MarkerType[] | Marker types to recognize |
| storagePath | string | JSONL file path |
| idPrefix | string | Prefix for generated IDs |
ValidationError
| Field | Type | Description |
|--------------|----------|-------------------------------------|
| notationId | string | ID of the notation with the error |
| field | string | Field name that failed validation |
| message | string | Human-readable error message |
ActionResult
| Field | Type | Description |
|-----------|-----------|----------------------------------|
| success | boolean | Whether the action succeeded |
| message | string | Result or error message |
| verb | string | The action verb that was executed|
ActionHandler
type ActionHandler = (action: NotationAction) => Promise<ActionResult>ParsedAttributes
| Field | Type | Description |
|----------------|---------------------|--------------------------------|
| author | string \| undefined | Parsed author |
| assignee | string \| undefined | Parsed assignee |
| priority | Priority \| undefined | Parsed priority |
| risk | RiskLevel \| undefined | Parsed risk level |
| tags | string[] | Parsed tags |
| dueDate | string \| undefined | Parsed due date (ISO) |
| createdDate | string \| undefined | Parsed created date (ISO) |
| performance | PerformanceImpact \| undefined | Parsed performance |
| debt | TechnicalDebt \| undefined | Parsed debt |
| relationships| string[] | Parsed relationship IDs |
Architecture
Layer Diagram
┌─────────────────────────────────────────────────────────┐
│ Executor │
│ registerActionHandler · executeAction │
├─────────────────────────────────────────────────────────┤
│ Manager │
│ NotationManager · query · validate · stats · updaters │
├─────────────────────────────────────────────────────────┤
│ Scanner │
│ scanFiles · parseFileContent · parseAttributes │
├─────────────────────────────────────────────────────────┤
│ Storage │
│ JsonlStorage · loadConfig │
├─────────────────────────────────────────────────────────┤
│ Utils │
│ generateId · generateStableId · parseDate │
├─────────────────────────────────────────────────────────┤
│ Types │
│ enums · Notation · Action · Config · Query · Stats │
└─────────────────────────────────────────────────────────┘Each layer depends only on layers below it.
Source Tree
packages/tracker/
├── src/
│ ├── index.ts # Public barrel export
│ ├── types/
│ │ ├── index.ts # Type barrel
│ │ ├── enums.ts # MarkerType, Priority, RiskLevel, Status, CompoundingRate
│ │ ├── notation.ts # Notation, SourceLocation, NotationQuery, NotationStats
│ │ ├── action.ts # ActionVerb, NotationAction, all ActionArgs variants
│ │ └── config.ts # TrackerConfig, DEFAULT_CONFIG
│ ├── utils/
│ │ ├── index.ts # Utils barrel
│ │ ├── id-generator.ts # generateId, generateStableId
│ │ ├── id-generator.test.ts
│ │ ├── date-parser.ts # parseDate, isOverdue
│ │ └── date-parser.test.ts
│ ├── storage/
│ │ ├── index.ts # Storage barrel
│ │ ├── jsonl-storage.ts # JsonlStorage class
│ │ ├── jsonl-storage.test.ts
│ │ └── config-loader.ts # loadConfig
│ ├── scanner/
│ │ ├── index.ts # Scanner barrel
│ │ ├── file-scanner.ts # scanFiles (glob + read + parse)
│ │ ├── notation-parser.ts # parseFileContent
│ │ ├── notation-parser.test.ts
│ │ ├── attribute-parser.ts # parseAttributes (3 style strategies)
│ │ ├── attribute-parser.test.ts
│ │ ├── action-parser.ts # parseActions (chained call parser)
│ │ └── action-parser.test.ts
│ ├── manager/
│ │ ├── index.ts # Manager barrel
│ │ ├── notation-manager.ts # NotationManager class
│ │ ├── notation-manager.test.ts
│ │ ├── notation-updater.ts # updateStatus, addTag, removeTag, setAssignee
│ │ ├── relationship-manager.ts # getBlockers, isBlocked, detectCircularDependencies
│ │ ├── validator.ts # validateNotation, validateAll
│ │ └── stats.ts # computeStats
│ └── executor/
│ ├── index.ts # Executor barrel
│ ├── action-executor.ts # registerActionHandler, executeAction
│ └── actions/
│ ├── index.ts # Action stubs barrel
│ ├── replace-action.ts # handleReplace stub
│ ├── remove-action.ts # handleRemove stub
│ └── rename-action.ts # handleRename stub
├── package.json
├── tsconfig.json
└── vitest.config.tsDesign Decisions
- JSONL storage — Append-friendly format; each line is an independent JSON object, making it resilient to partial writes and easy to stream
- Atomic writes —
writeAllwrites to a.tmpfile then renames, preventing data corruption on crash - Stable IDs —
generateStableIduses SHA-256 offile:line, so re-scanning produces the same IDs for unchanged notations - Corrupt line resilience —
readAllsilently skips unparseable lines, so a single corrupt entry doesn't break the entire store - Immutable updaters — Mutation helpers (
updateStatus,addTag, etc.) return new objects, leaving originals unchanged - Plugin executor — The handler registry decouples action parsing from execution, allowing consumers to implement their own file-modification logic
Contributor Guide
Prerequisites
- Node.js 20+
- Yarn 1.x (classic)
- TypeScript 5.9+
Setup
git clone <repo-url>
cd mono-labs-cli
yarn install
yarn workspace @mono-labs/tracker buildProject Structure
| Directory | Purpose |
|----------------|--------------------------------------------------------|
| src/types/ | All TypeScript types, enums, and config defaults |
| src/utils/ | Pure utility functions (ID generation, date parsing) |
| src/storage/ | JSONL persistence and config file loading |
| src/scanner/ | File discovery, notation parsing, attribute/action extraction |
| src/manager/ | High-level facade, querying, validation, stats, mutation helpers |
| src/executor/| Action dispatch registry and built-in handler stubs |
Development Workflow
# Edit source files in src/
# Build the package
yarn workspace @mono-labs/tracker build
# Run tests
yarn workspace @mono-labs/tracker testTesting
Tests use Vitest with colocated .test.ts files. Run them with:
yarn workspace @mono-labs/tracker testTest Files
| File | Covers |
|-------------------------------------|------------------------------------------------------------|
| utils/id-generator.test.ts | generateId, generateStableId — format, uniqueness, determinism |
| utils/date-parser.test.ts | parseDate, isOverdue — ISO, US, relative formats |
| scanner/notation-parser.test.ts | parseFileContent — marker extraction, multi-line, code context, inline IDs |
| scanner/attribute-parser.test.ts | parseAttributes — all 3 styles, priority/risk maps, relationships |
| scanner/action-parser.test.ts | parseActions — all verbs, chained calls, edge cases |
| storage/jsonl-storage.test.ts | JsonlStorage — read/write/append, atomic writes, corrupt line handling |
| manager/notation-manager.test.ts | NotationManager, computeStats, relationships, updaters, validation |
Writing Tests
Tests follow these patterns:
// Helper factory — override only what you need
function makeNotation(overrides: Partial<Notation> = {}): Notation {
return {
id: 'N-test001',
type: 'TODO',
description: 'Test notation',
body: [],
codeContext: [],
location: { file: 'test.ts', line: 1, column: 1 },
status: 'open',
tags: [],
actions: [],
relationships: [],
rawBlock: '// TODO: Test',
scannedAt: '2026-01-01T00:00:00.000Z',
...overrides,
} as Notation
}
// Temp directory pattern for storage tests
let tmpDir: string
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tracker-test-'))
})
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true })
})Adding a New Marker Type
src/types/enums.ts— Add the new value to theMarkerTypeconst objectsrc/types/config.ts— Add it toDEFAULT_CONFIG.markerssrc/scanner/notation-parser.ts— Add it to theMARKER_REGEXalternation- Tests — Add test cases in
notation-parser.test.ts
Adding a New Attribute
src/scanner/attribute-parser.ts— Add a case inapplyKeyValue()for the new keysrc/scanner/attribute-parser.ts— UpdateParsedAttributesinterface if a new field is neededsrc/types/notation.ts— Add the field toNotationif it's a new top-level fieldsrc/scanner/notation-parser.ts— Map the parsed attribute to theNotationobject- Tests — Add test cases in
attribute-parser.test.ts
Adding a New Action Verb
src/types/action.ts— Add the verb toActionVerband create a new*Argsinterface; add it to theActionArgsunionsrc/scanner/action-parser.ts— Add a case inbuildActionArgs()for the new verbsrc/executor/actions/— Create a stub handler filesrc/executor/actions/index.ts— Export the new stubsrc/executor/index.ts— Re-export the new stubsrc/index.ts— Export the new type and handler- Tests — Add test cases in
action-parser.test.ts
Adding a New Query Filter
src/types/notation.ts— Add the field toNotationQuerysrc/manager/notation-manager.ts— Add the filter logic in thequery()method's filter chain- Tests — Add test cases in
notation-manager.test.ts
Code Style
- Tabs for indentation
- Single quotes for strings
- Trailing commas in multi-line constructs
- No semicolons
- Match the existing style — when in doubt, look at surrounding code
Monorepo Integration
The tracker package lives within the mono-labs-cli monorepo:
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]- Deploy —
yarn workspace @mono-labs/tracker deploybuilds and publishes to npm - Release scripts —
release:patch,release:minor,release:majorhandle version bump + publish in one command
PR Checklist
- [ ]
yarn workspace @mono-labs/tracker buildpasses with no errors - [ ]
yarn workspace @mono-labs/tracker testpasses (all 102+ tests green) - [ ] New types are exported from barrel files (
src/*/index.tsandsrc/index.ts) - [ ] No regressions in existing tests
- [ ] Code follows existing style (tabs, single quotes, no semicolons, trailing commas)
- [ ] Tests added for new functionality
License
MIT
