contextgit 0.1.1 → 0.1.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/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +53 -74
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/init-helpers.d.ts +2 -1
- package/dist/lib/init-helpers.d.ts.map +1 -1
- package/dist/lib/init-helpers.js +9 -6
- package/dist/lib/init-helpers.js.map +1 -1
- package/dist/lib/init-helpers.test.js +67 -1
- package/dist/lib/init-helpers.test.js.map +1 -1
- package/node_modules/@contextgit/mcp/dist/auto-snapshot.d.ts +30 -0
- package/node_modules/@contextgit/mcp/dist/auto-snapshot.d.ts.map +1 -0
- package/node_modules/@contextgit/mcp/dist/auto-snapshot.js +68 -0
- package/node_modules/@contextgit/mcp/dist/auto-snapshot.js.map +1 -0
- package/node_modules/@contextgit/mcp/dist/config.d.ts +17 -0
- package/node_modules/@contextgit/mcp/dist/config.d.ts.map +1 -0
- package/node_modules/@contextgit/mcp/dist/config.js +48 -0
- package/node_modules/@contextgit/mcp/dist/config.js.map +1 -0
- package/node_modules/@contextgit/mcp/dist/git-sync.d.ts +15 -0
- package/node_modules/@contextgit/mcp/dist/git-sync.d.ts.map +1 -0
- package/node_modules/@contextgit/mcp/dist/git-sync.js +87 -0
- package/node_modules/@contextgit/mcp/dist/git-sync.js.map +1 -0
- package/node_modules/@contextgit/mcp/dist/index.d.ts +3 -0
- package/node_modules/@contextgit/mcp/dist/index.d.ts.map +1 -0
- package/node_modules/@contextgit/mcp/dist/index.js +17 -0
- package/node_modules/@contextgit/mcp/dist/index.js.map +1 -0
- package/node_modules/@contextgit/mcp/dist/server.d.ts +3 -0
- package/node_modules/@contextgit/mcp/dist/server.d.ts.map +1 -0
- package/node_modules/@contextgit/mcp/dist/server.js +377 -0
- package/node_modules/@contextgit/mcp/dist/server.js.map +1 -0
- package/node_modules/@contextgit/mcp/package.json +31 -0
- package/node_modules/@contextgit/mcp/src/auto-snapshot.ts +83 -0
- package/node_modules/@contextgit/mcp/src/config.ts +53 -0
- package/node_modules/@contextgit/mcp/src/git-sync.ts +94 -0
- package/node_modules/@contextgit/mcp/src/index.ts +19 -0
- package/node_modules/@contextgit/mcp/src/server.ts +568 -0
- package/node_modules/@contextgit/mcp/tsconfig.json +9 -0
- package/package.json +13 -10
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// config.ts — load and validate .contextgit/config.json
|
|
2
|
+
// Searches from CWD upwards until it finds the config file.
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'fs'
|
|
5
|
+
import { join, dirname } from 'path'
|
|
6
|
+
import type { ContextGitConfig } from '@contextgit/core'
|
|
7
|
+
|
|
8
|
+
export class ConfigNotFoundError extends Error {
|
|
9
|
+
constructor(startDir: string) {
|
|
10
|
+
super(`No .contextgit/config.json found searching upward from: ${startDir}`)
|
|
11
|
+
this.name = 'ConfigNotFoundError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Search upward from `startDir` for `.contextgit/config.json`.
|
|
17
|
+
* Returns the first match found, or throws ConfigNotFoundError.
|
|
18
|
+
*/
|
|
19
|
+
export function findConfigPath(startDir: string = process.cwd()): string {
|
|
20
|
+
let current = startDir
|
|
21
|
+
while (true) {
|
|
22
|
+
const candidate = join(current, '.contextgit', 'config.json')
|
|
23
|
+
try {
|
|
24
|
+
readFileSync(candidate)
|
|
25
|
+
return candidate
|
|
26
|
+
} catch {
|
|
27
|
+
const parent = dirname(current)
|
|
28
|
+
if (parent === current) {
|
|
29
|
+
throw new ConfigNotFoundError(startDir)
|
|
30
|
+
}
|
|
31
|
+
current = parent
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load and parse `.contextgit/config.json`.
|
|
38
|
+
* Throws ConfigNotFoundError if not found, or Error if JSON is invalid.
|
|
39
|
+
*/
|
|
40
|
+
export function loadConfig(startDir?: string): ContextGitConfig & { configDir: string } {
|
|
41
|
+
const configPath = findConfigPath(startDir)
|
|
42
|
+
const raw = readFileSync(configPath, 'utf-8')
|
|
43
|
+
const config = JSON.parse(raw) as ContextGitConfig
|
|
44
|
+
|
|
45
|
+
if (!config.projectId) {
|
|
46
|
+
throw new Error(`Invalid config at ${configPath}: missing required field 'projectId'`)
|
|
47
|
+
}
|
|
48
|
+
if (!config.project) {
|
|
49
|
+
throw new Error(`Invalid config at ${configPath}: missing required field 'project'`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { ...config, configDir: dirname(configPath) }
|
|
53
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// git-sync.ts — git metadata capture and hook installation.
|
|
2
|
+
//
|
|
3
|
+
// captureGitMetadata: used by context_commit (MCP) and commit CLI command to
|
|
4
|
+
// auto-populate gitCommitSha on every context commit.
|
|
5
|
+
//
|
|
6
|
+
// installGitHooks: idempotent hook installer — writes post-commit,
|
|
7
|
+
// post-checkout, post-merge scripts into .git/hooks/.
|
|
8
|
+
|
|
9
|
+
import { writeFileSync, readFileSync, mkdirSync, existsSync, appendFileSync } from 'fs'
|
|
10
|
+
import { join, resolve } from 'path'
|
|
11
|
+
import { homedir } from 'os'
|
|
12
|
+
import { simpleGit } from 'simple-git'
|
|
13
|
+
|
|
14
|
+
const SENTINEL = '# contextgit'
|
|
15
|
+
const HOOKS_LOG = join(homedir(), '.contextgit', 'hooks.log')
|
|
16
|
+
|
|
17
|
+
// ─── captureGitMetadata ────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Capture the current git commit SHA and branch name.
|
|
21
|
+
* Returns null on any error — must never block a context commit.
|
|
22
|
+
*/
|
|
23
|
+
export async function captureGitMetadata(
|
|
24
|
+
cwd: string,
|
|
25
|
+
): Promise<{ sha: string; branch: string } | null> {
|
|
26
|
+
try {
|
|
27
|
+
const git = simpleGit(cwd)
|
|
28
|
+
const [sha, branch] = await Promise.all([
|
|
29
|
+
git.revparse(['HEAD']),
|
|
30
|
+
git.revparse(['--abbrev-ref', 'HEAD']),
|
|
31
|
+
])
|
|
32
|
+
return { sha: sha.trim(), branch: branch.trim() }
|
|
33
|
+
} catch {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── installGitHooks ──────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const HOOK_SCRIPTS: Record<string, string> = {
|
|
41
|
+
'post-commit': `#!/bin/sh
|
|
42
|
+
${SENTINEL}
|
|
43
|
+
contextgit commit -m "git: $(git log -1 --pretty=%s)" 2>>"${HOOKS_LOG}" || true
|
|
44
|
+
`,
|
|
45
|
+
'post-checkout': `#!/bin/sh
|
|
46
|
+
${SENTINEL}
|
|
47
|
+
contextgit context --quiet 2>>"${HOOKS_LOG}" || true
|
|
48
|
+
`,
|
|
49
|
+
'post-merge': `#!/bin/sh
|
|
50
|
+
${SENTINEL}
|
|
51
|
+
contextgit commit -m "Merged into $(git rev-parse --abbrev-ref HEAD)" 2>>"${HOOKS_LOG}" || true
|
|
52
|
+
`,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Install contextgit git hooks into <projectRoot>/.git/hooks/.
|
|
57
|
+
* Idempotent: checks for the sentinel comment before appending.
|
|
58
|
+
* Hook failures are logged to ~/.contextgit/hooks.log — never to stderr.
|
|
59
|
+
*/
|
|
60
|
+
export async function installGitHooks(projectRoot: string): Promise<void> {
|
|
61
|
+
const hooksDir = join(resolve(projectRoot), '.git', 'hooks')
|
|
62
|
+
mkdirSync(hooksDir, { recursive: true })
|
|
63
|
+
|
|
64
|
+
for (const [hookName, script] of Object.entries(HOOK_SCRIPTS)) {
|
|
65
|
+
const hookPath = join(hooksDir, hookName)
|
|
66
|
+
|
|
67
|
+
if (existsSync(hookPath)) {
|
|
68
|
+
const existing = readFileSync(hookPath, 'utf-8')
|
|
69
|
+
if (existing.includes(SENTINEL)) continue // already installed
|
|
70
|
+
// Append to existing hook
|
|
71
|
+
writeFileSync(hookPath, existing.trimEnd() + '\n\n' + script)
|
|
72
|
+
} else {
|
|
73
|
+
writeFileSync(hookPath, script)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Make executable (chmod +x)
|
|
77
|
+
try {
|
|
78
|
+
const { chmodSync } = await import('fs')
|
|
79
|
+
chmodSync(hookPath, 0o755)
|
|
80
|
+
} catch {
|
|
81
|
+
logHookError(`chmod failed for ${hookPath}`)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function logHookError(msg: string): void {
|
|
87
|
+
try {
|
|
88
|
+
const dir = join(homedir(), '.contextgit')
|
|
89
|
+
mkdirSync(dir, { recursive: true })
|
|
90
|
+
appendFileSync(HOOKS_LOG, `[${new Date().toISOString()}] ${msg}\n`)
|
|
91
|
+
} catch {
|
|
92
|
+
// truly silent
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// index.ts — entry point for the ContextGit MCP server process.
|
|
3
|
+
// Started by the MCP host (Claude Desktop / Claude Code) via stdio.
|
|
4
|
+
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
6
|
+
import { createServer } from './server.js'
|
|
7
|
+
|
|
8
|
+
async function main(): Promise<void> {
|
|
9
|
+
const server = await createServer()
|
|
10
|
+
const transport = new StdioServerTransport()
|
|
11
|
+
await server.connect(transport)
|
|
12
|
+
// Server is now listening on stdin/stdout — process stays alive until the
|
|
13
|
+
// host closes the connection.
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
main().catch(err => {
|
|
17
|
+
console.error('[contextgit-mcp] Fatal error:', err)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
})
|