chronicle-ai 0.0.1
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 +132 -0
- package/package.json +86 -0
- package/packages/README.md +139 -0
- package/packages/cli/README.md +92 -0
- package/packages/cli/bin/chronicle.js +2 -0
- package/packages/cli/dist/commands/add.d.ts +2 -0
- package/packages/cli/dist/commands/add.d.ts.map +1 -0
- package/packages/cli/dist/commands/add.js +82 -0
- package/packages/cli/dist/commands/add.js.map +1 -0
- package/packages/cli/dist/commands/diff.d.ts +11 -0
- package/packages/cli/dist/commands/diff.d.ts.map +1 -0
- package/packages/cli/dist/commands/diff.js +164 -0
- package/packages/cli/dist/commands/diff.js.map +1 -0
- package/packages/cli/dist/commands/init.d.ts +2 -0
- package/packages/cli/dist/commands/init.d.ts.map +1 -0
- package/packages/cli/dist/commands/init.js +54 -0
- package/packages/cli/dist/commands/init.js.map +1 -0
- package/packages/cli/dist/commands/list.d.ts +2 -0
- package/packages/cli/dist/commands/list.d.ts.map +1 -0
- package/packages/cli/dist/commands/list.js +62 -0
- package/packages/cli/dist/commands/list.js.map +1 -0
- package/packages/cli/dist/commands/log.d.ts +4 -0
- package/packages/cli/dist/commands/log.d.ts.map +1 -0
- package/packages/cli/dist/commands/log.js +223 -0
- package/packages/cli/dist/commands/log.js.map +1 -0
- package/packages/cli/dist/commands/pause.d.ts +3 -0
- package/packages/cli/dist/commands/pause.d.ts.map +1 -0
- package/packages/cli/dist/commands/pause.js +49 -0
- package/packages/cli/dist/commands/pause.js.map +1 -0
- package/packages/cli/dist/commands/queue.d.ts +2 -0
- package/packages/cli/dist/commands/queue.d.ts.map +1 -0
- package/packages/cli/dist/commands/queue.js +96 -0
- package/packages/cli/dist/commands/queue.js.map +1 -0
- package/packages/cli/dist/commands/remove.d.ts +2 -0
- package/packages/cli/dist/commands/remove.d.ts.map +1 -0
- package/packages/cli/dist/commands/remove.js +80 -0
- package/packages/cli/dist/commands/remove.js.map +1 -0
- package/packages/cli/dist/commands/reset.d.ts +5 -0
- package/packages/cli/dist/commands/reset.d.ts.map +1 -0
- package/packages/cli/dist/commands/reset.js +90 -0
- package/packages/cli/dist/commands/reset.js.map +1 -0
- package/packages/cli/dist/commands/restart.d.ts +2 -0
- package/packages/cli/dist/commands/restart.d.ts.map +1 -0
- package/packages/cli/dist/commands/restart.js +72 -0
- package/packages/cli/dist/commands/restart.js.map +1 -0
- package/packages/cli/dist/commands/start.d.ts +2 -0
- package/packages/cli/dist/commands/start.d.ts.map +1 -0
- package/packages/cli/dist/commands/start.js +127 -0
- package/packages/cli/dist/commands/start.js.map +1 -0
- package/packages/cli/dist/commands/status.d.ts +2 -0
- package/packages/cli/dist/commands/status.d.ts.map +1 -0
- package/packages/cli/dist/commands/status.js +181 -0
- package/packages/cli/dist/commands/status.js.map +1 -0
- package/packages/cli/dist/commands/stop.d.ts +2 -0
- package/packages/cli/dist/commands/stop.d.ts.map +1 -0
- package/packages/cli/dist/commands/stop.js +64 -0
- package/packages/cli/dist/commands/stop.js.map +1 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +85 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/utils/paths.d.ts +26 -0
- package/packages/cli/dist/utils/paths.d.ts.map +1 -0
- package/packages/cli/dist/utils/paths.js +183 -0
- package/packages/cli/dist/utils/paths.js.map +1 -0
- package/packages/cli/package.json +25 -0
- package/packages/daemon/README.md +83 -0
- package/packages/daemon/dist/ai_test.d.ts +2 -0
- package/packages/daemon/dist/ai_test.d.ts.map +1 -0
- package/packages/daemon/dist/ai_test.js +4 -0
- package/packages/daemon/dist/ai_test.js.map +1 -0
- package/packages/daemon/dist/index.d.ts +2 -0
- package/packages/daemon/dist/index.d.ts.map +1 -0
- package/packages/daemon/dist/index.js +147 -0
- package/packages/daemon/dist/index.js.map +1 -0
- package/packages/daemon/dist/jobs/AIProcessor.d.ts +6 -0
- package/packages/daemon/dist/jobs/AIProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/AIProcessor.js +58 -0
- package/packages/daemon/dist/jobs/AIProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/DocProcessor.d.ts +8 -0
- package/packages/daemon/dist/jobs/DocProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/DocProcessor.js +336 -0
- package/packages/daemon/dist/jobs/DocProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/FileProcessor.d.ts +7 -0
- package/packages/daemon/dist/jobs/FileProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/FileProcessor.js +29 -0
- package/packages/daemon/dist/jobs/FileProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/SystemProcessor.d.ts +13 -0
- package/packages/daemon/dist/jobs/SystemProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/SystemProcessor.js +38 -0
- package/packages/daemon/dist/jobs/SystemProcessor.js.map +1 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.d.ts +21 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.d.ts.map +1 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.js +222 -0
- package/packages/daemon/dist/jobs/UpdateProcessor.js.map +1 -0
- package/packages/daemon/dist/services/AIService.d.ts +90 -0
- package/packages/daemon/dist/services/AIService.d.ts.map +1 -0
- package/packages/daemon/dist/services/AIService.js +451 -0
- package/packages/daemon/dist/services/AIService.js.map +1 -0
- package/packages/daemon/dist/services/ConfigService.d.ts +30 -0
- package/packages/daemon/dist/services/ConfigService.d.ts.map +1 -0
- package/packages/daemon/dist/services/ConfigService.js +69 -0
- package/packages/daemon/dist/services/ConfigService.js.map +1 -0
- package/packages/daemon/dist/services/DatabaseService.d.ts +204 -0
- package/packages/daemon/dist/services/DatabaseService.d.ts.map +1 -0
- package/packages/daemon/dist/services/DatabaseService.js +692 -0
- package/packages/daemon/dist/services/DatabaseService.js.map +1 -0
- package/packages/daemon/dist/services/GitService.d.ts +12 -0
- package/packages/daemon/dist/services/GitService.d.ts.map +1 -0
- package/packages/daemon/dist/services/GitService.js +68 -0
- package/packages/daemon/dist/services/GitService.js.map +1 -0
- package/packages/daemon/dist/services/QueueService.d.ts +16 -0
- package/packages/daemon/dist/services/QueueService.d.ts.map +1 -0
- package/packages/daemon/dist/services/QueueService.js +87 -0
- package/packages/daemon/dist/services/QueueService.js.map +1 -0
- package/packages/daemon/dist/services/TriggerService.d.ts +37 -0
- package/packages/daemon/dist/services/TriggerService.d.ts.map +1 -0
- package/packages/daemon/dist/services/TriggerService.js +150 -0
- package/packages/daemon/dist/services/TriggerService.js.map +1 -0
- package/packages/daemon/dist/services/WatcherService.d.ts +12 -0
- package/packages/daemon/dist/services/WatcherService.d.ts.map +1 -0
- package/packages/daemon/dist/services/WatcherService.js +77 -0
- package/packages/daemon/dist/services/WatcherService.js.map +1 -0
- package/packages/daemon/dist/services/index.d.ts +2 -0
- package/packages/daemon/dist/services/index.d.ts.map +1 -0
- package/packages/daemon/dist/services/index.js +18 -0
- package/packages/daemon/dist/services/index.js.map +1 -0
- package/packages/daemon/dist/test-ignore.js +0 -0
- package/packages/daemon/package.json +28 -0
- package/packages/ui/app/actions.ts +73 -0
- package/packages/ui/app/api/ai-activity/route.ts +14 -0
- package/packages/ui/app/globals.css +98 -0
- package/packages/ui/app/layout.tsx +29 -0
- package/packages/ui/app/page.tsx +109 -0
- package/packages/ui/components/AutoRefresh.tsx +18 -0
- package/packages/ui/components/DocumentationViewer.tsx +276 -0
- package/packages/ui/components/FileList.tsx +69 -0
- package/packages/ui/components/HeaderWithThinking.tsx +102 -0
- package/packages/ui/components/JobQueue.tsx +194 -0
- package/packages/ui/components/JobQueueWrapper.tsx +31 -0
- package/packages/ui/components/MermaidInit.tsx +24 -0
- package/packages/ui/components/ProjectContentArea.tsx +186 -0
- package/packages/ui/components/ProjectList.tsx +136 -0
- package/packages/ui/components/ThinkingDrawer.tsx +377 -0
- package/packages/ui/components/ThinkingPanel.tsx +63 -0
- package/packages/ui/components/TriggerSettings.tsx +185 -0
- package/packages/ui/components/VersionSelector.tsx +132 -0
- package/packages/ui/lib/db.ts +521 -0
- package/packages/ui/next-env.d.ts +5 -0
- package/packages/ui/next.config.js +4 -0
- package/packages/ui/package.json +32 -0
- package/packages/ui/postcss.config.js +6 -0
- package/packages/ui/public/logo.png +0 -0
- package/packages/ui/tailwind.config.ts +32 -0
- package/packages/ui/tsconfig.json +40 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Clock, GitCommit, CheckCircle, AlertCircle, Archive } from 'lucide-react';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
|
|
7
|
+
interface DocVersion {
|
|
8
|
+
id: number;
|
|
9
|
+
version_number: number;
|
|
10
|
+
trigger_type: string;
|
|
11
|
+
commit_hash: string | null;
|
|
12
|
+
status: 'draft' | 'pending_review' | 'active' | 'archived';
|
|
13
|
+
created_at: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
versions: DocVersion[];
|
|
18
|
+
currentVersionId: number | null;
|
|
19
|
+
onVersionChange: (versionId: number) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const statusIcons = {
|
|
23
|
+
draft: Clock,
|
|
24
|
+
pending_review: AlertCircle,
|
|
25
|
+
active: CheckCircle,
|
|
26
|
+
archived: Archive
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const statusColors = {
|
|
30
|
+
draft: 'text-gray-400',
|
|
31
|
+
pending_review: 'text-amber-500',
|
|
32
|
+
active: 'text-green-500',
|
|
33
|
+
archived: 'text-gray-300'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function VersionSelector({ versions, currentVersionId, onVersionChange }: Props) {
|
|
37
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
38
|
+
|
|
39
|
+
const currentVersion = versions.find(v => v.id === currentVersionId) || versions[0];
|
|
40
|
+
|
|
41
|
+
if (!currentVersion) return null;
|
|
42
|
+
|
|
43
|
+
const StatusIcon = statusIcons[currentVersion.status];
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="relative">
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
49
|
+
className={clsx(
|
|
50
|
+
"flex items-center gap-1.5 px-2 py-1 rounded text-[10px] font-mono",
|
|
51
|
+
"bg-gray-100 hover:bg-gray-200 transition-colors"
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<StatusIcon size={12} className={statusColors[currentVersion.status]} />
|
|
55
|
+
<span>v{currentVersion.version_number}</span>
|
|
56
|
+
{currentVersion.commit_hash && (
|
|
57
|
+
<span className="text-gray-400">
|
|
58
|
+
({currentVersion.commit_hash.slice(0, 7)})
|
|
59
|
+
</span>
|
|
60
|
+
)}
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
{isOpen && (
|
|
64
|
+
<>
|
|
65
|
+
<div
|
|
66
|
+
className="fixed inset-0 z-10"
|
|
67
|
+
onClick={() => setIsOpen(false)}
|
|
68
|
+
/>
|
|
69
|
+
<div className="absolute right-0 top-full mt-1 z-20 bg-white border border-gray-200 rounded-lg shadow-lg min-w-[200px] max-h-[300px] overflow-y-auto">
|
|
70
|
+
<div className="p-2 border-b border-gray-100">
|
|
71
|
+
<span className="text-[10px] font-bold uppercase tracking-wider text-gray-400">
|
|
72
|
+
Versions
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="py-1">
|
|
76
|
+
{versions.map((version) => {
|
|
77
|
+
const Icon = statusIcons[version.status];
|
|
78
|
+
const isActive = version.id === currentVersionId;
|
|
79
|
+
const date = new Date(version.created_at * 1000);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<button
|
|
83
|
+
key={version.id}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
onVersionChange(version.id);
|
|
86
|
+
setIsOpen(false);
|
|
87
|
+
}}
|
|
88
|
+
className={clsx(
|
|
89
|
+
"w-full px-3 py-2 flex items-center gap-2 hover:bg-gray-50 text-left",
|
|
90
|
+
isActive && "bg-blue-50"
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
<Icon size={14} className={statusColors[version.status]} />
|
|
94
|
+
<div className="flex-1 min-w-0">
|
|
95
|
+
<div className="flex items-center gap-2">
|
|
96
|
+
<span className="text-xs font-medium">
|
|
97
|
+
v{version.version_number}
|
|
98
|
+
</span>
|
|
99
|
+
{version.status === 'active' && (
|
|
100
|
+
<span className="text-[9px] bg-green-100 text-green-600 px-1 rounded">
|
|
101
|
+
CURRENT
|
|
102
|
+
</span>
|
|
103
|
+
)}
|
|
104
|
+
{version.status === 'pending_review' && (
|
|
105
|
+
<span className="text-[9px] bg-amber-100 text-amber-600 px-1 rounded">
|
|
106
|
+
REVIEW
|
|
107
|
+
</span>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
<div className="text-[10px] text-gray-400 flex items-center gap-1">
|
|
111
|
+
<span>{version.trigger_type}</span>
|
|
112
|
+
<span>•</span>
|
|
113
|
+
<span>{date.toLocaleDateString()}</span>
|
|
114
|
+
{version.commit_hash && (
|
|
115
|
+
<>
|
|
116
|
+
<span>•</span>
|
|
117
|
+
<GitCommit size={10} />
|
|
118
|
+
<span>{version.commit_hash.slice(0, 7)}</span>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</button>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { marked } from 'marked';
|
|
5
|
+
|
|
6
|
+
// Configure marked to render mermaid blocks as divs
|
|
7
|
+
const renderer = new marked.Renderer();
|
|
8
|
+
renderer.code = ({ text, lang, escaped }: any) => {
|
|
9
|
+
const code = text;
|
|
10
|
+
const language = (lang || '').match(/\S*/)[0];
|
|
11
|
+
|
|
12
|
+
if (language === 'mermaid') {
|
|
13
|
+
return `<div class="mermaid">${code}</div>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const className = language ? `class="language-${language}"` : '';
|
|
17
|
+
return `<pre><code ${className}>${code}</code></pre>\n`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
marked.use({ renderer });
|
|
21
|
+
|
|
22
|
+
// Connect to the Daemon's DB
|
|
23
|
+
// Assumes UI and Daemon are in the same mono-repo structure
|
|
24
|
+
const DB_PATH = path.resolve(process.cwd(), '../daemon/.chronicle/db.sqlite');
|
|
25
|
+
|
|
26
|
+
// Lazy database connection - handle build-time scenarios where DB doesn't exist
|
|
27
|
+
let _db: Database.Database | null = null;
|
|
28
|
+
|
|
29
|
+
function getDb(): Database.Database | null {
|
|
30
|
+
if (_db) return _db;
|
|
31
|
+
|
|
32
|
+
// Check if database file exists before attempting to open
|
|
33
|
+
if (!require('fs').existsSync(DB_PATH)) {
|
|
34
|
+
console.warn('Database not found at:', DB_PATH);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
_db = new Database(DB_PATH, {
|
|
40
|
+
readonly: false,
|
|
41
|
+
fileMustExist: true
|
|
42
|
+
});
|
|
43
|
+
return _db;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Failed to connect to database:', error);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// For backward compatibility - functions will check if db is null
|
|
51
|
+
const db = {
|
|
52
|
+
prepare: (sql: string) => {
|
|
53
|
+
const database = getDb();
|
|
54
|
+
if (!database) {
|
|
55
|
+
throw new Error('Database not available');
|
|
56
|
+
}
|
|
57
|
+
return database.prepare(sql);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ===== Stats =====
|
|
62
|
+
|
|
63
|
+
export interface FileRecord {
|
|
64
|
+
id: number;
|
|
65
|
+
path: string;
|
|
66
|
+
hash: string;
|
|
67
|
+
last_modified: number;
|
|
68
|
+
last_indexed: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface JobRecord {
|
|
72
|
+
id: number;
|
|
73
|
+
name: string;
|
|
74
|
+
data: string;
|
|
75
|
+
status: string;
|
|
76
|
+
created_at: number;
|
|
77
|
+
updated_at: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getStats() {
|
|
81
|
+
const fileCount = db.prepare('SELECT COUNT(*) as count FROM files').get() as { count: number };
|
|
82
|
+
const jobStats = db.prepare(`
|
|
83
|
+
SELECT status, COUNT(*) as count
|
|
84
|
+
FROM jobs
|
|
85
|
+
GROUP BY status
|
|
86
|
+
`).all() as { status: string; count: number }[];
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
files: fileCount.count,
|
|
90
|
+
jobs: jobStats
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getRecentFiles(limit = 50, projectPath?: string) {
|
|
95
|
+
let query = 'SELECT * FROM files';
|
|
96
|
+
const args: any[] = [];
|
|
97
|
+
|
|
98
|
+
if (projectPath) {
|
|
99
|
+
query += ' WHERE path LIKE ?';
|
|
100
|
+
args.push(`${projectPath}%`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
query += ' ORDER BY last_indexed DESC LIMIT ?';
|
|
104
|
+
args.push(limit);
|
|
105
|
+
|
|
106
|
+
return db.prepare(query).all(...args) as FileRecord[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getProjects() {
|
|
110
|
+
return db.prepare('SELECT * FROM projects ORDER BY created_at DESC').all() as any[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getProjectConfig(projectPath: string): any | null {
|
|
114
|
+
try {
|
|
115
|
+
const configPath = path.join(projectPath, '.chronicle.json');
|
|
116
|
+
if (!fs.existsSync(configPath)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
120
|
+
return JSON.parse(content);
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getProjectConfigRaw(projectPath: string): string | null {
|
|
127
|
+
try {
|
|
128
|
+
const configPath = path.join(projectPath, '.chronicle.json');
|
|
129
|
+
if (!fs.existsSync(configPath)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return fs.readFileSync(configPath, 'utf-8');
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function saveProjectConfig(projectPath: string, content: string): boolean {
|
|
139
|
+
try {
|
|
140
|
+
const configPath = path.join(projectPath, '.chronicle.json');
|
|
141
|
+
fs.writeFileSync(configPath, content, 'utf-8');
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function enqueueJob(name: string, data: any) {
|
|
149
|
+
return db.prepare(`
|
|
150
|
+
INSERT INTO jobs (name, data, status, created_at, updated_at)
|
|
151
|
+
VALUES (?, ?, 'pending', unixepoch(), unixepoch())
|
|
152
|
+
`).run(name, JSON.stringify(data));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function clearQueue() {
|
|
156
|
+
// Get all doc jobs that will be cancelled to reset their stages
|
|
157
|
+
const docJobs = db.prepare(`
|
|
158
|
+
SELECT data FROM jobs
|
|
159
|
+
WHERE (name = 'doc:initialize' OR name = 'doc:update')
|
|
160
|
+
AND (status = 'pending' OR status = 'processing')
|
|
161
|
+
`).all() as { data: string }[];
|
|
162
|
+
|
|
163
|
+
// Cancel current job
|
|
164
|
+
db.prepare(`
|
|
165
|
+
UPDATE jobs
|
|
166
|
+
SET status = 'failed', error = 'Cancelled by user', updated_at = unixepoch()
|
|
167
|
+
WHERE status = 'processing'
|
|
168
|
+
`).run();
|
|
169
|
+
|
|
170
|
+
// Clear pending jobs
|
|
171
|
+
const result = db.prepare(`DELETE FROM jobs WHERE status = 'pending'`).run();
|
|
172
|
+
|
|
173
|
+
// Reset doc stages for affected projects
|
|
174
|
+
for (const job of docJobs) {
|
|
175
|
+
try {
|
|
176
|
+
const jobData = JSON.parse(job.data);
|
|
177
|
+
if (jobData.projectPath) {
|
|
178
|
+
const project = db.prepare(`
|
|
179
|
+
SELECT id FROM projects WHERE path = ?
|
|
180
|
+
`).get(jobData.projectPath) as { id: number } | undefined;
|
|
181
|
+
if (project) {
|
|
182
|
+
resetDocStage(project.id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// Skip invalid JSON
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function killCurrentJob() {
|
|
194
|
+
// Get the current processing job to reset its stage if it's a doc job
|
|
195
|
+
const currentJob = db.prepare(`
|
|
196
|
+
SELECT name, data FROM jobs WHERE status = 'processing'
|
|
197
|
+
`).get() as { name: string; data: string } | undefined;
|
|
198
|
+
|
|
199
|
+
const result = db.prepare(`
|
|
200
|
+
UPDATE jobs
|
|
201
|
+
SET status = 'failed', error = 'Killed by user', updated_at = unixepoch()
|
|
202
|
+
WHERE status = 'processing'
|
|
203
|
+
`).run();
|
|
204
|
+
|
|
205
|
+
// Reset doc stage if we killed a doc job
|
|
206
|
+
if (currentJob && (currentJob.name === 'doc:initialize' || currentJob.name === 'doc:update')) {
|
|
207
|
+
try {
|
|
208
|
+
const jobData = JSON.parse(currentJob.data);
|
|
209
|
+
if (jobData.projectPath) {
|
|
210
|
+
const project = db.prepare(`
|
|
211
|
+
SELECT id FROM projects WHERE path = ?
|
|
212
|
+
`).get(jobData.projectPath) as { id: number } | undefined;
|
|
213
|
+
if (project) {
|
|
214
|
+
resetDocStage(project.id);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// Skip invalid JSON
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function toggleProjectPaused(path: string, paused: boolean) {
|
|
226
|
+
return db.prepare('UPDATE projects SET is_paused = ? WHERE path = ?').run(paused ? 1 : 0, path);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getJobs(limit = 20) {
|
|
230
|
+
// Get recent jobs
|
|
231
|
+
return db.prepare('SELECT * FROM jobs ORDER BY created_at DESC LIMIT ?').all(limit) as JobRecord[];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Reads documentation files from a project's filesystem.
|
|
236
|
+
* Recursively reads all .md files from docs/ folder and subfolders.
|
|
237
|
+
*/
|
|
238
|
+
export function getProjectDocs(projectPath: string): DocFile[] {
|
|
239
|
+
const docs: DocFile[] = [];
|
|
240
|
+
|
|
241
|
+
// Root level docs
|
|
242
|
+
const rootDocs = ['README.md', 'chronicle.md'];
|
|
243
|
+
for (const docName of rootDocs) {
|
|
244
|
+
const docPath = path.join(projectPath, docName);
|
|
245
|
+
if (fs.existsSync(docPath)) {
|
|
246
|
+
const content = fs.readFileSync(docPath, 'utf-8');
|
|
247
|
+
docs.push({
|
|
248
|
+
name: docName,
|
|
249
|
+
path: docPath,
|
|
250
|
+
content: marked.parse(content) as string,
|
|
251
|
+
type: 'root'
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Recursively read docs folder
|
|
257
|
+
const docsDir = path.join(projectPath, 'docs');
|
|
258
|
+
if (fs.existsSync(docsDir) && fs.statSync(docsDir).isDirectory()) {
|
|
259
|
+
readDocsRecursively(docsDir, docsDir, docs);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return docs;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function readDocsRecursively(baseDir: string, currentDir: string, docs: DocFile[]): void {
|
|
266
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
267
|
+
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
270
|
+
|
|
271
|
+
if (entry.isDirectory()) {
|
|
272
|
+
// Skip hidden folders
|
|
273
|
+
if (!entry.name.startsWith('.')) {
|
|
274
|
+
readDocsRecursively(baseDir, fullPath, docs);
|
|
275
|
+
}
|
|
276
|
+
} else if (entry.name.endsWith('.md') && !entry.name.startsWith('.')) {
|
|
277
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
278
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
279
|
+
docs.push({
|
|
280
|
+
name: entry.name,
|
|
281
|
+
path: fullPath,
|
|
282
|
+
content: marked.parse(content) as string,
|
|
283
|
+
type: 'docs'
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface DocFile {
|
|
290
|
+
name: string;
|
|
291
|
+
path: string;
|
|
292
|
+
content: string;
|
|
293
|
+
type: 'root' | 'docs';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Checks if there's an active doc job (pending or processing) for a project.
|
|
298
|
+
*/
|
|
299
|
+
function hasActiveDocJob(projectPath: string): boolean {
|
|
300
|
+
try {
|
|
301
|
+
const jobs = db.prepare(`
|
|
302
|
+
SELECT name, data, status
|
|
303
|
+
FROM jobs
|
|
304
|
+
WHERE (name = 'doc:initialize' OR name = 'doc:update')
|
|
305
|
+
AND (status = 'pending' OR status = 'processing')
|
|
306
|
+
`).all() as { name: string; data: string; status: string }[];
|
|
307
|
+
|
|
308
|
+
for (const job of jobs) {
|
|
309
|
+
try {
|
|
310
|
+
const jobData = JSON.parse(job.data);
|
|
311
|
+
if (jobData.projectPath === projectPath) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
// Skip invalid JSON
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Gets the documentation stage progress for a project.
|
|
326
|
+
* Only returns stage if there's an active doc job running.
|
|
327
|
+
*/
|
|
328
|
+
export function getDocStage(projectId: number, projectPath?: string): { currentStage: number; totalStages: number } | null {
|
|
329
|
+
try {
|
|
330
|
+
const result = db.prepare(`
|
|
331
|
+
SELECT current_stage, total_stages FROM doc_stages WHERE project_id = ?
|
|
332
|
+
`).get(projectId) as { current_stage: number; total_stages: number } | undefined;
|
|
333
|
+
|
|
334
|
+
if (!result) return null;
|
|
335
|
+
|
|
336
|
+
// Only show stage if there's an active doc job
|
|
337
|
+
if (projectPath && !hasActiveDocJob(projectPath)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
currentStage: result.current_stage,
|
|
343
|
+
totalStages: result.total_stages
|
|
344
|
+
};
|
|
345
|
+
} catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Resets the doc stage for a project (sets current_stage to 0).
|
|
352
|
+
*/
|
|
353
|
+
export function resetDocStage(projectId: number): void {
|
|
354
|
+
try {
|
|
355
|
+
db.prepare(`
|
|
356
|
+
UPDATE doc_stages
|
|
357
|
+
SET current_stage = 0, last_updated = unixepoch()
|
|
358
|
+
WHERE project_id = ?
|
|
359
|
+
`).run(projectId);
|
|
360
|
+
} catch {
|
|
361
|
+
// Ignore errors
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ===== Doc Version Functions =====
|
|
366
|
+
|
|
367
|
+
export interface DocVersion {
|
|
368
|
+
id: number;
|
|
369
|
+
version_number: number;
|
|
370
|
+
trigger_type: string;
|
|
371
|
+
commit_hash: string | null;
|
|
372
|
+
status: 'draft' | 'pending_review' | 'active' | 'archived';
|
|
373
|
+
created_at: number;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function getDocVersions(projectId: number): DocVersion[] {
|
|
377
|
+
try {
|
|
378
|
+
return db.prepare(`
|
|
379
|
+
SELECT id, version_number, trigger_type, commit_hash, status, created_at
|
|
380
|
+
FROM doc_versions
|
|
381
|
+
WHERE project_id = ?
|
|
382
|
+
ORDER BY version_number DESC
|
|
383
|
+
`).all(projectId) as DocVersion[];
|
|
384
|
+
} catch {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function getActiveDocVersion(projectId: number): DocVersion | null {
|
|
390
|
+
try {
|
|
391
|
+
return db.prepare(`
|
|
392
|
+
SELECT id, version_number, trigger_type, commit_hash, status, created_at
|
|
393
|
+
FROM doc_versions
|
|
394
|
+
WHERE project_id = ? AND status = 'active'
|
|
395
|
+
ORDER BY version_number DESC
|
|
396
|
+
LIMIT 1
|
|
397
|
+
`).get(projectId) as DocVersion | null;
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function getThoughtLogsForVersion(versionId: number): { doc_path: string; thinking_summary: string }[] {
|
|
404
|
+
try {
|
|
405
|
+
return db.prepare(`
|
|
406
|
+
SELECT doc_path, thinking_summary
|
|
407
|
+
FROM thought_logs
|
|
408
|
+
WHERE version_id = ?
|
|
409
|
+
ORDER BY created_at ASC
|
|
410
|
+
`).all(versionId) as { doc_path: string; thinking_summary: string }[];
|
|
411
|
+
} catch {
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function getThoughtLogForDoc(versionId: number, docPath: string): string | null {
|
|
417
|
+
try {
|
|
418
|
+
const result = db.prepare(`
|
|
419
|
+
SELECT thinking_summary
|
|
420
|
+
FROM thought_logs
|
|
421
|
+
WHERE version_id = ? AND doc_path = ?
|
|
422
|
+
LIMIT 1
|
|
423
|
+
`).get(versionId, docPath) as { thinking_summary: string } | undefined;
|
|
424
|
+
return result?.thinking_summary || null;
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ===== AI Activity (Phase 1.6) =====
|
|
431
|
+
|
|
432
|
+
export interface AIActivity {
|
|
433
|
+
id: number;
|
|
434
|
+
project_id: number | null;
|
|
435
|
+
version_id: number | null;
|
|
436
|
+
activity_type: 'analyze' | 'generate' | 'plan' | 'embed' | 'action' | 'other';
|
|
437
|
+
prompt_summary: string | null;
|
|
438
|
+
response_preview: string | null;
|
|
439
|
+
thinking_summary: string | null;
|
|
440
|
+
tokens_used: number | null;
|
|
441
|
+
duration_ms: number | null;
|
|
442
|
+
status: 'running' | 'completed' | 'error';
|
|
443
|
+
created_at: number; // milliseconds
|
|
444
|
+
project_path?: string;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Gets recent AI activity for the thinking drawer.
|
|
449
|
+
*/
|
|
450
|
+
export function getRecentAIActivity(limit: number = 20): AIActivity[] {
|
|
451
|
+
try {
|
|
452
|
+
return db.prepare(`
|
|
453
|
+
SELECT
|
|
454
|
+
a.*,
|
|
455
|
+
p.path as project_path
|
|
456
|
+
FROM ai_activity a
|
|
457
|
+
LEFT JOIN projects p ON a.project_id = p.id
|
|
458
|
+
ORDER BY a.created_at DESC
|
|
459
|
+
LIMIT ?
|
|
460
|
+
`).all(limit) as AIActivity[];
|
|
461
|
+
} catch {
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Gets AI activity for a specific version.
|
|
468
|
+
*/
|
|
469
|
+
export function getActivityForVersion(versionId: number): AIActivity[] {
|
|
470
|
+
try {
|
|
471
|
+
return db.prepare(`
|
|
472
|
+
SELECT * FROM ai_activity
|
|
473
|
+
WHERE version_id = ?
|
|
474
|
+
ORDER BY created_at ASC
|
|
475
|
+
`).all(versionId) as AIActivity[];
|
|
476
|
+
} catch {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Kills/cancels a running AI activity by marking it as error.
|
|
483
|
+
*/
|
|
484
|
+
export function killAIActivity(activityId: number): boolean {
|
|
485
|
+
const database = getDb();
|
|
486
|
+
if (!database) return false;
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const result = database.prepare(`
|
|
490
|
+
UPDATE ai_activity
|
|
491
|
+
SET status = 'error',
|
|
492
|
+
response_preview = 'Cancelled by user',
|
|
493
|
+
thinking_summary = 'Activity was cancelled.'
|
|
494
|
+
WHERE id = ? AND status = 'running'
|
|
495
|
+
`).run(activityId);
|
|
496
|
+
return result.changes > 0;
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Kills all running AI activities.
|
|
504
|
+
*/
|
|
505
|
+
export function killAllRunningAIActivities(): number {
|
|
506
|
+
const database = getDb();
|
|
507
|
+
if (!database) return 0;
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
const result = database.prepare(`
|
|
511
|
+
UPDATE ai_activity
|
|
512
|
+
SET status = 'error',
|
|
513
|
+
response_preview = 'Cancelled by user',
|
|
514
|
+
thinking_summary = 'Activity was cancelled.'
|
|
515
|
+
WHERE status = 'running'
|
|
516
|
+
`).run();
|
|
517
|
+
return result.changes;
|
|
518
|
+
} catch {
|
|
519
|
+
return 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chronicle/ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@types/marked": "^5.0.2",
|
|
13
|
+
"better-sqlite3": "^11.10.0",
|
|
14
|
+
"clsx": "^2.1.1",
|
|
15
|
+
"date-fns": "^2.30.0",
|
|
16
|
+
"lucide-react": "^0.562.0",
|
|
17
|
+
"marked": "^17.0.1",
|
|
18
|
+
"next": "14.0.3",
|
|
19
|
+
"react": "^18",
|
|
20
|
+
"react-dom": "^18",
|
|
21
|
+
"tailwind-merge": "^3.4.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20",
|
|
25
|
+
"@types/react": "^18",
|
|
26
|
+
"@types/react-dom": "^18",
|
|
27
|
+
"autoprefixer": "^10.0.1",
|
|
28
|
+
"postcss": "^8",
|
|
29
|
+
"tailwindcss": "^3.3.0",
|
|
30
|
+
"typescript": "^5"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
Binary file
|