session-collab-mcp 0.4.7 → 0.5.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.
@@ -39,8 +39,7 @@ class SqlitePreparedStatement implements PreparedStatement {
39
39
 
40
40
  constructor(
41
41
  private db: Database.Database,
42
- private sql: string,
43
- private onWrite?: () => void
42
+ private sql: string
44
43
  ) {}
45
44
 
46
45
  bind(...values: unknown[]): PreparedStatement {
@@ -66,8 +65,8 @@ class SqlitePreparedStatement implements PreparedStatement {
66
65
  async run(): Promise<{ meta: { changes: number } }> {
67
66
  const stmt = this.db.prepare(this.sql);
68
67
  const result = stmt.run(...this.bindings);
69
- // Trigger checkpoint after write
70
- this.onWrite?.();
68
+ // Note: wal_autocheckpoint handles periodic checkpoints automatically
69
+ // No need to checkpoint after every write - reduces I/O overhead
71
70
  return {
72
71
  meta: { changes: result.changes },
73
72
  };
@@ -104,12 +103,13 @@ class SqliteDatabase implements DatabaseAdapter {
104
103
  }
105
104
 
106
105
  // Force checkpoint to make changes visible to other processes
106
+ // Only called after batch operations, not after individual writes
107
107
  checkpoint(): void {
108
108
  this.db.pragma('wal_checkpoint(PASSIVE)');
109
109
  }
110
110
 
111
111
  prepare(sql: string): PreparedStatement {
112
- return new SqlitePreparedStatement(this.db, sql, () => this.checkpoint());
112
+ return new SqlitePreparedStatement(this.db, sql);
113
113
  }
114
114
 
115
115
  async batch(statements: PreparedStatement[]): Promise<QueryResult<unknown>[]> {
@@ -124,7 +124,7 @@ class SqliteDatabase implements DatabaseAdapter {
124
124
  });
125
125
  });
126
126
  const results = transaction();
127
- // Checkpoint after batch write
127
+ // Checkpoint after batch write to ensure visibility to other processes
128
128
  this.checkpoint();
129
129
  return results;
130
130
  }
package/src/db/types.ts CHANGED
@@ -120,6 +120,62 @@ export interface ClaimFile {
120
120
  is_pattern: number; // 0 or 1, SQLite boolean
121
121
  }
122
122
 
123
+ // Symbol types for fine-grained conflict detection
124
+ export type SymbolType = 'function' | 'class' | 'method' | 'variable' | 'block' | 'other';
125
+
126
+ export interface ClaimSymbol {
127
+ id: number;
128
+ claim_id: string;
129
+ file_path: string;
130
+ symbol_name: string;
131
+ symbol_type: SymbolType;
132
+ created_at: string;
133
+ }
134
+
135
+ // Input format for claiming symbols
136
+ export interface SymbolClaim {
137
+ file: string;
138
+ symbols: string[];
139
+ symbol_type?: SymbolType;
140
+ }
141
+
142
+ // Symbol reference for impact tracking
143
+ export interface SymbolReference {
144
+ id: number;
145
+ source_file: string;
146
+ source_symbol: string;
147
+ ref_file: string;
148
+ ref_line: number | null;
149
+ ref_context: string | null;
150
+ session_id: string;
151
+ created_at: string;
152
+ }
153
+
154
+ // Input format for storing references
155
+ export interface ReferenceInput {
156
+ source_file: string;
157
+ source_symbol: string;
158
+ references: Array<{
159
+ file: string;
160
+ line: number;
161
+ context?: string;
162
+ }>;
163
+ }
164
+
165
+ // Impact analysis result
166
+ export interface ImpactInfo {
167
+ symbol: string;
168
+ file: string;
169
+ affected_claims: Array<{
170
+ claim_id: string;
171
+ session_name: string | null;
172
+ intent: string;
173
+ affected_symbols: string[];
174
+ }>;
175
+ reference_count: number;
176
+ affected_files: string[];
177
+ }
178
+
123
179
  export interface Message {
124
180
  id: string;
125
181
  from_session_id: string;
@@ -152,4 +208,8 @@ export interface ConflictInfo {
152
208
  intent: string;
153
209
  scope: ClaimScope;
154
210
  created_at: string;
211
+ // Symbol-level conflict info (optional)
212
+ symbol_name?: string;
213
+ symbol_type?: SymbolType;
214
+ conflict_level: 'file' | 'symbol';
155
215
  }
@@ -0,0 +1,200 @@
1
+ // Zod schemas for MCP tool input validation
2
+ import { z } from 'zod';
3
+
4
+ // Common schemas
5
+ export const sessionIdSchema = z.string().min(1, 'session_id is required');
6
+ export const claimIdSchema = z.string().min(1, 'claim_id is required');
7
+ export const filePathSchema = z.string().min(1);
8
+ export const filesArraySchema = z.array(filePathSchema).min(1, 'At least one file is required');
9
+
10
+ // Symbol claim schema
11
+ export const symbolClaimSchema = z.object({
12
+ file: z.string().min(1),
13
+ symbols: z.array(z.string().min(1)).min(1),
14
+ symbol_type: z.enum(['function', 'class', 'method', 'variable', 'block', 'other']).optional(),
15
+ });
16
+
17
+ export const symbolClaimsArraySchema = z.array(symbolClaimSchema);
18
+
19
+ // Claim scope schema
20
+ export const claimScopeSchema = z.enum(['small', 'medium', 'large']).default('medium');
21
+
22
+ // Claim status schema
23
+ export const claimStatusSchema = z.enum(['completed', 'abandoned']);
24
+
25
+ // Session tools input schemas
26
+ export const sessionStartSchema = z.object({
27
+ project_root: z.string().min(1, 'project_root is required'),
28
+ name: z.string().optional(),
29
+ machine_id: z.string().optional(),
30
+ });
31
+
32
+ export const sessionEndSchema = z.object({
33
+ session_id: sessionIdSchema,
34
+ release_claims: z.enum(['complete', 'abandon']).default('abandon'),
35
+ });
36
+
37
+ export const sessionListSchema = z.object({
38
+ include_inactive: z.boolean().optional(),
39
+ project_root: z.string().optional(),
40
+ });
41
+
42
+ export const sessionHeartbeatSchema = z.object({
43
+ session_id: sessionIdSchema,
44
+ current_task: z.string().optional(),
45
+ todos: z.array(z.object({
46
+ content: z.string(),
47
+ status: z.enum(['pending', 'in_progress', 'completed']),
48
+ })).optional(),
49
+ });
50
+
51
+ export const statusUpdateSchema = z.object({
52
+ session_id: sessionIdSchema,
53
+ current_task: z.string().optional(),
54
+ todos: z.array(z.object({
55
+ content: z.string(),
56
+ status: z.enum(['pending', 'in_progress', 'completed']),
57
+ })).optional(),
58
+ });
59
+
60
+ export const configSchema = z.object({
61
+ session_id: sessionIdSchema,
62
+ mode: z.enum(['strict', 'smart', 'bypass']).optional(),
63
+ allow_release_others: z.boolean().optional(),
64
+ auto_release_stale: z.boolean().optional(),
65
+ stale_threshold_hours: z.number().min(0).optional(),
66
+ });
67
+
68
+ // Claim tools input schemas
69
+ export const claimCreateSchema = z.object({
70
+ session_id: sessionIdSchema,
71
+ files: z.array(filePathSchema).optional(),
72
+ symbols: symbolClaimsArraySchema.optional(),
73
+ intent: z.string().min(1, 'intent is required'),
74
+ scope: claimScopeSchema.optional(),
75
+ }).refine(
76
+ (data) => (data.files && data.files.length > 0) || (data.symbols && data.symbols.length > 0),
77
+ { message: 'Either files or symbols must be provided' }
78
+ );
79
+
80
+ export const claimCheckSchema = z.object({
81
+ files: filesArraySchema,
82
+ symbols: symbolClaimsArraySchema.optional(),
83
+ session_id: z.string().optional(),
84
+ });
85
+
86
+ export const claimReleaseSchema = z.object({
87
+ session_id: sessionIdSchema,
88
+ claim_id: claimIdSchema,
89
+ status: claimStatusSchema,
90
+ summary: z.string().optional(),
91
+ force: z.boolean().optional(),
92
+ });
93
+
94
+ export const claimListSchema = z.object({
95
+ session_id: z.string().optional(),
96
+ status: z.enum(['active', 'completed', 'abandoned', 'all']).optional(),
97
+ project_root: z.string().optional(),
98
+ });
99
+
100
+ // Message tools input schemas
101
+ export const messageSendSchema = z.object({
102
+ from_session_id: sessionIdSchema,
103
+ to_session_id: z.string().optional(),
104
+ content: z.string().min(1, 'content is required'),
105
+ });
106
+
107
+ export const messageListSchema = z.object({
108
+ session_id: sessionIdSchema,
109
+ unread_only: z.boolean().optional(),
110
+ mark_as_read: z.boolean().optional(),
111
+ });
112
+
113
+ // Decision tools input schemas
114
+ export const decisionAddSchema = z.object({
115
+ session_id: sessionIdSchema,
116
+ category: z.enum(['architecture', 'naming', 'api', 'database', 'ui', 'other']).optional(),
117
+ title: z.string().min(1, 'title is required'),
118
+ description: z.string().min(1, 'description is required'),
119
+ });
120
+
121
+ export const decisionListSchema = z.object({
122
+ category: z.enum(['architecture', 'naming', 'api', 'database', 'ui', 'other']).optional(),
123
+ limit: z.number().min(1).max(100).optional(),
124
+ });
125
+
126
+ // LSP tools input schemas
127
+ // Using z.ZodType to properly type recursive schema
128
+ type LspSymbol = {
129
+ name: string;
130
+ kind: number;
131
+ range?: { start: { line: number; character: number }; end: { line: number; character: number } };
132
+ children?: LspSymbol[];
133
+ };
134
+
135
+ export const lspSymbolSchema: z.ZodType<LspSymbol> = z.object({
136
+ name: z.string(),
137
+ kind: z.number(),
138
+ range: z.object({
139
+ start: z.object({ line: z.number(), character: z.number() }),
140
+ end: z.object({ line: z.number(), character: z.number() }),
141
+ }).optional(),
142
+ children: z.lazy(() => z.array(lspSymbolSchema)).optional(),
143
+ });
144
+
145
+ export const analyzeSymbolsSchema = z.object({
146
+ session_id: sessionIdSchema,
147
+ files: z.array(z.object({
148
+ file: z.string(),
149
+ symbols: z.array(lspSymbolSchema),
150
+ })),
151
+ check_symbols: z.array(z.string()).optional(),
152
+ references: z.array(z.object({
153
+ symbol: z.string(),
154
+ file: z.string(),
155
+ references: z.array(z.object({
156
+ file: z.string(),
157
+ line: z.number(),
158
+ context: z.string().optional(),
159
+ })),
160
+ })).optional(),
161
+ });
162
+
163
+ export const validateSymbolsSchema = z.object({
164
+ file: z.string().min(1),
165
+ symbols: z.array(z.string().min(1)),
166
+ lsp_symbols: z.array(lspSymbolSchema),
167
+ });
168
+
169
+ export const storeReferencesSchema = z.object({
170
+ session_id: sessionIdSchema,
171
+ references: z.array(z.object({
172
+ source_file: z.string(),
173
+ source_symbol: z.string(),
174
+ references: z.array(z.object({
175
+ file: z.string(),
176
+ line: z.number(),
177
+ context: z.string().optional(),
178
+ })),
179
+ })),
180
+ clear_existing: z.boolean().optional(),
181
+ });
182
+
183
+ export const impactAnalysisSchema = z.object({
184
+ session_id: sessionIdSchema,
185
+ file: z.string().min(1),
186
+ symbol: z.string().min(1),
187
+ });
188
+
189
+ // Helper function to validate and return parsed data or error result
190
+ export function validateInput<T>(
191
+ schema: z.ZodSchema<T>,
192
+ data: unknown
193
+ ): { success: true; data: T } | { success: false; error: string } {
194
+ const result = schema.safeParse(data);
195
+ if (result.success) {
196
+ return { success: true, data: result.data };
197
+ }
198
+ const errors = result.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
199
+ return { success: false, error: errors };
200
+ }
package/src/mcp/server.ts CHANGED
@@ -19,6 +19,7 @@ import { sessionTools, handleSessionTool } from './tools/session';
19
19
  import { claimTools, handleClaimTool } from './tools/claim';
20
20
  import { messageTools, handleMessageTool } from './tools/message';
21
21
  import { decisionTools, handleDecisionTool } from './tools/decision';
22
+ import { lspTools, handleLspTool } from './tools/lsp';
22
23
  import type { AuthContext } from '../auth/types';
23
24
  import { VERSION, SERVER_NAME, SERVER_INSTRUCTIONS } from '../constants.js';
24
25
 
@@ -32,7 +33,7 @@ const CAPABILITIES: McpCapabilities = {
32
33
  };
33
34
 
34
35
  // Combine all tools
35
- const ALL_TOOLS: McpTool[] = [...sessionTools, ...claimTools, ...messageTools, ...decisionTools];
36
+ const ALL_TOOLS: McpTool[] = [...sessionTools, ...claimTools, ...messageTools, ...decisionTools, ...lspTools];
36
37
 
37
38
  export class McpServer {
38
39
  private authContext?: AuthContext;
@@ -105,6 +106,13 @@ export class McpServer {
105
106
  result = await handleMessageTool(this.db, name, args);
106
107
  } else if (name.startsWith('collab_decision_')) {
107
108
  result = await handleDecisionTool(this.db, name, args);
109
+ } else if (
110
+ name === 'collab_analyze_symbols' ||
111
+ name === 'collab_validate_symbols' ||
112
+ name === 'collab_store_references' ||
113
+ name === 'collab_impact_analysis'
114
+ ) {
115
+ result = await handleLspTool(this.db, name, args);
108
116
  } else {
109
117
  result = createToolResult(`Unknown tool: ${name}`, true);
110
118
  }
@@ -148,6 +156,13 @@ export async function handleMcpRequest(
148
156
  return await handleMessageTool(db, name, args);
149
157
  } else if (name.startsWith('collab_decision_')) {
150
158
  return await handleDecisionTool(db, name, args);
159
+ } else if (
160
+ name === 'collab_analyze_symbols' ||
161
+ name === 'collab_validate_symbols' ||
162
+ name === 'collab_store_references' ||
163
+ name === 'collab_impact_analysis'
164
+ ) {
165
+ return await handleLspTool(db, name, args);
151
166
  } else {
152
167
  return createToolResult(`Unknown tool: ${name}`, true);
153
168
  }