contextgit 0.0.2 → 0.0.4
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/dist/bootstrap.d.ts +10 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +43 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/commands/branch.d.ts +13 -0
- package/dist/commands/branch.d.ts.map +1 -0
- package/dist/commands/branch.js +52 -0
- package/dist/commands/branch.js.map +1 -0
- package/dist/commands/claim.d.ts +15 -0
- package/dist/commands/claim.d.ts.map +1 -0
- package/dist/commands/claim.js +60 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/commit.d.ts +17 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +83 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/context.d.ts +9 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +38 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +84 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +184 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keygen.d.ts +10 -0
- package/dist/commands/keygen.d.ts.map +1 -0
- package/dist/commands/keygen.js +57 -0
- package/dist/commands/keygen.js.map +1 -0
- package/dist/commands/log.d.ts +13 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +91 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/merge.d.ts +12 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.js +29 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/pull.d.ts +10 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +123 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +10 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +141 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/remote-show.d.ts +6 -0
- package/dist/commands/remote-show.d.ts.map +1 -0
- package/dist/commands/remote-show.js +71 -0
- package/dist/commands/remote-show.js.map +1 -0
- package/dist/commands/search.d.ts +11 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +47 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/serve.d.ts +9 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +51 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/set-remote.d.ts +9 -0
- package/dist/commands/set-remote.d.ts.map +1 -0
- package/dist/commands/set-remote.js +26 -0
- package/dist/commands/set-remote.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/unclaim.d.ts +9 -0
- package/dist/commands/unclaim.d.ts.map +1 -0
- package/dist/commands/unclaim.js +22 -0
- package/dist/commands/unclaim.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/git-hooks.d.ts +6 -0
- package/dist/git-hooks.d.ts.map +1 -0
- package/dist/git-hooks.js +58 -0
- package/dist/git-hooks.js.map +1 -0
- package/package.json +24 -18
- package/.claude/settings.local.json +0 -41
- package/.contextgit/config.json +0 -10
- package/.contextgit/system-prompt.md +0 -4
- package/.github/workflows/contextgit-ci.yml +0 -40
- package/CLAUDE.md +0 -123
- package/CLAUDE.md.next +0 -65
- package/docs/ContextGit_ARCHITECTURE_v3.md +0 -1141
- package/docs/ContextGit_DELTA.md +0 -84
- package/docs/ContextGit_PHASE1_PLAN.md +0 -177
- package/docs/ContextGit_PHASE2_PLAN.md +0 -535
- package/docs/ContextGit_PRD_v4.md +0 -488
- package/docs/decisions.md +0 -370
- package/packages/api/package.json +0 -25
- package/packages/api/src/bootstrap.ts +0 -64
- package/packages/api/src/config.ts +0 -45
- package/packages/api/src/index.ts +0 -17
- package/packages/api/src/middleware/auth.test.ts +0 -83
- package/packages/api/src/middleware/auth.ts +0 -41
- package/packages/api/src/remote-store.test.ts +0 -301
- package/packages/api/src/router.ts +0 -121
- package/packages/api/src/server-config.ts +0 -34
- package/packages/api/src/server.ts +0 -38
- package/packages/api/src/store-router.ts +0 -241
- package/packages/api/tsconfig.json +0 -8
- package/packages/cli/package.json +0 -29
- package/packages/cli/src/bootstrap.ts +0 -68
- package/packages/cli/src/commands/branch.ts +0 -58
- package/packages/cli/src/commands/claim.ts +0 -58
- package/packages/cli/src/commands/commit.ts +0 -79
- package/packages/cli/src/commands/context.ts +0 -46
- package/packages/cli/src/commands/doctor.ts +0 -99
- package/packages/cli/src/commands/init.ts +0 -141
- package/packages/cli/src/commands/keygen.ts +0 -65
- package/packages/cli/src/commands/log.ts +0 -103
- package/packages/cli/src/commands/merge.ts +0 -36
- package/packages/cli/src/commands/pull.ts +0 -145
- package/packages/cli/src/commands/push.ts +0 -158
- package/packages/cli/src/commands/remote-show.ts +0 -87
- package/packages/cli/src/commands/search.ts +0 -54
- package/packages/cli/src/commands/serve.ts +0 -61
- package/packages/cli/src/commands/set-remote.ts +0 -30
- package/packages/cli/src/commands/status.ts +0 -62
- package/packages/cli/src/commands/unclaim.ts +0 -28
- package/packages/cli/src/config.ts +0 -64
- package/packages/cli/src/git-hooks.ts +0 -61
- package/packages/cli/tsconfig.json +0 -9
- package/packages/core/package.json +0 -28
- package/packages/core/src/embeddings.test.ts +0 -58
- package/packages/core/src/embeddings.ts +0 -75
- package/packages/core/src/engine.ts +0 -274
- package/packages/core/src/index.ts +0 -6
- package/packages/core/src/snapshot.ts +0 -82
- package/packages/core/src/summarizer.test.ts +0 -120
- package/packages/core/src/summarizer.ts +0 -113
- package/packages/core/src/threads.ts +0 -29
- package/packages/core/src/types.ts +0 -240
- package/packages/core/tsconfig.json +0 -9
- package/packages/mcp/package.json +0 -31
- package/packages/mcp/src/auto-snapshot.ts +0 -83
- package/packages/mcp/src/config.ts +0 -53
- package/packages/mcp/src/git-sync.ts +0 -94
- package/packages/mcp/src/index.ts +0 -19
- package/packages/mcp/src/server.ts +0 -377
- package/packages/mcp/tsconfig.json +0 -9
- package/packages/store/package.json +0 -30
- package/packages/store/src/branch-merge.test.ts +0 -127
- package/packages/store/src/engine-integration.test.ts +0 -93
- package/packages/store/src/index.ts +0 -3
- package/packages/store/src/interface.ts +0 -62
- package/packages/store/src/local/claims.test.ts +0 -190
- package/packages/store/src/local/index.ts +0 -380
- package/packages/store/src/local/local-store.test.ts +0 -164
- package/packages/store/src/local/migrations.ts +0 -99
- package/packages/store/src/local/queries.ts +0 -760
- package/packages/store/src/local/schema.ts +0 -157
- package/packages/store/src/remote/index.ts +0 -300
- package/packages/store/tsconfig.json +0 -9
- package/pnpm-workspace.yaml +0 -2
- package/scripts/build.sh +0 -28
- package/tsconfig.base.json +0 -14
- package/vitest.config.ts +0 -15
- /package/{packages/cli/bin → bin}/run.js +0 -0
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Agent,
|
|
3
|
-
AgentInput,
|
|
4
|
-
AgentRole,
|
|
5
|
-
Branch,
|
|
6
|
-
BranchInput,
|
|
7
|
-
Claim,
|
|
8
|
-
ClaimInput,
|
|
9
|
-
Commit,
|
|
10
|
-
CommitInput,
|
|
11
|
-
Pagination,
|
|
12
|
-
Project,
|
|
13
|
-
ProjectInput,
|
|
14
|
-
SearchResult,
|
|
15
|
-
SessionSnapshot,
|
|
16
|
-
SnapshotFormat,
|
|
17
|
-
Thread,
|
|
18
|
-
} from '@contextgit/core'
|
|
19
|
-
|
|
20
|
-
export interface ContextStore {
|
|
21
|
-
// Projects
|
|
22
|
-
createProject(project: ProjectInput): Promise<Project>
|
|
23
|
-
getProject(id: string): Promise<Project | null>
|
|
24
|
-
|
|
25
|
-
// Branches
|
|
26
|
-
createBranch(branch: BranchInput): Promise<Branch>
|
|
27
|
-
getBranch(id: string): Promise<Branch | null>
|
|
28
|
-
getBranchByGitName(projectId: string, gitBranch: string): Promise<Branch | null>
|
|
29
|
-
listBranches(projectId: string): Promise<Branch[]>
|
|
30
|
-
updateBranchHead(branchId: string, commitId: string): Promise<void>
|
|
31
|
-
mergeBranch(sourceBranchId: string, targetBranchId: string, summary: string): Promise<Commit>
|
|
32
|
-
|
|
33
|
-
// Commits
|
|
34
|
-
createCommit(commit: CommitInput): Promise<Commit>
|
|
35
|
-
getCommit(id: string): Promise<Commit | null>
|
|
36
|
-
listCommits(branchId: string, pagination: Pagination): Promise<Commit[]>
|
|
37
|
-
|
|
38
|
-
// Snapshots
|
|
39
|
-
getSessionSnapshot(projectId: string, branchId: string, options?: { agentRole?: AgentRole }): Promise<SessionSnapshot>
|
|
40
|
-
getFormattedSnapshot(projectId: string, branchId: string, format: SnapshotFormat): Promise<string>
|
|
41
|
-
|
|
42
|
-
// Threads
|
|
43
|
-
listOpenThreads(projectId: string): Promise<Thread[]>
|
|
44
|
-
listOpenThreadsByBranch(branchId: string): Promise<Thread[]>
|
|
45
|
-
syncThread(thread: Thread): Promise<Thread>
|
|
46
|
-
|
|
47
|
-
// Search
|
|
48
|
-
// vector is a 384-dim Float32Array produced by EmbeddingService in core.
|
|
49
|
-
// Callers that cannot generate a vector should skip this and use fullTextSearch.
|
|
50
|
-
indexEmbedding(commitId: string, vector: Float32Array): Promise<void>
|
|
51
|
-
semanticSearch(vector: Float32Array, projectId: string, limit: number): Promise<SearchResult[]>
|
|
52
|
-
fullTextSearch(query: string, projectId: string): Promise<SearchResult[]>
|
|
53
|
-
|
|
54
|
-
// Agents
|
|
55
|
-
upsertAgent(agent: AgentInput): Promise<Agent>
|
|
56
|
-
listAgents(projectId: string): Promise<Agent[]>
|
|
57
|
-
|
|
58
|
-
// Claims
|
|
59
|
-
claimTask(projectId: string, branchId: string, input: ClaimInput): Promise<Claim>
|
|
60
|
-
unclaimTask(claimId: string): Promise<void>
|
|
61
|
-
listActiveClaims(projectId: string): Promise<Claim[]>
|
|
62
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
-
import { LocalStore } from './index.js'
|
|
3
|
-
|
|
4
|
-
// Helpers to set up a minimal project + branch
|
|
5
|
-
async function setupStore() {
|
|
6
|
-
const store = new LocalStore(':memory:')
|
|
7
|
-
const project = await store.createProject({ name: 'test-project' })
|
|
8
|
-
const branch = await store.createBranch({
|
|
9
|
-
projectId: project.id,
|
|
10
|
-
name: 'main',
|
|
11
|
-
gitBranch: 'main',
|
|
12
|
-
})
|
|
13
|
-
return { store, project, branch }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('claimTask', () => {
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
// stores are :memory: — closed per test via store.close()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('creates a claim with correct fields', async () => {
|
|
22
|
-
const { store, project, branch } = await setupStore()
|
|
23
|
-
|
|
24
|
-
const claim = await store.claimTask(project.id, branch.id, {
|
|
25
|
-
task: 'build auth module',
|
|
26
|
-
agentId: 'agent-1',
|
|
27
|
-
role: 'dev',
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
expect(claim.id).toBeTruthy()
|
|
31
|
-
expect(claim.projectId).toBe(project.id)
|
|
32
|
-
expect(claim.branchId).toBe(branch.id)
|
|
33
|
-
expect(claim.task).toBe('build auth module')
|
|
34
|
-
expect(claim.agentId).toBe('agent-1')
|
|
35
|
-
expect(claim.role).toBe('dev')
|
|
36
|
-
expect(claim.status).toBe('proposed')
|
|
37
|
-
expect(claim.ttl).toBe(7_200_000)
|
|
38
|
-
expect(claim.claimedAt).toBeInstanceOf(Date)
|
|
39
|
-
expect(claim.releasedAt).toBeUndefined()
|
|
40
|
-
|
|
41
|
-
store.close()
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('respects custom status and ttl', async () => {
|
|
45
|
-
const { store, project, branch } = await setupStore()
|
|
46
|
-
|
|
47
|
-
const claim = await store.claimTask(project.id, branch.id, {
|
|
48
|
-
task: 'write tests',
|
|
49
|
-
agentId: 'agent-2',
|
|
50
|
-
role: 'test',
|
|
51
|
-
status: 'active',
|
|
52
|
-
ttl: 3_600_000, // 1h
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
expect(claim.status).toBe('active')
|
|
56
|
-
expect(claim.ttl).toBe(3_600_000)
|
|
57
|
-
|
|
58
|
-
store.close()
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('listActiveClaims', () => {
|
|
63
|
-
it('returns proposed and active claims', async () => {
|
|
64
|
-
const { store, project, branch } = await setupStore()
|
|
65
|
-
|
|
66
|
-
await store.claimTask(project.id, branch.id, { task: 'task-a', agentId: 'agent-1', role: 'dev', status: 'proposed' })
|
|
67
|
-
await store.claimTask(project.id, branch.id, { task: 'task-b', agentId: 'agent-2', role: 'test', status: 'active' })
|
|
68
|
-
|
|
69
|
-
const claims = await store.listActiveClaims(project.id)
|
|
70
|
-
expect(claims).toHaveLength(2)
|
|
71
|
-
|
|
72
|
-
store.close()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('excludes released claims', async () => {
|
|
76
|
-
const { store, project, branch } = await setupStore()
|
|
77
|
-
|
|
78
|
-
const c1 = await store.claimTask(project.id, branch.id, { task: 'task-a', agentId: 'agent-1', role: 'dev' })
|
|
79
|
-
await store.claimTask(project.id, branch.id, { task: 'task-b', agentId: 'agent-2', role: 'dev' })
|
|
80
|
-
await store.unclaimTask(c1.id)
|
|
81
|
-
|
|
82
|
-
const claims = await store.listActiveClaims(project.id)
|
|
83
|
-
expect(claims).toHaveLength(1)
|
|
84
|
-
expect(claims[0].task).toBe('task-b')
|
|
85
|
-
|
|
86
|
-
store.close()
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('excludes TTL-expired claims', async () => {
|
|
90
|
-
const { store, project, branch } = await setupStore()
|
|
91
|
-
|
|
92
|
-
// Create a claim with 1ms TTL (already expired)
|
|
93
|
-
await store.claimTask(project.id, branch.id, { task: 'expired', agentId: 'agent-1', role: 'dev', ttl: 1 })
|
|
94
|
-
// Wait a tick to ensure the TTL has elapsed
|
|
95
|
-
await new Promise((r) => setTimeout(r, 10))
|
|
96
|
-
await store.claimTask(project.id, branch.id, { task: 'active', agentId: 'agent-2', role: 'dev', ttl: 7_200_000 })
|
|
97
|
-
|
|
98
|
-
const claims = await store.listActiveClaims(project.id)
|
|
99
|
-
expect(claims).toHaveLength(1)
|
|
100
|
-
expect(claims[0].task).toBe('active')
|
|
101
|
-
|
|
102
|
-
store.close()
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
describe('unclaimTask', () => {
|
|
107
|
-
it('sets status to released and sets releasedAt', async () => {
|
|
108
|
-
const { store, project, branch } = await setupStore()
|
|
109
|
-
|
|
110
|
-
const claim = await store.claimTask(project.id, branch.id, { task: 'do work', agentId: 'agent-1', role: 'solo' })
|
|
111
|
-
await store.unclaimTask(claim.id)
|
|
112
|
-
|
|
113
|
-
const active = await store.listActiveClaims(project.id)
|
|
114
|
-
expect(active).toHaveLength(0)
|
|
115
|
-
|
|
116
|
-
store.close()
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('createCommit auto-release', () => {
|
|
121
|
-
it('releases this agent\'s claims on this branch after commit', async () => {
|
|
122
|
-
const { store, project, branch } = await setupStore()
|
|
123
|
-
|
|
124
|
-
await store.claimTask(project.id, branch.id, { task: 'my task', agentId: 'agent-1', role: 'dev' })
|
|
125
|
-
// Second agent's claim should NOT be released
|
|
126
|
-
await store.claimTask(project.id, branch.id, { task: 'other task', agentId: 'agent-2', role: 'dev' })
|
|
127
|
-
|
|
128
|
-
await store.createCommit({
|
|
129
|
-
branchId: branch.id,
|
|
130
|
-
agentId: 'agent-1',
|
|
131
|
-
agentRole: 'dev',
|
|
132
|
-
tool: 'test',
|
|
133
|
-
workflowType: 'interactive',
|
|
134
|
-
message: 'done',
|
|
135
|
-
content: 'finished',
|
|
136
|
-
summary: 'finished',
|
|
137
|
-
commitType: 'manual',
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
const remaining = await store.listActiveClaims(project.id)
|
|
141
|
-
expect(remaining).toHaveLength(1)
|
|
142
|
-
expect(remaining[0].agentId).toBe('agent-2')
|
|
143
|
-
|
|
144
|
-
store.close()
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('does not release claims on other branches', async () => {
|
|
148
|
-
const { store, project, branch } = await setupStore()
|
|
149
|
-
const otherBranch = await store.createBranch({
|
|
150
|
-
projectId: project.id,
|
|
151
|
-
name: 'feature',
|
|
152
|
-
gitBranch: 'feature/x',
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
// agent-1 has a claim on the other branch
|
|
156
|
-
await store.claimTask(project.id, otherBranch.id, { task: 'other branch task', agentId: 'agent-1', role: 'dev' })
|
|
157
|
-
|
|
158
|
-
await store.createCommit({
|
|
159
|
-
branchId: branch.id,
|
|
160
|
-
agentId: 'agent-1',
|
|
161
|
-
agentRole: 'dev',
|
|
162
|
-
tool: 'test',
|
|
163
|
-
workflowType: 'interactive',
|
|
164
|
-
message: 'commit on main',
|
|
165
|
-
content: 'work',
|
|
166
|
-
summary: 'work',
|
|
167
|
-
commitType: 'manual',
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
const remaining = await store.listActiveClaims(project.id)
|
|
171
|
-
expect(remaining).toHaveLength(1)
|
|
172
|
-
expect(remaining[0].task).toBe('other branch task')
|
|
173
|
-
|
|
174
|
-
store.close()
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
describe('snapshot activeClaims', () => {
|
|
179
|
-
it('includes active claims in session snapshot', async () => {
|
|
180
|
-
const { store, project, branch } = await setupStore()
|
|
181
|
-
|
|
182
|
-
await store.claimTask(project.id, branch.id, { task: 'build feature', agentId: 'agent-1', role: 'dev' })
|
|
183
|
-
|
|
184
|
-
const snapshot = await store.getSessionSnapshot(project.id, branch.id)
|
|
185
|
-
expect(snapshot.activeClaims).toHaveLength(1)
|
|
186
|
-
expect(snapshot.activeClaims[0].task).toBe('build feature')
|
|
187
|
-
|
|
188
|
-
store.close()
|
|
189
|
-
})
|
|
190
|
-
})
|
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
// LocalStore — better-sqlite3 + sqlite-vec implementation of ContextStore.
|
|
2
|
-
//
|
|
3
|
-
// Key invariants:
|
|
4
|
-
// • All SQLite calls are synchronous (better-sqlite3).
|
|
5
|
-
// • The ContextStore interface returns Promises, so we wrap at the boundary
|
|
6
|
-
// with Promise.resolve() / Promise.reject().
|
|
7
|
-
// • IDs are nanoid() TEXT — never auto-increment integers.
|
|
8
|
-
// • DB path: ~/.contextgit/projects/<projectId>.db
|
|
9
|
-
|
|
10
|
-
import { homedir } from 'os'
|
|
11
|
-
import { mkdirSync } from 'fs'
|
|
12
|
-
import { join } from 'path'
|
|
13
|
-
import { createRequire } from 'module'
|
|
14
|
-
import Database from 'better-sqlite3'
|
|
15
|
-
import { nanoid } from 'nanoid'
|
|
16
|
-
|
|
17
|
-
const require = createRequire(import.meta.url)
|
|
18
|
-
import type {
|
|
19
|
-
Agent,
|
|
20
|
-
AgentInput,
|
|
21
|
-
Branch,
|
|
22
|
-
BranchInput,
|
|
23
|
-
Claim,
|
|
24
|
-
ClaimInput,
|
|
25
|
-
Commit,
|
|
26
|
-
CommitInput,
|
|
27
|
-
Pagination,
|
|
28
|
-
Project,
|
|
29
|
-
ProjectInput,
|
|
30
|
-
SearchResult,
|
|
31
|
-
SessionSnapshot,
|
|
32
|
-
SnapshotFormat,
|
|
33
|
-
Thread,
|
|
34
|
-
} from '@contextgit/core'
|
|
35
|
-
import { SnapshotFormatter } from '@contextgit/core'
|
|
36
|
-
import type { ContextStore } from '../interface.js'
|
|
37
|
-
import { runMigrations } from './migrations.js'
|
|
38
|
-
import { Queries } from './queries.js'
|
|
39
|
-
|
|
40
|
-
const snapshotFormatter = new SnapshotFormatter()
|
|
41
|
-
|
|
42
|
-
// ─── LocalStore ───────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
export class LocalStore implements ContextStore {
|
|
45
|
-
private readonly db: Database.Database
|
|
46
|
-
private readonly q: Queries
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @param projectId Used to derive the DB file path.
|
|
50
|
-
* Pass ':memory:' for tests.
|
|
51
|
-
*/
|
|
52
|
-
constructor(projectId: string) {
|
|
53
|
-
let dbPath: string
|
|
54
|
-
if (projectId === ':memory:') {
|
|
55
|
-
dbPath = ':memory:'
|
|
56
|
-
} else {
|
|
57
|
-
const dir = join(homedir(), '.contextgit', 'projects')
|
|
58
|
-
mkdirSync(dir, { recursive: true })
|
|
59
|
-
dbPath = join(dir, `${projectId}.db`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.db = new Database(dbPath)
|
|
63
|
-
this.db.pragma('journal_mode = WAL')
|
|
64
|
-
this.db.pragma('busy_timeout = 5000')
|
|
65
|
-
this.db.pragma('foreign_keys = ON')
|
|
66
|
-
|
|
67
|
-
// Attempt to load sqlite-vec extension — graceful failure if not available
|
|
68
|
-
try {
|
|
69
|
-
const sqliteVec = require('sqlite-vec') as { load: (db: Database.Database) => void }
|
|
70
|
-
sqliteVec.load(this.db)
|
|
71
|
-
} catch {
|
|
72
|
-
// sqlite-vec not installed or ABI mismatch — semantic search disabled
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
runMigrations(this.db)
|
|
76
|
-
this.q = new Queries(this.db)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─── Projects ─────────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
createProject(project: ProjectInput): Promise<Project> {
|
|
82
|
-
try {
|
|
83
|
-
return Promise.resolve(this.q.insertProject({ id: project.id ?? nanoid(), ...project }))
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return Promise.reject(e)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getProject(id: string): Promise<Project | null> {
|
|
90
|
-
try {
|
|
91
|
-
return Promise.resolve(this.q.getProject(id))
|
|
92
|
-
} catch (e) {
|
|
93
|
-
return Promise.reject(e)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ─── Branches ─────────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
createBranch(branch: BranchInput): Promise<Branch> {
|
|
100
|
-
try {
|
|
101
|
-
const id = branch.id ?? nanoid()
|
|
102
|
-
// Idempotent: if caller supplied an ID and it already exists, return existing
|
|
103
|
-
if (branch.id) {
|
|
104
|
-
const existing = this.q.getBranch(branch.id)
|
|
105
|
-
if (existing) return Promise.resolve(existing)
|
|
106
|
-
}
|
|
107
|
-
return Promise.resolve(this.q.insertBranch({ ...branch, id }))
|
|
108
|
-
} catch (e) {
|
|
109
|
-
return Promise.reject(e)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getBranch(id: string): Promise<Branch | null> {
|
|
114
|
-
try {
|
|
115
|
-
return Promise.resolve(this.q.getBranch(id))
|
|
116
|
-
} catch (e) {
|
|
117
|
-
return Promise.reject(e)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
getBranchByGitName(projectId: string, gitBranch: string): Promise<Branch | null> {
|
|
122
|
-
try {
|
|
123
|
-
return Promise.resolve(this.q.getBranchByGitName(projectId, gitBranch))
|
|
124
|
-
} catch (e) {
|
|
125
|
-
return Promise.reject(e)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
listBranches(projectId: string): Promise<Branch[]> {
|
|
130
|
-
try {
|
|
131
|
-
return Promise.resolve(this.q.listBranches(projectId))
|
|
132
|
-
} catch (e) {
|
|
133
|
-
return Promise.reject(e)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
updateBranchHead(branchId: string, commitId: string): Promise<void> {
|
|
138
|
-
try {
|
|
139
|
-
this.q.updateBranchHead(branchId, commitId)
|
|
140
|
-
return Promise.resolve()
|
|
141
|
-
} catch (e) {
|
|
142
|
-
return Promise.reject(e)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
mergeBranch(sourceBranchId: string, targetBranchId: string, summary: string): Promise<Commit> {
|
|
147
|
-
try {
|
|
148
|
-
const result = this.db.transaction((): Commit => {
|
|
149
|
-
const sourceBranch = this.q.getBranch(sourceBranchId)
|
|
150
|
-
if (!sourceBranch) throw new Error(`Source branch not found: ${sourceBranchId}`)
|
|
151
|
-
const targetBranch = this.q.getBranch(targetBranchId)
|
|
152
|
-
if (!targetBranch) throw new Error(`Target branch not found: ${targetBranchId}`)
|
|
153
|
-
|
|
154
|
-
const mergeCommitInput: CommitInput = {
|
|
155
|
-
branchId: targetBranchId,
|
|
156
|
-
parentId: targetBranch.headCommitId,
|
|
157
|
-
agentId: 'system',
|
|
158
|
-
agentRole: 'orchestrator',
|
|
159
|
-
tool: 'contextgit',
|
|
160
|
-
workflowType: 'interactive',
|
|
161
|
-
message: `Merge ${sourceBranch.name} into ${targetBranch.name}`,
|
|
162
|
-
content: summary,
|
|
163
|
-
summary,
|
|
164
|
-
commitType: 'merge',
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const mergeCommit = this.q.insertCommit(
|
|
168
|
-
nanoid(),
|
|
169
|
-
mergeCommitInput,
|
|
170
|
-
targetBranch.headCommitId ?? null,
|
|
171
|
-
sourceBranchId,
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
// Update target branch HEAD
|
|
175
|
-
this.q.updateBranchHead(targetBranchId, mergeCommit.id)
|
|
176
|
-
|
|
177
|
-
// Mark source branch as merged
|
|
178
|
-
this.q.markBranchMerged(sourceBranchId)
|
|
179
|
-
|
|
180
|
-
// Carry open threads from source branch to target branch
|
|
181
|
-
this.q.reassignOpenThreads(sourceBranchId, targetBranchId)
|
|
182
|
-
|
|
183
|
-
return mergeCommit
|
|
184
|
-
})()
|
|
185
|
-
|
|
186
|
-
return Promise.resolve(result)
|
|
187
|
-
} catch (e) {
|
|
188
|
-
return Promise.reject(e)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ─── Commits ──────────────────────────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
createCommit(input: CommitInput): Promise<Commit> {
|
|
195
|
-
try {
|
|
196
|
-
// Idempotent: if caller supplied an ID and it already exists, return existing
|
|
197
|
-
if (input.id) {
|
|
198
|
-
const existing = this.q.getCommit(input.id)
|
|
199
|
-
if (existing) return Promise.resolve(existing)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const result = this.db.transaction((): Commit => {
|
|
203
|
-
const branch = this.q.getBranch(input.branchId)
|
|
204
|
-
if (!branch) throw new Error(`Branch not found: ${input.branchId}`)
|
|
205
|
-
|
|
206
|
-
const commitId = input.id ?? nanoid()
|
|
207
|
-
const parentId = input.parentId ?? branch.headCommitId ?? null
|
|
208
|
-
|
|
209
|
-
const commit = this.q.insertCommit(commitId, input, parentId)
|
|
210
|
-
|
|
211
|
-
// Handle thread operations
|
|
212
|
-
if (input.threads?.open?.length) {
|
|
213
|
-
for (const description of input.threads.open) {
|
|
214
|
-
this.q.insertThread(
|
|
215
|
-
nanoid(),
|
|
216
|
-
description,
|
|
217
|
-
branch.projectId,
|
|
218
|
-
input.branchId,
|
|
219
|
-
commitId,
|
|
220
|
-
input.workflowType ?? null,
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (input.threads?.close?.length) {
|
|
226
|
-
for (const { id, note } of input.threads.close) {
|
|
227
|
-
this.q.closeThread(id, commitId, note)
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Update branch HEAD
|
|
232
|
-
this.q.updateBranchHead(input.branchId, commitId)
|
|
233
|
-
|
|
234
|
-
// Update agent stats
|
|
235
|
-
this.q.incrementAgentCommits(input.agentId)
|
|
236
|
-
|
|
237
|
-
// Auto-release this agent's claims on this branch (branch-scoped)
|
|
238
|
-
this.q.releaseClaimsByAgent(input.agentId, input.branchId)
|
|
239
|
-
|
|
240
|
-
return commit
|
|
241
|
-
})()
|
|
242
|
-
|
|
243
|
-
return Promise.resolve(result)
|
|
244
|
-
} catch (e) {
|
|
245
|
-
return Promise.reject(e)
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
getCommit(id: string): Promise<Commit | null> {
|
|
250
|
-
try {
|
|
251
|
-
return Promise.resolve(this.q.getCommit(id))
|
|
252
|
-
} catch (e) {
|
|
253
|
-
return Promise.reject(e)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
listCommits(branchId: string, pagination: Pagination): Promise<Commit[]> {
|
|
258
|
-
try {
|
|
259
|
-
return Promise.resolve(this.q.listCommits(branchId, pagination))
|
|
260
|
-
} catch (e) {
|
|
261
|
-
return Promise.reject(e)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// ─── Snapshots ────────────────────────────────────────────────────────────
|
|
266
|
-
|
|
267
|
-
getSessionSnapshot(projectId: string, branchId: string, options?: { agentRole?: import('@contextgit/core').AgentRole }): Promise<SessionSnapshot> {
|
|
268
|
-
try {
|
|
269
|
-
return Promise.resolve(this.q.getSessionSnapshot(projectId, branchId, options))
|
|
270
|
-
} catch (e) {
|
|
271
|
-
return Promise.reject(e)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
getFormattedSnapshot(projectId: string, branchId: string, format: SnapshotFormat): Promise<string> {
|
|
276
|
-
try {
|
|
277
|
-
const snapshot = this.q.getSessionSnapshot(projectId, branchId)
|
|
278
|
-
return Promise.resolve(snapshotFormatter.format(snapshot, format))
|
|
279
|
-
} catch (e) {
|
|
280
|
-
return Promise.reject(e)
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ─── Threads ──────────────────────────────────────────────────────────────
|
|
285
|
-
|
|
286
|
-
listOpenThreads(projectId: string): Promise<Thread[]> {
|
|
287
|
-
try {
|
|
288
|
-
return Promise.resolve(this.q.listOpenThreads(projectId))
|
|
289
|
-
} catch (e) {
|
|
290
|
-
return Promise.reject(e)
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
listOpenThreadsByBranch(branchId: string): Promise<Thread[]> {
|
|
295
|
-
try {
|
|
296
|
-
return Promise.resolve(this.q.listOpenThreadsByBranch(branchId))
|
|
297
|
-
} catch (e) {
|
|
298
|
-
return Promise.reject(e)
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
syncThread(thread: Thread): Promise<Thread> {
|
|
303
|
-
try {
|
|
304
|
-
return Promise.resolve(this.q.syncThread(thread))
|
|
305
|
-
} catch (e) {
|
|
306
|
-
return Promise.reject(e)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ─── Search ───────────────────────────────────────────────────────────────
|
|
311
|
-
|
|
312
|
-
indexEmbedding(commitId: string, vector: Float32Array): Promise<void> {
|
|
313
|
-
this.q.insertEmbedding(this.db, commitId, vector)
|
|
314
|
-
return Promise.resolve()
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
semanticSearch(vector: Float32Array, projectId: string, limit: number): Promise<SearchResult[]> {
|
|
318
|
-
return Promise.resolve(this.q.semanticSearch(this.db, vector, projectId, limit))
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
fullTextSearch(query: string, projectId: string): Promise<SearchResult[]> {
|
|
322
|
-
try {
|
|
323
|
-
return Promise.resolve(this.q.fullTextSearch(this.db, query, projectId))
|
|
324
|
-
} catch (e) {
|
|
325
|
-
return Promise.reject(e)
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ─── Agents ───────────────────────────────────────────────────────────────
|
|
330
|
-
|
|
331
|
-
upsertAgent(agent: AgentInput): Promise<Agent> {
|
|
332
|
-
try {
|
|
333
|
-
return Promise.resolve(this.q.upsertAgent(agent))
|
|
334
|
-
} catch (e) {
|
|
335
|
-
return Promise.reject(e)
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
listAgents(projectId: string): Promise<Agent[]> {
|
|
340
|
-
try {
|
|
341
|
-
return Promise.resolve(this.q.listAgents(projectId))
|
|
342
|
-
} catch (e) {
|
|
343
|
-
return Promise.reject(e)
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// ─── Claims ───────────────────────────────────────────────────────────────
|
|
348
|
-
|
|
349
|
-
claimTask(projectId: string, branchId: string, input: ClaimInput): Promise<Claim> {
|
|
350
|
-
try {
|
|
351
|
-
return Promise.resolve(this.q.insertClaim(nanoid(), projectId, branchId, input))
|
|
352
|
-
} catch (e) {
|
|
353
|
-
return Promise.reject(e)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
unclaimTask(claimId: string): Promise<void> {
|
|
358
|
-
try {
|
|
359
|
-
this.q.updateClaimStatus(claimId, 'released', Date.now())
|
|
360
|
-
return Promise.resolve()
|
|
361
|
-
} catch (e) {
|
|
362
|
-
return Promise.reject(e)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
listActiveClaims(projectId: string): Promise<Claim[]> {
|
|
367
|
-
try {
|
|
368
|
-
return Promise.resolve(this.q.listActiveClaims(projectId))
|
|
369
|
-
} catch (e) {
|
|
370
|
-
return Promise.reject(e)
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
375
|
-
|
|
376
|
-
close(): void {
|
|
377
|
-
this.db.close()
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|