@unerr-ai/unerr 0.2.1 → 0.2.3
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 +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -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,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Community Detection — Louvain clustering + profile computation + name inference.
|
|
3
|
-
*
|
|
4
|
-
* M.1: Runs Louvain community detection via graphology on the entity graph.
|
|
5
|
-
* M.2: Computes per-community profiles (cohesion, coupling, dominant kinds).
|
|
6
|
-
* M.3: Infers community names from file paths + entity names.
|
|
7
|
-
* M.6: Recomputation trigger (>10% entity change threshold).
|
|
8
|
-
*
|
|
9
|
-
* Performance: runs once per full index, cached. Recomputed only when
|
|
10
|
-
* entity count changes by >10% from last computation.
|
|
11
|
-
*/
|
|
12
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
13
|
-
const log = createModuleLogger("community-detector");
|
|
14
|
-
let lastEntityCount = 0;
|
|
15
|
-
let cachedResult = null;
|
|
16
|
-
const RECOMPUTE_THRESHOLD = 0.1;
|
|
17
|
-
/**
|
|
18
|
-
* Check if community detection needs recomputation.
|
|
19
|
-
*/
|
|
20
|
-
export function needsRecomputation(currentEntityCount) {
|
|
21
|
-
if (!cachedResult)
|
|
22
|
-
return true;
|
|
23
|
-
if (lastEntityCount === 0)
|
|
24
|
-
return true;
|
|
25
|
-
const changeRatio = Math.abs(currentEntityCount - lastEntityCount) / lastEntityCount;
|
|
26
|
-
return changeRatio > RECOMPUTE_THRESHOLD;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Run Louvain community detection on the entity graph.
|
|
30
|
-
*/
|
|
31
|
-
export async function detectCommunities(entities, edges) {
|
|
32
|
-
if (!needsRecomputation(entities.length) && cachedResult) {
|
|
33
|
-
return cachedResult;
|
|
34
|
-
}
|
|
35
|
-
const start = performance.now();
|
|
36
|
-
const graphologyMod = (await import("graphology"));
|
|
37
|
-
const Graph = graphologyMod.default ?? graphologyMod;
|
|
38
|
-
const louvainMod = (await import("graphology-communities-louvain"));
|
|
39
|
-
const louvain = louvainMod.default ?? louvainMod;
|
|
40
|
-
const graph = new Graph({ type: "undirected", allowSelfLoops: false });
|
|
41
|
-
for (const entity of entities) {
|
|
42
|
-
if (!graph.hasNode(entity.key)) {
|
|
43
|
-
graph.addNode(entity.key, {
|
|
44
|
-
kind: entity.kind,
|
|
45
|
-
name: entity.name,
|
|
46
|
-
file_path: entity.file_path,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const entityKeys = new Set(entities.map((e) => e.key));
|
|
51
|
-
for (const edge of edges) {
|
|
52
|
-
if (entityKeys.has(edge.from_key) &&
|
|
53
|
-
entityKeys.has(edge.to_key) &&
|
|
54
|
-
edge.from_key !== edge.to_key &&
|
|
55
|
-
!graph.hasEdge(edge.from_key, edge.to_key)) {
|
|
56
|
-
try {
|
|
57
|
-
graph.addEdge(edge.from_key, edge.to_key);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
/* edge already exists or invalid — skip */
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (graph.order === 0) {
|
|
65
|
-
const empty = {
|
|
66
|
-
communities: [],
|
|
67
|
-
assignments: new Map(),
|
|
68
|
-
modularity: 0,
|
|
69
|
-
entityCount: 0,
|
|
70
|
-
computedAt: new Date().toISOString(),
|
|
71
|
-
};
|
|
72
|
-
cachedResult = empty;
|
|
73
|
-
lastEntityCount = 0;
|
|
74
|
-
return empty;
|
|
75
|
-
}
|
|
76
|
-
const assignments = louvain(graph);
|
|
77
|
-
const modularity = louvain.assign(graph);
|
|
78
|
-
const communityMap = new Map();
|
|
79
|
-
for (const [nodeKey, communityId] of Object.entries(assignments)) {
|
|
80
|
-
const cid = communityId;
|
|
81
|
-
if (!communityMap.has(cid))
|
|
82
|
-
communityMap.set(cid, []);
|
|
83
|
-
communityMap.get(cid)?.push(nodeKey);
|
|
84
|
-
}
|
|
85
|
-
const profiles = [];
|
|
86
|
-
const assignmentMap = new Map();
|
|
87
|
-
for (const [communityId, memberKeys] of communityMap) {
|
|
88
|
-
for (const key of memberKeys) {
|
|
89
|
-
assignmentMap.set(key, communityId);
|
|
90
|
-
}
|
|
91
|
-
const memberEntities = entities.filter((e) => memberKeys.includes(e.key));
|
|
92
|
-
const kindCounts = new Map();
|
|
93
|
-
const fileSet = new Set();
|
|
94
|
-
for (const e of memberEntities) {
|
|
95
|
-
kindCounts.set(e.kind, (kindCounts.get(e.kind) ?? 0) + 1);
|
|
96
|
-
fileSet.add(e.file_path);
|
|
97
|
-
}
|
|
98
|
-
const dominantKinds = [...kindCounts.entries()]
|
|
99
|
-
.sort((a, b) => b[1] - a[1])
|
|
100
|
-
.slice(0, 3)
|
|
101
|
-
.map(([kind, count]) => ({ kind, count }));
|
|
102
|
-
const internalEdges = edges.filter((e) => memberKeys.includes(e.from_key) && memberKeys.includes(e.to_key)).length;
|
|
103
|
-
const externalEdges = edges.filter((e) => (memberKeys.includes(e.from_key) && !memberKeys.includes(e.to_key)) ||
|
|
104
|
-
(!memberKeys.includes(e.from_key) && memberKeys.includes(e.to_key))).length;
|
|
105
|
-
const totalPossibleInternal = (memberKeys.length * (memberKeys.length - 1)) / 2;
|
|
106
|
-
const cohesion = totalPossibleInternal > 0 ? internalEdges / totalPossibleInternal : 0;
|
|
107
|
-
const coupling = internalEdges + externalEdges > 0
|
|
108
|
-
? externalEdges / (internalEdges + externalEdges)
|
|
109
|
-
: 0;
|
|
110
|
-
const name = inferCommunityName([...fileSet], memberEntities.map((e) => e.name));
|
|
111
|
-
profiles.push({
|
|
112
|
-
id: communityId,
|
|
113
|
-
name,
|
|
114
|
-
entityCount: memberKeys.length,
|
|
115
|
-
entityKeys: memberKeys,
|
|
116
|
-
dominantKinds,
|
|
117
|
-
files: [...fileSet],
|
|
118
|
-
cohesion: Math.round(cohesion * 1000) / 1000,
|
|
119
|
-
coupling: Math.round(coupling * 1000) / 1000,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
profiles.sort((a, b) => b.entityCount - a.entityCount);
|
|
123
|
-
const result = {
|
|
124
|
-
communities: profiles,
|
|
125
|
-
assignments: assignmentMap,
|
|
126
|
-
modularity: typeof modularity === "number" ? modularity : 0,
|
|
127
|
-
entityCount: entities.length,
|
|
128
|
-
computedAt: new Date().toISOString(),
|
|
129
|
-
};
|
|
130
|
-
cachedResult = result;
|
|
131
|
-
lastEntityCount = entities.length;
|
|
132
|
-
log.info(`Detected ${profiles.length} communities in ${Math.round(performance.now() - start)}ms (modularity: ${result.modularity.toFixed(3)})`);
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Infer a human-readable community name from file paths + entity names.
|
|
137
|
-
*/
|
|
138
|
-
function inferCommunityName(files, names) {
|
|
139
|
-
const dirFreq = new Map();
|
|
140
|
-
for (const file of files) {
|
|
141
|
-
const parts = file.split("/").filter(Boolean);
|
|
142
|
-
for (const part of parts.slice(0, -1)) {
|
|
143
|
-
if (!["src", "lib", "app", "index"].includes(part)) {
|
|
144
|
-
dirFreq.set(part, (dirFreq.get(part) ?? 0) + 1);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (dirFreq.size > 0) {
|
|
149
|
-
const topDir = [...dirFreq.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
150
|
-
if (topDir && topDir[1] >= files.length * 0.3) {
|
|
151
|
-
return topDir[0];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
const nameFreq = new Map();
|
|
155
|
-
for (const name of names) {
|
|
156
|
-
const tokens = name
|
|
157
|
-
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
158
|
-
.toLowerCase()
|
|
159
|
-
.split(/[\s_-]+/);
|
|
160
|
-
for (const token of tokens) {
|
|
161
|
-
if (token.length > 2)
|
|
162
|
-
nameFreq.set(token, (nameFreq.get(token) ?? 0) + 1);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (nameFreq.size > 0) {
|
|
166
|
-
const topToken = [...nameFreq.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
167
|
-
if (topToken)
|
|
168
|
-
return topToken[0];
|
|
169
|
-
}
|
|
170
|
-
return `community-${files.length}`;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Get cached result (or null if not computed).
|
|
174
|
-
*/
|
|
175
|
-
export function getCachedCommunities() {
|
|
176
|
-
return cachedResult;
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Clear cache (for testing).
|
|
180
|
-
*/
|
|
181
|
-
export function clearCommunityCache() {
|
|
182
|
-
cachedResult = null;
|
|
183
|
-
lastEntityCount = 0;
|
|
184
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resource-Aware Computation Scheduler — defers heavy ops under pressure.
|
|
3
|
-
*
|
|
4
|
-
* Checks heap usage before running expensive computations (Louvain, full
|
|
5
|
-
* health grade, convention detection). If heap >75%, defers to next idle period.
|
|
6
|
-
*/
|
|
7
|
-
import { createModuleLogger } from "../utils/logger.js";
|
|
8
|
-
import { getResourceSnapshot, } from "./indexer/resource-monitor.js";
|
|
9
|
-
const log = createModuleLogger("computation-scheduler");
|
|
10
|
-
const pendingQueue = [];
|
|
11
|
-
let isProcessing = false;
|
|
12
|
-
/**
|
|
13
|
-
* Schedule a computation respecting resource limits.
|
|
14
|
-
* Critical = always runs. Normal = defers if heap >75%. Background = defers if heap >60%.
|
|
15
|
-
*/
|
|
16
|
-
export async function scheduleComputation(id, priority, fn) {
|
|
17
|
-
const snap = getResourceSnapshot();
|
|
18
|
-
if (priority === "critical") {
|
|
19
|
-
await fn();
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
if (priority === "normal" && snap.degradationLevel >= 2) {
|
|
23
|
-
log.info(`Deferring ${id}: heap pressure level ${snap.degradationLevel}`);
|
|
24
|
-
pendingQueue.push({ id, priority, fn });
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (priority === "background" && snap.degradationLevel >= 1) {
|
|
28
|
-
log.info(`Deferring background ${id}: heap pressure level ${snap.degradationLevel}`);
|
|
29
|
-
pendingQueue.push({ id, priority, fn });
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
await fn();
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Process pending computations when resources free up.
|
|
37
|
-
*/
|
|
38
|
-
export async function processPending() {
|
|
39
|
-
if (isProcessing || pendingQueue.length === 0)
|
|
40
|
-
return 0;
|
|
41
|
-
isProcessing = true;
|
|
42
|
-
let processed = 0;
|
|
43
|
-
const snap = getResourceSnapshot();
|
|
44
|
-
if (snap.degradationLevel >= 2) {
|
|
45
|
-
isProcessing = false;
|
|
46
|
-
return 0;
|
|
47
|
-
}
|
|
48
|
-
while (pendingQueue.length > 0) {
|
|
49
|
-
const current = getResourceSnapshot();
|
|
50
|
-
if (current.degradationLevel >= 2)
|
|
51
|
-
break;
|
|
52
|
-
const task = pendingQueue.shift();
|
|
53
|
-
try {
|
|
54
|
-
await task.fn();
|
|
55
|
-
processed++;
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
log.warn(`Scheduled computation ${task.id} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
isProcessing = false;
|
|
62
|
-
return processed;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Get pending computation count.
|
|
66
|
-
*/
|
|
67
|
-
export function getPendingCount() {
|
|
68
|
-
return pendingQueue.length;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Clear all pending computations.
|
|
72
|
-
*/
|
|
73
|
-
export function clearPending() {
|
|
74
|
-
pendingQueue.length = 0;
|
|
75
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Confidence Propagation — MIN across traversed edges, edge source counts.
|
|
3
|
-
*
|
|
4
|
-
* When traversing the graph (e.g., blast radius), the confidence of the
|
|
5
|
-
* result is the MINIMUM confidence of any edge in the traversal path.
|
|
6
|
-
* One heuristic edge makes the whole path heuristic.
|
|
7
|
-
*/
|
|
8
|
-
const CONFIDENCE_ORDER = {
|
|
9
|
-
"compiler-verified": 3,
|
|
10
|
-
structural: 2,
|
|
11
|
-
heuristic: 1,
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Propagate confidence across a path of edges.
|
|
15
|
-
* Result confidence = MIN of all edge confidences in the path.
|
|
16
|
-
*/
|
|
17
|
-
export function propagateConfidence(edgeConfidences) {
|
|
18
|
-
if (edgeConfidences.length === 0) {
|
|
19
|
-
return { level: "structural", edgeSources: {}, pathLength: 0 };
|
|
20
|
-
}
|
|
21
|
-
let minLevel = "compiler-verified";
|
|
22
|
-
const sources = {};
|
|
23
|
-
for (const conf of edgeConfidences) {
|
|
24
|
-
sources[conf] = (sources[conf] ?? 0) + 1;
|
|
25
|
-
if (CONFIDENCE_ORDER[conf] < CONFIDENCE_ORDER[minLevel]) {
|
|
26
|
-
minLevel = conf;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
level: minLevel,
|
|
31
|
-
edgeSources: sources,
|
|
32
|
-
pathLength: edgeConfidences.length,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Get the numeric confidence score for a propagated result.
|
|
37
|
-
*/
|
|
38
|
-
export function confidenceToScore(level) {
|
|
39
|
-
switch (level) {
|
|
40
|
-
case "compiler-verified":
|
|
41
|
-
return 1.0;
|
|
42
|
-
case "structural":
|
|
43
|
-
return 0.85;
|
|
44
|
-
case "heuristic":
|
|
45
|
-
return 0.5;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convention Detector — automated detection of naming, structural, and
|
|
3
|
-
* import direction conventions from the codebase graph.
|
|
4
|
-
*
|
|
5
|
-
* O.6: Naming conventions (suffix/prefix patterns)
|
|
6
|
-
* O.7: Structure conventions (file organization)
|
|
7
|
-
* O.8: Import direction conventions (layer violations)
|
|
8
|
-
* O.9: Confidence scoring per convention
|
|
9
|
-
* O.10: Persistence to .unerr/conventions/detected.json
|
|
10
|
-
*/
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
/**
|
|
14
|
-
* Detect naming conventions (suffix/prefix patterns).
|
|
15
|
-
*/
|
|
16
|
-
export function detectNamingConventions(entities) {
|
|
17
|
-
const conventions = [];
|
|
18
|
-
const suffixCounts = new Map();
|
|
19
|
-
const prefixCounts = new Map();
|
|
20
|
-
for (const entity of entities) {
|
|
21
|
-
if (entity.kind === "function" || entity.kind === "method") {
|
|
22
|
-
const prefixes = [
|
|
23
|
-
"get",
|
|
24
|
-
"set",
|
|
25
|
-
"is",
|
|
26
|
-
"has",
|
|
27
|
-
"create",
|
|
28
|
-
"update",
|
|
29
|
-
"delete",
|
|
30
|
-
"fetch",
|
|
31
|
-
"handle",
|
|
32
|
-
"on",
|
|
33
|
-
];
|
|
34
|
-
for (const prefix of prefixes) {
|
|
35
|
-
if (entity.name.startsWith(prefix) &&
|
|
36
|
-
entity.name.length > prefix.length) {
|
|
37
|
-
const entry = prefixCounts.get(prefix) ?? {
|
|
38
|
-
count: 0,
|
|
39
|
-
kind: entity.kind,
|
|
40
|
-
examples: [],
|
|
41
|
-
};
|
|
42
|
-
entry.count++;
|
|
43
|
-
if (entry.examples.length < 3)
|
|
44
|
-
entry.examples.push(entity.name);
|
|
45
|
-
prefixCounts.set(prefix, entry);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (entity.kind === "class" || entity.kind === "interface") {
|
|
50
|
-
const suffixes = [
|
|
51
|
-
"Service",
|
|
52
|
-
"Controller",
|
|
53
|
-
"Repository",
|
|
54
|
-
"Handler",
|
|
55
|
-
"Factory",
|
|
56
|
-
"Provider",
|
|
57
|
-
"Manager",
|
|
58
|
-
"Helper",
|
|
59
|
-
"Util",
|
|
60
|
-
];
|
|
61
|
-
for (const suffix of suffixes) {
|
|
62
|
-
if (entity.name.endsWith(suffix)) {
|
|
63
|
-
const entry = suffixCounts.get(suffix) ?? {
|
|
64
|
-
count: 0,
|
|
65
|
-
kind: entity.kind,
|
|
66
|
-
examples: [],
|
|
67
|
-
};
|
|
68
|
-
entry.count++;
|
|
69
|
-
if (entry.examples.length < 3)
|
|
70
|
-
entry.examples.push(entity.name);
|
|
71
|
-
suffixCounts.set(suffix, entry);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
for (const [suffix, data] of suffixCounts) {
|
|
77
|
-
if (data.count >= 3) {
|
|
78
|
-
const totalOfKind = entities.filter((e) => e.kind === data.kind).length;
|
|
79
|
-
const adherence = totalOfKind > 0 ? data.count / totalOfKind : 0;
|
|
80
|
-
conventions.push({
|
|
81
|
-
id: `naming:suffix:${suffix.toLowerCase()}`,
|
|
82
|
-
type: "naming",
|
|
83
|
-
name: `${data.kind}s use "${suffix}" suffix`,
|
|
84
|
-
pattern: `*${suffix}`,
|
|
85
|
-
examples: data.examples,
|
|
86
|
-
adherence: Math.round(adherence * 100) / 100,
|
|
87
|
-
confidence: Math.min(0.95, 0.5 + data.count * 0.05),
|
|
88
|
-
entityCount: data.count,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
for (const [prefix, data] of prefixCounts) {
|
|
93
|
-
if (data.count >= 5) {
|
|
94
|
-
const totalFuncs = entities.filter((e) => e.kind === "function" || e.kind === "method").length;
|
|
95
|
-
const adherence = totalFuncs > 0 ? data.count / totalFuncs : 0;
|
|
96
|
-
conventions.push({
|
|
97
|
-
id: `naming:prefix:${prefix}`,
|
|
98
|
-
type: "naming",
|
|
99
|
-
name: `Functions use "${prefix}" prefix pattern`,
|
|
100
|
-
pattern: `${prefix}*`,
|
|
101
|
-
examples: data.examples,
|
|
102
|
-
adherence: Math.round(adherence * 100) / 100,
|
|
103
|
-
confidence: Math.min(0.9, 0.4 + data.count * 0.03),
|
|
104
|
-
entityCount: data.count,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return conventions;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Detect structural conventions (file organization patterns).
|
|
112
|
-
*/
|
|
113
|
-
export function detectStructureConventions(entities) {
|
|
114
|
-
const conventions = [];
|
|
115
|
-
const dirKinds = new Map();
|
|
116
|
-
for (const entity of entities) {
|
|
117
|
-
const parts = entity.file_path.split("/");
|
|
118
|
-
if (parts.length < 2)
|
|
119
|
-
continue;
|
|
120
|
-
const dir = parts.slice(0, -1).join("/");
|
|
121
|
-
if (!dirKinds.has(dir))
|
|
122
|
-
dirKinds.set(dir, new Map());
|
|
123
|
-
const kindMap = dirKinds.get(dir);
|
|
124
|
-
kindMap.set(entity.kind, (kindMap.get(entity.kind) ?? 0) + 1);
|
|
125
|
-
}
|
|
126
|
-
const dirPatterns = new Map();
|
|
127
|
-
for (const [dir, kinds] of dirKinds) {
|
|
128
|
-
const dominant = [...kinds.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
129
|
-
if (dominant && dominant[1] >= 3) {
|
|
130
|
-
const dirName = dir.split("/").pop() ?? dir;
|
|
131
|
-
const pattern = `${dirName}→${dominant[0]}`;
|
|
132
|
-
dirPatterns.set(pattern, (dirPatterns.get(pattern) ?? 0) + 1);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
for (const [pattern, count] of dirPatterns) {
|
|
136
|
-
if (count >= 2) {
|
|
137
|
-
const [dirName, kind] = pattern.split("→");
|
|
138
|
-
conventions.push({
|
|
139
|
-
id: `structure:${pattern.toLowerCase().replace(/[^a-z0-9]/g, "-")}`,
|
|
140
|
-
type: "structure",
|
|
141
|
-
name: `"${dirName}" directories contain primarily ${kind}s`,
|
|
142
|
-
pattern,
|
|
143
|
-
examples: [],
|
|
144
|
-
adherence: 0.8,
|
|
145
|
-
confidence: Math.min(0.85, 0.5 + count * 0.1),
|
|
146
|
-
entityCount: count,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return conventions;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Detect import direction conventions (layer violations).
|
|
154
|
-
*/
|
|
155
|
-
export function detectImportDirectionConventions(entities, edges) {
|
|
156
|
-
const conventions = [];
|
|
157
|
-
const layerOrder = [
|
|
158
|
-
"utils",
|
|
159
|
-
"lib",
|
|
160
|
-
"core",
|
|
161
|
-
"services",
|
|
162
|
-
"api",
|
|
163
|
-
"routes",
|
|
164
|
-
"commands",
|
|
165
|
-
"entrypoints",
|
|
166
|
-
];
|
|
167
|
-
const importEdges = edges.filter((e) => e.type === "imports");
|
|
168
|
-
let violations = 0;
|
|
169
|
-
let total = 0;
|
|
170
|
-
for (const edge of importEdges) {
|
|
171
|
-
const fromEntity = entities.find((e) => e.key === edge.from_key);
|
|
172
|
-
const toEntity = entities.find((e) => e.key === edge.to_key);
|
|
173
|
-
if (!fromEntity || !toEntity)
|
|
174
|
-
continue;
|
|
175
|
-
const fromLayer = getLayerIndex(fromEntity.file_path, layerOrder);
|
|
176
|
-
const toLayer = getLayerIndex(toEntity.file_path, layerOrder);
|
|
177
|
-
if (fromLayer >= 0 && toLayer >= 0) {
|
|
178
|
-
total++;
|
|
179
|
-
if (toLayer > fromLayer)
|
|
180
|
-
violations++;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (total >= 5) {
|
|
184
|
-
const adherence = total > 0 ? 1 - violations / total : 1;
|
|
185
|
-
conventions.push({
|
|
186
|
-
id: "import-direction:layer-order",
|
|
187
|
-
type: "import-direction",
|
|
188
|
-
name: "Imports flow from high-level to low-level (no upward deps)",
|
|
189
|
-
pattern: "entrypoints→commands→services→core→utils",
|
|
190
|
-
examples: [],
|
|
191
|
-
adherence: Math.round(adherence * 100) / 100,
|
|
192
|
-
confidence: 0.8,
|
|
193
|
-
entityCount: total,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return conventions;
|
|
197
|
-
}
|
|
198
|
-
function getLayerIndex(filePath, layers) {
|
|
199
|
-
for (let i = 0; i < layers.length; i++) {
|
|
200
|
-
if (filePath.includes(layers[i]))
|
|
201
|
-
return i;
|
|
202
|
-
}
|
|
203
|
-
return -1;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Run all convention detection and return combined report.
|
|
207
|
-
*/
|
|
208
|
-
export function detectAllConventions(entities, edges) {
|
|
209
|
-
const naming = detectNamingConventions(entities);
|
|
210
|
-
const structure = detectStructureConventions(entities);
|
|
211
|
-
const imports = detectImportDirectionConventions(entities, edges);
|
|
212
|
-
return {
|
|
213
|
-
conventions: [...naming, ...structure, ...imports]
|
|
214
|
-
.filter((c) => c.confidence >= 0.5)
|
|
215
|
-
.sort((a, b) => b.confidence - a.confidence),
|
|
216
|
-
totalEntities: entities.length,
|
|
217
|
-
analyzedAt: new Date().toISOString(),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Persist conventions to .unerr/conventions/detected.json.
|
|
222
|
-
*/
|
|
223
|
-
export function persistConventions(unerrDir, report) {
|
|
224
|
-
const convDir = join(unerrDir, "conventions");
|
|
225
|
-
if (!existsSync(convDir))
|
|
226
|
-
mkdirSync(convDir, { recursive: true });
|
|
227
|
-
writeFileSync(join(convDir, "detected.json"), JSON.stringify(report, null, 2), "utf-8");
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Load persisted conventions.
|
|
231
|
-
*/
|
|
232
|
-
export function loadConventions(unerrDir) {
|
|
233
|
-
const path = join(unerrDir, "conventions", "detected.json");
|
|
234
|
-
if (!existsSync(path))
|
|
235
|
-
return null;
|
|
236
|
-
try {
|
|
237
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
}
|