feat: comprehensive observability layer (diagnostics + metrics)
I maintainer di solito rispondono entro 1 giorno
@Kuldeep2822k ci sta già lavorando.
Dal 5/11/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Task: Comprehensive Observability Layer for PALEE CLI
Current State Analysis
PALEE currently has zero observability infrastructure:
- No timing metrics anywhere in production code
- No structured logging framework
- No performance profiling or diagnostics
- No historical records of command duration
- No ability to reproduce performance regressions
This gap makes it impossible to answer basic questions like:
- Why did palee adopt take 15 seconds on my 500-note vault?
- Was the recent refactor faster or slower than before?
- How many times was each phase of the scan called?
- Where does time get spent during session start?
Architecture Context
graph TD
A[User invokes palee] --> B{command?}
B -->|adopt| C[src/cli/adopt.ts]
B -->|session start| D[src/cli/session-start.ts]
B -->|validate| E[src/cli/validate.ts]
C --> F[vault scan via src/storage/vault-walker.ts]
D --> G[load hot.md .palee/hot.md]
E --> H[parse frontmatter src/storage/frontmatter.ts]
F --> I[src/engine/sm2.ts SM-2 compute]
G --> I
H --> I
I --> J[atomic writes src/storage/atomic-write.ts]
J --> K[console.log output]
style A fill:#8ba7d4,stroke:#222
style K fill:#8bd4b2,stroke:#222
Current architecture shows single-path execution without any measurement hooks. Every layer executes directly to stdout, with no parallel tracking of durations, counts, or failures.
Proposed Solution Architecture
Multi-Layer Design
flowchart LR
subgraph Commands["Application Commands"]
A[adopt.ts]
B[sessions/start.ts]
C[validate.ts]
end
subgraph Observability["Observability Layer"]
D[diagnostics.ts]
E[recordEvent]
F[writeSummary]
end
subgraph Storage["Storage Backends"]
G[JSONL events.jsonl]
H[metrics/session-id.json]
end
A --> D
B --> D
C --> D
D --> E
D --> F
E --> G
F --> H
style D fill:#9c8ac9,stroke:#222,stroke-width:2px
style E fill:#8ab8d4,stroke:#222,stroke-width:1px
style G fill:#d48a8a,stroke:#222,stroke-width:1px
Key design principles:
- Zero runtime dependencies: Uses Node.js built-in fs, performance.now()
- Opt-in activation: Set PALEE_DIAGNOSTICS=1 environment variable
- Non-blocking writes: Logging failures never corrupt main execution
- Privacy-first: Sanitize apiKey, store under .palee/ directory only
- Backward compatible: Existing behavior unchanged when disabled
Layer 1: Structured JSONL Event Streaming
Log Format Example
Each event is a single-line JSON object written atomically:
{"timestamp":"2026-11-04T22:45:00.123Z","session_id":"S-20261104T224500-a1b2c3d4","event_type":"vault_scan_start","payload":{"vault_path":"/home/user/study-notes","scope":"all"}}
{"timestamp":"2026-11-04T22:45:00.578Z","session_id":"S-20261104T224500-a1b2c3d4","event_type":"frontmatter_parse","payload":{"topics_parsed":128,"duration_ms":455,"warnings_oversized":2}}
{"timestamp":"2026-11-04T22:45:01.102Z","session_id":"S-20261104T224500-a1b2c3d4","event_type":"sm2_compute_end","payload":{"topics_processed":128,"duration_ms":524}}
{"timestamp":"2026-11-04T22:45:03.089Z","session_id":"S-20261104T224500-a1b2c3d4","event_type":"adopt_end","payload":{"duration_ms":2966,"adopted_count":47,"rejected_paths":[".git","node_modules"],"conflicts_occurred":0}}
Event Types to Instrument
| Phase | Event Type | Payload Fields |
|---|---|---|
| Vault Scan | vault_scan_start | vault_path, scope, estimated_file_count |
| Vault Scan | file_scanned | file_path, size_bytes, is_markdown |
| Frontmatter | frontmatter_parse | topics_parsed, duration_ms, failed_count |
| Frontmatter | validation_error | file_path, error_type, message |
| Adoption | adopt_start | target_path, dry_run |
| Adoption | adopt_success | file_path, frontmatter_keys_written, duration_ms |
| Adoption | adopt_conflict | file_path, expected_fingerprint, actual_fingerprint |
| Validation | validate_start | vault_path, strict_mode |
| Validation | validate_result | status, total_topics, errors_count, warnings_count |
| Session | session_start | session_id, hot_memory_loaded, topic_count |
| Session | session_end | session_id, duration_ms, topics_updated |
| Error | failure_event | command, exit_code, error_type, stack_sanitized |
Layer 2: Per-Run Metrics Summary
Compact JSON Summary at End of Run
Written automatically when diagnostic mode is enabled:
{
"schema_version": 1,
"session_id": "S-20261104T224500-a1b2c3d4",
"command": "adopt",
"start_iso": "2026-11-04T22:45:00.123Z",
"end_iso": "2026-11-04T22:45:03.089Z",
"total_duration_ms": 2966,
"exit_code": 0,
"phase_breakdown": {
"vault_scan_time_ms": 455,
"frontmatter_parse_time_ms": 524,
"sm2_compute_time_ms": 892,
"atomic_write_time_ms": 1095,
"other_overhead_ms": 0
},
"counts": {
"topics_scanned": 128,
"topics_adapted": 47,
"topics_rejected": 1,
"skipped_dot_directories": 3,
"oversized_notes_skipped": 2,
"occ_conflicts_detected": 0
},
"warnings": [
{"type": "oversized_note", "path": "big-topic.md", "size_bytes": 524288},
{"type": "symlink_skipped", "path": "link-to-outside"}
],
"memory_footprint_estimate_mb": 12.4,
"config_used": {
"vault_path": "/home/user/study-notes",
"provider_configured": true,
"json_output_enabled": false
}
}
Per-Phase Timing Breakdown
For debugging slow runs, break down where milliseconds get consumed:
| Command | Critical Phases to Time |
|---|---|
| palee adopt | vault scanning → frontmatter parsing → dependency graph build → atomic writes |
| palee session start | hot.md reload → session history load → SM-2 queries → tool call count |
| palee validate | scanner loop → per-rule evaluation → cycle detection → report generation |
| palee plan | readiness gate → prerequisite resolution → mastery threshold filter |
| palee roadmap --from | YAML parsing → link resolution → cycle detection → write validation |
Comparison with Other CLI Tools
How Other CLI Tools Handle Observability
1. ESLint (JavaScript Linter)
- Approach: --debug flag outputs detailed timing for each rule
- Example output:
Processing...
Rule 'no-unused-vars': 0.023s
Rule 'prefer-const': 0.012s
Time taken: 2.34s
- Link: https://eslint.org/docs/latest/use/command-line-interface#show-time
- Relevance: Simple opt-in timing, zero deps, matches our approach
2. Rust's cargo Build Tool
- Approach: -v verbose + --timings flag generates HTML report
- Example: Generates target/cargo-timings/timings.html with call graphs
- Link: https://doc.rust-lang.org/cargo/reference/unstable.html#timings
- Relevance: Shows how to make observability actionable (visual reports)
3. kubectl Kubernetes CLI
- Approach: KUBECONFIG_DEBUG=1 logs HTTP request/response timestamps
- Example:
$ KUBECONFIG_DEBUG=1 kubectl get pods
GET /api/v1/namespaces/default/pods - 23ms
GET /api/v1/namespaces/kube-system/pods - 15ms
- Link: https://kubernetes.io/docs/tasks/debug-application-cluster/debug-kubectl/#debugging-beginner
- Relevance: Network-aware timing for distributed systems context
4. Go's cobra CLI Framework
- Approach: Built-in benchmark mode with --bench flag
- Usage: ./myapp run --bench 100 measures average over 100 runs
- Link: https://pkg.go.dev/github.com/spf13/cobra#section-readme-benchmarks
- Relevance: Statistical aggregation for regression detection
5. Git's Verbose Mode
- Approach: -vvv flag shows low-level filesystem operations
- Example:
git -vvv checkout branch
D:\cli-build\git-check-cache.c:23: read(0x7ff7...) = 4096
D:\cli-build\git-check-cache.c:25: lstat(".git/index") = 0
- Link: https://git-scm.com/docs/git#_description_verbose-output
- Relevance: Deep tracing for troubleshooting complex scenarios
What We're NOT Doing
No OpenTelemetry instrumentation — too heavy for CLI, no central backend exists
No remote telemetry — violates INV-29 privacy boundary
No real-time dashboards — CLI runs once-and-exits, doesn't maintain state
These trade-offs align with the privacy-first and offline-first design philosophy.
User-Priority Scenarios
Scenario 1: "Why is session start slow after lunch?"
Without observability:
- User reports "slow", maintainer asks "how slow?"
- User responds "2 minutes"
- Maintainer cannot prove whether hot.md rebuild vs. tool call vs. SM-2 compute caused delay
With observability:
{
"session_start": {
"hot_reload_time_ms": 12,
"session_history_load_time_ms": 89,
"sm2_queries_time_ms": 34,
"tool_calls_count": 7,
"total_latency_ms": 1247,
"dominant_bottleneck": "tool_calls"
}
}
Conclusion: 1.2s spent in tool calls; user can disable provider temporarily to confirm.
Scenario 2: Regression Detection After Refactor
Before PR lands:
{
"command": "adopt",
"avg_duration_ms": 2450,
"stdev_ms": 123,
"n_runs": 50
}
After PR lands:
{
"command": "adopt",
"avg_duration_ms": 3890,
"stdev_ms": 412,
"n_runs": 50
}
Diff script (palee metrics diff baseline.json post-pr.json) shows:
SLOWDOWN DETECTED: +58% (1440ms)
├─ vault_scan: -2ms (unchanged)
├─ frontmatter_parse: +523ms (+21%)
└─ sm2_compute: +919ms (+67%) ← PRIMARY culprit
Outcome: CI blocks merge until performance regression explained/fixed.
Implementation Plan
Phase 1: Foundation (~30 min)
Create src/cli/diagnostics.ts with three core functions:
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
interface DiagnosticEvent {
timestamp: string;
session_id: string;
event_type: string;
payload: Record<string, unknown>;
}
export function recordEvent(eventType: string, payload: Record<string, unknown>): void {
const sessionId = getSessionId();
const event: DiagnosticEvent = {
timestamp: new Date().toISOString(),
session_id: sessionId,
event_type: eventType,
payload: sanitizeSensitiveFields(payload),
};
const logPath = getLogPath();
if (!existsSync(logPath)) {
mkdirSync(join(logPath, '..'), { recursive: true });
}
appendFileSync(logPath, JSON.stringify(event) + '\n', 'utf8');
}
export async function writeSummary(command: string, metrics: Record<string, unknown>): Promise<void> {
const sessionId = getSessionId();
const summary = {
schema_version: 1,
session_id: sessionId,
command,
start_iso: /* captured earlier */,
end_iso: new Date().toISOString(),
total_duration_ms: /* measure at start */ ,
...metrics
};
const summaryPath = join(getMetricsPath(), `session-${sessionId}.json`);
writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8');
}
function sanitizeSensitiveFields(obj: Record<string, unknown>): Record<string, unknown> {
const sanitized = { ...obj };
delete sanitized.apiKey;
delete sanitized.api_key;
delete sanitized.secret;
return sanitized;
}
Phase 2: Command Instrumentation (~45 min)
Wrap key phases in adopt.ts:
import { recordEvent, writeSummary } from './diagnostics';
async function adoptionEntry(...): Promise<void> {
const startTime = performance.now();
const sessionId = generateSessionId();
try {
recordEvent('adopt_start', { scope: options.all ? 'all' : 'single', target_path });
const scanStart = performance.now();
const notes = walkVault(vaultDir);
const scanDuration = performance.now() - scanStart;
recordEvent('vault_scan_complete', {
topics_found: notes.length,
duration_ms: scanDuration
});
const parseStart = performance.now();
const parsed = notes.map(parseFrontmatter);
const parseDuration = performance.now() - parseStart;
recordEvent('frontmatter_parse_complete', {
topics_parsed: parsed.length,
duration_ms: parseDuration
});
recordEvent('adopt_end', {
adopted_count,
rejected_paths,
duration_ms: performance.now() - startTime
});
} catch (e: unknown) {
recordEvent('failure', { exitCode, errorType: e instanceof Error ? e.name : 'Unknown' });
throw e;
} finally {
await writeSummary('adopt', {
vault_scan_time_ms: scanDuration,
frontmatter_parse_time_ms: parseDuration,
// ... other metrics
});
}
}
Repeat pattern for session/start.ts and validate.ts.
Phase 3: Test Matrix (~60 min)
New test file: test/cli-diagnostics.test.ts
import { describe, test, expect, beforeEach, afterEach } from 'node:test';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { recordEvent, writeSummary, getLogPath } from '../src/cli/diagnostics';
describe('CLI Diagnostics Module', () => {
let tempDir: string;
beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), 'palee-diag-test-'));
process.env.PALEE_LOG_DIR = tempDir;
process.env.PALEE_DIAGNOSTICS = '1';
});
afterEach(() => {
rmSync(tempDir, { recursive: true, force: true });
delete process.env.PALEE_LOG_DIR;
delete process.env.PALEE_DIAGNOSTICS;
});
test('records events to JSONL file', () => {
recordEvent('test_event', { field: 'value' });
const lines = fs.readFileSync(getLogPath(), 'utf8').trim().split('\n');
expect(lines).toHaveLength(1);
const event = JSON.parse(lines[0]);
expect(event.event_type).toBe('test_event');
expect(event.payload.field).toBe('value');
});
test('sanitizes apiKey from payloads', () => {
recordEvent('secret_log', { apiKey: 'should-be-hidden', visible: 'kept' });
const lines = fs.readFileSync(getLogPath(), 'utf8').trim().split('\n');
const event = JSON.parse(lines[0]);
expect(event.payload).not.toHaveProperty('apiKey');
expect(event.payload.visible).toBe('kept');
});
test('writes summary JSON at command end', async () => {
await writeSummary('adopt', { duration_ms: 1234, topics_adapted: 5 });
const summaryPath = join(getMetricsPath(), 'session-S-*.json');
expect(existsSync(summaryPath)).toBe(true);
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
expect(summary.command).toBe('adopt');
expect(summary.duration_ms).toBe(1234);
});
});
Update existing tests to verify timing capture:
- test/cli-adopt-batch.test.ts: Assert vault_scan_time_ms logged
- test/cli-session-start.test.ts: Assert tool_call_count included
- test/storage-scanner.test.ts: Assert files_scanned counted
- test/validation-report.test.ts: Assert violations_by_type grouped
- test/cli-exit-codes.test.ts: Assert failure events have exit_code field
Phase 4: Documentation & ADR (~30 min)
Create ADR document: docs/adr/00XX-observability-layer.md
# ADR-00XX: Local Observability Layer for PALEE CLI
## Status
Accepted (2026-11-04)
## Context
PALEE lacks timing metrics and structured diagnostics. Users reporting "slow performance" have no way to collect runtime data without manual console.log intervention.
## Decision
Implement lightweight local observability with these constraints:
1. Zero third-party dependencies (uses Node.js built-in fs, performance.now())
2. Opt-in activation via PALEE_DIAGNOSTICS=1 environment variable
3. All data stored under .palee/ directory; no remote transmission
4. Non-blocking writes: logging failures never disrupt main execution flow
5. Privacy-preserving: apiKey and secrets filtered before any storage
6. Follows invariant INV-29 (network boundary) by avoiding network IO
## Data Formats
### JSONL Event Stream (.palee/logs/events.jsonl)
Append-only line-delimited JSON objects with fields: timestamp, session_id, event_type, payload.
### Per-Run Metrics (.palee/metrics/session-{id}.json)
Single JSON object per command invocation with fields: command, start_iso, end_iso, phase_breakdown, counts, warnings.
## Alternatives Considered
### Alternative 1: OpenTelemetry SDK
Pros: Standard format, portable exports
Cons: Adds ~150KB bundle, requires learning curve, introduces dependency chain
Verdict: Deferred to future phases when centralized dashboard requirement emerges.
### Alternative 2: diagnostics_channel Module
Pros: Built into Node.js v22+, zero deps
Cons: Experimental channel shapes, less developer control over export destinations
Verdict: Consider as follow-up optimization if OTel needed later.
### Alternative 3: Third-Party Logger (winston, pino)
Pros: Battle-tested, multi-backend support
Cons: Runtime bloat, unnecessary complexity for CLI use case
Verdict: Reject — adds deps we don't need.
## Consequences
### Positive
- Debugging "slow adopt" becomes tractable (identify which phase dominates)
- Performance regressions detected pre-merge via CI baseline comparison
- User support improved with objective diagnostics data
- Test coverage expanded to include timing assertions
### Negative
- Minor file I/O overhead (~1-2ms per command)
- Extra disk space for .palee/logs/ and .palee/metrics/ directories (~5KB/day typical)
- Added maintenance burden for observers team
## References
- planning/palee_cli_spec.md - Section 6 (CLI Layer specs)
- planning/invariants.md - INV-29 (Network Boundary), INV-30 (JSON contract)
- src/cli/onboarding.ts - Line 68 (emitError function signature)
- ESLint timing docs: https://eslint.org/docs/latest/use/command-line-interface#show-time
- Cargo timings RFC: https://doc.rust-lang.org/cargo/reference/unstable.html#timings
Update README.md section:
Add new subsection under "Configuration & Environment Variables":
## Debugging & Diagnostics
Enable optional observability logging by setting:
```bash
export PALEE_DIAGNOSTICS=1
This creates two additional directories under .palee/:
- .palee/logs/events.jsonl - Append-only stream of events (structured JSONL)
- .palee/metrics/session-{id}.json - Compact summary per command run
Note: All observability data stays local. No network transmission occurs. Secrets like apiKey are automatically filtered before writing.
To view recent diagnostic events:
tail -n 50 ~/.palee/logs/events.jsonl
Or inspect latest session metrics:
cat ~/.palee/metrics/session-*.json | tail -20
Environment Variables
| Variable | Purpose | Default |
|---|---|---|
| PALEE_CONFIG_DIR | Override config directory path | ~/.palee |
| PALEE_JSON | Enable machine-readable output | false |
| PALEE_DIAGNOSTICS | Enable observability logging | false |
| PALEE_LOG_DIR | Override diagnostics log path | ~/.palee/logs |
| PALEE_DEBUG | Include stack traces on failure | false |
Acceptance Criteria Checklist
Phase 1: Core Implementation
- src/cli/diagnostics.ts created with recordEvent() and writeSummary()
- Session ID generation reuses existing .palee/sessions/ logic
- Log path configuration respects PALEE_LOG_DIR env var
- JSON sanitization removes apiKey and similar secrets
- Unit tests pass: npm test test/cli-diagnostics.test.ts
Phase 2: Command Instrumentation
- adopt.ts wraps 3 phases: vault scan, frontmatter parse, atomic writes
- session/start.ts captures: hot reload time, session history load time
- validate.ts logs: violation counts by type, scan duration
- Performance overhead < 5ms per command (benchmark verified)
Phase 3: Test Coverage
- 6+ existing tests updated to assert metrics captured
- New test file test/cli-diagnostics.test.ts with 8+ test cases
- Integration test verifies non-blocking behavior (logging failure doesn't crash)
- Memory footprint increase < 1MB (verified with heap snapshot)
Phase 4: Documentation & Adoption
- ADR document created at docs/adr/00XX-observability-layer.md
- README.md updated with "Debugging & Diagnostics" subsection
- All environment variables documented in CONTRIBUTING.md
- Related invariants cited: INV-29, INV-30, INV-43
Phase 5: Optional Future Enhancement
- palee diagnostics show command added (views last N events)
- --last N flag controls number of events shown (default 10)
- --json flag dumps raw JSONL (human mode shows summary)
Scope Boundaries
In Scope (Phase 1)
- Duration tracking for core commands
- Structured JSONL event logging (opt-in)
- Per-run metrics summary files
- Error failure recording with sanitized stacks
- Full test coverage matrix
- Zero-runtime-dependency implementation
Out of Scope (Phase 1)
- Remote telemetry / cloud backend
- Real-time dashboard or alerting
- Distributed tracing across machines
- Performance regression alerts (these come later)
- Auto-generating performance recommendations
Related Issues
-
#166 - Fix diagnostics: non-finite frontmatter values render as null in validation findings
(Shows existing concern for diagnostic accuracy; this extends the pattern to timing/metrics) -
#565 - Confirmation prompts hang on EOF
(Session continuity contract; observability will help track checkpoint counts and retry attempts) -
ADR-0024 - Exit Codes and Error Contract
(Preserves existing exit code semantics while adding structured metadata)
Labels
- enhancement - Feature request
- core - Core subsystem impact
- tests - Requires new test suite
- cli - CLI layer primary component
Estimated Effort
| Component | Time | Dependencies |
|---|---|---|
| src/cli/diagnostics.ts implementation | ~30 min | None |
| Command instrumentation (3 commands) | ~45 min | diagnostics.ts complete |
| Test matrix + verification | ~60 min | diagnostics.ts + instrumentation |
| Documentation + ADR creation | ~30 min | Final implementation locked |
| Total | ~2.5 hours | Sequential progression |
Risk Assessment: Low risk — purely additive changes, no breaking modifications to existing APIs, optional opt-in activation preserves backward compatibility.
Author
Created through comprehensive analysis of codebase, community web search, and architecture review. Follows repository template format for consistency with existing GitHub workflows.
- Lingua principale
- TypeScript
- Stelle
- 1
- Fork
- 0
- Merge medio
- 15h 5m
- PR unite (30g)
- 163
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di Kuldeep2822k/cli
-
Task: Full-repository improvement audit at 24947bc — verified P1/P2/P3 remediation checklistForse già presa @Kuldeep2822k l’ha presa 8 giorni fa. Apertachore ci cli core dependencies P1 security storage tests working
Kuldeep2822k/cli#565 · 2 commenti · 1 reazione · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
chore priority: low refactor
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
Kuldeep2822k/cli#548 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
Kuldeep2822k/cli#522 ·
I maintainer di solito rispondono entro 1 giorno
-
bug tests
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
Kuldeep2822k/cli#504 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Dependency DashboardApertaci dependencies security
Difficoltà 4/5 3-5 giorni Idoneità per principianti 8/100
Kuldeep2822k/cli#463 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di Kuldeep2822k/cli
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
earendil-works/pi-claude-code#36 ·
I maintainer di solito rispondono entro 6 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
I maintainer di solito rispondono entro 1 giorno
-
Resources: New work of jabodetabekForse già presa @rmt-svc l’ha presa oggi. Apertaresources
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
railmapgen/rmp-gallery#4167 ·
I maintainer di solito rispondono entro 2 giorni
-
documentation p1
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
microsoft/monaco-editor#5577 ·
I maintainer di solito rispondono entro 1 giorno