loki-mode 11.3.0 → 11.3.1

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 (215) hide show
  1. package/README.md +68 -650
  2. package/SKILL.md +4 -4
  3. package/VERSION +1 -1
  4. package/autonomy/completion-council.sh +2 -2
  5. package/autonomy/context-tracker.py +4 -4
  6. package/autonomy/council-v2.sh +1 -1
  7. package/autonomy/grill.sh +1 -1
  8. package/autonomy/lib/done-recognition.sh +1 -1
  9. package/autonomy/lib/prd-enrich.sh +1 -1
  10. package/autonomy/lib/voter-agents.sh +1 -1
  11. package/autonomy/loki +99 -75
  12. package/autonomy/run.sh +30 -18
  13. package/autonomy/sandbox.sh +1 -1
  14. package/autonomy/tui.sh +6 -5
  15. package/autonomy/verify.sh +1 -1
  16. package/bin/loki +166 -59
  17. package/completions/_loki +2 -0
  18. package/completions/loki.bash +2 -2
  19. package/dashboard/__init__.py +1 -1
  20. package/dashboard/server.py +25 -31
  21. package/docs/ARCHITECTURE.md +337 -0
  22. package/docs/BRANDING.md +124 -0
  23. package/docs/CLI-REFERENCE.md +7 -1
  24. package/docs/COMPONENTS.md +447 -0
  25. package/docs/DEPLOYMENT.md +9 -9
  26. package/docs/DOCKER.md +469 -0
  27. package/docs/INSTALLATION.md +67 -1045
  28. package/docs/KNOWN-ISSUES.md +11 -35
  29. package/docs/TESTING.md +161 -0
  30. package/docs/air-gapped.md +1 -2
  31. package/docs/alternative-installations.md +14 -24
  32. package/docs/architecture/DECISIONS.md +333 -0
  33. package/docs/audit-logging.md +7 -7
  34. package/docs/authentication.md +3 -3
  35. package/docs/authorization.md +1 -1
  36. package/docs/certification/04-production-deployment/lab.md +6 -6
  37. package/docs/certification/04-production-deployment/lesson.md +4 -4
  38. package/docs/certification/README.md +1 -1
  39. package/docs/control-plane-container.md +2 -2
  40. package/docs/dev/real-run.md +34 -0
  41. package/docs/dev/release-checklist.md +2 -2
  42. package/docs/exit-codes.md +3 -3
  43. package/docs/git-workflow.md +3 -3
  44. package/docs/metrics.md +37 -55
  45. package/docs/network-security.md +1 -1
  46. package/docs/siem-integration.md +5 -5
  47. package/docs/v10/CLI-MODERN.md +4 -3
  48. package/docs/v10/DECISIONS.md +44 -0
  49. package/docs/v10/DEPS.md +22 -22
  50. package/docs/v10/ENGINE.md +1 -1
  51. package/docs/v10/FAILURE-CLASSES.md +118 -1
  52. package/docs/v10/FOUNDER-QUEUE.md +1 -0
  53. package/docs/v10/GUARDS.md +13 -0
  54. package/docs/v10/LEGACY-REMOVAL.md +4 -6
  55. package/docs/v10/METRICS.md +2 -2
  56. package/docs/v10/MODERNIZE.md +1 -1
  57. package/docs/v10/OPERATING-MODEL.md +2 -1
  58. package/docs/v10/PROGRESS.md +29 -0
  59. package/docs/v11/COST-HALF.md +352 -0
  60. package/docs/v11/MARKET-SLICES.md +315 -0
  61. package/docs/v11/REGISTRIES.md +730 -0
  62. package/loki-ts/data/model-pricing.json +52 -24
  63. package/loki-ts/dist/loki.js +1036 -957
  64. package/mcp/__init__.py +1 -1
  65. package/package.json +1 -1
  66. package/packages/control-plane/dist/server.js +2467 -736
  67. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  68. package/src/protocols/mcp-client.js +6 -2
  69. package/src/protocols/mcp-server.js +13 -3
  70. package/tools/audit-docs.py +1 -1
  71. package/web-app/dist/assets/{AdminPage-D_e45uTw.js → AdminPage-CMkHNJbF.js} +1 -1
  72. package/web-app/dist/assets/{Avatar-T3nIpoaf.js → Avatar-BJyjV7Zi.js} +1 -1
  73. package/web-app/dist/assets/{Badge-BKqb59pP.js → Badge-D572l0uq.js} +1 -1
  74. package/web-app/dist/assets/{Button-DjVlkh8_.js → Button-T4BnOZgl.js} +1 -1
  75. package/web-app/dist/assets/{CockpitPage-DXJgLGmh.js → CockpitPage-Cj_GFsNn.js} +1 -1
  76. package/web-app/dist/assets/{ComparePage-BRP_eVc_.js → ComparePage-A8m4ZXak.js} +1 -1
  77. package/web-app/dist/assets/{ErrorBoundary-CLjpGLjC.js → ErrorBoundary-hugCg5yg.js} +1 -1
  78. package/web-app/dist/assets/{EvidenceReceiptPanel-DX-vb4Xy.js → EvidenceReceiptPanel-CuJKDC2G.js} +1 -1
  79. package/web-app/dist/assets/{GitHubIssuesPanel-214HVHtX.js → GitHubIssuesPanel-BYGKjarC.js} +1 -1
  80. package/web-app/dist/assets/{GitHubPRsPanel-BEiZKc4t.js → GitHubPRsPanel-c7sFIJlf.js} +1 -1
  81. package/web-app/dist/assets/{HomePage-VvMHS5ga.js → HomePage-A1MIkG2P.js} +2 -2
  82. package/web-app/dist/assets/{LoginPage-5WD-v7n3.js → LoginPage-8KQMRhtR.js} +1 -1
  83. package/web-app/dist/assets/{MagicPage-DUQT_Jye.js → MagicPage-C05QW2Lx.js} +1 -1
  84. package/web-app/dist/assets/{MetricsPage-lga-z79v.js → MetricsPage-Oukv5hme.js} +1 -1
  85. package/web-app/dist/assets/{NotFoundPage-CGhBX3G1.js → NotFoundPage-DKIwsawz.js} +1 -1
  86. package/web-app/dist/assets/{ProjectPage-CdzF2GzZ.js → ProjectPage-DwFRPLCG.js} +3 -3
  87. package/web-app/dist/assets/{ProjectsPage-DRG64N8s.js → ProjectsPage-CgY4YYDX.js} +1 -1
  88. package/web-app/dist/assets/{SettingsPage-5fzW9Frb.js → SettingsPage-BpfcSjUA.js} +1 -1
  89. package/web-app/dist/assets/{ShowcasePage-DrTdxO4y.js → ShowcasePage-BqBakC4K.js} +1 -1
  90. package/web-app/dist/assets/{SystemSettingsPage-C9Mqs6Rd.js → SystemSettingsPage-CZLu5VB0.js} +1 -1
  91. package/web-app/dist/assets/{TeamsPage-CUGPdSWo.js → TeamsPage-D55S1jKz.js} +1 -1
  92. package/web-app/dist/assets/{TemplatesPage-CNraDY0F.js → TemplatesPage-D3SydvxN.js} +1 -1
  93. package/web-app/dist/assets/{TerminalOutput-BIu_6Pea.js → TerminalOutput-hNg8G1-e.js} +1 -1
  94. package/web-app/dist/assets/{activity-Bk-kgqaf.js → activity-D2C0VagO.js} +1 -1
  95. package/web-app/dist/assets/{bell-BXmUdmo8.js → bell-CMvYi36w.js} +1 -1
  96. package/web-app/dist/assets/{bot-CiuzXxUR.js → bot-BvtQ6lmo.js} +1 -1
  97. package/web-app/dist/assets/{check-BEzjXppd.js → check-B13xkXed.js} +1 -1
  98. package/web-app/dist/assets/{chevron-left-AYWCXHf8.js → chevron-left-emu6r49E.js} +1 -1
  99. package/web-app/dist/assets/{circle-alert-BNg2uSRq.js → circle-alert-CExlwjuy.js} +1 -1
  100. package/web-app/dist/assets/{clock-DTj0kkGT.js → clock-D7Uas3oL.js} +1 -1
  101. package/web-app/dist/assets/{cloud-x4nTDBLT.js → cloud-CkB4S86C.js} +1 -1
  102. package/web-app/dist/assets/{code-xml-BVh_MLrk.js → code-xml-SL9IIWIn.js} +1 -1
  103. package/web-app/dist/assets/{database-DdfBaowG.js → database-BQFkd7t1.js} +1 -1
  104. package/web-app/dist/assets/{dollar-sign-XuYFmAOY.js → dollar-sign-qHmnNYsU.js} +1 -1
  105. package/web-app/dist/assets/{file-code-corner-D4cRnmeL.js → file-code-corner-C2YUDP0K.js} +1 -1
  106. package/web-app/dist/assets/{file-plus-CyoVlHc_.js → file-plus-C_ooIkDe.js} +1 -1
  107. package/web-app/dist/assets/{globe-Y6IK2RPW.js → globe-CQ-GL485.js} +1 -1
  108. package/web-app/dist/assets/{hammer-BKzr9VZI.js → hammer-U05L_D7V.js} +1 -1
  109. package/web-app/dist/assets/{index-B20cSx5G.js → index-CooHd7I-.js} +3 -3
  110. package/web-app/dist/assets/{layers-DebNOhGS.js → layers-Buo17rjW.js} +1 -1
  111. package/web-app/dist/assets/{loader-circle-DyGbrEgd.js → loader-circle-15vK3oV1.js} +1 -1
  112. package/web-app/dist/assets/{lock-BKPpyt20.js → lock-BeXLebuJ.js} +1 -1
  113. package/web-app/dist/assets/{package-egF5Wi4y.js → package-MVh_6-6R.js} +1 -1
  114. package/web-app/dist/assets/{plus-OiEgKIc-.js → plus-Dh_nd9vS.js} +1 -1
  115. package/web-app/dist/assets/{refresh-cw-CzKvrONq.js → refresh-cw-C9cBJEAT.js} +1 -1
  116. package/web-app/dist/assets/{rotate-ccw-Bu2mII0p.js → rotate-ccw-D3PAtzST.js} +1 -1
  117. package/web-app/dist/assets/{scroll-text-CSkiyax8.js → scroll-text-DZCoXQjo.js} +1 -1
  118. package/web-app/dist/assets/{server-DlPthJFw.js → server-OCOLI5J_.js} +1 -1
  119. package/web-app/dist/assets/{shield-alert-CbpHWQQX.js → shield-alert-oVhmf1_h.js} +1 -1
  120. package/web-app/dist/assets/{thumbs-up-DgGpZe-Q.js → thumbs-up-B-kbX4vN.js} +1 -1
  121. package/web-app/dist/assets/{trash-2-BV3GlCB-.js → trash-2-EL6moveW.js} +1 -1
  122. package/web-app/dist/assets/{trending-down-Y4TJtXhD.js → trending-down-Cl3kjpsx.js} +1 -1
  123. package/web-app/dist/assets/{trending-up-DsDNoHZY.js → trending-up-DST7I6M2.js} +1 -1
  124. package/web-app/dist/assets/{upload-gkThrc_I.js → upload-CUOT780G.js} +1 -1
  125. package/web-app/dist/assets/{usePolling-jpNSeNAe.js → usePolling-Djmt90eb.js} +1 -1
  126. package/web-app/dist/assets/{user-3Zez1WOK.js → user-BRBohA6J.js} +1 -1
  127. package/web-app/dist/index.html +1 -1
  128. package/docs/ARCHITECTURE-OVERVIEW.md +0 -209
  129. package/docs/AUTONOMI-ECOSYSTEM.md +0 -107
  130. package/docs/COCKPIT-SPEC.md +0 -72
  131. package/docs/COCKPIT-UX-MANDATE.md +0 -53
  132. package/docs/COMPETITIVE-ANALYSIS.md +0 -303
  133. package/docs/COMPETITIVE-NEXT-10.md +0 -316
  134. package/docs/COMPETITIVE-RALPH-ZEROSHOT-2026-07-08.md +0 -81
  135. package/docs/DASHBOARD-9.12-EVIDENCE.md +0 -97
  136. package/docs/DASHBOARD-ARCHITECTURE.md +0 -425
  137. package/docs/DASHBOARD_V2_CHECKLIST.md +0 -243
  138. package/docs/DEMOS.md +0 -63
  139. package/docs/ENTERPRISE-IDENTITY-ROADMAP.md +0 -206
  140. package/docs/EVIDENCE-RECEIPT-BUILD-APPLICABILITY.md +0 -79
  141. package/docs/FEAT-PRDREUSE-DOCKER-PLAN.md +0 -144
  142. package/docs/GATE-FAILURE-TRIAGE.md +0 -254
  143. package/docs/HANDOFF-2026-08-03.md +0 -439
  144. package/docs/LOOP-CANDIDATE-PROPOSAL-v1.md +0 -167
  145. package/docs/LOOP-HARNESS-AUDIT.md +0 -684
  146. package/docs/MERGE-DEDUP-MAP.md +0 -420
  147. package/docs/MERGE-ROUTE-MAP.md +0 -139
  148. package/docs/MODEL-REGISTRY-PLAN.md +0 -155
  149. package/docs/OUTCOME-FRONTIER.md +0 -536
  150. package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +0 -192
  151. package/docs/PLANS-INDEX.md +0 -32
  152. package/docs/PRD-REUSE-DONE-RECOGNITION-PLAN.md +0 -536
  153. package/docs/PROMPT-ABLATION-RESULT.md +0 -97
  154. package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +0 -127
  155. package/docs/R5-AUTO-WIKI-DESIGN.md +0 -137
  156. package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +0 -107
  157. package/docs/R7-ZERO-CONFIG-FIRST-RUN-PLAN.md +0 -137
  158. package/docs/R8-SHAREABLE-TEAM-ASSETS-PLAN.md +0 -129
  159. package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +0 -113
  160. package/docs/RARV-C-100X-PLAN.md +0 -286
  161. package/docs/RARV-C-LOOP-EFFICIENCY-PLAN.md +0 -470
  162. package/docs/SHAREABLE-PROOF-PLAN.md +0 -194
  163. package/docs/SONNET5-DEFAULT-PLAN.md +0 -157
  164. package/docs/SPEED-AND-FIRST-PASS-PLAN.md +0 -281
  165. package/docs/STALE-STATE-AUDIT.md +0 -174
  166. package/docs/SYNERGY-TASKS.md +0 -143
  167. package/docs/TASKLIST-ACCURACY-PLAN.md +0 -355
  168. package/docs/TOP-100-BACKLOG.md +0 -159
  169. package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +0 -114
  170. package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +0 -45
  171. package/docs/V8-MAJOR-RELEASE-PLAN.md +0 -137
  172. package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +0 -82
  173. package/docs/V8-SDK-RESEARCH-RAW.md +0 -129
  174. package/docs/WELCOME-OPENER-PLAN.md +0 -106
  175. package/docs/adoption-baseline-2026-07-31.md +0 -84
  176. package/docs/architecture/bmad-integration-epic.md +0 -271
  177. package/docs/architecture/bmad-integration-review.md +0 -86
  178. package/docs/architecture/bmad-integration-validation.md +0 -249
  179. package/docs/architecture/bmad-loki-voice-agent-council-analysis.md +0 -61
  180. package/docs/auto-claude-comparison.md +0 -250
  181. package/docs/bug-fixes/agent-01-cli-fixes.md +0 -101
  182. package/docs/bug-fixes/agent-02-purplelab-fixes.md +0 -88
  183. package/docs/bug-fixes/agent-03-dashboard-fixes.md +0 -119
  184. package/docs/bug-fixes/agent-04-memory-fixes.md +0 -105
  185. package/docs/bug-fixes/agent-05-provider-fixes.md +0 -86
  186. package/docs/bug-fixes/agent-06-integration-fixes.md +0 -101
  187. package/docs/bug-fixes/agent-07-dash-run-fixes.md +0 -101
  188. package/docs/bug-fixes/agent-08-docker-fixes.md +0 -164
  189. package/docs/bug-fixes/agent-09-e2e-build-fixes.md +0 -69
  190. package/docs/bug-fixes/agent-10-e2e-fullstack-fixes.md +0 -102
  191. package/docs/bug-fixes/agent-11-e2e-session-fixes.md +0 -70
  192. package/docs/bug-fixes/agent-12-scenario-fixes.md +0 -120
  193. package/docs/bug-fixes/agent-13-enterprise-fixes.md +0 -143
  194. package/docs/bug-fixes/agent-14-uat-newuser-fixes.md +0 -88
  195. package/docs/bug-fixes/agent-15-uat-poweruser-fixes.md +0 -132
  196. package/docs/bug-fixes/agent-19-code-review.md +0 -316
  197. package/docs/bug-fixes/agent-20-architecture-review.md +0 -331
  198. package/docs/competitive/bolt-new-analysis.md +0 -579
  199. package/docs/competitive/emergence-others-analysis.md +0 -605
  200. package/docs/competitive/replit-lovable-analysis.md +0 -622
  201. package/docs/dashboard-guide.md +0 -487
  202. package/docs/enterprise/migration.md +0 -265
  203. package/docs/history/one-run-audit.md +0 -304
  204. package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +0 -312
  205. package/docs/plans/ULTRAPLAN-FORGE-BAAS.md +0 -612
  206. package/docs/plans/UT2-6-LSP-DIAGNOSTIC-BROADCAST.md +0 -394
  207. package/docs/prd-purple-lab-ide.md +0 -152
  208. package/docs/prd-purple-lab-platform-v2.md +0 -223
  209. package/docs/prd-purple-lab-platform.md +0 -319
  210. package/docs/retrospectives/v7.5.15-fleet-postmortem.md +0 -325
  211. package/docs/retrospectives/v7.5.15-honesty-audit.md +0 -136
  212. package/docs/retrospectives/v7.5.15-llm-failure-modes.md +0 -49
  213. package/docs/show-hn-post.md +0 -46
  214. package/docs/test-scenarios/enterprise-scenarios.md +0 -732
  215. package/docs/thick2thin.md +0 -173
