@unerr-ai/unerr 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +37236 -35793
  3. package/package.json +1 -1
  4. package/dist/behaviors/agent-llm-bridge.js +0 -166
  5. package/dist/behaviors/architecture-guard.js +0 -256
  6. package/dist/behaviors/auto-doc.js +0 -247
  7. package/dist/behaviors/cascade-guard.js +0 -289
  8. package/dist/behaviors/change-narrative.js +0 -270
  9. package/dist/behaviors/convention-drift.js +0 -290
  10. package/dist/behaviors/framework.js +0 -235
  11. package/dist/behaviors/guard-formatter.js +0 -44
  12. package/dist/behaviors/incomplete-work.js +0 -270
  13. package/dist/behaviors/loop-breaker.js +0 -300
  14. package/dist/behaviors/session-continuity.js +0 -208
  15. package/dist/commands/branches.js +0 -97
  16. package/dist/commands/check-commit.js +0 -225
  17. package/dist/commands/compress-output.js +0 -64
  18. package/dist/commands/config-verify.js +0 -243
  19. package/dist/commands/daemon.js +0 -905
  20. package/dist/commands/dashboard.js +0 -52
  21. package/dist/commands/debug.js +0 -200
  22. package/dist/commands/enrich.js +0 -184
  23. package/dist/commands/exec.js +0 -233
  24. package/dist/commands/gain.js +0 -156
  25. package/dist/commands/hook.js +0 -88
  26. package/dist/commands/index.js +0 -88
  27. package/dist/commands/init.js +0 -74
  28. package/dist/commands/install.js +0 -505
  29. package/dist/commands/learn.js +0 -116
  30. package/dist/commands/manifest.js +0 -193
  31. package/dist/commands/rewind.js +0 -103
  32. package/dist/commands/serve.js +0 -19
  33. package/dist/commands/setup-wizard.js +0 -414
  34. package/dist/commands/skills.js +0 -64
  35. package/dist/commands/stats.js +0 -20
  36. package/dist/commands/status.js +0 -654
  37. package/dist/commands/timeline.js +0 -139
  38. package/dist/commands/uninstall.js +0 -230
  39. package/dist/components/App.js +0 -109
  40. package/dist/components/Banner.js +0 -12
  41. package/dist/components/ConfirmPrompt.js +0 -25
  42. package/dist/components/DriftSummary.js +0 -23
  43. package/dist/components/GradeBadge.js +0 -15
  44. package/dist/components/HealthCard.js +0 -18
  45. package/dist/components/InkSpinner.js +0 -22
  46. package/dist/components/InputBox.js +0 -17
  47. package/dist/components/KeyValue.js +0 -13
  48. package/dist/components/MessageList.js +0 -14
  49. package/dist/components/ProgressBar.js +0 -26
  50. package/dist/components/Section.js +0 -16
  51. package/dist/components/SessionSummaryCard.js +0 -73
  52. package/dist/components/StartupDisplay.js +0 -24
  53. package/dist/components/StatusDashboard.js +0 -57
  54. package/dist/components/StatusLine.js +0 -8
  55. package/dist/components/StepLine.js +0 -22
  56. package/dist/components/Theme.js +0 -20
  57. package/dist/components/ToolProgress.js +0 -8
  58. package/dist/components/ViolationList.js +0 -21
  59. package/dist/components/render.js +0 -13
  60. package/dist/config/agent-registry.js +0 -237
  61. package/dist/config/claude-settings-hooks.js +0 -304
  62. package/dist/config/hook-installer.js +0 -65
  63. package/dist/config/instruction-writer.js +0 -388
  64. package/dist/config/mcp-config-writer.js +0 -266
  65. package/dist/config/settings.js +0 -174
  66. package/dist/config/tool-detector.js +0 -42
  67. package/dist/config/value-surfacing.js +0 -119
  68. package/dist/core/context-assembly.js +0 -108
  69. package/dist/core/conversation.js +0 -33
  70. package/dist/core/local-chat-provider.js +0 -475
  71. package/dist/core/provider-factory.js +0 -55
  72. package/dist/core/providers.js +0 -90
  73. package/dist/core/query-engine.js +0 -174
  74. package/dist/daemon/api.js +0 -312
  75. package/dist/daemon/autostart.js +0 -119
  76. package/dist/daemon/bootstrap.js +0 -39
  77. package/dist/daemon/client.js +0 -164
  78. package/dist/daemon/detect-ci.js +0 -81
  79. package/dist/daemon/platform-linux.js +0 -146
  80. package/dist/daemon/platform-macos.js +0 -134
  81. package/dist/daemon/platform-windows.js +0 -116
  82. package/dist/daemon/process-manager.js +0 -299
  83. package/dist/daemon/protocol.js +0 -23
  84. package/dist/daemon/registry.js +0 -270
  85. package/dist/daemon/settings-schema.js +0 -72
  86. package/dist/daemon/system-health.js +0 -134
  87. package/dist/daemon/version-checker.js +0 -262
  88. package/dist/daemon/warm-start.js +0 -223
  89. package/dist/entrypoints/cli.js +0 -1043
  90. package/dist/entrypoints/daemon.js +0 -380
  91. package/dist/entrypoints/repl.js +0 -147
  92. package/dist/hooks/adapters/claude-code.js +0 -90
  93. package/dist/hooks/adapters/cline.js +0 -100
  94. package/dist/hooks/adapters/cursor.js +0 -98
  95. package/dist/hooks/hook-dedup.js +0 -79
  96. package/dist/hooks/hook-runner.js +0 -113
  97. package/dist/hooks/navigation-hooks.js +0 -175
  98. package/dist/hooks/prompt-hooks.js +0 -63
  99. package/dist/hooks/shell-hooks.js +0 -47
  100. package/dist/ignore.js +0 -111
  101. package/dist/intelligence/approach-suggester.js +0 -61
  102. package/dist/intelligence/ast-extractor.js +0 -2615
  103. package/dist/intelligence/ast-worker.js +0 -34
  104. package/dist/intelligence/background-indexer.js +0 -121
  105. package/dist/intelligence/blast-radius.js +0 -200
  106. package/dist/intelligence/community-detection.js +0 -691
  107. package/dist/intelligence/community-detector.js +0 -184
  108. package/dist/intelligence/computation-scheduler.js +0 -75
  109. package/dist/intelligence/confidence-propagation.js +0 -47
  110. package/dist/intelligence/convention-detector.js +0 -242
  111. package/dist/intelligence/convention-learner.js +0 -205
  112. package/dist/intelligence/convention-matcher.js +0 -205
  113. package/dist/intelligence/cozo-schema.js +0 -376
  114. package/dist/intelligence/decision-point-detector.js +0 -90
  115. package/dist/intelligence/deep-dive-tools.js +0 -586
  116. package/dist/intelligence/durability-scorer.js +0 -84
  117. package/dist/intelligence/exploration-cost.js +0 -204
  118. package/dist/intelligence/exploration-pattern-tracker.js +0 -61
  119. package/dist/intelligence/fact-generator.js +0 -322
  120. package/dist/intelligence/facts-schema.js +0 -90
  121. package/dist/intelligence/file-intelligence.js +0 -59
  122. package/dist/intelligence/graph-holder.js +0 -220
  123. package/dist/intelligence/graph-temporal-joiner.js +0 -238
  124. package/dist/intelligence/health-grade.js +0 -423
  125. package/dist/intelligence/health-grader.js +0 -200
  126. package/dist/intelligence/health-map-data.js +0 -259
  127. package/dist/intelligence/import-symbols.js +0 -136
  128. package/dist/intelligence/incremental-indexer.js +0 -658
  129. package/dist/intelligence/indexer/centrality.js +0 -62
  130. package/dist/intelligence/indexer/cfg-context.js +0 -95
  131. package/dist/intelligence/indexer/confidence.js +0 -34
  132. package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
  133. package/dist/intelligence/indexer/edge-repair.js +0 -89
  134. package/dist/intelligence/indexer/entity-key.js +0 -17
  135. package/dist/intelligence/indexer/export-map.js +0 -132
  136. package/dist/intelligence/indexer/git-cochange.js +0 -128
  137. package/dist/intelligence/indexer/graph-patch.js +0 -147
  138. package/dist/intelligence/indexer/incremental.js +0 -78
  139. package/dist/intelligence/indexer/ingest.js +0 -160
  140. package/dist/intelligence/indexer/language-detect.js +0 -226
  141. package/dist/intelligence/indexer/metadata.js +0 -63
  142. package/dist/intelligence/indexer/mutation-tracker.js +0 -79
  143. package/dist/intelligence/indexer/orchestrator.js +0 -155
  144. package/dist/intelligence/indexer/plugin-interface.js +0 -31
  145. package/dist/intelligence/indexer/plugins/csharp.js +0 -440
  146. package/dist/intelligence/indexer/plugins/go.js +0 -335
  147. package/dist/intelligence/indexer/plugins/java.js +0 -370
  148. package/dist/intelligence/indexer/plugins/python.js +0 -358
  149. package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
  150. package/dist/intelligence/indexer/plugins/ruby.js +0 -290
  151. package/dist/intelligence/indexer/plugins/rust.js +0 -484
  152. package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
  153. package/dist/intelligence/indexer/plugins/typescript.js +0 -456
  154. package/dist/intelligence/indexer/resource-monitor.js +0 -93
  155. package/dist/intelligence/indexer/scip/decoder.js +0 -253
  156. package/dist/intelligence/indexer/scip/detector.js +0 -232
  157. package/dist/intelligence/indexer/scip/downloader.js +0 -427
  158. package/dist/intelligence/indexer/scip/fallback.js +0 -34
  159. package/dist/intelligence/indexer/scip/merger.js +0 -109
  160. package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
  161. package/dist/intelligence/indexer/scip/runner.js +0 -98
  162. package/dist/intelligence/indexer/snapshot.js +0 -66
  163. package/dist/intelligence/indexer/test-detector.js +0 -196
  164. package/dist/intelligence/indexer/watch-integration.js +0 -61
  165. package/dist/intelligence/indexer/worker.js +0 -85
  166. package/dist/intelligence/local-convention-detector.js +0 -437
  167. package/dist/intelligence/local-embeddings.js +0 -190
  168. package/dist/intelligence/local-graph.js +0 -1946
  169. package/dist/intelligence/local-indexer.js +0 -1575
  170. package/dist/intelligence/local-llm.js +0 -163
  171. package/dist/intelligence/local-rule-generator.js +0 -154
  172. package/dist/intelligence/local-snapshot.js +0 -213
  173. package/dist/intelligence/negative-knowledge.js +0 -103
  174. package/dist/intelligence/persistent-db.js +0 -85
  175. package/dist/intelligence/query-router.js +0 -2556
  176. package/dist/intelligence/risk-classifier.js +0 -116
  177. package/dist/intelligence/rule-evaluator.js +0 -380
  178. package/dist/intelligence/rule-generator.js +0 -49
  179. package/dist/intelligence/search-index.js +0 -173
  180. package/dist/intelligence/semantic/docstring-extractor.js +0 -67
  181. package/dist/intelligence/semantic/embedding-store.js +0 -52
  182. package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
  183. package/dist/intelligence/semantic/git-message-miner.js +0 -114
  184. package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
  185. package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
  186. package/dist/intelligence/semantic/node2vec-walks.js +0 -103
  187. package/dist/intelligence/semantic/path-domain-inference.js +0 -112
  188. package/dist/intelligence/semantic/similarity-engine.js +0 -60
  189. package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
  190. package/dist/intelligence/session-brief-builder.js +0 -159
  191. package/dist/intelligence/session-context.js +0 -221
  192. package/dist/intelligence/session-health-monitor.js +0 -211
  193. package/dist/intelligence/session-narrative.js +0 -197
  194. package/dist/intelligence/session-pattern-analyzer.js +0 -218
  195. package/dist/intelligence/signal-scorer.js +0 -390
  196. package/dist/intelligence/signal-show-store.js +0 -182
  197. package/dist/intelligence/smart-truncate.js +0 -158
  198. package/dist/intelligence/subgraph-cache.js +0 -88
  199. package/dist/intelligence/temporal-facts.js +0 -494
  200. package/dist/intelligence/token-estimator.js +0 -100
  201. package/dist/intelligence/tool-injector.js +0 -87
  202. package/dist/intelligence/tree-sitter-loader.js +0 -71
  203. package/dist/intelligence/worker-pool.js +0 -116
  204. package/dist/proxy/arg-validator.js +0 -79
  205. package/dist/proxy/auto-bootstrap.js +0 -167
  206. package/dist/proxy/bridge.js +0 -147
  207. package/dist/proxy/budget-enforcer.js +0 -70
  208. package/dist/proxy/compression-quality-monitor.js +0 -160
  209. package/dist/proxy/compression-stats.js +0 -51
  210. package/dist/proxy/context-rot-detector.js +0 -137
  211. package/dist/proxy/drift-detector.js +0 -139
  212. package/dist/proxy/efficiency-tracker.js +0 -79
  213. package/dist/proxy/fact-ranking.js +0 -154
  214. package/dist/proxy/format-encoder.js +0 -266
  215. package/dist/proxy/http-transport.js +0 -90
  216. package/dist/proxy/lifecycle-actor.js +0 -55
  217. package/dist/proxy/lifecycle-machine.js +0 -187
  218. package/dist/proxy/log-tailer.js +0 -265
  219. package/dist/proxy/model-pricing.js +0 -98
  220. package/dist/proxy/network-firewall.js +0 -141
  221. package/dist/proxy/nudge-state.js +0 -93
  222. package/dist/proxy/output-compressor.js +0 -185
  223. package/dist/proxy/pid-lock.js +0 -291
  224. package/dist/proxy/proxy-context.js +0 -11
  225. package/dist/proxy/proxy.js +0 -2633
  226. package/dist/proxy/response-enrichment.js +0 -32
  227. package/dist/proxy/response-envelope.js +0 -313
  228. package/dist/proxy/session-dedup.js +0 -82
  229. package/dist/proxy/session-legend.js +0 -30
  230. package/dist/proxy/session-persistence.js +0 -210
  231. package/dist/proxy/session-resume.js +0 -94
  232. package/dist/proxy/session-stats.js +0 -513
  233. package/dist/proxy/shell-classifier.js +0 -1346
  234. package/dist/proxy/shell-compression-log.js +0 -93
  235. package/dist/proxy/shell-compressor.js +0 -390
  236. package/dist/proxy/shell-graph-boost.js +0 -202
  237. package/dist/proxy/shell-monitor-map.js +0 -18
  238. package/dist/proxy/shell-stats.js +0 -54
  239. package/dist/proxy/shell-strategies/cloud.js +0 -215
  240. package/dist/proxy/shell-strategies/diff.js +0 -159
  241. package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
  242. package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
  243. package/dist/proxy/shell-strategies/git-status.js +0 -177
  244. package/dist/proxy/shell-strategies/key-value.js +0 -193
  245. package/dist/proxy/shell-strategies/log-text.js +0 -154
  246. package/dist/proxy/shell-strategies/omni.js +0 -188
  247. package/dist/proxy/shell-strategies/progress.js +0 -55
  248. package/dist/proxy/shell-strategies/redact.js +0 -76
  249. package/dist/proxy/shell-strategies/structured.js +0 -241
  250. package/dist/proxy/shell-strategies/tabular.js +0 -243
  251. package/dist/proxy/shell-strategies/test-results-types.js +0 -13
  252. package/dist/proxy/shell-strategies/test-results.js +0 -784
  253. package/dist/proxy/shell-strategies/tree-paths.js +0 -144
  254. package/dist/proxy/shell-strategies/yaml.js +0 -182
  255. package/dist/proxy/shell-tee.js +0 -111
  256. package/dist/proxy/signal-dedup.js +0 -171
  257. package/dist/proxy/startup-renderer.js +0 -158
  258. package/dist/proxy/task-token-display.js +0 -38
  259. package/dist/proxy/token-counter.js +0 -61
  260. package/dist/proxy/tool-clusters.js +0 -273
  261. package/dist/proxy/tool-definitions.js +0 -525
  262. package/dist/proxy/transport-mux.js +0 -229
  263. package/dist/proxy/wire-cap.js +0 -268
  264. package/dist/rules/developer.mozilla.org.json +0 -9
  265. package/dist/rules/github.com.json +0 -21
  266. package/dist/schemas/api/skills.js +0 -19
  267. package/dist/schemas/common/errors.js +0 -7
  268. package/dist/schemas/common/headers.js +0 -5
  269. package/dist/schemas/entities/edge.js +0 -25
  270. package/dist/schemas/entities/entity.js +0 -22
  271. package/dist/schemas/entities/rule.js +0 -18
  272. package/dist/schemas/index.js +0 -14
  273. package/dist/server/event-bus.js +0 -59
  274. package/dist/server/http.js +0 -156
  275. package/dist/server/middleware.js +0 -70
  276. package/dist/server/routes/drift.js +0 -97
  277. package/dist/server/routes/intelligence.js +0 -1217
  278. package/dist/server/routes/reasoning-quality.js +0 -444
  279. package/dist/server/routes/session.js +0 -86
  280. package/dist/server/routes/stream.js +0 -120
  281. package/dist/server/routes/system.js +0 -73
  282. package/dist/server/routes/temporal.js +0 -170
  283. package/dist/server/routes/timeline.js +0 -232
  284. package/dist/server/routes/token-flow.js +0 -403
  285. package/dist/skills/effectiveness-tracker.js +0 -93
  286. package/dist/skills/local-pack.js +0 -380
  287. package/dist/skills/resolver.js +0 -495
  288. package/dist/state-detector.js +0 -83
  289. package/dist/timeline/intent-detector.js +0 -263
  290. package/dist/timeline/loop-miner.js +0 -140
  291. package/dist/timeline/open-threads.js +0 -49
  292. package/dist/timeline/signal-reinforcer.js +0 -62
  293. package/dist/timeline/timeline-bootstrap.js +0 -151
  294. package/dist/timeline/timeline-store.js +0 -618
  295. package/dist/tools/coding/bash.js +0 -49
  296. package/dist/tools/coding/file-edit.js +0 -72
  297. package/dist/tools/coding/file-outline.js +0 -227
  298. package/dist/tools/coding/file-read-protocol.js +0 -425
  299. package/dist/tools/coding/file-read.js +0 -35
  300. package/dist/tools/coding/file-write.js +0 -43
  301. package/dist/tools/coding/glob-tool.js +0 -109
  302. package/dist/tools/coding/grep.js +0 -162
  303. package/dist/tools/coding/index.js +0 -27
  304. package/dist/tools/intelligence/index.js +0 -269
  305. package/dist/tools/intelligence/record-fact.js +0 -48
  306. package/dist/tools/intelligence/timeline-markers.js +0 -130
  307. package/dist/tools/registry.js +0 -47
  308. package/dist/tools/types.js +0 -8
  309. package/dist/tracking/auto-snapshot-triggers.js +0 -246
  310. package/dist/tracking/branch-context.js +0 -115
  311. package/dist/tracking/branch-snapshot.js +0 -217
  312. package/dist/tracking/causal-bridge.js +0 -317
  313. package/dist/tracking/circuit-breaker.js +0 -147
  314. package/dist/tracking/commit-watcher.js +0 -114
  315. package/dist/tracking/context-ledger.js +0 -119
  316. package/dist/tracking/correction-detector.js +0 -324
  317. package/dist/tracking/drift-tracker.js +0 -874
  318. package/dist/tracking/durability-tracker.js +0 -94
  319. package/dist/tracking/entity-rewind.js +0 -200
  320. package/dist/tracking/file-hash-state.js +0 -114
  321. package/dist/tracking/git-attribution.js +0 -132
  322. package/dist/tracking/git-trailers.js +0 -171
  323. package/dist/tracking/intelligence-counter.js +0 -46
  324. package/dist/tracking/intent-correlator.js +0 -202
  325. package/dist/tracking/intent-encoder.js +0 -52
  326. package/dist/tracking/intent-token-tracker.js +0 -159
  327. package/dist/tracking/ledger-archiver.js +0 -94
  328. package/dist/tracking/ledger-chains.js +0 -245
  329. package/dist/tracking/metrics-store.js +0 -361
  330. package/dist/tracking/native-watcher.js +0 -131
  331. package/dist/tracking/offline-rewind.js +0 -295
  332. package/dist/tracking/pending-violations.js +0 -74
  333. package/dist/tracking/persistence-effectiveness.js +0 -167
  334. package/dist/tracking/prompt-durability.js +0 -202
  335. package/dist/tracking/quality-signals.js +0 -213
  336. package/dist/tracking/redactor.js +0 -73
  337. package/dist/tracking/rewind-engine.js +0 -161
  338. package/dist/tracking/session-history.js +0 -128
  339. package/dist/tracking/session-receipt.js +0 -88
  340. package/dist/tracking/session-summary-writer.js +0 -157
  341. package/dist/tracking/shadow-ledger.js +0 -321
  342. package/dist/tracking/stash-manager.js +0 -258
  343. package/dist/tracking/timeline-fork.js +0 -213
  344. package/dist/tracking/timeline.js +0 -69
  345. package/dist/tracking/token-flow.js +0 -276
  346. package/dist/tracking/turn-segmenter.js +0 -122
  347. package/dist/tracking/weekly-accumulator.js +0 -179
  348. package/dist/tracking/working-snapshots.js +0 -188
  349. package/dist/tracking/workspace-manifest.js +0 -176
  350. package/dist/transport/http.js +0 -102
  351. package/dist/utils/counterfactual.js +0 -65
  352. package/dist/utils/deep-link.js +0 -34
  353. package/dist/utils/detect.js +0 -193
  354. package/dist/utils/exec.js +0 -73
  355. package/dist/utils/file-logger.js +0 -87
  356. package/dist/utils/format-error.js +0 -29
  357. package/dist/utils/git.js +0 -181
  358. package/dist/utils/log.js +0 -57
  359. package/dist/utils/logger.js +0 -35
  360. package/dist/utils/mcp-content-json.js +0 -8
  361. package/dist/utils/session-logger.js +0 -154
  362. package/dist/utils/startup-log.js +0 -512
  363. package/dist/utils/ui.js +0 -56
