idea-manager 1.7.0 → 1.9.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 (147) hide show
  1. package/.next/build-manifest.json +2 -2
  2. package/.next/routes-manifest.json +8 -0
  3. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  4. package/.next/server/app/_global-error.html +2 -2
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/api/archive/route.js +12 -3
  22. package/.next/server/app/api/archive/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/filesystem/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/filesystem/tree/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/global-memo/route.js +9 -0
  26. package/.next/server/app/api/global-memo/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/projects/[id]/advisor/route.js +34 -0
  29. package/.next/server/app/api/projects/[id]/advisor/route_client-reference-manifest.js +1 -0
  30. package/.next/server/app/api/projects/[id]/apply-distribute/route.js +5 -5
  31. package/.next/server/app/api/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/projects/[id]/auto-distribute/route.js +3 -3
  33. package/.next/server/app/api/projects/[id]/auto-distribute/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/projects/[id]/brainstorm/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/projects/[id]/git-sync/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/projects/[id]/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route.js +12 -3
  38. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.js +3 -3
  40. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.js +9 -0
  42. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route.js +3 -3
  44. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.js +12 -3
  46. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route.js +12 -3
  48. package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/projects/[id]/sub-projects/route.js +12 -3
  50. package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/search/route.js +9 -0
  53. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/sync/route.js +9 -0
  55. package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/index.html +2 -2
  59. package/.next/server/app/index.rsc +3 -3
  60. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  61. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  62. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  64. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  65. package/.next/server/app/page.js +6 -6
  66. package/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
  68. package/.next/server/app-paths-manifest.json +12 -11
  69. package/.next/server/chunks/117.js +9 -0
  70. package/.next/server/pages/404.html +2 -2
  71. package/.next/server/pages/500.html +2 -2
  72. package/.next/static/chunks/374-769431701aab500f.js +1 -0
  73. package/.next/static/chunks/app/_global-error/page-3ff8f59aaa75b8f8.js +1 -0
  74. package/.next/static/chunks/app/api/archive/route-3ff8f59aaa75b8f8.js +1 -0
  75. package/.next/static/chunks/app/api/filesystem/route-3ff8f59aaa75b8f8.js +1 -0
  76. package/.next/static/chunks/app/api/filesystem/tree/route-3ff8f59aaa75b8f8.js +1 -0
  77. package/.next/static/chunks/app/api/global-memo/route-3ff8f59aaa75b8f8.js +1 -0
  78. package/.next/static/chunks/app/api/health/route-3ff8f59aaa75b8f8.js +1 -0
  79. package/.next/static/chunks/app/api/projects/[id]/advisor/route-3ff8f59aaa75b8f8.js +1 -0
  80. package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-3ff8f59aaa75b8f8.js +1 -0
  81. package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-3ff8f59aaa75b8f8.js +1 -0
  82. package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-3ff8f59aaa75b8f8.js +1 -0
  83. package/.next/static/chunks/app/api/projects/[id]/git-sync/route-3ff8f59aaa75b8f8.js +1 -0
  84. package/.next/static/chunks/app/api/projects/[id]/route-3ff8f59aaa75b8f8.js +1 -0
  85. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-3ff8f59aaa75b8f8.js +1 -0
  86. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-3ff8f59aaa75b8f8.js +1 -0
  87. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-3ff8f59aaa75b8f8.js +1 -0
  88. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-3ff8f59aaa75b8f8.js +1 -0
  89. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-3ff8f59aaa75b8f8.js +1 -0
  90. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-3ff8f59aaa75b8f8.js +1 -0
  91. package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-3ff8f59aaa75b8f8.js +1 -0
  92. package/.next/static/chunks/app/api/projects/route-3ff8f59aaa75b8f8.js +1 -0
  93. package/.next/static/chunks/app/api/search/route-3ff8f59aaa75b8f8.js +1 -0
  94. package/.next/static/chunks/app/api/sync/route-3ff8f59aaa75b8f8.js +1 -0
  95. package/.next/static/chunks/app/api/update/route-3ff8f59aaa75b8f8.js +1 -0
  96. package/.next/static/chunks/app/api/version/route-3ff8f59aaa75b8f8.js +1 -0
  97. package/.next/static/chunks/app/{page-9a1dc101e82c397c.js → page-e935ee928da68ca2.js} +7 -7
  98. package/.next/static/chunks/next/dist/client/components/builtin/app-error-3ff8f59aaa75b8f8.js +1 -0
  99. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-3ff8f59aaa75b8f8.js +1 -0
  100. package/.next/static/chunks/next/dist/client/components/builtin/not-found-3ff8f59aaa75b8f8.js +1 -0
  101. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-3ff8f59aaa75b8f8.js +1 -0
  102. package/.next/static/css/e4c7cd5a570312d9.css +3 -0
  103. package/README.ja.md +2 -0
  104. package/README.ko.md +3 -1
  105. package/README.md +3 -1
  106. package/README.zh.md +2 -0
  107. package/package.json +1 -1
  108. package/src/app/api/projects/[id]/advisor/route.ts +71 -0
  109. package/src/cli.ts +10 -0
  110. package/src/components/workspace/ProjectAdvisor.tsx +194 -0
  111. package/src/components/workspace/WorkspacePanel.tsx +23 -0
  112. package/src/lib/ai/project-context.ts +111 -0
  113. package/src/lib/auto-update.ts +136 -0
  114. package/src/lib/db/queries/project-conversations.ts +29 -0
  115. package/src/lib/db/schema.ts +11 -0
  116. package/src/types/index.ts +8 -0
  117. package/.next/static/chunks/374-23189d7e246ad164.js +0 -1
  118. package/.next/static/chunks/app/_global-error/page-e6a77f238d2cdbb9.js +0 -1
  119. package/.next/static/chunks/app/api/archive/route-e6a77f238d2cdbb9.js +0 -1
  120. package/.next/static/chunks/app/api/filesystem/route-e6a77f238d2cdbb9.js +0 -1
  121. package/.next/static/chunks/app/api/filesystem/tree/route-e6a77f238d2cdbb9.js +0 -1
  122. package/.next/static/chunks/app/api/global-memo/route-e6a77f238d2cdbb9.js +0 -1
  123. package/.next/static/chunks/app/api/health/route-e6a77f238d2cdbb9.js +0 -1
  124. package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-e6a77f238d2cdbb9.js +0 -1
  125. package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-e6a77f238d2cdbb9.js +0 -1
  126. package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-e6a77f238d2cdbb9.js +0 -1
  127. package/.next/static/chunks/app/api/projects/[id]/git-sync/route-e6a77f238d2cdbb9.js +0 -1
  128. package/.next/static/chunks/app/api/projects/[id]/route-e6a77f238d2cdbb9.js +0 -1
  129. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-e6a77f238d2cdbb9.js +0 -1
  130. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-e6a77f238d2cdbb9.js +0 -1
  131. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-e6a77f238d2cdbb9.js +0 -1
  132. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-e6a77f238d2cdbb9.js +0 -1
  133. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-e6a77f238d2cdbb9.js +0 -1
  134. package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-e6a77f238d2cdbb9.js +0 -1
  135. package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-e6a77f238d2cdbb9.js +0 -1
  136. package/.next/static/chunks/app/api/projects/route-e6a77f238d2cdbb9.js +0 -1
  137. package/.next/static/chunks/app/api/search/route-e6a77f238d2cdbb9.js +0 -1
  138. package/.next/static/chunks/app/api/sync/route-e6a77f238d2cdbb9.js +0 -1
  139. package/.next/static/chunks/app/api/update/route-e6a77f238d2cdbb9.js +0 -1
  140. package/.next/static/chunks/app/api/version/route-e6a77f238d2cdbb9.js +0 -1
  141. package/.next/static/chunks/next/dist/client/components/builtin/app-error-e6a77f238d2cdbb9.js +0 -1
  142. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-e6a77f238d2cdbb9.js +0 -1
  143. package/.next/static/chunks/next/dist/client/components/builtin/not-found-e6a77f238d2cdbb9.js +0 -1
  144. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-e6a77f238d2cdbb9.js +0 -1
  145. package/.next/static/css/eab748b03f49c43a.css +0 -3
  146. /package/.next/static/{mxrEVQX3r5YlDPZgpDvSp → pxqzEiwniZAUDOUTb5SnX}/_buildManifest.js +0 -0
  147. /package/.next/static/{mxrEVQX3r5YlDPZgpDvSp → pxqzEiwniZAUDOUTb5SnX}/_ssgManifest.js +0 -0
