Reporting Commands
Relevant Source Files
Reporting commands provide deep visibility into the educational health of your Obsidian vault. They scan topic frontmatter to calculate global mastery metrics, report difficulty distributions, track spaced repetition statistics, and detect structural integrity violations across the prerequisite graph.
1. Dashboard Command (palee dashboard)
The palee dashboard command provides a high-level executive summary of your vault's learning state. It aggregates overall topic mastery percentages, tallies review queues, breaks down progress by difficulty tiers, and surfaces the single most urgent upcoming review.
Syntax & Options
palee dashboard [flags]| Flag | Type | Default | Description | Example |
|---|---|---|---|---|
--json | boolean | false | Output dashboard metrics as structured JSON (auto-activated in non-TTY environments). | palee dashboard --json |
Data Aggregation & Metric Calculations
dashboardCommand scans all topic files in the vault and classifies notes using the 4-pillar mastery threshold (
- Mastered: Topics with
topic_mastery >= 0.70. - Learning: Topics actively in progress (
0.0 < topic_mastery < 0.70). - New: Unreviewed topics with
topic_mastery === 0.0. - Reviews Due: Topics where
due_atis in the past or equal to the current system time. - Difficulty Tiers: Aggregated counts and mastered subtotals across
beginner,intermediate, andadvancedtiers.
Example Human-Readable Output
$ palee dashboard
╔════════════════════════════════════════════════════════════╗
║ PALEE Learning Dashboard ║
╚════════════════════════════════════════════════════════════╝
Total Topics: 15
Mastered (≥70%): 6 (40.0%)
Learning: 7 (46.7%)
New: 2 (13.3%)
Due for Review: 3
By Difficulty:
beginner : 5 topics (4 mastered)
intermediate : 7 topics (2 mastered)
advanced : 3 topics (0 mastered)
Next Review:
Memory Ownership (T-20260814T120100-efgh)
Mastery: 45.0% | Reps: 2
─────────────────────────────────────────────────────────────
Run "palee next" to start reviewing
Run "palee plan" to see today's learning plan2. Progress Command (palee progress)
The palee progress command offers granular analytics into learning retention, historical repetitions, lapse counts, and topic-specific mastery scores.
Syntax & Options
palee progress [flags]| Flag | Type | Default | Description | Example |
|---|---|---|---|---|
--topic <id> | string | undefined | Inspect a specific topic by ID (e.g. T-20260814T120000-abcd) or unique title substring. | palee progress --topic "Recursion" |
--json | boolean | false | Output progress metrics as structured JSON (auto-activated in non-TTY environments). | palee progress --json |
Vault-Wide vs Topic-Specific Modes
1. Vault-Wide Mode (Default)
Aggregates all active topics across the vault src/cli/progress.ts#140-233:
Archived Topic Exclusion: Notes with
status: archivedin frontmatter are tracked separately and excluded fromglobal_masteryandactive_topic_count.Global Average Mastery: Calculates the true mathematical mean of mastery across all active topics:
textglobal_mastery = sum(active_topics.mastery) / active_topic_countMastery Status: Classifies vault overall state as
'no_data'(active_count === 0),'learning'(< 0.70), or'mastered'(>= 0.70).Total Repetitions & Lapses: Aggregates lifetime review repetitions and memory lapses across all active topics.
2. Topic-Specific Mode (--topic <id>)
Surfaces comprehensive SRS metadata for an individual topic note:
topic_masterypercentagedifficultytierrepetitioncount andlapsescountassessed_atandlast_reviewed_attimestamps
Example Outputs
Vault-Wide Human-Readable Summary
$ palee progress
=== Learning Progress ===
Active Topics: 14 (1 archived)
Mastered (≥70%): 6 (42.9%)
Learning: 6 (42.9%)
New: 2 (14.3%)
Average Mastery: 54.3% (learning)
Total Reviews: 28
Total Lapses: 3
By Difficulty:
beginner: 5 topics, avg mastery 78.0%
intermediate: 6 topics, avg mastery 48.3%
advanced: 3 topics, avg mastery 26.7%Topic Lookup
$ palee progress --topic "Recursion"
Progress for: Recursion and Backtracking
ID: T-20260814T120000-abcd
Path: DSA/Recursion.md
Mastery: 80.0%
Difficulty: advanced
Repetitions: 5
Lapses: 0
Last Assessed: 2026-08-25
Last Reviewed: 2026-08-253. Validate Command (palee validate)
The palee validate command performs static analysis on the entire Obsidian vault to verify data model integrity and prerequisite graph acyclicity.
Syntax & Options
palee validate [flags]| Flag | Type | Default | Description | Example |
|---|---|---|---|---|
--fix | boolean | false | Attempt automated repairs for detected validation errors (Phase 1 diagnostic flag). | palee validate --fix |
--json | boolean | false | Output validation diagnostics as structured JSON (auto-activated in non-TTY environments). | palee validate --json |
--strict | boolean | false | Escalate warnings to a non-zero exit code: a warnings-only vault exits 3 like an errors vault (default: warnings never gate the exit code). | palee validate --strict |
Vault Structural Integrity Rules
palee validate runs a nineteen-rule validation framework (rules live under src/validation/rules/, registered in src/cli/validate.ts, exported through the src/validation/ barrel): eleven error-default rules (the graph integrity ports minus missing-dependency, the schema/identity rules, the assessment, review-state, session-schema, and vault-path-boundary rules) and eight warning-default rules — the two snapshot rules that explain gaps in the collected topic set (read-failure covers walked notes, session notes, and memory-subsystem components alike — a partial snapshot is reported wherever it was discovered), the mastery-drift rule, missing-dependency findings (the engine quarantines the dependent topic instead of failing the scan; roadmap --from pre-validation keeps its own separate error path), the ambiguous-kind rule for managed notes, the session-unknown-topic rule, the session-index rule, and the hot-memory rule. valid-dependency-list is error-default but emits its duplicate-entry findings as warnings. Errors exit 3; warnings exit 0 unless --strict escalates them to 3. The memory subsystem (.palee/sessions/*, .palee/index.md, .palee/hot.md) is collected in the same single-read snapshot as the topic vault; a fresh vault with no memory subsystem validates clean.
Malformed Frontmatter (
parse-frontmatter, warning): A note whose YAML frontmatter cannot be parsed (including unclosed---fences whose body reads like YAML). The scan always continues — one bad note is a finding, never a dead validation.Read Failures (
read-failure, warning): A file that could not be read at all (locked or deleted mid-scan). Validation ran on an incomplete snapshot; the warning appears alongside any graph findings so transient conditions are visible without downgrading them.Schema Version (
valid-palee-schema, error): Every PALEE-managed note must declarepalee_schema: 1; missing, non-integer, and unsupported versions are errors so mutations can refuse to guess at unknown data. Non-managed user notes are never reported.Topic ID Format (
valid-topic-id-format, error): Topic IDs must match the centralized policy insrc/engine/topic-id.ts—T-plus lowercase kebab-case segments; the exact legacy adopt-generated format stays valid.Topic Status (
valid-topic-status, error): Status must be one ofnot_started|learning|paused|archived; missing status is tolerated as the adopt default.Duplicate Topic IDs (
no-duplicate-topic-id, error): Multiple Markdown notes sharing the samepalee_idin their frontmatter.Dependency List Shape (
valid-dependency-list, error):depends_onmust be an array of non-empty topic-ID strings — a bare string, a non-string item (numbers, booleans, nulls), or an empty-string slot is a shape error, and a self-reference (T-adepending onT-a) is a structural error. Duplicate entries are a separate warning: the loader dedupes them, so scheduling is unaffected. Missing or nulldepends_onis the documented empty-list default and never reports. The rule runs before the graph rules and reads raw frontmatter (pre-normalization) so defects the loader's coercion would hide are exposed.Missing Dependencies (
no-missing-dependency, warning): A topic referencing a prerequisite ID independs_onthat does not exist anywhere in the vault. A warning in vault scans (the engine quarantines the dependent topic fromplan/nextinstead of failing the scan — incrementally-written vaults are the norm, and a note mid-flight must not fail a whole validation run);roadmap --frompre-validation keeps its own separate hard-error path, so an import-time dangling reference still blocks the import. Findings never depend on unrelated vault state; if a dependency target was itself unreadable, theread-failurewarning appears alongside explaining the transient condition, and re-running settles it.--strictescalates the warning for CI use.Dependency Cycles (
no-dependency-cycle, error): Circular dependency chains (e.g.) detected by the dependency engine src/engine/dependency.ts (iterative Tarjan SCC analysis with a lexicographic-first cycle search; the rule reports the exact path). Assessment Fields (
valid-assessment-fields, error): Assessment scores (conceptual,practical,debug,feynman) must be finite numbers within[0.0, 1.0]as stored on disk, andassessed_atmust benullor a real calendar date — date-only strings (YYYY-MM-DD) and ISO timestamps (2026-02-30T12:00:00Z) alike are rejected when their written calendar rolls over (2026-02-30is not normalized into March). The rule reads raw frontmatter values — the loader clamps and coerces during normalization, so this rule exposes real vault corruption instead of silently blessing it. Missing assessment fields follow the documented default policy (they are the newly-adopted state) and pass.Topic Mastery Drift (
valid-topic-mastery, warning): When a topic's assessment fields are shape-valid, storedtopic_masterymust equal the engine formularound((conceptual + practical + debug + 2*feynman) / 5, 4)— recomputed withcomputeTopicMasteryfrom src/engine/mastery.ts. Drift reports a warning withdetails.actual(stored) anddetails.expected(computed); a present-but-malformed stored value (non-numeric, non-finite) is itself a mismatch — the loader would coerce it to 0 at runtime, so the rule reads the raw value to expose that. Topics whose assessment fields fail rule 10 are skipped (no double-reporting), topics with all four pillars absent are the newly-adopted default state and never report, missing assessment data never crashes the rule, and archived topics are still checked — internal consistency matters for any stored topic. A warning only, so--strictgates it.Review Fields (
valid-review-fields, error): SM-2 review state must match the engine contract as stored on disk:ease_factora finite number>= 1.3(the SM-2 floor),interval_daysan integer>= 1,repetition/lapsesintegers>= 0, andlast_qualitynullor an integer0-5. The rule reads raw frontmatter (the loader'sparseNumber/parseIntegercoercion would silently accept stringified or fractional values), so values no PALEE writer could produce — stringified numbers,nullnumeric state, fractional counters — are exposed as errors. Missing keys are the adopt-default state and pass.Review Dates (
valid-review-dates, error):last_reviewed_atanddue_atmust benull(newly adopted) or strict zero-padded date-onlyYYYY-MM-DDstrings naming real calendar dates — the exact shapeformatLocalDateOnlypersists. Full ISO timestamps fail (date-only is the product contract: review scheduling compares local calendar days), impossible calendars fail (2026-02-31is not normalized into March), and when both fields are valid,due_atearlier thanlast_reviewed_atreports the inversion. Validation is pure component analysis shared with theassessed_atpolicy — no timezone-dependent parsing.Managed Note Kind (
valid-managed-note-kind, warning): A note declaringpalee_schemaasserts PALEE owns part of its frontmatter — it must carry exactly one recognizable identity:palee_id(topic),session_id(session),memory_id(hot memory), ortype: session_index(index). A versioned note with no identity, or one claiming two kinds at once (e.g.palee_id+session_id), reports a warning — a human decides what the note is; guessing is unsafe for validation and migration. Runs before the schema rules so kind-specific errors are read with the kind in hand. Internal notes (.palee/sessions/*,index.md,hot.md) never reach the vault walker, so the rule classifies them from the collected memory snapshot too: a session note also carryingpalee_id, or the index also carryingsession_id, reports the same conflict — their kind is fixed by location, so only conflicting-identity claims are findings there (shape defects stay with the memory rules).Session Schema (
valid-session-schema, error): Canonical session notes under.palee/sessions/— the durable learning history thathot.mdandindex.mdrebuild from — must carry the full schema: required fields (session_id,topic_id,started_at,ended_at),statusofcompletedordraftcoherent with theS-/DRAFT-S-filename convention,session_idmatching the filename stem (the rebuild paths key on it), ISO 8601 timestamps carrying an explicit timezone designator (Zor±HH:MM— offset forms pass; date-only and timezone-less strings fail) withended_atnot precedingstarted_at,ended_at: nullfor drafts and a timestamp for completed sessions. Every session note must also declare a supportedpalee_schemaversion — the canonical writers emitpalee_schema: 1and the rebuild paths default missing versions silently, so validation is where a foreign or missing version on canonical data surfaces (the same policy asvalid-palee-schema, session side). Malformed YAML is one error, never a silently skipped note; a note that could not be read at all (locked or deleted mid-scan) is theread-failurerule's finding, never a schema error. Reads raw frontmatter — the rebuild paths cast withas string, so shape drift is silent at runtime and validation is the only place it surfaces.Session Unknown Topic (
no-session-unknown-topic, warning): A session'stopic_idmust reference a topic that exists in the vault — a session pointing at a missing topic cannot be connected back to the learning graph. The historicalT-generalphantom sessions (from the pre-hot-memorysession endfallback) are reported like any other unknown topic unless a real topic with that ID exists; no special case hides the bug the rule exists to surface. Drafts follow the same policy; sessions whose frontmatter failedvalid-session-schemaare skipped (no double-reporting). A warning while existing vaults may still contain legacyT-generalsessions;--strictescalates.Session Index (
valid-session-index, warning): The derived.palee/index.mdmust parse, and its[[S-…]]session references must point at existing confirmed session notes. A stale or broken entry reports the missing session ID; the index is a rebuildable projection (canonical session notes are the source of truth — ADR-0008 decision 4), so findings never become errors and never gate the exit code by default;--strictescalates them like every other warning (ADR-0008 decision 3), and a rebuild restores correctness. Only session-shaped[[S-…]]/[[DRAFT-S-…]]links are index entries — a topic link or hand-added note link in the editable body is never treated as a session reference. A missing index never reports (fresh vaults have none), and an empty index is legal even when sessions exist (staleness-by-omission is deferred until the index format is finalized per the issue). An unreadable index (locked mid-scan) is reported byread-failure, not here.Hot Memory (
valid-hot-memory, warning): The derived.palee/hot.md— the note that orientssession startwhen the learner resumes — must carry the writer's identity (memory_id: H-active), its body must stay within the 250-word cap (MAX_HOT_WORDS, counted with the same whitespace-delimitedcountWordsthe writer's truncation uses, frontmatter excluded), and itslast_session/active_topicreferences must point at existing session/topic notes. A missing hot memory never reports (fresh vault), an unparsable one is rebuilt rather than diagnosed field-by-field, and reference findings are suppressed on a read-incomplete snapshot (the referenced note may be exactly the one that failed to read). Findings never become errors and never gate the exit code by default — hot memory is a rebuildable projection (ADR-0008 decision 4);--strictescalates them like every other warning (decision 3), and a rebuild restores correctness.Safe Vault Paths (
safe-vault-paths, error): Every PALEE-managed path in the collected snapshot — topic notes, scanned notes, and session notes — must resolve inside the configured vault. Paths normalize (\→/) before validation so Windows separators validate identically; parent-directory traversal (../outside.md, including mid-patha/../../escape.md), absolute POSIX paths, and absolute Windows drive paths (C:/…) are boundary escapes and report as errors. This mirrors the containment policy the vault walker and roadmap import already enforce, making the write boundary explicit in validation output; symlink resolution itself stays owned by the walker.
Example Human-Readable Output (Failures Detected)
$ palee validate
Validating vault: /Users/dev/ObsidianVault
Found 18 PALEE topics in 24 files
✗ Found 1 validation error(s):
• Dependency cycle detected: T-topic-a -> T-topic-b -> T-topic-a
Rule: no-dependency-cycle
⚠ Found 1 validation warning(s):
• Topic T-cloud-native depends on missing topic T-docker-missing
Rule: no-missing-dependencyAssessment-Review Independence (enforced by regression tests, #40)
Assessment fields (conceptual, practical, debug, feynman, assessed_at, topic_mastery) and SM-2 review fields (last_quality, last_reviewed_at, due_at, ease_factor, interval_days, repetition, lapses) are independent state. Per the #40 design, independence is not a static vault rule — command-level mutation tests are the enforcement mechanism, because independence is about what commands write, not what the vault looks like. test/e2e/assessment-review-independence.test.ts pins the contract in both directions: palee review updates only SM-2 fields and preserves assessment data (including non-zero topic_mastery) byte-for-byte; palee roadmap import — the curriculum write path — preserves all seven SM-2 review fields byte-for-byte on reviewed topics, so an assessment-path mutation never clobbers review state unless an explicit confirmed review mutation is added.
4. Machine-Readable Output & Non-TTY Detection
All reporting commands support the PALEE automated JSON streaming contract (isJsonOutput()):
# Direct JSON piping to jq for CI/CD checks
$ palee validate | jq .valid
true
# Extract total reviews due from dashboard
$ palee dashboard | jq .reviews_due
3Structured Error Handling
When an error occurs (such as an unconfigured vault or a missing topic query in --topic), PALEE emits a structured error JSON object on stderr and exits with code 2:
{"error": "Topic not found: NonExistentTopic"}5. Exit Codes for Reporting Commands
| Command | Exit Code 0 | Exit Code 1 | Exit Code 2 | Exit Code 3 | Exit Code 4 | Exit Code 5 |
|---|---|---|---|---|---|---|
palee dashboard | Successfully displayed dashboard metrics or empty vault onboarding. | N/A | Vault path not configured or directory does not exist. | N/A | N/A | Unexpected runtime exception or calculation failure. |
palee progress | Successfully displayed vault progress summary, topic detail (--topic), or empty vault state. | N/A | Vault path unconfigured, or topic query not found for --topic. | N/A | N/A | Unexpected runtime exception or file read failure. |
palee validate | Vault validation passed with 0 structural errors. | N/A | Vault path not configured or invalid directory. | Any validation error (malformed schema, topic ID, status, duplicate palee_id, missing dependency, cycle, or assessment-field shape); warnings also exit 3 under --strict. | N/A | Unexpected runtime exception or directory walk failure. |
