@brainst0rm/cli 0.13.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/README.md +32 -0
- package/dist/App-DPXJYXKH.js +2794 -0
- package/dist/App-DPXJYXKH.js.map +1 -0
- package/dist/App-SSKWB7CT.js +2795 -0
- package/dist/App-SSKWB7CT.js.map +1 -0
- package/dist/brainstorm.js +4636 -0
- package/dist/brainstorm.js.map +1 -0
- package/dist/chunk-2CHZHDIM.js +391 -0
- package/dist/chunk-2CHZHDIM.js.map +1 -0
- package/dist/chunk-55ITCWZZ.js +1307 -0
- package/dist/chunk-55ITCWZZ.js.map +1 -0
- package/dist/chunk-5NA3GH6X.js +1308 -0
- package/dist/chunk-5NA3GH6X.js.map +1 -0
- package/dist/chunk-7D4SUZUM.js +38 -0
- package/dist/chunk-7D4SUZUM.js.map +1 -0
- package/dist/chunk-D474E47D.js +148 -0
- package/dist/chunk-D474E47D.js.map +1 -0
- package/dist/chunk-GJXEX2A3.js +146 -0
- package/dist/chunk-GJXEX2A3.js.map +1 -0
- package/dist/chunk-OVGL3NJQ.js +307 -0
- package/dist/chunk-OVGL3NJQ.js.map +1 -0
- package/dist/chunk-VY6MPJXL.js +389 -0
- package/dist/chunk-VY6MPJXL.js.map +1 -0
- package/dist/chunk-YWXOPUDW.js +305 -0
- package/dist/chunk-YWXOPUDW.js.map +1 -0
- package/dist/chunk-ZWE3DS7E.js +39 -0
- package/dist/chunk-ZWE3DS7E.js.map +1 -0
- package/dist/dist-DUDO3RDM.js +9573 -0
- package/dist/dist-DUDO3RDM.js.map +1 -0
- package/dist/dist-GNHTH2DH.js +292 -0
- package/dist/dist-GNHTH2DH.js.map +1 -0
- package/dist/dist-JUDVPE7G.js +293 -0
- package/dist/dist-JUDVPE7G.js.map +1 -0
- package/dist/dist-V5DTSTKJ.js +9572 -0
- package/dist/dist-V5DTSTKJ.js.map +1 -0
- package/dist/dist-WLTQTLFO.js +14 -0
- package/dist/dist-WLTQTLFO.js.map +1 -0
- package/dist/dist-YIGU37Q2.js +15 -0
- package/dist/dist-YIGU37Q2.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4635 -0
- package/dist/index.js.map +1 -0
- package/dist/recorder-D6ILEOZP.js +67 -0
- package/dist/recorder-D6ILEOZP.js.map +1 -0
- package/dist/recorder-SPYYF4DL.js +66 -0
- package/dist/recorder-SPYYF4DL.js.map +1 -0
- package/dist/roles-2DGF4PZU.js +16 -0
- package/dist/roles-2DGF4PZU.js.map +1 -0
- package/dist/roles-UIPX7GBC.js +17 -0
- package/dist/roles-UIPX7GBC.js.map +1 -0
- package/dist/slash-PDWKCZOQ.js +13 -0
- package/dist/slash-PDWKCZOQ.js.map +1 -0
- package/dist/slash-ZDC4DKL4.js +14 -0
- package/dist/slash-ZDC4DKL4.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../projects/dist/index.js
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { basename, resolve } from "path";
|
|
6
|
+
import { existsSync, readdirSync, statSync, readFileSync } from "fs";
|
|
7
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
8
|
+
function rowToProject(row) {
|
|
9
|
+
return {
|
|
10
|
+
id: row.id,
|
|
11
|
+
name: row.name,
|
|
12
|
+
path: row.path,
|
|
13
|
+
description: row.description,
|
|
14
|
+
customInstructions: row.custom_instructions ?? void 0,
|
|
15
|
+
knowledgeFiles: JSON.parse(row.knowledge_files || "[]"),
|
|
16
|
+
budgetDaily: row.budget_daily ?? void 0,
|
|
17
|
+
budgetMonthly: row.budget_monthly ?? void 0,
|
|
18
|
+
isActive: Boolean(row.is_active),
|
|
19
|
+
createdAt: row.created_at,
|
|
20
|
+
updatedAt: row.updated_at
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
var ProjectRepository = class {
|
|
24
|
+
constructor(db) {
|
|
25
|
+
this.db = db;
|
|
26
|
+
}
|
|
27
|
+
list(includeInactive = false) {
|
|
28
|
+
const sql = includeInactive ? "SELECT * FROM projects ORDER BY name" : "SELECT * FROM projects WHERE is_active = 1 ORDER BY name";
|
|
29
|
+
return this.db.prepare(sql).all().map(rowToProject);
|
|
30
|
+
}
|
|
31
|
+
getById(id) {
|
|
32
|
+
const row = this.db.prepare("SELECT * FROM projects WHERE id = ?").get(id);
|
|
33
|
+
return row ? rowToProject(row) : void 0;
|
|
34
|
+
}
|
|
35
|
+
getByName(name) {
|
|
36
|
+
const row = this.db.prepare("SELECT * FROM projects WHERE name = ? AND is_active = 1").get(name);
|
|
37
|
+
return row ? rowToProject(row) : void 0;
|
|
38
|
+
}
|
|
39
|
+
getByPath(path) {
|
|
40
|
+
const row = this.db.prepare("SELECT * FROM projects WHERE path = ? AND is_active = 1").get(path);
|
|
41
|
+
return row ? rowToProject(row) : void 0;
|
|
42
|
+
}
|
|
43
|
+
create(data) {
|
|
44
|
+
const id = randomUUID();
|
|
45
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
46
|
+
this.db.prepare(
|
|
47
|
+
`INSERT INTO projects (id, name, path, description, custom_instructions, knowledge_files, budget_daily, budget_monthly, created_at, updated_at)
|
|
48
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
49
|
+
).run(
|
|
50
|
+
id,
|
|
51
|
+
data.name,
|
|
52
|
+
data.path,
|
|
53
|
+
data.description ?? "",
|
|
54
|
+
data.customInstructions ?? null,
|
|
55
|
+
JSON.stringify(data.knowledgeFiles ?? []),
|
|
56
|
+
data.budgetDaily ?? null,
|
|
57
|
+
data.budgetMonthly ?? null,
|
|
58
|
+
now,
|
|
59
|
+
now
|
|
60
|
+
);
|
|
61
|
+
return this.getById(id);
|
|
62
|
+
}
|
|
63
|
+
update(id, data) {
|
|
64
|
+
const sets = [];
|
|
65
|
+
const values = [];
|
|
66
|
+
if (data.name !== void 0) {
|
|
67
|
+
sets.push("name = ?");
|
|
68
|
+
values.push(data.name);
|
|
69
|
+
}
|
|
70
|
+
if (data.description !== void 0) {
|
|
71
|
+
sets.push("description = ?");
|
|
72
|
+
values.push(data.description);
|
|
73
|
+
}
|
|
74
|
+
if (data.customInstructions !== void 0) {
|
|
75
|
+
sets.push("custom_instructions = ?");
|
|
76
|
+
values.push(data.customInstructions);
|
|
77
|
+
}
|
|
78
|
+
if (data.knowledgeFiles !== void 0) {
|
|
79
|
+
sets.push("knowledge_files = ?");
|
|
80
|
+
values.push(JSON.stringify(data.knowledgeFiles));
|
|
81
|
+
}
|
|
82
|
+
if (data.budgetDaily !== void 0) {
|
|
83
|
+
sets.push("budget_daily = ?");
|
|
84
|
+
values.push(data.budgetDaily);
|
|
85
|
+
}
|
|
86
|
+
if (data.budgetMonthly !== void 0) {
|
|
87
|
+
sets.push("budget_monthly = ?");
|
|
88
|
+
values.push(data.budgetMonthly);
|
|
89
|
+
}
|
|
90
|
+
if (data.isActive !== void 0) {
|
|
91
|
+
sets.push("is_active = ?");
|
|
92
|
+
values.push(data.isActive ? 1 : 0);
|
|
93
|
+
}
|
|
94
|
+
if (sets.length === 0) return this.getById(id);
|
|
95
|
+
sets.push("updated_at = ?");
|
|
96
|
+
values.push(Math.floor(Date.now() / 1e3));
|
|
97
|
+
values.push(id);
|
|
98
|
+
this.db.prepare(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
99
|
+
return this.getById(id);
|
|
100
|
+
}
|
|
101
|
+
delete(id) {
|
|
102
|
+
this.update(id, { isActive: false });
|
|
103
|
+
}
|
|
104
|
+
/** Get total cost for a project in a given time window. */
|
|
105
|
+
getCost(projectPath, sinceTimestamp) {
|
|
106
|
+
const row = this.db.prepare(
|
|
107
|
+
"SELECT COALESCE(SUM(cost), 0) as total FROM cost_records WHERE project_path = ? AND timestamp >= ?"
|
|
108
|
+
).get(projectPath, sinceTimestamp);
|
|
109
|
+
return row?.total ?? 0;
|
|
110
|
+
}
|
|
111
|
+
/** Count sessions for a project. */
|
|
112
|
+
getSessionCount(projectId) {
|
|
113
|
+
const row = this.db.prepare("SELECT COUNT(*) as count FROM sessions WHERE project_id = ?").get(projectId);
|
|
114
|
+
return row?.count ?? 0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
function rowToMemory(row) {
|
|
118
|
+
return {
|
|
119
|
+
id: row.id,
|
|
120
|
+
projectId: row.project_id,
|
|
121
|
+
key: row.key,
|
|
122
|
+
value: row.value,
|
|
123
|
+
category: row.category,
|
|
124
|
+
createdAt: row.created_at,
|
|
125
|
+
updatedAt: row.updated_at
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
var ProjectMemoryRepository = class {
|
|
129
|
+
constructor(db) {
|
|
130
|
+
this.db = db;
|
|
131
|
+
}
|
|
132
|
+
list(projectId, category) {
|
|
133
|
+
if (category) {
|
|
134
|
+
return this.db.prepare(
|
|
135
|
+
"SELECT * FROM project_memory WHERE project_id = ? AND category = ? ORDER BY key"
|
|
136
|
+
).all(projectId, category).map(rowToMemory);
|
|
137
|
+
}
|
|
138
|
+
return this.db.prepare(
|
|
139
|
+
"SELECT * FROM project_memory WHERE project_id = ? ORDER BY category, key"
|
|
140
|
+
).all(projectId).map(rowToMemory);
|
|
141
|
+
}
|
|
142
|
+
get(projectId, key) {
|
|
143
|
+
const row = this.db.prepare("SELECT * FROM project_memory WHERE project_id = ? AND key = ?").get(projectId, key);
|
|
144
|
+
return row ? rowToMemory(row) : void 0;
|
|
145
|
+
}
|
|
146
|
+
set(projectId, key, value, category = "general") {
|
|
147
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
148
|
+
this.db.prepare(
|
|
149
|
+
`INSERT INTO project_memory (project_id, key, value, category, created_at, updated_at)
|
|
150
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
151
|
+
ON CONFLICT(project_id, key) DO UPDATE SET value = excluded.value, category = excluded.category, updated_at = excluded.updated_at`
|
|
152
|
+
).run(projectId, key, value, category, now, now);
|
|
153
|
+
return this.get(projectId, key);
|
|
154
|
+
}
|
|
155
|
+
remove(projectId, key) {
|
|
156
|
+
this.db.prepare("DELETE FROM project_memory WHERE project_id = ? AND key = ?").run(projectId, key);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var ProjectManager = class {
|
|
160
|
+
constructor(db) {
|
|
161
|
+
this.db = db;
|
|
162
|
+
this.projects = new ProjectRepository(db);
|
|
163
|
+
this.memory = new ProjectMemoryRepository(db);
|
|
164
|
+
}
|
|
165
|
+
projects;
|
|
166
|
+
memory;
|
|
167
|
+
activeProjectId = null;
|
|
168
|
+
/** Register a new project from a directory path. */
|
|
169
|
+
register(path, name, opts) {
|
|
170
|
+
const absPath = resolve(path);
|
|
171
|
+
if (!existsSync(absPath)) {
|
|
172
|
+
throw new Error(`Path does not exist: ${absPath}`);
|
|
173
|
+
}
|
|
174
|
+
const existing = this.projects.getByPath(absPath);
|
|
175
|
+
if (existing) return existing;
|
|
176
|
+
const projectName = name ?? basename(absPath);
|
|
177
|
+
if (this.projects.getByName(projectName)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Project "${projectName}" already exists. Use --name to specify a different name.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
let description = opts?.description ?? "";
|
|
183
|
+
if (!description) {
|
|
184
|
+
try {
|
|
185
|
+
const pkg = JSON.parse(
|
|
186
|
+
readFileSync(`${absPath}/package.json`, "utf-8")
|
|
187
|
+
);
|
|
188
|
+
description = pkg.description ?? "";
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return this.projects.create({
|
|
193
|
+
name: projectName,
|
|
194
|
+
path: absPath,
|
|
195
|
+
description,
|
|
196
|
+
budgetDaily: opts?.budgetDaily,
|
|
197
|
+
budgetMonthly: opts?.budgetMonthly
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Switch the active project context. */
|
|
201
|
+
switch(nameOrId) {
|
|
202
|
+
const project = this.projects.getByName(nameOrId) ?? this.projects.getById(nameOrId);
|
|
203
|
+
if (!project) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Project "${nameOrId}" not found. Run 'storm projects list' to see registered projects.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (!project.isActive) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Project "${project.name}" has been removed. Use 'storm projects register' to re-add it.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
this.activeProjectId = project.id;
|
|
214
|
+
return project;
|
|
215
|
+
}
|
|
216
|
+
/** Get the currently active project, or null. */
|
|
217
|
+
getActive() {
|
|
218
|
+
if (!this.activeProjectId) return null;
|
|
219
|
+
return this.projects.getById(this.activeProjectId) ?? null;
|
|
220
|
+
}
|
|
221
|
+
/** Set active project by path (called automatically on session start). */
|
|
222
|
+
activateByPath(path) {
|
|
223
|
+
const absPath = resolve(path);
|
|
224
|
+
const project = this.projects.getByPath(absPath);
|
|
225
|
+
if (project) {
|
|
226
|
+
this.activeProjectId = project.id;
|
|
227
|
+
return project;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
/** Auto-register the current directory if not already registered. */
|
|
232
|
+
autoDetect(path) {
|
|
233
|
+
const absPath = resolve(path);
|
|
234
|
+
const existing = this.projects.getByPath(absPath);
|
|
235
|
+
if (existing) {
|
|
236
|
+
this.activeProjectId = existing.id;
|
|
237
|
+
return existing;
|
|
238
|
+
}
|
|
239
|
+
const hasBrainstormConfig = existsSync(`${absPath}/brainstorm.toml`);
|
|
240
|
+
const hasGit = existsSync(`${absPath}/.git`);
|
|
241
|
+
if (!hasBrainstormConfig && !hasGit) return null;
|
|
242
|
+
const project = this.register(absPath);
|
|
243
|
+
this.activeProjectId = project.id;
|
|
244
|
+
return project;
|
|
245
|
+
}
|
|
246
|
+
/** Scan a directory and register all subdirectories that look like projects. */
|
|
247
|
+
import(parentDir) {
|
|
248
|
+
const absDir = resolve(parentDir);
|
|
249
|
+
if (!existsSync(absDir)) return [];
|
|
250
|
+
const registered = [];
|
|
251
|
+
for (const entry of readdirSync(absDir)) {
|
|
252
|
+
const fullPath = `${absDir}/${entry}`;
|
|
253
|
+
try {
|
|
254
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
255
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
256
|
+
const hasGit = existsSync(`${fullPath}/.git`);
|
|
257
|
+
const hasConfig = existsSync(`${fullPath}/brainstorm.toml`);
|
|
258
|
+
if (!hasGit && !hasConfig) continue;
|
|
259
|
+
if (this.projects.getByPath(fullPath)) continue;
|
|
260
|
+
const project = this.register(fullPath);
|
|
261
|
+
registered.push(project);
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return registered;
|
|
266
|
+
}
|
|
267
|
+
/** Get dashboard summary for a project. */
|
|
268
|
+
dashboard(projectId) {
|
|
269
|
+
const project = this.projects.getById(projectId);
|
|
270
|
+
if (!project) return void 0;
|
|
271
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
272
|
+
const startOfDay = now - now % 86400;
|
|
273
|
+
const startOfMonth = now - now % (86400 * 30);
|
|
274
|
+
const costToday = this.projects.getCost(project.path, startOfDay);
|
|
275
|
+
const costThisMonth = this.projects.getCost(project.path, startOfMonth);
|
|
276
|
+
const sessionCount = this.projects.getSessionCount(project.id);
|
|
277
|
+
return {
|
|
278
|
+
project,
|
|
279
|
+
sessionCount,
|
|
280
|
+
costToday,
|
|
281
|
+
costThisMonth,
|
|
282
|
+
budgetDailyUsed: project.budgetDaily ? costToday / project.budgetDaily * 100 : 0,
|
|
283
|
+
budgetMonthlyUsed: project.budgetMonthly ? costThisMonth / project.budgetMonthly * 100 : 0
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** Check if a project is within budget. Returns remaining budget or null if no limit. */
|
|
287
|
+
checkBudget(projectId) {
|
|
288
|
+
const project = this.projects.getById(projectId);
|
|
289
|
+
if (!project) return { withinBudget: true, remaining: null };
|
|
290
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
291
|
+
if (project.budgetDaily) {
|
|
292
|
+
const startOfDay = now - now % 86400;
|
|
293
|
+
const costToday = this.projects.getCost(project.path, startOfDay);
|
|
294
|
+
const remaining = project.budgetDaily - costToday;
|
|
295
|
+
if (remaining <= 0) {
|
|
296
|
+
return {
|
|
297
|
+
withinBudget: false,
|
|
298
|
+
remaining: 0,
|
|
299
|
+
message: `Daily budget exceeded for "${project.name}": $${costToday.toFixed(2)} / $${project.budgetDaily.toFixed(2)}`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (project.budgetMonthly) {
|
|
304
|
+
const startOfMonth = now - now % (86400 * 30);
|
|
305
|
+
const costThisMonth = this.projects.getCost(project.path, startOfMonth);
|
|
306
|
+
const remaining = project.budgetMonthly - costThisMonth;
|
|
307
|
+
if (remaining <= 0) {
|
|
308
|
+
return {
|
|
309
|
+
withinBudget: false,
|
|
310
|
+
remaining: 0,
|
|
311
|
+
message: `Monthly budget exceeded for "${project.name}": $${costThisMonth.toFixed(2)} / $${project.budgetMonthly.toFixed(2)}`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { withinBudget: true, remaining };
|
|
315
|
+
}
|
|
316
|
+
return { withinBudget: true, remaining: null };
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
function buildProjectContext(project, memory) {
|
|
320
|
+
const parts = [];
|
|
321
|
+
parts.push(`# Active Project: ${project.name}`);
|
|
322
|
+
parts.push(`Path: ${project.path}`);
|
|
323
|
+
if (project.description) {
|
|
324
|
+
parts.push(project.description);
|
|
325
|
+
}
|
|
326
|
+
if (project.customInstructions) {
|
|
327
|
+
parts.push("");
|
|
328
|
+
parts.push("## Project Instructions");
|
|
329
|
+
parts.push(project.customInstructions);
|
|
330
|
+
}
|
|
331
|
+
if (project.knowledgeFiles.length > 0) {
|
|
332
|
+
const knowledgeSections = [];
|
|
333
|
+
for (const filePath of project.knowledgeFiles) {
|
|
334
|
+
const fullPath = filePath.startsWith("/") ? filePath : `${project.path}/${filePath}`;
|
|
335
|
+
if (!existsSync2(fullPath)) continue;
|
|
336
|
+
try {
|
|
337
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
338
|
+
const maxChars = 4e3;
|
|
339
|
+
const truncated = content.length > maxChars ? content.slice(0, maxChars) + "\n\n[... truncated]" : content;
|
|
340
|
+
knowledgeSections.push(`### ${filePath}
|
|
341
|
+
|
|
342
|
+
${truncated}`);
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (knowledgeSections.length > 0) {
|
|
347
|
+
parts.push("");
|
|
348
|
+
parts.push("## Project Knowledge");
|
|
349
|
+
parts.push(...knowledgeSections);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (memory.length > 0) {
|
|
353
|
+
parts.push("");
|
|
354
|
+
parts.push("## Project Memory");
|
|
355
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
356
|
+
for (const entry of memory) {
|
|
357
|
+
const list = byCategory.get(entry.category) ?? [];
|
|
358
|
+
list.push(entry);
|
|
359
|
+
byCategory.set(entry.category, list);
|
|
360
|
+
}
|
|
361
|
+
for (const cat of ["warning", "convention", "decision", "general"]) {
|
|
362
|
+
const entries = byCategory.get(cat);
|
|
363
|
+
if (!entries) continue;
|
|
364
|
+
const label = cat === "warning" ? "Warnings" : cat === "convention" ? "Conventions" : cat === "decision" ? "Decisions" : "Notes";
|
|
365
|
+
parts.push(`
|
|
366
|
+
### ${label}`);
|
|
367
|
+
for (const entry of entries) {
|
|
368
|
+
parts.push(`- **${entry.key}**: ${entry.value}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (project.budgetDaily || project.budgetMonthly) {
|
|
373
|
+
parts.push("");
|
|
374
|
+
parts.push("## Budget");
|
|
375
|
+
if (project.budgetDaily) {
|
|
376
|
+
parts.push(`- Daily limit: $${project.budgetDaily.toFixed(2)}`);
|
|
377
|
+
}
|
|
378
|
+
if (project.budgetMonthly) {
|
|
379
|
+
parts.push(`- Monthly limit: $${project.budgetMonthly.toFixed(2)}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return parts.join("\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export {
|
|
386
|
+
ProjectRepository,
|
|
387
|
+
ProjectMemoryRepository,
|
|
388
|
+
ProjectManager,
|
|
389
|
+
buildProjectContext
|
|
390
|
+
};
|
|
391
|
+
//# sourceMappingURL=chunk-2CHZHDIM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../projects/src/repository.ts","../../projects/src/manager.ts","../../projects/src/context-builder.ts"],"sourcesContent":["/**\n * ProjectRepository — CRUD for the projects and project_memory tables.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Project, ProjectMemoryEntry } from \"@brainst0rm/shared\";\n\nfunction rowToProject(row: any): Project {\n return {\n id: row.id,\n name: row.name,\n path: row.path,\n description: row.description,\n customInstructions: row.custom_instructions ?? undefined,\n knowledgeFiles: JSON.parse(row.knowledge_files || \"[]\"),\n budgetDaily: row.budget_daily ?? undefined,\n budgetMonthly: row.budget_monthly ?? undefined,\n isActive: Boolean(row.is_active),\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport class ProjectRepository {\n constructor(private db: Database.Database) {}\n\n list(includeInactive = false): Project[] {\n const sql = includeInactive\n ? \"SELECT * FROM projects ORDER BY name\"\n : \"SELECT * FROM projects WHERE is_active = 1 ORDER BY name\";\n return this.db.prepare(sql).all().map(rowToProject);\n }\n\n getById(id: string): Project | undefined {\n const row = this.db.prepare(\"SELECT * FROM projects WHERE id = ?\").get(id);\n return row ? rowToProject(row) : undefined;\n }\n\n getByName(name: string): Project | undefined {\n const row = this.db\n .prepare(\"SELECT * FROM projects WHERE name = ? AND is_active = 1\")\n .get(name);\n return row ? rowToProject(row) : undefined;\n }\n\n getByPath(path: string): Project | undefined {\n const row = this.db\n .prepare(\"SELECT * FROM projects WHERE path = ? AND is_active = 1\")\n .get(path);\n return row ? rowToProject(row) : undefined;\n }\n\n create(data: {\n name: string;\n path: string;\n description?: string;\n customInstructions?: string;\n knowledgeFiles?: string[];\n budgetDaily?: number;\n budgetMonthly?: number;\n }): Project {\n const id = randomUUID();\n const now = Math.floor(Date.now() / 1000);\n\n this.db\n .prepare(\n `INSERT INTO projects (id, name, path, description, custom_instructions, knowledge_files, budget_daily, budget_monthly, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n id,\n data.name,\n data.path,\n data.description ?? \"\",\n data.customInstructions ?? null,\n JSON.stringify(data.knowledgeFiles ?? []),\n data.budgetDaily ?? null,\n data.budgetMonthly ?? null,\n now,\n now,\n );\n\n return this.getById(id)!;\n }\n\n update(\n id: string,\n data: Partial<{\n name: string;\n description: string;\n customInstructions: string | null;\n knowledgeFiles: string[];\n budgetDaily: number | null;\n budgetMonthly: number | null;\n isActive: boolean;\n }>,\n ): Project | undefined {\n const sets: string[] = [];\n const values: any[] = [];\n\n if (data.name !== undefined) {\n sets.push(\"name = ?\");\n values.push(data.name);\n }\n if (data.description !== undefined) {\n sets.push(\"description = ?\");\n values.push(data.description);\n }\n if (data.customInstructions !== undefined) {\n sets.push(\"custom_instructions = ?\");\n values.push(data.customInstructions);\n }\n if (data.knowledgeFiles !== undefined) {\n sets.push(\"knowledge_files = ?\");\n values.push(JSON.stringify(data.knowledgeFiles));\n }\n if (data.budgetDaily !== undefined) {\n sets.push(\"budget_daily = ?\");\n values.push(data.budgetDaily);\n }\n if (data.budgetMonthly !== undefined) {\n sets.push(\"budget_monthly = ?\");\n values.push(data.budgetMonthly);\n }\n if (data.isActive !== undefined) {\n sets.push(\"is_active = ?\");\n values.push(data.isActive ? 1 : 0);\n }\n\n if (sets.length === 0) return this.getById(id);\n\n sets.push(\"updated_at = ?\");\n values.push(Math.floor(Date.now() / 1000));\n values.push(id);\n\n this.db\n .prepare(`UPDATE projects SET ${sets.join(\", \")} WHERE id = ?`)\n .run(...values);\n\n return this.getById(id);\n }\n\n delete(id: string): void {\n this.update(id, { isActive: false });\n }\n\n /** Get total cost for a project in a given time window. */\n getCost(projectPath: string, sinceTimestamp: number): number {\n const row = this.db\n .prepare(\n \"SELECT COALESCE(SUM(cost), 0) as total FROM cost_records WHERE project_path = ? AND timestamp >= ?\",\n )\n .get(projectPath, sinceTimestamp) as any;\n return row?.total ?? 0;\n }\n\n /** Count sessions for a project. */\n getSessionCount(projectId: string): number {\n const row = this.db\n .prepare(\"SELECT COUNT(*) as count FROM sessions WHERE project_id = ?\")\n .get(projectId) as any;\n return row?.count ?? 0;\n }\n}\n\n// ── Project Memory ──────────────────────────────────────────────────\n\nfunction rowToMemory(row: any): ProjectMemoryEntry {\n return {\n id: row.id,\n projectId: row.project_id,\n key: row.key,\n value: row.value,\n category: row.category,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport class ProjectMemoryRepository {\n constructor(private db: Database.Database) {}\n\n list(projectId: string, category?: string): ProjectMemoryEntry[] {\n if (category) {\n return this.db\n .prepare(\n \"SELECT * FROM project_memory WHERE project_id = ? AND category = ? ORDER BY key\",\n )\n .all(projectId, category)\n .map(rowToMemory);\n }\n return this.db\n .prepare(\n \"SELECT * FROM project_memory WHERE project_id = ? ORDER BY category, key\",\n )\n .all(projectId)\n .map(rowToMemory);\n }\n\n get(projectId: string, key: string): ProjectMemoryEntry | undefined {\n const row = this.db\n .prepare(\"SELECT * FROM project_memory WHERE project_id = ? AND key = ?\")\n .get(projectId, key);\n return row ? rowToMemory(row) : undefined;\n }\n\n set(\n projectId: string,\n key: string,\n value: string,\n category = \"general\",\n ): ProjectMemoryEntry {\n const now = Math.floor(Date.now() / 1000);\n this.db\n .prepare(\n `INSERT INTO project_memory (project_id, key, value, category, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, key) DO UPDATE SET value = excluded.value, category = excluded.category, updated_at = excluded.updated_at`,\n )\n .run(projectId, key, value, category, now, now);\n return this.get(projectId, key)!;\n }\n\n remove(projectId: string, key: string): void {\n this.db\n .prepare(\"DELETE FROM project_memory WHERE project_id = ? AND key = ?\")\n .run(projectId, key);\n }\n}\n","/**\n * ProjectManager — high-level project operations.\n *\n * Handles registration, switching, auto-detect, budget checking,\n * and dashboard aggregation.\n */\n\nimport type Database from \"better-sqlite3\";\nimport { basename, resolve } from \"node:path\";\nimport { existsSync, readdirSync, statSync, readFileSync } from \"node:fs\";\nimport type { Project } from \"@brainst0rm/shared\";\nimport { ProjectRepository, ProjectMemoryRepository } from \"./repository.js\";\n\nexport interface ProjectDashboard {\n project: Project;\n sessionCount: number;\n costToday: number;\n costThisMonth: number;\n budgetDailyUsed: number; // percentage\n budgetMonthlyUsed: number; // percentage\n}\n\nexport class ProjectManager {\n readonly projects: ProjectRepository;\n readonly memory: ProjectMemoryRepository;\n private activeProjectId: string | null = null;\n\n constructor(private db: Database.Database) {\n this.projects = new ProjectRepository(db);\n this.memory = new ProjectMemoryRepository(db);\n }\n\n /** Register a new project from a directory path. */\n register(\n path: string,\n name?: string,\n opts?: {\n description?: string;\n budgetDaily?: number;\n budgetMonthly?: number;\n },\n ): Project {\n const absPath = resolve(path);\n if (!existsSync(absPath)) {\n throw new Error(`Path does not exist: ${absPath}`);\n }\n\n // Check if already registered\n const existing = this.projects.getByPath(absPath);\n if (existing) return existing;\n\n const projectName = name ?? basename(absPath);\n\n // Check name uniqueness\n if (this.projects.getByName(projectName)) {\n throw new Error(\n `Project \"${projectName}\" already exists. Use --name to specify a different name.`,\n );\n }\n\n // Auto-detect description from BRAINSTORM.md or package.json\n let description = opts?.description ?? \"\";\n if (!description) {\n try {\n const pkg = JSON.parse(\n readFileSync(`${absPath}/package.json`, \"utf-8\"),\n );\n description = pkg.description ?? \"\";\n } catch {\n // No package.json\n }\n }\n\n return this.projects.create({\n name: projectName,\n path: absPath,\n description,\n budgetDaily: opts?.budgetDaily,\n budgetMonthly: opts?.budgetMonthly,\n });\n }\n\n /** Switch the active project context. */\n switch(nameOrId: string): Project {\n const project =\n this.projects.getByName(nameOrId) ?? this.projects.getById(nameOrId);\n if (!project) {\n throw new Error(\n `Project \"${nameOrId}\" not found. Run 'storm projects list' to see registered projects.`,\n );\n }\n if (!project.isActive) {\n throw new Error(\n `Project \"${project.name}\" has been removed. Use 'storm projects register' to re-add it.`,\n );\n }\n this.activeProjectId = project.id;\n return project;\n }\n\n /** Get the currently active project, or null. */\n getActive(): Project | null {\n if (!this.activeProjectId) return null;\n return this.projects.getById(this.activeProjectId) ?? null;\n }\n\n /** Set active project by path (called automatically on session start). */\n activateByPath(path: string): Project | null {\n const absPath = resolve(path);\n const project = this.projects.getByPath(absPath);\n if (project) {\n this.activeProjectId = project.id;\n return project;\n }\n return null;\n }\n\n /** Auto-register the current directory if not already registered. */\n autoDetect(path: string): Project | null {\n const absPath = resolve(path);\n const existing = this.projects.getByPath(absPath);\n if (existing) {\n this.activeProjectId = existing.id;\n return existing;\n }\n\n // Only auto-register if there's a brainstorm.toml or .git directory\n const hasBrainstormConfig = existsSync(`${absPath}/brainstorm.toml`);\n const hasGit = existsSync(`${absPath}/.git`);\n if (!hasBrainstormConfig && !hasGit) return null;\n\n const project = this.register(absPath);\n this.activeProjectId = project.id;\n return project;\n }\n\n /** Scan a directory and register all subdirectories that look like projects. */\n import(parentDir: string): Project[] {\n const absDir = resolve(parentDir);\n if (!existsSync(absDir)) return [];\n\n const registered: Project[] = [];\n for (const entry of readdirSync(absDir)) {\n const fullPath = `${absDir}/${entry}`;\n try {\n if (!statSync(fullPath).isDirectory()) continue;\n if (entry.startsWith(\".\") || entry === \"node_modules\") continue;\n\n // Must have .git or brainstorm.toml to qualify\n const hasGit = existsSync(`${fullPath}/.git`);\n const hasConfig = existsSync(`${fullPath}/brainstorm.toml`);\n if (!hasGit && !hasConfig) continue;\n\n // Skip if already registered\n if (this.projects.getByPath(fullPath)) continue;\n\n const project = this.register(fullPath);\n registered.push(project);\n } catch {\n // Skip directories we can't access\n }\n }\n return registered;\n }\n\n /** Get dashboard summary for a project. */\n dashboard(projectId: string): ProjectDashboard | undefined {\n const project = this.projects.getById(projectId);\n if (!project) return undefined;\n\n const now = Math.floor(Date.now() / 1000);\n const startOfDay = now - (now % 86400);\n const startOfMonth = now - (now % (86400 * 30)); // approximate\n\n const costToday = this.projects.getCost(project.path, startOfDay);\n const costThisMonth = this.projects.getCost(project.path, startOfMonth);\n const sessionCount = this.projects.getSessionCount(project.id);\n\n return {\n project,\n sessionCount,\n costToday,\n costThisMonth,\n budgetDailyUsed: project.budgetDaily\n ? (costToday / project.budgetDaily) * 100\n : 0,\n budgetMonthlyUsed: project.budgetMonthly\n ? (costThisMonth / project.budgetMonthly) * 100\n : 0,\n };\n }\n\n /** Check if a project is within budget. Returns remaining budget or null if no limit. */\n checkBudget(projectId: string): {\n withinBudget: boolean;\n remaining: number | null;\n message?: string;\n } {\n const project = this.projects.getById(projectId);\n if (!project) return { withinBudget: true, remaining: null };\n\n const now = Math.floor(Date.now() / 1000);\n\n if (project.budgetDaily) {\n const startOfDay = now - (now % 86400);\n const costToday = this.projects.getCost(project.path, startOfDay);\n const remaining = project.budgetDaily - costToday;\n if (remaining <= 0) {\n return {\n withinBudget: false,\n remaining: 0,\n message: `Daily budget exceeded for \"${project.name}\": $${costToday.toFixed(2)} / $${project.budgetDaily.toFixed(2)}`,\n };\n }\n }\n\n if (project.budgetMonthly) {\n const startOfMonth = now - (now % (86400 * 30));\n const costThisMonth = this.projects.getCost(project.path, startOfMonth);\n const remaining = project.budgetMonthly - costThisMonth;\n if (remaining <= 0) {\n return {\n withinBudget: false,\n remaining: 0,\n message: `Monthly budget exceeded for \"${project.name}\": $${costThisMonth.toFixed(2)} / $${project.budgetMonthly.toFixed(2)}`,\n };\n }\n return { withinBudget: true, remaining };\n }\n\n return { withinBudget: true, remaining: null };\n }\n}\n","/**\n * Build project-specific context for injection into the system prompt.\n *\n * Combines: custom instructions + knowledge file summaries + project memory.\n * This is the Brainstorm equivalent of claude.ai's Project Instructions.\n */\n\nimport type { Project, ProjectMemoryEntry } from \"@brainst0rm/shared\";\nimport { existsSync, readFileSync } from \"node:fs\";\n\n/**\n * Build a markdown section with project context for the system prompt.\n */\nexport function buildProjectContext(\n project: Project,\n memory: ProjectMemoryEntry[],\n): string {\n const parts: string[] = [];\n\n parts.push(`# Active Project: ${project.name}`);\n parts.push(`Path: ${project.path}`);\n if (project.description) {\n parts.push(project.description);\n }\n\n // Custom instructions (the project's \"system prompt override\")\n if (project.customInstructions) {\n parts.push(\"\");\n parts.push(\"## Project Instructions\");\n parts.push(project.customInstructions);\n }\n\n // Knowledge files — read and include relevant ones\n if (project.knowledgeFiles.length > 0) {\n const knowledgeSections: string[] = [];\n for (const filePath of project.knowledgeFiles) {\n const fullPath = filePath.startsWith(\"/\")\n ? filePath\n : `${project.path}/${filePath}`;\n\n if (!existsSync(fullPath)) continue;\n\n try {\n const content = readFileSync(fullPath, \"utf-8\");\n // Truncate large files\n const maxChars = 4000;\n const truncated =\n content.length > maxChars\n ? content.slice(0, maxChars) + \"\\n\\n[... truncated]\"\n : content;\n knowledgeSections.push(`### ${filePath}\\n\\n${truncated}`);\n } catch {\n // Skip unreadable files\n }\n }\n\n if (knowledgeSections.length > 0) {\n parts.push(\"\");\n parts.push(\"## Project Knowledge\");\n parts.push(...knowledgeSections);\n }\n }\n\n // Project memory — decisions, conventions, warnings\n if (memory.length > 0) {\n parts.push(\"\");\n parts.push(\"## Project Memory\");\n\n const byCategory = new Map<string, ProjectMemoryEntry[]>();\n for (const entry of memory) {\n const list = byCategory.get(entry.category) ?? [];\n list.push(entry);\n byCategory.set(entry.category, list);\n }\n\n // Warnings first (most important)\n for (const cat of [\"warning\", \"convention\", \"decision\", \"general\"]) {\n const entries = byCategory.get(cat);\n if (!entries) continue;\n\n const label =\n cat === \"warning\"\n ? \"Warnings\"\n : cat === \"convention\"\n ? \"Conventions\"\n : cat === \"decision\"\n ? \"Decisions\"\n : \"Notes\";\n\n parts.push(`\\n### ${label}`);\n for (const entry of entries) {\n parts.push(`- **${entry.key}**: ${entry.value}`);\n }\n }\n }\n\n // Budget context\n if (project.budgetDaily || project.budgetMonthly) {\n parts.push(\"\");\n parts.push(\"## Budget\");\n if (project.budgetDaily) {\n parts.push(`- Daily limit: $${project.budgetDaily.toFixed(2)}`);\n }\n if (project.budgetMonthly) {\n parts.push(`- Monthly limit: $${project.budgetMonthly.toFixed(2)}`);\n }\n }\n\n return parts.join(\"\\n\");\n}\n"],"mappings":"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","names":["existsSync","readFileSync"]}
|