Obsidian ObservabilitySAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-09Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: obsidian-observability description: 'Implement structured logging, metrics, error tracking, and a debug panel for Obsidian plugins. Use when adding debug logging, tracking plugin performance, building a diagnostics view, or setting up error reporting. Trigger with phrases like "obsidian logging", "obsidian monitoring", "obsidian debug panel", "track obsidian plugin performance". ' allowed-tools: Read, Write, Edit, Bash(npm:*) version: 1.13.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - obsidian - monitoring - debugging - performance - logging compatibility: Designed for Claude Code --- # Obsidian Observability ## Overview Implement production observability for Obsidian plugins: a structured logger with levels and ring buffer history, a metrics collector with counters/gauges/timers, an error tracker with deduplication, and a debug sidebar panel that displays all of it in real time. Every component is copy-pasteable and uses only Obsidian's built-in APIs. ## Prerequisites - Working Obsidian plugin (see `obsidian-core-workflow-a`) - TypeScript strict mode enabled - Familiarity with `ItemView` for the debug panel ## Instructions ### Step 1: Structured Logger with Levels and History ```typescript // src/services/logger.ts type LogLevel = 'debug' | 'info' | 'warn' | 'error'; const LEVEL_PRIORITY: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3, }; interface LogEntry { timestamp: number; level: LogLevel; message: string; data?: unknown; } export class Logger { private history: LogEntry[] = []; private maxHistory = 200; private level: LogLevel; private prefix: string; constructor(pluginId: string, level: LogLevel = 'info') { this.prefix = `[${pluginId}]`; this.level = level; } setLevel(level: LogLevel) { this.level = level; } debug(msg: string, data?: unknown) { this.log('debug', msg, data); } info(msg: string, data?: unknown) { this.log('info', msg, data); } warn(msg: string, data?: unknown) { this.log('warn', msg, data); } error(msg: string, data?: unknown) { this.log('error', msg, data); } /** Start a timer, returns a function that stops it and logs duration */ time(label: string): () => number { const start = performance.now(); return () => { const ms = performance.now() - start; this.debug(`${label} (${ms.toFixed(2)}ms)`); return ms; }; } /** Get last N log entries */ getHistory(count?: number): LogEntry[] { return count ? this.history.slice(-count) : [...this.history]; } /** Export history as JSON string */ export(): string { return JSON.stringify(this.history, null, 2); } private log(level: LogLevel, message: string, data?: unknown) { if (LEVEL_PRIORITY[level] < LEVEL_PRIORITY[this.level]) return; const entry: LogEntry = { timestamp: Date.now(), level, message, data }; this.history.push(entry); if (this.history.length > this.maxHistory) { this.history.splice(0, this.history.length - this.maxHistory); } const fn = level === 'debug' ? console.debug : level === 'warn' ? console.warn : level === 'error' ? console.error : console.log; if (data !== undefined) { fn(this.prefix, message, data); } else { fn(this.prefix, message); } } } ``` ### Step 2: Metrics Collector with Counters, Gauges, and Timers ```typescript // src/services/metrics.ts interface TimerStats { count: number; total: number; min: number; max: number; values: number[]; // last 100 values for percentile calculation } export class MetricsCollector { private counters = new Map<string, number>(); private gauges = new Map<string, number>(); private timers = new Map<string, TimerStats>(); // Counters — monotonically increasing increment(name: string, amount = 1) { this.counters.set(name, (this.counters.get(name) ?? 0) + amount); } getCounter(name: string): number { return this.counters.get(name) ?? 0; } // Gauges — point-in-time values setGauge(name: string, value: number) { this.gauges.set(name, value); } getGauge(name: string): number { return this.gauges.get(name) ?? 0; } // Timers — track duration distributions recordTime(name: string, ms: number) { let stats = this.timers.get(name); if (!stats) { stats = { count: 0, total: 0, min: Infinity, max: 0, values: [] }; this.timers.set(name, stats); } stats.count++; stats.total += ms; stats.min = Math.min(stats.min, ms); stats.max = Math.max(stats.max, ms); stats.values.push(ms); if (stats.values.length > 100) stats.values.shift(); } /** Wrap an async function with automatic timing */ async timeAsync<T>(name: string, fn: () => Promise<T>): Promise<T> { const start = performance.now(); try { return await fn(); } finally { this.recordTime(name, performance.now() - start); } } getTimerStats(name: string): { avg: number; p95: number; min: number; max: number; count: number } | null { const stats = this.timers.get(name); if (!stats || stats.count === 0) return null; const sorted = [...stats.values].sort((a, b) => a - b); const p95Index = Math.floor(sorted.length * 0.95); return { avg: stats.total / stats.count, p95: sorted[p95Index] ?? sorted[sorted.length - 1], min: stats.min, max: stats.max, count: stats.count, }; } /** Get all metrics as a plain object for serialization */ snapshot(): Record<string, unknown> { const result: Record<string, unknown> = {}; for (const [k, v] of this.counters) result[`counter.${k}`] = v; for (const [k, v] of this.gauges) result[`gauge.${k}`] = v; for (const [k] of this.timers) { const s = this.getTimerStats(k); if (s) result[`timer.${k}`] = s; } return result; } } ``` ### Step 3: Error Tracker with Deduplication ```typescript // src
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__obsidian-observability.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-09 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Obsidian Observability skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Obsidian Observability safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Obsidian Observability access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Obsidian Observability work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-09. The repository is watched, and a new audit runs when it changes — this is the first audit.