@snapback/cli 1.1.12 → 1.1.14

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.
Files changed (41) hide show
  1. package/README.md +9 -9
  2. package/dist/{analysis-Z53F5FT2.js → analysis-C6XVLBAL.js} +3 -3
  3. package/dist/{analysis-Z53F5FT2.js.map → analysis-C6XVLBAL.js.map} +1 -1
  4. package/dist/{chunk-KPETDXQO.js → chunk-2TOJVUVJ.js} +296 -33
  5. package/dist/chunk-2TOJVUVJ.js.map +1 -0
  6. package/dist/chunk-5EQLSU5B.js +385 -0
  7. package/dist/chunk-5EQLSU5B.js.map +1 -0
  8. package/dist/{chunk-YOVA65PS.js → chunk-A3TUM7U4.js} +320 -63
  9. package/dist/chunk-A3TUM7U4.js.map +1 -0
  10. package/dist/{chunk-ISVRGBWT.js → chunk-LEXNOXPV.js} +6030 -632
  11. package/dist/chunk-LEXNOXPV.js.map +1 -0
  12. package/dist/{chunk-G7QXHNGB.js → chunk-OJNDAPC2.js} +41 -15
  13. package/dist/chunk-OJNDAPC2.js.map +1 -0
  14. package/dist/{chunk-NKBZIXCN.js → chunk-Q5XZ3DCB.js} +5 -5
  15. package/dist/{chunk-NKBZIXCN.js.map → chunk-Q5XZ3DCB.js.map} +1 -1
  16. package/dist/chunk-QLCHTUT5.js +1067 -0
  17. package/dist/chunk-QLCHTUT5.js.map +1 -0
  18. package/dist/dist-D2SHOZMS.js +8 -0
  19. package/dist/{dist-7UKXVKH3.js.map → dist-D2SHOZMS.js.map} +1 -1
  20. package/dist/{dist-7UKXVKH3.js → dist-L76VXYJ5.js} +3 -3
  21. package/dist/{dist-VDK7WEF4.js.map → dist-L76VXYJ5.js.map} +1 -1
  22. package/dist/dist-RPM72FHJ.js +5 -0
  23. package/dist/{dist-WKLJSPJT.js.map → dist-RPM72FHJ.js.map} +1 -1
  24. package/dist/index.js +38672 -24130
  25. package/dist/index.js.map +1 -1
  26. package/dist/learning-pruner-YSZSOOOC.js +7 -0
  27. package/dist/learning-pruner-YSZSOOOC.js.map +1 -0
  28. package/dist/{secure-credentials-6UMEU22H.js → secure-credentials-A4QHHOE2.js} +14 -6
  29. package/dist/secure-credentials-A4QHHOE2.js.map +1 -0
  30. package/dist/{snapback-dir-T3CRQRY6.js → snapback-dir-6QUSO6Y3.js} +3 -3
  31. package/dist/{snapback-dir-T3CRQRY6.js.map → snapback-dir-6QUSO6Y3.js.map} +1 -1
  32. package/dist/storage-H366UNAR.js +6 -0
  33. package/dist/storage-H366UNAR.js.map +1 -0
  34. package/package.json +8 -9
  35. package/dist/chunk-G7QXHNGB.js.map +0 -1
  36. package/dist/chunk-ISVRGBWT.js.map +0 -1
  37. package/dist/chunk-KPETDXQO.js.map +0 -1
  38. package/dist/chunk-YOVA65PS.js.map +0 -1
  39. package/dist/dist-VDK7WEF4.js +0 -5
  40. package/dist/dist-WKLJSPJT.js +0 -8
  41. package/dist/secure-credentials-6UMEU22H.js.map +0 -1
