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,164 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { LocalStore } from './index.js'
|
|
3
|
+
|
|
4
|
+
describe('LocalStore (in-memory)', () => {
|
|
5
|
+
let store: LocalStore
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
store = new LocalStore(':memory:')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
store.close()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('creates and retrieves a project', async () => {
|
|
16
|
+
const project = await store.createProject({ name: 'test-project', description: 'A test' })
|
|
17
|
+
expect(project.id).toBeTruthy()
|
|
18
|
+
expect(project.name).toBe('test-project')
|
|
19
|
+
|
|
20
|
+
const fetched = await store.getProject(project.id)
|
|
21
|
+
expect(fetched).toBeTruthy()
|
|
22
|
+
expect(fetched!.name).toBe('test-project')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('creates and retrieves a branch', async () => {
|
|
26
|
+
const project = await store.createProject({ name: 'p' })
|
|
27
|
+
const branch = await store.createBranch({
|
|
28
|
+
projectId: project.id,
|
|
29
|
+
name: 'main',
|
|
30
|
+
gitBranch: 'main',
|
|
31
|
+
})
|
|
32
|
+
expect(branch.id).toBeTruthy()
|
|
33
|
+
expect(branch.gitBranch).toBe('main')
|
|
34
|
+
|
|
35
|
+
const byGit = await store.getBranchByGitName(project.id, 'main')
|
|
36
|
+
expect(byGit!.id).toBe(branch.id)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('creates a commit, opens a thread, closes it', async () => {
|
|
40
|
+
const project = await store.createProject({ name: 'p' })
|
|
41
|
+
const branch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
42
|
+
|
|
43
|
+
// Upsert agent first (required for commit attribution)
|
|
44
|
+
await store.upsertAgent({
|
|
45
|
+
id: 'agent-1',
|
|
46
|
+
projectId: project.id,
|
|
47
|
+
role: 'dev',
|
|
48
|
+
tool: 'claude-code',
|
|
49
|
+
workflowType: 'interactive',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const commit1 = await store.createCommit({
|
|
53
|
+
branchId: branch.id,
|
|
54
|
+
agentId: 'agent-1',
|
|
55
|
+
agentRole: 'dev',
|
|
56
|
+
tool: 'claude-code',
|
|
57
|
+
workflowType: 'interactive',
|
|
58
|
+
message: 'Initial commit',
|
|
59
|
+
content: 'Set up project structure',
|
|
60
|
+
summary: 'Project scaffolded',
|
|
61
|
+
commitType: 'manual',
|
|
62
|
+
threads: { open: ['Authentication approach not decided'] },
|
|
63
|
+
})
|
|
64
|
+
expect(commit1.id).toBeTruthy()
|
|
65
|
+
expect(commit1.message).toBe('Initial commit')
|
|
66
|
+
|
|
67
|
+
// Thread should be open
|
|
68
|
+
const openThreads = await store.listOpenThreads(project.id)
|
|
69
|
+
expect(openThreads).toHaveLength(1)
|
|
70
|
+
expect(openThreads[0].description).toBe('Authentication approach not decided')
|
|
71
|
+
const threadId = openThreads[0].id
|
|
72
|
+
|
|
73
|
+
// Second commit closes the thread
|
|
74
|
+
const commit2 = await store.createCommit({
|
|
75
|
+
branchId: branch.id,
|
|
76
|
+
agentId: 'agent-1',
|
|
77
|
+
agentRole: 'dev',
|
|
78
|
+
tool: 'claude-code',
|
|
79
|
+
workflowType: 'interactive',
|
|
80
|
+
message: 'Choose JWT auth',
|
|
81
|
+
content: 'Implemented JWT RS256',
|
|
82
|
+
summary: 'JWT RS256 chosen, implemented',
|
|
83
|
+
commitType: 'manual',
|
|
84
|
+
threads: { close: [{ id: threadId, note: 'Using JWT RS256' }] },
|
|
85
|
+
})
|
|
86
|
+
expect(commit2.id).toBeTruthy()
|
|
87
|
+
|
|
88
|
+
const stillOpen = await store.listOpenThreads(project.id)
|
|
89
|
+
expect(stillOpen).toHaveLength(0)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('getSessionSnapshot returns project/branch summaries and threads', async () => {
|
|
93
|
+
const project = await store.createProject({ name: 'snap-test' })
|
|
94
|
+
const main = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
95
|
+
const feature = await store.createBranch({
|
|
96
|
+
projectId: project.id,
|
|
97
|
+
name: 'feature/auth',
|
|
98
|
+
gitBranch: 'feature/auth',
|
|
99
|
+
parentBranchId: main.id,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
await store.upsertAgent({ id: 'ag', projectId: project.id, role: 'dev', tool: 'cli', workflowType: 'interactive' })
|
|
103
|
+
|
|
104
|
+
await store.createCommit({
|
|
105
|
+
branchId: main.id, agentId: 'ag', agentRole: 'dev', tool: 'cli',
|
|
106
|
+
workflowType: 'interactive', message: 'main commit', content: 'content',
|
|
107
|
+
summary: 'Main branch summary', commitType: 'manual',
|
|
108
|
+
})
|
|
109
|
+
await store.createCommit({
|
|
110
|
+
branchId: feature.id, agentId: 'ag', agentRole: 'dev', tool: 'cli',
|
|
111
|
+
workflowType: 'interactive', message: 'feature commit', content: 'feature content',
|
|
112
|
+
summary: 'Feature branch summary', commitType: 'manual',
|
|
113
|
+
threads: { open: ['Open thread on feature'] },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const snapshot = await store.getSessionSnapshot(project.id, feature.id)
|
|
117
|
+
expect(snapshot.projectSummary).toBe('Main branch summary')
|
|
118
|
+
expect(snapshot.branchSummary).toBe('Feature branch summary')
|
|
119
|
+
expect(snapshot.branchName).toBe('feature/auth')
|
|
120
|
+
expect(snapshot.recentCommits).toHaveLength(1)
|
|
121
|
+
expect(snapshot.openThreads).toHaveLength(1)
|
|
122
|
+
expect(snapshot.openThreads[0].description).toBe('Open thread on feature')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('getFormattedSnapshot returns text format', async () => {
|
|
126
|
+
const project = await store.createProject({ name: 'fmt-test' })
|
|
127
|
+
const branch = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
128
|
+
const text = await store.getFormattedSnapshot(project.id, branch.id, 'text')
|
|
129
|
+
expect(text).toContain('=== PROJECT STATE ===')
|
|
130
|
+
expect(text).toContain('=== CURRENT BRANCH: main ===')
|
|
131
|
+
expect(text).toContain('=== OPEN THREADS ===')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('mergeBranch carries open threads to target and marks source merged', async () => {
|
|
135
|
+
const project = await store.createProject({ name: 'merge-test' })
|
|
136
|
+
const main = await store.createBranch({ projectId: project.id, name: 'main', gitBranch: 'main' })
|
|
137
|
+
const feat = await store.createBranch({ projectId: project.id, name: 'feat', gitBranch: 'feat', parentBranchId: main.id })
|
|
138
|
+
|
|
139
|
+
await store.upsertAgent({ id: 'ag', projectId: project.id, role: 'dev', tool: 'cli', workflowType: 'interactive' })
|
|
140
|
+
|
|
141
|
+
await store.createCommit({
|
|
142
|
+
branchId: main.id, agentId: 'ag', agentRole: 'dev', tool: 'cli',
|
|
143
|
+
workflowType: 'interactive', message: 'main init', content: 'c', summary: 's', commitType: 'branch-init',
|
|
144
|
+
})
|
|
145
|
+
await store.createCommit({
|
|
146
|
+
branchId: feat.id, agentId: 'ag', agentRole: 'dev', tool: 'cli',
|
|
147
|
+
workflowType: 'interactive', message: 'feat work', content: 'c', summary: 's', commitType: 'manual',
|
|
148
|
+
threads: { open: ['Thread from feat'] },
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
const mergeCommit = await store.mergeBranch(feat.id, main.id, 'Merged feat into main')
|
|
152
|
+
expect(mergeCommit.commitType).toBe('merge')
|
|
153
|
+
expect(mergeCommit.branchId).toBe(main.id)
|
|
154
|
+
|
|
155
|
+
// Source branch is merged
|
|
156
|
+
const updatedFeat = await store.getBranch(feat.id)
|
|
157
|
+
expect(updatedFeat!.status).toBe('merged')
|
|
158
|
+
|
|
159
|
+
// Thread carried to main
|
|
160
|
+
const mainThreads = await store.listOpenThreadsByBranch(main.id)
|
|
161
|
+
expect(mainThreads).toHaveLength(1)
|
|
162
|
+
expect(mainThreads[0].description).toBe('Thread from feat')
|
|
163
|
+
})
|
|
164
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Database } from 'better-sqlite3'
|
|
2
|
+
import { SCHEMA_V1_DDL, SCHEMA_V2_DDL, SCHEMA_V3_DDL, SCHEMA_V4_DDL, CREATE_COMMIT_EMBEDDINGS } from './schema.js'
|
|
3
|
+
|
|
4
|
+
interface MigrationRow {
|
|
5
|
+
version: number
|
|
6
|
+
name: string
|
|
7
|
+
applied_at: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const MIGRATIONS_TABLE = `
|
|
11
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
12
|
+
version INTEGER PRIMARY KEY,
|
|
13
|
+
name TEXT NOT NULL,
|
|
14
|
+
applied_at INTEGER NOT NULL
|
|
15
|
+
)
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
interface Migration {
|
|
19
|
+
version: number
|
|
20
|
+
name: string
|
|
21
|
+
run(db: Database): void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const MIGRATIONS: Migration[] = [
|
|
25
|
+
{
|
|
26
|
+
version: 1,
|
|
27
|
+
name: 'initial_schema',
|
|
28
|
+
run(db) {
|
|
29
|
+
for (const sql of SCHEMA_V1_DDL) {
|
|
30
|
+
db.exec(sql)
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
version: 2,
|
|
36
|
+
name: 'fts5_and_vectors',
|
|
37
|
+
run(db) {
|
|
38
|
+
// FTS5 full-text index
|
|
39
|
+
for (const sql of SCHEMA_V2_DDL) {
|
|
40
|
+
db.exec(sql)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// sqlite-vec virtual table — optional, skipped if extension not loaded
|
|
44
|
+
try {
|
|
45
|
+
db.exec(CREATE_COMMIT_EMBEDDINGS)
|
|
46
|
+
} catch {
|
|
47
|
+
// sqlite-vec not loaded; semantic search will return empty results
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
version: 3,
|
|
53
|
+
name: 'fts_trigger',
|
|
54
|
+
run(db) {
|
|
55
|
+
// Add INSERT trigger to keep commits_fts in sync with commits table.
|
|
56
|
+
// Then rebuild the index to pick up any rows inserted before this migration.
|
|
57
|
+
for (const sql of SCHEMA_V3_DDL) {
|
|
58
|
+
db.exec(sql)
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
db.exec(`INSERT INTO commits_fts(commits_fts) VALUES('rebuild')`)
|
|
62
|
+
} catch {
|
|
63
|
+
// FTS5 table not available — skip rebuild
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
version: 4,
|
|
69
|
+
name: 'claims_table',
|
|
70
|
+
run(db) {
|
|
71
|
+
for (const sql of SCHEMA_V4_DDL) {
|
|
72
|
+
db.exec(sql)
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
export function runMigrations(db: Database): void {
|
|
79
|
+
// Ensure migrations table exists
|
|
80
|
+
db.exec(MIGRATIONS_TABLE)
|
|
81
|
+
|
|
82
|
+
const getApplied = db.prepare<[], MigrationRow>(
|
|
83
|
+
'SELECT version, name, applied_at FROM _migrations ORDER BY version'
|
|
84
|
+
)
|
|
85
|
+
const insertMigration = db.prepare(
|
|
86
|
+
'INSERT INTO _migrations (version, name, applied_at) VALUES (?, ?, ?)'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const applied = new Set(getApplied.all().map((r) => r.version))
|
|
90
|
+
|
|
91
|
+
for (const migration of MIGRATIONS) {
|
|
92
|
+
if (applied.has(migration.version)) continue
|
|
93
|
+
|
|
94
|
+
db.transaction(() => {
|
|
95
|
+
migration.run(db)
|
|
96
|
+
insertMigration.run(migration.version, migration.name, Date.now())
|
|
97
|
+
})()
|
|
98
|
+
}
|
|
99
|
+
}
|