How CareerOps Manages the Job Application Tracker Using `data/applications.md`
CareerOps treats data/applications.md as the single source of truth for your job search, exposing a CLI-driven API that enforces canonical status values, atomic row updates, and file-level locking to prevent manual edits and data corruption.
The CareerOps repository implements a disciplined, script-mediated workflow to maintain its application tracker. Rather than editing data/applications.md directly, you interact with the file through specialized Node.js modules that guarantee consistency, traceability, and safe concurrent access across all write operations.
Single Source of Truth Architecture
The Canonical Schema in templates/states.yml
All status values in the application tracker are strictly defined in templates/states.yml. This file acts as the schema authority, ensuring that every status cell—whether "Applied," "Evaluated," or "Offer Received"—uses a normalized string. When scripts like set-status.mjs process updates, they validate incoming values against this canonical list before writing to disk, preventing typographical drift and maintaining filterability.
The Master File at data/applications.md
The markdown file at data/applications.md serves as the atomic database for your entire job search history. It stores data in a header-aware table format that tracker-parse.mjs parses into structured rows. The system maintains original line numbers during rewrites to preserve git blame history, ensuring you can trace any status change back to the exact commit and CLI command that triggered it.
The Write API: CLI Tools for Atomic Updates
Updating Row Status with set-status.mjs
When you need to progress a job application through your pipeline, use set-status.mjs to modify a specific row. This tool loads data/applications.md, locates the target row by ID, normalizes the status against templates/states.yml, and executes an atomic rewrite.
node set-status.mjs 42 Applied --note "Sent application"
Behind the scenes, set-status.mjs imports tracker-utils.mjs and invokes updateRow(), which handles CSV-style quoting, column alignment, and cross-platform line endings before acquiring the write lock.
Batch Importing with merge-tracker.mjs and add-entry.mjs
Adding new applications never requires touching applications.md directly. Instead, generate a TSV file in batch/tracker-additions/ and run the batch importer:
node add-entry.mjs \
--company "Acme Corp" \
--role "Senior ML Engineer" \
--date "2024-07-15" \
--status "Evaluated" \
--score "—"
The add-entry.mjs wrapper generates the TSV and delegates to merge-tracker.mjs, which validates column order (ensuring score appears before status), checks for duplicate rows, and injects the new entry while maintaining the table's structural integrity.
# Process all pending TSV additions
node merge-tracker.mjs
Safety Mechanisms and File Locking
Atomic Writes via tracker-utils.mjs
The tracker-utils.mjs module provides the low-level updateRow() and insertRow() functions that mediate all disk access. These utilities handle the complexity of CSV-style quoting within markdown tables, ensure column alignment across rows, and manage temporary file creation for atomic write operations. By buffering changes to a temporary file before renaming, the system prevents half-written states that could corrupt the tracker during a process crash.
Concurrent Write Protection with tracker-writer-lock.mjs
To prevent race conditions when multiple CLI commands execute simultaneously, CareerOps implements tracker-writer-lock.mjs. This lock file mechanism ensures that only one process can write to data/applications.md at any given moment. If you trigger set-status.mjs while merge-tracker.mjs is already running, the second command waits for the lock to release, guaranteeing that row IDs and line numbers remain consistent.
Read Operations and Data Integrity
Querying with tracker.mjs
For UI components and reporting scripts that need to inspect the tracker without modifying it, tracker.mjs provides a read-only interface. This module exposes the parsed table structure to the rest of the application while enforcing the write-only via CLI helpers architectural rule, ensuring that no UI bug can accidentally alter historical application data.
Cross-Validation via tracker-sync-check.mjs
The tracker-sync-check.mjs script enforces consistency between data/applications.md and data/active-interviews.md. It cross-references the Status column against the interview log, flags drift (such as an application marked "Interviewing" in the tracker but missing from the active interviews file), and suggests corrective actions. Crucially, this tool operates in a read-only mode; it never writes to the tracker file, preserving the separation between validation and modification concerns.
Practical Workflow Examples
Combine these tools into a reproducible, version-controlled pipeline:
# 1. Create a new tracker row via the batch helper
node add-entry.mjs \
--company "Acme Corp" \
--role "Senior ML Engineer" \
--date "2024-07-15" \
--status "Evaluated" \
--score "—"
# 2. Change the status of row #42
node set-status.mjs 42 Applied --note "Application sent on 2024-07-16"
# 3. Merge a batch of TSV additions (e.g., after scanning resumes)
node merge-tracker.mjs
# 4. Verify consistency with the interview log
node tracker-sync-check.mjs
Because every write flows through tracker-utils.mjs and respects the locking protocol, the application tracker remains tidy, sortable (by date, company, or status), and safely merge-able via Git without conflict.
Summary
data/applications.mdserves as the immutable source of truth, parsed bytracker-parse.mjsand never edited by hand.templates/states.ymldefines canonical status strings to prevent data drift and ensure consistent filtering.set-status.mjsandmerge-tracker.mjsprovide the sole write pathways, both utilizingtracker-utils.mjsfor atomic, locked updates.tracker-writer-lock.mjsprevents concurrent write conflicts, maintaining row ID stability andgit blametraceability.tracker-sync-check.mjsvalidates consistency withdata/active-interviews.mdwithout modifying the tracker.
Frequently Asked Questions
Can I edit applications.md manually in a text editor?
No. The system explicitly prohibits manual edits to prevent CSV-style quoting errors, column misalignment, and status typos. Always use set-status.mjs for updates or add-entry.mjs for new rows. The tracker-writer-lock.mjs mechanism assumes exclusive script access; manual edits could cause data corruption or merge conflicts.
What happens if two CLI commands run simultaneously?
The second command blocks until tracker-writer-lock.mjs releases the exclusive lock. This sequential execution ensures that updateRow() operations in tracker-utils.mjs do not overwrite each other, preventing duplicate row IDs or truncated file writes.
How does the system prevent invalid status values?
All write operations validate the status parameter against the canonical list defined in templates/states.yml before persisting changes. If you attempt to set a status like "In-Progress" when the canonical value is "Applied," the script exits with an error, enforcing schema consistency across the application tracker.
How do I add multiple applications at once?
Generate TSV files in the batch/tracker-additions/ directory, then run node merge-tracker.mjs. The script validates the TSV structure (ensuring score precedes status), checks for duplicates, and atomically inserts all rows while maintaining the original line numbering of existing entries.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →