@unerr-ai/unerr 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/cli.js +37236 -35793
- package/package.json +6 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Logger — consola-based structured logging to .unerr/logs/.
|
|
3
|
-
*
|
|
4
|
-
* All debug and diagnostic data goes here, never to the terminal.
|
|
5
|
-
* Terminal shows only user-facing output (ora spinners, styled stats).
|
|
6
|
-
*
|
|
7
|
-
* Format: NDJSON — one JSON object per line.
|
|
8
|
-
* Rotation: max 10 files, max 10MB each.
|
|
9
|
-
* Retention: 30 days (older files auto-deleted on boot).
|
|
10
|
-
*
|
|
11
|
-
* Temporal intelligence note: session log entries form the episodic memory
|
|
12
|
-
* tier (fast decay, power-law half-life ~3 days). The consolidation daemon
|
|
13
|
-
* compresses these into semantic facts during idle phases.
|
|
14
|
-
*/
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
16
|
-
import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, } from "node:fs";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
import { createConsola } from "consola";
|
|
19
|
-
const SESSION_ID = randomUUID();
|
|
20
|
-
const RETENTION_DAYS = 30;
|
|
21
|
-
const MAX_FILES = 10;
|
|
22
|
-
function formatTimestamp() {
|
|
23
|
-
const d = new Date();
|
|
24
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
25
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
26
|
-
}
|
|
27
|
-
function cleanupOldLogs(logsDir) {
|
|
28
|
-
if (!existsSync(logsDir))
|
|
29
|
-
return;
|
|
30
|
-
try {
|
|
31
|
-
const files = readdirSync(logsDir)
|
|
32
|
-
.filter((f) => f.startsWith("session-") && f.endsWith(".log"))
|
|
33
|
-
.map((f) => {
|
|
34
|
-
const fullPath = join(logsDir, f);
|
|
35
|
-
const stat = statSync(fullPath);
|
|
36
|
-
return {
|
|
37
|
-
name: f,
|
|
38
|
-
path: fullPath,
|
|
39
|
-
mtimeMs: stat.mtimeMs,
|
|
40
|
-
};
|
|
41
|
-
})
|
|
42
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
43
|
-
const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
if (file.mtimeMs < cutoff) {
|
|
46
|
-
try {
|
|
47
|
-
unlinkSync(file.path);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
/* best effort */
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const remaining = files.filter((f) => existsSync(f.path));
|
|
55
|
-
if (remaining.length > MAX_FILES) {
|
|
56
|
-
for (const file of remaining.slice(MAX_FILES)) {
|
|
57
|
-
try {
|
|
58
|
-
unlinkSync(file.path);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
/* best effort */
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
/* never crash on log cleanup failure */
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
let _logger = null;
|
|
71
|
-
let _logFilePath = null;
|
|
72
|
-
/**
|
|
73
|
-
* Initialize the session logger. Call once at boot.
|
|
74
|
-
* Returns the consola logger instance. Subsequent calls return the same instance.
|
|
75
|
-
*/
|
|
76
|
-
export function initSessionLogger(opts = {}) {
|
|
77
|
-
if (_logger)
|
|
78
|
-
return _logger;
|
|
79
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
80
|
-
const logsDir = join(cwd, ".unerr", "logs");
|
|
81
|
-
mkdirSync(logsDir, { recursive: true });
|
|
82
|
-
cleanupOldLogs(logsDir);
|
|
83
|
-
const timestamp = formatTimestamp();
|
|
84
|
-
_logFilePath = join(logsDir, `session-${timestamp}.log`);
|
|
85
|
-
const levelNum = opts.level !== undefined ? Number(opts.level) : undefined;
|
|
86
|
-
const envLevel = process.env.UNERR_LOG_LEVEL;
|
|
87
|
-
const resolvedLevel = levelNum ?? (envLevel ? Number(envLevel) : 3);
|
|
88
|
-
_logger = createConsola({
|
|
89
|
-
level: resolvedLevel,
|
|
90
|
-
stdout: process.stderr,
|
|
91
|
-
stderr: process.stderr,
|
|
92
|
-
reporters: [
|
|
93
|
-
{
|
|
94
|
-
log: (logObj) => {
|
|
95
|
-
if (!_logFilePath)
|
|
96
|
-
return;
|
|
97
|
-
try {
|
|
98
|
-
const entry = {
|
|
99
|
-
time: new Date().toISOString(),
|
|
100
|
-
level: logObj.type,
|
|
101
|
-
session_id: SESSION_ID,
|
|
102
|
-
tag: logObj.tag,
|
|
103
|
-
msg: logObj.args
|
|
104
|
-
.map((a) => (typeof a === "string" ? a : JSON.stringify(a)))
|
|
105
|
-
.join(" "),
|
|
106
|
-
};
|
|
107
|
-
appendFileSync(_logFilePath, `${JSON.stringify(entry)}\n`);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
/* best effort */
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
});
|
|
116
|
-
_logger.info({
|
|
117
|
-
module: "logger",
|
|
118
|
-
msg: "Session started",
|
|
119
|
-
data: { cwd, level: resolvedLevel },
|
|
120
|
-
});
|
|
121
|
-
return _logger;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Get the current session logger. Returns a silent logger if not initialized.
|
|
125
|
-
*/
|
|
126
|
-
export function getSessionLogger() {
|
|
127
|
-
if (_logger)
|
|
128
|
-
return _logger;
|
|
129
|
-
return createConsola({ level: -999 });
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Create a child logger scoped to a specific module.
|
|
133
|
-
*/
|
|
134
|
-
export function createSessionModuleLogger(module) {
|
|
135
|
-
return getSessionLogger().withTag(module);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Get the current session log file path.
|
|
139
|
-
*/
|
|
140
|
-
export function getSessionLogPath() {
|
|
141
|
-
return _logFilePath;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Get the current session ID.
|
|
145
|
-
*/
|
|
146
|
-
export function getSessionId() {
|
|
147
|
-
return SESSION_ID;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Flush the logger (for graceful shutdown). No-op for consola (writes are sync).
|
|
151
|
-
*/
|
|
152
|
-
export function flushSessionLogger() {
|
|
153
|
-
/* consola file writes are synchronous via appendFileSync — no flush needed */
|
|
154
|
-
}
|
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Premium Startup Logger — brand-aligned terminal output.
|
|
3
|
-
*
|
|
4
|
-
* Design principles (inspired by Claude Code, Vercel, PostHog):
|
|
5
|
-
* - Every line tells you something you didn't know
|
|
6
|
-
* - Strategic color: violet accent (brand), cyan (live data), emerald (success)
|
|
7
|
-
* - Dim metadata, bold insights — the eye goes to what matters
|
|
8
|
-
* - Step markers with timing — feels responsive and intentional
|
|
9
|
-
* - No walls of text — one insight per line, whitespace between phases
|
|
10
|
-
*
|
|
11
|
-
* Color palette (from styles/tailwind.css design system):
|
|
12
|
-
* - Violet #8B5CF6 (brand accent)
|
|
13
|
-
* - Cyan #22D3EE (live/active data)
|
|
14
|
-
* - Emerald #34D399 (success)
|
|
15
|
-
* - Amber #FBBF24 (warning)
|
|
16
|
-
* - Red #F87171 (error)
|
|
17
|
-
* - Cloud White #FAFAFA (primary text)
|
|
18
|
-
* - Muted #A1A1AA (metadata)
|
|
19
|
-
*
|
|
20
|
-
* All output to stderr (stdout is MCP JSON-RPC sacred).
|
|
21
|
-
* File logging: call `initFileLog(cwd)` once at boot to enable parallel
|
|
22
|
-
* NDJSON logging to `.unerr/logs/unerr.jsonl` with richer metadata than console.
|
|
23
|
-
*/
|
|
24
|
-
import { appendFileSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
25
|
-
import { join } from "node:path";
|
|
26
|
-
// ── File logging ────────────────────────────────────────────────────
|
|
27
|
-
let _fileLogPath = null;
|
|
28
|
-
let _fileLogCount = 0;
|
|
29
|
-
/** Strip ANSI escape sequences for machine-readable file output. */
|
|
30
|
-
function stripAnsi(s) {
|
|
31
|
-
const ESC = "\x1b";
|
|
32
|
-
const CSI = new RegExp(`${ESC}\\[[0-9;?]*[A-Za-z~]`, "g");
|
|
33
|
-
const OSC = new RegExp(`${ESC}\\][^\\x07]*\\x07`, "g");
|
|
34
|
-
return s.replace(CSI, "").replace(OSC, "");
|
|
35
|
-
}
|
|
36
|
-
function rotateIfNeeded(filePath, maxLines, keepLines) {
|
|
37
|
-
try {
|
|
38
|
-
const content = readFileSync(filePath, "utf-8");
|
|
39
|
-
const lines = content.split("\n").filter(Boolean);
|
|
40
|
-
if (lines.length > maxLines) {
|
|
41
|
-
writeFileSync(filePath, `${lines.slice(-keepLines).join("\n")}\n`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* file may not exist yet */
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** Initialize file logging for this process. Call once at boot. */
|
|
49
|
-
export function initFileLog(cwd) {
|
|
50
|
-
const logsDir = join(cwd, ".unerr", "logs");
|
|
51
|
-
mkdirSync(logsDir, { recursive: true });
|
|
52
|
-
_fileLogPath = join(logsDir, "unerr.jsonl");
|
|
53
|
-
rotateIfNeeded(_fileLogPath, 2000, 1000);
|
|
54
|
-
}
|
|
55
|
-
function writeToFile(level, message, meta) {
|
|
56
|
-
if (!_fileLogPath)
|
|
57
|
-
return;
|
|
58
|
-
const entry = {
|
|
59
|
-
ts: new Date().toISOString(),
|
|
60
|
-
pid: process.pid,
|
|
61
|
-
level,
|
|
62
|
-
msg: stripAnsi(message),
|
|
63
|
-
...meta,
|
|
64
|
-
};
|
|
65
|
-
try {
|
|
66
|
-
appendFileSync(_fileLogPath, `${JSON.stringify(entry)}\n`);
|
|
67
|
-
if (++_fileLogCount % 200 === 0)
|
|
68
|
-
rotateIfNeeded(_fileLogPath, 2000, 1000);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
/* best effort */
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// ── ANSI 256-color helpers ──────────────────────────────────────────
|
|
75
|
-
const ESC = "\x1b[";
|
|
76
|
-
const RESET = `${ESC}0m`;
|
|
77
|
-
const BOLD = `${ESC}1m`;
|
|
78
|
-
const DIM = `${ESC}2m`;
|
|
79
|
-
const ITALIC = `${ESC}3m`;
|
|
80
|
-
// 256-color foreground: \x1b[38;5;{n}m
|
|
81
|
-
function fg256(n) {
|
|
82
|
-
return `${ESC}38;5;${n}m`;
|
|
83
|
-
}
|
|
84
|
-
// True-color (24-bit) foreground: \x1b[38;2;r;g;bm
|
|
85
|
-
function fgRgb(r, g, b) {
|
|
86
|
-
return `${ESC}38;2;${r};${g};${b}m`;
|
|
87
|
-
}
|
|
88
|
-
// ── Brand color codes (true-color for maximum fidelity) ─────────────
|
|
89
|
-
const VIOLET = fgRgb(139, 92, 246); // #8B5CF6
|
|
90
|
-
const CYAN = fgRgb(34, 211, 238); // #22D3EE
|
|
91
|
-
const EMERALD = fgRgb(52, 211, 153); // #34D399
|
|
92
|
-
const AMBER = fgRgb(251, 191, 36); // #FBBF24
|
|
93
|
-
const RED = fgRgb(248, 113, 113); // #F87171
|
|
94
|
-
const MUTED = fgRgb(161, 161, 170); // #A1A1AA
|
|
95
|
-
const WHITE = fgRgb(250, 250, 250); // #FAFAFA
|
|
96
|
-
// ── Formatters ──────────────────────────────────────────��───────────
|
|
97
|
-
function violet(s) {
|
|
98
|
-
return `${VIOLET}${s}${RESET}`;
|
|
99
|
-
}
|
|
100
|
-
function cyan(s) {
|
|
101
|
-
return `${CYAN}${s}${RESET}`;
|
|
102
|
-
}
|
|
103
|
-
function emerald(s) {
|
|
104
|
-
return `${EMERALD}${s}${RESET}`;
|
|
105
|
-
}
|
|
106
|
-
function amber(s) {
|
|
107
|
-
return `${AMBER}${s}${RESET}`;
|
|
108
|
-
}
|
|
109
|
-
function red(s) {
|
|
110
|
-
return `${RED}${s}${RESET}`;
|
|
111
|
-
}
|
|
112
|
-
function muted(s) {
|
|
113
|
-
return `${MUTED}${s}${RESET}`;
|
|
114
|
-
}
|
|
115
|
-
function bold(s) {
|
|
116
|
-
return `${BOLD}${s}${RESET}`;
|
|
117
|
-
}
|
|
118
|
-
function dim(s) {
|
|
119
|
-
return `${DIM}${s}${RESET}`;
|
|
120
|
-
}
|
|
121
|
-
function brandBold(s) {
|
|
122
|
-
return `${BOLD}${VIOLET}${s}${RESET}`;
|
|
123
|
-
}
|
|
124
|
-
// ── Symbols ─────────────────────────────────────────────────────────
|
|
125
|
-
const SYM = {
|
|
126
|
-
step: `${VIOLET}▸${RESET}`,
|
|
127
|
-
done: `${EMERALD}✓${RESET}`,
|
|
128
|
-
warn: `${AMBER}⚠${RESET}`,
|
|
129
|
-
fail: `${RED}✗${RESET}`,
|
|
130
|
-
dot: `${MUTED}·${RESET}`,
|
|
131
|
-
arrow: `${MUTED}→${RESET}`,
|
|
132
|
-
bar: `${MUTED}│${RESET}`,
|
|
133
|
-
brain: `${VIOLET}◆${RESET}`,
|
|
134
|
-
bolt: `${CYAN}⚡${RESET}`,
|
|
135
|
-
};
|
|
136
|
-
// ── Output (always stderr) ──────────────────────────────────────────
|
|
137
|
-
// Pause buffer — when paused (e.g. while an interactive clack prompt owns
|
|
138
|
-
// the terminal during indexing), startup lines are buffered and flushed on
|
|
139
|
-
// resume. Avoids prints like "Dashboard — http://…" appearing under the
|
|
140
|
-
// prompt while the user is still picking an option.
|
|
141
|
-
let paused = false;
|
|
142
|
-
const pauseBuffer = [];
|
|
143
|
-
function write(line) {
|
|
144
|
-
if (paused) {
|
|
145
|
-
pauseBuffer.push(line);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
process.stderr.write(`${line}\n`);
|
|
149
|
-
}
|
|
150
|
-
function pauseWrites() {
|
|
151
|
-
paused = true;
|
|
152
|
-
}
|
|
153
|
-
function resumeWrites() {
|
|
154
|
-
paused = false;
|
|
155
|
-
if (pauseBuffer.length === 0)
|
|
156
|
-
return;
|
|
157
|
-
const pending = pauseBuffer.splice(0, pauseBuffer.length);
|
|
158
|
-
for (const line of pending) {
|
|
159
|
-
process.stderr.write(`${line}\n`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// ── Public API ──────────────────────────────────────────��───────────
|
|
163
|
-
export const startupLog = {
|
|
164
|
-
/** Brand header — first thing the user sees */
|
|
165
|
-
header() {
|
|
166
|
-
write("");
|
|
167
|
-
write(` ${brandBold("unerr")} ${muted("— intelligence engine for AI agents")}`);
|
|
168
|
-
write("");
|
|
169
|
-
writeToFile("header", "unerr — intelligence engine for AI agents");
|
|
170
|
-
},
|
|
171
|
-
/** Phase separator with dim label */
|
|
172
|
-
phase(label) {
|
|
173
|
-
write(` ${dim("─── ")}${muted(label)}${dim(" ───")}`);
|
|
174
|
-
writeToFile("phase", label);
|
|
175
|
-
},
|
|
176
|
-
/** Active step — something is happening */
|
|
177
|
-
step(msg) {
|
|
178
|
-
write(` ${SYM.step} ${msg}`);
|
|
179
|
-
writeToFile("step", stripAnsi(msg));
|
|
180
|
-
},
|
|
181
|
-
/** Completed step with optional timing */
|
|
182
|
-
done(msg, ms) {
|
|
183
|
-
const timing = ms !== undefined ? ` ${muted(`${ms}ms`)}` : "";
|
|
184
|
-
write(` ${SYM.done} ${msg}${timing}`);
|
|
185
|
-
writeToFile("done", stripAnsi(msg), ms !== undefined ? { ms } : undefined);
|
|
186
|
-
},
|
|
187
|
-
/** Insight line — the wow factor. Tells user something they didn't know */
|
|
188
|
-
insight(msg) {
|
|
189
|
-
write(` ${SYM.brain} ${msg}`);
|
|
190
|
-
writeToFile("insight", stripAnsi(msg));
|
|
191
|
-
},
|
|
192
|
-
/** Live data / metric with cyan accent */
|
|
193
|
-
metric(label, value, unit) {
|
|
194
|
-
const unitStr = unit ? ` ${muted(unit)}` : "";
|
|
195
|
-
write(` ${SYM.dot} ${muted(label)} ${cyan(String(value))}${unitStr}`);
|
|
196
|
-
writeToFile("metric", label, { value, unit });
|
|
197
|
-
},
|
|
198
|
-
/** Performance highlight — instant/fast operations */
|
|
199
|
-
perf(msg) {
|
|
200
|
-
write(` ${SYM.bolt} ${msg}`);
|
|
201
|
-
writeToFile("perf", stripAnsi(msg));
|
|
202
|
-
},
|
|
203
|
-
/** Warning — non-blocking issue */
|
|
204
|
-
warn(msg) {
|
|
205
|
-
write(` ${SYM.warn} ${amber(msg)}`);
|
|
206
|
-
writeToFile("warn", msg);
|
|
207
|
-
},
|
|
208
|
-
/**
|
|
209
|
-
* File-only log entry — writes to .unerr/logs/unerr.jsonl without touching
|
|
210
|
-
* stderr. Use this on hot paths like `unerr exec` where stderr gets merged
|
|
211
|
-
* into the agent's tool-result context (every byte we emit is LLM tokens),
|
|
212
|
-
* but we still want the event in the JSONL log for dashboards / debugging.
|
|
213
|
-
*/
|
|
214
|
-
fileOnly(level, msg, meta) {
|
|
215
|
-
writeToFile(level, msg, meta);
|
|
216
|
-
},
|
|
217
|
-
/** Error — blocking issue */
|
|
218
|
-
error(msg) {
|
|
219
|
-
write(` ${SYM.fail} ${red(msg)}`);
|
|
220
|
-
writeToFile("error", msg);
|
|
221
|
-
},
|
|
222
|
-
/** Detail line — supplementary info, indented + dim */
|
|
223
|
-
detail(msg) {
|
|
224
|
-
write(` ${muted(msg)}`);
|
|
225
|
-
writeToFile("detail", msg);
|
|
226
|
-
},
|
|
227
|
-
/** Blank line for breathing room */
|
|
228
|
-
blank() {
|
|
229
|
-
write("");
|
|
230
|
-
},
|
|
231
|
-
/** Final ready message — the "we're good" confirmation */
|
|
232
|
-
ready(toolCount, mode) {
|
|
233
|
-
write("");
|
|
234
|
-
write(` ${SYM.done} ${bold("Ready")} ${muted("—")} ${cyan(String(toolCount))} ${muted("tools")} ${muted("·")} ${muted(mode)} ${muted("mode")} ${muted("·")} ${emerald("<5ms")} ${muted("per query")}`);
|
|
235
|
-
write("");
|
|
236
|
-
writeToFile("ready", "Ready", { toolCount, mode });
|
|
237
|
-
},
|
|
238
|
-
/** Session summary box — end of session stats */
|
|
239
|
-
summary(stats) {
|
|
240
|
-
write("");
|
|
241
|
-
write(` ${dim("┌──────────────────────────────────────────┐")}`);
|
|
242
|
-
write(` ${dim("│")} ${brandBold("unerr")} session ${dim("│")}`);
|
|
243
|
-
write(` ${dim("├──────────────────────────────────────────┤")}`);
|
|
244
|
-
write(` ${dim("│")} Duration ${cyan(stats.duration.padEnd(24))}${dim("│")}`);
|
|
245
|
-
write(` ${dim("│")} Tool calls ${cyan(String(stats.toolCalls).padEnd(24))}${dim("│")}`);
|
|
246
|
-
if (stats.tokensSaved) {
|
|
247
|
-
write(` ${dim("│")} Saved ${emerald(stats.tokensSaved.padEnd(24))}${dim("│")}`);
|
|
248
|
-
}
|
|
249
|
-
if (stats.efficiency) {
|
|
250
|
-
write(` ${dim("│")} Efficiency ${emerald(stats.efficiency.padEnd(24))}${dim("│")}`);
|
|
251
|
-
}
|
|
252
|
-
write(` ${dim("└──────────────────────────────────────────┘")}`);
|
|
253
|
-
write("");
|
|
254
|
-
writeToFile("summary", "Session summary", {
|
|
255
|
-
duration: stats.duration,
|
|
256
|
-
toolCalls: stats.toolCalls,
|
|
257
|
-
tokensSaved: stats.tokensSaved,
|
|
258
|
-
efficiency: stats.efficiency,
|
|
259
|
-
});
|
|
260
|
-
},
|
|
261
|
-
// ── Convenience composites ──────────────────────────────────────
|
|
262
|
-
/** Graph loaded insight block — the money shot */
|
|
263
|
-
graphLoaded(stats) {
|
|
264
|
-
const avgConn = stats.edges > 0 ? (stats.edges / stats.entities).toFixed(1) : "0";
|
|
265
|
-
write(` ${SYM.done} ${bold("Graph loaded")} ${muted(`in ${stats.ms}ms`)}`);
|
|
266
|
-
write("");
|
|
267
|
-
write(` ${cyan(stats.entities.toLocaleString())} entities ${muted("across")} ${cyan(String(stats.files))} files ${muted("·")} ${cyan(stats.edges.toLocaleString())} edges ${muted(`(${avgConn} avg/entity)`)}`);
|
|
268
|
-
write(` ${violet(String(stats.communities))} communities ${muted("detected")} ${muted("·")} ${violet(String(stats.patterns))} conventions ${muted("→")} ${violet(String(stats.rules))} rules`);
|
|
269
|
-
if (stats.hottestFile) {
|
|
270
|
-
write(` ${muted("hottest:")} ${bold(stats.hottestFile)} ${muted(`(${stats.hottestCount} entities)`)}`);
|
|
271
|
-
}
|
|
272
|
-
write("");
|
|
273
|
-
writeToFile("graph_loaded", "Graph loaded", {
|
|
274
|
-
entities: stats.entities,
|
|
275
|
-
edges: stats.edges,
|
|
276
|
-
files: stats.files,
|
|
277
|
-
communities: stats.communities,
|
|
278
|
-
patterns: stats.patterns,
|
|
279
|
-
rules: stats.rules,
|
|
280
|
-
ms: stats.ms,
|
|
281
|
-
hottestFile: stats.hottestFile,
|
|
282
|
-
hottestCount: stats.hottestCount,
|
|
283
|
-
});
|
|
284
|
-
},
|
|
285
|
-
/** MCP tools registered */
|
|
286
|
-
toolsReady(count, ruleCount) {
|
|
287
|
-
write(` ${SYM.done} ${cyan(String(count))} intelligence tools registered ${ruleCount > 0 ? muted(`(${ruleCount} enforcement rules)`) : ""}`);
|
|
288
|
-
writeToFile("tools_ready", "Tools registered", { count, ruleCount });
|
|
289
|
-
},
|
|
290
|
-
/** Skills installed during setup */
|
|
291
|
-
skillsInstalled(names, ide) {
|
|
292
|
-
write(` ${SYM.done} ${emerald(String(names.length))} agent skills installed ${muted(`for ${ide}`)}`);
|
|
293
|
-
for (const name of names) {
|
|
294
|
-
write(` ${muted("·")} ${name}`);
|
|
295
|
-
}
|
|
296
|
-
writeToFile("skills_installed", "Skills installed", {
|
|
297
|
-
ide,
|
|
298
|
-
count: names.length,
|
|
299
|
-
names,
|
|
300
|
-
});
|
|
301
|
-
},
|
|
302
|
-
/** MCP config written */
|
|
303
|
-
mcpConfigured(ide, path) {
|
|
304
|
-
write(` ${SYM.done} MCP server registered ${muted("→")} ${dim(path)}`);
|
|
305
|
-
write(` ${muted(`${ide} will auto-connect to unerr intelligence`)}`);
|
|
306
|
-
writeToFile("mcp_configured", "MCP configured", { ide, path });
|
|
307
|
-
},
|
|
308
|
-
/** Background indexing started */
|
|
309
|
-
indexingStarted() {
|
|
310
|
-
write(` ${SYM.step} Deep indexing ${muted("(tree-sitter AST analysis)")}`);
|
|
311
|
-
writeToFile("indexing_started", "Deep indexing started");
|
|
312
|
-
},
|
|
313
|
-
/** Session resume context */
|
|
314
|
-
sessionResumed(prevCalls, prevMinutes) {
|
|
315
|
-
write(` ${SYM.done} Session resumed ${muted("— picking up where you left off")}`);
|
|
316
|
-
write(` ${muted("previous:")} ${cyan(String(prevCalls))} tool calls ${muted("·")} ${cyan(String(prevMinutes))}min`);
|
|
317
|
-
writeToFile("session_resumed", "Session resumed", {
|
|
318
|
-
prevCalls,
|
|
319
|
-
prevMinutes,
|
|
320
|
-
});
|
|
321
|
-
},
|
|
322
|
-
/** Token flow event — real-time savings display in proxy mode */
|
|
323
|
-
tokenFlow(opts) {
|
|
324
|
-
const toolSlug = opts.tool ?? "shell";
|
|
325
|
-
const prefix = opts.pid && opts.pid !== process.pid
|
|
326
|
-
? `${muted(`[exec:${opts.pid}]`)} `
|
|
327
|
-
: "";
|
|
328
|
-
write(` ${SYM.step} ${prefix}Turn ${cyan(String(opts.turn))}: ${bold(toolSlug)} ${muted("—")} ${emerald(opts.tokensSaved.toLocaleString())} tokens saved ${muted(`(${opts.mechanism})`)}${opts.tokensDelivered > 0 ? `, ${cyan(opts.tokensDelivered.toLocaleString())} delivered` : ""}`);
|
|
329
|
-
writeToFile("token_flow", `${toolSlug}: ${opts.tokensSaved} saved (${opts.mechanism})`, {
|
|
330
|
-
turn: opts.turn,
|
|
331
|
-
tool: opts.tool,
|
|
332
|
-
mechanism: opts.mechanism,
|
|
333
|
-
tokens_saved: opts.tokensSaved,
|
|
334
|
-
tokens_delivered: opts.tokensDelivered,
|
|
335
|
-
session_total: opts.sessionTotal,
|
|
336
|
-
pid: opts.pid,
|
|
337
|
-
});
|
|
338
|
-
},
|
|
339
|
-
/** Token flow session total — periodic summary line */
|
|
340
|
-
tokenFlowTotal(saved, delivered, efficiency) {
|
|
341
|
-
write(` ${SYM.bolt} Session: ${emerald(saved.toLocaleString())} saved ${muted("/")} ${cyan(delivered.toLocaleString())} delivered ${muted(`(${efficiency}% efficiency)`)}`);
|
|
342
|
-
writeToFile("token_flow_total", "Session token flow", {
|
|
343
|
-
tokens_saved: saved,
|
|
344
|
-
tokens_delivered: delivered,
|
|
345
|
-
efficiency_pct: efficiency,
|
|
346
|
-
});
|
|
347
|
-
},
|
|
348
|
-
/** Health card — visually rich architecture health display */
|
|
349
|
-
healthCard(health) {
|
|
350
|
-
const gradeColor = health.score >= 90
|
|
351
|
-
? emerald
|
|
352
|
-
: health.score >= 70
|
|
353
|
-
? cyan
|
|
354
|
-
: health.score >= 50
|
|
355
|
-
? amber
|
|
356
|
-
: red;
|
|
357
|
-
const gradeBg = health.score >= 90
|
|
358
|
-
? fgRgb(16, 185, 129) // deeper emerald
|
|
359
|
-
: health.score >= 70
|
|
360
|
-
? fgRgb(6, 182, 212) // deeper cyan
|
|
361
|
-
: health.score >= 50
|
|
362
|
-
? fgRgb(245, 158, 11) // deeper amber
|
|
363
|
-
: fgRgb(239, 68, 68); // deeper red
|
|
364
|
-
// ── Score bar (sub-character precision) ──
|
|
365
|
-
const BAR_WIDTH = 24;
|
|
366
|
-
const BLOCKS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
|
|
367
|
-
const fillWidth = (health.score / 100) * BAR_WIDTH;
|
|
368
|
-
const fullBlocks = Math.floor(fillWidth);
|
|
369
|
-
const partialIdx = Math.round((fillWidth - fullBlocks) * (BLOCKS.length - 1));
|
|
370
|
-
const emptyBlocks = BAR_WIDTH - fullBlocks - (partialIdx > 0 ? 1 : 0);
|
|
371
|
-
const bar = "█".repeat(fullBlocks) +
|
|
372
|
-
(partialIdx > 0 ? (BLOCKS[partialIdx] ?? "") : "") +
|
|
373
|
-
"░".repeat(Math.max(0, emptyBlocks));
|
|
374
|
-
write("");
|
|
375
|
-
write(` ${dim("┌─────────────────────────────────────────────────────┐")}`);
|
|
376
|
-
write(` ${dim("│")} ${violet("◆")} ${bold("Architecture Health")} ${dim("│")}`);
|
|
377
|
-
write(` ${dim("├─────────────────────────────────────────────────────┤")}`);
|
|
378
|
-
write(` ${dim("│")} ${dim("│")}`);
|
|
379
|
-
write(` ${dim("│")} ${gradeColor(`${BOLD}${health.grade}${RESET}`)} ${gradeBg}${bar}${RESET} ${gradeColor(`${health.score}`)}${muted("/100")} ${dim("│")}`);
|
|
380
|
-
write(` ${dim("│")} ${dim("│")}`);
|
|
381
|
-
write(` ${dim("├─────────────────────────────────────────────────────┤")}`);
|
|
382
|
-
// ── Metrics grid ──
|
|
383
|
-
const entityStr = health.totalEntities.toLocaleString();
|
|
384
|
-
const edgeStr = health.totalEdges.toLocaleString();
|
|
385
|
-
const connectivity = health.totalEntities > 0
|
|
386
|
-
? (health.totalEdges / health.totalEntities).toFixed(1)
|
|
387
|
-
: "0";
|
|
388
|
-
write(` ${dim("│")} ${muted("Entities")} ${cyan(entityStr.padEnd(8))} ${muted("Edges")} ${cyan(edgeStr.padEnd(8))} ${dim("│")}`);
|
|
389
|
-
write(` ${dim("│")} ${muted("Connectivity")} ${cyan(connectivity.padEnd(8))} ${muted("Rules")} ${violet(String(health.totalRules).padEnd(8))} ${dim("│")}`);
|
|
390
|
-
// ── Signals ──
|
|
391
|
-
write(` ${dim("├─────────────────────────────────────────────────────┤")}`);
|
|
392
|
-
write(` ${dim("│")} ${muted("Signal")} ${muted("Status")} ${dim("│")}`);
|
|
393
|
-
write(` ${dim("│")} ${dim("─────────────────────────────────────────────")} ${dim("│")}`);
|
|
394
|
-
// Dead functions
|
|
395
|
-
const deadIcon = health.deadFunctionCount === 0
|
|
396
|
-
? SYM.done
|
|
397
|
-
: health.deadFunctionCount > 20
|
|
398
|
-
? SYM.fail
|
|
399
|
-
: SYM.warn;
|
|
400
|
-
const deadColor = health.deadFunctionCount === 0
|
|
401
|
-
? emerald
|
|
402
|
-
: health.deadFunctionCount > 20
|
|
403
|
-
? red
|
|
404
|
-
: amber;
|
|
405
|
-
write(` ${dim("│")} ${deadIcon} ${muted("Dead functions")} ${deadColor(String(health.deadFunctionCount).padEnd(4))} ${dim("│")}`);
|
|
406
|
-
// Circular deps
|
|
407
|
-
const circCount = health.circularDeps?.length ?? 0;
|
|
408
|
-
const circIcon = circCount === 0 ? SYM.done : circCount > 5 ? SYM.fail : SYM.warn;
|
|
409
|
-
const circColor = circCount === 0 ? emerald : circCount > 5 ? red : amber;
|
|
410
|
-
write(` ${dim("│")} ${circIcon} ${muted("Circular dependencies")} ${circColor(String(circCount).padEnd(4))} ${dim("│")}`);
|
|
411
|
-
// Import depth
|
|
412
|
-
const depth = health.maxImportDepth ?? 0;
|
|
413
|
-
const depthIcon = depth <= 7 ? SYM.done : depth > 15 ? SYM.fail : SYM.warn;
|
|
414
|
-
const depthColor = depth <= 7 ? emerald : depth > 15 ? red : amber;
|
|
415
|
-
write(` ${dim("│")} ${depthIcon} ${muted("Max import chain")} ${depthColor(String(depth).padEnd(4))} ${dim("│")}`);
|
|
416
|
-
// Convention adherence
|
|
417
|
-
const adherence = health.conventionAdherence ?? 1;
|
|
418
|
-
const adherencePct = `${Math.round(adherence * 100)}%`;
|
|
419
|
-
const adhIcon = adherence >= 0.9 ? SYM.done : adherence >= 0.7 ? SYM.warn : SYM.fail;
|
|
420
|
-
const adhColor = adherence >= 0.9 ? emerald : adherence >= 0.7 ? amber : red;
|
|
421
|
-
write(` ${dim("│")} ${adhIcon} ${muted("Convention adherence")} ${adhColor(adherencePct.padEnd(4))} ${dim("│")}`);
|
|
422
|
-
// Drift impact
|
|
423
|
-
const drift = health.driftImpactScore ?? 0;
|
|
424
|
-
const driftIcon = drift === 0 ? SYM.done : drift > 10 ? SYM.fail : SYM.warn;
|
|
425
|
-
const driftColor = drift === 0 ? emerald : drift > 10 ? red : amber;
|
|
426
|
-
write(` ${dim("│")} ${driftIcon} ${muted("Drift in critical paths")} ${driftColor(String(drift).padEnd(4))} ${dim("│")}`);
|
|
427
|
-
// Orphan test files
|
|
428
|
-
const orphanCount = health.orphanTestFiles?.length ?? 0;
|
|
429
|
-
const orphanIcon = orphanCount === 0 ? SYM.done : orphanCount > 10 ? SYM.fail : SYM.warn;
|
|
430
|
-
const orphanColor = orphanCount === 0 ? emerald : orphanCount > 10 ? red : amber;
|
|
431
|
-
write(` ${dim("│")} ${orphanIcon} ${muted("Orphan test files")} ${orphanColor(String(orphanCount).padEnd(4))} ${dim("│")}`);
|
|
432
|
-
// ── High-risk entities (chokepoints) ──
|
|
433
|
-
if (health.highRiskEntities.length > 0) {
|
|
434
|
-
write(` ${dim("├─────────────────────────────────────────────────────┤")}`);
|
|
435
|
-
write(` ${dim("│")} ${amber("⚠")} ${bold("Chokepoints")} ${muted("— changes here ripple widely")} ${dim("│")}`);
|
|
436
|
-
for (const entity of health.highRiskEntities) {
|
|
437
|
-
const fanStr = `${entity.fan_in}↓ ${entity.fan_out}↑`;
|
|
438
|
-
const nameDisplay = entity.name.length > 24
|
|
439
|
-
? `${entity.name.slice(0, 22)}..`
|
|
440
|
-
: entity.name;
|
|
441
|
-
write(` ${dim("│")} ${cyan(nameDisplay.padEnd(26))} ${amber(fanStr.padEnd(10))} ${dim("│")}`);
|
|
442
|
-
const fileShort = entity.file_path.length > 40
|
|
443
|
-
? `...${entity.file_path.slice(-37)}`
|
|
444
|
-
: entity.file_path;
|
|
445
|
-
write(` ${dim("│")} ${dim(fileShort.padEnd(43))} ${dim("│")}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
write(` ${dim("└─────────────────────────────────────────────────────┘")}`);
|
|
449
|
-
write("");
|
|
450
|
-
writeToFile("health_card", "Architecture health", {
|
|
451
|
-
grade: health.grade,
|
|
452
|
-
score: health.score,
|
|
453
|
-
totalEntities: health.totalEntities,
|
|
454
|
-
totalEdges: health.totalEdges,
|
|
455
|
-
totalRules: health.totalRules,
|
|
456
|
-
deadFunctionCount: health.deadFunctionCount,
|
|
457
|
-
circularDeps: health.circularDeps?.length ?? 0,
|
|
458
|
-
maxImportDepth: health.maxImportDepth,
|
|
459
|
-
conventionAdherence: health.conventionAdherence,
|
|
460
|
-
driftImpactScore: health.driftImpactScore,
|
|
461
|
-
orphanTestFiles: health.orphanTestFiles?.length ?? 0,
|
|
462
|
-
highRiskCount: health.highRiskEntities.length,
|
|
463
|
-
});
|
|
464
|
-
},
|
|
465
|
-
/** MCP connection card — shows config snippet for manual agent setup */
|
|
466
|
-
mcpConnectionCard(configuredAgents, projectDir) {
|
|
467
|
-
write("");
|
|
468
|
-
write(` ${dim("┌─────────────────────────────────────────────────────┐")}`);
|
|
469
|
-
write(` ${dim("│")} ${violet("⚡")} ${bold("MCP Connection")} ${dim("│")}`);
|
|
470
|
-
write(` ${dim("├─────────────────────────────────────────────────────┤")}`);
|
|
471
|
-
if (configuredAgents.length > 0) {
|
|
472
|
-
write(` ${dim("│")} ${emerald(SYM.done)} ${muted("Auto-configured:")} ${cyan(configuredAgents.join(", "))} ${dim("│")}`);
|
|
473
|
-
}
|
|
474
|
-
write(` ${dim("│")} ${dim("│")}`);
|
|
475
|
-
write(` ${dim("│")} ${muted("For any MCP-compatible agent, add to config:")} ${dim("│")}`);
|
|
476
|
-
write(` ${dim("│")} ${dim("│")}`);
|
|
477
|
-
write(` ${dim("│")} ${dim("{")} ${dim("│")}`);
|
|
478
|
-
write(` ${dim("│")} ${cyan('"mcpServers"')}: ${dim("{")} ${dim("│")}`);
|
|
479
|
-
write(` ${dim("│")} ${cyan('"unerr"')}: ${dim("{")} ${dim("│")}`);
|
|
480
|
-
write(` ${dim("│")} ${cyan('"command"')}: ${emerald('"unerr"')}${dim(",")} ${dim("│")}`);
|
|
481
|
-
write(` ${dim("│")} ${cyan('"args"')}: [${emerald('"--mcp"')}] ${dim("│")}`);
|
|
482
|
-
write(` ${dim("│")} ${dim("}")} ${dim("│")}`);
|
|
483
|
-
write(` ${dim("│")} ${dim("}")} ${dim("│")}`);
|
|
484
|
-
write(` ${dim("│")} ${dim("}")} ${dim("│")}`);
|
|
485
|
-
write(` ${dim("│")} ${dim("│")}`);
|
|
486
|
-
write(` ${dim("│")} ${muted("Add more:")} ${dim("unerr install <agent>")} ${dim("│")}`);
|
|
487
|
-
write(` ${dim("└─────────────────────────────────────────────────────┘")}`);
|
|
488
|
-
write("");
|
|
489
|
-
writeToFile("mcp_connection_card", "MCP connection info", {
|
|
490
|
-
configuredAgents,
|
|
491
|
-
projectDir,
|
|
492
|
-
});
|
|
493
|
-
},
|
|
494
|
-
/** Layer 7 — web dashboard is listening (127.0.0.1, same process as proxy) */
|
|
495
|
-
dashboardReady(url) {
|
|
496
|
-
write(` ${SYM.brain} ${bold("Dashboard")} ${muted("—")} ${cyan(url)}`);
|
|
497
|
-
write(` ${muted("Tip:")} ${dim("unerr dashboard")} ${muted("opens this in your browser")}`);
|
|
498
|
-
writeToFile("dashboard_ready", "Dashboard ready", { url });
|
|
499
|
-
},
|
|
500
|
-
/**
|
|
501
|
-
* Buffer all subsequent startup-log writes until resume() is called.
|
|
502
|
-
* Used to keep interactive prompts (e.g. SCIP build-tool picker) free of
|
|
503
|
-
* stderr clutter from async events that complete while the prompt is on
|
|
504
|
-
* screen (Dashboard ready, etc.). Buffered lines flush on resume in order.
|
|
505
|
-
*/
|
|
506
|
-
pause: pauseWrites,
|
|
507
|
-
/** Resume writes and flush anything buffered since pause(). */
|
|
508
|
-
resume: resumeWrites,
|
|
509
|
-
// ── Direct color exports for custom formatting ────────────────
|
|
510
|
-
fmt: { violet, cyan, emerald, amber, red, muted, bold, dim, brandBold },
|
|
511
|
-
sym: SYM,
|
|
512
|
-
};
|