ltcai 4.3.0 → 4.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +186 -276
  2. package/bin/ltcai.js +6 -2
  3. package/docs/CHANGELOG.md +124 -3
  4. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
  5. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
  6. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
  7. package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
  8. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
  9. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
  10. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
  11. package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
  12. package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
  13. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -25
  14. package/frontend/openapi.json +11 -1
  15. package/frontend/src/App.tsx +15 -1
  16. package/frontend/src/api/client.ts +19 -1
  17. package/frontend/src/api/openapi.ts +10 -0
  18. package/frontend/src/components/primitives.tsx +92 -10
  19. package/frontend/src/pages/Act.tsx +72 -9
  20. package/frontend/src/pages/Ask.tsx +2 -2
  21. package/frontend/src/pages/Brain.tsx +607 -65
  22. package/frontend/src/pages/Capture.tsx +11 -7
  23. package/frontend/src/pages/Library.tsx +12 -6
  24. package/frontend/src/pages/System.tsx +186 -23
  25. package/lattice_brain/__init__.py +1 -1
  26. package/lattice_brain/archive.py +3 -3
  27. package/lattice_brain/storage/sqlite.py +15 -2
  28. package/latticeai/__init__.py +1 -1
  29. package/latticeai/api/agents.py +3 -1
  30. package/latticeai/api/models.py +66 -18
  31. package/latticeai/brain/projection.py +12 -2
  32. package/latticeai/brain/retrieval.py +10 -0
  33. package/latticeai/brain/store.py +6 -1
  34. package/latticeai/core/config.py +3 -1
  35. package/latticeai/core/marketplace.py +1 -1
  36. package/latticeai/core/multi_agent.py +1 -1
  37. package/latticeai/core/product_hardening.py +2 -1
  38. package/latticeai/core/workspace_os.py +1 -1
  39. package/latticeai/services/agent_runtime.py +52 -12
  40. package/latticeai/services/model_runtime.py +83 -2
  41. package/ltcai_cli.py +14 -3
  42. package/package.json +5 -7
  43. package/requirements.txt +17 -0
  44. package/scripts/build_vercel_static.mjs +77 -0
  45. package/scripts/check_markdown_links.mjs +75 -0
  46. package/src-tauri/Cargo.lock +1 -1
  47. package/src-tauri/Cargo.toml +1 -1
  48. package/src-tauri/src/main.rs +269 -27
  49. package/src-tauri/tauri.conf.json +20 -1
  50. package/static/app/asset-manifest.json +5 -5
  51. package/static/app/assets/index-CHHal8Zl.css +2 -0
  52. package/static/app/assets/index-pdzil9ac.js +333 -0
  53. package/static/app/assets/index-pdzil9ac.js.map +1 -0
  54. package/static/app/index.html +2 -2
  55. package/latticeai/api/deps.py +0 -15
  56. package/scripts/capture/README.md +0 -28
  57. package/scripts/capture/capture_enterprise.js +0 -8
  58. package/scripts/capture/capture_graph.js +0 -8
  59. package/scripts/capture/capture_onboarding.js +0 -8
  60. package/scripts/capture/capture_page.js +0 -43
  61. package/scripts/capture/capture_release_media.js +0 -125
  62. package/scripts/capture/capture_skills.js +0 -8
  63. package/scripts/capture/capture_v340.js +0 -88
  64. package/scripts/capture/capture_workspace.js +0 -8
  65. package/scripts/generate_diagrams.py +0 -512
  66. package/scripts/release-0.3.1.sh +0 -105
  67. package/scripts/take_screenshots.js +0 -69
  68. package/static/app/assets/index-RiJTJliG.js +0 -333
  69. package/static/app/assets/index-RiJTJliG.js.map +0 -1
  70. package/static/app/assets/index-yZswHE3d.css +0 -2
  71. package/static/css/tokens.3ba22e37.css +0 -260