@@ -0,0 +1,1067 @@
1
+ #!/usr/bin/env node
2
+ import { generateId } from './chunk-6MR2TINI.js';
3
+ import { __name, __require } from './chunk-BW7RALUZ.js';
4
+ import * as fs2 from 'fs';
5
+ import * as path3 from 'path';
6
+ import { writeFile } from 'atomically';
7
+ import * as crypto from 'crypto';
8
+ import * as zlib from 'zlib';
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __name2 = /* @__PURE__ */ __name((target, value) => __defProp(target, "name", {
12
+ value,
13
+ configurable: true
14
+ }), "__name");
15
+ var __require2 = /* @__PURE__ */ ((x) => typeof __require !== "undefined" ? __require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
+ get: /* @__PURE__ */ __name((a, b) => (typeof __require !== "undefined" ? __require : a)[b], "get")
17
+ }) : x)(function(x) {
18
+ if (typeof __require !== "undefined") return __require.apply(this, arguments);
19
+ throw Error('Dynamic require of "' + x + '" is not supported');
20
+ });
21
+ var ConfigStore = class {
22
+ static {
23
+ __name(this, "ConfigStore");
24
+ }
25
+ static {
26
+ __name2(this, "ConfigStore");
27
+ }
28
+ config;
29
+ cache = {};
30
+ constructor(config) {
31
+ this.config = config;
32
+ }
33
+ /**
34
+ * Get the root directory for intelligence data
35
+ */
36
+ get rootDir() {
37
+ return this.config.rootDir;
38
+ }
39
+ /**
40
+ * Resolve a path relative to rootDir
41
+ */
42
+ resolvePath(relativePath) {
43
+ return path3.join(this.config.rootDir, relativePath);
44
+ }
45
+ /**
46
+ * Load architecture documentation
47
+ */
48
+ loadArchitecture() {
49
+ if (this.cache.architecture) {
50
+ return this.cache.architecture;
51
+ }
52
+ const archPath = this.resolvePath("ARCHITECTURE.md");
53
+ if (fs2.existsSync(archPath)) {
54
+ this.cache.architecture = fs2.readFileSync(archPath, "utf-8");
55
+ } else {
56
+ this.cache.architecture = "";
57
+ }
58
+ return this.cache.architecture;
59
+ }
60
+ /**
61
+ * Load constraints/rules
62
+ */
63
+ loadConstraints() {
64
+ if (this.cache.constraints) {
65
+ return this.cache.constraints;
66
+ }
67
+ const constraintsPath = this.resolvePath(this.config.constraintsFile);
68
+ if (fs2.existsSync(constraintsPath)) {
69
+ this.cache.constraints = fs2.readFileSync(constraintsPath, "utf-8");
70
+ } else {
71
+ this.cache.constraints = "";
72
+ }
73
+ return this.cache.constraints;
74
+ }
75
+ /**
76
+ * Load codebase patterns
77
+ */
78
+ loadPatterns() {
79
+ if (this.cache.patterns) {
80
+ return this.cache.patterns;
81
+ }
82
+ const patternsPath = this.resolvePath(path3.join(this.config.patternsDir, "codebase-patterns.md"));
83
+ if (fs2.existsSync(patternsPath)) {
84
+ this.cache.patterns = fs2.readFileSync(patternsPath, "utf-8");
85
+ } else {
86
+ this.cache.patterns = "";
87
+ }
88
+ return this.cache.patterns;
89
+ }
90
+ /**
91
+ * Load any context file by name
92
+ */
93
+ loadContextFile(filename) {
94
+ const filePath = this.resolvePath(filename);
95
+ if (fs2.existsSync(filePath)) {
96
+ return fs2.readFileSync(filePath, "utf-8");
97
+ }
98
+ return "";
99
+ }
100
+ /**
101
+ * Get static context suitable for prompt caching
102
+ *
103
+ * Content changes rarely - cache for 5+ minutes.
104
+ * Marked with cache_control for Anthropic prompt caching.
105
+ *
106
+ * @see https://docs.anthropic.com/claude/docs/prompt-caching
107
+ */
108
+ getStaticContext() {
109
+ return {
110
+ architecture: this.loadArchitecture(),
111
+ constraints: this.loadConstraints(),
112
+ patterns: this.loadPatterns(),
113
+ timestamp: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
114
+ _cache_control: {
115
+ type: "ephemeral"
116
+ }
117
+ };
118
+ }
119
+ /**
120
+ * Clear the cache (for testing or after file changes)
121
+ */
122
+ clearCache() {
123
+ this.cache = {};
124
+ }
125
+ /**
126
+ * Check if a context file exists
127
+ */
128
+ contextFileExists(filename) {
129
+ return fs2.existsSync(this.resolvePath(filename));
130
+ }
131
+ /**
132
+ * List all context files that exist
133
+ */
134
+ listAvailableContextFiles() {
135
+ return this.config.contextFiles.filter((f) => this.contextFileExists(f));
136
+ }
137
+ };
138
+ function loadJsonl(filepath) {
139
+ if (!fs2.existsSync(filepath)) {
140
+ return [];
141
+ }
142
+ try {
143
+ return fs2.readFileSync(filepath, "utf-8").split(/\r?\n/).filter((line) => line.trim()).map((line) => JSON.parse(line));
144
+ } catch (e) {
145
+ console.error(`[JsonlStore] Error loading ${filepath}:`, e);
146
+ return [];
147
+ }
148
+ }
149
+ __name(loadJsonl, "loadJsonl");
150
+ __name2(loadJsonl, "loadJsonl");
151
+ function appendJsonl(filepath, data) {
152
+ const dir = path3.dirname(filepath);
153
+ if (!fs2.existsSync(dir)) {
154
+ fs2.mkdirSync(dir, {
155
+ recursive: true
156
+ });
157
+ }
158
+ fs2.appendFileSync(filepath, `${JSON.stringify(data)}
159
+ `);
160
+ }
161
+ __name(appendJsonl, "appendJsonl");
162
+ __name2(appendJsonl, "appendJsonl");
163
+ async function appendJsonlAsync(filepath, data) {
164
+ const dir = path3.dirname(filepath);
165
+ if (!fs2.existsSync(dir)) {
166
+ fs2.mkdirSync(dir, {
167
+ recursive: true
168
+ });
169
+ }
170
+ try {
171
+ const existing = fs2.existsSync(filepath) ? fs2.readFileSync(filepath, "utf-8") : "";
172
+ await writeFile(filepath, `${existing + JSON.stringify(data)}
173
+ `);
174
+ } catch {
175
+ fs2.appendFileSync(filepath, `${JSON.stringify(data)}
176
+ `);
177
+ }
178
+ }
179
+ __name(appendJsonlAsync, "appendJsonlAsync");
180
+ __name2(appendJsonlAsync, "appendJsonlAsync");
181
+ async function writeJsonl(filepath, records) {
182
+ const dir = path3.dirname(filepath);
183
+ if (!fs2.existsSync(dir)) {
184
+ fs2.mkdirSync(dir, {
185
+ recursive: true
186
+ });
187
+ }
188
+ const content = `${records.map((r) => JSON.stringify(r)).join("\n")}
189
+ `;
190
+ try {
191
+ await writeFile(filepath, content);
192
+ } catch {
193
+ fs2.writeFileSync(filepath, content);
194
+ }
195
+ }
196
+ __name(writeJsonl, "writeJsonl");
197
+ __name2(writeJsonl, "writeJsonl");
198
+ function findInJsonl(filepath, predicate) {
199
+ const records = loadJsonl(filepath);
200
+ return records.find(predicate);
201
+ }
202
+ __name(findInJsonl, "findInJsonl");
203
+ __name2(findInJsonl, "findInJsonl");
204
+ async function updateInJsonl(filepath, predicate, updater) {
205
+ const records = loadJsonl(filepath);
206
+ let updated = false;
207
+ const newRecords = records.map((record) => {
208
+ if (predicate(record)) {
209
+ updated = true;
210
+ return updater(record);
211
+ }
212
+ return record;
213
+ });
214
+ if (updated) {
215
+ await writeJsonl(filepath, newRecords);
216
+ }
217
+ return updated;
218
+ }
219
+ __name(updateInJsonl, "updateInJsonl");
220
+ __name2(updateInJsonl, "updateInJsonl");
221
+ function countInJsonl(filepath, predicate) {
222
+ const records = loadJsonl(filepath);
223
+ if (!predicate) {
224
+ return records.length;
225
+ }
226
+ return records.filter(predicate).length;
227
+ }
228
+ __name(countInJsonl, "countInJsonl");
229
+ __name2(countInJsonl, "countInJsonl");
230
+ function generateId2(prefix = "ID") {
231
+ return generateId(prefix);
232
+ }
233
+ __name(generateId2, "generateId");
234
+ __name2(generateId2, "generateId");
235
+ var DEFAULT_INTENT_CONFIG = {
236
+ maxResults: 10,
237
+ minScore: 0.3,
238
+ intentWeight: 0.4,
239
+ componentWeight: 0.3,
240
+ fileAffinityWeight: 0.2,
241
+ keywordWeight: 0.1,
242
+ includeRelated: true
243
+ };
244
+ function inferComponentType(filePath) {
245
+ const normalized = filePath.toLowerCase();
246
+ const filename = normalized.split("/").pop() ?? "";
247
+ if (normalized.includes("test") || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
248
+ return "test";
249
+ }
250
+ if (normalized.includes("hooks/") || filename.startsWith("use-")) {
251
+ return "hook";
252
+ }
253
+ if (normalized.includes("components/") || normalized.endsWith(".tsx") || normalized.endsWith(".jsx")) {
254
+ return "component";
255
+ }
256
+ if (normalized.includes("api/") || normalized.includes("routes/") || normalized.includes("endpoints/")) {
257
+ return "api";
258
+ }
259
+ if (normalized.includes("migrations/") || normalized.includes("drizzle/")) {
260
+ return "migration";
261
+ }
262
+ if (normalized.includes("utils/") || normalized.includes("lib/")) {
263
+ return "util";
264
+ }
265
+ if (normalized.includes("types/") || normalized.endsWith(".d.ts")) {
266
+ return "type";
267
+ }
268
+ if (normalized.includes("config") || normalized.endsWith(".config.ts") || normalized.endsWith(".config.js")) {
269
+ return "config";
270
+ }
271
+ if (normalized.includes("tools/") || normalized.includes("mcp/")) {
272
+ return "tool";
273
+ }
274
+ return void 0;
275
+ }
276
+ __name(inferComponentType, "inferComponentType");
277
+ __name2(inferComponentType, "inferComponentType");
278
+ function generateFileAffinityPatterns(filePaths) {
279
+ const patterns = /* @__PURE__ */ new Set();
280
+ for (const filePath of filePaths) {
281
+ const parts = filePath.split("/");
282
+ if (parts.length > 1) {
283
+ patterns.add(`${parts[0]}/**`);
284
+ if (parts.length > 2) {
285
+ patterns.add(`${parts.slice(0, 2).join("/")}/**`);
286
+ }
287
+ }
288
+ const ext = filePath.split(".").pop();
289
+ if (ext) {
290
+ patterns.add(`**/*.${ext}`);
291
+ }
292
+ const filename = parts[parts.length - 1];
293
+ if (filename?.startsWith("use-")) {
294
+ patterns.add("**/use-*.ts");
295
+ patterns.add("**/use-*.tsx");
296
+ }
297
+ }
298
+ return Array.from(patterns);
299
+ }
300
+ __name(generateFileAffinityPatterns, "generateFileAffinityPatterns");
301
+ __name2(generateFileAffinityPatterns, "generateFileAffinityPatterns");
302
+ function matchGlob(pattern, text) {
303
+ const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<<GLOBSTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<GLOBSTAR>>>/g, ".*").replace(/\?/g, ".");
304
+ const regex = new RegExp(`^${regexPattern}$`, "i");
305
+ return regex.test(text);
306
+ }
307
+ __name(matchGlob, "matchGlob");
308
+ __name2(matchGlob, "matchGlob");
309
+ var MAGIC_BYTES = Buffer.from("SNAPBACK", "utf8");
310
+ var HEADER_SIZE = 16;
311
+ var CURRENT_VERSION = 1;
312
+ var FLAG_ENCRYPTED = 1;
313
+ function createEmptyState() {
314
+ return {
315
+ schemaVersion: 1,
316
+ lastModified: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
317
+ learnings: [],
318
+ violations: [],
319
+ patterns: [],
320
+ sessions: [],
321
+ pendingSync: [],
322
+ syncVersion: 0,
323
+ syncStatus: "pending"
324
+ };
325
+ }
326
+ __name(createEmptyState, "createEmptyState");
327
+ __name2(createEmptyState, "createEmptyState");
328
+ var StateStore = class {
329
+ static {
330
+ __name(this, "StateStore");
331
+ }
332
+ static {
333
+ __name2(this, "StateStore");
334
+ }
335
+ statePath;
336
+ encrypt;
337
+ encryptionKey;
338
+ state;
339
+ dirty = false;
340
+ constructor(options) {
341
+ this.statePath = path3.join(options.snapbackDir, "state.bin");
342
+ this.encrypt = options.encrypt ?? false;
343
+ this.encryptionKey = options.encryptionKey;
344
+ if (this.encrypt && (!this.encryptionKey || this.encryptionKey.length !== 32)) {
345
+ throw new Error("Encryption requires a 32-byte key");
346
+ }
347
+ this.state = createEmptyState();
348
+ }
349
+ /**
350
+ * Load state from disk
351
+ */
352
+ async load() {
353
+ if (!fs2.existsSync(this.statePath)) {
354
+ this.state = createEmptyState();
355
+ return this.state;
356
+ }
357
+ try {
358
+ const raw = await fs2.promises.readFile(this.statePath);
359
+ this.state = this.deserialize(raw);
360
+ return this.state;
361
+ } catch (error) {
362
+ console.error("[StateStore] Failed to load state, starting fresh:", error);
363
+ this.state = createEmptyState();
364
+ return this.state;
365
+ }
366
+ }
367
+ /**
368
+ * Save state to disk
369
+ */
370
+ async save() {
371
+ this.state.lastModified = /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString();
372
+ const serialized = this.serialize(this.state);
373
+ const dir = path3.dirname(this.statePath);
374
+ if (!fs2.existsSync(dir)) {
375
+ await fs2.promises.mkdir(dir, {
376
+ recursive: true
377
+ });
378
+ }
379
+ const tempPath = `${this.statePath}.tmp`;
380
+ await fs2.promises.writeFile(tempPath, serialized);
381
+ await fs2.promises.rename(tempPath, this.statePath);
382
+ this.dirty = false;
383
+ }
384
+ /**
385
+ * Get current state (readonly)
386
+ */
387
+ getState() {
388
+ return this.state;
389
+ }
390
+ /**
391
+ * Check if state has unsaved changes
392
+ */
393
+ isDirty() {
394
+ return this.dirty;
395
+ }
396
+ // ============================================================================
397
+ // LEARNINGS
398
+ // ============================================================================
399
+ getLearnings(tier) {
400
+ if (!tier) {
401
+ return [
402
+ ...this.state.learnings
403
+ ];
404
+ }
405
+ return this.state.learnings.filter((l) => l.tier === tier);
406
+ }
407
+ addLearning(learning) {
408
+ const newLearning = {
409
+ ...learning,
410
+ id: crypto.randomUUID(),
411
+ createdAt: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
412
+ accessCount: 0,
413
+ appliedCount: 0,
414
+ relevanceScore: 1
415
+ };
416
+ this.state.learnings.push(newLearning);
417
+ this.dirty = true;
418
+ return newLearning;
419
+ }
420
+ updateLearning(id, updates) {
421
+ const index = this.state.learnings.findIndex((l) => l.id === id);
422
+ if (index === -1) {
423
+ return false;
424
+ }
425
+ this.state.learnings[index] = {
426
+ ...this.state.learnings[index],
427
+ ...updates
428
+ };
429
+ this.dirty = true;
430
+ return true;
431
+ }
432
+ /**
433
+ * Archive a learning (Phase 2.6b: set archived flag, preserve data)
434
+ */
435
+ archiveLearning(learningId) {
436
+ const learning = this.state.learnings.find((l) => l.id === learningId);
437
+ if (!learning) {
438
+ return false;
439
+ }
440
+ learning.archived = true;
441
+ learning.archivedAt = /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString();
442
+ this.dirty = true;
443
+ return true;
444
+ }
445
+ /**
446
+ * Delete a learning permanently (Phase 2.6b: remove from state)
447
+ */
448
+ deleteLearning(learningId) {
449
+ const index = this.state.learnings.findIndex((l) => l.id === learningId);
450
+ if (index === -1) {
451
+ return false;
452
+ }
453
+ this.state.learnings.splice(index, 1);
454
+ this.dirty = true;
455
+ return true;
456
+ }
457
+ /**
458
+ * Get archived learnings (Phase 2.6b)
459
+ */
460
+ getArchivedLearnings() {
461
+ return this.state.learnings.filter((l) => l.archived === true);
462
+ }
463
+ // ============================================================================
464
+ // INTENT-AWARE RETRIEVAL (Phase 2.6c)
465
+ // ============================================================================
466
+ /**
467
+ * Retrieve learnings using task-intent graph indexing
468
+ * Based on Cursor RAG + A-MEM research patterns
469
+ *
470
+ * @param context - Task context with intent, files, and keywords
471
+ * @param config - Optional retrieval configuration
472
+ * @returns Scored learnings ranked by relevance
473
+ */
474
+ retrieveByIntent(context, config = {}) {
475
+ const fullConfig = {
476
+ ...DEFAULT_INTENT_CONFIG,
477
+ ...config
478
+ };
479
+ const componentType = context.componentType ?? this.inferDominantComponentType(context.files);
480
+ const scored = [];
481
+ for (const learning of this.state.learnings) {
482
+ if (learning.archived) {
483
+ continue;
484
+ }
485
+ const score = this.scoreLearning(learning, context, componentType, fullConfig);
486
+ if (score.totalScore >= fullConfig.minScore) {
487
+ scored.push({
488
+ learningId: learning.id,
489
+ score: score.totalScore,
490
+ matchReasons: score.reasons
491
+ });
492
+ }
493
+ }
494
+ scored.sort((a, b) => b.score - a.score);
495
+ if (fullConfig.includeRelated) {
496
+ this.expandWithRelated(scored, fullConfig.maxResults);
497
+ }
498
+ return scored.slice(0, fullConfig.maxResults);
499
+ }
500
+ /**
501
+ * Enrich a learning with intent metadata
502
+ * Called when creating/updating learnings with file context
503
+ */
504
+ enrichWithIntentMetadata(learningId, context) {
505
+ const learning = this.state.learnings.find((l) => l.id === learningId);
506
+ if (!learning) {
507
+ return false;
508
+ }
509
+ const existingMeta = learning.intentMetadata;
510
+ const componentTypes = context.files.map((f) => inferComponentType(f)).filter(Boolean);
511
+ const fileAffinities = generateFileAffinityPatterns(context.files);
512
+ learning.intentMetadata = {
513
+ intents: existingMeta ? Array.from(/* @__PURE__ */ new Set([
514
+ ...existingMeta.intents,
515
+ context.intent
516
+ ])) : [
517
+ context.intent
518
+ ],
519
+ componentTypes: existingMeta ? Array.from(/* @__PURE__ */ new Set([
520
+ ...existingMeta.componentTypes,
521
+ ...componentTypes
522
+ ])) : componentTypes,
523
+ fileAffinities: existingMeta ? Array.from(/* @__PURE__ */ new Set([
524
+ ...existingMeta.fileAffinities,
525
+ ...fileAffinities
526
+ ])) : fileAffinities,
527
+ relatedLearnings: existingMeta?.relatedLearnings ?? [],
528
+ intentSuccessCount: {
529
+ ...existingMeta?.intentSuccessCount ?? {},
530
+ [context.intent]: (existingMeta?.intentSuccessCount?.[context.intent] ?? 0) + 1
531
+ }
532
+ };
533
+ this.dirty = true;
534
+ return true;
535
+ }
536
+ /**
537
+ * Link two learnings together (Zettelkasten-style)
538
+ */
539
+ linkLearnings(learningId1, learningId2) {
540
+ const learning1 = this.state.learnings.find((l) => l.id === learningId1);
541
+ const learning2 = this.state.learnings.find((l) => l.id === learningId2);
542
+ if (!learning1 || !learning2) {
543
+ return false;
544
+ }
545
+ if (!learning1.intentMetadata) {
546
+ learning1.intentMetadata = {
547
+ intents: [],
548
+ componentTypes: [],
549
+ fileAffinities: [],
550
+ relatedLearnings: [],
551
+ intentSuccessCount: {}
552
+ };
553
+ }
554
+ if (!learning2.intentMetadata) {
555
+ learning2.intentMetadata = {
556
+ intents: [],
557
+ componentTypes: [],
558
+ fileAffinities: [],
559
+ relatedLearnings: [],
560
+ intentSuccessCount: {}
561
+ };
562
+ }
563
+ if (!learning1.intentMetadata.relatedLearnings.includes(learningId2)) {
564
+ learning1.intentMetadata.relatedLearnings.push(learningId2);
565
+ }
566
+ if (!learning2.intentMetadata.relatedLearnings.includes(learningId1)) {
567
+ learning2.intentMetadata.relatedLearnings.push(learningId1);
568
+ }
569
+ this.dirty = true;
570
+ return true;
571
+ }
572
+ /**
573
+ * Private: Score a learning against task context
574
+ */
575
+ scoreLearning(learning, context, componentType, config) {
576
+ const meta = learning.intentMetadata;
577
+ const reasons = {
578
+ intentMatch: false,
579
+ componentMatch: false,
580
+ fileAffinityMatch: false,
581
+ keywordMatch: false,
582
+ relationshipMatch: false
583
+ };
584
+ let totalScore = 0;
585
+ if (meta?.intents.includes(context.intent)) {
586
+ reasons.intentMatch = true;
587
+ totalScore += config.intentWeight;
588
+ }
589
+ if (componentType && meta?.componentTypes.includes(componentType)) {
590
+ reasons.componentMatch = true;
591
+ totalScore += config.componentWeight;
592
+ }
593
+ if (meta?.fileAffinities.length) {
594
+ const matchCount = context.files.filter((file) => meta.fileAffinities.some((pattern) => matchGlob(pattern, file))).length;
595
+ if (matchCount > 0) {
596
+ reasons.fileAffinityMatch = true;
597
+ totalScore += config.fileAffinityWeight * (matchCount / context.files.length);
598
+ }
599
+ }
600
+ if (context.keywords?.length && learning.keywords?.length) {
601
+ const matchCount = context.keywords.filter((kw) => learning.keywords?.includes(kw)).length;
602
+ if (matchCount > 0) {
603
+ reasons.keywordMatch = true;
604
+ totalScore += config.keywordWeight * (matchCount / context.keywords.length);
605
+ }
606
+ }
607
+ return {
608
+ totalScore,
609
+ reasons
610
+ };
611
+ }
612
+ /**
613
+ * Private: Infer dominant component type from file list
614
+ */
615
+ inferDominantComponentType(files) {
616
+ const types = files.map((f) => inferComponentType(f)).filter((t) => t !== void 0);
617
+ if (types.length === 0) {
618
+ return void 0;
619
+ }
620
+ const counts = /* @__PURE__ */ new Map();
621
+ for (const type of types) {
622
+ counts.set(type, (counts.get(type) ?? 0) + 1);
623
+ }
624
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
625
+ }
626
+ /**
627
+ * Private: Expand results with related learnings via graph traversal
628
+ */
629
+ expandWithRelated(scored, _maxResults) {
630
+ const visited = new Set(scored.map((s) => s.learningId));
631
+ const toAdd = [];
632
+ for (const item of scored) {
633
+ const learning = this.state.learnings.find((l) => l.id === item.learningId);
634
+ if (!learning?.intentMetadata?.relatedLearnings.length) {
635
+ continue;
636
+ }
637
+ for (const relatedId of learning.intentMetadata.relatedLearnings) {
638
+ if (visited.has(relatedId)) {
639
+ continue;
640
+ }
641
+ visited.add(relatedId);
642
+ toAdd.push({
643
+ learningId: relatedId,
644
+ score: item.score * 0.7,
645
+ matchReasons: {
646
+ intentMatch: false,
647
+ componentMatch: false,
648
+ fileAffinityMatch: false,
649
+ keywordMatch: false,
650
+ relationshipMatch: true
651
+ }
652
+ });
653
+ }
654
+ }
655
+ scored.push(...toAdd);
656
+ scored.sort((a, b) => b.score - a.score);
657
+ }
658
+ // ============================================================================
659
+ // VIOLATIONS
660
+ // ============================================================================
661
+ getViolations() {
662
+ return [
663
+ ...this.state.violations
664
+ ];
665
+ }
666
+ recordViolation(violation) {
667
+ const existing = this.state.violations.find((v) => v.type === violation.type && v.file === violation.file);
668
+ if (existing) {
669
+ existing.count++;
670
+ existing.lastSeen = /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString();
671
+ this.dirty = true;
672
+ return existing;
673
+ }
674
+ const newViolation = {
675
+ ...violation,
676
+ id: crypto.randomUUID(),
677
+ count: 1,
678
+ firstSeen: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
679
+ lastSeen: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString()
680
+ };
681
+ this.state.violations.push(newViolation);
682
+ this.dirty = true;
683
+ return newViolation;
684
+ }
685
+ /**
686
+ * Remove a violation by ID
687
+ *
688
+ * @param violationId - Violation ID to remove
689
+ * @returns true if violation was removed, false if not found
690
+ */
691
+ removeViolation(violationId) {
692
+ const index = this.state.violations.findIndex((v) => v.id === violationId);
693
+ if (index === -1) {
694
+ return false;
695
+ }
696
+ this.state.violations.splice(index, 1);
697
+ this.dirty = true;
698
+ return true;
699
+ }
700
+ // ============================================================================
701
+ // PATTERNS
702
+ // ============================================================================
703
+ getPatterns() {
704
+ return [
705
+ ...this.state.patterns
706
+ ];
707
+ }
708
+ addPattern(pattern) {
709
+ const newPattern = {
710
+ ...pattern,
711
+ id: crypto.randomUUID(),
712
+ createdAt: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
713
+ usageCount: 0
714
+ };
715
+ this.state.patterns.push(newPattern);
716
+ this.dirty = true;
717
+ return newPattern;
718
+ }
719
+ // ============================================================================
720
+ // SESSIONS
721
+ // ============================================================================
722
+ getSessions() {
723
+ return [
724
+ ...this.state.sessions
725
+ ];
726
+ }
727
+ saveSession(session) {
728
+ const existing = session.id ? this.state.sessions.find((s) => s.id === session.id) : void 0;
729
+ if (existing) {
730
+ Object.assign(existing, session, {
731
+ lastCheckpoint: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString()
732
+ });
733
+ this.dirty = true;
734
+ return existing;
735
+ }
736
+ const newSession = {
737
+ id: session.id || crypto.randomUUID(),
738
+ taskDescription: session.taskDescription,
739
+ startedAt: session.startedAt,
740
+ snapshotId: session.snapshotId,
741
+ context: session.context,
742
+ appliedLearnings: session.appliedLearnings || [],
743
+ lastCheckpoint: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString()
744
+ };
745
+ this.state.sessions.push(newSession);
746
+ this.dirty = true;
747
+ return newSession;
748
+ }
749
+ /**
750
+ * Update applied learnings for a session (Phase 2.6a)
751
+ * Atomic operation for repetition avoidance
752
+ */
753
+ updateSessionLearnings(sessionId, learningIds) {
754
+ const session = this.state.sessions.find((s) => s.id === sessionId);
755
+ if (!session) {
756
+ return false;
757
+ }
758
+ const existing = new Set(session.appliedLearnings || []);
759
+ for (const id of learningIds) {
760
+ existing.add(id);
761
+ }
762
+ session.appliedLearnings = Array.from(existing);
763
+ session.lastCheckpoint = /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString();
764
+ this.dirty = true;
765
+ return true;
766
+ }
767
+ // ============================================================================
768
+ // PROJECT CONTEXT
769
+ // ============================================================================
770
+ getProjectContext() {
771
+ return this.state.projectContext;
772
+ }
773
+ setProjectContext(context) {
774
+ this.state.projectContext = context;
775
+ this.dirty = true;
776
+ }
777
+ // ============================================================================
778
+ // SYNC QUEUE
779
+ // ============================================================================
780
+ /**
781
+ * Get pending sync items
782
+ */
783
+ getPendingSync() {
784
+ return [
785
+ ...this.state.pendingSync || []
786
+ ];
787
+ }
788
+ /**
789
+ * Queue an item for cloud sync
790
+ */
791
+ queueForSync(item) {
792
+ const existing = this.state.pendingSync?.find((p) => p.patternKey === item.patternKey);
793
+ if (existing) {
794
+ return existing;
795
+ }
796
+ const newItem = {
797
+ ...item,
798
+ id: crypto.randomUUID(),
799
+ queuedAt: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString(),
800
+ retryCount: 0
801
+ };
802
+ if (!this.state.pendingSync) {
803
+ this.state.pendingSync = [];
804
+ }
805
+ this.state.pendingSync.push(newItem);
806
+ this.dirty = true;
807
+ return newItem;
808
+ }
809
+ /**
810
+ * Remove synced items from queue
811
+ */
812
+ removeSyncedItems(ids) {
813
+ if (!this.state.pendingSync) {
814
+ return;
815
+ }
816
+ const idSet = new Set(ids);
817
+ this.state.pendingSync = this.state.pendingSync.filter((p) => !idSet.has(p.id));
818
+ this.dirty = true;
819
+ }
820
+ /**
821
+ * Increment retry count for failed items
822
+ */
823
+ incrementRetryCount(ids) {
824
+ if (!this.state.pendingSync) {
825
+ return;
826
+ }
827
+ const idSet = new Set(ids);
828
+ for (const item of this.state.pendingSync) {
829
+ if (idSet.has(item.id)) {
830
+ item.retryCount++;
831
+ }
832
+ }
833
+ this.dirty = true;
834
+ }
835
+ // ============================================================================
836
+ // SYNC METADATA (Phase 1: Intelligence New Platform)
837
+ // ============================================================================
838
+ /**
839
+ * Get current sync status
840
+ */
841
+ getSyncStatus() {
842
+ return this.state.syncStatus;
843
+ }
844
+ /**
845
+ * Set sync status
846
+ */
847
+ setSyncStatus(status) {
848
+ this.state.syncStatus = status;
849
+ this.dirty = true;
850
+ }
851
+ /**
852
+ * Get current sync version (for CRDT conflict resolution)
853
+ */
854
+ getSyncVersion() {
855
+ return this.state.syncVersion;
856
+ }
857
+ /**
858
+ * Increment sync version and return new value
859
+ */
860
+ incrementSyncVersion() {
861
+ this.state.syncVersion++;
862
+ this.dirty = true;
863
+ return this.state.syncVersion;
864
+ }
865
+ /**
866
+ * Mark state as successfully synced with timestamp
867
+ * Clears any previous sync error
868
+ */
869
+ markSynced() {
870
+ this.state.syncStatus = "synced";
871
+ this.state.lastSynced = /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString();
872
+ this.state.lastSyncError = void 0;
873
+ this.dirty = true;
874
+ }
875
+ /**
876
+ * Get last synced timestamp
877
+ */
878
+ getLastSynced() {
879
+ return this.state.lastSynced;
880
+ }
881
+ /**
882
+ * Set sync error status with optional message
883
+ */
884
+ setSyncError(message) {
885
+ this.state.syncStatus = "error";
886
+ this.state.lastSyncError = message;
887
+ this.dirty = true;
888
+ }
889
+ // ============================================================================
890
+ // SERIALIZATION
891
+ // ============================================================================
892
+ serialize(state) {
893
+ const json = JSON.stringify(state);
894
+ const jsonBuffer = Buffer.from(json, "utf8");
895
+ const compressed = zlib.gzipSync(jsonBuffer, {
896
+ level: 9
897
+ });
898
+ let payload = compressed;
899
+ let flags = 0;
900
+ if (this.encrypt && this.encryptionKey) {
901
+ payload = this.encryptPayload(compressed);
902
+ flags |= FLAG_ENCRYPTED;
903
+ }
904
+ const header = Buffer.alloc(HEADER_SIZE);
905
+ MAGIC_BYTES.copy(header, 0);
906
+ header.writeUInt8(CURRENT_VERSION, 8);
907
+ header.writeUInt8(flags, 9);
908
+ return Buffer.concat([
909
+ header,
910
+ payload
911
+ ]);
912
+ }
913
+ deserialize(raw) {
914
+ if (raw.length < HEADER_SIZE) {
915
+ throw new Error("Invalid state file: too small");
916
+ }
917
+ const magic = raw.subarray(0, 8);
918
+ if (!magic.equals(MAGIC_BYTES)) {
919
+ throw new Error("Invalid state file: bad magic bytes");
920
+ }
921
+ const version = raw.readUInt8(8);
922
+ const flags = raw.readUInt8(9);
923
+ if (version > CURRENT_VERSION) {
924
+ throw new Error(`State file version ${version} is newer than supported ${CURRENT_VERSION}`);
925
+ }
926
+ let payload = raw.subarray(HEADER_SIZE);
927
+ if (flags & FLAG_ENCRYPTED) {
928
+ if (!this.encryptionKey) {
929
+ throw new Error("State file is encrypted but no key provided");
930
+ }
931
+ payload = this.decryptPayload(payload);
932
+ }
933
+ const decompressed = zlib.gunzipSync(payload);
934
+ const json = decompressed.toString("utf8");
935
+ return JSON.parse(json);
936
+ }
937
+ encryptPayload(data) {
938
+ if (!this.encryptionKey) {
939
+ throw new Error("No encryption key");
940
+ }
941
+ const iv = crypto.randomBytes(12);
942
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.encryptionKey, iv);
943
+ const encrypted = Buffer.concat([
944
+ cipher.update(data),
945
+ cipher.final()
946
+ ]);
947
+ const authTag = cipher.getAuthTag();
948
+ return Buffer.concat([
949
+ iv,
950
+ authTag,
951
+ encrypted
952
+ ]);
953
+ }
954
+ decryptPayload(data) {
955
+ if (!this.encryptionKey) {
956
+ throw new Error("No encryption key");
957
+ }
958
+ if (data.length < 28) {
959
+ throw new Error("Encrypted payload too small");
960
+ }
961
+ const iv = data.subarray(0, 12);
962
+ const authTag = data.subarray(12, 28);
963
+ const encrypted = data.subarray(28);
964
+ const decipher = crypto.createDecipheriv("aes-256-gcm", this.encryptionKey, iv);
965
+ decipher.setAuthTag(authTag);
966
+ return Buffer.concat([
967
+ decipher.update(encrypted),
968
+ decipher.final()
969
+ ]);
970
+ }
971
+ // ============================================================================
972
+ // MIGRATION
973
+ // ============================================================================
974
+ /**
975
+ * Migrate from legacy JSONL files to binary state
976
+ */
977
+ async migrateFromJsonl(learningsDir, patternsDir) {
978
+ const errors = [];
979
+ let migrated = 0;
980
+ const learningFiles = [
981
+ "hot.jsonl",
982
+ "warm.jsonl",
983
+ "cold.jsonl",
984
+ "user-learnings.jsonl",
985
+ "learnings.jsonl"
986
+ ];
987
+ for (const file of learningFiles) {
988
+ const filePath = path3.join(learningsDir, file);
989
+ if (fs2.existsSync(filePath)) {
990
+ try {
991
+ const content = fs2.readFileSync(filePath, "utf8");
992
+ const lines = content.split("\n").filter((l) => l.trim());
993
+ for (const line of lines) {
994
+ try {
995
+ const legacy = JSON.parse(line);
996
+ const tier = file.startsWith("hot") ? "hot" : file.startsWith("cold") ? "cold" : "warm";
997
+ this.addLearning({
998
+ type: legacy.type || "pattern",
999
+ trigger: legacy.trigger || "",
1000
+ action: legacy.action || "",
1001
+ keywords: legacy.keywords,
1002
+ source: legacy.source,
1003
+ tier: legacy.tier || tier,
1004
+ domain: legacy.domain
1005
+ });
1006
+ migrated++;
1007
+ } catch {
1008
+ errors.push(`Failed to parse learning: ${line.substring(0, 50)}...`);
1009
+ }
1010
+ }
1011
+ } catch (e) {
1012
+ errors.push(`Failed to read ${file}: ${e}`);
1013
+ }
1014
+ }
1015
+ }
1016
+ const violationsPath = path3.join(patternsDir, "violations.jsonl");
1017
+ if (fs2.existsSync(violationsPath)) {
1018
+ try {
1019
+ const content = fs2.readFileSync(violationsPath, "utf8");
1020
+ const lines = content.split("\n").filter((l) => l.trim());
1021
+ for (const line of lines) {
1022
+ try {
1023
+ const legacy = JSON.parse(line);
1024
+ this.recordViolation({
1025
+ type: legacy.type || "unknown",
1026
+ file: legacy.file || "",
1027
+ description: legacy.description || legacy.whatHappened || "",
1028
+ reason: legacy.reason || legacy.whyItHappened,
1029
+ prevention: legacy.prevention || legacy.howToPrevent || ""
1030
+ });
1031
+ migrated++;
1032
+ } catch {
1033
+ errors.push(`Failed to parse violation: ${line.substring(0, 50)}...`);
1034
+ }
1035
+ }
1036
+ } catch (e) {
1037
+ errors.push(`Failed to read violations.jsonl: ${e}`);
1038
+ }
1039
+ }
1040
+ await this.save();
1041
+ return {
1042
+ migrated,
1043
+ errors
1044
+ };
1045
+ }
1046
+ };
1047
+ function deriveEncryptionKey(workspaceId, machineId) {
1048
+ return crypto.pbkdf2Sync(workspaceId, machineId, 1e5, 32, "sha256");
1049
+ }
1050
+ __name(deriveEncryptionKey, "deriveEncryptionKey");
1051
+ __name2(deriveEncryptionKey, "deriveEncryptionKey");
1052
+ function getMachineId() {
1053
+ const os = __require2("os");
1054
+ const parts = [
1055
+ os.hostname(),
1056
+ os.platform(),
1057
+ os.arch(),
1058
+ os.cpus()[0]?.model || "unknown"
1059
+ ];
1060
+ return crypto.createHash("sha256").update(parts.join(":")).digest("hex").substring(0, 32);
1061
+ }
1062
+ __name(getMachineId, "getMachineId");
1063
+ __name2(getMachineId, "getMachineId");
1064
+
1065
+ export { ConfigStore, StateStore, appendJsonl, appendJsonlAsync, countInJsonl, deriveEncryptionKey, findInJsonl, generateId2 as generateId, getMachineId, loadJsonl, updateInJsonl, writeJsonl };
1066
+ //# sourceMappingURL=chunk-QLCHTUT5.js.map
1067
+ //# sourceMappingURL=chunk-QLCHTUT5.js.map