@@ -0,0 +1,111 @@
1
+ import { getProject } from '../db/queries/projects';
2
+ import { getSubProjects } from '../db/queries/sub-projects';
3
+ import { getTasksByProject } from '../db/queries/tasks';
4
+ import { getBrainstorm } from '../db/queries/brainstorms';
5
+ import type { ITask, TaskStatus } from '../../types';
6
+
7
+ const MAX_BRAINSTORM = 4000;
8
+ const NOTE_LIMIT_ACTIVE = 500;
9
+ const NOTE_LIMIT_DEFAULT = 200;
10
+ const MAX_HISTORY_MESSAGES = 20;
11
+
12
+ function truncate(s: string | null | undefined, max: number): string {
13
+ if (!s) return '';
14
+ if (s.length <= max) return s;
15
+ return s.slice(0, max) + '…';
16
+ }
17
+
18
+ function isActive(task: ITask): boolean {
19
+ return (['doing', 'problem', 'testing'] as TaskStatus[]).includes(task.status) || task.is_today;
20
+ }
21
+
22
+ const STATUS_ICON: Record<string, string> = {
23
+ idea: 'idea', doing: 'DOING', writing: 'writing', submitted: 'submitted',
24
+ testing: 'testing', done: 'done', problem: 'PROBLEM',
25
+ };
26
+
27
+ export function buildProjectAdvisorPrompt(projectId: string): string {
28
+ const project = getProject(projectId);
29
+ if (!project) return '';
30
+
31
+ const brainstorm = getBrainstorm(projectId);
32
+ const subs = getSubProjects(projectId);
33
+ const allTasks = getTasksByProject(projectId);
34
+
35
+ // Group tasks by sub-project
36
+ const tasksBySub = new Map<string, ITask[]>();
37
+ for (const t of allTasks) {
38
+ const list = tasksBySub.get(t.sub_project_id) ?? [];
39
+ list.push(t);
40
+ tasksBySub.set(t.sub_project_id, list);
41
+ }
42
+
43
+ // Build task summary per sub-project
44
+ const subSections: string[] = [];
45
+ for (const sub of subs) {
46
+ const tasks = tasksBySub.get(sub.id) ?? [];
47
+ if (tasks.length === 0) {
48
+ subSections.push(`### ${sub.name}\n${sub.description || '(설명 없음)'}\n태스크 없음.`);
49
+ continue;
50
+ }
51
+ const lines: string[] = [];
52
+ lines.push(`### ${sub.name}`);
53
+ if (sub.description) lines.push(sub.description);
54
+ lines.push(`태스크 ${tasks.length}개:`);
55
+ for (const t of tasks) {
56
+ const noteLimit = isActive(t) ? NOTE_LIMIT_ACTIVE : NOTE_LIMIT_DEFAULT;
57
+ const note = truncate(t.description, noteLimit);
58
+ const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
59
+ const flagStr = flags ? ` (${flags})` : '';
60
+ const noteStr = note ? ` — ${note}` : '';
61
+ lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}**${flagStr}${noteStr}`);
62
+ }
63
+ subSections.push(lines.join('\n'));
64
+ }
65
+
66
+ // Stats
67
+ const counts: Record<string, number> = {};
68
+ let todayCount = 0;
69
+ const problemTasks: string[] = [];
70
+ for (const t of allTasks) {
71
+ counts[t.status] = (counts[t.status] ?? 0) + 1;
72
+ if (t.is_today) todayCount++;
73
+ if (t.status === 'problem') problemTasks.push(t.title);
74
+ }
75
+ const statsLines = [
76
+ `- 전체: ${allTasks.length}개`,
77
+ ...Object.entries(counts).map(([k, v]) => ` - ${k}: ${v}`),
78
+ `- Today 표시: ${todayCount}개`,
79
+ ];
80
+ if (problemTasks.length > 0) {
81
+ statsLines.push(`- 문제 태스크: ${problemTasks.join(', ')}`);
82
+ }
83
+
84
+ // Assemble
85
+ const parts: string[] = [];
86
+ parts.push(`당신은 프로젝트 "${project.name}"의 어드바이저입니다.`);
87
+ parts.push(`사용자가 프로젝트 방향, 우선순위, 빠진 부분, 다음 단계 등을 논의하면 프로젝트 전체 맥락을 바탕으로 간결하게 답합니다.`);
88
+ parts.push(`태스크를 언급할 때는 정확한 제목을 쓰세요. 한국어로 답하세요. 긴 설교는 금지.`);
89
+
90
+ if (project.ai_context) {
91
+ parts.push(`\nProject AI Policy:\n${project.ai_context}`);
92
+ }
93
+
94
+ parts.push('\n=== PROJECT CONTEXT ===');
95
+
96
+ if (brainstorm?.content) {
97
+ parts.push(`\n## 브레인스토밍\n${truncate(brainstorm.content, MAX_BRAINSTORM)}`);
98
+ }
99
+
100
+ parts.push(`\n## 프로젝트 & 태스크\n${subSections.join('\n\n')}`);
101
+ parts.push(`\n## 통계\n${statsLines.join('\n')}`);
102
+
103
+ return parts.join('\n');
104
+ }
105
+
106
+ export function trimConversationHistory(
107
+ messages: { role: string; content: string }[],
108
+ ): { role: string; content: string }[] {
109
+ if (messages.length <= MAX_HISTORY_MESSAGES) return messages;
110
+ return messages.slice(-MAX_HISTORY_MESSAGES);
111
+ }
@@ -0,0 +1,136 @@
1
+ import { spawn } from 'child_process';
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ function readInstalledVersion(pkgRoot: string): string {
6
+ try {
7
+ const raw = readFileSync(join(pkgRoot, 'package.json'), 'utf8');
8
+ const parsed = JSON.parse(raw) as { version?: string };
9
+ return parsed.version ?? '0.0.0';
10
+ } catch {
11
+ return '0.0.0';
12
+ }
13
+ }
14
+
15
+ function cmpVersion(a: string, b: string): number {
16
+ const as = a.split('.').map(n => parseInt(n, 10) || 0);
17
+ const bs = b.split('.').map(n => parseInt(n, 10) || 0);
18
+ for (let i = 0; i < Math.max(as.length, bs.length); i++) {
19
+ const av = as[i] ?? 0;
20
+ const bv = bs[i] ?? 0;
21
+ if (av !== bv) return av - bv;
22
+ }
23
+ return 0;
24
+ }
25
+
26
+ async function fetchLatestVersion(): Promise<string | null> {
27
+ try {
28
+ const controller = new AbortController();
29
+ const timer = setTimeout(() => controller.abort(), 3000);
30
+ const res = await fetch('https://registry.npmjs.org/idea-manager/latest', {
31
+ signal: controller.signal,
32
+ });
33
+ clearTimeout(timer);
34
+ if (!res.ok) return null;
35
+ const data = await res.json() as { version?: string };
36
+ return typeof data.version === 'string' ? data.version : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function runNpmInstall(): Promise<boolean> {
43
+ return new Promise((resolve) => {
44
+ const child = spawn('npm', ['install', '-g', 'idea-manager@latest', '--no-fund', '--no-audit'], {
45
+ stdio: 'inherit',
46
+ shell: process.platform === 'win32',
47
+ env: process.env,
48
+ });
49
+ const timer = setTimeout(() => child.kill('SIGTERM'), 3 * 60 * 1000);
50
+ child.on('exit', (code) => {
51
+ clearTimeout(timer);
52
+ resolve(code === 0);
53
+ });
54
+ child.on('error', () => {
55
+ clearTimeout(timer);
56
+ resolve(false);
57
+ });
58
+ });
59
+ }
60
+
61
+ export interface AutoUpdateResult {
62
+ current: string;
63
+ latest?: string;
64
+ upgraded: boolean;
65
+ skipped: boolean;
66
+ reason?: string;
67
+ }
68
+
69
+ /**
70
+ * Check npm registry for a newer version and install it in place. Designed
71
+ * for `im start` only — `im mcp` / `im watch` should NOT auto-update because
72
+ * they may be long-running integrations where a mid-session restart is
73
+ * disruptive.
74
+ *
75
+ * Opt-outs:
76
+ * - `IM_NO_AUTO_UPDATE=1` env var
77
+ * - `CI` env var (any truthy value)
78
+ * - Network timeout / registry unreachable → silently skipped
79
+ * - Failed `npm install` → logged, falls back to current version
80
+ */
81
+ export async function maybeAutoUpdate(pkgRoot: string): Promise<AutoUpdateResult> {
82
+ const current = readInstalledVersion(pkgRoot);
83
+
84
+ if (process.env.IM_NO_AUTO_UPDATE === '1') {
85
+ return { current, upgraded: false, skipped: true, reason: 'IM_NO_AUTO_UPDATE=1' };
86
+ }
87
+ if (process.env.CI) {
88
+ return { current, upgraded: false, skipped: true, reason: 'CI environment' };
89
+ }
90
+
91
+ const latest = await fetchLatestVersion();
92
+ if (!latest) {
93
+ return { current, upgraded: false, skipped: true, reason: 'registry unreachable' };
94
+ }
95
+ if (cmpVersion(latest, current) <= 0) {
96
+ return { current, latest, upgraded: false, skipped: false };
97
+ }
98
+
99
+ console.log('');
100
+ console.log(` IM — 새 버전 감지: v${current} → v${latest}`);
101
+ console.log(` 업데이트 중... (IM_NO_AUTO_UPDATE=1로 건너뛸 수 있음)`);
102
+ console.log('');
103
+
104
+ const ok = await runNpmInstall();
105
+ if (!ok) {
106
+ console.log('');
107
+ console.log(` ⚠ 업데이트 실패 — 기존 v${current}로 계속 진행합니다.`);
108
+ console.log('');
109
+ return { current, latest, upgraded: false, skipped: false, reason: 'install failed' };
110
+ }
111
+
112
+ console.log('');
113
+ console.log(` ✓ v${latest} 설치 완료. 재시작합니다...`);
114
+ console.log('');
115
+ return { current, latest, upgraded: true, skipped: false };
116
+ }
117
+
118
+ /**
119
+ * Re-exec the `im` CLI with the same arguments so the freshly installed code
120
+ * replaces the old copy loaded in the current Node process. Sets
121
+ * IM_NO_AUTO_UPDATE=1 on the child to prevent an update-respawn loop.
122
+ */
123
+ export function respawnSelf(): void {
124
+ const args = process.argv.slice(2);
125
+ const child = spawn('im', args, {
126
+ stdio: 'inherit',
127
+ shell: process.platform === 'win32',
128
+ env: { ...process.env, IM_NO_AUTO_UPDATE: '1' },
129
+ });
130
+ child.on('exit', (code) => process.exit(code ?? 0));
131
+ child.on('error', (err) => {
132
+ console.error(` ⚠ 재시작 실패: ${err.message}`);
133
+ console.error(` 직접 다시 실행해주세요: im ${args.join(' ')}`);
134
+ process.exit(1);
135
+ });
136
+ }
@@ -0,0 +1,29 @@
1
+ import { getDb } from '../index';
2
+ import { generateId } from '../../utils/id';
3
+ import type { IProjectConversation } from '../../../types';
4
+
5
+ export function getProjectConversations(projectId: string, limit = 50): IProjectConversation[] {
6
+ const db = getDb();
7
+ return db.prepare(
8
+ `SELECT * FROM project_conversations WHERE project_id = ? ORDER BY created_at DESC LIMIT ?`
9
+ ).all(projectId, limit).reverse() as IProjectConversation[];
10
+ }
11
+
12
+ export function addProjectConversation(
13
+ projectId: string,
14
+ role: 'assistant' | 'user' | 'system',
15
+ content: string,
16
+ ): IProjectConversation {
17
+ const db = getDb();
18
+ const id = generateId();
19
+ const now = new Date().toISOString();
20
+ db.prepare(
21
+ 'INSERT INTO project_conversations (id, project_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)'
22
+ ).run(id, projectId, role, content, now);
23
+ return db.prepare('SELECT * FROM project_conversations WHERE id = ?').get(id) as IProjectConversation;
24
+ }
25
+
26
+ export function clearProjectConversations(projectId: string): void {
27
+ const db = getDb();
28
+ db.prepare('DELETE FROM project_conversations WHERE project_id = ?').run(projectId);
29
+ }
@@ -95,6 +95,17 @@ export function initSchema(db: any): void {
95
95
  );
96
96
  `);
97
97
 
98
+ db.exec(`
99
+ CREATE TABLE IF NOT EXISTS project_conversations (
100
+ id TEXT PRIMARY KEY,
101
+ project_id TEXT NOT NULL,
102
+ role TEXT NOT NULL CHECK(role IN ('assistant','user','system')),
103
+ content TEXT NOT NULL,
104
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
105
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
106
+ );
107
+ `);
108
+
98
109
  // tasks archive migration
99
110
  const taskCols = db.prepare("PRAGMA table_info(tasks)").all() as { name: string }[];
100
111
  if (!taskCols.some(c => c.name === 'is_archived')) {
@@ -70,6 +70,14 @@ export interface ITaskConversation {
70
70
  created_at: string;
71
71
  }
72
72
 
73
+ export interface IProjectConversation {
74
+ id: string;
75
+ project_id: string;
76
+ role: 'assistant' | 'user' | 'system';
77
+ content: string;
78
+ created_at: string;
79
+ }
80
+
73
81
  export interface IGitSyncResult {
74
82
  projectId: string;
75
83
  projectName: string;