@@ -1,512 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Generate Lattice AI documentation diagrams (PNG) + hero frames.
3
-
4
- These are *structural diagrams* rendered from the live codebase — the
5
- architecture layers, the onboarding flow, the real tri-state model
6
- recommendation output, the real Knowledge Graph node/edge taxonomy, the
7
- workspace/role model, and the skill-marketplace structure. They are deliberately
8
- diagrams (not UI screenshots) so they stay accurate and reproducible in CI.
9
-
10
- Run from the repo root:
11
-
12
- python scripts/generate_diagrams.py
13
-
14
- Outputs to ``docs/images/`` (and frame PNGs to ``docs/images/tmp_frames/`` which
15
- the hero GIF is assembled from via ffmpeg by the caller).
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import sys
21
- from pathlib import Path
22
-
23
- from PIL import Image, ImageDraw, ImageFont
24
-
25
- ROOT = Path(__file__).resolve().parent.parent
26
- sys.path.insert(0, str(ROOT))
27
- OUT = ROOT / "docs" / "images"
28
- FRAMES = OUT / "tmp_frames"
29
-
30
- # ── design system ─────────────────────────────────────────────────────────────
31
- BG = (11, 17, 32)
32
- PANEL = (23, 33, 58)
33
- PANEL_LT = (32, 45, 74)
34
- BORDER = (51, 65, 95)
35
- TEXT = (226, 232, 240)
36
- MUTED = (148, 163, 184)
37
- ACCENT = (124, 92, 255)
38
- ACCENT2 = (56, 189, 248)
39
- GREEN = (34, 197, 94)
40
- AMBER = (245, 158, 11)
41
- GRAY = (107, 114, 128)
42
-
43
- _F = "/System/Library/Fonts/Supplemental/Arial.ttf"
44
- _FB = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
45
- _FM = "/System/Library/Fonts/Menlo.ttc"
46
-
47
-
48
- def font(size, bold=False, mono=False):
49
- try:
50
- return ImageFont.truetype(_FM if mono else (_FB if bold else _F), size)
51
- except Exception:
52
- return ImageFont.load_default()
53
-
54
-
55
- def canvas(w, h, bg=BG):
56
- img = Image.new("RGB", (w, h), bg)
57
- return img, ImageDraw.Draw(img)
58
-
59
-
60
- def rrect(d, box, r, fill=None, outline=None, width=2):
61
- d.rounded_rectangle(box, radius=r, fill=fill, outline=outline, width=width)
62
-
63
-
64
- def measure(d, s, f):
65
- b = d.textbbox((0, 0), s, font=f)
66
- return b[2] - b[0], b[3] - b[1]
67
-
68
-
69
- def text(d, xy, s, f, fill=TEXT, anchor="la"):
70
- d.text(xy, s, font=f, fill=fill, anchor=anchor)
71
-
72
-
73
- def ctext(d, cx, y, s, f, fill=TEXT):
74
- w, _ = measure(d, s, f)
75
- d.text((cx - w / 2, y), s, font=f, fill=fill)
76
-
77
-
78
- def badge(d, x, y, label, color, fsize=15):
79
- f = font(fsize, bold=True)
80
- w, h = measure(d, label, f)
81
- pad = 9
82
- box = [x, y, x + w + pad * 2, y + h + 9]
83
- rrect(d, box, (h + 9) / 2, fill=color)
84
- text(d, (x + pad, y + 4), label, f, fill=(11, 17, 32) if color in (GREEN, AMBER) else (255, 255, 255))
85
- return box[2] - box[0]
86
-
87
-
88
- def watermark(d, w, h):
89
- f = font(15, bold=True)
90
- text(d, (w - 200, h - 30), "Lattice AI", f, fill=MUTED)
91
- d.ellipse([w - 222, h - 27, w - 208, h - 13], fill=ACCENT)
92
-
93
-
94
- def header(d, w, title, subtitle):
95
- text(d, (44, 34), title, font(34, bold=True), fill=TEXT)
96
- text(d, (44, 80), subtitle, font(18), fill=MUTED)
97
- d.line([(44, 116), (w - 44, 116)], fill=BORDER, width=2)
98
-
99
-
100
- def save(img, name):
101
- OUT.mkdir(parents=True, exist_ok=True)
102
- p = OUT / name
103
- img.save(p)
104
- print("wrote", p.relative_to(ROOT))
105
-
106
-
107
- def wrap(d, s, f, max_w):
108
- words, lines, cur = s.split(), [], ""
109
- for word in words:
110
- trial = (cur + " " + word).strip()
111
- if measure(d, trial, f)[0] <= max_w:
112
- cur = trial
113
- else:
114
- if cur:
115
- lines.append(cur)
116
- cur = word
117
- if cur:
118
- lines.append(cur)
119
- return lines
120
-
121
-
122
- # ── 1. architecture ───────────────────────────────────────────────────────────
123
- def diagram_architecture():
124
- w, h = 1680, 1080
125
- img, d = canvas(w, h)
126
- header(d, w, "Lattice AI — Architecture (v1.5.0)",
127
- "Thin compat entrypoint · modular FastAPI app · routers + services + core · local engines & knowledge graph")
128
-
129
- def band(y, bh, title, items, accent, item_color=PANEL_LT):
130
- rrect(d, [44, y, w - 44, y + bh], 14, fill=PANEL, outline=BORDER, width=2)
131
- text(d, (60, y + 12), title, font(17, bold=True), fill=accent)
132
- bx = 60
133
- by = y + 44
134
- fi = font(14, mono=True)
135
- for it in items:
136
- tw = measure(d, it, fi)[0] + 22
137
- if bx + tw > w - 60:
138
- bx = 60
139
- by += 38
140
- rrect(d, [bx, by, bx + tw, by + 28], 7, fill=item_color, outline=BORDER, width=1)
141
- text(d, (bx + 11, by + 6), it, fi, fill=TEXT)
142
- bx += tw + 10
143
-
144
- def arrow_down(cx, y1, y2):
145
- d.line([(cx, y1), (cx, y2)], fill=ACCENT2, width=3)
146
- d.polygon([(cx - 6, y2 - 9), (cx + 6, y2 - 9), (cx, y2)], fill=ACCENT2)
147
-
148
- band(140, 92, "CLIENTS",
149
- ["Web UI (static/)", "VS Code / Cursor (vscode-extension/)", "Telegram bot", "MCP clients", "PWA"],
150
- ACCENT2)
151
- arrow_down(w // 2, 232, 258)
152
-
153
- band(258, 78, "ENTRYPOINT",
154
- ["server.py → server:app (thin compat)", "latticeai/server_app.py (FastAPI assembly · lifespan · middleware · static mount · router wiring)"],
155
- ACCENT)
156
- arrow_down(w // 2, 336, 362)
157
-
158
- band(362, 130, "API ROUTERS latticeai/api/",
159
- ["chat", "models", "workspace", "mcp", "admin", "auth", "tools", "computer_use",
160
- "local_files", "permissions", "garden", "setup", "static_routes", "health", "security_dashboard"],
161
- ACCENT2)
162
- arrow_down(w // 2, 492, 518)
163
-
164
- band(518, 112, "SERVICES latticeai/services/",
165
- ["model_runtime", "model_catalog", "model_recommendation", "tool_dispatch", "upload_service",
166
- "chat_service", "workspace_service", "model_service", "app_context"],
167
- ACCENT)
168
- arrow_down(w // 2, 630, 656)
169
-
170
- band(656, 130, "CORE latticeai/core/",
171
- ["workspace_os", "tool_registry", "agent", "enterprise", "enterprise_admin", "security",
172
- "sessions", "audit", "config", "model_compat", "model_resolution", "graph_curator"],
173
- ACCENT2)
174
- arrow_down(w // 2, 786, 812)
175
-
176
- # bottom: engines + graph + storage as three columns
177
- cols = [
178
- ("ENGINES llm_router.py", ["MLX (Apple Silicon)", "Ollama", "vLLM", "llama.cpp", "LM Studio", "OpenAI-compatible"]),
179
- ("KNOWLEDGE GRAPH", ["knowledge_graph.py", "KGStore v2 (SQLite)", "Graph RAG", "graph_curator"]),
180
- ("STORAGE / MCP", ["~/.ltcai/ (local)", "~/.ltcai-brain/", "mcp_registry.py", "skills/"]),
181
- ]
182
- cw = (w - 44 * 2 - 24 * 2) // 3
183
- for i, (title, items) in enumerate(cols):
184
- x = 44 + i * (cw + 24)
185
- rrect(d, [x, 812, x + cw, 980], 14, fill=PANEL, outline=BORDER, width=2)
186
- text(d, (x + 16, 824), title, font(15, bold=True), fill=ACCENT)
187
- fy = 858
188
- fi = font(14, mono=True)
189
- for it in items:
190
- d.ellipse([x + 16, fy + 5, x + 24, fy + 13], fill=ACCENT2)
191
- text(d, (x + 32, fy), it, fi, fill=TEXT)
192
- fy += 28
193
-
194
- watermark(d, w, h)
195
- save(img, "architecture.png")
196
-
197
-
198
- # ── 2. onboarding flow ──────────────────────────────────────────────────────--
199
- def diagram_onboarding():
200
- w, h = 1680, 620
201
- img, d = canvas(w, h)
202
- header(d, w, "Onboarding Flow",
203
- "From install to first chat — system scan and model recommendation are built in")
204
- steps = [
205
- ("1", "Install", "pip install ltcai", ACCENT2),
206
- ("2", "System Scan", "OS · CPU · GPU · RAM · Disk", ACCENT),
207
- ("3", "Model Recommendation", "Recommended / Compatible / Not", GREEN),
208
- ("4", "Workspace", "Personal or Organization", ACCENT2),
209
- ("5", "Indexing", "approve local folders", ACCENT),
210
- ("6", "Knowledge Graph", "nodes & edges auto-built", ACCENT2),
211
- ("7", "First Chat", "graph-aware answers", GREEN),
212
- ]
213
- n = len(steps)
214
- margin = 44
215
- gap = 22
216
- bw = (w - margin * 2 - gap * (n - 1)) // n
217
- y = 200
218
- bh = 220
219
- for i, (num, title, sub, accent) in enumerate(steps):
220
- x = margin + i * (bw + gap)
221
- rrect(d, [x, y, x + bw, y + bh], 14, fill=PANEL, outline=BORDER, width=2)
222
- d.ellipse([x + bw / 2 - 22, y + 22, x + bw / 2 + 22, y + 66], fill=accent)
223
- ctext(d, x + bw / 2, y + 30, num, font(24, bold=True), fill=(11, 17, 32))
224
- for j, line in enumerate(wrap(d, title, font(17, bold=True), bw - 20)):
225
- ctext(d, x + bw / 2, y + 90 + j * 24, line, font(17, bold=True), fill=TEXT)
226
- for j, line in enumerate(wrap(d, sub, font(13), bw - 24)):
227
- ctext(d, x + bw / 2, y + 150 + j * 20, line, font(13), fill=MUTED)
228
- if i < n - 1:
229
- ax = x + bw + 4
230
- d.line([(ax, y + bh / 2), (ax + gap - 8, y + bh / 2)], fill=ACCENT2, width=3)
231
- d.polygon([(ax + gap - 12, y + bh / 2 - 6), (ax + gap - 12, y + bh / 2 + 6), (ax + gap - 2, y + bh / 2)], fill=ACCENT2)
232
- text(d, (margin, y + bh + 40),
233
- "Each step writes onboarding state via /workspace/onboarding/* so progress is resumable.",
234
- font(15), fill=MUTED)
235
- watermark(d, w, h)
236
- save(img, "onboarding.png")
237
-
238
-
239
- # ── 3. model recommendation (live data) ─────────────────────────────────────--
240
- def diagram_model_recommendation():
241
- from latticeai.services.model_recommendation import recommend_catalog, RECOMMENDED, COMPATIBLE
242
- # Representative machine: 32 GB Apple Silicon.
243
- profile = {"os": "darwin", "arch": "arm64", "ram_mb": 32 * 1024,
244
- "gpu": {"vendor": "apple", "vram_mb": 32 * 1024}}
245
- rec = recommend_catalog(profile, engine="local_mlx")
246
-
247
- w, h = 1680, 1020
248
- img, d = canvas(w, h)
249
- header(d, w, "Local Model Recommendation",
250
- "Hardware-aware tri-state classification — example: 32 GB Apple Silicon (MLX)")
251
-
252
- # legend
253
- lx = 44
254
- lx += badge(d, lx, 130, "Recommended", GREEN) + 14
255
- lx += badge(d, lx, 130, "Compatible", AMBER) + 14
256
- lx += badge(d, lx, 130, "Not Recommended", GRAY) + 14
257
- counts = rec["counts"]
258
- text(d, (w - 520, 134),
259
- f"recommended {counts['recommended']} · compatible {counts['compatible']} · not {counts['not_recommended']}",
260
- font(15, mono=True), fill=MUTED)
261
-
262
- # show top families per directive
263
- want = ["Gemma 4", "Qwen3-VL", "Llama 4"]
264
- fams = [f for f in rec["families"] if f["family"] in want][:6]
265
- y = 176
266
- fi = font(15)
267
- fm = font(13, mono=True)
268
- for fam in fams:
269
- models = fam["models"][:5]
270
- rh = 40 + len(models) * 30 + 16
271
- rrect(d, [44, y, w - 44, y + rh], 12, fill=PANEL, outline=BORDER, width=2)
272
- text(d, (60, y + 12), fam["family"], font(18, bold=True), fill=ACCENT2)
273
- best = fam.get("best")
274
- if best:
275
- badge(d, 60 + measure(d, fam["family"], font(18, bold=True))[0] + 18, y + 12,
276
- "best: " + (best["name"] or ""), GREEN, fsize=13)
277
- ry = y + 46
278
- for m in models:
279
- color = {RECOMMENDED: GREEN, COMPATIBLE: AMBER}.get(m["status"], GRAY)
280
- d.ellipse([62, ry + 5, 74, ry + 17], fill=color)
281
- text(d, (86, ry), m["name"] or m["id"], fi, fill=TEXT)
282
- text(d, (w - 360, ry), str(m["size"] or ""), fm, fill=MUTED)
283
- badge(d, w - 230, ry - 2, {"recommended": "Recommended", "compatible": "Compatible"}.get(m["status"], "Not Rec."), color, fsize=12)
284
- ry += 30
285
- y += rh + 14
286
-
287
- watermark(d, w, h)
288
- save(img, "model-recommendation.png")
289
-
290
-
291
- # ── 4. workspace model ──────────────────────────────────────────────────────--
292
- def diagram_workspace():
293
- w, h = 1680, 760
294
- img, d = canvas(w, h)
295
- header(d, w, "Workspaces — Personal & Organization",
296
- "Switch instantly · workspace-scoped data · explicit active-workspace state")
297
-
298
- def col(x, title, accent, lines):
299
- cw = (w - 44 * 2 - 40) // 2
300
- rrect(d, [x, 150, x + cw, 600], 16, fill=PANEL, outline=accent, width=2)
301
- text(d, (x + 22, 168), title, font(22, bold=True), fill=accent)
302
- fy = 218
303
- for icon, t, sub in lines:
304
- d.ellipse([x + 22, fy + 4, x + 38, fy + 20], fill=accent)
305
- text(d, (x + 52, fy), t, font(16, bold=True), fill=TEXT)
306
- text(d, (x + 52, fy + 24), sub, font(13), fill=MUTED)
307
- fy += 62
308
- return cw
309
-
310
- cw = col(44, "Personal Workspace", ACCENT2, [
311
- ("", "Single owner", "no-auth local owner fallback"),
312
- ("", "Private data", "snapshots · memory · agents · traces"),
313
- ("", "Local-first", "stored under ~/.ltcai"),
314
- ("", "Instant default", "active on first run"),
315
- ])
316
- col(44 + cw + 40, "Organization Workspace", ACCENT, [
317
- ("", "Roles", "owner · admin · member · viewer"),
318
- ("", "Shared & scoped", "data carries workspace_id"),
319
- ("", "Membership", "invite, manage, archive (non-destructive)"),
320
- ("", "Visibility", "active workspace + role shown in header"),
321
- ])
322
-
323
- rrect(d, [44, 624, w - 44, 700], 12, fill=PANEL_LT, outline=BORDER, width=2)
324
- text(d, (60, 636), "Scoping", font(15, bold=True), fill=ACCENT2)
325
- text(d, (60, 662),
326
- "Reads/writes resolve scope via the X-Workspace-Id header → WorkspaceService gate "
327
- "(non-members blocked, viewers read-only, owners/admins manage).",
328
- font(14, mono=True), fill=TEXT)
329
- watermark(d, w, h)
330
- save(img, "workspace.png")
331
-
332
-
333
- # ── 5. knowledge graph taxonomy (live data) ─────────────────────────────────--
334
- def diagram_graph():
335
- import kg_schema as k
336
- nodes = [e.value.title().replace("_", " ") for e in k.NodeType][:18]
337
- edges = [e.value.lower() for e in k.EdgeType][:18]
338
-
339
- w, h = 1680, 820
340
- img, d = canvas(w, h)
341
- header(d, w, "Knowledge Graph — Taxonomy",
342
- f"{len(list(k.NodeType))} node types · {len(list(k.EdgeType))} edge types · auto-built from chats, files & folders")
343
-
344
- # center hub + sample relationship
345
- cx, cy = w // 2, 300
346
- rrect(d, [cx - 90, cy - 34, cx + 90, cy + 34], 16, fill=ACCENT, outline=None)
347
- ctext(d, cx, cy - 12, "Document", font(18, bold=True), fill=(255, 255, 255))
348
- sat = [("Concept", -360, -120, ACCENT2, "mentions"),
349
- ("Person", 360, -120, ACCENT2, "authored_by"),
350
- ("Chat", -360, 120, GREEN, "references"),
351
- ("Task", 360, 120, AMBER, "contains")]
352
- for label, dx, dy, color, rel in sat:
353
- sx, sy = cx + dx, cy + dy
354
- d.line([(cx, cy), (sx, sy)], fill=BORDER, width=2)
355
- mx, my = (cx + sx) / 2, (cy + sy) / 2
356
- text(d, (mx, my - 18), rel, font(12, mono=True), fill=MUTED, anchor="ma")
357
- rrect(d, [sx - 70, sy - 26, sx + 70, sy + 26], 12, fill=PANEL, outline=color, width=2)
358
- ctext(d, sx, sy - 9, label, font(15, bold=True), fill=color)
359
-
360
- # node / edge type chips
361
- def chips(y, title, items, color):
362
- text(d, (44, y), title, font(16, bold=True), fill=color)
363
- bx, by = 44, y + 30
364
- fi = font(13, mono=True)
365
- for it in items:
366
- tw = measure(d, it, fi)[0] + 20
367
- if bx + tw > w - 44:
368
- bx = 44
369
- by += 34
370
- rrect(d, [bx, by, bx + tw, by + 26], 7, fill=PANEL, outline=color, width=1)
371
- text(d, (bx + 10, by + 5), it, fi, fill=TEXT)
372
- bx += tw + 9
373
- return by + 40
374
-
375
- ny = chips(470, "Node types", nodes, ACCENT2)
376
- chips(ny + 6, "Edge types", edges, GREEN)
377
- watermark(d, w, h)
378
- save(img, "graph.png")
379
-
380
-
381
- # ── 6. organization roles × permissions ─────────────────────────────────────--
382
- def diagram_organization():
383
- w, h = 1680, 720
384
- img, d = canvas(w, h)
385
- header(d, w, "Organization — Roles & Permissions",
386
- "Member visibility, role clarity, and a transparent permission matrix")
387
-
388
- roles = ["owner", "admin", "member", "viewer"]
389
- perms = ["read", "write", "manage_members", "manage_workspace"]
390
- grant = {
391
- "owner": {"read", "write", "manage_members", "manage_workspace"},
392
- "admin": {"read", "write", "manage_members"},
393
- "member": {"read", "write"},
394
- "viewer": {"read"},
395
- }
396
- x0, y0 = 60, 180
397
- col_w, row_h = 360, 92
398
- # header row
399
- for j, p in enumerate(perms):
400
- cx = x0 + col_w + j * 300 + 150
401
- ctext(d, cx, y0 - 4, p, font(15, bold=True), fill=ACCENT2)
402
- for i, r in enumerate(roles):
403
- ry = y0 + 30 + i * row_h
404
- rrect(d, [x0, ry, x0 + col_w, ry + row_h - 16], 12, fill=PANEL, outline=ACCENT, width=2)
405
- text(d, (x0 + 18, ry + 16), r, font(20, bold=True), fill=TEXT)
406
- text(d, (x0 + 18, ry + 46), {"owner": "full control", "admin": "manage members",
407
- "member": "create & edit", "viewer": "read-only"}[r], font(13), fill=MUTED)
408
- for j, p in enumerate(perms):
409
- cx = x0 + col_w + j * 300 + 150
410
- cy = ry + (row_h - 16) / 2
411
- if p in grant[r]:
412
- d.ellipse([cx - 16, cy - 16, cx + 16, cy + 16], fill=GREEN)
413
- ctext(d, cx, cy - 12, "✓", font(20, bold=True), fill=(11, 17, 32))
414
- else:
415
- d.ellipse([cx - 16, cy - 16, cx + 16, cy + 16], outline=GRAY, width=2)
416
- watermark(d, w, h)
417
- save(img, "organization.png")
418
-
419
-
420
- # ── 7. skill marketplace ────────────────────────────────────────────────────--
421
- def diagram_skills():
422
- w, h = 1680, 720
423
- img, d = canvas(w, h)
424
- header(d, w, "Skill Marketplace",
425
- "Browse, install, and keep skills current — Recommended · Popular · Installed · Updates")
426
-
427
- tabs = [("Recommended", ACCENT2), ("Popular", ACCENT), ("Installed", GREEN), ("Updates Available", AMBER)]
428
- tx = 44
429
- for label, color in tabs:
430
- tw = measure(d, label, font(16, bold=True))[0] + 36
431
- rrect(d, [tx, 150, tx + tw, 196], 10, fill=PANEL, outline=color, width=2)
432
- text(d, (tx + 18, 162), label, font(16, bold=True), fill=color)
433
- tx += tw + 14
434
-
435
- cards = [
436
- ("code-reviewer", "Recommended", ACCENT2, "Review diffs for bugs & risks"),
437
- ("docs-writer", "Popular", ACCENT, "Generate project documentation"),
438
- ("changelog-generator", "Installed", GREEN, "Changelog from git history"),
439
- ("security-review", "Updates Available", AMBER, "Scan code for vulnerabilities"),
440
- ("react-best-practices", "Recommended", ACCENT2, "React/Next performance"),
441
- ("deep-research", "Popular", ACCENT, "Multi-source cited research"),
442
- ]
443
- cw, ch, gap = 520, 150, 24
444
- for i, (name, tag, color, desc) in enumerate(cards):
445
- cxi = i % 3
446
- cyi = i // 3
447
- x = 44 + cxi * (cw + gap)
448
- y = 230 + cyi * (ch + gap)
449
- rrect(d, [x, y, x + cw, y + ch], 14, fill=PANEL, outline=BORDER, width=2)
450
- text(d, (x + 20, y + 18), name, font(19, bold=True), fill=TEXT)
451
- badge(d, x + 20, y + 54, tag, color, fsize=12)
452
- for j, line in enumerate(wrap(d, desc, font(14), cw - 40)):
453
- text(d, (x + 20, y + 92 + j * 20), line, font(14), fill=MUTED)
454
- text(d, (44, 230 + 2 * (ch + gap)),
455
- "Lifecycle: install · enable · disable · update · uninstall (admin-gated, audited via /workspace/skills/*)",
456
- font(15), fill=MUTED)
457
- watermark(d, w, h)
458
- save(img, "skills.png")
459
-
460
-
461
- # ── 8. hero frames (assembled into hero.gif by ffmpeg) ──────────────────────--
462
- def hero_frames():
463
- FRAMES.mkdir(parents=True, exist_ok=True)
464
- stages = [
465
- ("Local files · chats · folders", "your workspace, indexed locally", ACCENT2),
466
- ("Automatic Knowledge Graph", "nodes & edges built from real work", ACCENT),
467
- ("Graph-aware chat & agents", "answers grounded in your memory", GREEN),
468
- ("One local AI Workspace OS", "private · local-first · yours", ACCENT2),
469
- ]
470
- w, h = 1280, 640
471
- for idx in range(len(stages)):
472
- img, d = canvas(w, h)
473
- # title
474
- ctext(d, w // 2, 60, "Lattice AI", font(40, bold=True), fill=TEXT)
475
- ctext(d, w // 2, 112, "AI Workspace OS for local-first graph, memory & agents", font(18), fill=MUTED)
476
- # pipeline dots
477
- n = len(stages)
478
- cxs = [w // 2 - 420, w // 2 - 140, w // 2 + 140, w // 2 + 420]
479
- labels = ["Files", "Graph", "Chat", "OS"]
480
- for i in range(n):
481
- active = i <= idx
482
- color = stages[i][2] if active else PANEL_LT
483
- r = 30 if active else 22
484
- d.ellipse([cxs[i] - r, 230 - r, cxs[i] + r, 230 + r], fill=color)
485
- ctext(d, cxs[i], 270, labels[i], font(15, bold=True), fill=TEXT if active else MUTED)
486
- if i < n - 1:
487
- lc = ACCENT2 if i < idx else BORDER
488
- d.line([(cxs[i] + 34, 230), (cxs[i + 1] - 34, 230)], fill=lc, width=4)
489
- # active stage card
490
- title, sub, color = stages[idx]
491
- rrect(d, [w // 2 - 460, 360, w // 2 + 460, 520], 18, fill=PANEL, outline=color, width=3)
492
- ctext(d, w // 2, 396, title, font(30, bold=True), fill=color)
493
- ctext(d, w // 2, 446, sub, font(18), fill=TEXT)
494
- watermark(d, w, h)
495
- p = FRAMES / f"frame_{idx:02d}.png"
496
- img.save(p)
497
- print("wrote", p.relative_to(ROOT))
498
-
499
-
500
- def main():
501
- diagram_architecture()
502
- diagram_onboarding()
503
- diagram_model_recommendation()
504
- diagram_workspace()
505
- diagram_graph()
506
- diagram_organization()
507
- diagram_skills()
508
- hero_frames()
509
-
510
-
511
- if __name__ == "__main__":
512
- main()
@@ -1,105 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Release 0.3.1 — finalize commit + push.
3
- #
4
- # 이 스크립트는 sandbox 권한 제약으로 Claude가 마지막 git 명령을 끝내지 못했기 때문에
5
- # 사용자 본인 터미널에서 한 번 실행하기 위한 마무리 스크립트입니다.
6
- #
7
- # 사전 조건:
8
- # - 5개 피드백을 반영한 코드/문서/테스트/CI 변경이 워킹트리에 적용되어 있음
9
- # - 빌드 산출물(dist/ltcai-0.3.1*, ltcai-0.3.1.tgz)이 생성되어 있음 (gitignore라 commit에는 안 들어감)
10
- #
11
- # 사용법:
12
- # bash scripts/release-0.3.1.sh
13
-
14
- set -euo pipefail
15
-
16
- cd "$(dirname "$0")/.."
17
-
18
- # 1. stale lock 정리
19
- if [ -f .git/index.lock ]; then
20
- rm -f .git/index.lock
21
- fi
22
-
23
- # 2. 변경된 파일 add
24
- git add -A
25
-
26
- # 3. 어떤 게 들어가는지 확인용 짧은 요약
27
- echo "--- staged summary ---"
28
- git diff --cached --stat | tail -40
29
-
30
- # 4. commit
31
- git commit -m "Release 0.3.1 — model loading reliability + auto graph + security console
32
-
33
- External review (5 items) reflected:
34
-
35
- 1. lattice_ai_model_recommend_download_load_issue.txt
36
- - latticeai/core/model_resolution.py: ModelResolution unifies
37
- input_id / engine / resolved_model / download_id / load_id /
38
- expected_current across all stages.
39
- - prepare_and_load_model() + /engines/prepare-model/stream now share
40
- the same ModelResolution; LM Studio instance_id is reconciled via
41
- resolution.update_after_load().
42
-
43
- 2. lattice_ai_manual_model_select_auto_download_load_fix.txt
44
- - server.py runs _smoke_test_loaded_model() right after load and
45
- returns ready_to_chat / compatibility_status / smoke_test in the
46
- response. Cloud models are skipped to avoid user cost.
47
- - /models response now carries engine_options + compat_profiles.
48
- - chat.js trusts response.current (not the clicked model id);
49
- surfaces compatibility warnings; selectModelByCard() helper.
50
-
51
- 3. lattice_ai_model_compat_fast_path.txt
52
- - latticeai/core/model_compat.py: family detection, family profiles
53
- (stop tokens, disable_draft, postprocess, generation params),
54
- fast_postprocess, validate_smoke_response, compat_cache. Fast /
55
- Slow / Recovery path structure.
56
-
57
- 4. lattice_ai_auto_graph_direction.txt
58
- - latticeai/core/graph_curator.py: topic extraction → alias
59
- clustering (auto-merge) → promotion (with secret/dup/min-sources
60
- filters) → thread story edges → behavior-signal curation.
61
-
62
- 5. lattice_ai_admin_security_dashboard_review.txt
63
- - latticeai/api/security_dashboard.py: 11 endpoints under
64
- /admin/security/* — overview / users / events / event detail /
65
- conversation summary+raw / files / file detail+content / raw /
66
- export. Hard-secret redaction enforced on every response;
67
- admin_view_sensitive_raw audit event recorded for raw access.
68
- - admin.html + admin.js: AI Security & Audit Command Center panel
69
- with Security Overview cards, User Risk stacked bar, sensitive-
70
- type donut, drill-down, raw explorer, JSON/CSV/XLSX/PDF export.
71
-
72
- Tests / CI:
73
- - 28 new unit tests under tests/unit/test_{model_compat,
74
- model_resolution,graph_curator,security_dashboard}.py — all passing.
75
- - .github/workflows/ci.yml syntax-check extended for new modules.
76
- - .github/workflows/release.yml added — tag push v* triggers PyPI /
77
- npm / VS Code Marketplace / Open VSX publish. Secrets:
78
- PYPI_TOKEN, NPM_TOKEN, VSCE_PAT, OVSX_TOKEN (empty ones auto-skip).
79
-
80
- Version bumps:
81
- - package.json 0.3.0 → 0.3.1
82
- - pyproject.toml 0.3.0 → 0.3.1
83
- - vscode-extension/package.json 0.3.0 → 0.3.1
84
-
85
- Docs:
86
- - docs/CHANGELOG.md: 0.3.1 section
87
- - README.md: 'What's new in 0.3.1'
88
- - RELEASE.md: auto-publish via tag flow"
89
-
90
- # 5. push to origin/main
91
- git push origin main
92
-
93
- # 6. annotated release tag (CI release.yml triggers on v* tags)
94
- git tag -a v0.3.1 -m "Lattice AI 0.3.1 — model loading reliability + auto graph + security console"
95
- git push origin v0.3.1
96
-
97
- echo ""
98
- echo "✅ Release 0.3.1 pushed."
99
- echo ""
100
- echo "다음 단계:"
101
- echo " 1. GitHub → Repository Settings → Secrets and variables → Actions에"
102
- echo " PYPI_TOKEN / NPM_TOKEN / VSCE_PAT / OVSX_TOKEN 을 등록."
103
- echo " 2. 등록된 secret이 있는 채널은 .github/workflows/release.yml의 v0.3.1"
104
- echo " trigger로 자동 publish 됩니다. 미등록 채널은 자동 skip."
105
- echo " 3. 수동 publish가 필요하면 RELEASE.md 참고."
@@ -1,69 +0,0 @@
1
- /**
2
- * Lattice AI — automated screenshot capture
3
- * Usage: SESSION_TOKEN=xxx node scripts/take_screenshots.js
4
- */
5
- let playwright;
6
- try { playwright = require('playwright'); } catch(e) {
7
- playwright = require('/tmp/node_modules/playwright');
8
- }
9
- const { chromium } = playwright;
10
- const path = require('path');
11
- const fs = require('fs');
12
-
13
- const BASE = 'http://localhost:4825';
14
- const OUT = path.join(__dirname, '..', 'docs', 'images');
15
- const TOKEN = process.env.SESSION_TOKEN || '';
16
- fs.mkdirSync(OUT, { recursive: true });
17
-
18
- const PAGES = [
19
- { name: 'chat', url: `${BASE}/`, wait: 3500 },
20
- { name: 'admin', url: `${BASE}/admin`, wait: 3000 },
21
- { name: 'graph', url: `${BASE}/graph`, wait: 3500 },
22
- ];
23
-
24
- (async () => {
25
- const browser = await chromium.launch({ headless: true });
26
- const ctx = await browser.newContext({
27
- viewport: { width: 1440, height: 900 },
28
- deviceScaleFactor: 2,
29
- });
30
-
31
- // Inject session cookie so we skip the login page
32
- if (TOKEN) {
33
- await ctx.addCookies([{
34
- name: 'session_token',
35
- value: TOKEN,
36
- domain: 'localhost',
37
- path: '/',
38
- httpOnly: true,
39
- secure: false,
40
- }]);
41
- console.log('🍪 Session cookie injected');
42
- }
43
-
44
- for (const pg of PAGES) {
45
- const page = await ctx.newPage();
46
- console.log(`📸 ${pg.name} → ${pg.url}`);
47
- try {
48
- await page.goto(pg.url, { waitUntil: 'networkidle', timeout: 20000 });
49
- await page.waitForTimeout(pg.wait);
50
-
51
- // Dismiss any open modals
52
- await page.evaluate(() => {
53
- document.querySelectorAll('.modal-backdrop, [data-bs-backdrop], dialog[open]')
54
- .forEach(el => { el.style.display = 'none'; });
55
- });
56
-
57
- const outPath = path.join(OUT, `screenshot-${pg.name}.png`);
58
- await page.screenshot({ path: outPath, fullPage: false });
59
- console.log(` ✅ → ${outPath}`);
60
- } catch (e) {
61
- console.error(` ❌ ${pg.name}: ${e.message}`);
62
- } finally {
63
- await page.close();
64
- }
65
- }
66
-
67
- await browser.close();
68
- console.log('\n✅ Done!');
69
- })();