@aprovan/hardcopy 0.1.0
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/.eslintrc.json +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.prettierignore +17 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2950 -0
- package/dist/index.d.ts +406 -0
- package/dist/index.js +2737 -0
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +2665 -0
- package/docs/research/crdt.md +777 -0
- package/docs/research/github-issues.md +684 -0
- package/docs/research/gql.md +876 -0
- package/docs/research/index.md +19 -0
- package/docs/specs/conflict-resolution.md +1254 -0
- package/docs/specs/hardcopy.md +742 -0
- package/docs/specs/patchwork-integration.md +227 -0
- package/docs/specs/plugin-architecture.md +747 -0
- package/mcp.json +8 -0
- package/package.json +64 -0
- package/scripts/install-graphqlite.ts +156 -0
- package/src/cli.ts +356 -0
- package/src/config.ts +104 -0
- package/src/conflict-store.ts +136 -0
- package/src/conflict.ts +147 -0
- package/src/crdt.ts +100 -0
- package/src/db.ts +600 -0
- package/src/env.ts +34 -0
- package/src/format.ts +72 -0
- package/src/formats/github-issue.ts +55 -0
- package/src/hardcopy/core.ts +78 -0
- package/src/hardcopy/diff.ts +188 -0
- package/src/hardcopy/index.ts +67 -0
- package/src/hardcopy/init.ts +24 -0
- package/src/hardcopy/push.ts +444 -0
- package/src/hardcopy/sync.ts +37 -0
- package/src/hardcopy/types.ts +49 -0
- package/src/hardcopy/views.ts +199 -0
- package/src/hardcopy.ts +1 -0
- package/src/index.ts +13 -0
- package/src/llm-merge.ts +109 -0
- package/src/mcp-server.ts +388 -0
- package/src/merge.ts +75 -0
- package/src/provider.ts +40 -0
- package/src/providers/a2a/index.ts +166 -0
- package/src/providers/git/index.ts +212 -0
- package/src/providers/github/index.ts +236 -0
- package/src/providers/github/issues.ts +66 -0
- package/src/providers.ts +7 -0
- package/src/types.ts +101 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +10 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { Database } from 'better-sqlite3';
|
|
2
|
+
export { Database as BetterSqlite3Database } from 'better-sqlite3';
|
|
3
|
+
import { LoroDoc } from 'loro-crdt';
|
|
4
|
+
export { createMcpServer, serveMcp } from './mcp-server.js';
|
|
5
|
+
import '@modelcontextprotocol/sdk/server/index.js';
|
|
6
|
+
|
|
7
|
+
interface Node {
|
|
8
|
+
id: string;
|
|
9
|
+
type: string;
|
|
10
|
+
attrs: Record<string, unknown>;
|
|
11
|
+
syncedAt?: number;
|
|
12
|
+
versionToken?: string;
|
|
13
|
+
cursor?: string;
|
|
14
|
+
}
|
|
15
|
+
interface Edge {
|
|
16
|
+
id?: number;
|
|
17
|
+
type: string;
|
|
18
|
+
fromId: string;
|
|
19
|
+
toId: string;
|
|
20
|
+
attrs?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
interface FetchRequest {
|
|
23
|
+
query: NodeQuery;
|
|
24
|
+
cursor?: string;
|
|
25
|
+
pageSize?: number;
|
|
26
|
+
versionToken?: string;
|
|
27
|
+
}
|
|
28
|
+
interface FetchResult {
|
|
29
|
+
nodes: Node[];
|
|
30
|
+
edges: Edge[];
|
|
31
|
+
cursor?: string;
|
|
32
|
+
hasMore: boolean;
|
|
33
|
+
versionToken?: string | null;
|
|
34
|
+
cached?: boolean;
|
|
35
|
+
}
|
|
36
|
+
interface PushResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
versionToken?: string;
|
|
40
|
+
}
|
|
41
|
+
interface NodeQuery {
|
|
42
|
+
id?: string;
|
|
43
|
+
type?: string;
|
|
44
|
+
attrs?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
interface Change {
|
|
47
|
+
field: string;
|
|
48
|
+
oldValue: unknown;
|
|
49
|
+
newValue: unknown;
|
|
50
|
+
}
|
|
51
|
+
interface SyncDecision {
|
|
52
|
+
strategy: "auto" | "llm" | "manual";
|
|
53
|
+
reason: string;
|
|
54
|
+
}
|
|
55
|
+
interface SyncError {
|
|
56
|
+
resourceId: string;
|
|
57
|
+
strategy: "auto" | "llm";
|
|
58
|
+
error: string;
|
|
59
|
+
llmExplanation?: string;
|
|
60
|
+
suggestedActions?: string[];
|
|
61
|
+
}
|
|
62
|
+
interface IndexState {
|
|
63
|
+
cursor?: string;
|
|
64
|
+
total?: number;
|
|
65
|
+
loaded: number;
|
|
66
|
+
pageSize: number;
|
|
67
|
+
lastFetch: string;
|
|
68
|
+
ttl: number;
|
|
69
|
+
}
|
|
70
|
+
declare enum ConflictStatus {
|
|
71
|
+
CLEAN = "clean",
|
|
72
|
+
REMOTE_ONLY = "remote",
|
|
73
|
+
DIVERGED = "diverged"
|
|
74
|
+
}
|
|
75
|
+
interface ThreeWayState {
|
|
76
|
+
base: unknown;
|
|
77
|
+
local: unknown;
|
|
78
|
+
remote: unknown;
|
|
79
|
+
}
|
|
80
|
+
interface FieldConflict {
|
|
81
|
+
field: string;
|
|
82
|
+
status: ConflictStatus;
|
|
83
|
+
base: unknown;
|
|
84
|
+
local: unknown;
|
|
85
|
+
remote: unknown;
|
|
86
|
+
canAutoMerge: boolean;
|
|
87
|
+
}
|
|
88
|
+
interface ConflictInfo {
|
|
89
|
+
nodeId: string;
|
|
90
|
+
nodeType: string;
|
|
91
|
+
filePath: string;
|
|
92
|
+
detectedAt: number;
|
|
93
|
+
fields: FieldConflict[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface Tool {
|
|
97
|
+
name: string;
|
|
98
|
+
description: string;
|
|
99
|
+
parameters?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
interface Provider {
|
|
102
|
+
name: string;
|
|
103
|
+
nodeTypes: string[];
|
|
104
|
+
edgeTypes: string[];
|
|
105
|
+
fetch(request: FetchRequest): Promise<FetchResult>;
|
|
106
|
+
push(node: Node, changes: Change[]): Promise<PushResult>;
|
|
107
|
+
fetchNode(nodeId: string): Promise<Node | null>;
|
|
108
|
+
getTools(): Tool[];
|
|
109
|
+
}
|
|
110
|
+
type ProviderFactory = (config: Record<string, unknown>) => Provider;
|
|
111
|
+
declare function registerProvider(name: string, factory: ProviderFactory): void;
|
|
112
|
+
declare function getProvider(name: string): ProviderFactory | undefined;
|
|
113
|
+
declare function listProviders(): string[];
|
|
114
|
+
|
|
115
|
+
interface ParsedFile {
|
|
116
|
+
attrs: Record<string, unknown>;
|
|
117
|
+
body: string;
|
|
118
|
+
}
|
|
119
|
+
interface FormatHandler {
|
|
120
|
+
type: string;
|
|
121
|
+
editableFields: string[];
|
|
122
|
+
render(node: Node): string;
|
|
123
|
+
parse(content: string): ParsedFile;
|
|
124
|
+
}
|
|
125
|
+
declare function registerFormat(handler: FormatHandler): void;
|
|
126
|
+
declare function getFormat(type: string): FormatHandler | undefined;
|
|
127
|
+
declare function listFormats(): string[];
|
|
128
|
+
declare function renderNode(node: Node, template?: string): string;
|
|
129
|
+
declare function parseFile(content: string, type: string): ParsedFile;
|
|
130
|
+
|
|
131
|
+
interface LinkConfig {
|
|
132
|
+
edge: string;
|
|
133
|
+
to: string;
|
|
134
|
+
match: string;
|
|
135
|
+
}
|
|
136
|
+
interface SourceConfig {
|
|
137
|
+
name: string;
|
|
138
|
+
provider: string;
|
|
139
|
+
orgs?: string[];
|
|
140
|
+
repositories?: {
|
|
141
|
+
path: string;
|
|
142
|
+
}[];
|
|
143
|
+
links?: LinkConfig[];
|
|
144
|
+
[key: string]: unknown;
|
|
145
|
+
}
|
|
146
|
+
interface RenderConfig {
|
|
147
|
+
path: string;
|
|
148
|
+
type?: string;
|
|
149
|
+
template?: string;
|
|
150
|
+
args?: Record<string, unknown>;
|
|
151
|
+
}
|
|
152
|
+
interface ViewConfig {
|
|
153
|
+
path: string;
|
|
154
|
+
description?: string;
|
|
155
|
+
query: string;
|
|
156
|
+
partition?: {
|
|
157
|
+
by: string;
|
|
158
|
+
fallback?: string;
|
|
159
|
+
};
|
|
160
|
+
render: RenderConfig[];
|
|
161
|
+
}
|
|
162
|
+
interface Config {
|
|
163
|
+
sources: SourceConfig[];
|
|
164
|
+
views: ViewConfig[];
|
|
165
|
+
}
|
|
166
|
+
declare function loadConfig(path: string): Promise<Config>;
|
|
167
|
+
declare function parseConfig(content: string): Config;
|
|
168
|
+
|
|
169
|
+
declare class HardcopyDatabase {
|
|
170
|
+
private db;
|
|
171
|
+
private graphqliteLoaded;
|
|
172
|
+
constructor(db: Database);
|
|
173
|
+
static open(path: string): Promise<HardcopyDatabase>;
|
|
174
|
+
private initialize;
|
|
175
|
+
private migrateLegacySchema;
|
|
176
|
+
private getTableColumns;
|
|
177
|
+
private renameTableIfNeeded;
|
|
178
|
+
private dropLegacyIndexes;
|
|
179
|
+
private resolveGraphqliteLoadPath;
|
|
180
|
+
private getExtensionCandidates;
|
|
181
|
+
private ensureGraphqliteLoaded;
|
|
182
|
+
private normalizeCypher;
|
|
183
|
+
private escapeCypherType;
|
|
184
|
+
private extractNodeIds;
|
|
185
|
+
private parseCypherRows;
|
|
186
|
+
cypher(query: string, params?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
187
|
+
queryViewNodes(query: string, params?: Record<string, unknown>): Promise<Node[]>;
|
|
188
|
+
getNodesByIds(ids: string[]): Promise<Node[]>;
|
|
189
|
+
upsertNode(node: Node): Promise<void>;
|
|
190
|
+
upsertNodes(nodes: Node[]): Promise<void>;
|
|
191
|
+
getNode(id: string): Promise<Node | null>;
|
|
192
|
+
queryNodes(type?: string): Promise<Node[]>;
|
|
193
|
+
deleteNode(id: string): Promise<void>;
|
|
194
|
+
upsertEdge(edge: Edge): Promise<void>;
|
|
195
|
+
upsertEdges(edges: Edge[]): Promise<void>;
|
|
196
|
+
getEdges(fromId?: string, toId?: string, type?: string): Promise<Edge[]>;
|
|
197
|
+
deleteEdge(fromId: string, toId: string, type: string): Promise<void>;
|
|
198
|
+
private escapeCypherString;
|
|
199
|
+
private upsertGraphNode;
|
|
200
|
+
private upsertGraphEdge;
|
|
201
|
+
private deleteGraphNode;
|
|
202
|
+
private deleteGraphEdge;
|
|
203
|
+
close(): Promise<void>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
declare class CRDTStore {
|
|
207
|
+
private basePath;
|
|
208
|
+
constructor(basePath: string);
|
|
209
|
+
private getPath;
|
|
210
|
+
exists(nodeId: string): Promise<boolean>;
|
|
211
|
+
load(nodeId: string): Promise<LoroDoc | null>;
|
|
212
|
+
save(nodeId: string, doc: LoroDoc): Promise<void>;
|
|
213
|
+
create(nodeId: string): Promise<LoroDoc>;
|
|
214
|
+
loadOrCreate(nodeId: string): Promise<LoroDoc>;
|
|
215
|
+
delete(nodeId: string): Promise<void>;
|
|
216
|
+
merge(nodeId: string, remote: LoroDoc): Promise<LoroDoc>;
|
|
217
|
+
}
|
|
218
|
+
declare function setDocContent(doc: LoroDoc, content: string): void;
|
|
219
|
+
declare function getDocContent(doc: LoroDoc): string;
|
|
220
|
+
declare function setDocAttrs(doc: LoroDoc, attrs: Record<string, unknown>): void;
|
|
221
|
+
declare function getDocAttrs(doc: LoroDoc): Record<string, unknown>;
|
|
222
|
+
|
|
223
|
+
interface HardcopyOptions {
|
|
224
|
+
root: string;
|
|
225
|
+
}
|
|
226
|
+
interface SyncStats {
|
|
227
|
+
nodes: number;
|
|
228
|
+
edges: number;
|
|
229
|
+
errors: string[];
|
|
230
|
+
}
|
|
231
|
+
interface StatusInfo {
|
|
232
|
+
totalNodes: number;
|
|
233
|
+
totalEdges: number;
|
|
234
|
+
nodesByType: Record<string, number>;
|
|
235
|
+
changedFiles: ChangedFile[];
|
|
236
|
+
conflicts: ConflictInfo[];
|
|
237
|
+
}
|
|
238
|
+
interface ChangedFile {
|
|
239
|
+
path: string;
|
|
240
|
+
fullPath: string;
|
|
241
|
+
nodeId: string;
|
|
242
|
+
nodeType: string;
|
|
243
|
+
status: "new" | "modified" | "deleted";
|
|
244
|
+
mtime: number;
|
|
245
|
+
syncedAt: number;
|
|
246
|
+
}
|
|
247
|
+
interface RefreshResult {
|
|
248
|
+
rendered: number;
|
|
249
|
+
orphaned: string[];
|
|
250
|
+
cleaned: boolean;
|
|
251
|
+
}
|
|
252
|
+
interface DiffResult {
|
|
253
|
+
nodeId: string;
|
|
254
|
+
nodeType: string;
|
|
255
|
+
filePath: string;
|
|
256
|
+
changes: Change[];
|
|
257
|
+
}
|
|
258
|
+
interface PushStats {
|
|
259
|
+
pushed: number;
|
|
260
|
+
skipped: number;
|
|
261
|
+
conflicts: number;
|
|
262
|
+
errors: string[];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface GitHubConfig {
|
|
266
|
+
orgs?: string[];
|
|
267
|
+
repos?: string[];
|
|
268
|
+
token?: string;
|
|
269
|
+
}
|
|
270
|
+
declare function createGitHubProvider(config: GitHubConfig): Provider;
|
|
271
|
+
|
|
272
|
+
interface A2AConfig {
|
|
273
|
+
endpoint?: string;
|
|
274
|
+
links?: {
|
|
275
|
+
edge: string;
|
|
276
|
+
to: string;
|
|
277
|
+
match: string;
|
|
278
|
+
}[];
|
|
279
|
+
}
|
|
280
|
+
declare function createA2AProvider(config: A2AConfig): Provider;
|
|
281
|
+
|
|
282
|
+
interface GitConfig {
|
|
283
|
+
repositories?: {
|
|
284
|
+
path: string;
|
|
285
|
+
}[];
|
|
286
|
+
links?: {
|
|
287
|
+
edge: string;
|
|
288
|
+
to: string;
|
|
289
|
+
match: string;
|
|
290
|
+
}[];
|
|
291
|
+
}
|
|
292
|
+
declare function createGitProvider(config: GitConfig): Provider;
|
|
293
|
+
|
|
294
|
+
declare class ConflictStore {
|
|
295
|
+
private conflictsDir;
|
|
296
|
+
constructor(conflictsDir: string);
|
|
297
|
+
save(info: ConflictInfo): Promise<void>;
|
|
298
|
+
list(): Promise<ConflictInfo[]>;
|
|
299
|
+
get(nodeId: string): Promise<ConflictInfo | null>;
|
|
300
|
+
read(nodeId: string): Promise<{
|
|
301
|
+
info: ConflictInfo;
|
|
302
|
+
body: string;
|
|
303
|
+
} | null>;
|
|
304
|
+
remove(nodeId: string): Promise<void>;
|
|
305
|
+
private getPath;
|
|
306
|
+
getArtifactPath(nodeId: string): string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
declare class Hardcopy {
|
|
310
|
+
readonly root: string;
|
|
311
|
+
readonly dataDir: string;
|
|
312
|
+
private _db;
|
|
313
|
+
private _crdt;
|
|
314
|
+
private _config;
|
|
315
|
+
private _providers;
|
|
316
|
+
private _conflictStore;
|
|
317
|
+
constructor(options: HardcopyOptions);
|
|
318
|
+
initialize(): Promise<void>;
|
|
319
|
+
loadConfig(): Promise<Config>;
|
|
320
|
+
private initializeProviders;
|
|
321
|
+
getDatabase(): HardcopyDatabase;
|
|
322
|
+
getCRDTStore(): CRDTStore;
|
|
323
|
+
getConflictStore(): ConflictStore;
|
|
324
|
+
getProviders(): Map<string, Provider>;
|
|
325
|
+
close(): Promise<void>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
declare function initHardcopy(root: string): Promise<void>;
|
|
329
|
+
|
|
330
|
+
declare module "./core" {
|
|
331
|
+
interface Hardcopy {
|
|
332
|
+
sync(): Promise<SyncStats>;
|
|
333
|
+
getViews(): Promise<string[]>;
|
|
334
|
+
refreshView(viewPath: string, options?: {
|
|
335
|
+
clean?: boolean;
|
|
336
|
+
}): Promise<RefreshResult>;
|
|
337
|
+
diff(pattern?: string, options?: {
|
|
338
|
+
smart?: boolean;
|
|
339
|
+
}): Promise<DiffResult[]>;
|
|
340
|
+
getChangedFiles(pattern?: string): Promise<ChangedFile[]>;
|
|
341
|
+
push(filePath?: string, options?: {
|
|
342
|
+
force?: boolean;
|
|
343
|
+
}): Promise<PushStats>;
|
|
344
|
+
status(): Promise<StatusInfo>;
|
|
345
|
+
listConflicts(): Promise<ConflictInfo[]>;
|
|
346
|
+
getConflict(nodeId: string): Promise<ConflictInfo | null>;
|
|
347
|
+
getConflictDetail(nodeId: string): Promise<{
|
|
348
|
+
info: ConflictInfo;
|
|
349
|
+
body: string;
|
|
350
|
+
artifactPath: string;
|
|
351
|
+
} | null>;
|
|
352
|
+
resolveConflict(nodeId: string, resolution: Record<string, "local" | "remote">): Promise<void>;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
declare function detectFieldConflict(field: string, state: ThreeWayState): FieldConflict;
|
|
357
|
+
declare function detectConflicts(baseNode: Node, localParsed: ParsedFile, remoteNode: Node, editableFields: string[]): FieldConflict[];
|
|
358
|
+
declare function hasUnresolvableConflicts(conflicts: FieldConflict[]): boolean;
|
|
359
|
+
declare function autoMergeField(conflict: FieldConflict): unknown | null;
|
|
360
|
+
declare function generateConflictMarkers(conflict: FieldConflict): string;
|
|
361
|
+
declare function parseConflictMarkers(text: string): {
|
|
362
|
+
local: string;
|
|
363
|
+
base: string;
|
|
364
|
+
remote: string;
|
|
365
|
+
} | null;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* LLM-based merge for conflicts that can't be auto-resolved.
|
|
369
|
+
*
|
|
370
|
+
* Uses an OpenAI-compatible endpoint (e.g., copilot-proxy) to intelligently
|
|
371
|
+
* merge conflicting changes by understanding semantic intent.
|
|
372
|
+
*/
|
|
373
|
+
interface LLMMergeOptions {
|
|
374
|
+
/** OpenAI-compatible API base URL (default: OPENAI_BASE_URL or http://localhost:6433) */
|
|
375
|
+
baseURL?: string;
|
|
376
|
+
/** Model to use (default: OPENAI_MODEL or gpt-4o) */
|
|
377
|
+
model?: string;
|
|
378
|
+
/** API key for authentication (default: OPENAI_API_KEY) */
|
|
379
|
+
apiKey?: string;
|
|
380
|
+
/** Temperature for generation (default: 0) */
|
|
381
|
+
temperature?: number;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Attempts to merge conflicting text using an LLM.
|
|
385
|
+
*
|
|
386
|
+
* @param base - The common ancestor text
|
|
387
|
+
* @param local - The local (your) version
|
|
388
|
+
* @param remote - The remote (their) version
|
|
389
|
+
* @param options - LLM configuration options
|
|
390
|
+
* @returns The merged text, or null if the LLM call fails
|
|
391
|
+
*/
|
|
392
|
+
declare function llmMergeText(base: string, local: string, remote: string, options?: LLMMergeOptions): Promise<string | null>;
|
|
393
|
+
|
|
394
|
+
interface SemanticMergeOptions {
|
|
395
|
+
tempDir?: string;
|
|
396
|
+
filePath: string;
|
|
397
|
+
/** LLM merge options (URL, model, etc.) */
|
|
398
|
+
llmOptions?: LLMMergeOptions;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Attempts a 3-way merge using diff3, with LLM fallback for conflicts.
|
|
402
|
+
* Returns the merged text if successful, or null if merge fails entirely.
|
|
403
|
+
*/
|
|
404
|
+
declare function mergeText(base: string, local: string, remote: string, options: SemanticMergeOptions): Promise<string | null>;
|
|
405
|
+
|
|
406
|
+
export { CRDTStore, type Change, type ChangedFile, type Config, type ConflictInfo, ConflictStatus, ConflictStore, HardcopyDatabase as Database, type DiffResult, type Edge, type FetchRequest, type FetchResult, type FieldConflict, type FormatHandler, Hardcopy, HardcopyDatabase, type HardcopyOptions, type IndexState, type LLMMergeOptions, type LinkConfig, type Node, type NodeQuery, type ParsedFile, type Provider, type ProviderFactory, type PushResult, type PushStats, type RefreshResult, type RenderConfig, type SemanticMergeOptions, type SourceConfig, type StatusInfo, type SyncDecision, type SyncError, type SyncStats, type ThreeWayState, type Tool, type ViewConfig, autoMergeField, createA2AProvider, createGitHubProvider, createGitProvider, detectConflicts, detectFieldConflict, generateConflictMarkers, getDocAttrs, getDocContent, getFormat, getProvider, hasUnresolvableConflicts, initHardcopy, listFormats, listProviders, llmMergeText, loadConfig, mergeText, parseConfig, parseConflictMarkers, parseFile, registerFormat, registerProvider, renderNode, setDocAttrs, setDocContent };
|