contextgit 0.0.1 → 0.0.2
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/.claude/settings.local.json +41 -0
- package/.contextgit/config.json +10 -0
- package/.contextgit/system-prompt.md +4 -0
- package/.github/workflows/contextgit-ci.yml +40 -0
- package/CLAUDE.md +123 -0
- package/CLAUDE.md.next +65 -0
- package/docs/ContextGit_ARCHITECTURE_v3.md +1141 -0
- package/docs/ContextGit_DELTA.md +84 -0
- package/docs/ContextGit_PHASE1_PLAN.md +177 -0
- package/docs/ContextGit_PHASE2_PLAN.md +535 -0
- package/docs/ContextGit_PRD_v4.md +488 -0
- package/docs/decisions.md +370 -0
- package/package.json +23 -8
- package/packages/api/package.json +25 -0
- package/packages/api/src/bootstrap.ts +64 -0
- package/packages/api/src/config.ts +45 -0
- package/packages/api/src/index.ts +17 -0
- package/packages/api/src/middleware/auth.test.ts +83 -0
- package/packages/api/src/middleware/auth.ts +41 -0
- package/packages/api/src/remote-store.test.ts +301 -0
- package/packages/api/src/router.ts +121 -0
- package/packages/api/src/server-config.ts +34 -0
- package/packages/api/src/server.ts +38 -0
- package/packages/api/src/store-router.ts +241 -0
- package/packages/api/tsconfig.json +8 -0
- package/packages/cli/bin/run.js +4 -0
- package/packages/cli/package.json +29 -0
- package/packages/cli/src/bootstrap.ts +68 -0
- package/packages/cli/src/commands/branch.ts +58 -0
- package/packages/cli/src/commands/claim.ts +58 -0
- package/packages/cli/src/commands/commit.ts +79 -0
- package/packages/cli/src/commands/context.ts +46 -0
- package/packages/cli/src/commands/doctor.ts +99 -0
- package/packages/cli/src/commands/init.ts +141 -0
- package/packages/cli/src/commands/keygen.ts +65 -0
- package/packages/cli/src/commands/log.ts +103 -0
- package/packages/cli/src/commands/merge.ts +36 -0
- package/packages/cli/src/commands/pull.ts +145 -0
- package/packages/cli/src/commands/push.ts +158 -0
- package/packages/cli/src/commands/remote-show.ts +87 -0
- package/packages/cli/src/commands/search.ts +54 -0
- package/packages/cli/src/commands/serve.ts +61 -0
- package/packages/cli/src/commands/set-remote.ts +30 -0
- package/packages/cli/src/commands/status.ts +62 -0
- package/packages/cli/src/commands/unclaim.ts +28 -0
- package/packages/cli/src/config.ts +64 -0
- package/packages/cli/src/git-hooks.ts +61 -0
- package/packages/cli/tsconfig.json +9 -0
- package/packages/core/package.json +28 -0
- package/packages/core/src/embeddings.test.ts +58 -0
- package/packages/core/src/embeddings.ts +75 -0
- package/packages/core/src/engine.ts +274 -0
- package/packages/core/src/index.ts +6 -0
- package/packages/core/src/snapshot.ts +82 -0
- package/packages/core/src/summarizer.test.ts +120 -0
- package/packages/core/src/summarizer.ts +113 -0
- package/packages/core/src/threads.ts +29 -0
- package/packages/core/src/types.ts +240 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/mcp/package.json +31 -0
- package/packages/mcp/src/auto-snapshot.ts +83 -0
- package/packages/mcp/src/config.ts +53 -0
- package/packages/mcp/src/git-sync.ts +94 -0
- package/packages/mcp/src/index.ts +19 -0
- package/packages/mcp/src/server.ts +377 -0
- package/packages/mcp/tsconfig.json +9 -0
- package/packages/store/package.json +30 -0
- package/packages/store/src/branch-merge.test.ts +127 -0
- package/packages/store/src/engine-integration.test.ts +93 -0
- package/packages/store/src/index.ts +3 -0
- package/packages/store/src/interface.ts +62 -0
- package/packages/store/src/local/claims.test.ts +190 -0
- package/packages/store/src/local/index.ts +380 -0
- package/packages/store/src/local/local-store.test.ts +164 -0
- package/packages/store/src/local/migrations.ts +99 -0
- package/packages/store/src/local/queries.ts +760 -0
- package/packages/store/src/local/schema.ts +157 -0
- package/packages/store/src/remote/index.ts +300 -0
- package/packages/store/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/build.sh +28 -0
- package/tsconfig.base.json +14 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
// All SQL queries for LocalStore.
|
|
2
|
+
// Dates are stored as INTEGER (Unix ms) and converted to/from Date at this layer.
|
|
3
|
+
// IDs are generated by the caller (nanoid).
|
|
4
|
+
|
|
5
|
+
import type { Database, Statement } from 'better-sqlite3'
|
|
6
|
+
import type {
|
|
7
|
+
Agent,
|
|
8
|
+
AgentInput,
|
|
9
|
+
Branch,
|
|
10
|
+
BranchInput,
|
|
11
|
+
Claim,
|
|
12
|
+
ClaimInput,
|
|
13
|
+
Commit,
|
|
14
|
+
CommitInput,
|
|
15
|
+
Pagination,
|
|
16
|
+
Project,
|
|
17
|
+
ProjectInput,
|
|
18
|
+
SearchResult,
|
|
19
|
+
SessionSnapshot,
|
|
20
|
+
Thread,
|
|
21
|
+
} from '@contextgit/core'
|
|
22
|
+
|
|
23
|
+
// ─── Row types (SQLite column names → snake_case) ───────────────────────────
|
|
24
|
+
|
|
25
|
+
interface ProjectRow {
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
description: string | null
|
|
29
|
+
github_url: string | null
|
|
30
|
+
created_at: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface BranchRow {
|
|
34
|
+
id: string
|
|
35
|
+
project_id: string
|
|
36
|
+
name: string
|
|
37
|
+
git_branch: string
|
|
38
|
+
github_pr_url: string | null
|
|
39
|
+
parent_branch_id: string | null
|
|
40
|
+
head_commit_id: string | null
|
|
41
|
+
status: string
|
|
42
|
+
created_at: number
|
|
43
|
+
merged_at: number | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface CommitRow {
|
|
47
|
+
id: string
|
|
48
|
+
branch_id: string
|
|
49
|
+
parent_id: string | null
|
|
50
|
+
merge_source_branch_id: string | null
|
|
51
|
+
agent_id: string
|
|
52
|
+
agent_role: string
|
|
53
|
+
tool: string
|
|
54
|
+
workflow_type: string
|
|
55
|
+
loop_iteration: number | null
|
|
56
|
+
ci_run_id: string | null
|
|
57
|
+
pipeline_name: string | null
|
|
58
|
+
message: string
|
|
59
|
+
content: string
|
|
60
|
+
summary: string
|
|
61
|
+
commit_type: string
|
|
62
|
+
git_commit_sha: string | null
|
|
63
|
+
created_at: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ThreadRow {
|
|
67
|
+
id: string
|
|
68
|
+
project_id: string
|
|
69
|
+
branch_id: string
|
|
70
|
+
description: string
|
|
71
|
+
status: string
|
|
72
|
+
workflow_type: string | null
|
|
73
|
+
opened_in_commit: string
|
|
74
|
+
closed_in_commit: string | null
|
|
75
|
+
closed_note: string | null
|
|
76
|
+
created_at: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface AgentRow {
|
|
80
|
+
id: string
|
|
81
|
+
project_id: string
|
|
82
|
+
role: string
|
|
83
|
+
tool: string
|
|
84
|
+
workflow_type: string
|
|
85
|
+
display_name: string | null
|
|
86
|
+
total_commits: number
|
|
87
|
+
last_seen: number
|
|
88
|
+
created_at: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ClaimRow {
|
|
92
|
+
id: string
|
|
93
|
+
project_id: string
|
|
94
|
+
branch_id: string
|
|
95
|
+
task: string
|
|
96
|
+
agent_id: string
|
|
97
|
+
role: string
|
|
98
|
+
claimed_at: number
|
|
99
|
+
status: string
|
|
100
|
+
ttl: number
|
|
101
|
+
released_at: number | null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Row → domain type converters ───────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
function toProject(row: ProjectRow): Project {
|
|
107
|
+
return {
|
|
108
|
+
id: row.id,
|
|
109
|
+
name: row.name,
|
|
110
|
+
description: row.description ?? undefined,
|
|
111
|
+
githubUrl: row.github_url ?? undefined,
|
|
112
|
+
createdAt: new Date(row.created_at),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toBranch(row: BranchRow): Branch {
|
|
117
|
+
return {
|
|
118
|
+
id: row.id,
|
|
119
|
+
projectId: row.project_id,
|
|
120
|
+
name: row.name,
|
|
121
|
+
gitBranch: row.git_branch,
|
|
122
|
+
githubPrUrl: row.github_pr_url ?? undefined,
|
|
123
|
+
parentBranchId: row.parent_branch_id ?? undefined,
|
|
124
|
+
headCommitId: row.head_commit_id ?? undefined,
|
|
125
|
+
status: row.status as Branch['status'],
|
|
126
|
+
createdAt: new Date(row.created_at),
|
|
127
|
+
mergedAt: row.merged_at ? new Date(row.merged_at) : undefined,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toCommit(row: CommitRow): Commit {
|
|
132
|
+
return {
|
|
133
|
+
id: row.id,
|
|
134
|
+
branchId: row.branch_id,
|
|
135
|
+
parentId: row.parent_id ?? undefined,
|
|
136
|
+
mergeSourceBranchId: row.merge_source_branch_id ?? undefined,
|
|
137
|
+
agentId: row.agent_id,
|
|
138
|
+
agentRole: row.agent_role as Commit['agentRole'],
|
|
139
|
+
tool: row.tool,
|
|
140
|
+
workflowType: row.workflow_type as Commit['workflowType'],
|
|
141
|
+
loopIteration: row.loop_iteration ?? undefined,
|
|
142
|
+
ciRunId: row.ci_run_id ?? undefined,
|
|
143
|
+
pipelineName: row.pipeline_name ?? undefined,
|
|
144
|
+
message: row.message,
|
|
145
|
+
content: row.content,
|
|
146
|
+
summary: row.summary,
|
|
147
|
+
commitType: row.commit_type as Commit['commitType'],
|
|
148
|
+
gitCommitSha: row.git_commit_sha ?? undefined,
|
|
149
|
+
createdAt: new Date(row.created_at),
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function toThread(row: ThreadRow): Thread {
|
|
154
|
+
return {
|
|
155
|
+
id: row.id,
|
|
156
|
+
projectId: row.project_id,
|
|
157
|
+
branchId: row.branch_id,
|
|
158
|
+
description: row.description,
|
|
159
|
+
status: row.status as Thread['status'],
|
|
160
|
+
workflowType: row.workflow_type as Thread['workflowType'] ?? undefined,
|
|
161
|
+
openedInCommit: row.opened_in_commit,
|
|
162
|
+
closedInCommit: row.closed_in_commit ?? undefined,
|
|
163
|
+
closedNote: row.closed_note ?? undefined,
|
|
164
|
+
createdAt: new Date(row.created_at),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function toClaim(row: ClaimRow): Claim {
|
|
169
|
+
return {
|
|
170
|
+
id: row.id,
|
|
171
|
+
projectId: row.project_id,
|
|
172
|
+
branchId: row.branch_id,
|
|
173
|
+
task: row.task,
|
|
174
|
+
agentId: row.agent_id,
|
|
175
|
+
role: row.role as Claim['role'],
|
|
176
|
+
claimedAt: new Date(row.claimed_at),
|
|
177
|
+
status: row.status as Claim['status'],
|
|
178
|
+
ttl: row.ttl,
|
|
179
|
+
releasedAt: row.released_at ? new Date(row.released_at) : undefined,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function toAgent(row: AgentRow): Agent {
|
|
184
|
+
return {
|
|
185
|
+
id: row.id,
|
|
186
|
+
projectId: row.project_id,
|
|
187
|
+
role: row.role as Agent['role'],
|
|
188
|
+
tool: row.tool,
|
|
189
|
+
workflowType: row.workflow_type as Agent['workflowType'],
|
|
190
|
+
displayName: row.display_name ?? undefined,
|
|
191
|
+
totalCommits: row.total_commits,
|
|
192
|
+
lastSeen: new Date(row.last_seen),
|
|
193
|
+
createdAt: new Date(row.created_at),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Queries class ───────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
export class Queries {
|
|
200
|
+
private readonly db: Database
|
|
201
|
+
|
|
202
|
+
// Prepared statements (lazy init pattern via getters is avoided for simplicity;
|
|
203
|
+
// statements are prepared once in constructor)
|
|
204
|
+
private readonly stmts: {
|
|
205
|
+
insertProject: Statement
|
|
206
|
+
selectProject: Statement<[string]>
|
|
207
|
+
|
|
208
|
+
insertBranch: Statement
|
|
209
|
+
selectBranch: Statement<[string]>
|
|
210
|
+
selectBranchByGit: Statement<[string, string]>
|
|
211
|
+
selectBranches: Statement<[string]>
|
|
212
|
+
updateBranchHead: Statement<[string, string]>
|
|
213
|
+
updateBranchMerged: Statement<[number, string]>
|
|
214
|
+
|
|
215
|
+
insertCommit: Statement
|
|
216
|
+
selectCommit: Statement<[string]>
|
|
217
|
+
selectCommits: Statement<[string, number, number]>
|
|
218
|
+
selectCommitsByRole: Statement<[string, string, number]>
|
|
219
|
+
selectLastCommit: Statement<[string]>
|
|
220
|
+
|
|
221
|
+
insertThread: Statement
|
|
222
|
+
syncThread: Statement
|
|
223
|
+
closeThread: Statement<[string, string, string]>
|
|
224
|
+
selectOpenThreads: Statement<[string]>
|
|
225
|
+
selectOpenThreadsByBranch: Statement<[string]>
|
|
226
|
+
reassignThreads: Statement<[string, string]>
|
|
227
|
+
|
|
228
|
+
insertAgent: Statement
|
|
229
|
+
upsertAgent: Statement
|
|
230
|
+
selectAgent: Statement<[string]>
|
|
231
|
+
selectAgents: Statement<[string]>
|
|
232
|
+
incrementAgentCommits: Statement<[number, string]>
|
|
233
|
+
|
|
234
|
+
insertClaim: Statement
|
|
235
|
+
selectClaim: Statement<[string]>
|
|
236
|
+
listActiveClaims: Statement<[string, number]>
|
|
237
|
+
updateClaimStatus: Statement
|
|
238
|
+
releaseClaimsByAgent: Statement<[number, string, string]>
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
constructor(db: Database) {
|
|
242
|
+
this.db = db
|
|
243
|
+
|
|
244
|
+
this.stmts = {
|
|
245
|
+
// Projects
|
|
246
|
+
insertProject: db.prepare(`
|
|
247
|
+
INSERT INTO projects (id, name, description, github_url, created_at)
|
|
248
|
+
VALUES (@id, @name, @description, @github_url, @created_at)
|
|
249
|
+
`),
|
|
250
|
+
selectProject: db.prepare(`SELECT * FROM projects WHERE id = ?`),
|
|
251
|
+
|
|
252
|
+
// Branches
|
|
253
|
+
insertBranch: db.prepare(`
|
|
254
|
+
INSERT INTO branches
|
|
255
|
+
(id, project_id, name, git_branch, github_pr_url, parent_branch_id,
|
|
256
|
+
head_commit_id, status, created_at)
|
|
257
|
+
VALUES
|
|
258
|
+
(@id, @project_id, @name, @git_branch, @github_pr_url, @parent_branch_id,
|
|
259
|
+
@head_commit_id, @status, @created_at)
|
|
260
|
+
`),
|
|
261
|
+
selectBranch: db.prepare(`SELECT * FROM branches WHERE id = ?`),
|
|
262
|
+
selectBranchByGit: db.prepare(
|
|
263
|
+
`SELECT * FROM branches WHERE project_id = ? AND git_branch = ? LIMIT 1`
|
|
264
|
+
),
|
|
265
|
+
selectBranches: db.prepare(`SELECT * FROM branches WHERE project_id = ? ORDER BY created_at DESC`),
|
|
266
|
+
updateBranchHead: db.prepare(`UPDATE branches SET head_commit_id = ? WHERE id = ?`),
|
|
267
|
+
updateBranchMerged: db.prepare(
|
|
268
|
+
`UPDATE branches SET status = 'merged', merged_at = ? WHERE id = ?`
|
|
269
|
+
),
|
|
270
|
+
|
|
271
|
+
// Commits
|
|
272
|
+
insertCommit: db.prepare(`
|
|
273
|
+
INSERT INTO commits
|
|
274
|
+
(id, branch_id, parent_id, merge_source_branch_id, agent_id, agent_role,
|
|
275
|
+
tool, workflow_type, loop_iteration, ci_run_id, pipeline_name,
|
|
276
|
+
message, content, summary, commit_type, git_commit_sha, created_at)
|
|
277
|
+
VALUES
|
|
278
|
+
(@id, @branch_id, @parent_id, @merge_source_branch_id, @agent_id, @agent_role,
|
|
279
|
+
@tool, @workflow_type, @loop_iteration, @ci_run_id, @pipeline_name,
|
|
280
|
+
@message, @content, @summary, @commit_type, @git_commit_sha, @created_at)
|
|
281
|
+
`),
|
|
282
|
+
selectCommit: db.prepare(`SELECT * FROM commits WHERE id = ?`),
|
|
283
|
+
selectCommits: db.prepare(
|
|
284
|
+
`SELECT * FROM commits WHERE branch_id = ? ORDER BY created_at DESC, rowid DESC LIMIT ? OFFSET ?`
|
|
285
|
+
),
|
|
286
|
+
selectCommitsByRole: db.prepare(
|
|
287
|
+
`SELECT * FROM commits WHERE branch_id = ? AND agent_role = ? ORDER BY created_at DESC, rowid DESC LIMIT ?`
|
|
288
|
+
),
|
|
289
|
+
selectLastCommit: db.prepare(
|
|
290
|
+
`SELECT * FROM commits WHERE branch_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1`
|
|
291
|
+
),
|
|
292
|
+
|
|
293
|
+
// Threads
|
|
294
|
+
insertThread: db.prepare(`
|
|
295
|
+
INSERT INTO threads
|
|
296
|
+
(id, project_id, branch_id, description, status, workflow_type,
|
|
297
|
+
opened_in_commit, created_at)
|
|
298
|
+
VALUES
|
|
299
|
+
(@id, @project_id, @branch_id, @description, 'open', @workflow_type,
|
|
300
|
+
@opened_in_commit, @created_at)
|
|
301
|
+
`),
|
|
302
|
+
syncThread: db.prepare(`
|
|
303
|
+
INSERT OR IGNORE INTO threads
|
|
304
|
+
(id, project_id, branch_id, description, status, workflow_type,
|
|
305
|
+
opened_in_commit, created_at)
|
|
306
|
+
VALUES
|
|
307
|
+
(@id, @project_id, @branch_id, @description, @status, @workflow_type,
|
|
308
|
+
@opened_in_commit, @created_at)
|
|
309
|
+
`),
|
|
310
|
+
closeThread: db.prepare(`
|
|
311
|
+
UPDATE threads
|
|
312
|
+
SET status = 'closed', closed_in_commit = ?, closed_note = ?
|
|
313
|
+
WHERE id = ?
|
|
314
|
+
`),
|
|
315
|
+
selectOpenThreads: db.prepare(
|
|
316
|
+
`SELECT * FROM threads WHERE project_id = ? AND status = 'open' ORDER BY created_at ASC`
|
|
317
|
+
),
|
|
318
|
+
selectOpenThreadsByBranch: db.prepare(
|
|
319
|
+
`SELECT * FROM threads WHERE branch_id = ? AND status = 'open' ORDER BY created_at ASC`
|
|
320
|
+
),
|
|
321
|
+
reassignThreads: db.prepare(
|
|
322
|
+
`UPDATE threads SET branch_id = ? WHERE branch_id = ? AND status = 'open'`
|
|
323
|
+
),
|
|
324
|
+
|
|
325
|
+
// Agents
|
|
326
|
+
insertAgent: db.prepare(`
|
|
327
|
+
INSERT INTO agents
|
|
328
|
+
(id, project_id, role, tool, workflow_type, display_name, total_commits,
|
|
329
|
+
last_seen, created_at)
|
|
330
|
+
VALUES
|
|
331
|
+
(@id, @project_id, @role, @tool, @workflow_type, @display_name, 0,
|
|
332
|
+
@last_seen, @created_at)
|
|
333
|
+
`),
|
|
334
|
+
upsertAgent: db.prepare(`
|
|
335
|
+
INSERT INTO agents
|
|
336
|
+
(id, project_id, role, tool, workflow_type, display_name, total_commits,
|
|
337
|
+
last_seen, created_at)
|
|
338
|
+
VALUES
|
|
339
|
+
(@id, @project_id, @role, @tool, @workflow_type, @display_name, 0,
|
|
340
|
+
@last_seen, @created_at)
|
|
341
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
342
|
+
role = excluded.role,
|
|
343
|
+
tool = excluded.tool,
|
|
344
|
+
workflow_type = excluded.workflow_type,
|
|
345
|
+
display_name = excluded.display_name,
|
|
346
|
+
last_seen = excluded.last_seen
|
|
347
|
+
`),
|
|
348
|
+
selectAgent: db.prepare(`SELECT * FROM agents WHERE id = ?`),
|
|
349
|
+
selectAgents: db.prepare(
|
|
350
|
+
`SELECT * FROM agents WHERE project_id = ? ORDER BY last_seen DESC`
|
|
351
|
+
),
|
|
352
|
+
incrementAgentCommits: db.prepare(`
|
|
353
|
+
UPDATE agents SET total_commits = total_commits + 1, last_seen = ? WHERE id = ?
|
|
354
|
+
`),
|
|
355
|
+
|
|
356
|
+
// Claims
|
|
357
|
+
insertClaim: db.prepare(`
|
|
358
|
+
INSERT INTO claims
|
|
359
|
+
(id, project_id, branch_id, task, agent_id, role, claimed_at, status, ttl, released_at)
|
|
360
|
+
VALUES
|
|
361
|
+
(@id, @project_id, @branch_id, @task, @agent_id, @role, @claimed_at, @status, @ttl, NULL)
|
|
362
|
+
`),
|
|
363
|
+
selectClaim: db.prepare(`SELECT * FROM claims WHERE id = ?`),
|
|
364
|
+
listActiveClaims: db.prepare(`
|
|
365
|
+
SELECT * FROM claims
|
|
366
|
+
WHERE project_id = ?
|
|
367
|
+
AND status != 'released'
|
|
368
|
+
AND (claimed_at + ttl) > ?
|
|
369
|
+
ORDER BY claimed_at ASC
|
|
370
|
+
`),
|
|
371
|
+
updateClaimStatus: db.prepare(`
|
|
372
|
+
UPDATE claims SET status = @status, released_at = @released_at WHERE id = @id
|
|
373
|
+
`),
|
|
374
|
+
releaseClaimsByAgent: db.prepare(`
|
|
375
|
+
UPDATE claims
|
|
376
|
+
SET status = 'released', released_at = ?
|
|
377
|
+
WHERE agent_id = ? AND branch_id = ? AND status != 'released'
|
|
378
|
+
`),
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Projects ────────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
insertProject(input: ProjectInput & { id: string }): Project {
|
|
385
|
+
const now = Date.now()
|
|
386
|
+
this.stmts.insertProject.run({
|
|
387
|
+
id: input.id,
|
|
388
|
+
name: input.name,
|
|
389
|
+
description: input.description ?? null,
|
|
390
|
+
github_url: input.githubUrl ?? null,
|
|
391
|
+
created_at: now,
|
|
392
|
+
})
|
|
393
|
+
return toProject({
|
|
394
|
+
id: input.id,
|
|
395
|
+
name: input.name,
|
|
396
|
+
description: input.description ?? null,
|
|
397
|
+
github_url: input.githubUrl ?? null,
|
|
398
|
+
created_at: now,
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
getProject(id: string): Project | null {
|
|
403
|
+
const row = this.stmts.selectProject.get(id) as ProjectRow | undefined
|
|
404
|
+
return row ? toProject(row) : null
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── Branches ────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
insertBranch(input: BranchInput & { id: string }): Branch {
|
|
410
|
+
const now = Date.now()
|
|
411
|
+
this.stmts.insertBranch.run({
|
|
412
|
+
id: input.id,
|
|
413
|
+
project_id: input.projectId,
|
|
414
|
+
name: input.name,
|
|
415
|
+
git_branch: input.gitBranch,
|
|
416
|
+
github_pr_url: input.githubPrUrl ?? null,
|
|
417
|
+
parent_branch_id: input.parentBranchId ?? null,
|
|
418
|
+
head_commit_id: null,
|
|
419
|
+
status: 'active',
|
|
420
|
+
created_at: now,
|
|
421
|
+
})
|
|
422
|
+
return toBranch({
|
|
423
|
+
id: input.id,
|
|
424
|
+
project_id: input.projectId,
|
|
425
|
+
name: input.name,
|
|
426
|
+
git_branch: input.gitBranch,
|
|
427
|
+
github_pr_url: input.githubPrUrl ?? null,
|
|
428
|
+
parent_branch_id: input.parentBranchId ?? null,
|
|
429
|
+
head_commit_id: null,
|
|
430
|
+
status: 'active',
|
|
431
|
+
created_at: now,
|
|
432
|
+
merged_at: null,
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getBranch(id: string): Branch | null {
|
|
437
|
+
const row = this.stmts.selectBranch.get(id) as BranchRow | undefined
|
|
438
|
+
return row ? toBranch(row) : null
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
getBranchByGitName(projectId: string, gitBranch: string): Branch | null {
|
|
442
|
+
const row = this.stmts.selectBranchByGit.get(projectId, gitBranch) as BranchRow | undefined
|
|
443
|
+
return row ? toBranch(row) : null
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
listBranches(projectId: string): Branch[] {
|
|
447
|
+
const rows = this.stmts.selectBranches.all(projectId) as BranchRow[]
|
|
448
|
+
return rows.map(toBranch)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
updateBranchHead(branchId: string, commitId: string): void {
|
|
452
|
+
this.stmts.updateBranchHead.run(commitId, branchId)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
markBranchMerged(branchId: string): void {
|
|
456
|
+
this.stmts.updateBranchMerged.run(Date.now(), branchId)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ─── Commits ─────────────────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
insertCommit(
|
|
462
|
+
id: string,
|
|
463
|
+
input: CommitInput,
|
|
464
|
+
parentId: string | null,
|
|
465
|
+
mergeSourceBranchId: string | null = null,
|
|
466
|
+
): Commit {
|
|
467
|
+
const now = Date.now()
|
|
468
|
+
this.stmts.insertCommit.run({
|
|
469
|
+
id,
|
|
470
|
+
branch_id: input.branchId,
|
|
471
|
+
parent_id: parentId,
|
|
472
|
+
merge_source_branch_id: mergeSourceBranchId,
|
|
473
|
+
agent_id: input.agentId,
|
|
474
|
+
agent_role: input.agentRole,
|
|
475
|
+
tool: input.tool,
|
|
476
|
+
workflow_type: input.workflowType,
|
|
477
|
+
loop_iteration: input.loopIteration ?? null,
|
|
478
|
+
ci_run_id: input.ciRunId ?? null,
|
|
479
|
+
pipeline_name: input.pipelineName ?? null,
|
|
480
|
+
message: input.message,
|
|
481
|
+
content: input.content,
|
|
482
|
+
summary: input.summary,
|
|
483
|
+
commit_type: input.commitType,
|
|
484
|
+
git_commit_sha: input.gitCommitSha ?? null,
|
|
485
|
+
created_at: now,
|
|
486
|
+
})
|
|
487
|
+
return {
|
|
488
|
+
id,
|
|
489
|
+
branchId: input.branchId,
|
|
490
|
+
parentId: parentId ?? undefined,
|
|
491
|
+
mergeSourceBranchId: mergeSourceBranchId ?? undefined,
|
|
492
|
+
agentId: input.agentId,
|
|
493
|
+
agentRole: input.agentRole,
|
|
494
|
+
tool: input.tool,
|
|
495
|
+
workflowType: input.workflowType,
|
|
496
|
+
loopIteration: input.loopIteration,
|
|
497
|
+
ciRunId: input.ciRunId,
|
|
498
|
+
pipelineName: input.pipelineName,
|
|
499
|
+
message: input.message,
|
|
500
|
+
content: input.content,
|
|
501
|
+
summary: input.summary,
|
|
502
|
+
commitType: input.commitType,
|
|
503
|
+
gitCommitSha: input.gitCommitSha,
|
|
504
|
+
createdAt: new Date(now),
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
getCommit(id: string): Commit | null {
|
|
509
|
+
const row = this.stmts.selectCommit.get(id) as CommitRow | undefined
|
|
510
|
+
return row ? toCommit(row) : null
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
listCommits(branchId: string, pagination: Pagination): Commit[] {
|
|
514
|
+
const rows = this.stmts.selectCommits.all(
|
|
515
|
+
branchId,
|
|
516
|
+
pagination.limit,
|
|
517
|
+
pagination.offset,
|
|
518
|
+
) as CommitRow[]
|
|
519
|
+
return rows.map(toCommit)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
getLastCommit(branchId: string): Commit | null {
|
|
523
|
+
const row = this.stmts.selectLastCommit.get(branchId) as CommitRow | undefined
|
|
524
|
+
return row ? toCommit(row) : null
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ─── Threads ──────────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
insertThread(
|
|
530
|
+
id: string,
|
|
531
|
+
description: string,
|
|
532
|
+
projectId: string,
|
|
533
|
+
branchId: string,
|
|
534
|
+
openedInCommit: string,
|
|
535
|
+
workflowType: string | null,
|
|
536
|
+
): Thread {
|
|
537
|
+
const now = Date.now()
|
|
538
|
+
this.stmts.insertThread.run({
|
|
539
|
+
id,
|
|
540
|
+
project_id: projectId,
|
|
541
|
+
branch_id: branchId,
|
|
542
|
+
description,
|
|
543
|
+
workflow_type: workflowType,
|
|
544
|
+
opened_in_commit: openedInCommit,
|
|
545
|
+
created_at: now,
|
|
546
|
+
})
|
|
547
|
+
return {
|
|
548
|
+
id,
|
|
549
|
+
projectId,
|
|
550
|
+
branchId,
|
|
551
|
+
description,
|
|
552
|
+
status: 'open',
|
|
553
|
+
workflowType: workflowType as Thread['workflowType'] ?? undefined,
|
|
554
|
+
openedInCommit,
|
|
555
|
+
createdAt: new Date(now),
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
syncThread(thread: Thread): Thread {
|
|
560
|
+
this.stmts.syncThread.run({
|
|
561
|
+
id: thread.id,
|
|
562
|
+
project_id: thread.projectId,
|
|
563
|
+
branch_id: thread.branchId,
|
|
564
|
+
description: thread.description,
|
|
565
|
+
status: thread.status,
|
|
566
|
+
workflow_type: thread.workflowType ?? null,
|
|
567
|
+
opened_in_commit: thread.openedInCommit,
|
|
568
|
+
created_at: thread.createdAt.getTime(),
|
|
569
|
+
})
|
|
570
|
+
return thread
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
closeThread(threadId: string, closedInCommit: string, note: string): void {
|
|
574
|
+
this.stmts.closeThread.run(closedInCommit, note, threadId)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
listOpenThreads(projectId: string): Thread[] {
|
|
578
|
+
const rows = this.stmts.selectOpenThreads.all(projectId) as ThreadRow[]
|
|
579
|
+
return rows.map(toThread)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
listOpenThreadsByBranch(branchId: string): Thread[] {
|
|
583
|
+
const rows = this.stmts.selectOpenThreadsByBranch.all(branchId) as ThreadRow[]
|
|
584
|
+
return rows.map(toThread)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Move open threads from source branch to target (called during merge). */
|
|
588
|
+
reassignOpenThreads(fromBranchId: string, toBranchId: string): void {
|
|
589
|
+
this.stmts.reassignThreads.run(toBranchId, fromBranchId)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ─── Agents ───────────────────────────────────────────────────────────────
|
|
593
|
+
|
|
594
|
+
upsertAgent(input: AgentInput): Agent {
|
|
595
|
+
const now = Date.now()
|
|
596
|
+
const existing = this.stmts.selectAgent.get(input.id) as AgentRow | undefined
|
|
597
|
+
this.stmts.upsertAgent.run({
|
|
598
|
+
id: input.id,
|
|
599
|
+
project_id: input.projectId,
|
|
600
|
+
role: input.role,
|
|
601
|
+
tool: input.tool,
|
|
602
|
+
workflow_type: input.workflowType,
|
|
603
|
+
display_name: input.displayName ?? null,
|
|
604
|
+
last_seen: now,
|
|
605
|
+
created_at: existing ? existing.created_at : now,
|
|
606
|
+
})
|
|
607
|
+
const row = this.stmts.selectAgent.get(input.id) as AgentRow
|
|
608
|
+
return toAgent(row)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
incrementAgentCommits(agentId: string): void {
|
|
612
|
+
this.stmts.incrementAgentCommits.run(Date.now(), agentId)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
listAgents(projectId: string): Agent[] {
|
|
616
|
+
const rows = this.stmts.selectAgents.all(projectId) as AgentRow[]
|
|
617
|
+
return rows.map(toAgent)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ─── Claims ───────────────────────────────────────────────────────────────
|
|
621
|
+
|
|
622
|
+
insertClaim(id: string, projectId: string, branchId: string, input: ClaimInput): Claim {
|
|
623
|
+
const now = Date.now()
|
|
624
|
+
const ttl = input.ttl ?? 7_200_000
|
|
625
|
+
const status = input.status ?? 'proposed'
|
|
626
|
+
this.stmts.insertClaim.run({
|
|
627
|
+
id,
|
|
628
|
+
project_id: projectId,
|
|
629
|
+
branch_id: branchId,
|
|
630
|
+
task: input.task,
|
|
631
|
+
agent_id: input.agentId,
|
|
632
|
+
role: input.role,
|
|
633
|
+
claimed_at: now,
|
|
634
|
+
status,
|
|
635
|
+
ttl,
|
|
636
|
+
})
|
|
637
|
+
return toClaim(this.stmts.selectClaim.get(id) as ClaimRow)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
listActiveClaims(projectId: string): Claim[] {
|
|
641
|
+
const rows = this.stmts.listActiveClaims.all(projectId, Date.now()) as ClaimRow[]
|
|
642
|
+
return rows.map(toClaim)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
updateClaimStatus(id: string, status: string, releasedAt: number | null = null): void {
|
|
646
|
+
this.stmts.updateClaimStatus.run({ id, status, released_at: releasedAt })
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
releaseClaimsByAgent(agentId: string, branchId: string): void {
|
|
650
|
+
this.stmts.releaseClaimsByAgent.run(Date.now(), agentId, branchId)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ─── Session snapshot helpers ─────────────────────────────────────────────
|
|
654
|
+
|
|
655
|
+
getSessionSnapshot(projectId: string, branchId: string, options?: { agentRole?: string }): SessionSnapshot {
|
|
656
|
+
// Project summary: head commit summary of the 'main' branch
|
|
657
|
+
const mainBranch = this.getBranchByGitName(projectId, 'main')
|
|
658
|
+
?? this.getBranchByGitName(projectId, 'master')
|
|
659
|
+
|
|
660
|
+
const projectSummary = mainBranch?.headCommitId
|
|
661
|
+
? (this.getCommit(mainBranch.headCommitId)?.summary ?? '')
|
|
662
|
+
: ''
|
|
663
|
+
|
|
664
|
+
// Branch summary: head commit summary of current branch
|
|
665
|
+
const branch = this.getBranch(branchId)
|
|
666
|
+
const branchSummary = branch?.headCommitId
|
|
667
|
+
? (this.getCommit(branch.headCommitId)?.summary ?? '')
|
|
668
|
+
: ''
|
|
669
|
+
|
|
670
|
+
// Last 3 commits on current branch (optionally filtered by agent role)
|
|
671
|
+
const recentCommits = options?.agentRole
|
|
672
|
+
? (this.stmts.selectCommitsByRole.all(branchId, options.agentRole, 3) as CommitRow[]).map(toCommit)
|
|
673
|
+
: this.listCommits(branchId, { limit: 3, offset: 0 })
|
|
674
|
+
|
|
675
|
+
// All open threads for the project
|
|
676
|
+
const openThreads = this.listOpenThreads(projectId)
|
|
677
|
+
|
|
678
|
+
const activeClaims = this.listActiveClaims(projectId)
|
|
679
|
+
|
|
680
|
+
return {
|
|
681
|
+
projectSummary,
|
|
682
|
+
branchName: branch?.name ?? '',
|
|
683
|
+
branchSummary,
|
|
684
|
+
recentCommits,
|
|
685
|
+
openThreads,
|
|
686
|
+
activeClaims,
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ─── Semantic search (sqlite-vec) ─────────────────────────────────────────
|
|
691
|
+
|
|
692
|
+
semanticSearch(
|
|
693
|
+
db: Database,
|
|
694
|
+
embeddingVector: Float32Array,
|
|
695
|
+
projectId: string,
|
|
696
|
+
limit: number,
|
|
697
|
+
): SearchResult[] {
|
|
698
|
+
try {
|
|
699
|
+
// KNN query joining commit_embeddings with commits filtered by project
|
|
700
|
+
const stmt = db.prepare<[string, Float32Array, number]>(`
|
|
701
|
+
SELECT c.*, ce.distance
|
|
702
|
+
FROM commit_embeddings ce
|
|
703
|
+
JOIN commits c ON c.id = ce.commit_id
|
|
704
|
+
JOIN branches b ON b.id = c.branch_id
|
|
705
|
+
WHERE b.project_id = ?
|
|
706
|
+
AND ce.embedding MATCH ?
|
|
707
|
+
AND k = ?
|
|
708
|
+
ORDER BY ce.distance
|
|
709
|
+
`)
|
|
710
|
+
const rows = stmt.all(projectId, embeddingVector, limit) as Array<CommitRow & { distance: number }>
|
|
711
|
+
return rows.map((r) => ({
|
|
712
|
+
commit: toCommit(r),
|
|
713
|
+
score: 1 - r.distance, // cosine similarity approximation
|
|
714
|
+
matchType: 'semantic' as const,
|
|
715
|
+
}))
|
|
716
|
+
} catch {
|
|
717
|
+
// sqlite-vec not available or no embeddings indexed
|
|
718
|
+
return []
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ─── Full-text search (FTS5) ──────────────────────────────────────────────
|
|
723
|
+
|
|
724
|
+
fullTextSearch(db: Database, query: string, projectId: string): SearchResult[] {
|
|
725
|
+
try {
|
|
726
|
+
const stmt = db.prepare(`
|
|
727
|
+
SELECT c.*, bm25(commits_fts) AS score
|
|
728
|
+
FROM commits_fts
|
|
729
|
+
JOIN commits c ON c.rowid = commits_fts.rowid
|
|
730
|
+
JOIN branches b ON b.id = c.branch_id
|
|
731
|
+
WHERE b.project_id = ?
|
|
732
|
+
AND commits_fts MATCH ?
|
|
733
|
+
ORDER BY score
|
|
734
|
+
LIMIT 20
|
|
735
|
+
`)
|
|
736
|
+
const rows = stmt.all(projectId, query) as Array<CommitRow & { score: number }>
|
|
737
|
+
return rows.map((r) => ({
|
|
738
|
+
commit: toCommit(r),
|
|
739
|
+
score: Math.abs(r.score),
|
|
740
|
+
matchType: 'fulltext' as const,
|
|
741
|
+
}))
|
|
742
|
+
} catch {
|
|
743
|
+
// FTS5 migration not yet applied or query error
|
|
744
|
+
return []
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ─── Embed helpers ────────────────────────────────────────────────────────
|
|
749
|
+
|
|
750
|
+
insertEmbedding(db: Database, commitId: string, embedding: Float32Array): void {
|
|
751
|
+
try {
|
|
752
|
+
const stmt = db.prepare(
|
|
753
|
+
`INSERT OR REPLACE INTO commit_embeddings (commit_id, embedding) VALUES (?, ?)`
|
|
754
|
+
)
|
|
755
|
+
stmt.run(commitId, embedding)
|
|
756
|
+
} catch {
|
|
757
|
+
// sqlite-vec not available
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|