@cryptiklemur/lattice 5.10.0 → 5.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{angular-html-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -2,10 +2,10 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, watch }
|
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { getLatticeHome } from "../config.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
let specsFile = "";
|
|
6
|
+
let specs = [];
|
|
7
|
+
let lastSaveTime = 0;
|
|
8
|
+
let onReloadCallback = null;
|
|
9
9
|
function getSpecsPath() {
|
|
10
10
|
if (!specsFile) {
|
|
11
11
|
specsFile = join(getLatticeHome(), "specs.json");
|
|
@@ -13,10 +13,10 @@ function getSpecsPath() {
|
|
|
13
13
|
return specsFile;
|
|
14
14
|
}
|
|
15
15
|
export function loadSpecs() {
|
|
16
|
-
|
|
16
|
+
const path = getSpecsPath();
|
|
17
17
|
if (existsSync(path)) {
|
|
18
18
|
try {
|
|
19
|
-
|
|
19
|
+
const raw = readFileSync(path, "utf-8");
|
|
20
20
|
specs = JSON.parse(raw);
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
@@ -32,12 +32,12 @@ export function loadSpecs() {
|
|
|
32
32
|
export function onSpecsReloaded(callback) {
|
|
33
33
|
onReloadCallback = callback;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
let watcher = null;
|
|
36
|
+
let reloadTimer = null;
|
|
37
37
|
function reloadFromDisk() {
|
|
38
|
-
|
|
38
|
+
const path = getSpecsPath();
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
const raw = readFileSync(path, "utf-8");
|
|
41
41
|
specs = JSON.parse(raw);
|
|
42
42
|
if (onReloadCallback)
|
|
43
43
|
onReloadCallback();
|
|
@@ -49,9 +49,9 @@ function reloadFromDisk() {
|
|
|
49
49
|
function watchSpecsFile() {
|
|
50
50
|
if (watcher)
|
|
51
51
|
return;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const path = getSpecsPath();
|
|
53
|
+
const dir = dirname(path);
|
|
54
|
+
const filename = path.slice(dir.length + 1);
|
|
55
55
|
if (!existsSync(dir)) {
|
|
56
56
|
mkdirSync(dir, { recursive: true });
|
|
57
57
|
}
|
|
@@ -75,12 +75,12 @@ function watchSpecsFile() {
|
|
|
75
75
|
catch { }
|
|
76
76
|
}
|
|
77
77
|
function saveSpecs() {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const path = getSpecsPath();
|
|
79
|
+
const dir = join(path, "..");
|
|
80
80
|
if (!existsSync(dir)) {
|
|
81
81
|
mkdirSync(dir, { recursive: true });
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
const tmp = path + ".tmp";
|
|
84
84
|
try {
|
|
85
85
|
lastSaveTime = Date.now();
|
|
86
86
|
writeFileSync(tmp, JSON.stringify(specs, null, 2));
|
|
@@ -96,15 +96,15 @@ export function listSpecs(projectSlug) {
|
|
|
96
96
|
return specs.filter(function (s) { return s.projectSlug === projectSlug; });
|
|
97
97
|
}
|
|
98
98
|
export function getSpec(id) {
|
|
99
|
-
for (
|
|
99
|
+
for (let i = 0; i < specs.length; i++) {
|
|
100
100
|
if (specs[i].id === id)
|
|
101
101
|
return specs[i];
|
|
102
102
|
}
|
|
103
103
|
return null;
|
|
104
104
|
}
|
|
105
105
|
export function createSpec(opts) {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const spec = {
|
|
108
108
|
id: "spec_" + now + "_" + randomBytes(3).toString("hex"),
|
|
109
109
|
projectSlug: opts.projectSlug,
|
|
110
110
|
title: opts.title,
|
|
@@ -139,11 +139,11 @@ export function createSpec(opts) {
|
|
|
139
139
|
return spec;
|
|
140
140
|
}
|
|
141
141
|
export function updateSpec(id, updates) {
|
|
142
|
-
for (
|
|
142
|
+
for (let i = 0; i < specs.length; i++) {
|
|
143
143
|
if (specs[i].id !== id)
|
|
144
144
|
continue;
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
const spec = specs[i];
|
|
146
|
+
const now = Date.now();
|
|
147
147
|
if (updates.status && updates.status !== spec.status) {
|
|
148
148
|
spec.activity.push({
|
|
149
149
|
timestamp: now,
|
|
@@ -173,9 +173,9 @@ export function updateSpec(id, updates) {
|
|
|
173
173
|
if (updates.blockedBy !== undefined)
|
|
174
174
|
spec.blockedBy = updates.blockedBy;
|
|
175
175
|
if (updates.sections) {
|
|
176
|
-
|
|
177
|
-
for (
|
|
178
|
-
|
|
176
|
+
const keys = Object.keys(updates.sections);
|
|
177
|
+
for (let k = 0; k < keys.length; k++) {
|
|
178
|
+
const key = keys[k];
|
|
179
179
|
if (updates.sections[key] !== undefined) {
|
|
180
180
|
spec.sections[key] = updates.sections[key];
|
|
181
181
|
}
|
|
@@ -188,7 +188,7 @@ export function updateSpec(id, updates) {
|
|
|
188
188
|
return null;
|
|
189
189
|
}
|
|
190
190
|
export function deleteSpec(id) {
|
|
191
|
-
for (
|
|
191
|
+
for (let i = 0; i < specs.length; i++) {
|
|
192
192
|
if (specs[i].id === id) {
|
|
193
193
|
specs.splice(i, 1);
|
|
194
194
|
saveSpecs();
|
|
@@ -198,10 +198,10 @@ export function deleteSpec(id) {
|
|
|
198
198
|
return false;
|
|
199
199
|
}
|
|
200
200
|
export function populateSpec(id, fields, sessionId) {
|
|
201
|
-
for (
|
|
201
|
+
for (let i = 0; i < specs.length; i++) {
|
|
202
202
|
if (specs[i].id !== id)
|
|
203
203
|
continue;
|
|
204
|
-
|
|
204
|
+
const spec = specs[i];
|
|
205
205
|
if (fields.title && typeof fields.title === "string")
|
|
206
206
|
spec.title = fields.title;
|
|
207
207
|
if (fields.tagline && typeof fields.tagline === "string")
|
|
@@ -235,13 +235,13 @@ export function populateSpec(id, fields, sessionId) {
|
|
|
235
235
|
return null;
|
|
236
236
|
}
|
|
237
237
|
export function parseSpecPopulate(text) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
const startTag = "<spec-populate>";
|
|
239
|
+
const endTag = "</spec-populate>";
|
|
240
|
+
const startIdx = text.indexOf(startTag);
|
|
241
|
+
const endIdx = text.indexOf(endTag);
|
|
242
242
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
|
|
243
243
|
return null;
|
|
244
|
-
|
|
244
|
+
const jsonStr = text.slice(startIdx + startTag.length, endIdx).trim();
|
|
245
245
|
try {
|
|
246
246
|
return JSON.parse(jsonStr);
|
|
247
247
|
}
|
|
@@ -250,22 +250,22 @@ export function parseSpecPopulate(text) {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
export function parsePlanContent(text) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
const startTag = "<plan-content>";
|
|
254
|
+
const endTag = "</plan-content>";
|
|
255
|
+
const startIdx = text.indexOf(startTag);
|
|
256
|
+
const endIdx = text.indexOf(endTag);
|
|
257
257
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
|
|
258
258
|
return null;
|
|
259
259
|
return text.slice(startIdx + startTag.length, endIdx).trim();
|
|
260
260
|
}
|
|
261
261
|
export function parseSpecActivity(text) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
const startTag = "<spec-activity>";
|
|
263
|
+
const endTag = "</spec-activity>";
|
|
264
|
+
const startIdx = text.indexOf(startTag);
|
|
265
|
+
const endIdx = text.indexOf(endTag);
|
|
266
266
|
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
|
|
267
267
|
return null;
|
|
268
|
-
|
|
268
|
+
const jsonStr = text.slice(startIdx + startTag.length, endIdx).trim();
|
|
269
269
|
try {
|
|
270
270
|
return JSON.parse(jsonStr);
|
|
271
271
|
}
|
|
@@ -274,8 +274,8 @@ export function parseSpecActivity(text) {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
export function findSpecBySession(sessionId) {
|
|
277
|
-
for (
|
|
278
|
-
for (
|
|
277
|
+
for (let i = 0; i < specs.length; i++) {
|
|
278
|
+
for (let j = 0; j < specs[i].linkedSessions.length; j++) {
|
|
279
279
|
if (specs[i].linkedSessions[j].sessionId === sessionId)
|
|
280
280
|
return specs[i];
|
|
281
281
|
}
|
|
@@ -283,11 +283,11 @@ export function findSpecBySession(sessionId) {
|
|
|
283
283
|
return null;
|
|
284
284
|
}
|
|
285
285
|
export function linkSession(specId, sessionId, note, sessionType) {
|
|
286
|
-
for (
|
|
286
|
+
for (let i = 0; i < specs.length; i++) {
|
|
287
287
|
if (specs[i].id !== specId)
|
|
288
288
|
continue;
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
const spec = specs[i];
|
|
290
|
+
const now = Date.now();
|
|
291
291
|
spec.linkedSessions.push({
|
|
292
292
|
sessionId,
|
|
293
293
|
linkedAt: now,
|
|
@@ -307,12 +307,12 @@ export function linkSession(specId, sessionId, note, sessionType) {
|
|
|
307
307
|
return null;
|
|
308
308
|
}
|
|
309
309
|
export function unlinkSession(specId, sessionId) {
|
|
310
|
-
for (
|
|
310
|
+
for (let i = 0; i < specs.length; i++) {
|
|
311
311
|
if (specs[i].id !== specId)
|
|
312
312
|
continue;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
for (
|
|
313
|
+
const spec = specs[i];
|
|
314
|
+
let idx = -1;
|
|
315
|
+
for (let j = 0; j < spec.linkedSessions.length; j++) {
|
|
316
316
|
if (spec.linkedSessions[j].sessionId === sessionId) {
|
|
317
317
|
idx = j;
|
|
318
318
|
break;
|
|
@@ -328,11 +328,11 @@ export function unlinkSession(specId, sessionId) {
|
|
|
328
328
|
return null;
|
|
329
329
|
}
|
|
330
330
|
export function addActivity(specId, activityType, detail, sessionId) {
|
|
331
|
-
for (
|
|
331
|
+
for (let i = 0; i < specs.length; i++) {
|
|
332
332
|
if (specs[i].id !== specId)
|
|
333
333
|
continue;
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
const spec = specs[i];
|
|
335
|
+
const now = Date.now();
|
|
336
336
|
spec.activity.push({
|
|
337
337
|
timestamp: now,
|
|
338
338
|
type: activityType,
|
|
@@ -2,8 +2,8 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { getLatticeHome } from "../config.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
let notesFile = "";
|
|
6
|
+
let notes = [];
|
|
7
7
|
function getNotesPath() {
|
|
8
8
|
if (!notesFile) {
|
|
9
9
|
notesFile = join(getLatticeHome(), "notes.jsonl");
|
|
@@ -11,22 +11,22 @@ function getNotesPath() {
|
|
|
11
11
|
return notesFile;
|
|
12
12
|
}
|
|
13
13
|
export function loadNotes() {
|
|
14
|
-
|
|
14
|
+
const path = getNotesPath();
|
|
15
15
|
if (!existsSync(path)) {
|
|
16
16
|
notes = [];
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const raw = readFileSync(path, "utf-8");
|
|
21
|
+
const lines = raw.trim().split("\n");
|
|
22
22
|
notes = [];
|
|
23
|
-
for (
|
|
24
|
-
|
|
23
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24
|
+
const line = lines[i].trim();
|
|
25
25
|
if (!line) {
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const note = JSON.parse(line);
|
|
30
30
|
notes.push(note);
|
|
31
31
|
}
|
|
32
32
|
catch {
|
|
@@ -40,15 +40,15 @@ export function loadNotes() {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
function saveNotes() {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const path = getNotesPath();
|
|
44
|
+
const dir = join(path, "..");
|
|
45
45
|
if (!existsSync(dir)) {
|
|
46
46
|
mkdirSync(dir, { recursive: true });
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
const tmp = path + ".tmp";
|
|
49
49
|
try {
|
|
50
|
-
|
|
51
|
-
for (
|
|
50
|
+
const lines = [];
|
|
51
|
+
for (let i = 0; i < notes.length; i++) {
|
|
52
52
|
lines.push(JSON.stringify(notes[i]));
|
|
53
53
|
}
|
|
54
54
|
writeFileSync(tmp, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
|
|
@@ -64,8 +64,8 @@ export function listNotes(projectSlug) {
|
|
|
64
64
|
return notes.filter(function (n) { return n.projectSlug === projectSlug; });
|
|
65
65
|
}
|
|
66
66
|
export function createNote(content, projectSlug) {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
const note = {
|
|
69
69
|
id: "note_" + now + "_" + randomBytes(3).toString("hex"),
|
|
70
70
|
content,
|
|
71
71
|
createdAt: now,
|
|
@@ -77,7 +77,7 @@ export function createNote(content, projectSlug) {
|
|
|
77
77
|
return note;
|
|
78
78
|
}
|
|
79
79
|
export function updateNote(id, content) {
|
|
80
|
-
for (
|
|
80
|
+
for (let i = 0; i < notes.length; i++) {
|
|
81
81
|
if (notes[i].id === id) {
|
|
82
82
|
notes[i].content = content;
|
|
83
83
|
notes[i].updatedAt = Date.now();
|
|
@@ -88,7 +88,7 @@ export function updateNote(id, content) {
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
export function deleteNote(id) {
|
|
91
|
-
for (
|
|
91
|
+
for (let i = 0; i < notes.length; i++) {
|
|
92
92
|
if (notes[i].id === id) {
|
|
93
93
|
notes.splice(i, 1);
|
|
94
94
|
saveNotes();
|
|
@@ -2,22 +2,22 @@ import { existsSync, readFileSync, watch } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { log } from "../logger.js";
|
|
5
|
-
|
|
5
|
+
const TRACKED_SKILLS = [
|
|
6
6
|
"brainstorming",
|
|
7
7
|
"writing-plans",
|
|
8
8
|
"subagent-driven-development",
|
|
9
9
|
"executing-plans",
|
|
10
10
|
];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
let installed = false;
|
|
12
|
+
let version = null;
|
|
13
|
+
let installPath = null;
|
|
14
|
+
const skillContent = new Map();
|
|
15
|
+
let watcher = null;
|
|
16
16
|
function getPluginsFilePath() {
|
|
17
17
|
return join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
18
18
|
}
|
|
19
19
|
function detectSuperpowers() {
|
|
20
|
-
|
|
20
|
+
const pluginsFile = getPluginsFilePath();
|
|
21
21
|
if (!existsSync(pluginsFile)) {
|
|
22
22
|
installed = false;
|
|
23
23
|
version = null;
|
|
@@ -26,15 +26,15 @@ function detectSuperpowers() {
|
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (
|
|
29
|
+
const raw = readFileSync(pluginsFile, "utf-8");
|
|
30
|
+
const data = JSON.parse(raw);
|
|
31
|
+
const plugins = data.plugins || {};
|
|
32
|
+
let found = false;
|
|
33
|
+
for (const key of Object.keys(plugins)) {
|
|
34
34
|
if (key.startsWith("superpowers@")) {
|
|
35
|
-
|
|
35
|
+
const entries = plugins[key];
|
|
36
36
|
if (Array.isArray(entries) && entries.length > 0) {
|
|
37
|
-
|
|
37
|
+
const entry = entries[0];
|
|
38
38
|
installed = true;
|
|
39
39
|
version = entry.version || null;
|
|
40
40
|
installPath = entry.installPath || null;
|
|
@@ -64,11 +64,11 @@ function loadSkills() {
|
|
|
64
64
|
skillContent.clear();
|
|
65
65
|
if (!installPath)
|
|
66
66
|
return;
|
|
67
|
-
for (
|
|
68
|
-
|
|
67
|
+
for (const name of TRACKED_SKILLS) {
|
|
68
|
+
const skillPath = join(installPath, "skills", name, "SKILL.md");
|
|
69
69
|
if (existsSync(skillPath)) {
|
|
70
70
|
try {
|
|
71
|
-
|
|
71
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
72
72
|
skillContent.set(name, content);
|
|
73
73
|
}
|
|
74
74
|
catch (err) {
|
|
@@ -80,7 +80,7 @@ function loadSkills() {
|
|
|
80
80
|
}
|
|
81
81
|
export function initSuperpowers() {
|
|
82
82
|
detectSuperpowers();
|
|
83
|
-
|
|
83
|
+
const pluginsDir = join(homedir(), ".claude", "plugins");
|
|
84
84
|
if (existsSync(pluginsDir)) {
|
|
85
85
|
try {
|
|
86
86
|
watcher = watch(pluginsDir, function (_eventType, filename) {
|
|
@@ -107,10 +107,10 @@ export function getAvailableSkills() {
|
|
|
107
107
|
return Array.from(skillContent.keys());
|
|
108
108
|
}
|
|
109
109
|
export function buildBrainstormPrompt(spec, projectSlug) {
|
|
110
|
-
|
|
110
|
+
const content = getSkillContent("brainstorming");
|
|
111
111
|
if (!content)
|
|
112
112
|
return "";
|
|
113
|
-
|
|
113
|
+
let append = content + "\n\n---\n\n";
|
|
114
114
|
append += "## Lattice Integration Instructions\n\n";
|
|
115
115
|
append += "You are helping design a spec in the Lattice project management tool.\n\n";
|
|
116
116
|
append += "**Project:** " + projectSlug + "\n";
|
|
@@ -133,10 +133,10 @@ export function buildBrainstormPrompt(spec, projectSlug) {
|
|
|
133
133
|
return append;
|
|
134
134
|
}
|
|
135
135
|
export function buildWritePlanPrompt(spec, projectSlug) {
|
|
136
|
-
|
|
136
|
+
const content = getSkillContent("writing-plans");
|
|
137
137
|
if (!content)
|
|
138
138
|
return "";
|
|
139
|
-
|
|
139
|
+
let append = content + "\n\n---\n\n";
|
|
140
140
|
append += "## Lattice Integration Instructions\n\n";
|
|
141
141
|
append += "You are writing an implementation plan for a spec in Lattice.\n\n";
|
|
142
142
|
append += "**Project:** " + projectSlug + "\n";
|
|
@@ -155,10 +155,10 @@ export function buildWritePlanPrompt(spec, projectSlug) {
|
|
|
155
155
|
return append;
|
|
156
156
|
}
|
|
157
157
|
export function buildExecutePrompt(spec, projectSlug) {
|
|
158
|
-
|
|
158
|
+
const content = getSkillContent("subagent-driven-development") || getSkillContent("executing-plans");
|
|
159
159
|
if (!content)
|
|
160
160
|
return "";
|
|
161
|
-
|
|
161
|
+
let append = content + "\n\n---\n\n";
|
|
162
162
|
append += "## Lattice Integration Instructions\n\n";
|
|
163
163
|
append += "You are executing an implementation plan from a Lattice spec.\n\n";
|
|
164
164
|
append += "**Project:** " + projectSlug + "\n";
|
|
@@ -3,7 +3,7 @@ import { sendTo } from "../ws/broadcast.js";
|
|
|
3
3
|
import { streamAnalyticsSections } from "../analytics/engine.js";
|
|
4
4
|
registerHandler("analytics", function (clientId, message) {
|
|
5
5
|
if (message.type === "analytics:request") {
|
|
6
|
-
|
|
6
|
+
const msg = message;
|
|
7
7
|
streamAnalyticsSections(msg.scope, msg.period, msg.projectSlug, msg.sessionId, msg.forceRefresh, function (sectionName, sectionData) {
|
|
8
8
|
sendTo(clientId, {
|
|
9
9
|
type: "analytics:section",
|
|
@@ -5,13 +5,13 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { registerHandler } from "../ws/router.js";
|
|
6
6
|
import { sendTo } from "../ws/broadcast.js";
|
|
7
7
|
import { log } from "../logger.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024;
|
|
9
|
+
const stores = new Map();
|
|
10
|
+
const completed = new Map();
|
|
11
|
+
const TTL_MS = 5 * 60 * 1000;
|
|
12
|
+
const CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
13
13
|
function getClientStore(clientId) {
|
|
14
|
-
|
|
14
|
+
let store = stores.get(clientId);
|
|
15
15
|
if (!store) {
|
|
16
16
|
store = new Map();
|
|
17
17
|
stores.set(clientId, store);
|
|
@@ -19,7 +19,7 @@ function getClientStore(clientId) {
|
|
|
19
19
|
return store;
|
|
20
20
|
}
|
|
21
21
|
function getClientCompleted(clientId) {
|
|
22
|
-
|
|
22
|
+
let store = completed.get(clientId);
|
|
23
23
|
if (!store) {
|
|
24
24
|
store = new Map();
|
|
25
25
|
completed.set(clientId, store);
|
|
@@ -36,12 +36,12 @@ async function cleanupPending(pending) {
|
|
|
36
36
|
}
|
|
37
37
|
registerHandler("attachment", async function (clientId, message) {
|
|
38
38
|
if (message.type === "attachment:chunk") {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const msg = message;
|
|
40
|
+
const store = getClientStore(clientId);
|
|
41
|
+
let pending = store.get(msg.attachmentId);
|
|
42
42
|
if (!pending) {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const tempDir = await mkdtemp(join(tmpdir(), "lattice-attach-"));
|
|
44
|
+
const tempPath = join(tempDir, "data.bin");
|
|
45
45
|
pending = {
|
|
46
46
|
tempDir: tempDir,
|
|
47
47
|
tempPath: tempPath,
|
|
@@ -62,7 +62,7 @@ registerHandler("attachment", async function (clientId, message) {
|
|
|
62
62
|
});
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
const chunkBuffer = Buffer.from(msg.data, "base64");
|
|
66
66
|
if (pending.totalBytes + chunkBuffer.length > MAX_ATTACHMENT_SIZE) {
|
|
67
67
|
await cleanupPending(pending);
|
|
68
68
|
store.delete(msg.attachmentId);
|
|
@@ -86,9 +86,9 @@ registerHandler("attachment", async function (clientId, message) {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
if (message.type === "attachment:complete") {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const completeMsg = message;
|
|
90
|
+
const completeStore = getClientStore(clientId);
|
|
91
|
+
const completePending = completeStore.get(completeMsg.attachmentId);
|
|
92
92
|
if (!completePending) {
|
|
93
93
|
sendTo(clientId, {
|
|
94
94
|
type: "attachment:error",
|
|
@@ -109,10 +109,10 @@ registerHandler("attachment", async function (clientId, message) {
|
|
|
109
109
|
completePending.writeStream.end(function () { resolve(); });
|
|
110
110
|
});
|
|
111
111
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
const assembled = await readTempFile(completePending.tempPath);
|
|
113
|
+
const isText = completeMsg.attachmentType === "paste" || isTextMimeType(completeMsg.mimeType);
|
|
114
|
+
const content = isText ? assembled.toString("utf-8") : assembled.toString("base64");
|
|
115
|
+
const attachment = {
|
|
116
116
|
type: completeMsg.attachmentType,
|
|
117
117
|
name: completeMsg.name,
|
|
118
118
|
content,
|
|
@@ -120,7 +120,7 @@ registerHandler("attachment", async function (clientId, message) {
|
|
|
120
120
|
size: completeMsg.size,
|
|
121
121
|
lineCount: completeMsg.lineCount,
|
|
122
122
|
};
|
|
123
|
-
|
|
123
|
+
const finishedStore = getClientCompleted(clientId);
|
|
124
124
|
finishedStore.set(completeMsg.attachmentId, attachment);
|
|
125
125
|
}
|
|
126
126
|
catch (err) {
|
|
@@ -138,8 +138,8 @@ registerHandler("attachment", async function (clientId, message) {
|
|
|
138
138
|
});
|
|
139
139
|
function readTempFile(path) {
|
|
140
140
|
return new Promise(function (resolve, reject) {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const chunks = [];
|
|
142
|
+
const stream = createReadStream(path);
|
|
143
143
|
stream.on("data", function (chunk) { chunks.push(chunk); });
|
|
144
144
|
stream.on("end", function () { resolve(Buffer.concat(chunks)); });
|
|
145
145
|
stream.on("error", reject);
|
|
@@ -148,7 +148,7 @@ function readTempFile(path) {
|
|
|
148
148
|
function isTextMimeType(mime) {
|
|
149
149
|
if (mime.startsWith("text/"))
|
|
150
150
|
return true;
|
|
151
|
-
|
|
151
|
+
const textTypes = [
|
|
152
152
|
"application/json",
|
|
153
153
|
"application/xml",
|
|
154
154
|
"application/javascript",
|
|
@@ -160,10 +160,10 @@ function isTextMimeType(mime) {
|
|
|
160
160
|
return textTypes.indexOf(mime) !== -1;
|
|
161
161
|
}
|
|
162
162
|
export function getAttachments(clientId, ids) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for (
|
|
166
|
-
|
|
163
|
+
const store = getClientCompleted(clientId);
|
|
164
|
+
const result = [];
|
|
165
|
+
for (let i = 0; i < ids.length; i++) {
|
|
166
|
+
const att = store.get(ids[i]);
|
|
167
167
|
if (att) {
|
|
168
168
|
result.push(att);
|
|
169
169
|
store.delete(ids[i]);
|
|
@@ -172,17 +172,17 @@ export function getAttachments(clientId, ids) {
|
|
|
172
172
|
return result;
|
|
173
173
|
}
|
|
174
174
|
export function cleanupClient(clientId) {
|
|
175
|
-
|
|
175
|
+
const clientStore = stores.get(clientId);
|
|
176
176
|
if (clientStore) {
|
|
177
|
-
for (
|
|
177
|
+
for (const [, pending] of clientStore) {
|
|
178
178
|
void cleanupPending(pending);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
stores.delete(clientId);
|
|
182
182
|
completed.delete(clientId);
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
const ttlCleanupInterval = setInterval(function () {
|
|
185
|
+
const now = Date.now();
|
|
186
186
|
stores.forEach(function (store) {
|
|
187
187
|
store.forEach(function (pending, id) {
|
|
188
188
|
if (now - pending.createdAt > TTL_MS) {
|
|
@@ -3,8 +3,8 @@ import { sendTo } from "../ws/broadcast.js";
|
|
|
3
3
|
import { listBookmarks, addBookmark, removeBookmark } from "../project/bookmarks.js";
|
|
4
4
|
registerHandler("bookmark", function (clientId, message) {
|
|
5
5
|
if (message.type === "bookmark:list") {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const listMsg = message;
|
|
7
|
+
const isSessionScoped = Boolean(listMsg.sessionId);
|
|
8
8
|
sendTo(clientId, {
|
|
9
9
|
type: "bookmark:list_result",
|
|
10
10
|
scope: isSessionScoped ? "session" : "all",
|
|
@@ -13,7 +13,7 @@ registerHandler("bookmark", function (clientId, message) {
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
if (message.type === "bookmark:add") {
|
|
16
|
-
|
|
16
|
+
const addMsg = message;
|
|
17
17
|
addBookmark({
|
|
18
18
|
sessionId: addMsg.sessionId,
|
|
19
19
|
projectSlug: addMsg.projectSlug,
|
|
@@ -29,7 +29,7 @@ registerHandler("bookmark", function (clientId, message) {
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
if (message.type === "bookmark:remove") {
|
|
32
|
-
|
|
32
|
+
const removeMsg = message;
|
|
33
33
|
removeBookmark(removeMsg.id);
|
|
34
34
|
sendTo(clientId, {
|
|
35
35
|
type: "bookmark:list_result",
|
|
@@ -4,7 +4,7 @@ import { getActiveBrainstorm, getAnyActiveBrainstorm, writeBrainstormEvent, stop
|
|
|
4
4
|
import { getActiveProjectForClient } from "./fs.js";
|
|
5
5
|
registerHandler("brainstorm", function (clientId, message) {
|
|
6
6
|
if (message.type === "brainstorm:select") {
|
|
7
|
-
|
|
7
|
+
const selectMsg = message;
|
|
8
8
|
writeBrainstormEvent(selectMsg.sessionDir, {
|
|
9
9
|
type: "click",
|
|
10
10
|
choice: selectMsg.choice,
|
|
@@ -14,13 +14,13 @@ registerHandler("brainstorm", function (clientId, message) {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
if (message.type === "brainstorm:stop") {
|
|
17
|
-
|
|
17
|
+
const stopProjectSlug = getActiveProjectForClient(clientId);
|
|
18
18
|
stopBrainstorm(stopProjectSlug || undefined);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
if (message.type === "brainstorm:status_request") {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const projectSlug = getActiveProjectForClient(clientId);
|
|
23
|
+
const active = projectSlug ? getActiveBrainstorm(projectSlug) : getAnyActiveBrainstorm();
|
|
24
24
|
if (active) {
|
|
25
25
|
sendTo(clientId, {
|
|
26
26
|
type: "brainstorm:status",
|