Skip to content

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.

Dashboard Syntax & Options ​

bash
palee dashboard [flags]
FlagTypeDefaultDescriptionExample
--jsonbooleanfalseOutput dashboard metrics as structured JSON (PALEE_JSON=1 also works).palee dashboard --json

Data Aggregation & Metric Calculations ​

dashboardCommand scans all topic files in the vault and classifies notes using the 4-pillar mastery threshold (M=0.70) src/cli/dashboard.ts:

  • 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 (Due for Review, reviews_due): Topics where due_at is in the past or equal to the current system time. This is a review-backlog metric: never-reviewed notes (due_at: null) are deliberately excluded because they are already counted under New, so a freshly adopted vault reports Due for Review: 0 while palee plan and palee next list those same notes as actionable (Due: Never reviewed). That divergence is intentional, not a mismatch.
  • Next Review (next_review): The review palee next would hand over right now — the first entry of that command's prerequisite-gated actionable set (never-reviewed notes included, ordered never-reviewed first then oldest due_at), with archived notes hidden. It is computed by the same getNextTopics() use-case palee next uses, so the dashboard can never print next_review: null in the same breath as Run "palee next" to start reviewing (#307), and it can never name a topic palee plan reports as blocked behind an unmet prerequisite (#305). It is not the same figure as reviews_due: on a fresh vault reviews_due is 0 while next_review names a note.
  • Difficulty Tiers: Aggregated counts and mastered subtotals across beginner, intermediate, and advanced tiers.

Example Human-Readable Output ​

bash
$ 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 plan

2. Progress Command (palee progress) ​

The palee progress command offers granular analytics into learning retention, current repetition totals, lapse counts, and topic-specific mastery scores.

Progress Syntax & Options ​

bash
palee progress [flags]
FlagTypeDefaultDescriptionExample
--topic <query>stringundefinedInspect a specific topic by ID (e.g. T-20260814T120000-abcd), a unique title substring, or the exact vault path shown by next/plan.palee progress --topic "Recursion"
--jsonbooleanfalseOutput progress metrics as structured JSON (PALEE_JSON=1 also works).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:

  • Archived Topic Exclusion: Notes with status: archived in frontmatter are tracked separately and excluded from global_mastery and active_topic_count.

  • Global Average Mastery: Calculates the true mathematical mean of mastery across all active topics:

    text
    global_mastery = sum(active_topics.mastery) / active_topic_count
  • Mastery Status: Classifies vault overall state as 'no_data' (active_count === 0), 'learning' (< 0.70), or 'mastered' (>= 0.70).

  • Total Repetitions & Lapses: Sums the current repetition value across all active topics (a failed review resets repetition to 0, so this is not a lifetime count of review events) together with memory lapses.

2. Topic-Specific Mode (--topic <id>) ​

Surfaces comprehensive SRS metadata for an individual topic note:

  • topic_mastery percentage
  • difficulty tier
  • repetition count and lapses count
  • assessed_at and last_reviewed_at timestamps

Example Outputs ​

Vault-Wide Human-Readable Summary ​

bash
$ 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 Repetitions: 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 ​

bash
$ 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-25

3. 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.

Validate Syntax & Options ​

bash
palee validate [flags]
FlagTypeDefaultDescriptionExample
--fixbooleanfalseApplies the three shipped repairs: rebuild .palee/hot.md and .palee/index.md from canonical sessions (valid-session-index, valid-hot-memory); recompute topic_mastery for topics with all four pillars present (valid-topic-mastery, ADR-0035); and reset invalid SM-2 fields to adopt defaults (valid-review-fields). Reports repairs through additive repairs/repair_conflicts keys in --json, then re-validates before choosing the exit code.palee validate --fix
--jsonbooleanfalseOutput validation diagnostics as structured JSON (PALEE_JSON=1 also works).palee validate --json
--strictbooleanfalseEscalate 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

--fix writes to your vault

palee validate --fix can rewrite topic frontmatter and regenerate .palee/hot.md and .palee/index.md. Run a read-only validation first and review the reported repairs before applying them.


