contextgit 0.0.2 → 0.0.3
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 +13 -0
- package/dist/commands/claim.d.ts.map +1 -0
- package/dist/commands/claim.js +50 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/commit.d.ts +14 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +71 -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 +126 -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 +22 -19
- 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,377 +0,0 @@
|
|
|
1
|
-
// server.ts — ContextGit MCP Server
|
|
2
|
-
//
|
|
3
|
-
// Exposes three tools to Claude and other MCP clients:
|
|
4
|
-
// context_get — return a formatted project snapshot
|
|
5
|
-
// context_commit — persist a context commit
|
|
6
|
-
// context_search — full-text search over past commits
|
|
7
|
-
//
|
|
8
|
-
// Transport: stdio (launched by the MCP host, e.g. Claude Desktop / Claude Code)
|
|
9
|
-
//
|
|
10
|
-
// Initialization (per server process):
|
|
11
|
-
// 1. Load .contextgit/config.json (search from CWD upward)
|
|
12
|
-
// 2. Open LocalStore for projectId
|
|
13
|
-
// 3. Detect current git branch via simple-git
|
|
14
|
-
// 4. Resolve (or create) the context branch for that git branch
|
|
15
|
-
// 5. Build ContextEngine, call engine.init()
|
|
16
|
-
|
|
17
|
-
import os from 'os'
|
|
18
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
19
|
-
import { z } from 'zod'
|
|
20
|
-
import { simpleGit } from 'simple-git'
|
|
21
|
-
import { ContextEngine, EmbeddingService, SnapshotFormatter } from '@contextgit/core'
|
|
22
|
-
import { LocalStore, RemoteStore } from '@contextgit/store'
|
|
23
|
-
import { loadConfig } from './config.js'
|
|
24
|
-
import { captureGitMetadata } from './git-sync.js'
|
|
25
|
-
import { AutoSnapshotManager } from './auto-snapshot.js'
|
|
26
|
-
import type { ContextStore } from '@contextgit/store'
|
|
27
|
-
import type { ContextGitConfig } from '@contextgit/core'
|
|
28
|
-
|
|
29
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
async function detectGitBranch(): Promise<string> {
|
|
32
|
-
try {
|
|
33
|
-
const git = simpleGit(process.cwd())
|
|
34
|
-
const result = await git.revparse(['--abbrev-ref', 'HEAD'])
|
|
35
|
-
return result.trim()
|
|
36
|
-
} catch {
|
|
37
|
-
return 'main'
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Resolve the ContextGit branch that tracks `gitBranch`.
|
|
43
|
-
* Creates one if it doesn't exist yet.
|
|
44
|
-
*/
|
|
45
|
-
async function resolveContextBranch(
|
|
46
|
-
store: ContextStore,
|
|
47
|
-
projectId: string,
|
|
48
|
-
gitBranch: string,
|
|
49
|
-
): Promise<string> {
|
|
50
|
-
const existing = await store.getBranchByGitName(projectId, gitBranch)
|
|
51
|
-
if (existing) return existing.id
|
|
52
|
-
|
|
53
|
-
const created = await store.createBranch({
|
|
54
|
-
projectId,
|
|
55
|
-
name: `Context: ${gitBranch}`,
|
|
56
|
-
gitBranch,
|
|
57
|
-
})
|
|
58
|
-
return created.id
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ─── Server bootstrap ─────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
interface ServerContext {
|
|
64
|
-
engine: ContextEngine
|
|
65
|
-
store: ContextStore
|
|
66
|
-
projectId: string
|
|
67
|
-
branchId: string
|
|
68
|
-
config: ContextGitConfig
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function bootstrap(): Promise<ServerContext> {
|
|
72
|
-
const config = loadConfig()
|
|
73
|
-
const { projectId } = config
|
|
74
|
-
|
|
75
|
-
const store: ContextStore =
|
|
76
|
-
config.store && config.store !== 'local'
|
|
77
|
-
? new RemoteStore(config.store)
|
|
78
|
-
: new LocalStore(projectId)
|
|
79
|
-
|
|
80
|
-
// Ensure the project row exists before creating branches (FK constraint)
|
|
81
|
-
const existing = await store.getProject(projectId)
|
|
82
|
-
if (!existing) {
|
|
83
|
-
await store.createProject({ id: projectId, name: config.project })
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const gitBranch = await detectGitBranch()
|
|
87
|
-
const branchId = await resolveContextBranch(store, projectId, gitBranch)
|
|
88
|
-
|
|
89
|
-
const hostname = os.hostname()
|
|
90
|
-
const agentId = `${hostname}-mcp-claude-code-interactive`
|
|
91
|
-
|
|
92
|
-
const engine = new ContextEngine(
|
|
93
|
-
store,
|
|
94
|
-
agentId,
|
|
95
|
-
config.agentRole ?? 'solo',
|
|
96
|
-
'claude-code',
|
|
97
|
-
config.workflowType ?? 'interactive',
|
|
98
|
-
{ embeddingService: new EmbeddingService() },
|
|
99
|
-
)
|
|
100
|
-
await engine.init(projectId, branchId)
|
|
101
|
-
|
|
102
|
-
return { engine, store, projectId, branchId, config }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ─── Tool definitions ─────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
export async function createServer(): Promise<McpServer> {
|
|
108
|
-
const ctx = await bootstrap()
|
|
109
|
-
const autoSnapshot = new AutoSnapshotManager(ctx.engine, {
|
|
110
|
-
interval: ctx.config.snapshotInterval ?? 10,
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
const server = new McpServer({
|
|
114
|
-
name: 'contextgit',
|
|
115
|
-
version: '0.0.1',
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
// ── context_get ─────────────────────────────────────────────────────────────
|
|
119
|
-
server.tool(
|
|
120
|
-
'context_get',
|
|
121
|
-
'Retrieve the current project snapshot. Call this at every session start to load project state.',
|
|
122
|
-
{
|
|
123
|
-
scope: z.enum(['global', 'branch']).default('global').describe(
|
|
124
|
-
"'global' returns the full project summary + branch state. 'branch' scopes to the current branch.",
|
|
125
|
-
),
|
|
126
|
-
format: z.enum(['agents-md', 'json', 'text']).default('agents-md').describe(
|
|
127
|
-
'Output format. agents-md is optimized for agent consumption.',
|
|
128
|
-
),
|
|
129
|
-
agent_role: z.enum(['orchestrator','dev','test','review','background','ci','solo']).optional().describe(
|
|
130
|
-
'Filter recentCommits to this agent role only. Omit to return commits from all roles.',
|
|
131
|
-
),
|
|
132
|
-
},
|
|
133
|
-
async ({ format, agent_role }) => {
|
|
134
|
-
await autoSnapshot.onToolCall('context_get')
|
|
135
|
-
try {
|
|
136
|
-
const snapshot = await ctx.store.getSessionSnapshot(
|
|
137
|
-
ctx.projectId,
|
|
138
|
-
ctx.branchId,
|
|
139
|
-
agent_role ? { agentRole: agent_role } : undefined,
|
|
140
|
-
)
|
|
141
|
-
const text = new SnapshotFormatter().format(snapshot, format)
|
|
142
|
-
return {
|
|
143
|
-
content: [{ type: 'text', text }],
|
|
144
|
-
}
|
|
145
|
-
} catch (err) {
|
|
146
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
147
|
-
return {
|
|
148
|
-
content: [{ type: 'text', text: `Error retrieving snapshot: ${message}` }],
|
|
149
|
-
isError: true,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
// ── context_commit ───────────────────────────────────────────────────────────
|
|
156
|
-
server.tool(
|
|
157
|
-
'context_commit',
|
|
158
|
-
'Persist a context commit recording significant work. Call this after completing meaningful tasks or milestones.',
|
|
159
|
-
{
|
|
160
|
-
message: z.string().min(1).describe('Short summary of what was accomplished (1–2 sentences).'),
|
|
161
|
-
content: z.string().min(1).describe('Detailed description of the work done, decisions made, and current state.'),
|
|
162
|
-
open_threads: z.array(z.string()).optional().describe(
|
|
163
|
-
'New open questions or blockers to track (each max 200 chars).',
|
|
164
|
-
),
|
|
165
|
-
close_thread_ids: z.array(z.string()).optional().describe(
|
|
166
|
-
'IDs of threads to close.',
|
|
167
|
-
),
|
|
168
|
-
},
|
|
169
|
-
async ({ message, content, open_threads, close_thread_ids }) => {
|
|
170
|
-
await autoSnapshot.onToolCall('context_commit')
|
|
171
|
-
try {
|
|
172
|
-
const threads: { open?: string[]; close?: Array<{ id: string; note: string }> } = {}
|
|
173
|
-
if (open_threads?.length) threads.open = open_threads
|
|
174
|
-
if (close_thread_ids?.length) {
|
|
175
|
-
threads.close = close_thread_ids.map(id => ({ id, note: 'Closed via context_commit' }))
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const git = await captureGitMetadata(process.cwd())
|
|
179
|
-
const commit = await ctx.engine.commit({
|
|
180
|
-
message,
|
|
181
|
-
content,
|
|
182
|
-
gitCommitSha: git?.sha,
|
|
183
|
-
...(Object.keys(threads).length > 0 ? { threads } : {}),
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
content: [
|
|
188
|
-
{
|
|
189
|
-
type: 'text',
|
|
190
|
-
text: `Commit recorded.\nID: ${commit.id}\nMessage: ${commit.message}`,
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
}
|
|
194
|
-
} catch (err) {
|
|
195
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
196
|
-
return {
|
|
197
|
-
content: [{ type: 'text', text: `Error recording commit: ${message}` }],
|
|
198
|
-
isError: true,
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
// ── context_search ───────────────────────────────────────────────────────────
|
|
205
|
-
server.tool(
|
|
206
|
-
'context_search',
|
|
207
|
-
'Search past context commits. Uses semantic + full-text search and merges results.',
|
|
208
|
-
{
|
|
209
|
-
query: z.string().min(1).describe('Search query — natural language or keywords.'),
|
|
210
|
-
limit: z.number().int().min(1).max(20).default(5).describe('Maximum results to return.'),
|
|
211
|
-
},
|
|
212
|
-
async ({ query, limit }) => {
|
|
213
|
-
await autoSnapshot.onToolCall('context_search')
|
|
214
|
-
try {
|
|
215
|
-
// Run semantic and full-text in parallel; merge by commit ID, dedup.
|
|
216
|
-
const [semantic, fts] = await Promise.all([
|
|
217
|
-
ctx.engine.semanticSearch(query, ctx.projectId, limit),
|
|
218
|
-
ctx.store.fullTextSearch(query, ctx.projectId),
|
|
219
|
-
])
|
|
220
|
-
const seen = new Set<string>()
|
|
221
|
-
const merged = [...semantic, ...fts].filter(r => {
|
|
222
|
-
if (seen.has(r.commit.id)) return false
|
|
223
|
-
seen.add(r.commit.id)
|
|
224
|
-
return true
|
|
225
|
-
})
|
|
226
|
-
const trimmed = merged.slice(0, limit)
|
|
227
|
-
|
|
228
|
-
if (trimmed.length === 0) {
|
|
229
|
-
return {
|
|
230
|
-
content: [{ type: 'text', text: 'No results found.' }],
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const formatted = trimmed
|
|
235
|
-
.map(
|
|
236
|
-
(r, i) =>
|
|
237
|
-
`[${i + 1}] ${r.commit.message}\n` +
|
|
238
|
-
` ID: ${r.commit.id} Score: ${r.score.toFixed(3)}\n` +
|
|
239
|
-
` ${r.commit.content.slice(0, 200)}${r.commit.content.length > 200 ? '…' : ''}`,
|
|
240
|
-
)
|
|
241
|
-
.join('\n\n')
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
content: [{ type: 'text', text: formatted }],
|
|
245
|
-
}
|
|
246
|
-
} catch (err) {
|
|
247
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
248
|
-
return {
|
|
249
|
-
content: [{ type: 'text', text: `Error searching commits: ${message}` }],
|
|
250
|
-
isError: true,
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
// ── context_branch ───────────────────────────────────────────────────────────
|
|
257
|
-
server.tool(
|
|
258
|
-
'context_branch',
|
|
259
|
-
'Create a new context branch tracking a git branch. Call this when starting work on a new git branch.',
|
|
260
|
-
{
|
|
261
|
-
git_branch: z.string().min(1).describe('The git branch name to track.'),
|
|
262
|
-
name: z.string().optional().describe('Optional display name for the context branch.'),
|
|
263
|
-
},
|
|
264
|
-
async ({ git_branch, name }) => {
|
|
265
|
-
await autoSnapshot.onToolCall('context_branch')
|
|
266
|
-
try {
|
|
267
|
-
const branch = await ctx.engine.branch(git_branch, name)
|
|
268
|
-
return {
|
|
269
|
-
content: [
|
|
270
|
-
{
|
|
271
|
-
type: 'text',
|
|
272
|
-
text: `Branch created.\nID: ${branch.id}\nName: ${branch.name}`,
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
}
|
|
276
|
-
} catch (err) {
|
|
277
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
278
|
-
return {
|
|
279
|
-
content: [{ type: 'text', text: `Error creating branch: ${message}` }],
|
|
280
|
-
isError: true,
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
// ── context_claim ────────────────────────────────────────────────────────────
|
|
287
|
-
server.tool(
|
|
288
|
-
'context_claim',
|
|
289
|
-
'Claim a task to prevent other agents from picking it up simultaneously. Call this before starting any significant task.',
|
|
290
|
-
{
|
|
291
|
-
task: z.string().min(1).describe('Short description of the task being claimed (e.g. "build auth module").'),
|
|
292
|
-
ttl_hours: z.number().positive().default(2).describe('Time-to-live in hours before the claim auto-expires. Default: 2.'),
|
|
293
|
-
status: z.enum(['proposed', 'active']).default('proposed').describe("'proposed' for plan-mode claims; 'active' once approved and work begins."),
|
|
294
|
-
},
|
|
295
|
-
async ({ task, ttl_hours, status }) => {
|
|
296
|
-
await autoSnapshot.onToolCall('context_claim')
|
|
297
|
-
try {
|
|
298
|
-
const claim = await ctx.store.claimTask(ctx.projectId, ctx.branchId, {
|
|
299
|
-
task,
|
|
300
|
-
agentId: `${os.hostname()}-mcp-claude-code-interactive`,
|
|
301
|
-
role: ctx.config.agentRole ?? 'solo',
|
|
302
|
-
status,
|
|
303
|
-
ttl: Math.round(ttl_hours * 3_600_000),
|
|
304
|
-
})
|
|
305
|
-
return {
|
|
306
|
-
content: [
|
|
307
|
-
{
|
|
308
|
-
type: 'text',
|
|
309
|
-
text: `Claim recorded.\nID: ${claim.id}\nTask: ${claim.task}\nStatus: ${claim.status}\nTTL: ${ttl_hours}h`,
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
}
|
|
313
|
-
} catch (err) {
|
|
314
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
315
|
-
return {
|
|
316
|
-
content: [{ type: 'text', text: `Error creating claim: ${message}` }],
|
|
317
|
-
isError: true,
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
// ── context_unclaim ──────────────────────────────────────────────────────────
|
|
324
|
-
server.tool(
|
|
325
|
-
'context_unclaim',
|
|
326
|
-
'Release a previously claimed task. Use when abandoning a task without committing.',
|
|
327
|
-
{
|
|
328
|
-
claim_id: z.string().min(1).describe('ID of the claim to release.'),
|
|
329
|
-
},
|
|
330
|
-
async ({ claim_id }) => {
|
|
331
|
-
await autoSnapshot.onToolCall('context_unclaim')
|
|
332
|
-
try {
|
|
333
|
-
await ctx.store.unclaimTask(claim_id)
|
|
334
|
-
return {
|
|
335
|
-
content: [{ type: 'text', text: `Claim released.\nID: ${claim_id}` }],
|
|
336
|
-
}
|
|
337
|
-
} catch (err) {
|
|
338
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
339
|
-
return {
|
|
340
|
-
content: [{ type: 'text', text: `Error releasing claim: ${message}` }],
|
|
341
|
-
isError: true,
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
// ── context_merge ────────────────────────────────────────────────────────────
|
|
348
|
-
server.tool(
|
|
349
|
-
'context_merge',
|
|
350
|
-
'Merge a source context branch into the current branch. Call this after merging a git branch.',
|
|
351
|
-
{
|
|
352
|
-
source_branch_id: z.string().min(1).describe('ID of the context branch to merge in.'),
|
|
353
|
-
},
|
|
354
|
-
async ({ source_branch_id }) => {
|
|
355
|
-
await autoSnapshot.onToolCall('context_merge')
|
|
356
|
-
try {
|
|
357
|
-
const commit = await ctx.engine.merge(source_branch_id)
|
|
358
|
-
return {
|
|
359
|
-
content: [
|
|
360
|
-
{
|
|
361
|
-
type: 'text',
|
|
362
|
-
text: `Merge commit recorded.\nID: ${commit.id}`,
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
}
|
|
366
|
-
} catch (err) {
|
|
367
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
368
|
-
return {
|
|
369
|
-
content: [{ type: 'text', text: `Error merging branch: ${message}` }],
|
|
370
|
-
isError: true,
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
return server
|
|
377
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@contextgit/store",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
15
|
-
"test": "vitest run",
|
|
16
|
-
"typecheck": "tsc --noEmit"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@contextgit/core": "workspace:*",
|
|
20
|
-
"better-sqlite3": "^9.4.0",
|
|
21
|
-
"nanoid": "^5.0.0",
|
|
22
|
-
"sqlite-vec": "^0.1.6"
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@types/better-sqlite3": "^7.6.0",
|
|
26
|
-
"@types/node": "^20.0.0",
|
|
27
|
-
"typescript": "^5.4.0",
|
|
28
|
-
"vitest": "^1.6.0"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// Days 12–13 integration test: engine.branch() + engine.merge()
|
|
2
|
-
//
|
|
3
|
-
// Scenario: main → commit + thread → branch('feature') → 2 commits + thread
|
|
4
|
-
// → merge back to main → snapshot shows merged state + all threads.
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
7
|
-
import { ContextEngine } from '@contextgit/core'
|
|
8
|
-
import { LocalStore } from './local/index.js'
|
|
9
|
-
|
|
10
|
-
describe('engine.branch() + engine.merge()', () => {
|
|
11
|
-
let store: LocalStore
|
|
12
|
-
let mainEngine: ContextEngine
|
|
13
|
-
let featureEngine: ContextEngine
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
store = new LocalStore(':memory:')
|
|
17
|
-
mainEngine = new ContextEngine(store, 'agent-main', 'orchestrator', 'claude-code', 'interactive')
|
|
18
|
-
featureEngine = new ContextEngine(store, 'agent-feat', 'dev', 'claude-code', 'interactive')
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
store.close()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('branch → 2 commits → merge → snapshot shows merged state and all threads', async () => {
|
|
26
|
-
// 1. Setup: project + main branch
|
|
27
|
-
const project = await store.createProject({ name: 'contextgit' })
|
|
28
|
-
const mainBranch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
29
|
-
await mainEngine.init(project.id, mainBranch.id)
|
|
30
|
-
|
|
31
|
-
// 2. Commit on main with an open thread
|
|
32
|
-
await mainEngine.commit({
|
|
33
|
-
message: 'Initial main commit',
|
|
34
|
-
content: 'Set up project foundation.',
|
|
35
|
-
threads: { open: ['Choose DB migration strategy'] },
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// 3. Branch off main
|
|
39
|
-
const featureBranch = await mainEngine.branch('feature/auth', 'auth feature')
|
|
40
|
-
|
|
41
|
-
expect(featureBranch.gitBranch).toBe('feature/auth')
|
|
42
|
-
expect(featureBranch.name).toBe('auth feature')
|
|
43
|
-
expect(featureBranch.parentBranchId).toBe(mainBranch.id)
|
|
44
|
-
expect(featureBranch.status).toBe('active')
|
|
45
|
-
|
|
46
|
-
// feature branch should have a branch-init commit with main's summary
|
|
47
|
-
const featureCommits = await store.listCommits(featureBranch.id, { limit: 10, offset: 0 })
|
|
48
|
-
expect(featureCommits).toHaveLength(1)
|
|
49
|
-
expect(featureCommits[0].commitType).toBe('branch-init')
|
|
50
|
-
expect(featureCommits[0].summary).toBeTruthy() // carried parent summary
|
|
51
|
-
|
|
52
|
-
// 4. Two commits on feature branch (one with its own thread)
|
|
53
|
-
await featureEngine.init(project.id, featureBranch.id)
|
|
54
|
-
|
|
55
|
-
await featureEngine.commit({
|
|
56
|
-
message: 'Add auth middleware',
|
|
57
|
-
content: 'Implemented JWT middleware and route guards.',
|
|
58
|
-
threads: { open: ['Token expiry policy TBD'] },
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const lastFeatureCommit = await featureEngine.commit({
|
|
62
|
-
message: 'Add login endpoint',
|
|
63
|
-
content: 'POST /auth/login with bcrypt password verification.',
|
|
64
|
-
})
|
|
65
|
-
expect(lastFeatureCommit.summary).toContain('bcrypt')
|
|
66
|
-
|
|
67
|
-
// 5. Merge feature → main
|
|
68
|
-
const mergeCommit = await mainEngine.merge(featureBranch.id)
|
|
69
|
-
|
|
70
|
-
expect(mergeCommit.commitType).toBe('merge')
|
|
71
|
-
expect(mergeCommit.branchId).toBe(mainBranch.id)
|
|
72
|
-
expect(mergeCommit.mergeSourceBranchId).toBe(featureBranch.id)
|
|
73
|
-
|
|
74
|
-
// 6. Source branch should be marked merged
|
|
75
|
-
const mergedBranch = await store.getBranch(featureBranch.id)
|
|
76
|
-
expect(mergedBranch?.status).toBe('merged')
|
|
77
|
-
expect(mergedBranch?.mergedAt).toBeTruthy()
|
|
78
|
-
|
|
79
|
-
// 7. Snapshot on main should show merged state
|
|
80
|
-
const snapshot = await mainEngine.context('global')
|
|
81
|
-
|
|
82
|
-
expect(snapshot.branchName).toBe('main')
|
|
83
|
-
// recent commits: merge commit is the newest
|
|
84
|
-
expect(snapshot.recentCommits[0].commitType).toBe('merge')
|
|
85
|
-
// open threads: both the main thread and the feature thread carried forward
|
|
86
|
-
const threadDescriptions = snapshot.openThreads.map(t => t.description)
|
|
87
|
-
expect(threadDescriptions).toContain('Choose DB migration strategy')
|
|
88
|
-
expect(threadDescriptions).toContain('Token expiry policy TBD')
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('branch-init commit carries parent HEAD summary into new branch', async () => {
|
|
92
|
-
const project = await store.createProject({ name: 'p' })
|
|
93
|
-
const mainBranch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
94
|
-
await mainEngine.init(project.id, mainBranch.id)
|
|
95
|
-
|
|
96
|
-
await mainEngine.commit({ message: 'setup', content: 'foundation work done' })
|
|
97
|
-
|
|
98
|
-
const feat = await mainEngine.branch('feature/x')
|
|
99
|
-
|
|
100
|
-
// branch-init summary should match main's HEAD summary
|
|
101
|
-
const mainHead = await store.getBranch(mainBranch.id)
|
|
102
|
-
const mainHeadCommit = mainHead?.headCommitId
|
|
103
|
-
? await store.getCommit(mainHead.headCommitId)
|
|
104
|
-
: null
|
|
105
|
-
|
|
106
|
-
const featCommits = await store.listCommits(feat.id, { limit: 1, offset: 0 })
|
|
107
|
-
expect(featCommits[0].summary).toBe(mainHeadCommit?.summary)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('merge summary incorporates source branch content', async () => {
|
|
111
|
-
const project = await store.createProject({ name: 'p' })
|
|
112
|
-
const mainBranch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
113
|
-
await mainEngine.init(project.id, mainBranch.id)
|
|
114
|
-
|
|
115
|
-
await mainEngine.commit({ message: 'base', content: 'base content' })
|
|
116
|
-
|
|
117
|
-
const feat = await mainEngine.branch('feature/y')
|
|
118
|
-
await featureEngine.init(project.id, feat.id)
|
|
119
|
-
await featureEngine.commit({ message: 'feature work', content: 'feature specific content' })
|
|
120
|
-
|
|
121
|
-
const mergeCommit = await mainEngine.merge(feat.id)
|
|
122
|
-
|
|
123
|
-
// merge commit summary should reference feature content
|
|
124
|
-
expect(mergeCommit.summary).toBeTruthy()
|
|
125
|
-
expect(mergeCommit.content).toContain('feature specific content')
|
|
126
|
-
})
|
|
127
|
-
})
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Week 1 validation: ContextEngine + LocalStore end-to-end.
|
|
2
|
-
//
|
|
3
|
-
// Scenario: create project → 2 commits (one with open thread) →
|
|
4
|
-
// context('global') → snapshot shows thread + both commits.
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
7
|
-
import { ContextEngine } from '@contextgit/core'
|
|
8
|
-
import { LocalStore } from './local/index.js'
|
|
9
|
-
|
|
10
|
-
describe('ContextEngine + LocalStore integration', () => {
|
|
11
|
-
let store: LocalStore
|
|
12
|
-
let engine: ContextEngine
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
store = new LocalStore(':memory:')
|
|
16
|
-
engine = new ContextEngine(store, 'agent-1', 'dev', 'claude-code', 'interactive')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
store.close()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('Week 1 validation: project → 2 commits → context("global") shows thread + both commits', async () => {
|
|
24
|
-
// Setup: project + branch
|
|
25
|
-
const project = await store.createProject({ name: 'contextgit', description: 'Persistent memory layer' })
|
|
26
|
-
const branch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
27
|
-
|
|
28
|
-
await engine.init(project.id, branch.id)
|
|
29
|
-
|
|
30
|
-
// Commit 1 — opens a thread
|
|
31
|
-
const c1 = await engine.commit({
|
|
32
|
-
message: 'Set up monorepo structure',
|
|
33
|
-
content: 'Created pnpm workspaces, tsconfig.base.json, core and store packages.',
|
|
34
|
-
threads: { open: ['Database migration strategy not decided'] },
|
|
35
|
-
})
|
|
36
|
-
expect(c1.id).toBeTruthy()
|
|
37
|
-
expect(c1.summary).toBeTruthy()
|
|
38
|
-
expect(c1.agentRole).toBe('dev')
|
|
39
|
-
|
|
40
|
-
// Commit 2 — no thread changes; summary should roll forward
|
|
41
|
-
const c2 = await engine.commit({
|
|
42
|
-
message: 'Add LocalStore implementation',
|
|
43
|
-
content: 'Implemented LocalStore with better-sqlite3, schema, migrations, queries.',
|
|
44
|
-
})
|
|
45
|
-
expect(c2.id).toBeTruthy()
|
|
46
|
-
// Rolling summary must include the new content
|
|
47
|
-
expect(c2.summary).toContain('LocalStore')
|
|
48
|
-
|
|
49
|
-
// context('global') → SessionSnapshot
|
|
50
|
-
const snapshot = await engine.context('global')
|
|
51
|
-
|
|
52
|
-
expect(snapshot.branchName).toBe('main')
|
|
53
|
-
expect(snapshot.recentCommits).toHaveLength(2)
|
|
54
|
-
expect(snapshot.recentCommits[0].message).toBe('Add LocalStore implementation')
|
|
55
|
-
expect(snapshot.recentCommits[1].message).toBe('Set up monorepo structure')
|
|
56
|
-
|
|
57
|
-
// Open thread from commit 1 must survive commit 2
|
|
58
|
-
expect(snapshot.openThreads).toHaveLength(1)
|
|
59
|
-
expect(snapshot.openThreads[0].description).toBe('Database migration strategy not decided')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('summarizer rolls content into previous summary', async () => {
|
|
63
|
-
const project = await store.createProject({ name: 'p' })
|
|
64
|
-
const branch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
65
|
-
await engine.init(project.id, branch.id)
|
|
66
|
-
|
|
67
|
-
const c1 = await engine.commit({ message: 'first', content: 'alpha content' })
|
|
68
|
-
const c2 = await engine.commit({ message: 'second', content: 'beta content' })
|
|
69
|
-
|
|
70
|
-
// c2 summary must contain both pieces (budget large enough for test data)
|
|
71
|
-
expect(c2.summary).toContain('alpha content')
|
|
72
|
-
expect(c2.summary).toContain('beta content')
|
|
73
|
-
// c1 summary should be just the first content
|
|
74
|
-
expect(c1.summary).toBe('alpha content')
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('context("branch") returns the same snapshot as context("global")', async () => {
|
|
78
|
-
const project = await store.createProject({ name: 'p' })
|
|
79
|
-
const branch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
80
|
-
await engine.init(project.id, branch.id)
|
|
81
|
-
await engine.commit({ message: 'm', content: 'c' })
|
|
82
|
-
|
|
83
|
-
const global = await engine.context('global')
|
|
84
|
-
const branchCtx = await engine.context('branch')
|
|
85
|
-
expect(branchCtx.branchName).toBe(global.branchName)
|
|
86
|
-
expect(branchCtx.recentCommits).toHaveLength(global.recentCommits.length)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('throws if context() called before init()', async () => {
|
|
90
|
-
const uninitEngine = new ContextEngine(store, 'a', 'solo', 'cli', 'interactive')
|
|
91
|
-
await expect(uninitEngine.context('global')).rejects.toThrow('not initialized')
|
|
92
|
-
})
|
|
93
|
-
})
|