@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,388 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Instruction Writer — injects tool-preference instructions into agent instruction files.
|
|
3
|
-
*
|
|
4
|
-
* Registry-driven: uses agent-registry.ts to determine instruction file path and format.
|
|
5
|
-
* Idempotent — uses sentinel markers to create/update/skip sections without clobbering user content.
|
|
6
|
-
*
|
|
7
|
-
* Supported formats:
|
|
8
|
-
* - markdown: CLAUDE.md, AGENTS.md, GEMINI.md, copilot-instructions.md, .clinerules
|
|
9
|
-
* Uses <!-- unerr:start --> / <!-- unerr:end --> sentinel markers
|
|
10
|
-
* - mdc: .cursor/rules/*.mdc files (standalone file, overwrite entire file)
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
14
|
-
import { getAgent } from "./agent-registry.js";
|
|
15
|
-
const SENTINEL_START = "<!-- unerr:start -->";
|
|
16
|
-
const SENTINEL_END = "<!-- unerr:end -->";
|
|
17
|
-
/**
|
|
18
|
-
* The tool-preference instructions injected into agent instruction files.
|
|
19
|
-
* Concise: tells the agent WHEN and WHY to use unerr tools over built-ins.
|
|
20
|
-
*/
|
|
21
|
-
function getInstructionContent(ide) {
|
|
22
|
-
const isClaudeCode = ide === "claude-code";
|
|
23
|
-
// Claude Code requires built-in Read before Edit (readFileState constraint).
|
|
24
|
-
// Other agents can use file_read directly before editing.
|
|
25
|
-
const readForEditRow = isClaudeCode
|
|
26
|
-
? `| Understand a file before editing | \`file_read\` with \`purpose:'explore'\` to understand, then built-in \`Read\` (offset/limit) on target lines before Edit | Reading entire file |`
|
|
27
|
-
: "| Read a file before editing | `file_read` with `entity` param or offset/limit for targeted access | Reading entire file |";
|
|
28
|
-
const twoStepSection = isClaudeCode
|
|
29
|
-
? `
|
|
30
|
-
### IMPORTANT: Two-step Read Routing (Claude Code specific)
|
|
31
|
-
|
|
32
|
-
**Why this matters:** Claude Code's Edit tool requires built-in \`Read\` to have been called on the file first. \`file_read\` (unerr MCP) does NOT satisfy this because it's a separate MCP tool. Meanwhile, built-in Read misses project conventions and facts that \`file_read\` auto-injects.
|
|
33
|
-
|
|
34
|
-
**The rule — two paths, choose by intent:**
|
|
35
|
-
|
|
36
|
-
| Intent | Tool | Why |
|
|
37
|
-
|--------|------|-----|
|
|
38
|
-
| Reading to understand code | \`file_read\` (unerr MCP) | Auto-injects conventions, facts, drift status |
|
|
39
|
-
| Reading immediately before Edit | Built-in \`Read\` with offset/limit | Required by Edit tool — \`file_read\` does NOT satisfy this. Use targeted reads (offset/limit) for only the lines you plan to edit. |
|
|
40
|
-
|
|
41
|
-
When your next action is Edit, use built-in Read with offset/limit on the target lines. For everything else, use \`file_read\`.
|
|
42
|
-
|
|
43
|
-
**Common failure mode:** Using \`file_read\` to understand a file, then attempting Edit without calling built-in Read first. The Edit tool WILL reject with "File has not been read yet". Always call built-in Read (with offset/limit) immediately before Edit.`
|
|
44
|
-
: "";
|
|
45
|
-
const summaryEditNote = isClaudeCode
|
|
46
|
-
? "\nNEVER use built-in Read/Grep/Glob for code navigation. EXCEPTION: built-in Read (with offset/limit) is REQUIRED immediately before Edit (file_read cannot substitute — Edit will fail without it)."
|
|
47
|
-
: "\nNEVER use built-in Read/Grep/Glob for code navigation — use unerr MCP tools instead.";
|
|
48
|
-
return `## REQUIRED: Use unerr Graph Intelligence Tools (22 MCP tools)
|
|
49
|
-
|
|
50
|
-
This project has unerr MCP tools installed. You MUST use these instead of built-in Read/Grep/Glob for code navigation. unerr tools are graph-backed, return results in <5ms, and include project context that built-in tools miss.
|
|
51
|
-
|
|
52
|
-
### Tool Routing (MANDATORY — match your goal before calling any tool)
|
|
53
|
-
|
|
54
|
-
| If you need to... | You MUST call | DO NOT use |
|
|
55
|
-
|---|---|---|
|
|
56
|
-
| Find a function, class, or type | \`search_code\` | Grep, Glob |
|
|
57
|
-
| Find callers or callees | \`get_references\` (direction: callers/callees) | Grep for function name |
|
|
58
|
-
| Read a file for understanding | \`file_read\` with \`purpose:'explore'\` (default, auto-injects conventions/facts) | Built-in Read/Grep/Glob |
|
|
59
|
-
${readForEditRow}
|
|
60
|
-
| Get file structure overview | \`file_outline\` | Reading the whole file |
|
|
61
|
-
| Get a specific function or class | \`get_entity\` or \`file_read\` with \`entity\` param | Reading entire file |
|
|
62
|
-
| Trace imports/dependencies | \`get_imports\` or \`get_references\` (direction: callees) | Manual import scanning |
|
|
63
|
-
| Find hotspots / high fan-in / blast-radius candidates | \`get_critical_nodes\` | \`get_entity\` (won't show ranked list), guessing |
|
|
64
|
-
| Run a shell command | Automatic — routed through shell intelligence | N/A |
|
|
65
|
-
|
|
66
|
-
### FORBIDDEN Patterns (these waste tokens and miss context)
|
|
67
|
-
|
|
68
|
-
- Reading an entire file to find one function -> use \`get_entity\` or \`file_read\` with \`entity\` param
|
|
69
|
-
- Grep for a function name to find callers -> use \`get_references\` (finds indirect refs too)
|
|
70
|
-
- Glob + Grep to search for code -> use \`search_code\` (indexes ALL entities, <5ms)
|
|
71
|
-
- Reading multiple files to understand conventions -> use \`get_conventions\`
|
|
72
|
-
- Guessing code style for new code -> use \`get_conventions\`
|
|
73
|
-
- Guessing which entity has the highest fan-in / is the biggest hotspot -> use \`get_critical_nodes\`
|
|
74
|
-
- Reading a full file when you only need a section -> use \`file_read\` with \`entity\` param or offset/limit${twoStepSection}
|
|
75
|
-
|
|
76
|
-
### Tool Reference
|
|
77
|
-
|
|
78
|
-
#### Graph Navigation (6 tools)
|
|
79
|
-
|
|
80
|
-
| Task | Tool | Replaces |
|
|
81
|
-
|------|------|----------|
|
|
82
|
-
| Find callers or callees | \`get_references\` (direction: callers/callees) | Grep for function name / manual import tracing |
|
|
83
|
-
| Search code entities | \`search_code\` | Glob + Grep across files |
|
|
84
|
-
| Get entity details | \`get_entity\` | Reading full file for one function/class |
|
|
85
|
-
| Get file summary | \`get_file\` | Reading entire file top-to-bottom |
|
|
86
|
-
| Trace imports | \`get_imports\` | Scanning import statements |
|
|
87
|
-
| Detect conventions | \`get_conventions\` | Guessing code style |
|
|
88
|
-
|
|
89
|
-
#### Structural Analysis (5 tools)
|
|
90
|
-
|
|
91
|
-
| Task | Tool | Replaces |
|
|
92
|
-
|------|------|----------|
|
|
93
|
-
| Find chokepoint entities | \`get_critical_nodes\` | Manually tracing callers across files |
|
|
94
|
-
| Find cross-module coupling | \`get_cross_boundary_links\` | Manually tracing imports across directories |
|
|
95
|
-
| Project overview stats | \`get_project_stats\` | Counting files / reading multiple files |
|
|
96
|
-
| File dependency neighborhood | \`file_connections\` | Scanning import statements across codebase |
|
|
97
|
-
| Find tests for an entity | \`get_test_coverage\` | Grepping for function names in test files |
|
|
98
|
-
|
|
99
|
-
#### File Protocol (2 tools)
|
|
100
|
-
|
|
101
|
-
| Task | Tool | Replaces |
|
|
102
|
-
|------|------|----------|
|
|
103
|
-
| File structure overview | \`file_outline\` | Reading entire large files |
|
|
104
|
-
| Read file with context | \`file_read\` | Built-in Read (misses conventions) |
|
|
105
|
-
|
|
106
|
-
\`file_read\` auto-injects relevant facts and conventions. For files >50 lines, call \`file_outline\` first, then \`file_read\` with \`entity\` param for targeted access.
|
|
107
|
-
|
|
108
|
-
**\`purpose\` parameter:** Controls read behavior — set it to match your intent:
|
|
109
|
-
- \`purpose:'explore'\` (default) — budget-capped, returns outline for large files. Use for browsing and pre-edit understanding.
|
|
110
|
-
- \`purpose:'reference'\` — tight budget, entity/offset reads only. Use for quick lookups.
|
|
111
|
-
|
|
112
|
-
#### Shell Compression (automatic)
|
|
113
|
-
|
|
114
|
-
All shell commands automatically route through unerr's compression layer. It strips ANSI codes, classifies output (diffs, test results, logs, errors), and returns compressed summaries — saving tokens without losing critical information.
|
|
115
|
-
|
|
116
|
-
#### Response Signal Prefix \`ur|<tag>\`
|
|
117
|
-
|
|
118
|
-
unerr tool responses may begin with one or more \`ur|<tag> <message>\` lines BEFORE the actual content. These are anti-drift signals injected directly in the body (because MCP \`_meta\` is filtered by clients before reaching you). Treat them as high-priority instructions and act on them before consuming the rest of the response.
|
|
119
|
-
|
|
120
|
-
The bare \`ur|\` prefix is deliberately short — it tokenizes to 1-2 BPE tokens, and the 3-char tag is another token. Total signal overhead: ~2-3 tokens.
|
|
121
|
-
|
|
122
|
-
| Tag | Meaning | What to do |
|
|
123
|
-
|---|---|---|
|
|
124
|
-
| \`hlt\` | halt — loop / circuit-break detected | Stop retrying this entity; switch approach |
|
|
125
|
-
| \`dft\` | drift — file or entity changed since last seen | Re-read with \`file_read\`/\`get_entity\` before editing |
|
|
126
|
-
| \`rsk\` | risk — high blast radius (many callers/callees) | Check callers via \`get_references\` before editing |
|
|
127
|
-
| \`wrn\` | warn — anti-pattern / negative fact | Avoid the listed failure mode |
|
|
128
|
-
| \`hnt\` | hint — guidance / co-change suggestion | Consider co-modifying the listed files |
|
|
129
|
-
| \`fct\` | fact — surfaced project fact (subtype in [brackets]: procedural, convention, semantic) | Use as session/project context |
|
|
130
|
-
| \`ctx\` | context already delivered for this entity | Do not re-query; use what was already returned |
|
|
131
|
-
| \`hth\` | health — session degraded | Consider starting a new session |
|
|
132
|
-
| \`hst\` | hist — prior failures on this entity | Read failure modes carefully before retrying |
|
|
133
|
-
| (no tag) | \`ur| <msg>\` generic nudge | Read the message |
|
|
134
|
-
|
|
135
|
-
Example:
|
|
136
|
-
\`\`\`
|
|
137
|
-
ur|rsk fan_in=24 fan_out=3 (high blast radius — get_references first)
|
|
138
|
-
ur|dft modified on main by intent-abc
|
|
139
|
-
|
|
140
|
-
{actual tool response data here…}
|
|
141
|
-
\`\`\`
|
|
142
|
-
|
|
143
|
-
When you see one of these prefixes, act on it. Do not strip or ignore them in your reasoning.
|
|
144
|
-
|
|
145
|
-
#### Pagination & Narrowing
|
|
146
|
-
|
|
147
|
-
unerr tool responses are universally capped (typical default 5-30 items per call) and may show this hint right after the prefix:
|
|
148
|
-
\`\`\`
|
|
149
|
-
ur| <tool>: N more available — pass limit:N or <filter>:V to narrow (use token_budget bump only for full payloads)
|
|
150
|
-
\`\`\`
|
|
151
|
-
**Prefer narrowing over budget bumps.** When you see the page hint:
|
|
152
|
-
- Pass a more specific filter — \`fact_type:negative\`, \`entity:<name>\`, \`kind:function\`, \`direction:callees\`. This returns the *missing* slice.
|
|
153
|
-
- Bump \`limit:N\` only if you genuinely need more items of the same kind.
|
|
154
|
-
- \`token_budget:N\` is a special-case escape hatch — use only when you must read a full payload (e.g., reading a complete function body to refactor it).
|
|
155
|
-
|
|
156
|
-
#### Response Body Formats
|
|
157
|
-
|
|
158
|
-
Three on-the-wire shapes; the body's first line tells you which:
|
|
159
|
-
- \`{...JSON...}\` — minified JSON (default for single objects)
|
|
160
|
-
- \`_fmt:columnar\` — pipe-delimited table; line 2 is the column header (\`col1|col2|...\`), rows below
|
|
161
|
-
- \`_fmt:multi\` — multi-section: \`@meta k=v|...\` for scalars, then \`@<arrayName>[col1|col2|...]\` for tabular sections, \`@<arrayName>[]\` for string lists. Used by \`file_outline\`, \`file_connections\`, \`get_conventions\`.
|
|
162
|
-
|
|
163
|
-
A cell is escaped if it contains \`|\` or \`"\` — wrapped in double quotes, internal quotes doubled. Newlines in cell values become literal \`\\n\`.
|
|
164
|
-
|
|
165
|
-
#### Persistent Intelligence (2 tools)
|
|
166
|
-
|
|
167
|
-
| Task | Tool |
|
|
168
|
-
|------|------|
|
|
169
|
-
| Record a project fact/convention/anti-pattern | \`record_fact\` |
|
|
170
|
-
| Recall stored facts | \`recall_facts\` |
|
|
171
|
-
|
|
172
|
-
When the user says "remember this" or states a convention/anti-pattern, call \`record_fact\`. Facts also auto-detect from coding sessions — conventions, hot files, file coupling, and modification history are learned automatically. Episodic facts capture what was built, why, and how — they surface as \`ur|fct\` prefix lines on \`file_read\` / \`recall_facts\` responses when you work on previously-modified files.
|
|
173
|
-
|
|
174
|
-
#### Session Narrative — Markers (4 tools)
|
|
175
|
-
|
|
176
|
-
| Task | Tool |
|
|
177
|
-
|------|------|
|
|
178
|
-
| Mark the start of a non-trivial task (one short sentence) | \`mark_intent\` |
|
|
179
|
-
| Record a deliberate choice between approaches | \`mark_decision\` |
|
|
180
|
-
| Flag an unresolved obstacle you hit this turn | \`mark_blocker\` |
|
|
181
|
-
| Resolve a previously marked blocker | \`mark_resolution\` (pass the marker_id from mark_blocker as \`blocker_ref\`) |
|
|
182
|
-
|
|
183
|
-
Emit markers inline as you work — NOT as an end-of-turn summary. Each is one short string; mark_intent ≤80 chars, the rest ≤140. Unresolved blockers carry into the next session's resume strip. Markers are persisted to the shadow ledger and timeline.db; they power turn titles, intent stitching, and loop/blocker mining. Optional: layer is useful without them, but agents that mark intent + decisions make the timeline dramatically more readable.
|
|
184
|
-
|
|
185
|
-
### When to fall back to built-in tools
|
|
186
|
-
|
|
187
|
-
ONLY use built-in Read/Grep/Glob when:
|
|
188
|
-
- The unerr MCP server is not responding
|
|
189
|
-
- You need to read a non-code file (images, binaries, PDFs)
|
|
190
|
-
- You need complex regex patterns that \`search_code\` doesn't support
|
|
191
|
-
|
|
192
|
-
### Summary (CRITICAL — read this even if you skimmed above)
|
|
193
|
-
|
|
194
|
-
ALWAYS use unerr MCP tools: \`search_code\`, \`get_references\`, \`file_read\`, \`file_outline\`, \`get_entity\`.${summaryEditNote}
|
|
195
|
-
Before writing code: \`get_conventions\`. To record decisions: \`record_fact\`.`;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Generate MDC-formatted instruction content for Cursor rules.
|
|
199
|
-
*/
|
|
200
|
-
function getMdcContent() {
|
|
201
|
-
return `---
|
|
202
|
-
description: REQUIRED — use unerr graph intelligence tools instead of built-in Read/Grep/Glob
|
|
203
|
-
alwaysApply: true
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
${getInstructionContent("cursor")}
|
|
207
|
-
`;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Write tool-preference instructions into the agent's instruction file.
|
|
211
|
-
* Idempotent: creates, updates, or skips based on current state.
|
|
212
|
-
*/
|
|
213
|
-
export function writeInstructionFile(cwd, ide) {
|
|
214
|
-
const agentDef = getAgent(ide);
|
|
215
|
-
if (!agentDef?.instructionFilePath) {
|
|
216
|
-
return { path: "", action: "skipped" };
|
|
217
|
-
}
|
|
218
|
-
const filePath = join(cwd, agentDef.instructionFilePath);
|
|
219
|
-
if (agentDef.instructionFormat === "mdc") {
|
|
220
|
-
return writeMdcInstructionFile(filePath);
|
|
221
|
-
}
|
|
222
|
-
if (agentDef.instructionFormat === "windsurf-rule") {
|
|
223
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
224
|
-
const content = getInstructionContent(ide);
|
|
225
|
-
// Windsurf enforces 6K char limit per rule
|
|
226
|
-
const truncatedContent = content.length > 5800
|
|
227
|
-
? `${content.slice(0, 5800)}\n\n[Truncated — full content exceeds Windsurf 6K limit]`
|
|
228
|
-
: content;
|
|
229
|
-
const windsurfContent = `---\ntrigger: always_on\ndescription: "Tool routing instructions for unerr MCP integration"\n---\n\n${truncatedContent}\n`;
|
|
230
|
-
const existed = existsSync(filePath);
|
|
231
|
-
if (existed) {
|
|
232
|
-
const existing = readFileSync(filePath, "utf-8");
|
|
233
|
-
if (existing === windsurfContent) {
|
|
234
|
-
return { path: filePath, action: "skipped" };
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
writeFileSync(filePath, windsurfContent, "utf-8");
|
|
238
|
-
return { path: filePath, action: existed ? "updated" : "created" };
|
|
239
|
-
}
|
|
240
|
-
if (agentDef.instructionFormat === "antigravity-rule") {
|
|
241
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
242
|
-
const content = getInstructionContent(ide);
|
|
243
|
-
const antigravityContent = `---\nname: unerr-instructions\ndescription: Tool routing instructions for unerr MCP integration\ntype: manual\n---\n\n${content}\n`;
|
|
244
|
-
const existed = existsSync(filePath);
|
|
245
|
-
if (existed) {
|
|
246
|
-
const existing = readFileSync(filePath, "utf-8");
|
|
247
|
-
if (existing === antigravityContent) {
|
|
248
|
-
return { path: filePath, action: "skipped" };
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
writeFileSync(filePath, antigravityContent, "utf-8");
|
|
252
|
-
return { path: filePath, action: existed ? "updated" : "created" };
|
|
253
|
-
}
|
|
254
|
-
// markdown format (CLAUDE.md, AGENTS.md, GEMINI.md, copilot-instructions.md, .clinerules)
|
|
255
|
-
return mergeMarkdownSection(filePath, getInstructionContent(ide));
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Merge a sentinel-wrapped section into a markdown file.
|
|
259
|
-
* - File doesn't exist → create with sentinel-wrapped content → "created"
|
|
260
|
-
* - File exists, no sentinel → append at end → "updated"
|
|
261
|
-
* - File exists, sentinel present, same content → "skipped"
|
|
262
|
-
* - File exists, sentinel present, different → replace block → "updated"
|
|
263
|
-
*/
|
|
264
|
-
function mergeMarkdownSection(filePath, content) {
|
|
265
|
-
const wrappedContent = `${SENTINEL_START}\n${content}\n${SENTINEL_END}`;
|
|
266
|
-
if (!existsSync(filePath)) {
|
|
267
|
-
// Create new file with sentinel-wrapped content
|
|
268
|
-
const dir = dirname(filePath);
|
|
269
|
-
if (!existsSync(dir))
|
|
270
|
-
mkdirSync(dir, { recursive: true });
|
|
271
|
-
writeFileSync(filePath, `${wrappedContent}\n`);
|
|
272
|
-
return { path: filePath, action: "created" };
|
|
273
|
-
}
|
|
274
|
-
const existing = readFileSync(filePath, "utf-8");
|
|
275
|
-
const startIdx = existing.indexOf(SENTINEL_START);
|
|
276
|
-
const endIdx = existing.indexOf(SENTINEL_END);
|
|
277
|
-
if (startIdx === -1 || endIdx === -1) {
|
|
278
|
-
// No sentinel markers — prepend at top (primacy bias: top-positioned instructions
|
|
279
|
-
// are followed 2-3x more often by LLMs than bottom-positioned ones)
|
|
280
|
-
writeFileSync(filePath, `${wrappedContent}\n\n${existing}`);
|
|
281
|
-
return { path: filePath, action: "updated" };
|
|
282
|
-
}
|
|
283
|
-
// Sentinel markers found — check if content is identical
|
|
284
|
-
const existingBlock = existing.slice(startIdx, endIdx + SENTINEL_END.length);
|
|
285
|
-
if (existingBlock === wrappedContent) {
|
|
286
|
-
return { path: filePath, action: "skipped" };
|
|
287
|
-
}
|
|
288
|
-
// Replace existing block
|
|
289
|
-
const before = existing.slice(0, startIdx);
|
|
290
|
-
const after = existing.slice(endIdx + SENTINEL_END.length);
|
|
291
|
-
writeFileSync(filePath, `${before}${wrappedContent}${after}`);
|
|
292
|
-
return { path: filePath, action: "updated" };
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Write a standalone .mdc instruction file for Cursor.
|
|
296
|
-
* Overwrites entire file (it's ours). Returns "created" or "skipped".
|
|
297
|
-
*/
|
|
298
|
-
function writeMdcInstructionFile(filePath) {
|
|
299
|
-
const content = getMdcContent();
|
|
300
|
-
const alreadyExists = existsSync(filePath);
|
|
301
|
-
if (alreadyExists) {
|
|
302
|
-
const existing = readFileSync(filePath, "utf-8");
|
|
303
|
-
if (existing === content) {
|
|
304
|
-
return { path: filePath, action: "skipped" };
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const dir = dirname(filePath);
|
|
308
|
-
if (!existsSync(dir))
|
|
309
|
-
mkdirSync(dir, { recursive: true });
|
|
310
|
-
writeFileSync(filePath, content);
|
|
311
|
-
return {
|
|
312
|
-
path: filePath,
|
|
313
|
-
action: alreadyExists ? "updated" : "created",
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Remove unerr instruction section from the agent's instruction file.
|
|
318
|
-
* For markdown: removes sentinel-wrapped block. For mdc: deletes the file.
|
|
319
|
-
*/
|
|
320
|
-
export function removeInstructionSection(cwd, ide) {
|
|
321
|
-
const agentDef = getAgent(ide);
|
|
322
|
-
if (!agentDef?.instructionFilePath)
|
|
323
|
-
return false;
|
|
324
|
-
const filePath = join(cwd, agentDef.instructionFilePath);
|
|
325
|
-
if (!existsSync(filePath))
|
|
326
|
-
return false;
|
|
327
|
-
if (agentDef.instructionFormat === "mdc") {
|
|
328
|
-
// Delete the entire file (it's ours)
|
|
329
|
-
unlinkSync(filePath);
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
if (agentDef.instructionFormat === "windsurf-rule") {
|
|
333
|
-
if (existsSync(filePath)) {
|
|
334
|
-
unlinkSync(filePath);
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
if (agentDef.instructionFormat === "antigravity-rule") {
|
|
340
|
-
if (existsSync(filePath)) {
|
|
341
|
-
unlinkSync(filePath);
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
// Markdown: remove sentinel block
|
|
347
|
-
const content = readFileSync(filePath, "utf-8");
|
|
348
|
-
const startIdx = content.indexOf(SENTINEL_START);
|
|
349
|
-
const endIdx = content.indexOf(SENTINEL_END);
|
|
350
|
-
if (startIdx === -1 || endIdx === -1)
|
|
351
|
-
return false;
|
|
352
|
-
const before = content.slice(0, startIdx);
|
|
353
|
-
const after = content.slice(endIdx + SENTINEL_END.length);
|
|
354
|
-
// Clean up extra blank lines left behind
|
|
355
|
-
const cleaned = `${(before + after).replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
356
|
-
// If nothing meaningful remains, delete the file only if we created it
|
|
357
|
-
const trimmed = cleaned.replace(/\s/g, "");
|
|
358
|
-
if (trimmed.length === 0) {
|
|
359
|
-
unlinkSync(filePath);
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
writeFileSync(filePath, cleaned);
|
|
363
|
-
}
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Generate formatted custom instructions text for --show-instructions output.
|
|
368
|
-
*/
|
|
369
|
-
export function generateCustomInstructions(ide) {
|
|
370
|
-
const content = getInstructionContent(ide);
|
|
371
|
-
if (ide && ide !== "other") {
|
|
372
|
-
const agentDef = getAgent(ide);
|
|
373
|
-
if (agentDef?.instructionFilePath) {
|
|
374
|
-
return [
|
|
375
|
-
`Add the following to ${agentDef.instructionFilePath}:`,
|
|
376
|
-
"",
|
|
377
|
-
content,
|
|
378
|
-
].join("\n");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
// Generic instructions for any agent
|
|
382
|
-
return [
|
|
383
|
-
"Add the following to your agent's instruction file",
|
|
384
|
-
"(CLAUDE.md, AGENTS.md, .cursorrules, GEMINI.md, etc.):",
|
|
385
|
-
"",
|
|
386
|
-
content,
|
|
387
|
-
].join("\n");
|
|
388
|
-
}
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Config Writer — generates per-agent MCP config with unerr server configuration.
|
|
3
|
-
*
|
|
4
|
-
* Registry-driven: uses agent-registry.ts to determine config path and format.
|
|
5
|
-
* Idempotent — never overwrites user customizations, only adds/merges unerr entry.
|
|
6
|
-
*
|
|
7
|
-
* Supported formats:
|
|
8
|
-
* - mcp-json: { mcpServers: { unerr: { ... } } }
|
|
9
|
-
* - settings-json: { "mcp": { "servers": { unerr: { ... } } } } (Gemini CLI)
|
|
10
|
-
* - continue-config: { mcpServers: [{ name: "unerr", ... }] } (Continue.dev)
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
|
-
import { dirname, join } from "node:path";
|
|
15
|
-
import { getAgent, } from "./agent-registry.js";
|
|
16
|
-
const UNERR_SERVER_KEY = "unerr";
|
|
17
|
-
function createUnerrServerEntry() {
|
|
18
|
-
return {
|
|
19
|
-
type: "stdio",
|
|
20
|
-
command: "unerr",
|
|
21
|
-
args: ["--mcp"],
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function createCopilotServerEntry() {
|
|
25
|
-
return {
|
|
26
|
-
type: "local",
|
|
27
|
-
command: "unerr",
|
|
28
|
-
args: ["--mcp"],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Write MCP config for a detected agent.
|
|
33
|
-
* Idempotent: if config exists with unerr entry, skips.
|
|
34
|
-
* If config exists without unerr entry, merges without overwriting.
|
|
35
|
-
*/
|
|
36
|
-
export function writeMcpConfig(cwd, ide) {
|
|
37
|
-
const agent = getAgent(ide);
|
|
38
|
-
if (!agent) {
|
|
39
|
-
return { path: "", action: "skipped" };
|
|
40
|
-
}
|
|
41
|
-
const configPath = agent.configScope === "global"
|
|
42
|
-
? join(homedir(), agent.projectConfigPath)
|
|
43
|
-
: join(cwd, agent.projectConfigPath);
|
|
44
|
-
const dir = dirname(configPath);
|
|
45
|
-
if (!existsSync(dir)) {
|
|
46
|
-
mkdirSync(dir, { recursive: true });
|
|
47
|
-
}
|
|
48
|
-
switch (agent.configFormat) {
|
|
49
|
-
case "mcp-json":
|
|
50
|
-
return writeMcpJsonFormat(configPath);
|
|
51
|
-
case "settings-json":
|
|
52
|
-
return writeSettingsJsonFormat(configPath);
|
|
53
|
-
case "copilot-json":
|
|
54
|
-
return writeCopilotJsonFormat(configPath);
|
|
55
|
-
case "continue-config":
|
|
56
|
-
return writeContinueFormat(configPath);
|
|
57
|
-
default:
|
|
58
|
-
return writeMcpJsonFormat(configPath);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Write config for ALL detected agents at once.
|
|
63
|
-
*/
|
|
64
|
-
export function writeAllMcpConfigs(cwd, agents) {
|
|
65
|
-
return agents.map((ide) => {
|
|
66
|
-
const result = writeMcpConfig(cwd, ide);
|
|
67
|
-
return { ide, ...result };
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Remove unerr entry from MCP config.
|
|
72
|
-
*/
|
|
73
|
-
export function removeMcpConfig(cwd, ide) {
|
|
74
|
-
const agent = getAgent(ide);
|
|
75
|
-
if (!agent)
|
|
76
|
-
return false;
|
|
77
|
-
const configPath = agent.configScope === "global"
|
|
78
|
-
? join(homedir(), agent.projectConfigPath)
|
|
79
|
-
: join(cwd, agent.projectConfigPath);
|
|
80
|
-
if (!existsSync(configPath))
|
|
81
|
-
return false;
|
|
82
|
-
try {
|
|
83
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
84
|
-
if (agent.configFormat === "continue-config") {
|
|
85
|
-
if (!Array.isArray(existing.mcpServers))
|
|
86
|
-
return false;
|
|
87
|
-
existing.mcpServers = existing.mcpServers.filter((s) => s.name !== UNERR_SERVER_KEY);
|
|
88
|
-
}
|
|
89
|
-
else if (agent.configFormat === "settings-json") {
|
|
90
|
-
if (!existing.mcp?.servers?.[UNERR_SERVER_KEY])
|
|
91
|
-
return false;
|
|
92
|
-
delete existing.mcp.servers[UNERR_SERVER_KEY];
|
|
93
|
-
}
|
|
94
|
-
else if (agent.configFormat === "copilot-json") {
|
|
95
|
-
if (!existing.mcpServers?.[UNERR_SERVER_KEY])
|
|
96
|
-
return false;
|
|
97
|
-
delete existing.mcpServers[UNERR_SERVER_KEY];
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
if (!existing.mcpServers?.[UNERR_SERVER_KEY])
|
|
101
|
-
return false;
|
|
102
|
-
delete existing.mcpServers[UNERR_SERVER_KEY];
|
|
103
|
-
}
|
|
104
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Check if unerr is already configured for an IDE.
|
|
113
|
-
*/
|
|
114
|
-
export function isConfigured(cwd, ide) {
|
|
115
|
-
const agent = getAgent(ide);
|
|
116
|
-
if (!agent)
|
|
117
|
-
return false;
|
|
118
|
-
const configPath = agent.configScope === "global"
|
|
119
|
-
? join(homedir(), agent.projectConfigPath)
|
|
120
|
-
: join(cwd, agent.projectConfigPath);
|
|
121
|
-
if (!existsSync(configPath))
|
|
122
|
-
return false;
|
|
123
|
-
try {
|
|
124
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
125
|
-
if (agent.configFormat === "continue-config") {
|
|
126
|
-
return (Array.isArray(existing.mcpServers) &&
|
|
127
|
-
existing.mcpServers.some((s) => s.name === UNERR_SERVER_KEY));
|
|
128
|
-
}
|
|
129
|
-
if (agent.configFormat === "settings-json") {
|
|
130
|
-
return !!existing.mcp?.servers?.[UNERR_SERVER_KEY];
|
|
131
|
-
}
|
|
132
|
-
if (agent.configFormat === "copilot-json") {
|
|
133
|
-
return !!existing.mcpServers?.[UNERR_SERVER_KEY];
|
|
134
|
-
}
|
|
135
|
-
return !!existing.mcpServers?.[UNERR_SERVER_KEY];
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Generate the MCP config JSON string for manual copy-paste.
|
|
143
|
-
* Useful for agents that can't be auto-configured.
|
|
144
|
-
*/
|
|
145
|
-
export function generateConfigSnippet(ide) {
|
|
146
|
-
const agent = getAgent(ide);
|
|
147
|
-
if (!agent)
|
|
148
|
-
return "";
|
|
149
|
-
const entry = createUnerrServerEntry();
|
|
150
|
-
switch (agent.configFormat) {
|
|
151
|
-
case "settings-json":
|
|
152
|
-
return JSON.stringify({ mcp: { servers: { [UNERR_SERVER_KEY]: entry } } }, null, 2);
|
|
153
|
-
case "copilot-json": {
|
|
154
|
-
const copilotEntry = createCopilotServerEntry();
|
|
155
|
-
return JSON.stringify({ mcpServers: { [UNERR_SERVER_KEY]: copilotEntry } }, null, 2);
|
|
156
|
-
}
|
|
157
|
-
case "continue-config":
|
|
158
|
-
return JSON.stringify({ mcpServers: [{ name: UNERR_SERVER_KEY, ...entry }] }, null, 2);
|
|
159
|
-
default:
|
|
160
|
-
return JSON.stringify({ mcpServers: { [UNERR_SERVER_KEY]: entry } }, null, 2);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Get config info for display purposes.
|
|
165
|
-
*/
|
|
166
|
-
export function getConfigInfo(ide) {
|
|
167
|
-
const agent = getAgent(ide);
|
|
168
|
-
if (!agent)
|
|
169
|
-
return null;
|
|
170
|
-
return {
|
|
171
|
-
path: agent.projectConfigPath,
|
|
172
|
-
format: agent.configFormat,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// ── Format-specific writers ─────────────────────────────────────
|
|
176
|
-
function writeMcpJsonFormat(configPath) {
|
|
177
|
-
if (existsSync(configPath)) {
|
|
178
|
-
try {
|
|
179
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
180
|
-
if (existing.mcpServers?.[UNERR_SERVER_KEY]) {
|
|
181
|
-
return { path: configPath, action: "skipped" };
|
|
182
|
-
}
|
|
183
|
-
existing.mcpServers = existing.mcpServers ?? {};
|
|
184
|
-
existing.mcpServers[UNERR_SERVER_KEY] = createUnerrServerEntry();
|
|
185
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
186
|
-
return { path: configPath, action: "updated" };
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
return { path: configPath, action: "skipped" };
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const config = {
|
|
193
|
-
mcpServers: { [UNERR_SERVER_KEY]: createUnerrServerEntry() },
|
|
194
|
-
};
|
|
195
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
196
|
-
return { path: configPath, action: "created" };
|
|
197
|
-
}
|
|
198
|
-
function writeSettingsJsonFormat(configPath) {
|
|
199
|
-
if (existsSync(configPath)) {
|
|
200
|
-
try {
|
|
201
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
202
|
-
const mcp = (existing.mcp ?? {});
|
|
203
|
-
const servers = (mcp.servers ?? {});
|
|
204
|
-
if (servers[UNERR_SERVER_KEY]) {
|
|
205
|
-
return { path: configPath, action: "skipped" };
|
|
206
|
-
}
|
|
207
|
-
servers[UNERR_SERVER_KEY] = createUnerrServerEntry();
|
|
208
|
-
mcp.servers = servers;
|
|
209
|
-
existing.mcp = mcp;
|
|
210
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
211
|
-
return { path: configPath, action: "updated" };
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
return { path: configPath, action: "skipped" };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const config = {
|
|
218
|
-
mcp: { servers: { [UNERR_SERVER_KEY]: createUnerrServerEntry() } },
|
|
219
|
-
};
|
|
220
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
221
|
-
return { path: configPath, action: "created" };
|
|
222
|
-
}
|
|
223
|
-
function writeContinueFormat(configPath) {
|
|
224
|
-
const entry = { name: UNERR_SERVER_KEY, ...createUnerrServerEntry() };
|
|
225
|
-
if (existsSync(configPath)) {
|
|
226
|
-
try {
|
|
227
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
228
|
-
const servers = (existing.mcpServers ?? []);
|
|
229
|
-
if (servers.some((s) => s.name === UNERR_SERVER_KEY)) {
|
|
230
|
-
return { path: configPath, action: "skipped" };
|
|
231
|
-
}
|
|
232
|
-
servers.push(entry);
|
|
233
|
-
existing.mcpServers = servers;
|
|
234
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
235
|
-
return { path: configPath, action: "updated" };
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return { path: configPath, action: "skipped" };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
const config = { mcpServers: [entry] };
|
|
242
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
243
|
-
return { path: configPath, action: "created" };
|
|
244
|
-
}
|
|
245
|
-
function writeCopilotJsonFormat(configPath) {
|
|
246
|
-
if (existsSync(configPath)) {
|
|
247
|
-
try {
|
|
248
|
-
const existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
249
|
-
if (existing.mcpServers?.[UNERR_SERVER_KEY]) {
|
|
250
|
-
return { path: configPath, action: "skipped" };
|
|
251
|
-
}
|
|
252
|
-
existing.mcpServers = existing.mcpServers ?? {};
|
|
253
|
-
existing.mcpServers[UNERR_SERVER_KEY] = createCopilotServerEntry();
|
|
254
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
255
|
-
return { path: configPath, action: "updated" };
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return { path: configPath, action: "skipped" };
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const config = {
|
|
262
|
-
mcpServers: { [UNERR_SERVER_KEY]: createCopilotServerEntry() },
|
|
263
|
-
};
|
|
264
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
265
|
-
return { path: configPath, action: "created" };
|
|
266
|
-
}
|