@@ -1,394 +0,0 @@
1
- # UT2-6: Multi-agent LSP Diagnostic Broadcast
2
-
3
- Status: draft plan, not scheduled for v7.7.x
4
- Author: planning session, 2026-05-24
5
- Supersedes acceptance criterion #3 of the v7.7.0 LSP grounding work
6
- Owner: TBD
7
-
8
- ## 1. Problem statement
9
-
10
- Loki v7.7.0 shipped LSP grounding via `mcp/lsp_proxy.py`. Each MCP server instance lazily spawns per-language LSP processes (`pyright-langserver`, `typescript-language-server`, `gopls`, `rust-analyzer`, `jdtls`) on first tool call. When Loki runs in parallel mode (`skills/parallel-workflows.md`), each worktree gets its own Claude session, which spawns its own MCP server instance, which spawns its own LSP pool. Two failure modes follow:
11
-
12
- 1. **Cold-start tax**: N worktrees x M languages = N*M LSP spawns. Pyright on a real TS project is 3-6 s cold; multiplied by 5 worktrees that is 15-30 s of wall clock and ~250 MB RSS per process duplicated.
13
- 2. **Diagnostic blindness**: when worktree-A discovers via `lsp_get_diagnostics` that `foo.ts` now has a type error after its edit, worktrees B/C/D never see that signal. If B is planning an overlapping edit on `foo.ts`, B will compute against a stale model and may introduce conflicting changes that surface only at merge time.
14
-
15
- The v7.7.0 CHANGELOG explicitly defers this:
16
-
17
- > Multi-agent diagnostic broadcast (acceptance #3): we share one LSP client per language per process; two agents in PARALLEL WORKTREES get their own process pool. Cross-worktree broadcast deferred to v7.7.1.
18
-
19
- This document is the planning input for that deferred work. **It does not need to ship in v7.7.x.**
20
-
21
- ## 2. Design pivot: broadcast first, pool later
22
-
23
- The task framing puts "shared LSP pool" (1 process for N worktrees) on equal footing with "diagnostic broadcast." This plan splits them and downgrades pooling to a follow-on. Reasoning:
24
-
25
- - Worktrees are different working copies. Two worktrees of a TypeScript project diverge in:
26
- - On-disk source (the whole point of worktrees).
27
- - `node_modules/` if `npm install` was run per worktree (common; see `spawn-parallel.sh` in `parallel-workflows.md` line 360).
28
- - Active `tsconfig.json` references (a feature branch can edit tsconfig).
29
- - Per-worktree generated files (`.next/`, `dist/`, etc.).
30
- - LSP servers maintain per-workspace in-memory module graphs. Multi-root workspaces (`workspaceFolders` in the LSP `initialize` params) exist, but pyright and typescript-language-server treat each root as semi-independent; switching the active root mid-flight or running concurrent edits across roots is not a tested path and surfaces server-specific quirks.
31
- - The win the requirement actually asks for is "agent B is notified to re-plan when A's edit produced a new diagnostic." That is achievable without process consolidation. Broadcasting from N processes to a shared bus solves the correctness problem; pool consolidation is a separate cold-start optimization.
32
-
33
- So v1 keeps the N-process model and adds a publish/subscribe channel. v2 (out of scope here) considers process consolidation behind a feature flag, with measured trade-offs.
34
-
35
- ## 3. Prerequisite: fix the orphan `pending_diagnostics` reference
36
-
37
- > **UPDATE 2026-05-27 (v7.7.14):** RESOLVED. The fix described below
38
- > shipped in v7.7.14. `LSPClient` now spawns a dedicated daemon reader
39
- > thread at end of `start()` that owns `proc.stdout`, routes responses
40
- > by id to per-request `Queue`s, and routes `publishDiagnostics` into
41
- > `self.pending_diagnostics`. `request()` parks on its Queue instead
42
- > of reading stdout. Re-spawn after crash drains old reader cleanly.
43
- > Reader-death drain pushes error sentinel to all pending waiters.
44
- > See `mcp/lsp_proxy.py` and `tests/test-lsp-diagnostics-regression.sh`
45
- > (5/5 PASS). The broadcast layer described in sections 4-13 can now
46
- > be built on top of a working reader. The section below is kept for
47
- > historical context.
48
-
49
- `lsp_proxy.py` line 867 (in `lsp_get_diagnostics`) reads:
50
-
51
- ```python
52
- if hasattr(client, 'pending_diagnostics'):
53
- target_uri = _path_to_uri(abs_file)
54
- for _ in range(5):
55
- with getattr(client, '_lock', threading.Lock()):
56
- buf = getattr(client, 'pending_diagnostics', {})
57
- ...
58
- ```
59
-
60
- The class `LSPClient` has no `pending_diagnostics` attribute and no notification-reader thread. `LSPClient.request()` reads messages in a busy loop until it sees the response with the matching `id`, dropping all notifications it sees along the way (including `textDocument/publishDiagnostics`). So `lsp_get_diagnostics` today always returns an empty diagnostics array on every call. **This is a latent bug that any broadcast implementation must fix as a prerequisite**, because the diagnostics the bus will broadcast come from those exact notifications.
61
-
62
- Required change to `LSPClient`:
63
- - Spawn a single notification-reader thread per client at end of `start()`.
64
- - The reader owns `proc.stdout`. `request()` no longer reads from stdout directly; instead it parks on a per-request `threading.Event` (or `Queue`) keyed by request id, and the reader thread routes responses by id and routes notifications by method.
65
- - `publishDiagnostics` notifications populate `self.pending_diagnostics: Dict[str, List[Diagnostic]]` keyed by URI, and also fire a registered callback (the broadcast hook, see section 5).
66
- - Locking: notification reads and response routing share a single `threading.Lock`; the request-side `Event.wait(timeout)` handles flow control.
67
-
68
- This is mostly a refactor of the existing send/receive code; no new dependencies. ~150 LOC delta in `lsp_proxy.py`.
69
-
70
- ## 4. Architecture overview
71
-
72
- ```
73
- +-----------+ +-----------+ +-----------+
74
- | worktree-A| | worktree-B| | worktree-C|
75
- | | | | | |
76
- | Claude | | Claude | | Claude |
77
- | MCP svr | | MCP svr | | MCP svr |
78
- | lsp-pxy | | lsp-pxy | | lsp-pxy |
79
- | +pyright | | +pyright | | +pyright |
80
- +----+------+ +----+------+ +----+------+
81
- | publish | publish | publish
82
- v v v
83
- +--------------------+
84
- | shared .loki/events/lsp/ | (or main worktree's .loki/)
85
- +----------+---------+
86
- | subscribe
87
- +----------------+----------------+
88
- v v v
89
- worktree-A worktree-B worktree-C
90
- (subscriber) (subscriber) (subscriber)
91
- ```
92
-
93
- Key points:
94
- - Each worktree still runs its own LSP processes (no pooling in v1).
95
- - Publishing target is a shared `.loki/events/lsp/` directory resolved at MCP startup. Resolution rule: walk up from CWD looking for a `.loki/parallel-root` marker file (written by `spawn-parallel.sh`); if found, use that directory's `.loki/events/lsp/`; else use local `.loki/events/lsp/` (single-worktree behavior degrades to a noop bus that still works).
96
- - Reuses `events/bus.py` semantics (file-based, processed-id dedup, lockfile-based atomic writes, cross-language compatible) but with a dedicated subdirectory so LSP events do not pollute the existing pending/archive flow that the dashboard consumes.
97
-
98
- ## 5. Component design
99
-
100
- ### 5.1 New module: `mcp/lsp_broadcast.py`
101
-
102
- Responsibilities:
103
- 1. Resolve the shared bus root.
104
- 2. Publish diagnostic events from LSP notifications.
105
- 3. Provide a subscriber MCP tool `lsp_subscribe_diagnostics` for agents.
106
- 4. Provide a one-shot poll tool `lsp_recent_diagnostics(since=...)` for stateless agents.
107
- 5. Maintain a per-process subscriber state file so an agent that disconnects and reconnects resumes from `last_event_id`.
108
-
109
- Pseudocode sketch:
110
-
111
- ```python
112
- # mcp/lsp_broadcast.py
113
-
114
- DIAG_EVENT_DIR = ".loki/events/lsp" # under shared root
115
- DIAG_JSONL = "diagnostics.jsonl" # append-only log, primary channel
116
- DIAG_INDEX = "index.json" # {last_event_id, last_offset}
117
- PARALLEL_MARKER = ".loki/parallel-root"
118
-
119
- def resolve_shared_root(cwd: str) -> Path:
120
- # Walk up until we find PARALLEL_MARKER or hit fs root / first .git.
121
- # Marker file contains the absolute path of the shared root.
122
- # Fallback: local .loki.
123
- ...
124
-
125
- class DiagnosticPublisher:
126
- def __init__(self, workspace_id: str, root: Path):
127
- self.workspace_id = workspace_id # = worktree basename + git branch hash
128
- self.path = root / DIAG_EVENT_DIR / DIAG_JSONL
129
- self.path.parent.mkdir(parents=True, exist_ok=True)
130
-
131
- def publish(self, uri: str, diagnostics: list[dict]) -> None:
132
- # Called from LSPClient notification thread. Must be lock-free
133
- # against subscribers; uses POSIX O_APPEND semantics (atomic for
134
- # writes < PIPE_BUF, 4096 on linux/macos). Each line < 4 KB.
135
- for d in diagnostics:
136
- evt = {
137
- "event_id": uuid4().hex[:12],
138
- "ts": utcnow_iso(),
139
- "workspace_id": self.workspace_id,
140
- "uri": uri,
141
- "file_relpath": relpath_from_workspace(uri),
142
- "severity": d.get("severity"), # LSP: 1=error 2=warn 3=info 4=hint
143
- "range": d.get("range"),
144
- "message": d.get("message", "")[:512],
145
- "source": d.get("source"),
146
- "msg_hash": sha256(d.get("message",""))[:8],
147
- }
148
- line = json.dumps(evt, separators=(",", ":")) + "\n"
149
- with open(self.path, "a", encoding="utf-8") as f:
150
- f.write(line)
151
- # Truncation policy: see section 7.2.
152
- ```
153
-
154
- Subscriber MCP tool:
155
-
156
- ```python
157
- @mcp.tool()
158
- async def lsp_subscribe_diagnostics(
159
- file_globs: list[str] | None = None,
160
- severity_min: int = 2,
161
- since_event_id: str | None = None,
162
- timeout_s: float = 5.0,
163
- max_events: int = 50,
164
- ) -> str:
165
- """Return diagnostic events from peer worktrees since the given id.
166
-
167
- Blocks up to timeout_s if no new events match. Returns ordered by
168
- event_id (which is monotonic per-file via the JSONL byte offset).
169
- """
170
- ...
171
- ```
172
-
173
- Optional follow-on tool `lsp_recent_diagnostics(seconds=60)` for one-shot polling without the cursor pattern.
174
-
175
- ### 5.2 Wiring into `LSPClient`
176
-
177
- ```python
178
- # in mcp/lsp_proxy.py, inside LSPClient
179
- def _on_notification(self, method: str, params: dict) -> None:
180
- if method == "textDocument/publishDiagnostics":
181
- uri = params.get("uri")
182
- diags = params.get("diagnostics") or []
183
- # Local buffer (fixes the section 3 orphan):
184
- with self._lock:
185
- self.pending_diagnostics[uri] = diags
186
- # Broadcast hook (no-op if disabled):
187
- if _broadcast_publisher is not None:
188
- _broadcast_publisher.publish(uri, diags)
189
- ```
190
-
191
- `_broadcast_publisher` is initialized lazily on first tool call when the env var `LOKI_LSP_BROADCAST=1` is set (see section 8).
192
-
193
- ### 5.3 Workspace identity
194
-
195
- `workspace_id` must be stable across the process lifetime and distinguish worktrees on disk:
196
-
197
- ```
198
- workspace_id = sha1(abspath(worktree_root))[:10]
199
- worktree_root = first ancestor with .git file (worktree pointer) or .git dir
200
- ```
201
-
202
- This avoids leaking absolute paths into the event log while letting subscribers map back via a small local cache.
203
-
204
- ### 5.4 File-relative path normalization
205
-
206
- Each event carries `file_relpath` resolved against the **publishing worktree's** root. A subscriber that wants to react to "the same logical file" applies the path against its own worktree root. This is correct for the common case (worktrees of the same repo); it breaks if worktrees rename a file or restructure directories (rare; documented as a known limitation in section 10).
207
-
208
- ## 6. Dedup and re-plan signal
209
-
210
- The brief's requirement: agent A edits `foo.ts` and produces a new diagnostic; agent B was about to do an overlapping edit; B needs to be notified to re-plan.
211
-
212
- ### 6.1 Dedup key
213
-
214
- Diagnostic identity for dedup:
215
-
216
- ```
217
- key = (workspace_id, file_relpath, range.start.line, range.start.character, severity, msg_hash)
218
- ```
219
-
220
- This is intentionally stable across LSP republishes of the same diagnostic on every keystroke. The publisher applies it: if the last event for the same key (within the same workspace_id) had identical `range.end` and `message`, the publish is suppressed. Implementation: a small per-publisher LRU keyed by `(file_relpath, key)` of size 256.
221
-
222
- Cross-workspace dedup is intentionally *not* applied. If both A and B independently produce the same diagnostic for the same file, both events ship; the subscriber decides whether to coalesce.
223
-
224
- ### 6.2 Subscriber-side overlap detection
225
-
226
- This is what gives B the "re-plan" signal. Pseudocode for a planner-tier agent's inner loop:
227
-
228
- ```
229
- before each tool_use that edits file F at range R:
230
- events = lsp_subscribe_diagnostics(
231
- file_globs=[F],
232
- since_event_id=my_cursor,
233
- timeout_s=0.2, # cheap poll, not block
234
- )
235
- for e in events:
236
- if e.workspace_id == self.workspace_id:
237
- continue # my own edit echoing back
238
- if ranges_overlap(R, e.range) and e.severity == 1:
239
- yield Replan(reason=f"peer worktree {e.workspace_id} produced error at {e.range}")
240
- my_cursor = events[-1].event_id if events else my_cursor
241
- ```
242
-
243
- The MCP tool returns events; whether the agent's prompt instructs it to actually re-plan is a system-prompt change, scoped out of this design doc (it belongs in the v7.7.0 acceptance #5 follow-up).
244
-
245
- ### 6.3 Cursor persistence
246
-
247
- Subscribers persist `my_cursor` to `.loki/state/lsp-cursor-{agent_id}.json` so a session restart resumes correctly. The publisher rotates JSONL files at 5 MB and keeps the last 3; an over-stale cursor that points into a rotated file gets a `{"cursor_reset": true, "events": [...]}` response telling the agent it lost continuity and should re-baseline (call `lsp_get_diagnostics` for its files of interest).
248
-
249
- ## 7. Failure modes
250
-
251
- ### 7.1 LSP process crashes
252
-
253
- Symptom: `LSPClient.proc.poll() is not None` in `_get_or_spawn_client`. Today the code re-spawns on next call. With broadcast: also publish a synthetic event `{"event_type": "lsp_down", "language": ..., "workspace_id": ...}` so peer worktrees know diagnostics from this workspace are stale until the next `lsp_up` event. Cleanup: on re-spawn success, publish `lsp_up`. Subscribers that filter by severity will not see these events unless they explicitly opt in via a separate event type.
254
-
255
- ### 7.2 Broadcast channel backs up
256
-
257
- The JSONL grows unboundedly if no one consumes. Mitigations:
258
- - **Size-based rotation**: at 5 MB, rename to `diagnostics.jsonl.1`, shift 1 to 2, drop 3. Implemented in the publisher, guarded by a short-held lockfile (`diagnostics.jsonl.rotlock`) to prevent two publishers rotating simultaneously.
259
- - **Time-based GC**: on each publish, with probability 1/1000, scan `.loki/events/lsp/diagnostics.jsonl.*` and delete files older than 24 h.
260
- - **Per-publisher rate cap**: each `DiagnosticPublisher` enforces less than or equal to 50 events/second; excess gets batched with a 100 ms timer and a single coalesced event per (file, severity).
261
- - **Subscriber backpressure**: subscribers reading via `lsp_subscribe_diagnostics` get at most `max_events` per call; if a subscriber is very slow, the rotation may eat its tail. The `cursor_reset` mechanism in section 6.3 handles this gracefully.
262
-
263
- ### 7.3 Agent times out waiting for events
264
-
265
- `lsp_subscribe_diagnostics(timeout_s=5.0)` returns an empty list on timeout. No error. The subscriber polls again next loop iteration. This is the same pattern as `events/bus.py` `subscribe()`.
266
-
267
- ### 7.4 Disk full / EIO on publish
268
-
269
- Publisher catches `OSError` from `open`/`write` and logs to stderr, dropping the event. Diagnostics being best-effort is acceptable; the worst case is "agent B does its overlapping edit and we catch the conflict at merge time," which is the current behavior anyway.
270
-
271
- ### 7.5 Concurrent writers and reader-during-write
272
-
273
- Append-only writes of less than PIPE_BUF (4 KB on Linux/macOS) are atomic per POSIX. Each event line is capped at ~1 KB by the 512-char message truncation. Readers use line-buffered `for line in f:` with a recovery loop: if `json.loads(line)` raises, log and skip the line, advance the cursor past the partial line.
274
-
275
- ### 7.6 Shared root resolution disagrees across worktrees
276
-
277
- If worktree-A finds the parallel marker but worktree-B does not (e.g., spawn script didn't copy `.loki/parallel-root` into B), they publish to different directories and never see each other. Mitigation: `loki doctor` validates `.loki/parallel-root` exists in every worktree in the orchestrator's tracked set; warns on mismatch. Documented in `skills/parallel-workflows.md` change.
278
-
279
- ### 7.7 Symlink and case-sensitivity quirks across worktrees
280
-
281
- macOS case-insensitive default filesystems can produce different `file_relpath` casing for `Foo.ts` vs `foo.ts`. Normalize via `os.path.normcase` at publish time and document the assumption that subscribers run on the same machine (broadcast is single-host in v1).
282
-
283
- ## 8. Backward compatibility
284
-
285
- Default: **opt-in via env var `LOKI_LSP_BROADCAST=1` for v7.7.x, default-on starting v7.8.x**.
286
-
287
- Rationale:
288
- - Today's parallel-mode users do not expect cross-worktree diagnostic flow; turning it on by default could change behavior in ways they cannot diagnose (e.g., agent B's "why did you re-plan?" trace points to an event from a different worktree).
289
- - The opt-in flag is set by `spawn-parallel.sh` automatically when it provisions worktrees, so users running parallel mode via the documented entry point get it for free.
290
- - Single-worktree users see no change; the publisher is initialized only when the env var is set, and `lsp_subscribe_diagnostics` returns an empty list with a clean diagnostic message if broadcast is off.
291
-
292
- The MCP tool surface is additive: `lsp_subscribe_diagnostics` and `lsp_recent_diagnostics` are new; no existing tool semantics change. The orphan `pending_diagnostics` fix (section 3) is a bug fix and ships as a patch regardless of the broadcast flag.
293
-
294
- Migration note for the changelog: any agent prompt that says "use lsp_get_diagnostics to check for errors" continues to work; the new subscribe tool is additive for parallel-mode agents.
295
-
296
- ## 9. Test plan (no $30 multi-agent runs)
297
-
298
- Three layers, all runnable locally and in CI.
299
-
300
- ### 9.1 Unit tests: `tests/test_lsp_broadcast.py` (pytest)
301
-
302
- Coverage:
303
- - `DiagnosticPublisher.publish` dedup LRU: emit same diagnostic twice, assert one event line written.
304
- - Rotation: publish events until file exceeds 5 MB, assert `diagnostics.jsonl.1` exists and primary file is reset.
305
- - Cursor reset: subscriber with a cursor pointing into a rotated file gets `cursor_reset=True`.
306
- - Workspace id stability: same `worktree_root` produces same id; different roots produce different ids.
307
- - Race: 5 threads each calling `publish` 100 times; assert all 500 events present and parseable.
308
-
309
- These tests use a **fake LSPClient** that exposes a `inject_notification(method, params)` method. No real LSP binary is spawned.
310
-
311
- ### 9.2 Subprocess harness: `tests/test-lsp-broadcast.sh` (bash)
312
-
313
- Coverage:
314
- - Spawns 5 background `python3 -m mcp.lsp_proxy` processes pointing at the same shared root (a temp dir).
315
- - Each fake-publishes diagnostics on a 100 ms interval for 5 s using a small helper script that imports `DiagnosticPublisher` directly.
316
- - A 6th process subscribes and asserts it sees events from all 5 workspace_ids.
317
- - Kills one publisher mid-stream; asserts a `lsp_down` event arrives.
318
- - Total runtime: less than 15 s. No Claude API calls. No real LSP.
319
-
320
- This is the test that would have caught the v7.7.0 acceptance #3 gap, and it costs $0.
321
-
322
- ### 9.3 Integration test: one worktree, real pyright, two subscriber processes
323
-
324
- Smallest realistic end-to-end: a fixture project with a single .py file that has a type error. One publisher process spawns pyright via `LSPClient`, opens the file, waits for diagnostics. Two subscriber processes call `lsp_subscribe_diagnostics(file_globs=["*.py"])` and assert they each see the pyright diagnostic. Validates the section 3 prerequisite (notification reader) end-to-end.
325
-
326
- CI cost: ~$0 (no Claude). Wall time: ~10 s (mostly pyright cold start).
327
-
328
- ### 9.4 What we deliberately do not test in CI
329
-
330
- - Five real Claude sessions in five worktrees. Documented as a "release manual test" with a fixture PRD that has a known overlapping-edit hazard between two features. Run before any release that touches the broadcast code, recorded in `tests/manual/lsp-broadcast-real-agents.md`. Cost gate: only run when the broadcast code changed in the release window.
331
-
332
- ## 10. What won't work / scoped out
333
-
334
- Honest list of things this plan deliberately does not solve:
335
-
336
- 1. **One LSP process serving N worktrees.** The task framed this as item 1; this plan defers it. To do this correctly requires:
337
- - Investigating per-server `workspaceFolders` behavior in pyright / tsls / gopls / rust-analyzer / jdtls under concurrent edits across roots.
338
- - A path-rewriting layer that maps tool args from worktree-relative to a canonical root.
339
- - Solving the divergent-dependency-tree problem (different `node_modules` per worktree). For TypeScript this likely requires running one tsls per `tsconfig.json` root anyway, so the savings are smaller than they look.
340
- - Recommended only if profiling shows cold-start is a real bottleneck; in practice the LSP processes are long-lived and the cold-start cost amortizes across a session.
341
-
342
- 2. **Cross-machine broadcast.** Single-host only. Multi-host parallel workflows are not a documented use case; if they become one, replace the file bus with a real pub/sub (NATS, Redis) behind the same `DiagnosticPublisher` interface.
343
-
344
- 3. **Cross-language diagnostic correlation.** A TS edit that should trigger a Python type check does not happen through LSP; out of scope. If we want this, it lives in a higher-level agent prompt, not the LSP layer.
345
-
346
- 4. **Worktree file-rename handling.** A diagnostic published as `src/foo.ts` from worktree-A is delivered as `src/foo.ts` to worktree-B even if B has renamed it to `src/bar.ts`. Documented limitation; the subscriber can detect "this file does not exist in my worktree" and discard.
347
-
348
- 5. **The orphan `pending_diagnostics` code path.** Fixing it is a *prerequisite* (section 3), not a goal of this design. If it ships standalone as a patch before broadcast lands, that is strictly better than today's silent-empty-array behavior.
349
-
350
- 6. **Replacing `events/bus.py` with a "real" event bus.** Tempting but out of scope. The existing bus is good enough for diagnostic events at the rates we expect (less than or equal to 50/s per publisher x less than or equal to 5 publishers = 250/s, well under what a JSONL append handles).
351
-
352
- 7. **Notifying about edits that don't yet appear as diagnostics.** "B is about to edit foo.ts" requires a separate pre-flight intent signal (`.loki/signals/EDIT_INTENT_*` per `parallel-workflows.md` section "Inter-Stream Communication"). Out of scope; the subscriber's `ranges_overlap` check covers only the post-edit-diagnostic case.
353
-
354
- ## 11. File-level work breakdown
355
-
356
- New files:
357
- - `mcp/lsp_broadcast.py` -- publisher, subscriber MCP tools, shared-root resolver. ~250 LOC.
358
- - `tests/test_lsp_broadcast.py` -- unit tests with fake LSPClient. ~200 LOC.
359
- - `tests/test-lsp-broadcast.sh` -- 5-publisher subprocess harness. ~80 LOC.
360
- - `tests/manual/lsp-broadcast-real-agents.md` -- manual release test runbook. ~60 LOC.
361
-
362
- Modified files:
363
- - `mcp/lsp_proxy.py`:
364
- - Add notification-reader thread to `LSPClient` (~150 LOC delta).
365
- - Populate `self.pending_diagnostics` (fixes section 3 orphan).
366
- - Wire optional `_broadcast_publisher` hook in `_on_notification`.
367
- - Register the two new MCP tools by importing from `mcp/lsp_broadcast.py`.
368
- - `events/bus.py`:
369
- - Document that `.loki/events/lsp/` is reserved for LSP diagnostics and is not consumed by the standard `pending/` flow (or move LSP under its own subtype if we later want dashboard surfacing).
370
- - `autonomy/lib/mcp-config.sh`:
371
- - When `LOKI_LSP_BROADCAST=1` is in env at config-generation time, add the env var to the lsp-proxy server entry so it propagates to the spawned MCP process.
372
- - `skills/parallel-workflows.md`:
373
- - New section "Cross-worktree LSP diagnostics" describing the contract, the env var, the `.loki/parallel-root` marker, and the subscriber pattern.
374
- - `CHANGELOG.md`:
375
- - Closes v7.7.0 acceptance criterion #3.
376
-
377
- ## 12. Open questions for the future planning session
378
-
379
- 1. Should `workspace_id` include the git branch name? Argument for: easier debugging. Argument against: leaks branch names into events; subscribers should filter by relpath, not branch.
380
- 2. Should we surface broadcast events to the dashboard? Probably yes (a small "diagnostics from peer worktrees" panel) but that is a v7.8.x concern.
381
- 3. Should `lsp_subscribe_diagnostics` use long-poll or SSE-style streaming? Long-poll is simpler and matches the existing event bus pattern; streaming requires MCP transport changes. Recommend long-poll for v1.
382
- 4. Should the publisher emit a heartbeat event every N seconds so subscribers can detect a silent publisher death (vs. "no diagnostics to publish")? Probably yes, 30 s interval, severity=info, file_relpath=null.
383
- 5. Are we OK with the silent-skip philosophy if `.loki/parallel-root` is missing? `loki doctor` will warn but the tool itself returns empty. Consistent with the project's "no env var, no flag, no per-language config" philosophy from v7.7.0.
384
-
385
- ## 13. Acceptance criteria for this work
386
-
387
- When this plan is implemented, the following should be true:
388
-
389
- 1. With `LOKI_LSP_BROADCAST=1` set in two worktrees of the same repo, an edit to `foo.ts` in worktree-A that produces a type error appears as an event in worktree-B's `lsp_subscribe_diagnostics` response within 1 second.
390
- 2. The unit test suite in `tests/test_lsp_broadcast.py` runs in less than 5 s and passes 20+ assertions covering publish, dedup, rotation, cursor reset, and concurrent writers.
391
- 3. The subprocess harness `tests/test-lsp-broadcast.sh` runs 5 publishers + 1 subscriber and asserts cross-workspace event delivery, in less than 15 s.
392
- 4. `lsp_get_diagnostics` returns non-empty diagnostics arrays for a file with real errors (closes the section 3 prerequisite bug).
393
- 5. `loki doctor` reports "LSP broadcast: enabled (shared root: ...)" or "LSP broadcast: disabled (set LOKI_LSP_BROADCAST=1)" in the Integrations block.
394
- 6. With the flag off, behavior is byte-identical to v7.7.0 (verified by running the existing `tests/test-lsp-proxy.sh` unchanged).
@@ -1,152 +0,0 @@
1
- # PRD: Purple Lab Full IDE Experience
2
-
3
- ## Overview
4
-
5
- Transform Purple Lab from a basic file viewer into a production-grade fullstack IDE comparable to Replit, Lovable, and Bolt. Users should be able to build a project via PRD, then immediately browse, edit, preview, and iterate on the output -- all within the browser.
6
-
7
- ## Target Users
8
-
9
- - Developers using Loki Mode to generate projects from PRDs
10
- - Non-technical founders using Purple Lab as their primary development environment
11
- - Teams evaluating Loki Mode for autonomous development workflows
12
-
13
- ## Current State (v6.39.0)
14
-
15
- - React Router with `/project/:sessionId` URLs
16
- - File tree sidebar with collapsible directories
17
- - Code viewer with line numbers (read-only)
18
- - Live preview via iframe serving real files with correct MIME types
19
- - Session state persists across refresh
20
- - WebSocket real-time log streaming during builds
21
-
22
- ## Requirements
23
-
24
- ### P0 -- Must Have
25
-
26
- #### Monaco Editor Integration
27
- - Replace the read-only code viewer with Monaco Editor (VS Code's editor)
28
- - Syntax highlighting for all major languages (JS, TS, Python, HTML, CSS, JSON, MD, Go, Rust)
29
- - Read-only mode by default, editable when user clicks "Edit" button
30
- - File save via `PUT /api/sessions/:id/file?path=` endpoint
31
- - Keyboard shortcut: Cmd/Ctrl+S to save
32
- - Show unsaved changes indicator (dot on tab)
33
-
34
- #### Resizable Split Panes
35
- - Three-pane layout: file tree | editor | preview
36
- - Drag handles between panes to resize
37
- - Pane sizes persist to localStorage
38
- - Collapse/expand each pane via toggle buttons
39
- - Minimum widths: file tree 180px, editor 300px, preview 300px
40
-
41
- #### Integrated Terminal
42
- - xterm.js terminal emulator in a bottom panel
43
- - WebSocket-connected to backend for real-time output
44
- - Resizable height (drag handle)
45
- - Support for ANSI color codes
46
- - Show build output, loki logs, and allow running commands in project directory
47
- - Toggle with Cmd/Ctrl+` keyboard shortcut
48
-
49
- #### File Operations
50
- - Create new file (right-click context menu or button)
51
- - Create new directory
52
- - Rename file/directory (inline edit)
53
- - Delete file/directory (with confirmation)
54
- - All operations via REST API with path traversal protection
55
-
56
- ### P1 -- Should Have
57
-
58
- #### Tab System
59
- - Open multiple files in tabs
60
- - Close tabs (with unsaved changes warning)
61
- - Tab reordering via drag
62
- - "Close All" and "Close Others" context menu
63
- - Active tab highlighted
64
-
65
- #### Search
66
- - Cmd/Ctrl+P: fuzzy file search (like VS Code quick open)
67
- - Cmd/Ctrl+Shift+F: search across all files in project
68
- - Search results panel with file + line previews
69
- - Click result to open file at line
70
-
71
- #### Preview Enhancements
72
- - URL bar showing current preview path
73
- - Back/forward navigation buttons
74
- - Refresh button
75
- - Open in new tab button
76
- - Console panel showing preview JS errors (via postMessage from sandboxed iframe)
77
- - Auto-refresh on file save
78
-
79
- ### P2 -- Nice to Have
80
-
81
- #### Git Integration
82
- - Show git status in file tree (modified, added, untracked)
83
- - Diff view for modified files
84
- - Commit from UI
85
- - Branch indicator in header
86
-
87
- #### AI Chat Panel
88
- - Side panel for chat with Claude about the project
89
- - Context-aware: knows which file is open, what project is about
90
- - "Fix this" button on error highlights
91
- - "Explain" button on selected code
92
-
93
- #### Deployment
94
- - "Deploy" button that runs loki deploy
95
- - Deploy status indicator
96
- - Preview deployed URL
97
-
98
- ## Technical Architecture
99
-
100
- ### Frontend
101
- - React 19 + TypeScript + Tailwind CSS (existing stack)
102
- - Monaco Editor: `@monaco-editor/react` package
103
- - xterm.js: `@xterm/xterm` + `@xterm/addon-fit` + `@xterm/addon-web-links`
104
- - Split panes: custom implementation with CSS resize or `react-resizable-panels`
105
- - File operations: REST API calls to new CRUD endpoints
106
-
107
- ### Backend (web-app/server.py)
108
- - `PUT /api/sessions/:id/file?path=` -- save file content
109
- - `POST /api/sessions/:id/file` -- create new file
110
- - `DELETE /api/sessions/:id/file?path=` -- delete file
111
- - `POST /api/sessions/:id/directory` -- create directory
112
- - `POST /api/sessions/:id/rename` -- rename file/directory
113
- - `GET /api/sessions/:id/search?q=&glob=` -- search files
114
- - WebSocket terminal: `/ws/terminal/:id` -- PTY-backed terminal session
115
-
116
- ### Security
117
- - All file operations scoped to project directory (path traversal protection)
118
- - File size limits on saves (1MB)
119
- - Rate limiting on file operations
120
- - No shell injection in terminal (PTY spawns bash in project dir only)
121
- - Preview iframe sandboxed
122
-
123
- ## Success Criteria
124
-
125
- - User can build a project via PRD, then immediately edit files and see preview update
126
- - Preview loads all relative assets (CSS, JS, images) correctly
127
- - Terminal shows real-time build output and allows running commands
128
- - All operations work without page refresh
129
- - Performance: file tree renders 1000+ files without freezing
130
- - Mobile: graceful degradation (single-pane view)
131
-
132
- ## Non-Goals
133
-
134
- - Multi-user collaboration (Google Docs style)
135
- - Version control UI beyond basic git status
136
- - Package manager integration (npm/pip install from UI)
137
- - Database management UI
138
- - Cloud deployment orchestration
139
-
140
- ## Timeline
141
-
142
- - P0: 2-3 days focused development
143
- - P1: 2 days
144
- - P2: Future sprints
145
-
146
- ## References
147
-
148
- - Replit: https://replit.com
149
- - Lovable: https://lovable.dev
150
- - Bolt: https://bolt.new
151
- - Monaco Editor: https://microsoft.github.io/monaco-editor/
152
- - xterm.js: https://xtermjs.org/