idea-manager 0.3.2 → 0.5.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.
Files changed (43) hide show
  1. package/package.json +2 -1
  2. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.ts +13 -3
  3. package/src/app/globals.css +72 -0
  4. package/src/app/projects/[id]/page.tsx +34 -6
  5. package/src/components/brainstorm/Editor.tsx +1 -84
  6. package/src/components/task/TaskChat.tsx +8 -5
  7. package/src/components/ui/AiPolicyModal.tsx +98 -0
  8. package/src/lib/ai/client.ts +45 -172
  9. package/src/lib/db/queries/projects.ts +3 -2
  10. package/src/lib/db/schema.ts +3 -70
  11. package/src/types/index.ts +1 -90
  12. package/src/app/api/projects/[id]/cleanup/route.ts +0 -32
  13. package/src/app/api/projects/[id]/conversations/route.ts +0 -50
  14. package/src/app/api/projects/[id]/items/[itemId]/prompt/route.ts +0 -51
  15. package/src/app/api/projects/[id]/items/[itemId]/refine/route.ts +0 -36
  16. package/src/app/api/projects/[id]/items/[itemId]/route.ts +0 -95
  17. package/src/app/api/projects/[id]/items/route.ts +0 -67
  18. package/src/app/api/projects/[id]/memos/route.ts +0 -18
  19. package/src/app/api/projects/[id]/scan/route.ts +0 -73
  20. package/src/app/api/projects/[id]/scan/stream/route.ts +0 -112
  21. package/src/app/api/projects/[id]/structure/route.ts +0 -59
  22. package/src/app/api/projects/[id]/structure/stream/route.ts +0 -157
  23. package/src/components/ScanPanel.tsx +0 -743
  24. package/src/components/brainstorm/MemoPin.tsx +0 -117
  25. package/src/components/tree/CardView.tsx +0 -206
  26. package/src/components/tree/ItemDetail.tsx +0 -196
  27. package/src/components/tree/LockToggle.tsx +0 -23
  28. package/src/components/tree/RefinePopover.tsx +0 -157
  29. package/src/components/tree/StatusBadge.tsx +0 -32
  30. package/src/components/tree/TreeNode.tsx +0 -227
  31. package/src/components/tree/TreeView.tsx +0 -304
  32. package/src/lib/ai/chat-responder.ts +0 -71
  33. package/src/lib/ai/cleanup.ts +0 -87
  34. package/src/lib/ai/prompter.ts +0 -78
  35. package/src/lib/ai/refiner.ts +0 -128
  36. package/src/lib/ai/structurer.ts +0 -403
  37. package/src/lib/db/queries/context.ts +0 -76
  38. package/src/lib/db/queries/conversations.ts +0 -46
  39. package/src/lib/db/queries/items.ts +0 -268
  40. package/src/lib/db/queries/memos.ts +0 -66
  41. package/src/lib/db/queries/prompts.ts +0 -68
  42. package/src/lib/scanner.ts +0 -573
  43. package/src/lib/task-store.ts +0 -97