@@ -1,1043 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * unerr — Local-first code intelligence CLI.
4
- *
5
- * Boot State Machine:
6
- * unerr — THE command. First-run: wizard → index → serve. Subsequent: resume → serve.
7
- * unerr chat — Interactive AI assistant (Ink REPL)
8
- * unerr status — Quick diagnostic dump
9
- * unerr debug — Full diagnostic for support
10
- *
11
- * All other commands are hidden but remain callable for power users and scripts.
12
- */
13
- import { existsSync, readFileSync } from "node:fs";
14
- import { join } from "node:path";
15
- import { Command } from "commander";
16
- import { registerBranchesCommand } from "../commands/branches.js";
17
- import { registerCheckCommitCommand } from "../commands/check-commit.js";
18
- import { registerCompressOutputCommand } from "../commands/compress-output.js";
19
- import { registerConfigVerifyCommand } from "../commands/config-verify.js";
20
- import { registerDaemonCommand } from "../commands/daemon.js";
21
- import { registerDashboardCommand } from "../commands/dashboard.js";
22
- import { registerDebugCommand } from "../commands/debug.js";
23
- import { registerEnrichCommand } from "../commands/enrich.js";
24
- import { registerExecCommand } from "../commands/exec.js";
25
- import { registerDiscoverCommand, registerGainCommand, } from "../commands/gain.js";
26
- import { registerHookCommand } from "../commands/hook.js";
27
- import { registerIndexCommand } from "../commands/index.js";
28
- import { registerInitCommand } from "../commands/init.js";
29
- import { registerInstallCommand } from "../commands/install.js";
30
- import { registerLearnCommand } from "../commands/learn.js";
31
- import { registerManifestCommand } from "../commands/manifest.js";
32
- import { registerRewindCommand } from "../commands/rewind.js";
33
- import { registerSkillsCommand } from "../commands/skills.js";
34
- import { registerStatsCommand } from "../commands/stats.js";
35
- import { registerStatusCommand } from "../commands/status.js";
36
- import { registerTimelineCommand } from "../commands/timeline.js";
37
- import { registerUninstallCommand } from "../commands/uninstall.js";
38
- import { installFileLogger } from "../utils/file-logger.js";
39
- import { initFileLog } from "../utils/startup-log.js";
40
- // ── Helpers ─────────────────────────────────────────────────
41
- /**
42
- * Start the unified proxy.
43
- */
44
- async function startProxy(repoId) {
45
- const { startProxy: boot } = await import("../proxy/proxy.js");
46
- const httpPort = Number.parseInt(process.env.UNERR_HTTP_PORT ?? "0", 10);
47
- await boot({ repoId, httpPort: httpPort || undefined });
48
- }
49
- /**
50
- * Auto-verify and repair IDE MCP configs before proxy start.
51
- * Silently fixes stale MCP configs → local proxy format.
52
- */
53
- async function autoVerifyIdeConfigs() {
54
- try {
55
- const { checkIdeConfig, repairIdeConfig } = await import("../commands/config-verify.js");
56
- const os = await import("node:os");
57
- const path = await import("node:path");
58
- const ideConfigs = {
59
- cursor: path.join(os.homedir(), ".cursor", "mcp.json"),
60
- vscode: path.join(os.homedir(), ".vscode", "settings.json"),
61
- windsurf: path.join(os.homedir(), ".windsurf", "mcp.json"),
62
- };
63
- for (const [ideName, configPath] of Object.entries(ideConfigs)) {
64
- const result = checkIdeConfig(ideName, configPath);
65
- if (result.found && result.needsMigration) {
66
- const repaired = repairIdeConfig(ideName, configPath);
67
- if (repaired) {
68
- process.stderr.write(`[unerr] Auto-repaired ${ideName} MCP config (remote URL → local proxy)\n`);
69
- }
70
- }
71
- }
72
- }
73
- catch {
74
- // Non-blocking — config verification should never prevent proxy startup
75
- }
76
- }
77
- /**
78
- * Check if we're inside a git repository.
79
- */
80
- async function isInsideGitRepo() {
81
- const { isGitRepo } = await import("../utils/git.js");
82
- return isGitRepo(process.cwd());
83
- }
84
- // ── Project Root Detection (Scored Multi-Signal Analysis) ─────
85
- /**
86
- * TIER 1: Definitive project markers — files whose presence at root
87
- * guarantees this is a project directory. Grouped by ecosystem.
88
- */
89
- const DEFINITIVE_MARKERS = [
90
- // JavaScript / TypeScript
91
- "package.json",
92
- "tsconfig.json",
93
- "jsconfig.json",
94
- "deno.json",
95
- "deno.jsonc",
96
- "bun.lockb",
97
- "bunfig.toml",
98
- // Python
99
- "pyproject.toml",
100
- "setup.py",
101
- "setup.cfg",
102
- "Pipfile",
103
- "hatch.toml",
104
- // Rust
105
- "Cargo.toml",
106
- // Go
107
- "go.mod",
108
- // Java / Kotlin / Gradle
109
- "pom.xml",
110
- "build.gradle",
111
- "build.gradle.kts",
112
- "settings.gradle",
113
- "settings.gradle.kts",
114
- // C# / .NET
115
- "Directory.Build.props",
116
- "global.json",
117
- "nuget.config",
118
- // Ruby
119
- "Gemfile",
120
- "Rakefile",
121
- // PHP
122
- "composer.json",
123
- // Swift / Obj-C / Apple
124
- "Package.swift",
125
- "Podfile",
126
- // C / C++
127
- "CMakeLists.txt",
128
- "Makefile",
129
- "configure.ac",
130
- "meson.build",
131
- "conanfile.txt",
132
- "conanfile.py",
133
- "vcpkg.json",
134
- // Dart / Flutter
135
- "pubspec.yaml",
136
- // Elixir
137
- "mix.exs",
138
- // Scala
139
- "build.sbt",
140
- "build.sc",
141
- // Haskell
142
- "stack.yaml",
143
- "cabal.project",
144
- // Clojure
145
- "project.clj",
146
- "deps.edn",
147
- "shadow-cljs.edn",
148
- // Zig
149
- "build.zig",
150
- "build.zig.zon",
151
- // Julia
152
- "Project.toml",
153
- // Terraform / IaC
154
- "main.tf",
155
- "terraform.tf",
156
- "pulumi.yaml",
157
- "serverless.yml",
158
- "cdk.json",
159
- "sam.yaml",
160
- // Containers
161
- "Dockerfile",
162
- "docker-compose.yml",
163
- "docker-compose.yaml",
164
- // Monorepo
165
- "lerna.json",
166
- "nx.json",
167
- "turbo.json",
168
- "pnpm-workspace.yaml",
169
- "rush.json",
170
- "pants.toml",
171
- // Bazel
172
- "BUILD.bazel",
173
- "WORKSPACE",
174
- "WORKSPACE.bazel",
175
- // General build / task
176
- "Justfile",
177
- "Taskfile.yml",
178
- // Nix
179
- "flake.nix",
180
- "shell.nix",
181
- "default.nix",
182
- // Android
183
- "AndroidManifest.xml",
184
- ];
185
- /**
186
- * TIER 2: Strong signals — VCS, IDE, CI/CD, lock files, editor configs.
187
- * Not definitive alone (a stray .gitignore doesn't make a project) but
188
- * contribute significant score.
189
- */
190
- const STRONG_SIGNAL_FILES = [
191
- // Lock files (imply a package manager was run here)
192
- "package-lock.json",
193
- "yarn.lock",
194
- "pnpm-lock.yaml",
195
- "Pipfile.lock",
196
- "poetry.lock",
197
- "Gemfile.lock",
198
- "composer.lock",
199
- "Cargo.lock",
200
- "flake.lock",
201
- "pubspec.lock",
202
- "mix.lock",
203
- "go.sum",
204
- "uv.lock",
205
- "pdm.lock",
206
- // CI/CD (a CI config strongly implies project root)
207
- ".gitlab-ci.yml",
208
- "Jenkinsfile",
209
- ".travis.yml",
210
- "azure-pipelines.yml",
211
- "bitbucket-pipelines.yml",
212
- // Editor / LSP configs (placed at project root)
213
- ".editorconfig",
214
- ".prettierrc",
215
- ".prettierrc.json",
216
- ".eslintrc",
217
- ".eslintrc.json",
218
- ".eslintrc.js",
219
- "biome.json",
220
- "biome.jsonc",
221
- "pyrightconfig.json",
222
- "rust-toolchain.toml",
223
- ".clang-format",
224
- ".clangd",
225
- "compile_commands.json",
226
- ".luarc.json",
227
- "stylua.toml",
228
- ".rubocop.yml",
229
- ".php-cs-fixer.php",
230
- // Environment
231
- ".env",
232
- ".envrc",
233
- // Git-specific (at root level these are strong signals)
234
- ".gitignore",
235
- ".gitattributes",
236
- ];
237
- /** TIER 2: Directories that are strong signals when present at root. */
238
- const STRONG_SIGNAL_DIRS = [
239
- ".git",
240
- ".svn",
241
- ".hg",
242
- ".fossil",
243
- ".bzr",
244
- "_darcs", // VCS
245
- ".github",
246
- ".circleci",
247
- ".buildkite", // CI/CD
248
- ".idea",
249
- ".vscode",
250
- ".vs",
251
- ".fleet",
252
- ".zed", // IDE
253
- ];
254
- /** Glob patterns for file-extension markers (e.g., *.sln, *.csproj). */
255
- const EXTENSION_MARKERS = [
256
- ".sln",
257
- ".csproj",
258
- ".fsproj",
259
- ".vbproj", // .NET
260
- ".xcodeproj",
261
- ".xcworkspace", // Xcode (dirs)
262
- ".cabal", // Haskell
263
- ".nimble", // Nim
264
- ".gemspec", // Ruby
265
- ".rockspec", // Lua
266
- ];
267
- /**
268
- * TIER 3: Code file extensions — comprehensive across all mainstream
269
- * and emerging languages. Used to scan root + source dirs.
270
- */
271
- const CODE_EXTENSIONS = new Set([
272
- // Web / JS ecosystem
273
- ".ts",
274
- ".tsx",
275
- ".js",
276
- ".jsx",
277
- ".mjs",
278
- ".cjs",
279
- ".vue",
280
- ".svelte",
281
- ".astro",
282
- // Systems
283
- ".rs",
284
- ".go",
285
- ".c",
286
- ".h",
287
- ".cpp",
288
- ".cc",
289
- ".cxx",
290
- ".hpp",
291
- ".hxx",
292
- ".zig",
293
- // JVM
294
- ".java",
295
- ".kt",
296
- ".kts",
297
- ".scala",
298
- ".sc",
299
- ".clj",
300
- ".cljs",
301
- ".cljc",
302
- // .NET
303
- ".cs",
304
- ".fs",
305
- ".fsx",
306
- ".vb",
307
- // Scripting
308
- ".py",
309
- ".pyi",
310
- ".rb",
311
- ".php",
312
- ".lua",
313
- ".pl",
314
- ".pm",
315
- ".raku",
316
- // Apple / Mobile
317
- ".swift",
318
- ".m",
319
- ".mm",
320
- ".dart",
321
- // Functional
322
- ".hs",
323
- ".lhs",
324
- ".ml",
325
- ".mli",
326
- ".re",
327
- ".rei",
328
- ".elm",
329
- ".ex",
330
- ".exs",
331
- ".purs",
332
- ".idr",
333
- ".agda",
334
- ".lean",
335
- // Data / Science
336
- ".r",
337
- ".R",
338
- ".jl",
339
- // Infrastructure
340
- ".tf",
341
- ".hcl",
342
- // Emerging / Niche
343
- ".nim",
344
- ".cr",
345
- ".d",
346
- ".v",
347
- ".sol",
348
- ".move",
349
- ".cairo",
350
- ".nr",
351
- // Shell / Config as code
352
- ".sh",
353
- ".bash",
354
- ".zsh",
355
- ".fish",
356
- ".ps1",
357
- ]);
358
- /**
359
- * TIER 4: Anti-signals — files/dirs that indicate this is a home dir,
360
- * system path, or otherwise NOT a project root. Each match subtracts score.
361
- */
362
- const ANTI_SIGNAL_FILES = [
363
- ".bashrc",
364
- ".zshrc",
365
- ".bash_profile",
366
- ".profile",
367
- ".zprofile",
368
- ".zshenv",
369
- ".bash_history",
370
- ".zsh_history",
371
- ];
372
- const ANTI_SIGNAL_DIRS = [
373
- ".config",
374
- ".local",
375
- ".cache",
376
- ".ssh",
377
- ".gnupg",
378
- ".npm",
379
- ".cargo",
380
- "Desktop",
381
- "Downloads",
382
- "Documents",
383
- "Pictures",
384
- "Music",
385
- "Movies",
386
- "Library",
387
- "Applications",
388
- ];
389
- const ANTI_SIGNAL_PATHS = [
390
- "/",
391
- "/usr",
392
- "/tmp",
393
- "/var",
394
- "/etc",
395
- "/opt",
396
- "/home",
397
- "/Users",
398
- "/root",
399
- ];
400
- /**
401
- * TIER 5: Well-known source directories — if these exist at root AND
402
- * contain code files, that's a strong project signal.
403
- */
404
- const SOURCE_DIRS = new Set([
405
- "src",
406
- "lib",
407
- "app",
408
- "apps",
409
- "cmd",
410
- "pkg",
411
- "packages",
412
- "internal",
413
- "modules",
414
- "components",
415
- "pages",
416
- "routes",
417
- "api",
418
- "server",
419
- "client",
420
- "web",
421
- "core",
422
- "common",
423
- "shared",
424
- "utils",
425
- "helpers",
426
- "services",
427
- "models",
428
- "views",
429
- "controllers",
430
- "handlers",
431
- "middleware",
432
- "plugins",
433
- "test",
434
- "tests",
435
- "spec",
436
- "__tests__",
437
- "e2e",
438
- "integration",
439
- "scripts",
440
- "tools",
441
- "bin",
442
- "examples",
443
- "samples",
444
- "proto",
445
- "schemas",
446
- "migrations",
447
- "seeds",
448
- ]);
449
- /**
450
- * Scored project-root detection. Accumulates evidence across 5 tiers
451
- * and subtracts anti-signals. A score >= THRESHOLD means "this is a project".
452
- *
453
- * Scoring weights:
454
- * Definitive marker file: +10 (instant pass)
455
- * VCS directory (.git, .hg): +8
456
- * Strong signal file/dir: +4
457
- * Extension marker (*.sln): +10
458
- * Code files in root: +5
459
- * Code files in source dir: +6
460
- * Anti-signal file: -3
461
- * Anti-signal directory: -2
462
- * Anti-signal path: -15 (instant fail for /, /usr, etc.)
463
- *
464
- * Threshold: 5 (a single definitive marker or VCS dir passes)
465
- */
466
- const DETECTION_THRESHOLD = 5;
467
- async function detectProjectRoot(cwd) {
468
- const { readdirSync } = await import("node:fs");
469
- let score = 0;
470
- const signals = [];
471
- let hasGit = false;
472
- // ── TIER 4 first: Anti-signal paths (early reject) ──────────
473
- const normalizedCwd = cwd.replace(/\\/g, "/");
474
- for (const ap of ANTI_SIGNAL_PATHS) {
475
- if (normalizedCwd === ap || normalizedCwd === `${ap}/`) {
476
- return {
477
- isProject: false,
478
- hasGit: false,
479
- reason: `System/root path: ${cwd}`,
480
- score: -15,
481
- signals: [`anti-path: ${ap}`],
482
- };
483
- }
484
- }
485
- // Read root directory entries once
486
- let rootEntries = [];
487
- try {
488
- rootEntries = readdirSync(cwd, { withFileTypes: true }).map((e) => ({
489
- name: e.name,
490
- isFile: e.isFile(),
491
- isDir: e.isDirectory(),
492
- }));
493
- }
494
- catch {
495
- return {
496
- isProject: false,
497
- hasGit: false,
498
- reason: "Cannot read directory",
499
- score: -1,
500
- signals: ["unreadable"],
501
- };
502
- }
503
- const rootFileNames = new Set(rootEntries.filter((e) => e.isFile).map((e) => e.name));
504
- const rootDirNames = new Set(rootEntries.filter((e) => e.isDir).map((e) => e.name));
505
- // ── TIER 4: Anti-signal files & dirs at root ────────────────
506
- for (const af of ANTI_SIGNAL_FILES) {
507
- if (rootFileNames.has(af)) {
508
- score -= 3;
509
- signals.push(`anti-file: ${af}`);
510
- }
511
- }
512
- for (const ad of ANTI_SIGNAL_DIRS) {
513
- if (rootDirNames.has(ad)) {
514
- score -= 2;
515
- signals.push(`anti-dir: ${ad}/`);
516
- }
517
- }
518
- // ── TIER 1: Definitive marker files ─────────────────────────
519
- for (const marker of DEFINITIVE_MARKERS) {
520
- if (rootFileNames.has(marker)) {
521
- score += 10;
522
- signals.push(`marker: ${marker}`);
523
- break; // one is enough
524
- }
525
- }
526
- // ── TIER 1: Extension-based markers (*.sln, *.csproj, etc.) ─
527
- if (score < DETECTION_THRESHOLD) {
528
- for (const entry of rootEntries) {
529
- if (!entry.isFile && !entry.isDir)
530
- continue;
531
- const name = entry.name.toLowerCase();
532
- for (const ext of EXTENSION_MARKERS) {
533
- if (name.endsWith(ext)) {
534
- score += 10;
535
- signals.push(`ext-marker: ${entry.name}`);
536
- break;
537
- }
538
- }
539
- if (score >= DETECTION_THRESHOLD)
540
- break;
541
- }
542
- }
543
- // ── TIER 2: VCS directories ─────────────────────────────────
544
- for (const vcs of STRONG_SIGNAL_DIRS.slice(0, 6)) {
545
- if (rootDirNames.has(vcs)) {
546
- if (vcs === ".git")
547
- hasGit = true;
548
- score += 8;
549
- signals.push(`vcs: ${vcs}/`);
550
- break;
551
- }
552
- }
553
- // ── TIER 2: Strong signal files ─────────────────────────────
554
- for (const sf of STRONG_SIGNAL_FILES) {
555
- if (rootFileNames.has(sf)) {
556
- score += 4;
557
- signals.push(`strong: ${sf}`);
558
- if (score >= DETECTION_THRESHOLD)
559
- break;
560
- }
561
- }
562
- // ── TIER 2: Strong signal dirs (IDE, CI) ────────────────────
563
- for (const sd of STRONG_SIGNAL_DIRS.slice(6)) {
564
- if (rootDirNames.has(sd)) {
565
- score += 4;
566
- signals.push(`strong: ${sd}/`);
567
- if (score >= DETECTION_THRESHOLD)
568
- break;
569
- }
570
- }
571
- // ── TIER 2: Git repo check (may be in a parent .git) ───────
572
- if (!hasGit) {
573
- try {
574
- hasGit = await isInsideGitRepo();
575
- if (hasGit) {
576
- score += 6;
577
- signals.push("git: inside work tree");
578
- }
579
- }
580
- catch {
581
- // ignore
582
- }
583
- }
584
- // Already passing? Skip expensive file scan.
585
- if (score >= DETECTION_THRESHOLD) {
586
- const topSignal = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
587
- return { isProject: true, hasGit, reason: topSignal, score, signals };
588
- }
589
- // ── TIER 3 + 5: Code files in root, then source dirs ───────
590
- function dirHasCodeFile(dir) {
591
- try {
592
- const entries = readdirSync(dir, { withFileTypes: true });
593
- for (const entry of entries) {
594
- if (!entry.isFile())
595
- continue;
596
- const dot = entry.name.lastIndexOf(".");
597
- if (dot >= 0 &&
598
- CODE_EXTENSIONS.has(entry.name.slice(dot).toLowerCase())) {
599
- return entry.name;
600
- }
601
- }
602
- }
603
- catch {
604
- // skip
605
- }
606
- return null;
607
- }
608
- // Code files directly in cwd
609
- const rootCodeFile = dirHasCodeFile(cwd);
610
- if (rootCodeFile) {
611
- score += 5;
612
- signals.push(`code-root: ${rootCodeFile}`);
613
- }
614
- if (score >= DETECTION_THRESHOLD) {
615
- const topSignal = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "unknown";
616
- return { isProject: true, hasGit, reason: topSignal, score, signals };
617
- }
618
- // Code files inside well-known source dirs (+ one level deeper)
619
- for (const dirEntry of rootEntries) {
620
- if (!dirEntry.isDir || dirEntry.name.startsWith("."))
621
- continue;
622
- if (!SOURCE_DIRS.has(dirEntry.name.toLowerCase()))
623
- continue;
624
- const sourceDir = join(cwd, dirEntry.name);
625
- const srcCodeFile = dirHasCodeFile(sourceDir);
626
- if (srcCodeFile) {
627
- score += 6;
628
- signals.push(`code-src: ${dirEntry.name}/${srcCodeFile}`);
629
- break;
630
- }
631
- // One level deeper (src/components/, lib/utils/)
632
- try {
633
- const subEntries = readdirSync(sourceDir, { withFileTypes: true });
634
- let found = false;
635
- for (const sub of subEntries) {
636
- if (!sub.isDirectory() || sub.name.startsWith("."))
637
- continue;
638
- const deepCode = dirHasCodeFile(join(sourceDir, sub.name));
639
- if (deepCode) {
640
- score += 6;
641
- signals.push(`code-src: ${dirEntry.name}/${sub.name}/${deepCode}`);
642
- found = true;
643
- break;
644
- }
645
- }
646
- if (found)
647
- break;
648
- }
649
- catch {
650
- // skip
651
- }
652
- }
653
- const isProject = score >= DETECTION_THRESHOLD;
654
- const topSignal = signals.find((s) => !s.startsWith("anti-")) ?? signals[0] ?? "No signals";
655
- return {
656
- isProject,
657
- hasGit,
658
- reason: isProject ? topSignal : "No code files or project markers found",
659
- score,
660
- signals,
661
- };
662
- }
663
- /**
664
- * Read .unerr/config.json if it exists.
665
- */
666
- function readLocalConfig(cwd) {
667
- const configPath = join(cwd, ".unerr", "config.json");
668
- if (!existsSync(configPath))
669
- return null;
670
- try {
671
- return JSON.parse(readFileSync(configPath, "utf-8"));
672
- }
673
- catch {
674
- return null;
675
- }
676
- }
677
- // ── Boot State Machine ─────────────────────────────────────
678
- /**
679
- * Resume path: config exists, skip all interactive prompts.
680
- */
681
- async function resumeBoot(config) {
682
- initFileLog(process.cwd());
683
- // Verify this is still a valid project directory (config may be stale)
684
- const detection = await detectProjectRoot(process.cwd());
685
- if (!detection.isProject) {
686
- const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
687
- process.stderr.write(`\x1b[38;2;248;113;113m\u2717\x1b[0m No code project detected in this directory.\n Found .unerr/config.json but the directory doesn't look like a project root.\n Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})\n${antiSignals.length > 0
688
- ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}\n`
689
- : ""}\n unerr checks for: project files (package.json, Cargo.toml, go.mod, ...),\n VCS directories (.git, .hg), IDE configs, CI/CD files, lock files,\n and source code across 50+ languages.\n\n \u25b8 Run \x1b[1munerr\x1b[0m from a project root directory.\n`);
690
- process.exit(1);
691
- }
692
- const { initSessionLogger, createSessionModuleLogger } = await import("../utils/session-logger.js");
693
- initSessionLogger();
694
- const log = createSessionModuleLogger("boot");
695
- log.info({ msg: "Resume boot", mode: "local", repoId: config.repoId });
696
- // Show update notice (non-blocking, from cache only)
697
- try {
698
- const { getCachedUpdateInfo } = await import("../daemon/version-checker.js");
699
- const info = getCachedUpdateInfo();
700
- if (info.available && !info.dismissed) {
701
- process.stderr.write(`\x1b[38;2;34;211;238m▸\x1b[0m Update available: ${info.current} → ${info.latest} — run \x1b[38;2;139;92;246munerr daemon update\x1b[0m\n`);
702
- }
703
- }
704
- catch {
705
- // Non-blocking
706
- }
707
- process.stderr.write("[unerr] Starting proxy...\n");
708
- await autoVerifyIdeConfigs();
709
- await startProxy(config.repoId);
710
- }
711
- /**
712
- * First-run path: no config exists. Auto-detect environment, prompt if needed.
713
- */
714
- async function firstRunBoot() {
715
- initFileLog(process.cwd());
716
- const { initSessionLogger, createSessionModuleLogger } = await import("../utils/session-logger.js");
717
- initSessionLogger();
718
- const log = createSessionModuleLogger("boot");
719
- // Must be in a project directory
720
- const detection = await detectProjectRoot(process.cwd());
721
- if (!detection.isProject) {
722
- const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
723
- process.stderr.write(`\x1b[38;2;248;113;113m\u2717\x1b[0m No code project detected in this directory.\n unerr needs a folder containing source code to index.\n Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})\n${antiSignals.length > 0
724
- ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}\n`
725
- : ""}\n Checked for: project files (package.json, Cargo.toml, go.mod, pyproject.toml, ...),\n VCS directories (.git, .hg, .svn), IDE configs (.vscode, .idea),\n CI/CD files (.github, Jenkinsfile), lock files, and source code\n across 50+ languages in root + standard source directories.\n\n \u25b8 Run \x1b[1munerr\x1b[0m from a project root directory.\n`);
726
- process.exit(1);
727
- }
728
- if (!detection.hasGit) {
729
- process.stderr.write(`\x1b[38;2;251;191;36m\u26a0\x1b[0m Project detected (${detection.reason}) but no git repository found.\n unerr works best with git. Consider running \x1b[1mgit init\x1b[0m first.\n Continuing anyway...\n\n`);
730
- }
731
- log.info({
732
- msg: "First-run boot, entering local setup",
733
- detection: detection.reason,
734
- score: detection.score,
735
- hasGit: detection.hasGit,
736
- });
737
- const { runSetup } = await import("../commands/setup-wizard.js");
738
- const result = await runSetup();
739
- if (result.action === "setup") {
740
- await autoVerifyIdeConfigs();
741
- await startProxy(result.repoId);
742
- return;
743
- }
744
- process.exit(0);
745
- }
746
- /**
747
- * Daemon-child mode: spawned and managed by unerrd.
748
- *
749
- * Differences from standalone `unerr`:
750
- * - No interactive prompts (process.stdin.isTTY is false)
751
- * - IPC channel to parent: sends { type: "ready", sock }, { type: "activity" }
752
- * - SIGTERM triggers snapshot + clean exit (no delay)
753
- * - Orphan detection: checks ppid every 60s, self-exits if parent died
754
- * - Activity reports: stats sent every 60s while running
755
- */
756
- async function daemonChildBoot(cwd) {
757
- installFileLogger({
758
- filePath: join(cwd, ".unerr", "logs", `child-${process.pid}.log`),
759
- maxBytes: 5_000_000,
760
- keep: 5,
761
- });
762
- initFileLog(cwd);
763
- const config = readLocalConfig(cwd);
764
- if (!config) {
765
- process.stderr.write("[unerr:child] No .unerr/config.json — run `unerr` interactively first.\n");
766
- process.exit(1);
767
- }
768
- const originalPpid = process.ppid;
769
- // Orphan detection: if parent (unerrd) dies, self-exit
770
- const orphanTimer = setInterval(() => {
771
- if (process.ppid !== originalPpid) {
772
- process.stderr.write("[unerr:child] Parent died — orphan exit.\n");
773
- clearInterval(orphanTimer);
774
- clearInterval(statsTimer);
775
- shutdownProxy("orphan");
776
- }
777
- }, 60_000);
778
- orphanTimer.unref();
779
- // SIGTERM from parent: graceful shutdown
780
- process.on("SIGTERM", () => {
781
- clearInterval(orphanTimer);
782
- clearInterval(statsTimer);
783
- shutdownProxy("sigterm");
784
- });
785
- // Start the proxy (bypass local wrapper to get shutdown handle + stats)
786
- const { startProxy: bootProxy } = await import("../proxy/proxy.js");
787
- const proxyResult = await bootProxy({
788
- repoId: config.repoId,
789
- daemonChild: true,
790
- });
791
- // Notify parent: ready
792
- const stateDir = join(cwd, ".unerr", "state");
793
- const sockPath = join(stateDir, "proxy.sock");
794
- if (process.send) {
795
- process.send({ type: "ready", sock: sockPath });
796
- }
797
- // Inject update notification into MCP _meta if behind >2 minor versions
798
- try {
799
- const { getCachedUpdateInfo } = await import("../daemon/version-checker.js");
800
- const { setUpdateNotification } = await import("../proxy/response-envelope.js");
801
- const info = getCachedUpdateInfo();
802
- if (info.available && !info.dismissed && info.behindMinor > 2) {
803
- setUpdateNotification(info.latest, info.current);
804
- }
805
- }
806
- catch {
807
- // Non-critical
808
- }
809
- function collectStats() {
810
- const mem = process.memoryUsage();
811
- return {
812
- entities: proxyResult.stats.toolCallsLocal,
813
- edges: 0,
814
- memory: Math.round(mem.rss / 1024 / 1024),
815
- };
816
- }
817
- // Periodic stats report to parent
818
- const statsTimer = setInterval(() => {
819
- if (!process.send)
820
- return;
821
- process.send({ type: "stats", ...collectStats() });
822
- }, 60_000);
823
- statsTimer.unref();
824
- // IPC messages from parent
825
- process.on("message", (msg) => {
826
- if (msg.type === "shutdown") {
827
- clearInterval(orphanTimer);
828
- clearInterval(statsTimer);
829
- shutdownProxy("parent-shutdown");
830
- }
831
- if (msg.type === "get-stats") {
832
- if (!process.send)
833
- return;
834
- process.send({ type: "stats", ...collectStats() });
835
- }
836
- });
837
- async function shutdownProxy(reason) {
838
- process.stderr.write(`[unerr:child] Shutting down: ${reason}\n`);
839
- try {
840
- await proxyResult.shutdown();
841
- }
842
- catch (err) {
843
- process.stderr.write(`[unerr:child] Shutdown error: ${err.message}\n`);
844
- }
845
- process.exit(0);
846
- }
847
- }
848
- /**
849
- * MCP mode: headless boot for IDE integration.
850
- *
851
- * Socket discovery order:
852
- * 1. Per-repo proxy sock (`<cwd>/.unerr/state/proxy.sock`) — direct bridge
853
- * 2. unerrd daemon sock (`~/.unerr/unerrd.sock`) — bridge if repo is registered
854
- *
855
- * The bridge NEVER spawns unerrd or registers repos. It only connects to
856
- * what's already running. If nothing is available, it exits with an error.
857
- */
858
- async function mcpBoot(cwd) {
859
- installFileLogger({
860
- filePath: join(cwd, ".unerr", "logs", `mcp-${process.pid}.log`),
861
- maxBytes: 5_000_000,
862
- keep: 5,
863
- });
864
- initFileLog(cwd);
865
- const detection = await detectProjectRoot(cwd);
866
- if (!detection.isProject) {
867
- const antiSignals = detection.signals.filter((s) => s.startsWith("anti-"));
868
- process.stderr.write(`\x1b[38;2;248;113;113m\u2717\x1b[0m No code project detected in this directory.\n unerr needs a folder containing source code to index.\n Detection score: ${detection.score} (need ${DETECTION_THRESHOLD})\n${antiSignals.length > 0
869
- ? ` Negative signals: ${antiSignals.map((s) => s.replace("anti-", "")).join(", ")}\n`
870
- : ""}\n \u25b8 Run \x1b[1munerr --mcp\x1b[0m from a project root directory.\n`);
871
- process.exit(1);
872
- }
873
- const { startUdsBridge } = await import("../proxy/bridge.js");
874
- // ── Step 1: Try per-repo proxy sock (standalone `unerr` running) ──
875
- const repoSock = join(cwd, ".unerr", "state", "proxy.sock");
876
- if (existsSync(repoSock)) {
877
- const { PidLock } = await import("../proxy/pid-lock.js");
878
- const pidLock = new PidLock(join(cwd, ".unerr", "state"));
879
- const probeResult = await pidLock.probe();
880
- if (probeResult.alive) {
881
- process.stderr.write(`[unerr:mcp] Bridging to running proxy (PID ${probeResult.pid})\n`);
882
- return bridgeAndExit(startUdsBridge, repoSock);
883
- }
884
- }
885
- // ── Step 2: Try unerrd (must already be running, repo must be registered) ──
886
- const { daemonSockPath, probeDaemon, ensureRepo, connectRepo, disconnectRepo, sendActivity, } = await import("../daemon/client.js");
887
- const { findRepo } = await import("../daemon/registry.js");
888
- const daemonSock = daemonSockPath();
889
- const daemonRunning = await probeDaemon(daemonSock);
890
- if (!daemonRunning) {
891
- process.stderr.write("\x1b[38;2;248;113;113m\u2717\x1b[0m No unerr process found for this project.\n\n" +
892
- " To use standalone mode:\n" +
893
- " unerr # start per-repo process in this directory\n\n" +
894
- " To use daemon mode:\n" +
895
- " unerr daemon initialize # one-time setup\n" +
896
- " unerr install <agent> # register this repo\n");
897
- process.exit(1);
898
- }
899
- // Daemon is running — check if this repo is registered
900
- const repoEntry = findRepo(cwd);
901
- if (!repoEntry) {
902
- process.stderr.write("\x1b[38;2;248;113;113m\u2717\x1b[0m Repo not registered with unerrd.\n\n" +
903
- " Run: unerr install <agent> # registers this repo with the daemon\n" +
904
- " Or: unerr daemon add . # register without installing agent config\n");
905
- process.exit(1);
906
- }
907
- // Daemon running + repo registered → ensure the per-repo process is up
908
- let repoSockViaEnsure;
909
- try {
910
- repoSockViaEnsure = await ensureRepo(daemonSock, cwd);
911
- }
912
- catch (err) {
913
- process.stderr.write(`\x1b[38;2;248;113;113m\u2717\x1b[0m Failed to ensure repo process: ${err.message}\n`);
914
- process.exit(1);
915
- }
916
- // Register this bridge connection
917
- try {
918
- await connectRepo(daemonSock, cwd);
919
- }
920
- catch {
921
- // Non-fatal — idle sweep may stop the repo eventually
922
- }
923
- process.stderr.write(`[unerr:mcp] Bridging to repo process via unerrd (sock: ${repoSockViaEnsure})\n`);
924
- // Throttled activity reporting (1/min)
925
- const ACTIVITY_THROTTLE_MS = 60_000;
926
- let lastActivitySent = 0;
927
- const activityInterval = setInterval(() => {
928
- const now = Date.now();
929
- if (now - lastActivitySent >= ACTIVITY_THROTTLE_MS) {
930
- sendActivity(daemonSock, cwd);
931
- lastActivitySent = now;
932
- }
933
- }, ACTIVITY_THROTTLE_MS);
934
- activityInterval.unref();
935
- // Bridge to the per-repo process
936
- const bridgeResult = await startUdsBridge(repoSockViaEnsure);
937
- // Cleanup: disconnect from daemon, stop activity reporting
938
- clearInterval(activityInterval);
939
- try {
940
- await disconnectRepo(daemonSock, cwd);
941
- }
942
- catch {
943
- // Best-effort — daemon may already be gone
944
- }
945
- if (bridgeResult.reason === "daemon_dead") {
946
- process.stderr.write("\x1b[38;2;248;113;113m\u2717\x1b[0m unerr proxy disconnected mid-session.\n The repo process may have been idle-stopped or crashed.\n");
947
- process.exit(1);
948
- }
949
- }
950
- /** Bridge to a UDS sock and handle disconnect/error. */
951
- async function bridgeAndExit(startBridge, sockPath) {
952
- const result = await startBridge(sockPath);
953
- if (result.reason === "daemon_dead") {
954
- process.stderr.write("\x1b[38;2;248;113;113m\u2717\x1b[0m unerr proxy disconnected mid-session.\n");
955
- process.exit(1);
956
- }
957
- }
958
- // ── Commander Setup ─────────────────────────────────────────
959
- const program = new Command();
960
- program
961
- .name("unerr")
962
- .description("Code intelligence for AI agents")
963
- .version("0.1.0")
964
- .option("--ide <type>", "IDE type: cursor, vscode, claude-code, windsurf")
965
- .option("--mcp", "Start in MCP server mode (stdio, no interactive prompts)")
966
- .option("--daemon-child", "Run as a daemon-managed child process (internal, set by unerrd)")
967
- .showHelpAfterError("(use --help for available commands)")
968
- .action(async (opts) => {
969
- const cwd = process.cwd();
970
- // --daemon-child: managed child mode (spawned by unerrd)
971
- if (opts.daemonChild) {
972
- await daemonChildBoot(cwd);
973
- return;
974
- }
975
- // --mcp: headless MCP server mode for IDE integration
976
- if (opts.mcp) {
977
- await mcpBoot(cwd);
978
- return;
979
- }
980
- const config = readLocalConfig(cwd);
981
- if (config) {
982
- await resumeBoot(config);
983
- }
984
- else {
985
- await firstRunBoot();
986
- }
987
- });
988
- // ── Visible Commands (shown in --help) ──────────────────────
989
- program
990
- .command("chat")
991
- .description("Interactive AI assistant (coming soon)")
992
- .option("--model <model>", "Claude model to use")
993
- .option("--no-graph", "Skip loading the code intelligence graph")
994
- .action(async () => {
995
- process.stderr.write("\n unerr chat is temporarily disabled.\n Use unerr as an MCP proxy with your preferred AI agent instead.\n\n");
996
- process.exit(0);
997
- });
998
- registerStatusCommand(program);
999
- registerStatsCommand(program);
1000
- registerInstallCommand(program);
1001
- registerDashboardCommand(program);
1002
- registerDebugCommand(program);
1003
- registerGainCommand(program);
1004
- registerDiscoverCommand(program);
1005
- registerDaemonCommand(program);
1006
- // ── Hidden Commands (callable but not shown in --help) ──────
1007
- const hiddenCommands = [
1008
- registerBranchesCommand,
1009
- registerCheckCommitCommand,
1010
- registerCompressOutputCommand,
1011
- registerConfigVerifyCommand,
1012
- registerEnrichCommand,
1013
- registerExecCommand,
1014
- registerHookCommand,
1015
- registerIndexCommand,
1016
- registerInitCommand,
1017
- registerLearnCommand,
1018
- registerManifestCommand,
1019
- registerRewindCommand,
1020
- registerSkillsCommand,
1021
- registerTimelineCommand,
1022
- registerUninstallCommand,
1023
- ];
1024
- for (const register of hiddenCommands) {
1025
- register(program);
1026
- }
1027
- // Hide all commands except chat, status, debug from --help output
1028
- const visibleCommands = new Set([
1029
- "status",
1030
- "stats",
1031
- "install",
1032
- "dashboard",
1033
- "debug",
1034
- "init",
1035
- "daemon",
1036
- ]);
1037
- for (const cmd of program.commands) {
1038
- if (!visibleCommands.has(cmd.name())) {
1039
- // Commander internal property — commands with _hidden=true are omitted from help
1040
- cmd._hidden = true;
1041
- }
1042
- }
1043
- program.parse();