@mycontxt/core 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/dist/index.js ADDED
@@ -0,0 +1,1036 @@
1
+ // src/types.ts
2
+ var ValidationError = class extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "ValidationError";
6
+ }
7
+ };
8
+ var NotFoundError = class extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "NotFoundError";
12
+ }
13
+ };
14
+ var ConflictError = class extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "ConflictError";
18
+ }
19
+ };
20
+ var AuthError = class extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "AuthError";
24
+ }
25
+ };
26
+
27
+ // src/engine/memory.ts
28
+ var MemoryEngine = class {
29
+ constructor(db) {
30
+ this.db = db;
31
+ }
32
+ // ==================
33
+ // Decision Management
34
+ // ==================
35
+ async addDecision(projectId, input) {
36
+ if (!input.title?.trim()) {
37
+ throw new ValidationError("Decision title is required");
38
+ }
39
+ if (!input.rationale?.trim()) {
40
+ throw new ValidationError("Decision rationale is required");
41
+ }
42
+ const entry = await this.db.createEntry({
43
+ projectId,
44
+ type: "decision",
45
+ title: input.title.trim(),
46
+ content: input.rationale.trim(),
47
+ metadata: {
48
+ alternatives: input.alternatives || [],
49
+ consequences: input.consequences || [],
50
+ tags: input.tags || []
51
+ }
52
+ });
53
+ return entry;
54
+ }
55
+ async listDecisions(projectId, branch) {
56
+ return this.db.listEntries({
57
+ projectId,
58
+ type: "decision",
59
+ branch,
60
+ isArchived: false
61
+ });
62
+ }
63
+ async getDecision(id) {
64
+ const entry = await this.db.getEntry(id);
65
+ if (!entry || entry.type !== "decision") {
66
+ throw new NotFoundError(`Decision with id ${id} not found`);
67
+ }
68
+ return entry;
69
+ }
70
+ async updateDecision(id, updates) {
71
+ const existing = await this.getDecision(id);
72
+ return this.db.updateEntry(id, {
73
+ title: updates.title?.trim() || existing.title,
74
+ content: updates.rationale?.trim() || existing.content,
75
+ metadata: {
76
+ ...existing.metadata,
77
+ alternatives: updates.alternatives || existing.metadata.alternatives,
78
+ consequences: updates.consequences || existing.metadata.consequences,
79
+ tags: updates.tags || existing.metadata.tags
80
+ }
81
+ });
82
+ }
83
+ // ==================
84
+ // Pattern Management
85
+ // ==================
86
+ async addPattern(projectId, input) {
87
+ if (!input.title?.trim()) {
88
+ throw new ValidationError("Pattern title is required");
89
+ }
90
+ if (!input.content?.trim()) {
91
+ throw new ValidationError("Pattern content is required");
92
+ }
93
+ return this.db.createEntry({
94
+ projectId,
95
+ type: "pattern",
96
+ title: input.title.trim(),
97
+ content: input.content.trim(),
98
+ metadata: {
99
+ category: input.category || "general",
100
+ tags: input.tags || []
101
+ }
102
+ });
103
+ }
104
+ async listPatterns(projectId, branch) {
105
+ return this.db.listEntries({
106
+ projectId,
107
+ type: "pattern",
108
+ branch,
109
+ isArchived: false
110
+ });
111
+ }
112
+ async getPattern(id) {
113
+ const entry = await this.db.getEntry(id);
114
+ if (!entry || entry.type !== "pattern") {
115
+ throw new NotFoundError(`Pattern with id ${id} not found`);
116
+ }
117
+ return entry;
118
+ }
119
+ async updatePattern(id, updates) {
120
+ const existing = await this.getPattern(id);
121
+ return this.db.updateEntry(id, {
122
+ title: updates.title?.trim() || existing.title,
123
+ content: updates.content?.trim() || existing.content,
124
+ metadata: {
125
+ ...existing.metadata,
126
+ category: updates.category || existing.metadata.category,
127
+ tags: updates.tags || existing.metadata.tags
128
+ }
129
+ });
130
+ }
131
+ // ==================
132
+ // Context Management
133
+ // ==================
134
+ async setContext(projectId, input) {
135
+ const existing = await this.db.listEntries({
136
+ projectId,
137
+ type: "context",
138
+ isArchived: false
139
+ });
140
+ const contextData = {
141
+ feature: input.feature || "",
142
+ blockers: input.blockers || [],
143
+ nextSteps: input.nextSteps || [],
144
+ activeFiles: input.activeFiles || []
145
+ };
146
+ if (existing.length > 0) {
147
+ return this.db.updateEntry(existing[0].id, {
148
+ title: "Project Context",
149
+ content: JSON.stringify(contextData, null, 2),
150
+ metadata: contextData
151
+ });
152
+ } else {
153
+ return this.db.createEntry({
154
+ projectId,
155
+ type: "context",
156
+ title: "Project Context",
157
+ content: JSON.stringify(contextData, null, 2),
158
+ metadata: contextData
159
+ });
160
+ }
161
+ }
162
+ async getContext(projectId) {
163
+ const entries = await this.db.listEntries({
164
+ projectId,
165
+ type: "context",
166
+ isArchived: false
167
+ });
168
+ return entries[0] || null;
169
+ }
170
+ // ==================
171
+ // Document Management
172
+ // ==================
173
+ async addDocument(projectId, input) {
174
+ if (!input.title?.trim()) {
175
+ throw new ValidationError("Document title is required");
176
+ }
177
+ if (!input.content?.trim()) {
178
+ throw new ValidationError("Document content is required");
179
+ }
180
+ return this.db.createEntry({
181
+ projectId,
182
+ type: "document",
183
+ title: input.title.trim(),
184
+ content: input.content.trim(),
185
+ metadata: {
186
+ url: input.url,
187
+ tags: input.tags || []
188
+ }
189
+ });
190
+ }
191
+ async listDocuments(projectId, branch) {
192
+ return this.db.listEntries({
193
+ projectId,
194
+ type: "document",
195
+ branch,
196
+ isArchived: false
197
+ });
198
+ }
199
+ async getDocument(id) {
200
+ const entry = await this.db.getEntry(id);
201
+ if (!entry || entry.type !== "document") {
202
+ throw new NotFoundError(`Document with id ${id} not found`);
203
+ }
204
+ return entry;
205
+ }
206
+ // ==================
207
+ // Session Management
208
+ // ==================
209
+ async startSession(projectId, input) {
210
+ if (!input.feature?.trim()) {
211
+ throw new ValidationError("Session feature is required");
212
+ }
213
+ const active = await this.getActiveSession(projectId);
214
+ if (active) {
215
+ throw new ValidationError(
216
+ "An active session already exists. End it first or use a different branch."
217
+ );
218
+ }
219
+ return this.db.createEntry({
220
+ projectId,
221
+ type: "session",
222
+ title: `Session: ${input.feature}`,
223
+ content: input.description || "",
224
+ metadata: {
225
+ feature: input.feature,
226
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
227
+ endedAt: null
228
+ }
229
+ });
230
+ }
231
+ async endSession(projectId, summary) {
232
+ const active = await this.getActiveSession(projectId);
233
+ if (!active) {
234
+ throw new NotFoundError("No active session found");
235
+ }
236
+ return this.db.updateEntry(active.id, {
237
+ content: summary || active.content,
238
+ metadata: {
239
+ ...active.metadata,
240
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
241
+ }
242
+ });
243
+ }
244
+ async getActiveSession(projectId) {
245
+ const sessions = await this.db.listEntries({
246
+ projectId,
247
+ type: "session",
248
+ isArchived: false
249
+ });
250
+ return sessions.find((s) => !s.metadata.endedAt) || null;
251
+ }
252
+ async listSessions(projectId, branch) {
253
+ return this.db.listEntries({
254
+ projectId,
255
+ type: "session",
256
+ branch,
257
+ isArchived: false
258
+ });
259
+ }
260
+ // ==================
261
+ // Generic Operations
262
+ // ==================
263
+ async deleteEntry(id, hard = false) {
264
+ return this.db.deleteEntry(id, hard);
265
+ }
266
+ async searchEntries(projectId, searchTerm, options) {
267
+ return this.db.searchEntries(projectId, searchTerm, options);
268
+ }
269
+ async getAllEntries(query) {
270
+ return this.db.listEntries(query);
271
+ }
272
+ async getEntryCount(projectId, branch) {
273
+ return this.db.countEntries({
274
+ projectId,
275
+ branch,
276
+ isArchived: false
277
+ });
278
+ }
279
+ };
280
+
281
+ // src/engine/relevance.ts
282
+ function calculateRelevance(entry, options) {
283
+ let score = 0;
284
+ if (options.taskDescription) {
285
+ score += keywordOverlap(entry, options.taskDescription) * 0.4;
286
+ }
287
+ const daysOld = (Date.now() - entry.updatedAt.getTime()) / (1e3 * 60 * 60 * 24);
288
+ const recencyScore = Math.exp(-daysOld / 30);
289
+ score += recencyScore * 0.25;
290
+ const typePriority = getTypePriority(entry.type);
291
+ score += typePriority * 0.2;
292
+ if (options.activeFiles && options.activeFiles.length > 0) {
293
+ const hasFileMatch = options.activeFiles.some(
294
+ (file) => entry.content.toLowerCase().includes(file.toLowerCase()) || entry.title.toLowerCase().includes(file.toLowerCase())
295
+ );
296
+ if (hasFileMatch) {
297
+ score += 0.15;
298
+ }
299
+ }
300
+ return Math.min(score, 1);
301
+ }
302
+ function keywordOverlap(entry, query) {
303
+ const entryText = `${entry.title} ${entry.content}`.toLowerCase();
304
+ const queryWords = query.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
305
+ if (queryWords.length === 0) return 0;
306
+ const matchCount = queryWords.filter((word) => entryText.includes(word)).length;
307
+ return matchCount / queryWords.length;
308
+ }
309
+ function getTypePriority(type) {
310
+ const priorities = {
311
+ context: 1,
312
+ // Highest priority
313
+ decision: 0.8,
314
+ pattern: 0.8,
315
+ session: 0.5,
316
+ document: 0.3
317
+ // Lowest priority
318
+ };
319
+ return priorities[type] || 0.5;
320
+ }
321
+ function getMatchReasons(entry, options) {
322
+ const reasons = [];
323
+ if (options.taskDescription) {
324
+ const overlap = keywordOverlap(entry, options.taskDescription);
325
+ if (overlap > 0.3) {
326
+ reasons.push(`${Math.round(overlap * 100)}% keyword match`);
327
+ }
328
+ }
329
+ const daysOld = (Date.now() - entry.updatedAt.getTime()) / (1e3 * 60 * 60 * 24);
330
+ if (daysOld < 7) {
331
+ reasons.push("Recently updated");
332
+ }
333
+ if (entry.type === "context") {
334
+ reasons.push("Current context");
335
+ }
336
+ if (options.activeFiles) {
337
+ const matchedFiles = options.activeFiles.filter(
338
+ (file) => entry.content.toLowerCase().includes(file.toLowerCase())
339
+ );
340
+ if (matchedFiles.length > 0) {
341
+ reasons.push(`References ${matchedFiles.join(", ")}`);
342
+ }
343
+ }
344
+ return reasons;
345
+ }
346
+ function rankEntries(entries, options) {
347
+ const minRelevance = options.minRelevance || 0.3;
348
+ return entries.map((entry) => ({
349
+ entry,
350
+ score: calculateRelevance(entry, options),
351
+ reasons: getMatchReasons(entry, options)
352
+ })).filter((ranked) => ranked.score >= minRelevance).sort((a, b) => b.score - a.score);
353
+ }
354
+
355
+ // src/engine/sync.ts
356
+ var SyncEngine = class {
357
+ constructor(local, remote) {
358
+ this.local = local;
359
+ this.remote = remote;
360
+ }
361
+ /**
362
+ * Push local changes to remote
363
+ */
364
+ async push(projectId, options = {}) {
365
+ const result = {
366
+ pushed: 0,
367
+ pulled: 0,
368
+ conflicts: 0,
369
+ errors: []
370
+ };
371
+ try {
372
+ const project = await this.local.getProject(projectId);
373
+ if (!project) {
374
+ throw new Error("Project not found locally");
375
+ }
376
+ if (!options.dryRun) {
377
+ await this.remote.upsertProject(project);
378
+ }
379
+ const unsyncedEntries = await this.local.getUnsyncedEntries(projectId);
380
+ if (unsyncedEntries.length === 0) {
381
+ return result;
382
+ }
383
+ const conflicts = [];
384
+ if (!options.force) {
385
+ const lastPull = await this.local.getLastPull(projectId);
386
+ if (lastPull) {
387
+ const remoteChanges = await this.remote.pullEntries(projectId, lastPull);
388
+ const unsyncedIds = new Set(unsyncedEntries.map((e) => e.id));
389
+ for (const remoteEntry of remoteChanges) {
390
+ if (unsyncedIds.has(remoteEntry.id)) {
391
+ const localEntry = unsyncedEntries.find((e) => e.id === remoteEntry.id);
392
+ if (localEntry && localEntry.updatedAt.getTime() !== remoteEntry.updatedAt.getTime()) {
393
+ conflicts.push(localEntry);
394
+ }
395
+ }
396
+ }
397
+ }
398
+ }
399
+ if (conflicts.length > 0 && !options.force) {
400
+ result.conflicts = conflicts.length;
401
+ result.errors.push(
402
+ `${conflicts.length} conflict(s) found. Use --force to push anyway (last-write-wins).`
403
+ );
404
+ return result;
405
+ }
406
+ if (!options.dryRun) {
407
+ await this.remote.pushEntries(unsyncedEntries);
408
+ await this.local.markSynced(unsyncedEntries.map((e) => e.id));
409
+ }
410
+ result.pushed = unsyncedEntries.length;
411
+ const branches = await this.local.listBranches(projectId);
412
+ for (const branch of branches) {
413
+ if (!options.dryRun) {
414
+ await this.remote.upsertBranch(branch);
415
+ }
416
+ }
417
+ return result;
418
+ } catch (error) {
419
+ result.errors.push(
420
+ error instanceof Error ? error.message : "Unknown error during push"
421
+ );
422
+ return result;
423
+ }
424
+ }
425
+ /**
426
+ * Pull remote changes to local
427
+ */
428
+ async pull(projectId, options = {}) {
429
+ const result = {
430
+ pushed: 0,
431
+ pulled: 0,
432
+ conflicts: 0,
433
+ errors: []
434
+ };
435
+ try {
436
+ const project = await this.local.getProject(projectId);
437
+ if (!project) {
438
+ throw new Error("Project not found locally");
439
+ }
440
+ const lastPull = await this.local.getLastPull(projectId);
441
+ const since = lastPull || /* @__PURE__ */ new Date(0);
442
+ const remoteEntries = await this.remote.pullEntries(projectId, since);
443
+ if (remoteEntries.length === 0) {
444
+ return result;
445
+ }
446
+ const unsyncedEntries = await this.local.getUnsyncedEntries(projectId);
447
+ const unsyncedIds = new Set(unsyncedEntries.map((e) => e.id));
448
+ const conflicts = [];
449
+ for (const remoteEntry of remoteEntries) {
450
+ if (unsyncedIds.has(remoteEntry.id)) {
451
+ const localEntry = unsyncedEntries.find((e) => e.id === remoteEntry.id);
452
+ if (localEntry && localEntry.updatedAt.getTime() !== remoteEntry.updatedAt.getTime()) {
453
+ conflicts.push(remoteEntry);
454
+ }
455
+ }
456
+ }
457
+ if (conflicts.length > 0 && !options.force) {
458
+ result.conflicts = conflicts.length;
459
+ result.errors.push(
460
+ `${conflicts.length} conflict(s) found. Use --force to pull anyway (last-write-wins).`
461
+ );
462
+ return result;
463
+ }
464
+ if (!options.dryRun) {
465
+ for (const entry of remoteEntries) {
466
+ const localEntry = await this.local.getEntry(entry.id);
467
+ if (!localEntry) {
468
+ await this.local.createEntry({
469
+ id: entry.id,
470
+ projectId: entry.projectId,
471
+ type: entry.type,
472
+ title: entry.title,
473
+ content: entry.content,
474
+ metadata: entry.metadata,
475
+ branch: entry.branch
476
+ });
477
+ } else if (entry.updatedAt > localEntry.updatedAt || options.force) {
478
+ await this.local.updateEntry(entry.id, {
479
+ title: entry.title,
480
+ content: entry.content,
481
+ metadata: entry.metadata,
482
+ updatedAt: entry.updatedAt,
483
+ isSynced: true
484
+ });
485
+ }
486
+ }
487
+ await this.local.updateLastPull(projectId, /* @__PURE__ */ new Date());
488
+ }
489
+ result.pulled = remoteEntries.length;
490
+ return result;
491
+ } catch (error) {
492
+ result.errors.push(
493
+ error instanceof Error ? error.message : "Unknown error during pull"
494
+ );
495
+ return result;
496
+ }
497
+ }
498
+ /**
499
+ * Full bidirectional sync (pull then push)
500
+ */
501
+ async sync(projectId, options = {}) {
502
+ const pullResult = await this.pull(projectId, options);
503
+ if (pullResult.errors.length > 0) {
504
+ return pullResult;
505
+ }
506
+ const pushResult = await this.push(projectId, options);
507
+ return {
508
+ pushed: pushResult.pushed,
509
+ pulled: pullResult.pulled,
510
+ conflicts: pullResult.conflicts + pushResult.conflicts,
511
+ errors: [...pullResult.errors, ...pushResult.errors]
512
+ };
513
+ }
514
+ };
515
+
516
+ // src/utils/tokens.ts
517
+ import { get_encoding } from "tiktoken";
518
+ var encoding = get_encoding("cl100k_base");
519
+ function countTokens(text) {
520
+ const tokens = encoding.encode(text);
521
+ return tokens.length;
522
+ }
523
+ function countEntryTokens(entry) {
524
+ const text = formatEntryForContext(entry);
525
+ return countTokens(text);
526
+ }
527
+ function formatEntryForContext(entry) {
528
+ const lines = [];
529
+ lines.push(`## ${entry.type.toUpperCase()}: ${entry.title}`);
530
+ lines.push("");
531
+ lines.push(entry.content);
532
+ if (entry.metadata && Object.keys(entry.metadata).length > 0) {
533
+ lines.push("");
534
+ lines.push("**Metadata:**");
535
+ for (const [key, value] of Object.entries(entry.metadata)) {
536
+ if (Array.isArray(value) && value.length > 0) {
537
+ lines.push(`- ${key}: ${value.join(", ")}`);
538
+ } else if (value && typeof value === "string") {
539
+ lines.push(`- ${key}: ${value}`);
540
+ }
541
+ }
542
+ }
543
+ lines.push("");
544
+ lines.push("---");
545
+ lines.push("");
546
+ return lines.join("\n");
547
+ }
548
+ function fitToBudget(rankedEntries, maxTokens) {
549
+ const result = [];
550
+ let currentTokens = 0;
551
+ const headerTokens = countTokens("# MemoCore Context\n\n");
552
+ currentTokens += headerTokens;
553
+ for (const ranked of rankedEntries) {
554
+ const entryTokens = countEntryTokens(ranked.entry);
555
+ if (currentTokens + entryTokens <= maxTokens) {
556
+ result.push(ranked);
557
+ currentTokens += entryTokens;
558
+ } else {
559
+ break;
560
+ }
561
+ }
562
+ return result;
563
+ }
564
+ function buildContext(rankedEntries, options) {
565
+ const lines = [];
566
+ lines.push("# MemoCore Context");
567
+ lines.push("");
568
+ if (options?.includeStats) {
569
+ lines.push(`Found ${rankedEntries.length} relevant entries:`);
570
+ lines.push("");
571
+ }
572
+ for (const ranked of rankedEntries) {
573
+ if (options?.includeReasons && ranked.reasons.length > 0) {
574
+ lines.push(`<!-- Match reasons: ${ranked.reasons.join(", ")} -->`);
575
+ }
576
+ lines.push(formatEntryForContext(ranked.entry));
577
+ }
578
+ return lines.join("\n");
579
+ }
580
+ function cleanup() {
581
+ encoding.free();
582
+ }
583
+
584
+ // src/engine/context-builder.ts
585
+ function toRankedEntries(entries) {
586
+ return entries.map((entry) => ({
587
+ entry,
588
+ score: 1,
589
+ reasons: []
590
+ }));
591
+ }
592
+ function buildContextPayload(entries, options) {
593
+ const budget = options.maxTokens || 4e3;
594
+ let rankedEntries = [];
595
+ if (options.type === "task" && options.taskDescription) {
596
+ const suggestOpts = {
597
+ projectId: options.projectId,
598
+ taskDescription: options.taskDescription,
599
+ activeFiles: options.activeFiles,
600
+ maxTokens: budget
601
+ };
602
+ rankedEntries = rankEntries(entries, suggestOpts);
603
+ } else if (options.type === "files" && options.activeFiles && options.activeFiles.length > 0) {
604
+ const filtered = entries.filter(
605
+ (entry) => options.activeFiles.some(
606
+ (file) => entry.content.includes(file) || entry.metadata?.files?.includes(file)
607
+ )
608
+ );
609
+ filtered.sort(
610
+ (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
611
+ );
612
+ rankedEntries = toRankedEntries(filtered);
613
+ } else {
614
+ const sorted = [...entries];
615
+ sorted.sort(
616
+ (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
617
+ );
618
+ rankedEntries = toRankedEntries(sorted);
619
+ }
620
+ if (options.includeTypes && options.includeTypes.length > 0) {
621
+ rankedEntries = rankedEntries.filter(
622
+ (ranked) => options.includeTypes.includes(ranked.entry.type)
623
+ );
624
+ }
625
+ const fitted = fitToBudget(rankedEntries, budget);
626
+ const context = buildContext(fitted);
627
+ const tokensUsed = countTokens(context);
628
+ return {
629
+ context,
630
+ entriesIncluded: fitted.length,
631
+ tokensUsed,
632
+ budget
633
+ };
634
+ }
635
+ function buildContextSummary(entries) {
636
+ const byType = {};
637
+ const recentActivity = [];
638
+ let oldest = null;
639
+ let newest = null;
640
+ for (const entry of entries) {
641
+ byType[entry.type] = (byType[entry.type] || 0) + 1;
642
+ if (!oldest || entry.createdAt < oldest) {
643
+ oldest = entry.createdAt;
644
+ }
645
+ if (!newest || entry.updatedAt > newest) {
646
+ newest = entry.updatedAt;
647
+ }
648
+ }
649
+ const recent = [...entries].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()).slice(0, 5);
650
+ for (const entry of recent) {
651
+ const age = Date.now() - entry.updatedAt.getTime();
652
+ const daysAgo = Math.floor(age / (1e3 * 60 * 60 * 24));
653
+ let timeStr = "";
654
+ if (daysAgo === 0) {
655
+ timeStr = "today";
656
+ } else if (daysAgo === 1) {
657
+ timeStr = "yesterday";
658
+ } else if (daysAgo < 7) {
659
+ timeStr = `${daysAgo} days ago`;
660
+ } else {
661
+ timeStr = `${Math.floor(daysAgo / 7)} weeks ago`;
662
+ }
663
+ recentActivity.push(`${entry.type}: ${entry.title} (${timeStr})`);
664
+ }
665
+ return {
666
+ totalEntries: entries.length,
667
+ byType,
668
+ recentActivity,
669
+ oldestEntry: oldest,
670
+ newestEntry: newest
671
+ };
672
+ }
673
+
674
+ // src/scanner.ts
675
+ import { createHash } from "crypto";
676
+ var COMMENT_PATTERNS = [
677
+ // Single line comments: // @tag
678
+ { pattern: /^(\s*)\/\/\s*/, continuation: /^(\s*)\/\/\s+(?!@)/ },
679
+ // Python/Ruby/Shell: # @tag
680
+ { pattern: /^(\s*)#\s*/, continuation: /^(\s*)#\s+(?!@)/ },
681
+ // SQL: -- @tag
682
+ { pattern: /^(\s*)--\s*/, continuation: /^(\s*)--\s+(?!@)/ }
683
+ ];
684
+ var TAG_PATTERN = /@(decision|pattern|context)\s*/i;
685
+ var CATEGORY_PATTERN = /^\[([^\]]+)\]\s*/;
686
+ var FIELD_PATTERN = /\|\s*(\w+):\s*([^|]+)/g;
687
+ function parseFile(content, filePath) {
688
+ const lines = content.split("\n");
689
+ const comments = [];
690
+ let currentComment = null;
691
+ let currentIndent = "";
692
+ for (let i = 0; i < lines.length; i++) {
693
+ const line = lines[i];
694
+ const lineNum = i + 1;
695
+ const tagMatch = detectTag(line);
696
+ if (tagMatch) {
697
+ if (currentComment && currentComment.title) {
698
+ comments.push(finalizeComment(currentComment, filePath));
699
+ }
700
+ const { tag, content: content2, category } = tagMatch;
701
+ const { title, fields } = extractTitleAndFields(content2);
702
+ currentComment = {
703
+ tag,
704
+ category,
705
+ title,
706
+ content: title,
707
+ fields,
708
+ file: filePath,
709
+ line: lineNum
710
+ };
711
+ currentIndent = getIndent(line);
712
+ continue;
713
+ }
714
+ if (currentComment && isContinuationLine(line, currentIndent)) {
715
+ const continuationText = extractContinuationText(line);
716
+ if (continuationText) {
717
+ currentComment.content += " " + continuationText;
718
+ const moreFields = extractFields(continuationText);
719
+ Object.assign(currentComment.fields, moreFields);
720
+ }
721
+ continue;
722
+ }
723
+ if (currentComment && currentComment.title) {
724
+ comments.push(finalizeComment(currentComment, filePath));
725
+ currentComment = null;
726
+ }
727
+ }
728
+ if (currentComment && currentComment.title) {
729
+ comments.push(finalizeComment(currentComment, filePath));
730
+ }
731
+ return comments;
732
+ }
733
+ function detectTag(line) {
734
+ let commentContent = null;
735
+ for (const { pattern } of COMMENT_PATTERNS) {
736
+ const match = line.match(pattern);
737
+ if (match) {
738
+ commentContent = line.substring(match[0].length);
739
+ break;
740
+ }
741
+ }
742
+ if (!commentContent) return null;
743
+ const tagMatch = commentContent.match(TAG_PATTERN);
744
+ if (!tagMatch) return null;
745
+ const tag = tagMatch[1].toLowerCase();
746
+ let content = commentContent.substring(tagMatch[0].length).trim();
747
+ let category;
748
+ const categoryMatch = content.match(CATEGORY_PATTERN);
749
+ if (categoryMatch) {
750
+ category = categoryMatch[1];
751
+ content = content.substring(categoryMatch[0].length).trim();
752
+ }
753
+ return { tag, content, category };
754
+ }
755
+ function isContinuationLine(line, expectedIndent) {
756
+ for (const { continuation } of COMMENT_PATTERNS) {
757
+ const match = line.match(continuation);
758
+ if (match && match[1] === expectedIndent) {
759
+ return true;
760
+ }
761
+ }
762
+ return false;
763
+ }
764
+ function extractContinuationText(line) {
765
+ for (const { continuation } of COMMENT_PATTERNS) {
766
+ const match = line.match(continuation);
767
+ if (match) {
768
+ return line.substring(match[0].length).trim();
769
+ }
770
+ }
771
+ return null;
772
+ }
773
+ function getIndent(line) {
774
+ const match = line.match(/^(\s*)/);
775
+ return match ? match[1] : "";
776
+ }
777
+ function extractTitleAndFields(content) {
778
+ const fields = extractFields(content);
779
+ const pipeIndex = content.indexOf("|");
780
+ const title = pipeIndex >= 0 ? content.substring(0, pipeIndex).trim() : content.trim();
781
+ return { title, fields };
782
+ }
783
+ function extractFields(content) {
784
+ const fields = {};
785
+ let match;
786
+ FIELD_PATTERN.lastIndex = 0;
787
+ while ((match = FIELD_PATTERN.exec(content)) !== null) {
788
+ const key = match[1];
789
+ const value = match[2].trim();
790
+ fields[key] = value;
791
+ }
792
+ return fields;
793
+ }
794
+ function finalizeComment(partial, filePath) {
795
+ const content = partial.content || partial.title || "";
796
+ const hash = createContentHash(partial.tag, partial.title, content);
797
+ return {
798
+ tag: partial.tag,
799
+ category: partial.category,
800
+ title: partial.title,
801
+ content,
802
+ fields: partial.fields || {},
803
+ file: filePath,
804
+ line: partial.line,
805
+ hash
806
+ };
807
+ }
808
+ function createContentHash(tag, title, content) {
809
+ const normalized = `${tag}:${title}:${content}`.toLowerCase().replace(/\s+/g, " ");
810
+ return createHash("sha256").update(normalized).digest("hex").substring(0, 16);
811
+ }
812
+ function scanCommentToEntry(comment, projectId) {
813
+ const metadata = {
814
+ source: "scan",
815
+ file: comment.file,
816
+ line: comment.line,
817
+ hash: comment.hash,
818
+ category: comment.category,
819
+ ...comment.fields
820
+ };
821
+ if (comment.tag === "decision") {
822
+ return {
823
+ type: "decision",
824
+ title: comment.title,
825
+ content: comment.fields.rationale || comment.content,
826
+ metadata: {
827
+ ...metadata,
828
+ alternatives: comment.fields.alternatives,
829
+ consequences: comment.fields.consequences,
830
+ status: comment.fields.status || "active"
831
+ }
832
+ };
833
+ }
834
+ if (comment.tag === "pattern") {
835
+ return {
836
+ type: "pattern",
837
+ title: comment.title,
838
+ content: comment.fields.template || comment.content,
839
+ metadata: {
840
+ ...metadata,
841
+ when: comment.fields.when
842
+ }
843
+ };
844
+ }
845
+ return {
846
+ type: "context",
847
+ title: "Active Context",
848
+ content: comment.content,
849
+ metadata
850
+ };
851
+ }
852
+
853
+ // src/rules-parser.ts
854
+ function parseRulesFile(content) {
855
+ const lines = content.split("\n");
856
+ const result = {
857
+ stack: [],
858
+ decisions: [],
859
+ patterns: [],
860
+ context: null,
861
+ documents: []
862
+ };
863
+ let currentSection = null;
864
+ let currentContent = [];
865
+ for (let i = 0; i < lines.length; i++) {
866
+ const line = lines[i];
867
+ const headingMatch = line.match(/^#+\s+(.+)/);
868
+ if (headingMatch) {
869
+ if (currentSection && currentContent.length > 0) {
870
+ processSection(currentSection, currentContent.join("\n"), result);
871
+ }
872
+ currentSection = headingMatch[1].trim();
873
+ currentContent = [];
874
+ continue;
875
+ }
876
+ if (currentSection && line.trim()) {
877
+ currentContent.push(line);
878
+ }
879
+ }
880
+ if (currentSection && currentContent.length > 0) {
881
+ processSection(currentSection, currentContent.join("\n"), result);
882
+ }
883
+ return result;
884
+ }
885
+ function processSection(heading, content, result) {
886
+ const headingLower = heading.toLowerCase();
887
+ if (headingLower === "stack" || headingLower === "tech stack") {
888
+ result.stack = parseListItems(content);
889
+ } else if (headingLower === "decisions") {
890
+ result.decisions = parseDecisions(content);
891
+ } else if (headingLower === "patterns") {
892
+ result.patterns = parsePatterns(content);
893
+ } else if (headingLower === "context") {
894
+ result.context = parseContext(content);
895
+ } else {
896
+ result.documents.push({
897
+ type: "document",
898
+ title: heading,
899
+ content: content.trim(),
900
+ metadata: { source: "rules", section: heading }
901
+ });
902
+ }
903
+ }
904
+ function parseListItems(content) {
905
+ const items = [];
906
+ const lines = content.split("\n");
907
+ for (const line of lines) {
908
+ const match = line.match(/^[-*]\s+(.+)/);
909
+ if (match) {
910
+ items.push(match[1].trim());
911
+ }
912
+ }
913
+ return items;
914
+ }
915
+ function parseDecisions(content) {
916
+ const decisions = [];
917
+ const items = parseListItems(content);
918
+ for (const item of items) {
919
+ const match = item.match(/^(.+?)\s*\(([^)]+)\)\s*$/);
920
+ if (match) {
921
+ const title = match[1].trim();
922
+ const rationale = match[2].trim();
923
+ decisions.push({
924
+ type: "decision",
925
+ title,
926
+ content: rationale,
927
+ metadata: {
928
+ source: "rules",
929
+ rationale
930
+ }
931
+ });
932
+ } else {
933
+ decisions.push({
934
+ type: "decision",
935
+ title: item,
936
+ content: item,
937
+ metadata: { source: "rules" }
938
+ });
939
+ }
940
+ }
941
+ return decisions;
942
+ }
943
+ function parsePatterns(content) {
944
+ const patterns = [];
945
+ const items = parseListItems(content);
946
+ for (const item of items) {
947
+ const colonIndex = item.indexOf(":");
948
+ if (colonIndex >= 0) {
949
+ const name = item.substring(0, colonIndex).trim();
950
+ const description = item.substring(colonIndex + 1).trim();
951
+ patterns.push({
952
+ type: "pattern",
953
+ title: name,
954
+ content: description,
955
+ metadata: { source: "rules" }
956
+ });
957
+ } else {
958
+ patterns.push({
959
+ type: "pattern",
960
+ title: item,
961
+ content: item,
962
+ metadata: { source: "rules" }
963
+ });
964
+ }
965
+ }
966
+ return patterns;
967
+ }
968
+ function parseContext(content) {
969
+ return {
970
+ type: "context",
971
+ title: "Active Context",
972
+ content: content.trim(),
973
+ metadata: { source: "rules" }
974
+ };
975
+ }
976
+ function generateRulesFile(entries) {
977
+ const sections = [];
978
+ if (entries.stack.length > 0) {
979
+ sections.push("# Stack\n");
980
+ for (const item of entries.stack) {
981
+ sections.push(`- ${item}`);
982
+ }
983
+ sections.push("");
984
+ }
985
+ if (entries.decisions.length > 0) {
986
+ sections.push("# Decisions\n");
987
+ for (const decision of entries.decisions) {
988
+ const rationale = decision.metadata.rationale || decision.content;
989
+ sections.push(`- ${decision.title} (${rationale})`);
990
+ }
991
+ sections.push("");
992
+ }
993
+ if (entries.patterns.length > 0) {
994
+ sections.push("# Patterns\n");
995
+ for (const pattern of entries.patterns) {
996
+ sections.push(`- ${pattern.title}: ${pattern.content}`);
997
+ }
998
+ sections.push("");
999
+ }
1000
+ if (entries.context.length > 0) {
1001
+ sections.push("# Context\n");
1002
+ sections.push(entries.context[0].content);
1003
+ sections.push("");
1004
+ }
1005
+ for (const doc of entries.documents) {
1006
+ sections.push(`# ${doc.title}
1007
+ `);
1008
+ sections.push(doc.content);
1009
+ sections.push("");
1010
+ }
1011
+ return sections.join("\n");
1012
+ }
1013
+ export {
1014
+ AuthError,
1015
+ ConflictError,
1016
+ MemoryEngine,
1017
+ NotFoundError,
1018
+ SyncEngine,
1019
+ ValidationError,
1020
+ buildContext,
1021
+ buildContextPayload,
1022
+ buildContextSummary,
1023
+ calculateRelevance,
1024
+ cleanup,
1025
+ countEntryTokens,
1026
+ countTokens,
1027
+ fitToBudget,
1028
+ formatEntryForContext,
1029
+ generateRulesFile,
1030
+ getMatchReasons,
1031
+ parseFile,
1032
+ parseRulesFile,
1033
+ rankEntries,
1034
+ scanCommentToEntry
1035
+ };
1036
+ //# sourceMappingURL=index.js.map