@@ -1,76 +0,0 @@
1
- import { getDb } from '../index';
2
- import { generateId } from '../../utils/id';
3
- import type { IProjectContext } from '@/types';
4
-
5
- export function getProjectContexts(projectId: string): IProjectContext[] {
6
- const db = getDb();
7
- return db.prepare(
8
- 'SELECT * FROM project_context WHERE project_id = ? ORDER BY file_path ASC'
9
- ).all(projectId) as IProjectContext[];
10
- }
11
-
12
- export function getProjectContextSummary(projectId: string): string {
13
- const contexts = getProjectContexts(projectId);
14
- if (contexts.length === 0) return '';
15
-
16
- return contexts
17
- .map(c => `--- ${c.file_path} ---\n${c.content}`)
18
- .join('\n\n');
19
- }
20
-
21
- export interface SubProjectContext {
22
- name: string; // sub-project directory name (or "(root)")
23
- contexts: IProjectContext[];
24
- totalSize: number;
25
- }
26
-
27
- export function getProjectContextsBySubProject(projectId: string): SubProjectContext[] {
28
- const contexts = getProjectContexts(projectId);
29
- if (contexts.length === 0) return [];
30
-
31
- // Simple grouping: first path segment = sub-project
32
- // e.g. "jabis-template/apps/prototype/src/App.tsx" → "jabis-template"
33
- // "package.json" or "__directory_tree.txt" → "(root)"
34
- const groups = new Map<string, IProjectContext[]>();
35
-
36
- for (const ctx of contexts) {
37
- const parts = ctx.file_path.split('/');
38
- const group = (parts.length <= 1 || ctx.file_path.startsWith('__'))
39
- ? '(root)'
40
- : parts[0];
41
-
42
- if (!groups.has(group)) groups.set(group, []);
43
- groups.get(group)!.push(ctx);
44
- }
45
-
46
- return Array.from(groups.entries()).map(([name, ctxs]) => ({
47
- name,
48
- contexts: ctxs,
49
- totalSize: ctxs.reduce((sum, c) => sum + c.content.length, 0),
50
- }));
51
- }
52
-
53
- export function buildSubProjectSummary(sub: SubProjectContext): string {
54
- return sub.contexts
55
- .map(c => `--- ${c.file_path} ---\n${c.content}`)
56
- .join('\n\n');
57
- }
58
-
59
- export function replaceProjectContexts(projectId: string, files: { file_path: string; content: string }[]): IProjectContext[] {
60
- const db = getDb();
61
- const now = new Date().toISOString();
62
-
63
- const replace = db.transaction(() => {
64
- db.prepare('DELETE FROM project_context WHERE project_id = ?').run(projectId);
65
-
66
- for (const file of files) {
67
- const id = generateId();
68
- db.prepare(
69
- 'INSERT INTO project_context (id, project_id, file_path, content, scanned_at) VALUES (?, ?, ?, ?, ?)'
70
- ).run(id, projectId, file.file_path, file.content, now);
71
- }
72
- });
73
-
74
- replace();
75
- return getProjectContexts(projectId);
76
- }
@@ -1,46 +0,0 @@
1
- import { getDb } from '../index';
2
- import { generateId } from '../../utils/id';
3
- import type { IConversation } from '@/types';
4
-
5
- export function getConversations(projectId: string, limit = 20): IConversation[] {
6
- const db = getDb();
7
- return db.prepare(
8
- `SELECT * FROM conversations WHERE project_id = ?
9
- ORDER BY created_at ASC LIMIT ?`
10
- ).all(projectId, limit) as IConversation[];
11
- }
12
-
13
- export function getRecentConversations(projectId: string, limit = 20): IConversation[] {
14
- const db = getDb();
15
- // Get the last N messages ordered chronologically
16
- const rows = db.prepare(
17
- `SELECT * FROM (
18
- SELECT * FROM conversations WHERE project_id = ?
19
- ORDER BY created_at DESC LIMIT ?
20
- ) sub ORDER BY created_at ASC`
21
- ).all(projectId, limit) as IConversation[];
22
- return rows;
23
- }
24
-
25
- export function addMessage(
26
- projectId: string,
27
- role: 'assistant' | 'user',
28
- content: string,
29
- metadata?: string,
30
- ): IConversation {
31
- const db = getDb();
32
- const id = generateId();
33
- const now = new Date().toISOString();
34
-
35
- db.prepare(
36
- `INSERT INTO conversations (id, project_id, role, content, metadata, created_at)
37
- VALUES (?, ?, ?, ?, ?, ?)`
38
- ).run(id, projectId, role, content, metadata ?? null, now);
39
-
40
- return db.prepare('SELECT * FROM conversations WHERE id = ?').get(id) as IConversation;
41
- }
42
-
43
- export function deleteConversations(projectId: string): void {
44
- const db = getDb();
45
- db.prepare('DELETE FROM conversations WHERE project_id = ?').run(projectId);
46
- }
@@ -1,268 +0,0 @@
1
- import { getDb } from '../index';
2
- import { generateId } from '../../utils/id';
3
- import type { IItem, IItemTree, ItemType, ItemPriority, ItemStatus } from '@/types';
4
-
5
- export function getItems(projectId: string): IItem[] {
6
- const db = getDb();
7
- return db.prepare(
8
- 'SELECT * FROM items WHERE project_id = ? ORDER BY sort_order ASC'
9
- ).all(projectId) as IItem[];
10
- }
11
-
12
- export function getItem(id: string): IItem | undefined {
13
- const db = getDb();
14
- return db.prepare('SELECT * FROM items WHERE id = ?').get(id) as IItem | undefined;
15
- }
16
-
17
- export function getItemTree(projectId: string): IItemTree[] {
18
- const items = getItems(projectId);
19
- return buildTree(items);
20
- }
21
-
22
- function buildTree(items: IItem[]): IItemTree[] {
23
- const map = new Map<string, IItemTree>();
24
- const roots: IItemTree[] = [];
25
-
26
- for (const item of items) {
27
- map.set(item.id, { ...item, children: [] });
28
- }
29
-
30
- for (const item of items) {
31
- const node = map.get(item.id)!;
32
- if (item.parent_id && map.has(item.parent_id)) {
33
- map.get(item.parent_id)!.children.push(node);
34
- } else {
35
- roots.push(node);
36
- }
37
- }
38
-
39
- return roots;
40
- }
41
-
42
- export function createItem(data: {
43
- project_id: string;
44
- brainstorm_id?: string;
45
- parent_id?: string;
46
- title: string;
47
- description?: string;
48
- item_type?: ItemType;
49
- priority?: ItemPriority;
50
- sort_order?: number;
51
- }): IItem {
52
- const db = getDb();
53
- const id = generateId();
54
- const now = new Date().toISOString();
55
-
56
- db.prepare(`
57
- INSERT INTO items (id, project_id, brainstorm_id, parent_id, title, description,
58
- item_type, priority, status, is_locked, is_pinned, sort_order, created_at, updated_at)
59
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 1, 1, ?, ?, ?)
60
- `).run(
61
- id,
62
- data.project_id,
63
- data.brainstorm_id ?? null,
64
- data.parent_id ?? null,
65
- data.title,
66
- data.description ?? '',
67
- data.item_type ?? 'feature',
68
- data.priority ?? 'medium',
69
- data.sort_order ?? 0,
70
- now,
71
- now,
72
- );
73
-
74
- return db.prepare('SELECT * FROM items WHERE id = ?').get(id) as IItem;
75
- }
76
-
77
- export function updateItem(id: string, data: {
78
- title?: string;
79
- description?: string;
80
- status?: ItemStatus;
81
- is_locked?: boolean;
82
- is_pinned?: boolean;
83
- priority?: ItemPriority;
84
- sort_order?: number;
85
- }): IItem | undefined {
86
- const db = getDb();
87
- const item = db.prepare('SELECT * FROM items WHERE id = ?').get(id) as IItem | undefined;
88
- if (!item) return undefined;
89
-
90
- const now = new Date().toISOString();
91
- db.prepare(`
92
- UPDATE items SET
93
- title = ?, description = ?, status = ?, is_locked = ?, is_pinned = ?,
94
- priority = ?, sort_order = ?, updated_at = ?
95
- WHERE id = ?
96
- `).run(
97
- data.title ?? item.title,
98
- data.description ?? item.description,
99
- data.status ?? item.status,
100
- data.is_locked !== undefined ? (data.is_locked ? 1 : 0) : (item.is_locked ? 1 : 0),
101
- data.is_pinned !== undefined ? (data.is_pinned ? 1 : 0) : (item.is_pinned ? 1 : 0),
102
- data.priority ?? item.priority,
103
- data.sort_order ?? item.sort_order,
104
- now,
105
- id,
106
- );
107
-
108
- return db.prepare('SELECT * FROM items WHERE id = ?').get(id) as IItem;
109
- }
110
-
111
- export function deleteItem(id: string): boolean {
112
- const db = getDb();
113
- const item = db.prepare('SELECT * FROM items WHERE id = ?').get(id) as IItem | undefined;
114
- if (!item) return false;
115
-
116
- // Delete children first (recursive via collecting all descendant IDs)
117
- const deleteRecursive = db.transaction(() => {
118
- const collectIds = (parentId: string): string[] => {
119
- const children = db.prepare('SELECT id FROM items WHERE parent_id = ?').all(parentId) as { id: string }[];
120
- const ids: string[] = [];
121
- for (const child of children) {
122
- ids.push(...collectIds(child.id));
123
- ids.push(child.id);
124
- }
125
- return ids;
126
- };
127
-
128
- const idsToDelete = [...collectIds(id), id];
129
- const placeholders = idsToDelete.map(() => '?').join(',');
130
- db.prepare(`DELETE FROM prompts WHERE item_id IN (${placeholders})`).run(...idsToDelete);
131
- db.prepare(`DELETE FROM items WHERE id IN (${placeholders})`).run(...idsToDelete);
132
- });
133
-
134
- deleteRecursive();
135
- return true;
136
- }
137
-
138
- export function deleteItemsByProject(projectId: string): void {
139
- const db = getDb();
140
- db.prepare('DELETE FROM items WHERE project_id = ?').run(projectId);
141
- }
142
-
143
- export function bulkUpdateStatus(projectId: string, status: ItemStatus): void {
144
- const db = getDb();
145
- const now = new Date().toISOString();
146
- db.prepare('UPDATE items SET status = ?, updated_at = ? WHERE project_id = ?').run(status, now, projectId);
147
- }
148
-
149
- type NewItemInput = {
150
- parent_id: string | null;
151
- title: string;
152
- description: string;
153
- item_type: ItemType;
154
- priority: ItemPriority;
155
- status?: ItemStatus;
156
- children?: NewItemInput[];
157
- };
158
-
159
- /**
160
- * Append new items to existing ones (additive).
161
- * Existing items are preserved — only new items are inserted.
162
- */
163
- export function appendItems(projectId: string, brainstormId: string, newItems: NewItemInput[]): IItemTree[] {
164
- const db = getDb();
165
-
166
- const insertItems = db.transaction(() => {
167
- const maxOrder = db.prepare(
168
- 'SELECT MAX(sort_order) as max_order FROM items WHERE project_id = ?'
169
- ).get(projectId) as { max_order: number | null };
170
- let sortOrder = (maxOrder?.max_order ?? -1) + 1;
171
-
172
- const insertRecursive = (items: NewItemInput[], parentId: string | null) => {
173
- for (const item of items) {
174
- const id = generateId();
175
- const now = new Date().toISOString();
176
- db.prepare(`
177
- INSERT INTO items (id, project_id, brainstorm_id, parent_id, title, description,
178
- item_type, priority, status, is_locked, is_pinned, sort_order, created_at, updated_at)
179
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)
180
- `).run(id, projectId, brainstormId, parentId, item.title, item.description,
181
- item.item_type, item.priority, item.status || 'pending', sortOrder++, now, now);
182
-
183
- if (item.children?.length) {
184
- insertRecursive(item.children, id);
185
- }
186
- }
187
- };
188
-
189
- insertRecursive(newItems, null);
190
- });
191
-
192
- insertItems();
193
- return getItemTree(projectId);
194
- }
195
-
196
- /**
197
- * Add children under a specific parent item.
198
- */
199
- export function addChildItems(projectId: string, parentId: string, newChildren: NewItemInput[]): IItemTree[] {
200
- const db = getDb();
201
-
202
- const insertItems = db.transaction(() => {
203
- const maxOrder = db.prepare(
204
- 'SELECT MAX(sort_order) as max_order FROM items WHERE project_id = ?'
205
- ).get(projectId) as { max_order: number | null };
206
- let sortOrder = (maxOrder?.max_order ?? -1) + 1;
207
-
208
- const insertRecursive = (items: NewItemInput[], pid: string | null) => {
209
- for (const item of items) {
210
- const id = generateId();
211
- const now = new Date().toISOString();
212
- db.prepare(`
213
- INSERT INTO items (id, project_id, brainstorm_id, parent_id, title, description,
214
- item_type, priority, status, is_locked, is_pinned, sort_order, created_at, updated_at)
215
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)
216
- `).run(id, projectId, null, pid, item.title, item.description,
217
- item.item_type, item.priority, item.status || 'pending', sortOrder++, now, now);
218
-
219
- if (item.children?.length) {
220
- insertRecursive(item.children, id);
221
- }
222
- }
223
- };
224
-
225
- insertRecursive(newChildren, parentId);
226
- });
227
-
228
- insertItems();
229
- return getItemTree(projectId);
230
- }
231
-
232
- /**
233
- * Replace ALL items for the project with new ones.
234
- * Deletes all existing items first, then inserts the new tree.
235
- */
236
- export function replaceItems(projectId: string, brainstormId: string, newItems: NewItemInput[]): IItemTree[] {
237
- const db = getDb();
238
-
239
- const insertItems = db.transaction(() => {
240
- // Delete ALL existing items and their prompts
241
- db.prepare('DELETE FROM prompts WHERE project_id = ?').run(projectId);
242
- db.prepare('DELETE FROM items WHERE project_id = ?').run(projectId);
243
-
244
- let sortOrder = 0;
245
-
246
- const insertRecursive = (items: NewItemInput[], parentId: string | null) => {
247
- for (const item of items) {
248
- const id = generateId();
249
- const now = new Date().toISOString();
250
- db.prepare(`
251
- INSERT INTO items (id, project_id, brainstorm_id, parent_id, title, description,
252
- item_type, priority, status, is_locked, is_pinned, sort_order, created_at, updated_at)
253
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)
254
- `).run(id, projectId, brainstormId, parentId, item.title, item.description,
255
- item.item_type, item.priority, item.status || 'pending', sortOrder++, now, now);
256
-
257
- if (item.children?.length) {
258
- insertRecursive(item.children, id);
259
- }
260
- }
261
- };
262
-
263
- insertRecursive(newItems, null);
264
- });
265
-
266
- insertItems();
267
- return getItemTree(projectId);
268
- }
@@ -1,66 +0,0 @@
1
- import { getDb } from '../index';
2
- import { generateId } from '../../utils/id';
3
- import type { IMemo } from '@/types';
4
-
5
- export function getMemos(projectId: string, onlyUnresolved = false): IMemo[] {
6
- const db = getDb();
7
- const whereClause = onlyUnresolved
8
- ? 'WHERE project_id = ? AND is_resolved = 0'
9
- : 'WHERE project_id = ?';
10
-
11
- return db.prepare(
12
- `SELECT * FROM memos ${whereClause} ORDER BY created_at ASC`
13
- ).all(projectId) as IMemo[];
14
- }
15
-
16
- export function createMemo(data: {
17
- project_id: string;
18
- conversation_id?: string;
19
- anchor_text: string;
20
- question: string;
21
- }): IMemo {
22
- const db = getDb();
23
- const id = generateId();
24
- const now = new Date().toISOString();
25
-
26
- db.prepare(
27
- `INSERT INTO memos (id, project_id, conversation_id, anchor_text, question, is_resolved, created_at, updated_at)
28
- VALUES (?, ?, ?, ?, ?, 0, ?, ?)`
29
- ).run(id, data.project_id, data.conversation_id ?? null, data.anchor_text, data.question, now, now);
30
-
31
- return db.prepare('SELECT * FROM memos WHERE id = ?').get(id) as IMemo;
32
- }
33
-
34
- export function resolveMemos(projectId: string): void {
35
- const db = getDb();
36
- const now = new Date().toISOString();
37
- db.prepare(
38
- `UPDATE memos SET is_resolved = 1, updated_at = ? WHERE project_id = ? AND is_resolved = 0`
39
- ).run(now, projectId);
40
- }
41
-
42
- export function createMemosFromQuestions(
43
- projectId: string,
44
- conversationId: string,
45
- questions: { anchor_text: string; question: string }[],
46
- ): IMemo[] {
47
- const db = getDb();
48
- const memos: IMemo[] = [];
49
-
50
- const insertMemo = db.prepare(
51
- `INSERT INTO memos (id, project_id, conversation_id, anchor_text, question, is_resolved, created_at, updated_at)
52
- VALUES (?, ?, ?, ?, ?, 0, ?, ?)`
53
- );
54
-
55
- const insertAll = db.transaction(() => {
56
- for (const q of questions) {
57
- const id = generateId();
58
- const now = new Date().toISOString();
59
- insertMemo.run(id, projectId, conversationId, q.anchor_text, q.question, now, now);
60
- memos.push(db.prepare('SELECT * FROM memos WHERE id = ?').get(id) as IMemo);
61
- }
62
- });
63
-
64
- insertAll();
65
- return memos;
66
- }
@@ -1,68 +0,0 @@
1
- import { getDb } from '../index';
2
- import { generateId } from '../../utils/id';
3
- import type { IPrompt } from '@/types';
4
-
5
- export function getPrompt(itemId: string): IPrompt | undefined {
6
- const db = getDb();
7
- return db.prepare(
8
- 'SELECT * FROM prompts WHERE item_id = ? ORDER BY version DESC LIMIT 1'
9
- ).get(itemId) as IPrompt | undefined;
10
- }
11
-
12
- export function getPromptsByProject(projectId: string): IPrompt[] {
13
- const db = getDb();
14
- // Get latest version of each item's prompt
15
- return db.prepare(
16
- `SELECT p.* FROM prompts p
17
- INNER JOIN (
18
- SELECT item_id, MAX(version) as max_version
19
- FROM prompts WHERE project_id = ?
20
- GROUP BY item_id
21
- ) latest ON p.item_id = latest.item_id AND p.version = latest.max_version
22
- WHERE p.project_id = ?`
23
- ).all(projectId, projectId) as IPrompt[];
24
- }
25
-
26
- export function createPrompt(data: {
27
- project_id: string;
28
- item_id: string;
29
- content: string;
30
- prompt_type?: 'auto' | 'manual';
31
- }): IPrompt {
32
- const db = getDb();
33
- const id = generateId();
34
- const now = new Date().toISOString();
35
-
36
- // Get next version number
37
- const existing = db.prepare(
38
- 'SELECT MAX(version) as max_ver FROM prompts WHERE item_id = ?'
39
- ).get(data.item_id) as { max_ver: number | null } | undefined;
40
- const version = (existing?.max_ver ?? 0) + 1;
41
-
42
- db.prepare(
43
- `INSERT INTO prompts (id, project_id, item_id, content, prompt_type, version, created_at)
44
- VALUES (?, ?, ?, ?, ?, ?, ?)`
45
- ).run(id, data.project_id, data.item_id, data.content, data.prompt_type ?? 'auto', version, now);
46
-
47
- return db.prepare('SELECT * FROM prompts WHERE id = ?').get(id) as IPrompt;
48
- }
49
-
50
- export function updatePromptContent(itemId: string, content: string): IPrompt {
51
- // Create a new manual version
52
- const db = getDb();
53
- const existing = getPrompt(itemId);
54
- if (!existing) {
55
- throw new Error('No prompt found for this item');
56
- }
57
- return createPrompt({
58
- project_id: existing.project_id,
59
- item_id: itemId,
60
- content,
61
- prompt_type: 'manual',
62
- });
63
- }
64
-
65
- export function deletePromptsByItem(itemId: string): void {
66
- const db = getDb();
67
- db.prepare('DELETE FROM prompts WHERE item_id = ?').run(itemId);
68
- }