Vault Structural Integrity Rules ​

palee validate runs a nineteen-rule validation framework — the twenty #25 backlog items (issues #26–#45) reconcile to nineteen registered rules: valid-difficulty (#32) was dissolved by the type-level difficulty fix (no rule and no external enforcement — malformed stored difficulty values are coerced by the loader and go unreported), assessment-review-independence (#40) is the only item enforced outside the rule set (command-level mutation tests), while the framework item contributed the two snapshot rules (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. It also checks the depends_on_source label against the domain the engine reads (numbered, toc, declared, tie): an unrecognized value fails closed to gating, so a typo kept a note gated forever while the report said the vault was valid. 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.

  1. 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.

  2. 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.

  3. Schema Version (valid-palee-schema, error): Every PALEE-managed note must declare palee_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.

  4. Topic ID Format (valid-topic-id-format, error): Topic IDs must match the centralized policy in src/engine/topic-id.ts — T- plus lowercase kebab-case segments; the exact legacy adopt-generated format stays valid.

  5. Topic Status (valid-topic-status, error): Status must be one of not_started | learning | paused | archived; missing status is tolerated as the adopt default.

  6. Duplicate Topic IDs (no-duplicate-topic-id, error): Multiple Markdown notes sharing the same palee_id in their frontmatter.

  7. Dependency List Shape (valid-dependency-list, error): depends_on must 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-a depending on T-a) is a structural error. Duplicate entries are a separate warning: the loader dedupes them, so scheduling is unaffected. Missing or null depends_on is the documented empty-list default and never reports. Additionally, if a topic carries the legacy dependencies alias, an advisory warning is emitted guiding the user to migrate to depends_on, and the raw legacy shape is validated with the same contract. The rule runs before the graph rules and reads raw frontmatter (pre-normalization) so defects the loader's coercion would hide are exposed. It checks the depends_on_source label against the same raw read: a value that is present but outside numbered, toc, declared, tie is an error, because normalizeDependsOnSource fails closed to undefined — which means "learner-authored, so gate" — so a mistyped label holds a note behind a gate no other report mentions. An absent label is the pre-existing state and passes.

  8. Missing Dependencies (no-missing-dependency, warning): A topic referencing a prerequisite ID in depends_on that does not exist anywhere in the vault. A warning in vault scans (the engine quarantines the dependent topic from plan/next instead of failing the scan — incrementally-written vaults are the norm, and a note mid-flight must not fail a whole validation run); roadmap --from pre-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, the read-failure warning appears alongside explaining the transient condition, and re-running settles it. --strict escalates the warning for CI use.

  9. Dependency Cycles (no-dependency-cycle, error): Circular dependency chains (e.g. A→B→C→A) 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).

  10. 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, and assessed_at must be null or 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-30 is 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.

  11. Topic Mastery Drift (valid-topic-mastery, warning): When a topic's assessment fields are shape-valid, stored topic_mastery must equal the engine formula round((conceptual + practical + debug + 2*feynman) / 5, 4) — recomputed with computeTopicMastery from src/engine/mastery.ts. Drift reports a warning with details.actual (stored) and details.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 --strict gates it.

  12. Review Fields (valid-review-fields, error): SM-2 review state must match the engine contract as stored on disk: ease_factor a finite number >= 1.3 (the SM-2 floor), interval_days an integer >= 1, repetition/lapses integers >= 0, and last_quality null or an integer 0-5. The rule reads raw frontmatter (the loader's parseNumber/parseInteger coercion would silently accept stringified or fractional values), so values no PALEE writer could produce — stringified numbers, null numeric state, fractional counters — are exposed as errors. Missing keys are the adopt-default state and pass.

  13. Review Dates (valid-review-dates, error): last_reviewed_at and due_at must be null (newly adopted) or strict zero-padded date-only YYYY-MM-DD strings naming real calendar dates — the exact shape formatLocalDateOnly persists. Full ISO timestamps fail (date-only is the product contract: review scheduling compares local calendar days), impossible calendars fail (2026-02-31 is not normalized into March), and when both fields are valid, due_at earlier than last_reviewed_at reports the inversion. Validation is pure component analysis shared with the assessed_at policy — no timezone-dependent parsing.

  14. Managed Note Kind (valid-managed-note-kind, warning): A note declaring palee_schema asserts PALEE owns part of its frontmatter — it must carry exactly one recognizable identity: palee_id (topic), session_id (session), memory_id (hot memory), or type: 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 carrying palee_id, or the index also carrying session_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).

  15. Session Schema (valid-session-schema, error): Canonical session notes under .palee/sessions/ — the durable learning history that hot.md and index.md rebuild from — must carry the full schema: required fields (session_id, topic_id, started_at, ended_at), status of completed or draft coherent with the S-/DRAFT-S- filename convention, session_id matching the filename stem (the rebuild paths key on it), ISO 8601 timestamps carrying an explicit timezone designator (Z or ±HH:MM — offset forms pass; date-only and timezone-less strings fail) with ended_at not preceding started_at, ended_at: null for drafts and a timestamp for completed sessions. Every session note must also declare a supported palee_schema version — the canonical writers emit palee_schema: 1 and the rebuild paths default missing versions silently, so validation is where a foreign or missing version on canonical data surfaces (the same policy as valid-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 the read-failure rule's finding, never a schema error. Reads raw frontmatter — the rebuild paths cast with as string, so shape drift is silent at runtime and validation is the only place it surfaces.

  16. Session Unknown Topic (no-session-unknown-topic, warning): A session's topic_id must reference a topic that exists in the vault — a session pointing at a missing topic cannot be connected back to the learning graph. The historical T-general phantom sessions (from the pre-hot-memory session end fallback) 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 failed valid-session-schema are skipped (no double-reporting). A warning while existing vaults may still contain legacy T-general sessions; --strict escalates.

  17. Session Index (valid-session-index, warning): The derived .palee/index.md must 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; --strict escalates 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 by read-failure, not here.

  18. Hot Memory (valid-hot-memory, warning): The derived .palee/hot.md — the note that orients session start when 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-delimited countWords the writer's truncation uses, frontmatter excluded), and its last_session/active_topic references 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); --strict escalates them like every other warning (decision 3), and a rebuild restores correctness.

  19. 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-path a/../../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) ​

bash
$ 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-dependency

Assessment-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 ​

All reporting commands support explicit JSON selection (isJsonOutput()): pass --json or set PALEE_JSON=1. Piping alone does not switch formats — a redirected report stays human-readable (#357).

bash
# JSON output for CI/CD checks: opt in explicitly, then pipe to jq
$ palee validate --json | jq .valid
true

# Under --strict, valid stays errors-only; read the explicit gate field
$ palee validate --strict --json | jq '{valid, strict, gated_by_warnings, warning_count}'
{"valid":true,"strict":true,"gated_by_warnings":true,"warning_count":1}

# Extract total reviews due from dashboard
$ PALEE_JSON=1 palee dashboard | jq .reviews_due
3

Structured 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:

json
{"error": "Topic not found: NonExistentTopic"}

5. Exit Codes for Reporting Commands ​

Only exits a command can produce are listed; codes it never returns are omitted.

CommandExitBehavior
palee dashboard0Successfully displayed dashboard metrics or empty vault onboarding.
palee dashboard2Vault path not configured or directory does not exist.
palee dashboard5Unexpected runtime exception or calculation failure.
palee progress0Successfully displayed vault progress summary, topic detail (--topic), or empty vault state.
palee progress2Vault path unconfigured, or topic query not found for --topic.
palee progress5Unexpected runtime exception or file read failure.
palee validate0Vault validation passed with 0 structural errors (warnings exit 0 by default).
palee validate2Vault path not configured or invalid directory.
palee validate3Any validation error (malformed schema, topic ID, status, duplicate palee_id, cycle, or assessment-field shape); warnings (such as missing dependencies) exit 0 by default and only exit 3 under --strict.
palee validate5Unexpected runtime exception or directory walk failure.

Released under the MIT License.