@ulrichc1/sparn 1.2.2 → 1.4.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.
Files changed (42) hide show
  1. package/PRIVACY.md +1 -1
  2. package/README.md +136 -642
  3. package/SECURITY.md +1 -1
  4. package/dist/cli/dashboard.cjs +3977 -0
  5. package/dist/cli/dashboard.cjs.map +1 -0
  6. package/dist/cli/dashboard.d.cts +17 -0
  7. package/dist/cli/dashboard.d.ts +17 -0
  8. package/dist/cli/dashboard.js +3932 -0
  9. package/dist/cli/dashboard.js.map +1 -0
  10. package/dist/cli/index.cjs +3853 -484
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +3810 -457
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/daemon/index.cjs +411 -99
  15. package/dist/daemon/index.cjs.map +1 -1
  16. package/dist/daemon/index.js +423 -103
  17. package/dist/daemon/index.js.map +1 -1
  18. package/dist/hooks/post-tool-result.cjs +115 -266
  19. package/dist/hooks/post-tool-result.cjs.map +1 -1
  20. package/dist/hooks/post-tool-result.js +115 -266
  21. package/dist/hooks/post-tool-result.js.map +1 -1
  22. package/dist/hooks/pre-prompt.cjs +197 -268
  23. package/dist/hooks/pre-prompt.cjs.map +1 -1
  24. package/dist/hooks/pre-prompt.js +182 -268
  25. package/dist/hooks/pre-prompt.js.map +1 -1
  26. package/dist/hooks/stop-docs-refresh.cjs +123 -0
  27. package/dist/hooks/stop-docs-refresh.cjs.map +1 -0
  28. package/dist/hooks/stop-docs-refresh.d.cts +1 -0
  29. package/dist/hooks/stop-docs-refresh.d.ts +1 -0
  30. package/dist/hooks/stop-docs-refresh.js +126 -0
  31. package/dist/hooks/stop-docs-refresh.js.map +1 -0
  32. package/dist/index.cjs +1754 -337
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +539 -40
  35. package/dist/index.d.ts +539 -40
  36. package/dist/index.js +1737 -329
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp/index.cjs +304 -71
  39. package/dist/mcp/index.cjs.map +1 -1
  40. package/dist/mcp/index.js +308 -71
  41. package/dist/mcp/index.js.map +1 -1
  42. package/package.json +10 -3
@@ -0,0 +1,3977 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // node_modules/tsup/assets/cjs_shims.js
34
+ var getImportMetaUrl, importMetaUrl;
35
+ var init_cjs_shims = __esm({
36
+ "node_modules/tsup/assets/cjs_shims.js"() {
37
+ "use strict";
38
+ getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
39
+ importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
+ }
41
+ });
42
+
43
+ // src/utils/hash.ts
44
+ function hashContent(content) {
45
+ return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
46
+ }
47
+ var import_node_crypto;
48
+ var init_hash = __esm({
49
+ "src/utils/hash.ts"() {
50
+ "use strict";
51
+ init_cjs_shims();
52
+ import_node_crypto = require("crypto");
53
+ }
54
+ });
55
+
56
+ // src/core/btsp-embedder.ts
57
+ function createBTSPEmbedder(config) {
58
+ const BTSP_PATTERNS = [
59
+ // Error patterns
60
+ /\b(error|exception|failure|fatal|critical|panic)\b/i,
61
+ /\b(TypeError|ReferenceError|SyntaxError|RangeError|URIError)\b/,
62
+ /\bENOENT|EACCES|ECONNREFUSED|ETIMEDOUT\b/,
63
+ // Stack trace patterns
64
+ /^\s+at\s+.*\(.*:\d+:\d+\)/m,
65
+ // JavaScript stack trace
66
+ /^\s+at\s+.*\.[a-zA-Z]+:\d+/m,
67
+ // Python/Ruby stack trace
68
+ // Git diff new files
69
+ /^new file mode \d+$/m,
70
+ /^--- \/dev\/null$/m,
71
+ // Merge conflict markers
72
+ /^<<<<<<< /m,
73
+ /^=======/m,
74
+ /^>>>>>>> /m
75
+ ];
76
+ if (config?.customPatterns) {
77
+ for (const pattern of config.customPatterns) {
78
+ try {
79
+ BTSP_PATTERNS.push(new RegExp(pattern));
80
+ } catch {
81
+ }
82
+ }
83
+ }
84
+ function detectBTSP(content) {
85
+ return BTSP_PATTERNS.some((pattern) => pattern.test(content));
86
+ }
87
+ function createBTSPEntry(content, tags = [], metadata = {}) {
88
+ return {
89
+ id: (0, import_node_crypto2.randomUUID)(),
90
+ content,
91
+ hash: hashContent(content),
92
+ timestamp: Date.now(),
93
+ score: 1,
94
+ // Maximum initial score
95
+ ttl: 365 * 24 * 3600,
96
+ // 1 year in seconds (long retention)
97
+ state: "active",
98
+ // Always active
99
+ accessCount: 0,
100
+ tags: [...tags, "btsp"],
101
+ metadata,
102
+ isBTSP: true
103
+ };
104
+ }
105
+ return {
106
+ detectBTSP,
107
+ createBTSPEntry
108
+ };
109
+ }
110
+ var import_node_crypto2;
111
+ var init_btsp_embedder = __esm({
112
+ "src/core/btsp-embedder.ts"() {
113
+ "use strict";
114
+ init_cjs_shims();
115
+ import_node_crypto2 = require("crypto");
116
+ init_hash();
117
+ }
118
+ });
119
+
120
+ // src/core/confidence-states.ts
121
+ function createConfidenceStates(config) {
122
+ const { activeThreshold, readyThreshold } = config;
123
+ function calculateState(entry) {
124
+ if (entry.isBTSP) {
125
+ return "active";
126
+ }
127
+ if (entry.score >= activeThreshold) {
128
+ return "active";
129
+ }
130
+ if (entry.score >= readyThreshold) {
131
+ return "ready";
132
+ }
133
+ return "silent";
134
+ }
135
+ function transition(entry) {
136
+ const newState = calculateState(entry);
137
+ return {
138
+ ...entry,
139
+ state: newState
140
+ };
141
+ }
142
+ function getDistribution(entries) {
143
+ const distribution = {
144
+ silent: 0,
145
+ ready: 0,
146
+ active: 0,
147
+ total: entries.length
148
+ };
149
+ for (const entry of entries) {
150
+ const state = calculateState(entry);
151
+ distribution[state]++;
152
+ }
153
+ return distribution;
154
+ }
155
+ return {
156
+ calculateState,
157
+ transition,
158
+ getDistribution
159
+ };
160
+ }
161
+ var init_confidence_states = __esm({
162
+ "src/core/confidence-states.ts"() {
163
+ "use strict";
164
+ init_cjs_shims();
165
+ }
166
+ });
167
+
168
+ // src/core/engram-scorer.ts
169
+ function createEngramScorer(config) {
170
+ const { defaultTTL } = config;
171
+ const recencyWindowMs = (config.recencyBoostMinutes ?? 30) * 60 * 1e3;
172
+ const recencyMultiplier = config.recencyBoostMultiplier ?? 1.3;
173
+ function calculateDecay(ageInSeconds, ttlInSeconds) {
174
+ if (ttlInSeconds === 0) return 1;
175
+ if (ageInSeconds <= 0) return 0;
176
+ const ratio = ageInSeconds / ttlInSeconds;
177
+ const decay = 1 - Math.exp(-ratio);
178
+ return Math.max(0, Math.min(1, decay));
179
+ }
180
+ function calculateScore(entry, currentTime = Date.now()) {
181
+ const ageInMilliseconds = currentTime - entry.timestamp;
182
+ const ageInSeconds = Math.max(0, ageInMilliseconds / 1e3);
183
+ const decay = calculateDecay(ageInSeconds, entry.ttl);
184
+ let score = entry.score * (1 - decay);
185
+ if (entry.accessCount > 0) {
186
+ const accessBonus = Math.log(entry.accessCount + 1) * 0.1;
187
+ score = Math.min(1, score + accessBonus);
188
+ }
189
+ if (entry.isBTSP) {
190
+ score = Math.max(score, 0.9);
191
+ }
192
+ if (!entry.isBTSP && recencyWindowMs > 0) {
193
+ const ageMs = currentTime - entry.timestamp;
194
+ if (ageMs >= 0 && ageMs < recencyWindowMs) {
195
+ const boostFactor = 1 + (recencyMultiplier - 1) * (1 - ageMs / recencyWindowMs);
196
+ score = score * boostFactor;
197
+ }
198
+ }
199
+ return Math.max(0, Math.min(1, score));
200
+ }
201
+ function refreshTTL(entry) {
202
+ return {
203
+ ...entry,
204
+ ttl: defaultTTL * 3600,
205
+ // Convert hours to seconds
206
+ timestamp: Date.now()
207
+ };
208
+ }
209
+ return {
210
+ calculateScore,
211
+ refreshTTL,
212
+ calculateDecay
213
+ };
214
+ }
215
+ var init_engram_scorer = __esm({
216
+ "src/core/engram-scorer.ts"() {
217
+ "use strict";
218
+ init_cjs_shims();
219
+ }
220
+ });
221
+
222
+ // src/utils/tfidf.ts
223
+ function tokenize2(text) {
224
+ return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
225
+ }
226
+ function calculateTF(term, tokens) {
227
+ const count = tokens.filter((t) => t === term).length;
228
+ return Math.sqrt(count);
229
+ }
230
+ function createTFIDFIndex(entries) {
231
+ const documentFrequency = /* @__PURE__ */ new Map();
232
+ for (const entry of entries) {
233
+ const tokens = tokenize2(entry.content);
234
+ const uniqueTerms = new Set(tokens);
235
+ for (const term of uniqueTerms) {
236
+ documentFrequency.set(term, (documentFrequency.get(term) || 0) + 1);
237
+ }
238
+ }
239
+ return {
240
+ documentFrequency,
241
+ totalDocuments: entries.length
242
+ };
243
+ }
244
+ function scoreTFIDF(entry, index) {
245
+ const tokens = tokenize2(entry.content);
246
+ if (tokens.length === 0) return 0;
247
+ const uniqueTerms = new Set(tokens);
248
+ let totalScore = 0;
249
+ for (const term of uniqueTerms) {
250
+ const tf = calculateTF(term, tokens);
251
+ const docsWithTerm = index.documentFrequency.get(term) || 0;
252
+ if (docsWithTerm === 0) continue;
253
+ const idf = Math.log(index.totalDocuments / docsWithTerm);
254
+ totalScore += tf * idf;
255
+ }
256
+ return totalScore / tokens.length;
257
+ }
258
+ var init_tfidf = __esm({
259
+ "src/utils/tfidf.ts"() {
260
+ "use strict";
261
+ init_cjs_shims();
262
+ }
263
+ });
264
+
265
+ // src/utils/tokenizer.ts
266
+ function estimateTokens(text) {
267
+ if (!text || text.length === 0) {
268
+ return 0;
269
+ }
270
+ if (usePrecise) {
271
+ return (0, import_gpt_tokenizer.encode)(text).length;
272
+ }
273
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
274
+ const wordCount = words.length;
275
+ const charCount = text.length;
276
+ const charEstimate = Math.ceil(charCount / 4);
277
+ const wordEstimate = Math.ceil(wordCount * 0.75);
278
+ return Math.max(wordEstimate, charEstimate);
279
+ }
280
+ var import_gpt_tokenizer, usePrecise;
281
+ var init_tokenizer = __esm({
282
+ "src/utils/tokenizer.ts"() {
283
+ "use strict";
284
+ init_cjs_shims();
285
+ import_gpt_tokenizer = require("gpt-tokenizer");
286
+ usePrecise = false;
287
+ }
288
+ });
289
+
290
+ // src/core/sparse-pruner.ts
291
+ function createSparsePruner(config) {
292
+ const { threshold } = config;
293
+ function scoreEntry(entry, allEntries) {
294
+ return scoreTFIDF(entry, createTFIDFIndex(allEntries));
295
+ }
296
+ function prune(entries) {
297
+ if (entries.length === 0) {
298
+ return {
299
+ kept: [],
300
+ removed: [],
301
+ originalTokens: 0,
302
+ prunedTokens: 0
303
+ };
304
+ }
305
+ const originalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
306
+ const tfidfIndex = createTFIDFIndex(entries);
307
+ const scored = entries.map((entry) => ({
308
+ entry,
309
+ score: scoreTFIDF(entry, tfidfIndex)
310
+ }));
311
+ scored.sort((a, b) => b.score - a.score);
312
+ const keepCount = Math.max(1, Math.ceil(entries.length * (threshold / 100)));
313
+ const kept = scored.slice(0, keepCount).map((s) => s.entry);
314
+ const removed = scored.slice(keepCount).map((s) => s.entry);
315
+ const prunedTokens = kept.reduce((sum, e) => sum + estimateTokens(e.content), 0);
316
+ return {
317
+ kept,
318
+ removed,
319
+ originalTokens,
320
+ prunedTokens
321
+ };
322
+ }
323
+ return {
324
+ prune,
325
+ scoreEntry
326
+ };
327
+ }
328
+ var init_sparse_pruner = __esm({
329
+ "src/core/sparse-pruner.ts"() {
330
+ "use strict";
331
+ init_cjs_shims();
332
+ init_tfidf();
333
+ init_tokenizer();
334
+ }
335
+ });
336
+
337
+ // src/adapters/generic.ts
338
+ function createGenericAdapter(memory, config) {
339
+ const pruner = createSparsePruner(config.pruning);
340
+ const scorer = createEngramScorer(config.decay);
341
+ const states = createConfidenceStates(config.states);
342
+ const btsp = createBTSPEmbedder({ customPatterns: config.btspPatterns });
343
+ async function optimize(context, options = {}) {
344
+ const startTime = Date.now();
345
+ const lines = context.split("\n").filter((line) => line.trim().length > 0);
346
+ const now = Date.now();
347
+ const entries = lines.map((content, index) => {
348
+ const isBTSP = btsp.detectBTSP(content);
349
+ return {
350
+ id: (0, import_node_crypto3.randomUUID)(),
351
+ content,
352
+ hash: hashContent(content),
353
+ timestamp: now + index,
354
+ // Unique timestamps preserve ordering
355
+ score: isBTSP ? 1 : 0.5,
356
+ ttl: config.decay.defaultTTL * 3600,
357
+ state: "ready",
358
+ accessCount: 0,
359
+ tags: [],
360
+ metadata: {},
361
+ isBTSP
362
+ };
363
+ });
364
+ const tokensBefore = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
365
+ const scoredEntries = entries.map((entry) => ({
366
+ ...entry,
367
+ score: scorer.calculateScore(entry)
368
+ }));
369
+ const statedEntries = scoredEntries.map((entry) => states.transition(entry));
370
+ const pruneResult = pruner.prune(statedEntries);
371
+ const optimizedEntries = pruneResult.kept.filter(
372
+ (e) => e.state === "active" || e.state === "ready"
373
+ );
374
+ const tokensAfter = optimizedEntries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
375
+ const optimizedContext = optimizedEntries.map((e) => e.content).join("\n");
376
+ if (!options.dryRun) {
377
+ for (const entry of optimizedEntries) {
378
+ await memory.put(entry);
379
+ }
380
+ await memory.recordOptimization({
381
+ timestamp: Date.now(),
382
+ tokens_before: tokensBefore,
383
+ tokens_after: tokensAfter,
384
+ entries_pruned: entries.length - optimizedEntries.length,
385
+ duration_ms: Date.now() - startTime
386
+ });
387
+ }
388
+ const distribution = states.getDistribution(optimizedEntries);
389
+ const result = {
390
+ optimizedContext,
391
+ tokensBefore,
392
+ tokensAfter,
393
+ reduction: tokensBefore > 0 ? (tokensBefore - tokensAfter) / tokensBefore : 0,
394
+ entriesProcessed: entries.length,
395
+ entriesKept: optimizedEntries.length,
396
+ stateDistribution: distribution,
397
+ durationMs: Date.now() - startTime
398
+ };
399
+ if (options.verbose) {
400
+ result.details = optimizedEntries.map((e) => ({
401
+ id: e.id,
402
+ score: e.score,
403
+ state: e.state,
404
+ isBTSP: e.isBTSP,
405
+ tokens: estimateTokens(e.content)
406
+ }));
407
+ }
408
+ return result;
409
+ }
410
+ return {
411
+ optimize
412
+ };
413
+ }
414
+ var import_node_crypto3;
415
+ var init_generic = __esm({
416
+ "src/adapters/generic.ts"() {
417
+ "use strict";
418
+ init_cjs_shims();
419
+ import_node_crypto3 = require("crypto");
420
+ init_btsp_embedder();
421
+ init_confidence_states();
422
+ init_engram_scorer();
423
+ init_sparse_pruner();
424
+ init_hash();
425
+ init_tokenizer();
426
+ }
427
+ });
428
+
429
+ // src/types/config.ts
430
+ var DEFAULT_CONFIG;
431
+ var init_config = __esm({
432
+ "src/types/config.ts"() {
433
+ "use strict";
434
+ init_cjs_shims();
435
+ DEFAULT_CONFIG = {
436
+ pruning: {
437
+ threshold: 5,
438
+ aggressiveness: 50
439
+ },
440
+ decay: {
441
+ defaultTTL: 24,
442
+ decayThreshold: 0.95
443
+ },
444
+ states: {
445
+ activeThreshold: 0.7,
446
+ readyThreshold: 0.3
447
+ },
448
+ agent: "generic",
449
+ ui: {
450
+ colors: true,
451
+ sounds: false,
452
+ verbose: false
453
+ },
454
+ autoConsolidate: null,
455
+ realtime: {
456
+ tokenBudget: 4e4,
457
+ autoOptimizeThreshold: 6e4,
458
+ watchPatterns: ["**/*.jsonl"],
459
+ pidFile: ".sparn/daemon.pid",
460
+ logFile: ".sparn/daemon.log",
461
+ debounceMs: 5e3,
462
+ incremental: true,
463
+ windowSize: 500,
464
+ consolidationInterval: null
465
+ }
466
+ };
467
+ }
468
+ });
469
+
470
+ // src/cli/commands/optimize.ts
471
+ var optimize_exports = {};
472
+ __export(optimize_exports, {
473
+ optimizeCommand: () => optimizeCommand
474
+ });
475
+ async function optimizeCommand(options) {
476
+ const { memory, dryRun = false, verbose = false } = options;
477
+ let input;
478
+ if (options.inputFile) {
479
+ input = await (0, import_promises.readFile)(options.inputFile, "utf-8");
480
+ } else if (options.input) {
481
+ input = options.input;
482
+ } else {
483
+ throw new Error("No input provided. Use --input or --input-file");
484
+ }
485
+ const adapter = createGenericAdapter(memory, DEFAULT_CONFIG);
486
+ const result = await adapter.optimize(input, { dryRun, verbose });
487
+ if (options.outputFile) {
488
+ await (0, import_promises.writeFile)(options.outputFile, result.optimizedContext, "utf-8");
489
+ }
490
+ return {
491
+ ...result,
492
+ output: result.optimizedContext,
493
+ outputFile: options.outputFile
494
+ };
495
+ }
496
+ var import_promises;
497
+ var init_optimize = __esm({
498
+ "src/cli/commands/optimize.ts"() {
499
+ "use strict";
500
+ init_cjs_shims();
501
+ import_promises = require("fs/promises");
502
+ init_generic();
503
+ init_config();
504
+ }
505
+ });
506
+
507
+ // src/cli/commands/stats.ts
508
+ var stats_exports = {};
509
+ __export(stats_exports, {
510
+ statsCommand: () => statsCommand
511
+ });
512
+ async function statsCommand(options) {
513
+ const { memory, graph, reset, confirmReset, json } = options;
514
+ if (reset) {
515
+ if (confirmReset) {
516
+ await memory.clearOptimizationStats();
517
+ return {
518
+ totalCommands: 0,
519
+ totalTokensSaved: 0,
520
+ averageReduction: 0,
521
+ resetConfirmed: true
522
+ };
523
+ }
524
+ }
525
+ const stats = await memory.getOptimizationStats();
526
+ const totalCommands = stats.length;
527
+ const totalTokensSaved = stats.reduce((sum, s) => sum + (s.tokens_before - s.tokens_after), 0);
528
+ const averageReduction = totalCommands > 0 ? stats.reduce((sum, s) => {
529
+ const reduction = s.tokens_before > 0 ? (s.tokens_before - s.tokens_after) / s.tokens_before : 0;
530
+ return sum + reduction;
531
+ }, 0) / totalCommands : 0;
532
+ const result = {
533
+ totalCommands,
534
+ totalTokensSaved,
535
+ averageReduction
536
+ };
537
+ if (graph && totalCommands > 0) {
538
+ result.graph = generateBarChart(stats);
539
+ }
540
+ if (json) {
541
+ result.json = JSON.stringify(
542
+ {
543
+ totalCommands,
544
+ totalTokensSaved,
545
+ averageReduction: Math.round(averageReduction * 1e3) / 10,
546
+ // Convert to percentage
547
+ optimizations: stats.map((s) => ({
548
+ timestamp: s.timestamp,
549
+ tokensBefore: s.tokens_before,
550
+ tokensAfter: s.tokens_after,
551
+ entriesPruned: s.entries_pruned,
552
+ durationMs: s.duration_ms,
553
+ reduction: Math.round((s.tokens_before - s.tokens_after) / s.tokens_before * 1e3) / 10
554
+ }))
555
+ },
556
+ null,
557
+ 2
558
+ );
559
+ }
560
+ return result;
561
+ }
562
+ function generateBarChart(stats) {
563
+ const maxBars = 20;
564
+ const recentStats = stats.slice(0, maxBars);
565
+ const lines = [];
566
+ lines.push("\nOptimization History (most recent first):\n");
567
+ const maxReduction = Math.max(...recentStats.map((s) => s.tokens_before - s.tokens_after));
568
+ for (let i = 0; i < recentStats.length; i++) {
569
+ const s = recentStats[i];
570
+ if (!s) continue;
571
+ const reduction = s.tokens_before - s.tokens_after;
572
+ const reductionPct = s.tokens_before > 0 ? reduction / s.tokens_before * 100 : 0;
573
+ const barLength = Math.round(reduction / maxReduction * 40);
574
+ const bar = "\u2588".repeat(barLength);
575
+ const date = new Date(s.timestamp);
576
+ const timeStr = date.toLocaleTimeString();
577
+ lines.push(`${timeStr} \u2502 ${bar} ${reductionPct.toFixed(1)}%`);
578
+ }
579
+ return lines.join("\n");
580
+ }
581
+ var init_stats = __esm({
582
+ "src/cli/commands/stats.ts"() {
583
+ "use strict";
584
+ init_cjs_shims();
585
+ }
586
+ });
587
+
588
+ // src/core/sleep-compressor.ts
589
+ function createSleepCompressor() {
590
+ const scorer = createEngramScorer({ defaultTTL: 24, decayThreshold: 0.95 });
591
+ function consolidate(entries) {
592
+ const startTime = Date.now();
593
+ const originalCount = entries.length;
594
+ const now = Date.now();
595
+ const nonDecayed = entries.filter((entry) => {
596
+ const ageInSeconds = (now - entry.timestamp) / 1e3;
597
+ const decay = scorer.calculateDecay(ageInSeconds, entry.ttl);
598
+ return decay < 0.95;
599
+ });
600
+ const decayedRemoved = originalCount - nonDecayed.length;
601
+ const duplicateGroups = findDuplicates(nonDecayed);
602
+ const merged = mergeDuplicates(duplicateGroups);
603
+ const duplicateIds = new Set(duplicateGroups.flatMap((g) => g.entries.map((e) => e.id)));
604
+ const nonDuplicates = nonDecayed.filter((e) => !duplicateIds.has(e.id));
605
+ const kept = [...merged, ...nonDuplicates];
606
+ const removed = entries.filter((e) => !kept.some((k) => k.id === e.id));
607
+ const duplicatesRemoved = duplicateGroups.reduce((sum, g) => sum + (g.entries.length - 1), 0);
608
+ return {
609
+ kept,
610
+ removed,
611
+ entriesBefore: originalCount,
612
+ entriesAfter: kept.length,
613
+ decayedRemoved,
614
+ duplicatesRemoved,
615
+ compressionRatio: originalCount > 0 ? kept.length / originalCount : 0,
616
+ durationMs: Date.now() - startTime
617
+ };
618
+ }
619
+ function findDuplicates(entries) {
620
+ const groups = [];
621
+ const processed = /* @__PURE__ */ new Set();
622
+ for (let i = 0; i < entries.length; i++) {
623
+ const entry = entries[i];
624
+ if (!entry || processed.has(entry.id)) continue;
625
+ const duplicates = entries.filter((e, idx) => idx !== i && e.hash === entry.hash);
626
+ if (duplicates.length > 0) {
627
+ const group = {
628
+ entries: [entry, ...duplicates],
629
+ similarity: 1
630
+ // Exact match
631
+ };
632
+ groups.push(group);
633
+ processed.add(entry.id);
634
+ for (const dup of duplicates) {
635
+ processed.add(dup.id);
636
+ }
637
+ }
638
+ }
639
+ for (let i = 0; i < entries.length; i++) {
640
+ const entryI = entries[i];
641
+ if (!entryI || processed.has(entryI.id)) continue;
642
+ for (let j = i + 1; j < entries.length; j++) {
643
+ const entryJ = entries[j];
644
+ if (!entryJ || processed.has(entryJ.id)) continue;
645
+ const similarity = cosineSimilarity(entryI.content, entryJ.content);
646
+ if (similarity >= 0.85) {
647
+ const group = {
648
+ entries: [entryI, entryJ],
649
+ similarity
650
+ };
651
+ groups.push(group);
652
+ processed.add(entryI.id);
653
+ processed.add(entryJ.id);
654
+ break;
655
+ }
656
+ }
657
+ }
658
+ return groups;
659
+ }
660
+ function mergeDuplicates(groups) {
661
+ const merged = [];
662
+ for (const group of groups) {
663
+ const sorted = [...group.entries].sort((a, b) => b.score - a.score);
664
+ const best = sorted[0];
665
+ if (!best) continue;
666
+ const totalAccessCount = group.entries.reduce((sum, e) => sum + e.accessCount, 0);
667
+ const allTags = new Set(group.entries.flatMap((e) => e.tags));
668
+ merged.push({
669
+ ...best,
670
+ accessCount: totalAccessCount,
671
+ tags: Array.from(allTags)
672
+ });
673
+ }
674
+ return merged;
675
+ }
676
+ function cosineSimilarity(text1, text2) {
677
+ const words1 = tokenize2(text1);
678
+ const words2 = tokenize2(text2);
679
+ const vec1 = {};
680
+ const vec2 = {};
681
+ for (const word of words1) {
682
+ vec1[word] = (vec1[word] ?? 0) + 1;
683
+ }
684
+ for (const word of words2) {
685
+ vec2[word] = (vec2[word] ?? 0) + 1;
686
+ }
687
+ const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
688
+ let dotProduct = 0;
689
+ let mag1 = 0;
690
+ let mag2 = 0;
691
+ for (const word of vocab) {
692
+ const count1 = vec1[word] ?? 0;
693
+ const count2 = vec2[word] ?? 0;
694
+ dotProduct += count1 * count2;
695
+ mag1 += count1 * count1;
696
+ mag2 += count2 * count2;
697
+ }
698
+ mag1 = Math.sqrt(mag1);
699
+ mag2 = Math.sqrt(mag2);
700
+ if (mag1 === 0 || mag2 === 0) return 0;
701
+ return dotProduct / (mag1 * mag2);
702
+ }
703
+ return {
704
+ consolidate,
705
+ findDuplicates,
706
+ mergeDuplicates
707
+ };
708
+ }
709
+ var init_sleep_compressor = __esm({
710
+ "src/core/sleep-compressor.ts"() {
711
+ "use strict";
712
+ init_cjs_shims();
713
+ init_tfidf();
714
+ init_engram_scorer();
715
+ }
716
+ });
717
+
718
+ // src/cli/commands/consolidate.ts
719
+ var consolidate_exports = {};
720
+ __export(consolidate_exports, {
721
+ consolidateCommand: () => consolidateCommand
722
+ });
723
+ async function consolidateCommand(options) {
724
+ const { memory } = options;
725
+ const allIds = await memory.list();
726
+ const allEntries = await Promise.all(
727
+ allIds.map(async (id) => {
728
+ const entry = await memory.get(id);
729
+ return entry;
730
+ })
731
+ );
732
+ const entries = allEntries.filter((e) => e !== null);
733
+ const compressor = createSleepCompressor();
734
+ const result = compressor.consolidate(entries);
735
+ for (const removed of result.removed) {
736
+ await memory.delete(removed.id);
737
+ }
738
+ for (const kept of result.kept) {
739
+ await memory.put(kept);
740
+ }
741
+ await memory.compact();
742
+ return {
743
+ entriesBefore: result.entriesBefore,
744
+ entriesAfter: result.entriesAfter,
745
+ decayedRemoved: result.decayedRemoved,
746
+ duplicatesRemoved: result.duplicatesRemoved,
747
+ compressionRatio: result.compressionRatio,
748
+ durationMs: result.durationMs,
749
+ vacuumCompleted: true
750
+ };
751
+ }
752
+ var init_consolidate = __esm({
753
+ "src/cli/commands/consolidate.ts"() {
754
+ "use strict";
755
+ init_cjs_shims();
756
+ init_sleep_compressor();
757
+ }
758
+ });
759
+
760
+ // src/core/search-engine.ts
761
+ function hasRipgrep() {
762
+ try {
763
+ (0, import_node_child_process.execSync)("rg --version", { stdio: "pipe" });
764
+ return true;
765
+ } catch {
766
+ return false;
767
+ }
768
+ }
769
+ function ripgrepSearch(query, projectRoot, opts = {}) {
770
+ try {
771
+ const args = ["--line-number", "--no-heading", "--color=never"];
772
+ if (opts.fileGlob) {
773
+ args.push("--glob", opts.fileGlob);
774
+ }
775
+ args.push(
776
+ "--glob",
777
+ "!node_modules",
778
+ "--glob",
779
+ "!dist",
780
+ "--glob",
781
+ "!.git",
782
+ "--glob",
783
+ "!.sparn",
784
+ "--glob",
785
+ "!coverage"
786
+ );
787
+ const maxResults = opts.maxResults || 20;
788
+ args.push("--max-count", String(maxResults));
789
+ if (opts.includeContext) {
790
+ args.push("-C", "2");
791
+ }
792
+ args.push("--", query, projectRoot);
793
+ const output = (0, import_node_child_process.execFileSync)("rg", args, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 });
794
+ const results = [];
795
+ const lines = output.split("\n").filter(Boolean);
796
+ for (const line of lines) {
797
+ const match = line.match(/^(.+?):(\d+):(.*)/);
798
+ if (match) {
799
+ const filePath = (0, import_node_path.relative)(projectRoot, match[1] || "").replace(/\\/g, "/");
800
+ const lineNumber = Number.parseInt(match[2] || "0", 10);
801
+ const content = (match[3] || "").trim();
802
+ results.push({
803
+ filePath,
804
+ lineNumber,
805
+ content,
806
+ score: 0.8,
807
+ // Exact match gets high base score
808
+ context: [],
809
+ engram_score: 0
810
+ });
811
+ }
812
+ }
813
+ return results.slice(0, maxResults);
814
+ } catch {
815
+ return [];
816
+ }
817
+ }
818
+ function collectIndexableFiles(dir, projectRoot, ignoreDirs = ["node_modules", "dist", ".git", ".sparn", "coverage"], exts = [".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".yaml", ".yml"]) {
819
+ const files = [];
820
+ try {
821
+ const entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
822
+ for (const entry of entries) {
823
+ const fullPath = (0, import_node_path.join)(dir, entry.name);
824
+ if (entry.isDirectory()) {
825
+ if (!ignoreDirs.includes(entry.name)) {
826
+ files.push(...collectIndexableFiles(fullPath, projectRoot, ignoreDirs, exts));
827
+ }
828
+ } else if (entry.isFile() && exts.includes((0, import_node_path.extname)(entry.name))) {
829
+ try {
830
+ const stat = (0, import_node_fs.statSync)(fullPath);
831
+ if (stat.size < 100 * 1024) {
832
+ files.push((0, import_node_path.relative)(projectRoot, fullPath).replace(/\\/g, "/"));
833
+ }
834
+ } catch {
835
+ }
836
+ }
837
+ }
838
+ } catch {
839
+ }
840
+ return files;
841
+ }
842
+ function createSearchEngine(dbPath) {
843
+ let db = null;
844
+ let projectRoot = "";
845
+ const rgAvailable = hasRipgrep();
846
+ async function init(root) {
847
+ if (db) {
848
+ try {
849
+ db.close();
850
+ } catch {
851
+ }
852
+ }
853
+ projectRoot = root;
854
+ db = new import_better_sqlite3.default(dbPath);
855
+ db.pragma("journal_mode = WAL");
856
+ db.exec(`
857
+ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
858
+ filepath, line_number, content, tokenize='porter'
859
+ );
860
+ `);
861
+ db.exec(`
862
+ CREATE TABLE IF NOT EXISTS search_meta (
863
+ filepath TEXT PRIMARY KEY,
864
+ mtime INTEGER NOT NULL,
865
+ indexed_at INTEGER NOT NULL
866
+ );
867
+ `);
868
+ }
869
+ async function index(paths) {
870
+ if (!db) throw new Error("Search engine not initialized. Call init() first.");
871
+ const startTime = Date.now();
872
+ const filesToIndex = paths || collectIndexableFiles(projectRoot, projectRoot);
873
+ let filesIndexed = 0;
874
+ let totalLines = 0;
875
+ const insertStmt = db.prepare(
876
+ "INSERT INTO search_index(filepath, line_number, content) VALUES (?, ?, ?)"
877
+ );
878
+ const metaStmt = db.prepare(
879
+ "INSERT OR REPLACE INTO search_meta(filepath, mtime, indexed_at) VALUES (?, ?, ?)"
880
+ );
881
+ const checkMeta = db.prepare("SELECT mtime FROM search_meta WHERE filepath = ?");
882
+ const deleteFile = db.prepare("DELETE FROM search_index WHERE filepath = ?");
883
+ const transaction = db.transaction(() => {
884
+ for (const filePath of filesToIndex) {
885
+ const fullPath = (0, import_node_path.join)(projectRoot, filePath);
886
+ if (!(0, import_node_fs.existsSync)(fullPath)) continue;
887
+ try {
888
+ const stat = (0, import_node_fs.statSync)(fullPath);
889
+ const existing = checkMeta.get(filePath);
890
+ if (existing && existing.mtime >= stat.mtimeMs) {
891
+ continue;
892
+ }
893
+ deleteFile.run(filePath);
894
+ const content = (0, import_node_fs.readFileSync)(fullPath, "utf-8");
895
+ const lines = content.split("\n");
896
+ for (let i = 0; i < lines.length; i++) {
897
+ const line = lines[i];
898
+ if (line && line.trim().length > 0) {
899
+ insertStmt.run(filePath, i + 1, line);
900
+ totalLines++;
901
+ }
902
+ }
903
+ metaStmt.run(filePath, stat.mtimeMs, Date.now());
904
+ filesIndexed++;
905
+ } catch {
906
+ }
907
+ }
908
+ });
909
+ transaction();
910
+ return {
911
+ filesIndexed,
912
+ totalLines,
913
+ duration: Date.now() - startTime
914
+ };
915
+ }
916
+ async function search(query, opts = {}) {
917
+ if (!db) throw new Error("Search engine not initialized. Call init() first.");
918
+ const maxResults = opts.maxResults || 10;
919
+ const results = [];
920
+ const seen = /* @__PURE__ */ new Set();
921
+ try {
922
+ const ftsQuery = query.replace(/['"*(){}[\]^~\\:]/g, " ").trim().split(/\s+/).filter((w) => w.length > 0).map((w) => `content:${w}`).join(" ");
923
+ if (ftsQuery.length > 0) {
924
+ let sql = `
925
+ SELECT filepath, line_number, content, rank
926
+ FROM search_index
927
+ WHERE search_index MATCH ?
928
+ `;
929
+ const params = [ftsQuery];
930
+ if (opts.fileGlob) {
931
+ const likePattern = opts.fileGlob.replace(/\*/g, "%").replace(/\?/g, "_");
932
+ sql += " AND filepath LIKE ?";
933
+ params.push(likePattern);
934
+ }
935
+ sql += " ORDER BY rank LIMIT ?";
936
+ params.push(maxResults * 2);
937
+ const rows = db.prepare(sql).all(...params);
938
+ for (const row of rows) {
939
+ const key = `${row.filepath}:${row.line_number}`;
940
+ if (!seen.has(key)) {
941
+ seen.add(key);
942
+ const score = Math.min(1, Math.max(0, 1 + row.rank / 10));
943
+ const context = [];
944
+ if (opts.includeContext) {
945
+ const contextRows = db.prepare(
946
+ `SELECT content FROM search_index WHERE filepath = ? AND CAST(line_number AS INTEGER) BETWEEN ? AND ? ORDER BY CAST(line_number AS INTEGER)`
947
+ ).all(row.filepath, row.line_number - 2, row.line_number + 2);
948
+ context.push(...contextRows.map((r) => r.content));
949
+ }
950
+ results.push({
951
+ filePath: row.filepath,
952
+ lineNumber: row.line_number,
953
+ content: row.content,
954
+ score,
955
+ context,
956
+ engram_score: 0
957
+ });
958
+ }
959
+ }
960
+ }
961
+ } catch {
962
+ }
963
+ if (rgAvailable && opts.useRipgrep !== false) {
964
+ const rgResults = ripgrepSearch(query, projectRoot, opts);
965
+ for (const result of rgResults) {
966
+ const key = `${result.filePath}:${result.lineNumber}`;
967
+ if (!seen.has(key)) {
968
+ seen.add(key);
969
+ results.push(result);
970
+ }
971
+ }
972
+ }
973
+ results.sort((a, b) => b.score - a.score);
974
+ return results.slice(0, maxResults);
975
+ }
976
+ async function refresh() {
977
+ if (!db) throw new Error("Search engine not initialized. Call init() first.");
978
+ db.exec("DELETE FROM search_index");
979
+ db.exec("DELETE FROM search_meta");
980
+ return index();
981
+ }
982
+ async function close() {
983
+ if (db) {
984
+ db.close();
985
+ db = null;
986
+ }
987
+ }
988
+ return {
989
+ init,
990
+ index,
991
+ search,
992
+ refresh,
993
+ close
994
+ };
995
+ }
996
+ var import_node_child_process, import_node_fs, import_node_path, import_better_sqlite3;
997
+ var init_search_engine = __esm({
998
+ "src/core/search-engine.ts"() {
999
+ "use strict";
1000
+ init_cjs_shims();
1001
+ import_node_child_process = require("child_process");
1002
+ import_node_fs = require("fs");
1003
+ import_node_path = require("path");
1004
+ import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
1005
+ }
1006
+ });
1007
+
1008
+ // src/cli/commands/search.ts
1009
+ var search_exports = {};
1010
+ __export(search_exports, {
1011
+ searchCommand: () => searchCommand
1012
+ });
1013
+ async function searchCommand(options) {
1014
+ const projectRoot = (0, import_node_path2.resolve)(process.cwd());
1015
+ const dbPath = (0, import_node_path2.resolve)(projectRoot, ".sparn", "search.db");
1016
+ const engine = createSearchEngine(dbPath);
1017
+ try {
1018
+ await engine.init(projectRoot);
1019
+ if (options.subcommand === "init" || options.subcommand === "refresh") {
1020
+ const stats = options.subcommand === "refresh" ? await engine.refresh() : await engine.index();
1021
+ const result = {
1022
+ indexStats: stats,
1023
+ message: `Indexed ${stats.filesIndexed} files (${stats.totalLines} lines) in ${stats.duration}ms`
1024
+ };
1025
+ if (options.json) {
1026
+ result.json = JSON.stringify(stats, null, 2);
1027
+ }
1028
+ return result;
1029
+ }
1030
+ if (!options.query) {
1031
+ return { message: 'No search query provided. Usage: sparn search "query"' };
1032
+ }
1033
+ const results = await engine.search(options.query, {
1034
+ maxResults: options.maxResults || 10,
1035
+ fileGlob: options.glob,
1036
+ includeContext: true
1037
+ });
1038
+ const cmdResult = { results };
1039
+ if (options.json) {
1040
+ cmdResult.json = JSON.stringify(results, null, 2);
1041
+ }
1042
+ return cmdResult;
1043
+ } finally {
1044
+ await engine.close();
1045
+ }
1046
+ }
1047
+ var import_node_path2;
1048
+ var init_search = __esm({
1049
+ "src/cli/commands/search.ts"() {
1050
+ "use strict";
1051
+ init_cjs_shims();
1052
+ import_node_path2 = require("path");
1053
+ init_search_engine();
1054
+ }
1055
+ });
1056
+
1057
+ // src/core/dependency-graph.ts
1058
+ function parseImports(content, filePath) {
1059
+ const edges = [];
1060
+ const seen = /* @__PURE__ */ new Set();
1061
+ for (const pattern of IMPORT_PATTERNS) {
1062
+ const regex = new RegExp(pattern.source, pattern.flags);
1063
+ const matches = [...content.matchAll(regex)];
1064
+ for (const match of matches) {
1065
+ if (pattern.source.includes("from")) {
1066
+ const symbolsRaw = match[1] || "";
1067
+ const target = match[2] || "";
1068
+ if (!target || target.startsWith(".") === false && !target.startsWith("/")) {
1069
+ continue;
1070
+ }
1071
+ const symbols = symbolsRaw.split(",").map(
1072
+ (s) => s.trim().split(/\s+as\s+/)[0]?.trim() || ""
1073
+ ).filter(Boolean);
1074
+ const key = `${filePath}->${target}`;
1075
+ if (!seen.has(key)) {
1076
+ seen.add(key);
1077
+ edges.push({ source: filePath, target, symbols });
1078
+ }
1079
+ } else if (pattern.source.includes("require")) {
1080
+ const target = match[1] || "";
1081
+ if (!target || !target.startsWith(".") && !target.startsWith("/")) {
1082
+ continue;
1083
+ }
1084
+ const key = `${filePath}->${target}`;
1085
+ if (!seen.has(key)) {
1086
+ seen.add(key);
1087
+ edges.push({ source: filePath, target, symbols: [] });
1088
+ }
1089
+ } else {
1090
+ const target = match[1] || "";
1091
+ if (!target || !target.startsWith(".") && !target.startsWith("/")) {
1092
+ continue;
1093
+ }
1094
+ const key = `${filePath}->${target}`;
1095
+ if (!seen.has(key)) {
1096
+ seen.add(key);
1097
+ edges.push({ source: filePath, target, symbols: [] });
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ return edges;
1103
+ }
1104
+ function parseExports(content) {
1105
+ const exportsList = [];
1106
+ for (const pattern of EXPORT_PATTERNS) {
1107
+ const regex = new RegExp(pattern.source, pattern.flags);
1108
+ const matches = [...content.matchAll(regex)];
1109
+ for (const match of matches) {
1110
+ if (match[1]) {
1111
+ const symbols = match[1].split(",").map(
1112
+ (s) => s.trim().split(/\s+as\s+/)[0]?.trim() || ""
1113
+ ).filter(Boolean);
1114
+ exportsList.push(...symbols);
1115
+ } else {
1116
+ exportsList.push("default");
1117
+ }
1118
+ }
1119
+ }
1120
+ return [...new Set(exportsList)];
1121
+ }
1122
+ function resolveImportPath(importPath, fromFile, projectRoot, extensions) {
1123
+ const cleanImport = importPath.replace(/\.(js|ts|tsx|jsx)$/, "");
1124
+ const baseDir = (0, import_node_path3.join)(projectRoot, fromFile, "..");
1125
+ const candidates = [
1126
+ ...extensions.map((ext) => (0, import_node_path3.resolve)(baseDir, `${cleanImport}${ext}`)),
1127
+ ...extensions.map((ext) => (0, import_node_path3.resolve)(baseDir, cleanImport, `index${ext}`))
1128
+ ];
1129
+ for (const candidate of candidates) {
1130
+ if ((0, import_node_fs2.existsSync)(candidate)) {
1131
+ return (0, import_node_path3.relative)(projectRoot, candidate).replace(/\\/g, "/");
1132
+ }
1133
+ }
1134
+ return null;
1135
+ }
1136
+ function collectFiles(dir, projectRoot, extensions, ignoreDirs) {
1137
+ const files = [];
1138
+ try {
1139
+ const entries = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
1140
+ for (const entry of entries) {
1141
+ const fullPath = (0, import_node_path3.join)(dir, entry.name);
1142
+ if (entry.isDirectory()) {
1143
+ if (!ignoreDirs.includes(entry.name)) {
1144
+ files.push(...collectFiles(fullPath, projectRoot, extensions, ignoreDirs));
1145
+ }
1146
+ } else if (entry.isFile() && extensions.includes((0, import_node_path3.extname)(entry.name))) {
1147
+ files.push((0, import_node_path3.relative)(projectRoot, fullPath).replace(/\\/g, "/"));
1148
+ }
1149
+ }
1150
+ } catch {
1151
+ }
1152
+ return files;
1153
+ }
1154
+ function createDependencyGraph(config) {
1155
+ const {
1156
+ projectRoot,
1157
+ maxDepth = 50,
1158
+ extensions = [".ts", ".tsx", ".js", ".jsx"],
1159
+ ignoreDirs = ["node_modules", "dist", ".git", ".sparn", "coverage"]
1160
+ } = config;
1161
+ const nodes = /* @__PURE__ */ new Map();
1162
+ let built = false;
1163
+ async function build() {
1164
+ nodes.clear();
1165
+ const files = collectFiles(projectRoot, projectRoot, extensions, ignoreDirs);
1166
+ for (const filePath of files) {
1167
+ const fullPath = (0, import_node_path3.join)(projectRoot, filePath);
1168
+ try {
1169
+ const content = (0, import_node_fs2.readFileSync)(fullPath, "utf-8");
1170
+ const stat = (0, import_node_fs2.statSync)(fullPath);
1171
+ const exports2 = parseExports(content);
1172
+ const imports = parseImports(content, filePath);
1173
+ const resolvedImports = [];
1174
+ for (const imp of imports) {
1175
+ const resolved = resolveImportPath(imp.target, filePath, projectRoot, extensions);
1176
+ if (resolved) {
1177
+ resolvedImports.push({ ...imp, target: resolved });
1178
+ }
1179
+ }
1180
+ nodes.set(filePath, {
1181
+ filePath,
1182
+ exports: exports2,
1183
+ imports: resolvedImports,
1184
+ callers: [],
1185
+ // Populated in second pass
1186
+ engram_score: 0,
1187
+ lastModified: stat.mtimeMs,
1188
+ tokenEstimate: estimateTokens(content)
1189
+ });
1190
+ } catch {
1191
+ }
1192
+ }
1193
+ for (const [filePath, node] of nodes) {
1194
+ for (const imp of node.imports) {
1195
+ const targetNode = nodes.get(imp.target);
1196
+ if (targetNode && !targetNode.callers.includes(filePath)) {
1197
+ targetNode.callers.push(filePath);
1198
+ }
1199
+ }
1200
+ }
1201
+ built = true;
1202
+ return nodes;
1203
+ }
1204
+ async function analyze() {
1205
+ if (!built) await build();
1206
+ const entryPoints = [];
1207
+ const orphans = [];
1208
+ const callerCounts = /* @__PURE__ */ new Map();
1209
+ for (const [filePath, node] of nodes) {
1210
+ if (node.callers.length === 0 && node.imports.length > 0) {
1211
+ entryPoints.push(filePath);
1212
+ }
1213
+ if (node.callers.length === 0 && node.imports.length === 0) {
1214
+ orphans.push(filePath);
1215
+ }
1216
+ callerCounts.set(filePath, node.callers.length);
1217
+ }
1218
+ const sortedByCallers = [...callerCounts.entries()].sort((a, b) => b[1] - a[1]).filter(([, count]) => count > 0);
1219
+ const hotPaths = sortedByCallers.slice(0, 10).map(([path]) => path);
1220
+ const totalTokens = [...nodes.values()].reduce((sum, n) => sum + n.tokenEstimate, 0);
1221
+ const hotPathTokens = hotPaths.reduce(
1222
+ (sum, path) => sum + (nodes.get(path)?.tokenEstimate || 0),
1223
+ 0
1224
+ );
1225
+ return {
1226
+ entryPoints,
1227
+ hotPaths,
1228
+ orphans,
1229
+ totalTokens,
1230
+ optimizedTokens: hotPathTokens
1231
+ };
1232
+ }
1233
+ async function focus(pattern) {
1234
+ if (!built) await build();
1235
+ const matching = /* @__PURE__ */ new Map();
1236
+ const lowerPattern = pattern.toLowerCase();
1237
+ for (const [filePath, node] of nodes) {
1238
+ if (filePath.toLowerCase().includes(lowerPattern)) {
1239
+ matching.set(filePath, node);
1240
+ for (const imp of node.imports) {
1241
+ const depNode = nodes.get(imp.target);
1242
+ if (depNode) {
1243
+ matching.set(imp.target, depNode);
1244
+ }
1245
+ }
1246
+ for (const caller of node.callers) {
1247
+ const callerNode = nodes.get(caller);
1248
+ if (callerNode) {
1249
+ matching.set(caller, callerNode);
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+ return matching;
1255
+ }
1256
+ async function getFilesFromEntry(entryPoint, depth = maxDepth) {
1257
+ if (!built) await build();
1258
+ const visited = /* @__PURE__ */ new Set();
1259
+ const queue = [{ path: entryPoint, depth: 0 }];
1260
+ while (queue.length > 0) {
1261
+ const item = queue.shift();
1262
+ if (!item) break;
1263
+ if (visited.has(item.path) || item.depth > depth) continue;
1264
+ visited.add(item.path);
1265
+ const node = nodes.get(item.path);
1266
+ if (node) {
1267
+ for (const imp of node.imports) {
1268
+ if (!visited.has(imp.target)) {
1269
+ queue.push({ path: imp.target, depth: item.depth + 1 });
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+ return [...visited];
1275
+ }
1276
+ function getNodes() {
1277
+ return nodes;
1278
+ }
1279
+ return {
1280
+ build,
1281
+ analyze,
1282
+ focus,
1283
+ getFilesFromEntry,
1284
+ getNodes
1285
+ };
1286
+ }
1287
+ var import_node_fs2, import_node_path3, IMPORT_PATTERNS, EXPORT_PATTERNS;
1288
+ var init_dependency_graph = __esm({
1289
+ "src/core/dependency-graph.ts"() {
1290
+ "use strict";
1291
+ init_cjs_shims();
1292
+ import_node_fs2 = require("fs");
1293
+ import_node_path3 = require("path");
1294
+ init_tokenizer();
1295
+ IMPORT_PATTERNS = [
1296
+ // import { Foo, Bar } from './module'
1297
+ /import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g,
1298
+ // import Foo from './module'
1299
+ /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
1300
+ // import * as Foo from './module'
1301
+ /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
1302
+ // import './module' (side-effect)
1303
+ /import\s+['"]([^'"]+)['"]/g,
1304
+ // require('./module')
1305
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
1306
+ ];
1307
+ EXPORT_PATTERNS = [
1308
+ // export { Foo, Bar }
1309
+ /export\s+\{([^}]+)\}/g,
1310
+ // export function/class/const/let/var/type/interface
1311
+ /export\s+(?:default\s+)?(?:function|class|const|let|var|type|interface|enum)\s+(\w+)/g,
1312
+ // export default
1313
+ /export\s+default\s+/g
1314
+ ];
1315
+ }
1316
+ });
1317
+
1318
+ // src/cli/commands/graph.ts
1319
+ var graph_exports = {};
1320
+ __export(graph_exports, {
1321
+ graphCommand: () => graphCommand
1322
+ });
1323
+ async function graphCommand(options) {
1324
+ const projectRoot = (0, import_node_path4.resolve)(process.cwd());
1325
+ const graph = createDependencyGraph({
1326
+ projectRoot,
1327
+ maxDepth: options.depth
1328
+ });
1329
+ await graph.build();
1330
+ let nodes = graph.getNodes();
1331
+ if (options.focus) {
1332
+ nodes = await graph.focus(options.focus);
1333
+ }
1334
+ if (options.entry) {
1335
+ const allNodes = graph.getNodes();
1336
+ if (!allNodes.has(options.entry)) {
1337
+ throw new Error(
1338
+ `Entry point not found in graph: ${options.entry}. Available: ${[...allNodes.keys()].slice(0, 5).join(", ")}${allNodes.size > 5 ? "..." : ""}`
1339
+ );
1340
+ }
1341
+ const files = await graph.getFilesFromEntry(options.entry, options.depth);
1342
+ const entryNodes = /* @__PURE__ */ new Map();
1343
+ for (const f of files) {
1344
+ const node = nodes.get(f);
1345
+ if (node) entryNodes.set(f, node);
1346
+ }
1347
+ nodes = entryNodes;
1348
+ }
1349
+ const analysis = await graph.analyze();
1350
+ const result = {
1351
+ analysis,
1352
+ nodeCount: nodes.size
1353
+ };
1354
+ if (options.json) {
1355
+ result.json = JSON.stringify(
1356
+ {
1357
+ analysis,
1358
+ nodeCount: nodes.size,
1359
+ nodes: Object.fromEntries(
1360
+ [...nodes.entries()].map(([k, v]) => [
1361
+ k,
1362
+ {
1363
+ exports: v.exports,
1364
+ imports: v.imports.map((i) => i.target),
1365
+ callers: v.callers,
1366
+ tokens: v.tokenEstimate
1367
+ }
1368
+ ])
1369
+ )
1370
+ },
1371
+ null,
1372
+ 2
1373
+ );
1374
+ }
1375
+ return result;
1376
+ }
1377
+ var import_node_path4;
1378
+ var init_graph = __esm({
1379
+ "src/cli/commands/graph.ts"() {
1380
+ "use strict";
1381
+ init_cjs_shims();
1382
+ import_node_path4 = require("path");
1383
+ init_dependency_graph();
1384
+ }
1385
+ });
1386
+
1387
+ // src/core/workflow-planner.ts
1388
+ function createWorkflowPlanner(projectRoot) {
1389
+ const plansDir = (0, import_node_path5.join)(projectRoot, ".sparn", "plans");
1390
+ if (!(0, import_node_fs3.existsSync)(plansDir)) {
1391
+ (0, import_node_fs3.mkdirSync)(plansDir, { recursive: true });
1392
+ }
1393
+ function sanitizeId(id) {
1394
+ return id.replace(/[/\\:.]/g, "").replace(/\.\./g, "");
1395
+ }
1396
+ function planPath(planId) {
1397
+ const safeId = sanitizeId(planId);
1398
+ if (!safeId) throw new Error("Invalid plan ID");
1399
+ return (0, import_node_path5.join)(plansDir, `plan-${safeId}.json`);
1400
+ }
1401
+ async function createPlan(taskDescription, filesNeeded, searchQueries, steps, tokenBudget = { planning: 0, estimated_execution: 0, max_file_reads: 5 }) {
1402
+ const id = (0, import_node_crypto4.randomUUID)().split("-")[0] || "plan";
1403
+ const plan = {
1404
+ id,
1405
+ created_at: Date.now(),
1406
+ task_description: taskDescription,
1407
+ steps: steps.map((s) => ({ ...s, status: "pending" })),
1408
+ token_budget: tokenBudget,
1409
+ files_needed: filesNeeded,
1410
+ search_queries: searchQueries,
1411
+ status: "draft"
1412
+ };
1413
+ (0, import_node_fs3.writeFileSync)(planPath(id), JSON.stringify(plan, null, 2), "utf-8");
1414
+ return plan;
1415
+ }
1416
+ async function loadPlan(planId) {
1417
+ const path = planPath(planId);
1418
+ if (!(0, import_node_fs3.existsSync)(path)) return null;
1419
+ try {
1420
+ return JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
1421
+ } catch {
1422
+ return null;
1423
+ }
1424
+ }
1425
+ async function listPlans() {
1426
+ if (!(0, import_node_fs3.existsSync)(plansDir)) return [];
1427
+ const files = (0, import_node_fs3.readdirSync)(plansDir).filter((f) => f.startsWith("plan-") && f.endsWith(".json"));
1428
+ const plans = [];
1429
+ for (const file of files) {
1430
+ try {
1431
+ const plan = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path5.join)(plansDir, file), "utf-8"));
1432
+ plans.push({
1433
+ id: plan.id,
1434
+ task: plan.task_description,
1435
+ status: plan.status,
1436
+ created: plan.created_at
1437
+ });
1438
+ } catch {
1439
+ }
1440
+ }
1441
+ return plans.sort((a, b) => b.created - a.created);
1442
+ }
1443
+ async function updateStep(planId, stepOrder, status, result) {
1444
+ const plan = await loadPlan(planId);
1445
+ if (!plan) throw new Error(`Plan ${planId} not found`);
1446
+ const step = plan.steps.find((s) => s.order === stepOrder);
1447
+ if (!step) throw new Error(`Step ${stepOrder} not found in plan ${planId}`);
1448
+ step.status = status;
1449
+ if (result !== void 0) {
1450
+ step.result = result;
1451
+ }
1452
+ (0, import_node_fs3.writeFileSync)(planPath(planId), JSON.stringify(plan, null, 2), "utf-8");
1453
+ }
1454
+ async function startExec(planId) {
1455
+ const plan = await loadPlan(planId);
1456
+ if (!plan) throw new Error(`Plan ${planId} not found`);
1457
+ plan.status = "executing";
1458
+ (0, import_node_fs3.writeFileSync)(planPath(planId), JSON.stringify(plan, null, 2), "utf-8");
1459
+ return {
1460
+ maxFileReads: plan.token_budget.max_file_reads,
1461
+ tokenBudget: plan.token_budget.estimated_execution,
1462
+ allowReplan: false
1463
+ };
1464
+ }
1465
+ async function verify(planId) {
1466
+ const plan = await loadPlan(planId);
1467
+ if (!plan) throw new Error(`Plan ${planId} not found`);
1468
+ let stepsCompleted = 0;
1469
+ let stepsFailed = 0;
1470
+ let stepsSkipped = 0;
1471
+ let tokensUsed = 0;
1472
+ const details = [];
1473
+ for (const step of plan.steps) {
1474
+ switch (step.status) {
1475
+ case "completed":
1476
+ stepsCompleted++;
1477
+ tokensUsed += step.estimated_tokens;
1478
+ break;
1479
+ case "failed":
1480
+ stepsFailed++;
1481
+ break;
1482
+ case "skipped":
1483
+ stepsSkipped++;
1484
+ break;
1485
+ default:
1486
+ break;
1487
+ }
1488
+ details.push({
1489
+ step: step.order,
1490
+ action: step.action,
1491
+ target: step.target,
1492
+ status: step.status
1493
+ });
1494
+ }
1495
+ const totalSteps = plan.steps.length;
1496
+ const success = stepsFailed === 0 && stepsCompleted === totalSteps;
1497
+ const hasInProgress = plan.steps.some(
1498
+ (s) => s.status === "pending" || s.status === "in_progress"
1499
+ );
1500
+ if (!hasInProgress) {
1501
+ plan.status = success ? "completed" : "failed";
1502
+ (0, import_node_fs3.writeFileSync)(planPath(planId), JSON.stringify(plan, null, 2), "utf-8");
1503
+ }
1504
+ return {
1505
+ planId,
1506
+ stepsCompleted,
1507
+ stepsFailed,
1508
+ stepsSkipped,
1509
+ totalSteps,
1510
+ tokensBudgeted: plan.token_budget.estimated_execution,
1511
+ tokensUsed,
1512
+ success,
1513
+ details
1514
+ };
1515
+ }
1516
+ function getPlansDir() {
1517
+ return plansDir;
1518
+ }
1519
+ return {
1520
+ createPlan,
1521
+ loadPlan,
1522
+ listPlans,
1523
+ updateStep,
1524
+ startExec,
1525
+ verify,
1526
+ getPlansDir
1527
+ };
1528
+ }
1529
+ var import_node_crypto4, import_node_fs3, import_node_path5;
1530
+ var init_workflow_planner = __esm({
1531
+ "src/core/workflow-planner.ts"() {
1532
+ "use strict";
1533
+ init_cjs_shims();
1534
+ import_node_crypto4 = require("crypto");
1535
+ import_node_fs3 = require("fs");
1536
+ import_node_path5 = require("path");
1537
+ }
1538
+ });
1539
+
1540
+ // src/cli/commands/plan.ts
1541
+ var plan_exports = {};
1542
+ __export(plan_exports, {
1543
+ planCommand: () => planCommand,
1544
+ planListCommand: () => planListCommand
1545
+ });
1546
+ async function planCommand(options) {
1547
+ const projectRoot = (0, import_node_path6.resolve)(process.cwd());
1548
+ const planner = createWorkflowPlanner(projectRoot);
1549
+ const steps = [];
1550
+ let order = 1;
1551
+ if (options.searches) {
1552
+ for (const query of options.searches) {
1553
+ steps.push({
1554
+ order: order++,
1555
+ action: "search",
1556
+ target: query,
1557
+ description: `Search for: ${query}`,
1558
+ dependencies: [],
1559
+ estimated_tokens: 500
1560
+ });
1561
+ }
1562
+ }
1563
+ if (options.files) {
1564
+ for (const file of options.files) {
1565
+ steps.push({
1566
+ order: order++,
1567
+ action: "read",
1568
+ target: file,
1569
+ description: `Read file: ${file}`,
1570
+ dependencies: [],
1571
+ estimated_tokens: 1e3
1572
+ });
1573
+ }
1574
+ }
1575
+ steps.push({
1576
+ order: order++,
1577
+ action: "verify",
1578
+ target: "tests",
1579
+ description: "Run tests to verify changes",
1580
+ dependencies: steps.map((s) => s.order),
1581
+ estimated_tokens: 200
1582
+ });
1583
+ const plan = await planner.createPlan(
1584
+ options.task,
1585
+ options.files || [],
1586
+ options.searches || [],
1587
+ steps,
1588
+ {
1589
+ planning: 0,
1590
+ estimated_execution: steps.reduce((sum, s) => sum + s.estimated_tokens, 0),
1591
+ max_file_reads: options.maxReads || 5
1592
+ }
1593
+ );
1594
+ const result = {
1595
+ plan,
1596
+ message: `Plan ${plan.id} created with ${plan.steps.length} steps`
1597
+ };
1598
+ if (options.json) {
1599
+ result.json = JSON.stringify(plan, null, 2);
1600
+ }
1601
+ return result;
1602
+ }
1603
+ async function planListCommand(options) {
1604
+ const projectRoot = (0, import_node_path6.resolve)(process.cwd());
1605
+ const planner = createWorkflowPlanner(projectRoot);
1606
+ const plans = await planner.listPlans();
1607
+ const result = { plans };
1608
+ if (options.json) {
1609
+ result.json = JSON.stringify(plans, null, 2);
1610
+ }
1611
+ return result;
1612
+ }
1613
+ var import_node_path6;
1614
+ var init_plan = __esm({
1615
+ "src/cli/commands/plan.ts"() {
1616
+ "use strict";
1617
+ init_cjs_shims();
1618
+ import_node_path6 = require("path");
1619
+ init_workflow_planner();
1620
+ }
1621
+ });
1622
+
1623
+ // src/cli/commands/exec.ts
1624
+ var exec_exports = {};
1625
+ __export(exec_exports, {
1626
+ execCommand: () => execCommand
1627
+ });
1628
+ async function execCommand(options) {
1629
+ const projectRoot = (0, import_node_path7.resolve)(process.cwd());
1630
+ const planner = createWorkflowPlanner(projectRoot);
1631
+ const plan = await planner.loadPlan(options.planId);
1632
+ if (!plan) {
1633
+ throw new Error(`Plan ${options.planId} not found`);
1634
+ }
1635
+ const constraints = await planner.startExec(options.planId);
1636
+ const updatedPlan = await planner.loadPlan(options.planId) || plan;
1637
+ const result = {
1638
+ plan: updatedPlan,
1639
+ constraints,
1640
+ message: `Executing plan ${options.planId}: max ${constraints.maxFileReads} reads, ${constraints.tokenBudget} token budget`
1641
+ };
1642
+ if (options.json) {
1643
+ result.json = JSON.stringify({ plan: updatedPlan, constraints }, null, 2);
1644
+ }
1645
+ return result;
1646
+ }
1647
+ var import_node_path7;
1648
+ var init_exec = __esm({
1649
+ "src/cli/commands/exec.ts"() {
1650
+ "use strict";
1651
+ init_cjs_shims();
1652
+ import_node_path7 = require("path");
1653
+ init_workflow_planner();
1654
+ }
1655
+ });
1656
+
1657
+ // src/cli/commands/verify.ts
1658
+ var verify_exports = {};
1659
+ __export(verify_exports, {
1660
+ verifyCommand: () => verifyCommand
1661
+ });
1662
+ async function verifyCommand(options) {
1663
+ const projectRoot = (0, import_node_path8.resolve)(process.cwd());
1664
+ const planner = createWorkflowPlanner(projectRoot);
1665
+ const plan = await planner.loadPlan(options.planId);
1666
+ if (!plan) {
1667
+ throw new Error(`Plan ${options.planId} not found`);
1668
+ }
1669
+ const verification = await planner.verify(options.planId);
1670
+ const status = verification.success ? "PASSED" : "FAILED";
1671
+ const message = `Plan ${options.planId} verification: ${status} (${verification.stepsCompleted}/${verification.totalSteps} steps completed)`;
1672
+ const result = {
1673
+ verification,
1674
+ message
1675
+ };
1676
+ if (options.json) {
1677
+ result.json = JSON.stringify(verification, null, 2);
1678
+ }
1679
+ return result;
1680
+ }
1681
+ var import_node_path8;
1682
+ var init_verify = __esm({
1683
+ "src/cli/commands/verify.ts"() {
1684
+ "use strict";
1685
+ init_cjs_shims();
1686
+ import_node_path8 = require("path");
1687
+ init_workflow_planner();
1688
+ }
1689
+ });
1690
+
1691
+ // src/core/docs-generator.ts
1692
+ function detectEntryPoints(projectRoot) {
1693
+ const entries = [];
1694
+ const candidates = [
1695
+ { path: "src/index.ts", desc: "Library API" },
1696
+ { path: "src/cli/index.ts", desc: "CLI entry point" },
1697
+ { path: "src/daemon/index.ts", desc: "Daemon process" },
1698
+ { path: "src/mcp/index.ts", desc: "MCP server" },
1699
+ { path: "src/main.ts", desc: "Main entry" },
1700
+ { path: "src/app.ts", desc: "App entry" },
1701
+ { path: "src/server.ts", desc: "Server entry" },
1702
+ { path: "index.ts", desc: "Root entry" },
1703
+ { path: "index.js", desc: "Root entry" }
1704
+ ];
1705
+ for (const c of candidates) {
1706
+ if ((0, import_node_fs4.existsSync)((0, import_node_path9.join)(projectRoot, c.path))) {
1707
+ entries.push({ path: c.path, description: c.desc });
1708
+ }
1709
+ }
1710
+ return entries;
1711
+ }
1712
+ function scanModules(dir, projectRoot, ignoreDirs = ["node_modules", "dist", ".git", ".sparn", "coverage"]) {
1713
+ const modules = [];
1714
+ try {
1715
+ const entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
1716
+ for (const entry of entries) {
1717
+ const fullPath = (0, import_node_path9.join)(dir, entry.name);
1718
+ if (entry.isDirectory() && !ignoreDirs.includes(entry.name)) {
1719
+ modules.push(...scanModules(fullPath, projectRoot, ignoreDirs));
1720
+ } else if (entry.isFile() && [".ts", ".tsx", ".js", ".jsx"].includes((0, import_node_path9.extname)(entry.name))) {
1721
+ try {
1722
+ const content = (0, import_node_fs4.readFileSync)(fullPath, "utf-8");
1723
+ modules.push({
1724
+ path: (0, import_node_path9.relative)(projectRoot, fullPath).replace(/\\/g, "/"),
1725
+ lines: content.split("\n").length
1726
+ });
1727
+ } catch {
1728
+ }
1729
+ }
1730
+ }
1731
+ } catch {
1732
+ }
1733
+ return modules;
1734
+ }
1735
+ function readPackageJson(projectRoot) {
1736
+ const pkgPath = (0, import_node_path9.join)(projectRoot, "package.json");
1737
+ if (!(0, import_node_fs4.existsSync)(pkgPath)) return null;
1738
+ try {
1739
+ return JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf-8"));
1740
+ } catch {
1741
+ return null;
1742
+ }
1743
+ }
1744
+ function detectStack(projectRoot) {
1745
+ const stack = [];
1746
+ const pkg = readPackageJson(projectRoot);
1747
+ if (!pkg) return stack;
1748
+ const allDeps = {
1749
+ ...pkg.dependencies,
1750
+ ...pkg.devDependencies
1751
+ };
1752
+ if (allDeps["typescript"]) stack.push("TypeScript");
1753
+ if (allDeps["vitest"]) stack.push("Vitest");
1754
+ if (allDeps["@biomejs/biome"]) stack.push("Biome");
1755
+ if (allDeps["eslint"]) stack.push("ESLint");
1756
+ if (allDeps["prettier"]) stack.push("Prettier");
1757
+ if (allDeps["react"]) stack.push("React");
1758
+ if (allDeps["next"]) stack.push("Next.js");
1759
+ if (allDeps["express"]) stack.push("Express");
1760
+ if (allDeps["commander"]) stack.push("Commander.js CLI");
1761
+ if (allDeps["better-sqlite3"]) stack.push("SQLite (better-sqlite3)");
1762
+ if (allDeps["zod"]) stack.push("Zod validation");
1763
+ return stack;
1764
+ }
1765
+ function createDocsGenerator(config) {
1766
+ const { projectRoot, includeGraph = true } = config;
1767
+ async function generate(graph) {
1768
+ const lines = [];
1769
+ const pkg = readPackageJson(projectRoot);
1770
+ const projectName = pkg?.name || "Project";
1771
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1772
+ lines.push(`# ${projectName} \u2014 Developer Guide`);
1773
+ lines.push(`<!-- Auto-generated by Sparn v1.3.0 \u2014 ${now} -->`);
1774
+ lines.push("");
1775
+ const stack = detectStack(projectRoot);
1776
+ if (stack.length > 0) {
1777
+ lines.push(`**Stack**: ${stack.join(", ")}`);
1778
+ lines.push("");
1779
+ }
1780
+ if (pkg?.scripts) {
1781
+ lines.push("## Commands");
1782
+ lines.push("");
1783
+ const important = ["build", "dev", "test", "lint", "typecheck", "start"];
1784
+ for (const cmd of important) {
1785
+ if (pkg.scripts[cmd]) {
1786
+ lines.push(`- \`npm run ${cmd}\` \u2014 \`${pkg.scripts[cmd]}\``);
1787
+ }
1788
+ }
1789
+ lines.push("");
1790
+ }
1791
+ const entryPoints = detectEntryPoints(projectRoot);
1792
+ if (entryPoints.length > 0) {
1793
+ lines.push("## Entry Points");
1794
+ lines.push("");
1795
+ for (const ep of entryPoints) {
1796
+ lines.push(`- \`${ep.path}\` \u2014 ${ep.description}`);
1797
+ }
1798
+ lines.push("");
1799
+ }
1800
+ const srcDir = (0, import_node_path9.join)(projectRoot, "src");
1801
+ if ((0, import_node_fs4.existsSync)(srcDir)) {
1802
+ const modules = scanModules(srcDir, projectRoot);
1803
+ const dirGroups = /* @__PURE__ */ new Map();
1804
+ for (const mod of modules) {
1805
+ const parts = mod.path.split("/");
1806
+ const dir = parts.length > 2 ? parts.slice(0, 2).join("/") : parts[0] || "";
1807
+ if (!dirGroups.has(dir)) {
1808
+ dirGroups.set(dir, []);
1809
+ }
1810
+ dirGroups.get(dir)?.push({
1811
+ file: parts[parts.length - 1] || mod.path,
1812
+ lines: mod.lines
1813
+ });
1814
+ }
1815
+ lines.push("## Structure");
1816
+ lines.push("");
1817
+ for (const [dir, files] of dirGroups) {
1818
+ lines.push(`### ${dir}/ (${files.length} files)`);
1819
+ const shown = files.slice(0, 8);
1820
+ for (const f of shown) {
1821
+ lines.push(`- \`${f.file}\` (${f.lines}L)`);
1822
+ }
1823
+ if (files.length > 8) {
1824
+ lines.push(`- ... and ${files.length - 8} more`);
1825
+ }
1826
+ lines.push("");
1827
+ }
1828
+ }
1829
+ if (includeGraph && graph) {
1830
+ try {
1831
+ const analysis = await graph.analyze();
1832
+ lines.push("## Hot Dependencies (most imported)");
1833
+ lines.push("");
1834
+ for (const path of analysis.hotPaths.slice(0, 5)) {
1835
+ const node = graph.getNodes().get(path);
1836
+ const callerCount = node?.callers.length || 0;
1837
+ lines.push(`- \`${path}\` (imported by ${callerCount} modules)`);
1838
+ }
1839
+ lines.push("");
1840
+ if (analysis.orphans.length > 0) {
1841
+ lines.push(
1842
+ `**Orphaned files** (${analysis.orphans.length}): ${analysis.orphans.slice(0, 3).join(", ")}${analysis.orphans.length > 3 ? "..." : ""}`
1843
+ );
1844
+ lines.push("");
1845
+ }
1846
+ lines.push(
1847
+ `**Total tokens**: ${analysis.totalTokens.toLocaleString()} | **Hot path tokens**: ${analysis.optimizedTokens.toLocaleString()}`
1848
+ );
1849
+ lines.push("");
1850
+ } catch {
1851
+ }
1852
+ }
1853
+ const sparnConfigPath = (0, import_node_path9.join)(projectRoot, ".sparn", "config.yaml");
1854
+ if ((0, import_node_fs4.existsSync)(sparnConfigPath)) {
1855
+ lines.push("## Sparn Optimization");
1856
+ lines.push("");
1857
+ lines.push("Sparn is active in this project. Key features:");
1858
+ lines.push("- Context optimization (60-70% token reduction)");
1859
+ lines.push("- Use `sparn search` before reading files");
1860
+ lines.push("- Use `sparn graph` to understand dependencies");
1861
+ lines.push("- Use `sparn plan` for planning, `sparn exec` for execution");
1862
+ lines.push("");
1863
+ }
1864
+ if (config.customSections) {
1865
+ for (const section of config.customSections) {
1866
+ lines.push(section);
1867
+ lines.push("");
1868
+ }
1869
+ }
1870
+ return lines.join("\n");
1871
+ }
1872
+ return { generate };
1873
+ }
1874
+ var import_node_fs4, import_node_path9;
1875
+ var init_docs_generator = __esm({
1876
+ "src/core/docs-generator.ts"() {
1877
+ "use strict";
1878
+ init_cjs_shims();
1879
+ import_node_fs4 = require("fs");
1880
+ import_node_path9 = require("path");
1881
+ }
1882
+ });
1883
+
1884
+ // src/cli/commands/docs.ts
1885
+ var docs_exports = {};
1886
+ __export(docs_exports, {
1887
+ docsCommand: () => docsCommand
1888
+ });
1889
+ async function docsCommand(options) {
1890
+ const projectRoot = (0, import_node_path10.resolve)(process.cwd());
1891
+ const generator = createDocsGenerator({
1892
+ projectRoot,
1893
+ includeGraph: options.includeGraph !== false
1894
+ });
1895
+ let graph;
1896
+ if (options.includeGraph !== false) {
1897
+ graph = createDependencyGraph({ projectRoot });
1898
+ await graph.build();
1899
+ }
1900
+ const content = await generator.generate(graph);
1901
+ if (options.json) {
1902
+ return {
1903
+ content,
1904
+ message: `CLAUDE.md generated (${content.split("\n").length} lines)`
1905
+ };
1906
+ }
1907
+ const outputPath = options.output || (0, import_node_path10.resolve)(projectRoot, "CLAUDE.md");
1908
+ (0, import_node_fs5.writeFileSync)(outputPath, content, "utf-8");
1909
+ return {
1910
+ content,
1911
+ outputPath,
1912
+ message: `CLAUDE.md generated at ${outputPath} (${content.split("\n").length} lines)`
1913
+ };
1914
+ }
1915
+ var import_node_fs5, import_node_path10;
1916
+ var init_docs = __esm({
1917
+ "src/cli/commands/docs.ts"() {
1918
+ "use strict";
1919
+ init_cjs_shims();
1920
+ import_node_fs5 = require("fs");
1921
+ import_node_path10 = require("path");
1922
+ init_dependency_graph();
1923
+ init_docs_generator();
1924
+ }
1925
+ });
1926
+
1927
+ // src/core/debt-tracker.ts
1928
+ function createDebtTracker(dbPath) {
1929
+ const db = new import_better_sqlite32.default(dbPath);
1930
+ db.pragma("journal_mode = WAL");
1931
+ db.exec(`
1932
+ CREATE TABLE IF NOT EXISTS tech_debt (
1933
+ id TEXT PRIMARY KEY NOT NULL,
1934
+ description TEXT NOT NULL,
1935
+ created_at INTEGER NOT NULL,
1936
+ repayment_date INTEGER NOT NULL,
1937
+ severity TEXT NOT NULL CHECK(severity IN ('P0', 'P1', 'P2')),
1938
+ token_cost INTEGER NOT NULL DEFAULT 0,
1939
+ files_affected TEXT NOT NULL DEFAULT '[]',
1940
+ status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'in_progress', 'resolved')),
1941
+ resolution_tokens INTEGER,
1942
+ resolved_at INTEGER
1943
+ );
1944
+ `);
1945
+ db.exec(`
1946
+ CREATE INDEX IF NOT EXISTS idx_debt_status ON tech_debt(status);
1947
+ CREATE INDEX IF NOT EXISTS idx_debt_severity ON tech_debt(severity);
1948
+ CREATE INDEX IF NOT EXISTS idx_debt_repayment ON tech_debt(repayment_date);
1949
+ `);
1950
+ const insertStmt = db.prepare(`
1951
+ INSERT INTO tech_debt (id, description, created_at, repayment_date, severity, token_cost, files_affected, status)
1952
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'open')
1953
+ `);
1954
+ const getStmt = db.prepare("SELECT * FROM tech_debt WHERE id = ?");
1955
+ function rowToDebt(row) {
1956
+ return {
1957
+ id: row["id"],
1958
+ description: row["description"],
1959
+ created_at: row["created_at"],
1960
+ repayment_date: row["repayment_date"],
1961
+ severity: row["severity"],
1962
+ token_cost: row["token_cost"],
1963
+ files_affected: JSON.parse(row["files_affected"] || "[]"),
1964
+ status: row["status"],
1965
+ resolution_tokens: row["resolution_tokens"],
1966
+ resolved_at: row["resolved_at"]
1967
+ };
1968
+ }
1969
+ async function add(debt) {
1970
+ const id = (0, import_node_crypto5.randomUUID)().split("-")[0] || "debt";
1971
+ const created_at = Date.now();
1972
+ insertStmt.run(
1973
+ id,
1974
+ debt.description,
1975
+ created_at,
1976
+ debt.repayment_date,
1977
+ debt.severity,
1978
+ debt.token_cost,
1979
+ JSON.stringify(debt.files_affected)
1980
+ );
1981
+ return {
1982
+ id,
1983
+ created_at,
1984
+ status: "open",
1985
+ description: debt.description,
1986
+ repayment_date: debt.repayment_date,
1987
+ severity: debt.severity,
1988
+ token_cost: debt.token_cost,
1989
+ files_affected: debt.files_affected
1990
+ };
1991
+ }
1992
+ async function list(filter = {}) {
1993
+ let sql = "SELECT * FROM tech_debt WHERE 1=1";
1994
+ const params = [];
1995
+ if (filter.status) {
1996
+ sql += " AND status = ?";
1997
+ params.push(filter.status);
1998
+ }
1999
+ if (filter.severity) {
2000
+ sql += " AND severity = ?";
2001
+ params.push(filter.severity);
2002
+ }
2003
+ if (filter.overdue) {
2004
+ sql += " AND repayment_date < ? AND status != ?";
2005
+ params.push(Date.now());
2006
+ params.push("resolved");
2007
+ }
2008
+ sql += " ORDER BY severity ASC, repayment_date ASC";
2009
+ const rows = db.prepare(sql).all(...params);
2010
+ return rows.map(rowToDebt);
2011
+ }
2012
+ async function get(id) {
2013
+ const row = getStmt.get(id);
2014
+ if (!row) return null;
2015
+ return rowToDebt(row);
2016
+ }
2017
+ async function resolve9(id, resolutionTokens) {
2018
+ const result = db.prepare(
2019
+ "UPDATE tech_debt SET status = ?, resolution_tokens = ?, resolved_at = ? WHERE id = ?"
2020
+ ).run("resolved", resolutionTokens ?? null, Date.now(), id);
2021
+ if (result.changes === 0) {
2022
+ throw new Error(`Debt not found: ${id}`);
2023
+ }
2024
+ }
2025
+ async function start(id) {
2026
+ const result = db.prepare("UPDATE tech_debt SET status = ? WHERE id = ?").run("in_progress", id);
2027
+ if (result.changes === 0) {
2028
+ throw new Error(`Debt not found: ${id}`);
2029
+ }
2030
+ }
2031
+ async function remove(id) {
2032
+ db.prepare("DELETE FROM tech_debt WHERE id = ?").run(id);
2033
+ }
2034
+ async function stats() {
2035
+ const all = db.prepare("SELECT * FROM tech_debt").all();
2036
+ const debts = all.map(rowToDebt);
2037
+ const now = Date.now();
2038
+ const open = debts.filter((d) => d.status === "open");
2039
+ const inProgress = debts.filter((d) => d.status === "in_progress");
2040
+ const resolved = debts.filter((d) => d.status === "resolved");
2041
+ const overdue = debts.filter((d) => d.status !== "resolved" && d.repayment_date < now);
2042
+ const totalTokenCost = debts.reduce((sum, d) => sum + d.token_cost, 0);
2043
+ const resolvedTokenCost = resolved.reduce(
2044
+ (sum, d) => sum + (d.resolution_tokens || d.token_cost),
2045
+ 0
2046
+ );
2047
+ const resolvedOnTime = resolved.filter(
2048
+ (d) => d.resolved_at && d.resolved_at <= d.repayment_date
2049
+ ).length;
2050
+ const repaymentRate = resolved.length > 0 ? resolvedOnTime / resolved.length : 0;
2051
+ return {
2052
+ total: debts.length,
2053
+ open: open.length,
2054
+ in_progress: inProgress.length,
2055
+ resolved: resolved.length,
2056
+ overdue: overdue.length,
2057
+ totalTokenCost,
2058
+ resolvedTokenCost,
2059
+ repaymentRate
2060
+ };
2061
+ }
2062
+ async function getCritical() {
2063
+ const rows = db.prepare(
2064
+ "SELECT * FROM tech_debt WHERE severity = 'P0' AND status != 'resolved' ORDER BY repayment_date ASC"
2065
+ ).all();
2066
+ return rows.map(rowToDebt);
2067
+ }
2068
+ async function close() {
2069
+ db.close();
2070
+ }
2071
+ return {
2072
+ add,
2073
+ list,
2074
+ get,
2075
+ resolve: resolve9,
2076
+ start,
2077
+ remove,
2078
+ stats,
2079
+ getCritical,
2080
+ close
2081
+ };
2082
+ }
2083
+ var import_node_crypto5, import_better_sqlite32;
2084
+ var init_debt_tracker = __esm({
2085
+ "src/core/debt-tracker.ts"() {
2086
+ "use strict";
2087
+ init_cjs_shims();
2088
+ import_node_crypto5 = require("crypto");
2089
+ import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
2090
+ }
2091
+ });
2092
+
2093
+ // src/cli/commands/debt.ts
2094
+ var debt_exports = {};
2095
+ __export(debt_exports, {
2096
+ debtCommand: () => debtCommand
2097
+ });
2098
+ async function debtCommand(options) {
2099
+ const projectRoot = (0, import_node_path11.resolve)(process.cwd());
2100
+ const dbPath = (0, import_node_path11.resolve)(projectRoot, ".sparn", "memory.db");
2101
+ const tracker = createDebtTracker(dbPath);
2102
+ try {
2103
+ switch (options.subcommand) {
2104
+ case "add": {
2105
+ if (!options.description) {
2106
+ throw new Error("Description is required");
2107
+ }
2108
+ const severity = options.severity || "P1";
2109
+ if (!["P0", "P1", "P2"].includes(severity)) {
2110
+ throw new Error("Severity must be P0, P1, or P2");
2111
+ }
2112
+ let repaymentDate;
2113
+ if (options.due) {
2114
+ repaymentDate = new Date(options.due).getTime();
2115
+ if (Number.isNaN(repaymentDate)) {
2116
+ throw new Error(`Invalid date: ${options.due}`);
2117
+ }
2118
+ } else {
2119
+ repaymentDate = Date.now() + 14 * 24 * 60 * 60 * 1e3;
2120
+ }
2121
+ const debt = await tracker.add({
2122
+ description: options.description,
2123
+ repayment_date: repaymentDate,
2124
+ severity,
2125
+ token_cost: options.tokenCost || 0,
2126
+ files_affected: options.files || []
2127
+ });
2128
+ const result = {
2129
+ debt,
2130
+ message: `Debt ${debt.id} added (${severity}): ${options.description}`
2131
+ };
2132
+ if (options.json) {
2133
+ result.json = JSON.stringify(debt, null, 2);
2134
+ }
2135
+ return result;
2136
+ }
2137
+ case "list": {
2138
+ const debts = await tracker.list({
2139
+ overdue: options.overdue
2140
+ });
2141
+ const result = {
2142
+ debts,
2143
+ message: `${debts.length} debt item(s)`
2144
+ };
2145
+ if (options.json) {
2146
+ result.json = JSON.stringify(debts, null, 2);
2147
+ }
2148
+ return result;
2149
+ }
2150
+ case "resolve": {
2151
+ if (!options.id) {
2152
+ throw new Error("Debt ID is required");
2153
+ }
2154
+ await tracker.resolve(options.id, options.tokenCost);
2155
+ return {
2156
+ message: `Debt ${options.id} resolved`
2157
+ };
2158
+ }
2159
+ case "start": {
2160
+ if (!options.id) {
2161
+ throw new Error("Debt ID is required");
2162
+ }
2163
+ await tracker.start(options.id);
2164
+ return {
2165
+ message: `Debt ${options.id} started`
2166
+ };
2167
+ }
2168
+ case "stats": {
2169
+ const stats = await tracker.stats();
2170
+ const result = {
2171
+ stats,
2172
+ message: `${stats.total} total, ${stats.open} open, ${stats.overdue} overdue, ${(stats.repaymentRate * 100).toFixed(0)}% on-time rate`
2173
+ };
2174
+ if (options.json) {
2175
+ result.json = JSON.stringify(stats, null, 2);
2176
+ }
2177
+ return result;
2178
+ }
2179
+ default:
2180
+ throw new Error(`Unknown subcommand: ${options.subcommand}`);
2181
+ }
2182
+ } finally {
2183
+ await tracker.close();
2184
+ }
2185
+ }
2186
+ var import_node_path11;
2187
+ var init_debt = __esm({
2188
+ "src/cli/commands/debt.ts"() {
2189
+ "use strict";
2190
+ init_cjs_shims();
2191
+ import_node_path11 = require("path");
2192
+ init_debt_tracker();
2193
+ }
2194
+ });
2195
+
2196
+ // src/daemon/daemon-process.ts
2197
+ var daemon_process_exports = {};
2198
+ __export(daemon_process_exports, {
2199
+ createDaemonCommand: () => createDaemonCommand
2200
+ });
2201
+ function createDaemonCommand() {
2202
+ function isDaemonRunning(pidFile) {
2203
+ if (!(0, import_node_fs7.existsSync)(pidFile)) {
2204
+ return { running: false };
2205
+ }
2206
+ try {
2207
+ const pidStr = (0, import_node_fs7.readFileSync)(pidFile, "utf-8").trim();
2208
+ const pid = Number.parseInt(pidStr, 10);
2209
+ if (Number.isNaN(pid)) {
2210
+ return { running: false };
2211
+ }
2212
+ try {
2213
+ process.kill(pid, 0);
2214
+ return { running: true, pid };
2215
+ } catch {
2216
+ (0, import_node_fs7.unlinkSync)(pidFile);
2217
+ return { running: false };
2218
+ }
2219
+ } catch {
2220
+ return { running: false };
2221
+ }
2222
+ }
2223
+ function writePidFile(pidFile, pid) {
2224
+ const dir = (0, import_node_path12.dirname)(pidFile);
2225
+ if (!(0, import_node_fs7.existsSync)(dir)) {
2226
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
2227
+ }
2228
+ (0, import_node_fs7.writeFileSync)(pidFile, String(pid), "utf-8");
2229
+ }
2230
+ function removePidFile(pidFile) {
2231
+ if ((0, import_node_fs7.existsSync)(pidFile)) {
2232
+ (0, import_node_fs7.unlinkSync)(pidFile);
2233
+ }
2234
+ }
2235
+ async function start(config) {
2236
+ const { pidFile, logFile } = config.realtime;
2237
+ const status2 = isDaemonRunning(pidFile);
2238
+ if (status2.running) {
2239
+ return {
2240
+ success: false,
2241
+ pid: status2.pid,
2242
+ message: `Daemon already running (PID ${status2.pid})`,
2243
+ error: "Already running"
2244
+ };
2245
+ }
2246
+ try {
2247
+ const __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
2248
+ const __dirname = (0, import_node_path12.dirname)(__filename2);
2249
+ const daemonPath = (0, import_node_path12.join)(__dirname, "..", "daemon", "index.js");
2250
+ const isWindows = process.platform === "win32";
2251
+ const childEnv = {
2252
+ ...process.env,
2253
+ SPARN_CONFIG: JSON.stringify(config),
2254
+ SPARN_PID_FILE: pidFile,
2255
+ SPARN_LOG_FILE: logFile
2256
+ };
2257
+ if (isWindows) {
2258
+ const configFile = (0, import_node_path12.join)((0, import_node_path12.dirname)(pidFile), "daemon-config.json");
2259
+ (0, import_node_fs7.writeFileSync)(configFile, JSON.stringify({ config, pidFile, logFile }), "utf-8");
2260
+ const launcherFile = (0, import_node_path12.join)((0, import_node_path12.dirname)(pidFile), "daemon-launcher.mjs");
2261
+ const launcherCode = [
2262
+ `import { readFileSync } from 'node:fs';`,
2263
+ `const cfg = JSON.parse(readFileSync(${JSON.stringify(configFile)}, 'utf-8'));`,
2264
+ `process.env.SPARN_CONFIG = JSON.stringify(cfg.config);`,
2265
+ `process.env.SPARN_PID_FILE = cfg.pidFile;`,
2266
+ `process.env.SPARN_LOG_FILE = cfg.logFile;`,
2267
+ `await import(${JSON.stringify(`file:///${daemonPath.replace(/\\/g, "/")}`)});`
2268
+ ].join("\n");
2269
+ (0, import_node_fs7.writeFileSync)(launcherFile, launcherCode, "utf-8");
2270
+ const ps = (0, import_node_child_process2.spawn)(
2271
+ "powershell.exe",
2272
+ [
2273
+ "-NoProfile",
2274
+ "-WindowStyle",
2275
+ "Hidden",
2276
+ "-Command",
2277
+ `Start-Process -FilePath '${process.execPath}' -ArgumentList '${launcherFile}' -WindowStyle Hidden`
2278
+ ],
2279
+ { stdio: "ignore", windowsHide: true }
2280
+ );
2281
+ ps.unref();
2282
+ await new Promise((resolve9) => setTimeout(resolve9, 2e3));
2283
+ if ((0, import_node_fs7.existsSync)(pidFile)) {
2284
+ const pid = Number.parseInt((0, import_node_fs7.readFileSync)(pidFile, "utf-8").trim(), 10);
2285
+ if (!Number.isNaN(pid)) {
2286
+ return {
2287
+ success: true,
2288
+ pid,
2289
+ message: `Daemon started (PID ${pid})`
2290
+ };
2291
+ }
2292
+ }
2293
+ return {
2294
+ success: false,
2295
+ message: "Daemon failed to start (no PID file written)",
2296
+ error: "Timeout waiting for daemon PID"
2297
+ };
2298
+ }
2299
+ const child = (0, import_node_child_process2.spawn)(process.execPath, [daemonPath], {
2300
+ detached: true,
2301
+ stdio: "ignore",
2302
+ env: childEnv
2303
+ });
2304
+ child.unref();
2305
+ if (child.pid) {
2306
+ writePidFile(pidFile, child.pid);
2307
+ return {
2308
+ success: true,
2309
+ pid: child.pid,
2310
+ message: `Daemon started (PID ${child.pid})`
2311
+ };
2312
+ }
2313
+ return {
2314
+ success: false,
2315
+ message: "Failed to start daemon (no PID)",
2316
+ error: "No PID"
2317
+ };
2318
+ } catch (error) {
2319
+ return {
2320
+ success: false,
2321
+ message: "Failed to start daemon",
2322
+ error: error instanceof Error ? error.message : String(error)
2323
+ };
2324
+ }
2325
+ }
2326
+ async function stop(config) {
2327
+ const { pidFile } = config.realtime;
2328
+ const status2 = isDaemonRunning(pidFile);
2329
+ if (!status2.running || !status2.pid) {
2330
+ return {
2331
+ success: true,
2332
+ message: "Daemon not running"
2333
+ };
2334
+ }
2335
+ try {
2336
+ const isWindows = process.platform === "win32";
2337
+ if (isWindows) {
2338
+ process.kill(status2.pid);
2339
+ } else {
2340
+ process.kill(status2.pid, "SIGTERM");
2341
+ }
2342
+ const maxWait = 5e3;
2343
+ const interval = 100;
2344
+ let waited = 0;
2345
+ while (waited < maxWait) {
2346
+ try {
2347
+ process.kill(status2.pid, 0);
2348
+ await new Promise((resolve9) => setTimeout(resolve9, interval));
2349
+ waited += interval;
2350
+ } catch {
2351
+ removePidFile(pidFile);
2352
+ return {
2353
+ success: true,
2354
+ message: `Daemon stopped (PID ${status2.pid})`
2355
+ };
2356
+ }
2357
+ }
2358
+ try {
2359
+ if (!isWindows) {
2360
+ process.kill(status2.pid, "SIGKILL");
2361
+ }
2362
+ removePidFile(pidFile);
2363
+ return {
2364
+ success: true,
2365
+ message: `Daemon force killed (PID ${status2.pid})`
2366
+ };
2367
+ } catch {
2368
+ removePidFile(pidFile);
2369
+ return {
2370
+ success: true,
2371
+ message: `Daemon stopped (PID ${status2.pid})`
2372
+ };
2373
+ }
2374
+ } catch (error) {
2375
+ return {
2376
+ success: false,
2377
+ message: "Failed to stop daemon",
2378
+ error: error instanceof Error ? error.message : String(error)
2379
+ };
2380
+ }
2381
+ }
2382
+ async function status(config) {
2383
+ const { pidFile } = config.realtime;
2384
+ const daemonStatus = isDaemonRunning(pidFile);
2385
+ if (!daemonStatus.running || !daemonStatus.pid) {
2386
+ return {
2387
+ running: false,
2388
+ message: "Daemon not running"
2389
+ };
2390
+ }
2391
+ return {
2392
+ running: true,
2393
+ pid: daemonStatus.pid,
2394
+ message: `Daemon running (PID ${daemonStatus.pid})`
2395
+ };
2396
+ }
2397
+ return {
2398
+ start,
2399
+ stop,
2400
+ status
2401
+ };
2402
+ }
2403
+ var import_node_child_process2, import_node_fs7, import_node_path12, import_node_url;
2404
+ var init_daemon_process = __esm({
2405
+ "src/daemon/daemon-process.ts"() {
2406
+ "use strict";
2407
+ init_cjs_shims();
2408
+ import_node_child_process2 = require("child_process");
2409
+ import_node_fs7 = require("fs");
2410
+ import_node_path12 = require("path");
2411
+ import_node_url = require("url");
2412
+ }
2413
+ });
2414
+
2415
+ // src/cli/dashboard/app.tsx
2416
+ var app_exports = {};
2417
+ __export(app_exports, {
2418
+ renderDashboard: () => renderDashboard
2419
+ });
2420
+ module.exports = __toCommonJS(app_exports);
2421
+ init_cjs_shims();
2422
+ var import_ink7 = require("ink");
2423
+ var import_react4 = require("react");
2424
+
2425
+ // src/cli/dashboard/components/command-input.tsx
2426
+ init_cjs_shims();
2427
+ var import_ink = require("ink");
2428
+ var import_ink_text_input = __toESM(require("ink-text-input"), 1);
2429
+ var import_react = require("react");
2430
+
2431
+ // src/cli/dashboard/theme.ts
2432
+ init_cjs_shims();
2433
+ var theme = {
2434
+ neuralCyan: "#00D4AA",
2435
+ synapseViolet: "#7B61FF",
2436
+ errorRed: "#FF6B6B",
2437
+ brainPink: "#FF6B9D",
2438
+ dimGray: "#555555",
2439
+ white: "#FFFFFF"
2440
+ };
2441
+
2442
+ // src/cli/dashboard/components/command-input.tsx
2443
+ var import_jsx_runtime = require("react/jsx-runtime");
2444
+ function CommandInput({
2445
+ onSubmit,
2446
+ isRunning,
2447
+ runningCommand,
2448
+ active
2449
+ }) {
2450
+ const [value, setValue] = (0, import_react.useState)("");
2451
+ const handleSubmit = (input) => {
2452
+ if (input.trim().length === 0) return;
2453
+ onSubmit(input.trim());
2454
+ setValue("");
2455
+ };
2456
+ if (isRunning) {
2457
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: theme.synapseViolet, children: [
2458
+ "Running ",
2459
+ runningCommand || "command",
2460
+ "..."
2461
+ ] }) });
2462
+ }
2463
+ if (!active) {
2464
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: theme.dimGray, children: "Press : to enter command mode" }) });
2465
+ }
2466
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { paddingX: 1, children: [
2467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: theme.neuralCyan, children: [
2468
+ "\u276F",
2469
+ " "
2470
+ ] }),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2472
+ import_ink_text_input.default,
2473
+ {
2474
+ value,
2475
+ onChange: setValue,
2476
+ onSubmit: handleSubmit,
2477
+ focus: active,
2478
+ showCursor: true
2479
+ }
2480
+ ),
2481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: theme.dimGray, children: " [Esc: monitor mode]" })
2482
+ ] });
2483
+ }
2484
+
2485
+ // src/cli/dashboard/components/output-panel.tsx
2486
+ init_cjs_shims();
2487
+ var import_ink2 = require("ink");
2488
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2489
+ function OutputPanel({
2490
+ lines,
2491
+ scrollOffset,
2492
+ focused,
2493
+ maxVisibleLines = 8
2494
+ }) {
2495
+ const borderColor = focused ? theme.neuralCyan : theme.dimGray;
2496
+ if (lines.length === 0) {
2497
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2498
+ import_ink2.Box,
2499
+ {
2500
+ flexDirection: "column",
2501
+ borderStyle: "round",
2502
+ borderColor,
2503
+ paddingX: 1,
2504
+ width: "100%",
2505
+ children: [
2506
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { bold: true, color: theme.dimGray, children: "Output" }),
2507
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: theme.dimGray, children: "Type : then a command. Try help for available commands." })
2508
+ ]
2509
+ }
2510
+ );
2511
+ }
2512
+ const visible = lines.slice(scrollOffset, scrollOffset + maxVisibleLines);
2513
+ const hasMore = lines.length > maxVisibleLines;
2514
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2515
+ import_ink2.Box,
2516
+ {
2517
+ flexDirection: "column",
2518
+ borderStyle: "round",
2519
+ borderColor,
2520
+ paddingX: 1,
2521
+ width: "100%",
2522
+ children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { bold: true, color: theme.dimGray, children: "Output" }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: visible.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: line.color, children: line.text }, `${scrollOffset + i}`)) }),
2525
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: theme.dimGray, children: [
2526
+ "[",
2527
+ scrollOffset + 1,
2528
+ "-",
2529
+ Math.min(scrollOffset + maxVisibleLines, lines.length),
2530
+ "/",
2531
+ lines.length,
2532
+ "]",
2533
+ focused ? " \u2191\u2193 scroll" : ""
2534
+ ] })
2535
+ ]
2536
+ }
2537
+ );
2538
+ }
2539
+
2540
+ // src/cli/dashboard/hooks/use-command-executor.ts
2541
+ init_cjs_shims();
2542
+ var import_react2 = require("react");
2543
+
2544
+ // src/cli/dashboard/command-parser.ts
2545
+ init_cjs_shims();
2546
+ function tokenize(input) {
2547
+ const tokens = [];
2548
+ let current = "";
2549
+ let inQuote = null;
2550
+ for (const ch of input) {
2551
+ if (inQuote) {
2552
+ if (ch === inQuote) {
2553
+ inQuote = null;
2554
+ } else {
2555
+ current += ch;
2556
+ }
2557
+ } else if (ch === '"' || ch === "'") {
2558
+ inQuote = ch;
2559
+ } else if (ch === " " || ch === " ") {
2560
+ if (current.length > 0) {
2561
+ tokens.push(current);
2562
+ current = "";
2563
+ }
2564
+ } else {
2565
+ current += ch;
2566
+ }
2567
+ }
2568
+ if (current.length > 0) {
2569
+ tokens.push(current);
2570
+ }
2571
+ return tokens;
2572
+ }
2573
+ function parseCommand(input) {
2574
+ const trimmed = input.trim();
2575
+ if (trimmed.length === 0) return null;
2576
+ const tokens = tokenize(trimmed);
2577
+ const first = tokens[0];
2578
+ if (!first) return null;
2579
+ const name = first.toLowerCase();
2580
+ const positionals = [];
2581
+ const flags = {};
2582
+ let i = 1;
2583
+ while (i < tokens.length) {
2584
+ const token = tokens[i];
2585
+ if (!token) {
2586
+ i++;
2587
+ continue;
2588
+ }
2589
+ if (token.startsWith("--")) {
2590
+ const key = token.slice(2);
2591
+ const next = tokens[i + 1];
2592
+ if (next !== void 0 && !next.startsWith("-")) {
2593
+ flags[key] = next;
2594
+ i += 2;
2595
+ } else {
2596
+ flags[key] = true;
2597
+ i++;
2598
+ }
2599
+ } else if (token.startsWith("-") && token.length === 2) {
2600
+ const key = token.slice(1);
2601
+ const next = tokens[i + 1];
2602
+ if (next !== void 0 && !next.startsWith("-")) {
2603
+ flags[key] = next;
2604
+ i += 2;
2605
+ } else {
2606
+ flags[key] = true;
2607
+ i++;
2608
+ }
2609
+ } else {
2610
+ positionals.push(token);
2611
+ i++;
2612
+ }
2613
+ }
2614
+ return { name, positionals, flags };
2615
+ }
2616
+
2617
+ // src/cli/dashboard/formatters.ts
2618
+ init_cjs_shims();
2619
+ function formatOptimize(result) {
2620
+ const pct = (result.reduction * 100).toFixed(1);
2621
+ return [
2622
+ { text: `\u2713 Optimization complete in ${result.durationMs}ms`, color: theme.neuralCyan },
2623
+ {
2624
+ text: ` Tokens: ${result.tokensBefore} \u2192 ${result.tokensAfter} (${pct}% reduction)`
2625
+ },
2626
+ {
2627
+ text: ` Entries: processed=${result.entriesProcessed} kept=${result.entriesKept}`
2628
+ },
2629
+ ...result.outputFile ? [{ text: ` Output: ${result.outputFile}`, color: theme.dimGray }] : []
2630
+ ];
2631
+ }
2632
+ function formatStats(result) {
2633
+ if (result.resetConfirmed) {
2634
+ return [{ text: "\u2713 Statistics reset", color: theme.neuralCyan }];
2635
+ }
2636
+ const avgPct = (result.averageReduction * 100).toFixed(1);
2637
+ const lines = [
2638
+ { text: "Optimization Statistics", color: theme.neuralCyan },
2639
+ { text: ` Total commands: ${result.totalCommands}` },
2640
+ { text: ` Tokens saved: ${result.totalTokensSaved}` },
2641
+ { text: ` Avg reduction: ${avgPct}%` }
2642
+ ];
2643
+ if (result.graph) {
2644
+ for (const line of result.graph.split("\n")) {
2645
+ lines.push({ text: line, color: theme.dimGray });
2646
+ }
2647
+ }
2648
+ return lines;
2649
+ }
2650
+ function formatConsolidate(result) {
2651
+ const pct = (result.compressionRatio * 100).toFixed(1);
2652
+ return [
2653
+ { text: `\u2713 Consolidation complete in ${result.durationMs}ms`, color: theme.neuralCyan },
2654
+ { text: ` Entries: ${result.entriesBefore} \u2192 ${result.entriesAfter}` },
2655
+ { text: ` Decayed removed: ${result.decayedRemoved}` },
2656
+ { text: ` Duplicates removed: ${result.duplicatesRemoved}` },
2657
+ { text: ` Compression: ${pct}%` }
2658
+ ];
2659
+ }
2660
+ function formatSearch(result) {
2661
+ if (result.indexStats) {
2662
+ const s = result.indexStats;
2663
+ return [
2664
+ {
2665
+ text: `\u2713 Indexed ${s.filesIndexed} files (${s.totalLines} lines) in ${s.duration}ms`,
2666
+ color: theme.neuralCyan
2667
+ }
2668
+ ];
2669
+ }
2670
+ if (!result.results || result.results.length === 0) {
2671
+ return [{ text: result.message || "No results found", color: theme.dimGray }];
2672
+ }
2673
+ const lines = [
2674
+ { text: `${result.results.length} result(s):`, color: theme.neuralCyan }
2675
+ ];
2676
+ for (const r of result.results) {
2677
+ lines.push({
2678
+ text: ` ${r.filePath}:${r.lineNumber} (score: ${r.score.toFixed(2)})`,
2679
+ color: theme.synapseViolet
2680
+ });
2681
+ lines.push({ text: ` ${r.content.trim()}` });
2682
+ }
2683
+ return lines;
2684
+ }
2685
+ function formatGraph(result) {
2686
+ const a = result.analysis;
2687
+ const lines = [
2688
+ { text: `Graph: ${result.nodeCount} nodes`, color: theme.neuralCyan },
2689
+ { text: ` Tokens: ${a.totalTokens} (optimized: ${a.optimizedTokens})` }
2690
+ ];
2691
+ if (a.hotPaths.length > 0) {
2692
+ lines.push({
2693
+ text: ` Hot paths: ${a.hotPaths.slice(0, 5).join(", ")}`,
2694
+ color: theme.brainPink
2695
+ });
2696
+ }
2697
+ if (a.orphans.length > 0) {
2698
+ lines.push({ text: ` Orphans: ${a.orphans.length}`, color: theme.dimGray });
2699
+ }
2700
+ return lines;
2701
+ }
2702
+ function formatPlan(result) {
2703
+ return [{ text: `\u2713 ${result.message}`, color: theme.neuralCyan }];
2704
+ }
2705
+ function formatPlanList(result) {
2706
+ if (result.plans.length === 0) {
2707
+ return [{ text: "No plans found", color: theme.dimGray }];
2708
+ }
2709
+ const lines = [
2710
+ { text: `${result.plans.length} plan(s):`, color: theme.neuralCyan }
2711
+ ];
2712
+ for (const p of result.plans) {
2713
+ const statusColor = p.status === "completed" ? theme.neuralCyan : theme.synapseViolet;
2714
+ lines.push({ text: ` ${p.id} [${p.status}] ${p.task}`, color: statusColor });
2715
+ }
2716
+ return lines;
2717
+ }
2718
+ function formatExec(result) {
2719
+ return [
2720
+ { text: `\u2713 ${result.message}`, color: theme.neuralCyan },
2721
+ {
2722
+ text: ` Max reads: ${result.constraints.maxFileReads}, Budget: ${result.constraints.tokenBudget} tokens`
2723
+ }
2724
+ ];
2725
+ }
2726
+ function formatVerify(result) {
2727
+ const v = result.verification;
2728
+ const icon = v.success ? "\u2713" : "\u2717";
2729
+ const color = v.success ? theme.neuralCyan : theme.errorRed;
2730
+ return [
2731
+ { text: `${icon} ${result.message}`, color },
2732
+ { text: ` Steps: ${v.stepsCompleted}/${v.totalSteps} completed` }
2733
+ ];
2734
+ }
2735
+ function formatDocs(result) {
2736
+ return [{ text: `\u2713 ${result.message}`, color: theme.neuralCyan }];
2737
+ }
2738
+ function formatDebt(result) {
2739
+ if (result.stats) {
2740
+ const s = result.stats;
2741
+ return [
2742
+ { text: "Debt Stats", color: theme.neuralCyan },
2743
+ { text: ` Total: ${s.total}, Open: ${s.open}, Overdue: ${s.overdue}` },
2744
+ { text: ` Repayment rate: ${(s.repaymentRate * 100).toFixed(0)}%` }
2745
+ ];
2746
+ }
2747
+ if (result.debts) {
2748
+ if (result.debts.length === 0) {
2749
+ return [{ text: "No debt items", color: theme.dimGray }];
2750
+ }
2751
+ const lines = [
2752
+ { text: `${result.debts.length} debt item(s):`, color: theme.neuralCyan }
2753
+ ];
2754
+ for (const d of result.debts) {
2755
+ const sevColor = d.severity === "P0" ? theme.errorRed : d.severity === "P1" ? theme.synapseViolet : theme.dimGray;
2756
+ const desc = d.description.length > 40 ? `${d.description.substring(0, 37)}...` : d.description;
2757
+ lines.push({
2758
+ text: ` [${d.severity}] ${d.id.slice(0, 8)} ${desc} (${d.status})`,
2759
+ color: sevColor
2760
+ });
2761
+ }
2762
+ return lines;
2763
+ }
2764
+ return [{ text: `\u2713 ${result.message}`, color: theme.neuralCyan }];
2765
+ }
2766
+ function formatError(err) {
2767
+ return [{ text: `Error: ${err.message}`, color: theme.errorRed }];
2768
+ }
2769
+ function formatHelp() {
2770
+ return [
2771
+ { text: "Available commands:", color: theme.neuralCyan },
2772
+ { text: " optimize --input <text> [--input-file <path>] [--output-file <path>] [--dry-run]" },
2773
+ { text: " stats [--graph] [--reset --confirm-reset]" },
2774
+ { text: " consolidate" },
2775
+ { text: " search <query> [--glob <pattern>] [--max-results <n>]" },
2776
+ { text: " search init | search refresh" },
2777
+ { text: " graph [--entry <file>] [--depth <n>] [--focus <file>]" },
2778
+ { text: " plan <task> [--files <f1,f2>] [--searches <q1,q2>]" },
2779
+ { text: " plan list" },
2780
+ { text: " exec <planId>" },
2781
+ { text: " verify <planId>" },
2782
+ { text: " docs [--output <path>] [--no-graph]" },
2783
+ { text: " debt add <desc> [--severity P0|P1|P2] [--due <date>] [--token-cost <n>]" },
2784
+ { text: " debt list [--overdue]" },
2785
+ { text: " debt resolve <id> [--token-cost <n>]" },
2786
+ { text: " debt start <id>" },
2787
+ { text: " debt stats" },
2788
+ { text: " clear Clear output" },
2789
+ { text: " help Show this help" },
2790
+ { text: "" },
2791
+ { text: "Keybinds:", color: theme.neuralCyan },
2792
+ { text: " : Enter command mode" },
2793
+ { text: " Escape Exit command mode" },
2794
+ { text: " Tab Cycle panel focus" },
2795
+ { text: " q Quit (in monitor mode)" }
2796
+ ];
2797
+ }
2798
+
2799
+ // src/cli/dashboard/hooks/use-command-executor.ts
2800
+ var MAX_OUTPUT_LINES = 200;
2801
+ function useCommandExecutor({
2802
+ getMemory,
2803
+ forceRefresh,
2804
+ dbPath,
2805
+ projectRoot
2806
+ }) {
2807
+ const [outputLines, setOutputLines] = (0, import_react2.useState)([]);
2808
+ const [isRunning, setIsRunning] = (0, import_react2.useState)(false);
2809
+ const [runningCommand, setRunningCommand] = (0, import_react2.useState)();
2810
+ const appendLines = (0, import_react2.useCallback)((lines) => {
2811
+ setOutputLines((prev) => {
2812
+ const next = [...prev, ...lines];
2813
+ return next.length > MAX_OUTPUT_LINES ? next.slice(-MAX_OUTPUT_LINES) : next;
2814
+ });
2815
+ }, []);
2816
+ const clearOutput = (0, import_react2.useCallback)(() => {
2817
+ setOutputLines([]);
2818
+ }, []);
2819
+ const execute = (0, import_react2.useCallback)(
2820
+ (rawInput) => {
2821
+ const parsed = parseCommand(rawInput);
2822
+ if (!parsed) return;
2823
+ if (parsed.name === "clear") {
2824
+ clearOutput();
2825
+ return;
2826
+ }
2827
+ if (parsed.name === "help") {
2828
+ appendLines(formatHelp());
2829
+ return;
2830
+ }
2831
+ setIsRunning(true);
2832
+ setRunningCommand(parsed.name);
2833
+ void (async () => {
2834
+ try {
2835
+ const lines = await executeCommand(parsed.name, parsed.positionals, parsed.flags, {
2836
+ getMemory,
2837
+ dbPath,
2838
+ projectRoot
2839
+ });
2840
+ appendLines(lines);
2841
+ forceRefresh();
2842
+ } catch (err) {
2843
+ appendLines(formatError(err instanceof Error ? err : new Error(String(err))));
2844
+ } finally {
2845
+ setIsRunning(false);
2846
+ setRunningCommand(void 0);
2847
+ }
2848
+ })();
2849
+ },
2850
+ [getMemory, forceRefresh, dbPath, projectRoot, appendLines, clearOutput]
2851
+ );
2852
+ return { execute, outputLines, isRunning, runningCommand, clearOutput };
2853
+ }
2854
+ async function executeCommand(name, positionals, flags, ctx) {
2855
+ switch (name) {
2856
+ case "optimize": {
2857
+ const memory = ctx.getMemory();
2858
+ if (!memory) throw new Error("Memory not initialized");
2859
+ const { optimizeCommand: optimizeCommand2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
2860
+ const result = await optimizeCommand2({
2861
+ memory,
2862
+ input: typeof flags["input"] === "string" ? flags["input"] : void 0,
2863
+ inputFile: typeof flags["input-file"] === "string" ? flags["input-file"] : void 0,
2864
+ outputFile: typeof flags["output-file"] === "string" ? flags["output-file"] : void 0,
2865
+ dryRun: flags["dry-run"] === true,
2866
+ verbose: flags["verbose"] === true || flags["v"] === true
2867
+ });
2868
+ return formatOptimize(result);
2869
+ }
2870
+ case "stats": {
2871
+ const memory = ctx.getMemory();
2872
+ if (!memory) throw new Error("Memory not initialized");
2873
+ const { statsCommand: statsCommand2 } = await Promise.resolve().then(() => (init_stats(), stats_exports));
2874
+ const result = await statsCommand2({
2875
+ memory,
2876
+ graph: flags["graph"] === true || flags["g"] === true,
2877
+ reset: flags["reset"] === true,
2878
+ confirmReset: flags["confirm-reset"] === true
2879
+ });
2880
+ return formatStats(result);
2881
+ }
2882
+ case "consolidate": {
2883
+ const memory = ctx.getMemory();
2884
+ if (!memory) throw new Error("Memory not initialized");
2885
+ const { consolidateCommand: consolidateCommand2 } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
2886
+ const result = await consolidateCommand2({ memory });
2887
+ return formatConsolidate(result);
2888
+ }
2889
+ case "search": {
2890
+ const { searchCommand: searchCommand2 } = await Promise.resolve().then(() => (init_search(), search_exports));
2891
+ const subcommand = positionals[0] === "init" || positionals[0] === "refresh" ? positionals[0] : void 0;
2892
+ const query = subcommand ? void 0 : positionals.join(" ") || void 0;
2893
+ const result = await searchCommand2({
2894
+ query,
2895
+ subcommand,
2896
+ glob: typeof flags["glob"] === "string" ? flags["glob"] : void 0,
2897
+ maxResults: typeof flags["max-results"] === "string" ? Number.parseInt(flags["max-results"], 10) : void 0
2898
+ });
2899
+ return formatSearch(result);
2900
+ }
2901
+ case "graph": {
2902
+ const { graphCommand: graphCommand2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
2903
+ const result = await graphCommand2({
2904
+ entry: typeof flags["entry"] === "string" ? flags["entry"] : void 0,
2905
+ depth: typeof flags["depth"] === "string" ? Number.parseInt(flags["depth"], 10) : void 0,
2906
+ focus: typeof flags["focus"] === "string" ? flags["focus"] : void 0
2907
+ });
2908
+ return formatGraph(result);
2909
+ }
2910
+ case "plan": {
2911
+ if (positionals[0] === "list") {
2912
+ const { planListCommand: planListCommand2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
2913
+ const result2 = await planListCommand2({});
2914
+ return formatPlanList(result2);
2915
+ }
2916
+ const { planCommand: planCommand2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
2917
+ const task = positionals.join(" ");
2918
+ if (!task) throw new Error("Task description required: plan <task>");
2919
+ const files = typeof flags["files"] === "string" ? flags["files"].split(",") : void 0;
2920
+ const searches = typeof flags["searches"] === "string" ? flags["searches"].split(",") : void 0;
2921
+ const result = await planCommand2({ task, files, searches });
2922
+ return formatPlan(result);
2923
+ }
2924
+ case "exec": {
2925
+ const planId = positionals[0];
2926
+ if (!planId) throw new Error("Plan ID required: exec <planId>");
2927
+ const { execCommand: execCommand2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
2928
+ const result = await execCommand2({ planId });
2929
+ return formatExec(result);
2930
+ }
2931
+ case "verify": {
2932
+ const planId = positionals[0];
2933
+ if (!planId) throw new Error("Plan ID required: verify <planId>");
2934
+ const { verifyCommand: verifyCommand2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
2935
+ const result = await verifyCommand2({ planId });
2936
+ return formatVerify(result);
2937
+ }
2938
+ case "docs": {
2939
+ const { docsCommand: docsCommand2 } = await Promise.resolve().then(() => (init_docs(), docs_exports));
2940
+ const result = await docsCommand2({
2941
+ output: typeof flags["output"] === "string" ? flags["output"] : void 0,
2942
+ includeGraph: flags["no-graph"] !== true
2943
+ });
2944
+ return formatDocs(result);
2945
+ }
2946
+ case "debt": {
2947
+ const subcommand = positionals[0];
2948
+ if (!subcommand || !["add", "list", "resolve", "stats", "start"].includes(subcommand)) {
2949
+ throw new Error("Subcommand required: debt add|list|resolve|start|stats");
2950
+ }
2951
+ const { debtCommand: debtCommand2 } = await Promise.resolve().then(() => (init_debt(), debt_exports));
2952
+ const result = await debtCommand2({
2953
+ subcommand,
2954
+ description: subcommand === "add" ? positionals.slice(1).join(" ") || void 0 : void 0,
2955
+ severity: typeof flags["severity"] === "string" ? flags["severity"] : void 0,
2956
+ due: typeof flags["due"] === "string" ? flags["due"] : void 0,
2957
+ tokenCost: typeof flags["token-cost"] === "string" ? Number.parseInt(flags["token-cost"], 10) : void 0,
2958
+ id: subcommand === "resolve" || subcommand === "start" ? positionals[1] : void 0,
2959
+ overdue: flags["overdue"] === true
2960
+ });
2961
+ return formatDebt(result);
2962
+ }
2963
+ default:
2964
+ throw new Error(`Unknown command: ${name}. Type help for available commands.`);
2965
+ }
2966
+ }
2967
+
2968
+ // src/cli/dashboard/hooks/use-data.ts
2969
+ init_cjs_shims();
2970
+ var import_node_fs8 = require("fs");
2971
+ var import_react3 = require("react");
2972
+ init_debt_tracker();
2973
+ init_dependency_graph();
2974
+
2975
+ // src/core/kv-memory.ts
2976
+ init_cjs_shims();
2977
+ var import_node_fs6 = require("fs");
2978
+ var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
2979
+ function createBackup(dbPath) {
2980
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2981
+ const backupPath = `${dbPath}.backup-${timestamp}`;
2982
+ try {
2983
+ (0, import_node_fs6.copyFileSync)(dbPath, backupPath);
2984
+ console.log(`\u2713 Database backed up to: ${backupPath}`);
2985
+ return backupPath;
2986
+ } catch (error) {
2987
+ console.error(`Warning: Could not create backup: ${error}`);
2988
+ return "";
2989
+ }
2990
+ }
2991
+ async function createKVMemory(dbPath) {
2992
+ let db;
2993
+ try {
2994
+ db = new import_better_sqlite33.default(dbPath);
2995
+ const integrityCheck = db.pragma("quick_check", { simple: true });
2996
+ if (integrityCheck !== "ok") {
2997
+ console.error("\u26A0 Database corruption detected!");
2998
+ db.close();
2999
+ if ((0, import_node_fs6.existsSync)(dbPath)) {
3000
+ const backupPath = createBackup(dbPath);
3001
+ if (backupPath) {
3002
+ console.log(`Backup created at: ${backupPath}`);
3003
+ }
3004
+ }
3005
+ console.log("Attempting database recovery...");
3006
+ db = new import_better_sqlite33.default(dbPath);
3007
+ }
3008
+ } catch (error) {
3009
+ console.error("\u26A0 Database error detected:", error);
3010
+ if ((0, import_node_fs6.existsSync)(dbPath)) {
3011
+ createBackup(dbPath);
3012
+ console.log("Creating new database...");
3013
+ }
3014
+ db = new import_better_sqlite33.default(dbPath);
3015
+ }
3016
+ db.pragma("journal_mode = WAL");
3017
+ db.pragma("foreign_keys = ON");
3018
+ db.exec(`
3019
+ CREATE TABLE IF NOT EXISTS entries_index (
3020
+ id TEXT PRIMARY KEY NOT NULL,
3021
+ hash TEXT UNIQUE NOT NULL,
3022
+ timestamp INTEGER NOT NULL,
3023
+ score REAL NOT NULL DEFAULT 0.0 CHECK(score >= 0.0 AND score <= 1.0),
3024
+ ttl INTEGER NOT NULL CHECK(ttl >= 0),
3025
+ state TEXT NOT NULL CHECK(state IN ('silent', 'ready', 'active')),
3026
+ accessCount INTEGER NOT NULL DEFAULT 0 CHECK(accessCount >= 0),
3027
+ isBTSP INTEGER NOT NULL DEFAULT 0 CHECK(isBTSP IN (0, 1)),
3028
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
3029
+ );
3030
+ `);
3031
+ db.exec(`
3032
+ CREATE TABLE IF NOT EXISTS entries_value (
3033
+ id TEXT PRIMARY KEY NOT NULL,
3034
+ content TEXT NOT NULL,
3035
+ tags TEXT,
3036
+ metadata TEXT,
3037
+ FOREIGN KEY (id) REFERENCES entries_index(id) ON DELETE CASCADE
3038
+ );
3039
+ `);
3040
+ db.exec(`
3041
+ CREATE TABLE IF NOT EXISTS optimization_stats (
3042
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3043
+ timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
3044
+ tokens_before INTEGER NOT NULL,
3045
+ tokens_after INTEGER NOT NULL,
3046
+ entries_pruned INTEGER NOT NULL,
3047
+ duration_ms INTEGER NOT NULL
3048
+ );
3049
+ `);
3050
+ db.exec(`
3051
+ CREATE INDEX IF NOT EXISTS idx_entries_state ON entries_index(state);
3052
+ CREATE INDEX IF NOT EXISTS idx_entries_score ON entries_index(score DESC);
3053
+ CREATE INDEX IF NOT EXISTS idx_entries_hash ON entries_index(hash);
3054
+ CREATE INDEX IF NOT EXISTS idx_entries_timestamp ON entries_index(timestamp DESC);
3055
+ CREATE INDEX IF NOT EXISTS idx_stats_timestamp ON optimization_stats(timestamp DESC);
3056
+ `);
3057
+ db.exec(`
3058
+ CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(id, content, tokenize='porter');
3059
+ `);
3060
+ db.exec(`
3061
+ CREATE TRIGGER IF NOT EXISTS entries_fts_insert
3062
+ AFTER INSERT ON entries_value
3063
+ BEGIN
3064
+ INSERT OR REPLACE INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
3065
+ END;
3066
+ `);
3067
+ db.exec(`
3068
+ CREATE TRIGGER IF NOT EXISTS entries_fts_delete
3069
+ AFTER DELETE ON entries_value
3070
+ BEGIN
3071
+ DELETE FROM entries_fts WHERE id = OLD.id;
3072
+ END;
3073
+ `);
3074
+ db.exec(`
3075
+ CREATE TRIGGER IF NOT EXISTS entries_fts_update
3076
+ AFTER UPDATE ON entries_value
3077
+ BEGIN
3078
+ DELETE FROM entries_fts WHERE id = OLD.id;
3079
+ INSERT INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
3080
+ END;
3081
+ `);
3082
+ db.exec(`
3083
+ INSERT OR IGNORE INTO entries_fts(id, content)
3084
+ SELECT id, content FROM entries_value
3085
+ WHERE id NOT IN (SELECT id FROM entries_fts);
3086
+ `);
3087
+ const putIndexStmt = db.prepare(`
3088
+ INSERT OR REPLACE INTO entries_index
3089
+ (id, hash, timestamp, score, ttl, state, accessCount, isBTSP)
3090
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3091
+ `);
3092
+ const putValueStmt = db.prepare(`
3093
+ INSERT OR REPLACE INTO entries_value
3094
+ (id, content, tags, metadata)
3095
+ VALUES (?, ?, ?, ?)
3096
+ `);
3097
+ const getStmt = db.prepare(`
3098
+ SELECT
3099
+ i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
3100
+ v.content, v.tags, v.metadata
3101
+ FROM entries_index i
3102
+ JOIN entries_value v ON i.id = v.id
3103
+ WHERE i.id = ?
3104
+ `);
3105
+ const deleteIndexStmt = db.prepare("DELETE FROM entries_index WHERE id = ?");
3106
+ const deleteValueStmt = db.prepare("DELETE FROM entries_value WHERE id = ?");
3107
+ return {
3108
+ async put(entry) {
3109
+ const transaction = db.transaction(() => {
3110
+ putIndexStmt.run(
3111
+ entry.id,
3112
+ entry.hash,
3113
+ entry.timestamp,
3114
+ entry.score,
3115
+ entry.ttl,
3116
+ entry.state,
3117
+ entry.accessCount,
3118
+ entry.isBTSP ? 1 : 0
3119
+ );
3120
+ putValueStmt.run(
3121
+ entry.id,
3122
+ entry.content,
3123
+ JSON.stringify(entry.tags),
3124
+ JSON.stringify(entry.metadata)
3125
+ );
3126
+ });
3127
+ transaction();
3128
+ },
3129
+ async get(id) {
3130
+ const row = getStmt.get(id);
3131
+ if (!row) {
3132
+ return null;
3133
+ }
3134
+ const r = row;
3135
+ return {
3136
+ id: r.id,
3137
+ content: r.content,
3138
+ hash: r.hash,
3139
+ timestamp: r.timestamp,
3140
+ score: r.score,
3141
+ ttl: r.ttl,
3142
+ state: r.state,
3143
+ accessCount: r.accessCount,
3144
+ tags: r.tags ? JSON.parse(r.tags) : [],
3145
+ metadata: r.metadata ? JSON.parse(r.metadata) : {},
3146
+ isBTSP: r.isBTSP === 1
3147
+ };
3148
+ },
3149
+ async query(filters) {
3150
+ let sql = `
3151
+ SELECT
3152
+ i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
3153
+ v.content, v.tags, v.metadata
3154
+ FROM entries_index i
3155
+ JOIN entries_value v ON i.id = v.id
3156
+ WHERE 1=1
3157
+ `;
3158
+ const params = [];
3159
+ if (filters.state) {
3160
+ sql += " AND i.state = ?";
3161
+ params.push(filters.state);
3162
+ }
3163
+ if (filters.minScore !== void 0) {
3164
+ sql += " AND i.score >= ?";
3165
+ params.push(filters.minScore);
3166
+ }
3167
+ if (filters.maxScore !== void 0) {
3168
+ sql += " AND i.score <= ?";
3169
+ params.push(filters.maxScore);
3170
+ }
3171
+ if (filters.isBTSP !== void 0) {
3172
+ sql += " AND i.isBTSP = ?";
3173
+ params.push(filters.isBTSP ? 1 : 0);
3174
+ }
3175
+ if (filters.tags && filters.tags.length > 0) {
3176
+ for (const tag of filters.tags) {
3177
+ sql += " AND v.tags LIKE ?";
3178
+ params.push(`%"${tag}"%`);
3179
+ }
3180
+ }
3181
+ sql += " ORDER BY i.score DESC";
3182
+ if (filters.limit) {
3183
+ sql += " LIMIT ?";
3184
+ params.push(filters.limit);
3185
+ if (filters.offset) {
3186
+ sql += " OFFSET ?";
3187
+ params.push(filters.offset);
3188
+ }
3189
+ }
3190
+ const stmt = db.prepare(sql);
3191
+ const rows = stmt.all(...params);
3192
+ return rows.map((row) => {
3193
+ const r = row;
3194
+ return {
3195
+ id: r.id,
3196
+ content: r.content,
3197
+ hash: r.hash,
3198
+ timestamp: r.timestamp,
3199
+ score: r.score,
3200
+ ttl: r.ttl,
3201
+ state: r.state,
3202
+ accessCount: r.accessCount,
3203
+ tags: r.tags ? JSON.parse(r.tags) : [],
3204
+ metadata: r.metadata ? JSON.parse(r.metadata) : {},
3205
+ isBTSP: r.isBTSP === 1
3206
+ };
3207
+ });
3208
+ },
3209
+ async delete(id) {
3210
+ const transaction = db.transaction(() => {
3211
+ deleteIndexStmt.run(id);
3212
+ deleteValueStmt.run(id);
3213
+ });
3214
+ transaction();
3215
+ },
3216
+ async list() {
3217
+ const stmt = db.prepare("SELECT id FROM entries_index");
3218
+ const rows = stmt.all();
3219
+ return rows.map((r) => r.id);
3220
+ },
3221
+ async compact() {
3222
+ const before = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
3223
+ const now = Date.now();
3224
+ db.prepare("DELETE FROM entries_index WHERE isBTSP = 0 AND (timestamp + ttl * 1000) < ?").run(
3225
+ now
3226
+ );
3227
+ db.exec("DELETE FROM entries_index WHERE isBTSP = 0 AND ttl <= 0");
3228
+ const candidates = db.prepare("SELECT id, timestamp, ttl FROM entries_index WHERE isBTSP = 0").all();
3229
+ for (const row of candidates) {
3230
+ const ageSeconds = Math.max(0, (now - row.timestamp) / 1e3);
3231
+ const ttlSeconds = row.ttl;
3232
+ if (ttlSeconds <= 0) continue;
3233
+ const decay = 1 - Math.exp(-ageSeconds / ttlSeconds);
3234
+ if (decay >= 0.95) {
3235
+ db.prepare("DELETE FROM entries_index WHERE id = ?").run(row.id);
3236
+ }
3237
+ }
3238
+ db.exec("DELETE FROM entries_value WHERE id NOT IN (SELECT id FROM entries_index)");
3239
+ db.exec("VACUUM");
3240
+ const after = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
3241
+ return before.count - after.count;
3242
+ },
3243
+ async close() {
3244
+ db.close();
3245
+ },
3246
+ async recordOptimization(stats) {
3247
+ const stmt = db.prepare(`
3248
+ INSERT INTO optimization_stats (timestamp, tokens_before, tokens_after, entries_pruned, duration_ms)
3249
+ VALUES (?, ?, ?, ?, ?)
3250
+ `);
3251
+ stmt.run(
3252
+ stats.timestamp,
3253
+ stats.tokens_before,
3254
+ stats.tokens_after,
3255
+ stats.entries_pruned,
3256
+ stats.duration_ms
3257
+ );
3258
+ db.prepare(
3259
+ "DELETE FROM optimization_stats WHERE id NOT IN (SELECT id FROM optimization_stats ORDER BY timestamp DESC LIMIT 1000)"
3260
+ ).run();
3261
+ },
3262
+ async getOptimizationStats() {
3263
+ const stmt = db.prepare(`
3264
+ SELECT id, timestamp, tokens_before, tokens_after, entries_pruned, duration_ms
3265
+ FROM optimization_stats
3266
+ ORDER BY timestamp DESC
3267
+ `);
3268
+ const rows = stmt.all();
3269
+ return rows;
3270
+ },
3271
+ async clearOptimizationStats() {
3272
+ db.exec("DELETE FROM optimization_stats");
3273
+ },
3274
+ async searchFTS(query, limit = 10) {
3275
+ if (!query || query.trim().length === 0) return [];
3276
+ const sanitized = query.replace(/[{}()[\]"':*^~]/g, " ").trim();
3277
+ if (sanitized.length === 0) return [];
3278
+ const stmt = db.prepare(`
3279
+ SELECT
3280
+ f.id, f.content, rank,
3281
+ i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
3282
+ v.tags, v.metadata
3283
+ FROM entries_fts f
3284
+ JOIN entries_index i ON f.id = i.id
3285
+ JOIN entries_value v ON f.id = v.id
3286
+ WHERE entries_fts MATCH ?
3287
+ ORDER BY rank
3288
+ LIMIT ?
3289
+ `);
3290
+ try {
3291
+ const rows = stmt.all(sanitized, limit);
3292
+ return rows.map((r) => ({
3293
+ entry: {
3294
+ id: r.id,
3295
+ content: r.content,
3296
+ hash: r.hash,
3297
+ timestamp: r.timestamp,
3298
+ score: r.score,
3299
+ ttl: r.ttl,
3300
+ state: r.state,
3301
+ accessCount: r.accessCount,
3302
+ tags: r.tags ? JSON.parse(r.tags) : [],
3303
+ metadata: r.metadata ? JSON.parse(r.metadata) : {},
3304
+ isBTSP: r.isBTSP === 1
3305
+ },
3306
+ rank: r.rank
3307
+ }));
3308
+ } catch {
3309
+ return [];
3310
+ }
3311
+ }
3312
+ };
3313
+ }
3314
+
3315
+ // src/cli/dashboard/hooks/use-data.ts
3316
+ var NOOP = () => {
3317
+ };
3318
+ var EMPTY_STATE = {
3319
+ optimizationStats: [],
3320
+ totalEntries: 0,
3321
+ stateDistribution: { active: 0, ready: 0, silent: 0 },
3322
+ debts: [],
3323
+ debtStats: null,
3324
+ graphAnalysis: null,
3325
+ graphNodes: /* @__PURE__ */ new Map(),
3326
+ daemon: null,
3327
+ dbSizeBytes: 0,
3328
+ loading: true,
3329
+ error: null,
3330
+ lastRefresh: /* @__PURE__ */ new Date(),
3331
+ getMemory: () => null,
3332
+ forceRefresh: NOOP
3333
+ };
3334
+ function useDashboardData(dbPath, projectRoot, refreshInterval = 2e3) {
3335
+ const [data, setData] = (0, import_react3.useState)(EMPTY_STATE);
3336
+ const memoryRef = (0, import_react3.useRef)(null);
3337
+ const mountedRef = (0, import_react3.useRef)(true);
3338
+ const dbPathRef = (0, import_react3.useRef)(dbPath);
3339
+ dbPathRef.current = dbPath;
3340
+ const getMemory = (0, import_react3.useCallback)(() => memoryRef.current, []);
3341
+ const doRefreshFast = (0, import_react3.useCallback)(async () => {
3342
+ const memory = memoryRef.current;
3343
+ if (!memory) return;
3344
+ await refreshFast(memory, dbPathRef.current, projectRoot, mountedRef, setData);
3345
+ }, [projectRoot]);
3346
+ const forceRefresh = (0, import_react3.useCallback)(() => {
3347
+ void doRefreshFast();
3348
+ }, [doRefreshFast]);
3349
+ (0, import_react3.useEffect)(() => {
3350
+ let timer = null;
3351
+ async function init() {
3352
+ try {
3353
+ const memory = await createKVMemory(dbPath);
3354
+ if (!mountedRef.current) {
3355
+ await memory.close();
3356
+ return;
3357
+ }
3358
+ memoryRef.current = memory;
3359
+ await refreshFast(memory, dbPath, projectRoot, mountedRef, setData);
3360
+ await refreshGraph(projectRoot, mountedRef, setData);
3361
+ timer = setInterval(() => {
3362
+ if (memoryRef.current) {
3363
+ void refreshFast(memoryRef.current, dbPath, projectRoot, mountedRef, setData);
3364
+ }
3365
+ }, refreshInterval);
3366
+ } catch (err) {
3367
+ if (mountedRef.current) {
3368
+ setData((prev) => ({
3369
+ ...prev,
3370
+ loading: false,
3371
+ error: err instanceof Error ? err.message : String(err)
3372
+ }));
3373
+ }
3374
+ }
3375
+ }
3376
+ void init();
3377
+ return () => {
3378
+ mountedRef.current = false;
3379
+ if (timer) clearInterval(timer);
3380
+ if (memoryRef.current) {
3381
+ void memoryRef.current.close();
3382
+ memoryRef.current = null;
3383
+ }
3384
+ };
3385
+ }, [dbPath, projectRoot, refreshInterval]);
3386
+ return { ...data, getMemory, forceRefresh };
3387
+ }
3388
+ async function refreshFast(memory, path, projectRoot, mountedRef, setData) {
3389
+ try {
3390
+ const [stats, ids, activeEntries, readyEntries, silentEntries] = await Promise.all([
3391
+ memory.getOptimizationStats(),
3392
+ memory.list(),
3393
+ memory.query({ state: "active" }),
3394
+ memory.query({ state: "ready" }),
3395
+ memory.query({ state: "silent" })
3396
+ ]);
3397
+ let debts = [];
3398
+ let debtStats = null;
3399
+ try {
3400
+ const tracker = createDebtTracker(path);
3401
+ debts = await tracker.list();
3402
+ debtStats = await tracker.stats();
3403
+ await tracker.close();
3404
+ } catch {
3405
+ }
3406
+ let daemon = null;
3407
+ try {
3408
+ const { createDaemonCommand: createDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports));
3409
+ const { readFileSync: readFileSync6 } = await import("fs");
3410
+ const { resolve: resolve9 } = await import("path");
3411
+ const { load: parseYAML } = await import("js-yaml");
3412
+ const configPath = resolve9(projectRoot, ".sparn/config.yaml");
3413
+ const configContent = readFileSync6(configPath, "utf-8");
3414
+ const config = parseYAML(configContent);
3415
+ daemon = await createDaemonCommand2().status(config);
3416
+ } catch {
3417
+ }
3418
+ let dbSizeBytes = 0;
3419
+ try {
3420
+ dbSizeBytes = (0, import_node_fs8.statSync)(path).size;
3421
+ } catch {
3422
+ }
3423
+ if (mountedRef.current) {
3424
+ setData((prev) => ({
3425
+ ...prev,
3426
+ optimizationStats: stats,
3427
+ totalEntries: ids.length,
3428
+ stateDistribution: {
3429
+ active: activeEntries.length,
3430
+ ready: readyEntries.length,
3431
+ silent: silentEntries.length
3432
+ },
3433
+ debts,
3434
+ debtStats,
3435
+ daemon,
3436
+ dbSizeBytes,
3437
+ loading: false,
3438
+ error: null,
3439
+ lastRefresh: /* @__PURE__ */ new Date()
3440
+ }));
3441
+ }
3442
+ } catch (err) {
3443
+ if (mountedRef.current) {
3444
+ setData((prev) => ({
3445
+ ...prev,
3446
+ loading: false,
3447
+ error: err instanceof Error ? err.message : String(err)
3448
+ }));
3449
+ }
3450
+ }
3451
+ }
3452
+ async function refreshGraph(projectRoot, mountedRef, setData) {
3453
+ try {
3454
+ const graph = createDependencyGraph({ projectRoot });
3455
+ const analysis = await graph.analyze();
3456
+ const nodes = graph.getNodes();
3457
+ if (mountedRef.current) {
3458
+ setData((prev) => ({
3459
+ ...prev,
3460
+ graphAnalysis: analysis,
3461
+ graphNodes: nodes
3462
+ }));
3463
+ }
3464
+ } catch {
3465
+ }
3466
+ }
3467
+
3468
+ // src/cli/dashboard/panels/debt-tracker.tsx
3469
+ init_cjs_shims();
3470
+ var import_ink3 = require("ink");
3471
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3472
+ function severityColor(severity) {
3473
+ switch (severity) {
3474
+ case "P0":
3475
+ return theme.errorRed;
3476
+ case "P1":
3477
+ return theme.synapseViolet;
3478
+ default:
3479
+ return theme.dimGray;
3480
+ }
3481
+ }
3482
+ function formatDate(ts) {
3483
+ const d = new Date(ts);
3484
+ return `${d.getMonth() + 1}/${d.getDate()}`;
3485
+ }
3486
+ function DebtTrackerPanel({
3487
+ debts,
3488
+ debtStats,
3489
+ focused,
3490
+ scrollOffset
3491
+ }) {
3492
+ const borderColor = focused ? theme.neuralCyan : theme.dimGray;
3493
+ const now = Date.now();
3494
+ const openDebts = debts.filter((d) => d.status !== "resolved");
3495
+ const visibleDebts = openDebts.slice(scrollOffset, scrollOffset + 8);
3496
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3497
+ import_ink3.Box,
3498
+ {
3499
+ flexDirection: "column",
3500
+ borderStyle: "round",
3501
+ borderColor,
3502
+ paddingX: 1,
3503
+ width: "50%",
3504
+ flexGrow: 1,
3505
+ children: [
3506
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, color: theme.errorRed, children: "Tech Debt" }),
3507
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Box, { flexDirection: "column", children: [
3508
+ visibleDebts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: theme.dimGray, children: "No open debts" }),
3509
+ visibleDebts.map((d) => {
3510
+ const isOverdue = d.repayment_date < now && d.status !== "resolved";
3511
+ const color = severityColor(d.severity);
3512
+ const desc = d.description.length > 30 ? `${d.description.substring(0, 27)}...` : d.description;
3513
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
3514
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color, children: [
3515
+ "[",
3516
+ d.severity,
3517
+ "]"
3518
+ ] }),
3519
+ " ",
3520
+ desc,
3521
+ " ",
3522
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: theme.dimGray, children: [
3523
+ "(due: ",
3524
+ formatDate(d.repayment_date),
3525
+ ")"
3526
+ ] }),
3527
+ isOverdue && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: theme.errorRed, children: " OVERDUE" })
3528
+ ] }, d.id);
3529
+ }),
3530
+ openDebts.length > 8 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: theme.dimGray, children: [
3531
+ "[",
3532
+ scrollOffset + 1,
3533
+ "-",
3534
+ Math.min(scrollOffset + 8, openDebts.length),
3535
+ "/",
3536
+ openDebts.length,
3537
+ "]",
3538
+ focused ? " \u2191\u2193 scroll" : ""
3539
+ ] })
3540
+ ] }),
3541
+ debtStats && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: theme.dimGray, children: [
3542
+ "Open:",
3543
+ debtStats.open,
3544
+ " InProg:",
3545
+ debtStats.in_progress,
3546
+ " Resolved:",
3547
+ debtStats.resolved,
3548
+ " ",
3549
+ "Overdue:",
3550
+ debtStats.overdue
3551
+ ] }) })
3552
+ ]
3553
+ }
3554
+ );
3555
+ }
3556
+
3557
+ // src/cli/dashboard/panels/graph-view.tsx
3558
+ init_cjs_shims();
3559
+ var import_ink4 = require("ink");
3560
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3561
+ function formatTokens(n) {
3562
+ if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
3563
+ return String(n);
3564
+ }
3565
+ function basename(filePath) {
3566
+ const parts = filePath.replace(/\\/g, "/").split("/");
3567
+ return parts[parts.length - 1] || filePath;
3568
+ }
3569
+ function buildTreeLines(nodes) {
3570
+ const lines = [];
3571
+ const sorted = [...nodes.values()].filter((n) => n.callers.length > 0).sort((a, b) => b.callers.length - a.callers.length).slice(0, 8);
3572
+ for (const node of sorted) {
3573
+ const name = basename(node.filePath);
3574
+ const tok = formatTokens(node.tokenEstimate);
3575
+ lines.push({
3576
+ text: `${name} (${node.callers.length} callers, ${tok}tok)`,
3577
+ color: theme.neuralCyan
3578
+ });
3579
+ const callerNames = node.callers.slice(0, 3).map(basename);
3580
+ const remaining = node.callers.length - callerNames.length;
3581
+ for (let i = 0; i < callerNames.length; i++) {
3582
+ const isLast = i === callerNames.length - 1 && remaining === 0;
3583
+ const prefix = isLast ? " \u2514\u2500\u2500 " : " \u251C\u2500\u2500 ";
3584
+ lines.push({
3585
+ text: `${prefix}${callerNames[i]}`,
3586
+ color: theme.dimGray
3587
+ });
3588
+ }
3589
+ if (remaining > 0) {
3590
+ lines.push({
3591
+ text: ` \u2514\u2500\u2500 ... ${remaining} more`,
3592
+ color: theme.dimGray
3593
+ });
3594
+ }
3595
+ }
3596
+ return lines;
3597
+ }
3598
+ function GraphViewPanel({
3599
+ analysis,
3600
+ nodes,
3601
+ focused,
3602
+ scrollOffset
3603
+ }) {
3604
+ const borderColor = focused ? theme.neuralCyan : theme.dimGray;
3605
+ if (!analysis) {
3606
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3607
+ import_ink4.Box,
3608
+ {
3609
+ flexDirection: "column",
3610
+ borderStyle: "round",
3611
+ borderColor,
3612
+ paddingX: 1,
3613
+ width: "50%",
3614
+ flexGrow: 1,
3615
+ children: [
3616
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { bold: true, color: theme.synapseViolet, children: "Dependency Graph" }),
3617
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { color: theme.dimGray, children: "Loading graph..." })
3618
+ ]
3619
+ }
3620
+ );
3621
+ }
3622
+ const lines = buildTreeLines(nodes);
3623
+ const visibleLines = lines.slice(scrollOffset, scrollOffset + 12);
3624
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3625
+ import_ink4.Box,
3626
+ {
3627
+ flexDirection: "column",
3628
+ borderStyle: "round",
3629
+ borderColor,
3630
+ paddingX: 1,
3631
+ width: "50%",
3632
+ flexGrow: 1,
3633
+ children: [
3634
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { bold: true, color: theme.synapseViolet, children: "Dependency Graph" }),
3635
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink4.Text, { color: theme.dimGray, children: [
3636
+ analysis.entryPoints.length,
3637
+ " entries | ",
3638
+ analysis.orphans.length,
3639
+ " orphans |",
3640
+ " ",
3641
+ formatTokens(analysis.totalTokens),
3642
+ "tok"
3643
+ ] }),
3644
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink4.Box, { flexDirection: "column", marginTop: 1, children: [
3645
+ visibleLines.map((line) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { color: line.color, children: line.text }, line.text)),
3646
+ lines.length > 12 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink4.Text, { color: theme.dimGray, children: [
3647
+ "[",
3648
+ scrollOffset + 1,
3649
+ "-",
3650
+ Math.min(scrollOffset + 12, lines.length),
3651
+ "/",
3652
+ lines.length,
3653
+ "]",
3654
+ focused ? " \u2191\u2193 scroll" : ""
3655
+ ] })
3656
+ ] })
3657
+ ]
3658
+ }
3659
+ );
3660
+ }
3661
+
3662
+ // src/cli/dashboard/panels/memory-status.tsx
3663
+ init_cjs_shims();
3664
+ var import_ink5 = require("ink");
3665
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3666
+ function formatSize(bytes) {
3667
+ if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;
3668
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3669
+ return `${bytes} B`;
3670
+ }
3671
+ function formatTokens2(n) {
3672
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
3673
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
3674
+ return String(n);
3675
+ }
3676
+ function MemoryStatusPanel({
3677
+ totalEntries,
3678
+ dbSizeBytes,
3679
+ stateDistribution,
3680
+ daemon,
3681
+ focused
3682
+ }) {
3683
+ const borderColor = focused ? theme.neuralCyan : theme.dimGray;
3684
+ const daemonRunning = daemon?.running ?? false;
3685
+ const daemonPid = daemon?.pid;
3686
+ const sessions = daemon?.sessionsWatched;
3687
+ const tokensSaved = daemon?.tokensSaved;
3688
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
3689
+ import_ink5.Box,
3690
+ {
3691
+ flexDirection: "column",
3692
+ borderStyle: "round",
3693
+ borderColor,
3694
+ paddingX: 1,
3695
+ width: "50%",
3696
+ flexGrow: 1,
3697
+ children: [
3698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { bold: true, color: theme.brainPink, children: "Memory / Daemon" }),
3699
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { children: [
3700
+ "Entries: ",
3701
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.white, children: totalEntries }),
3702
+ " | Size:",
3703
+ " ",
3704
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.white, children: formatSize(dbSizeBytes) })
3705
+ ] }),
3706
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { children: [
3707
+ "Active: ",
3708
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.neuralCyan, children: stateDistribution.active }),
3709
+ " Ready:",
3710
+ " ",
3711
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.synapseViolet, children: stateDistribution.ready }),
3712
+ " Silent:",
3713
+ " ",
3714
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.dimGray, children: stateDistribution.silent })
3715
+ ] }),
3716
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { marginTop: 1, children: daemonRunning ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { children: [
3717
+ "Daemon: ",
3718
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: theme.neuralCyan, children: [
3719
+ "\u25CF",
3720
+ " Running"
3721
+ ] }),
3722
+ daemonPid != null ? ` (PID ${daemonPid})` : ""
3723
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { children: [
3724
+ "Daemon: ",
3725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: theme.errorRed, children: [
3726
+ "\u25CF",
3727
+ " Stopped"
3728
+ ] })
3729
+ ] }) }),
3730
+ daemonRunning && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { children: [
3731
+ "Sessions: ",
3732
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.white, children: sessions ?? 0 }),
3733
+ " | Saved:",
3734
+ " ",
3735
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: theme.white, children: formatTokens2(tokensSaved ?? 0) })
3736
+ ] })
3737
+ ]
3738
+ }
3739
+ );
3740
+ }
3741
+
3742
+ // src/cli/dashboard/panels/optimization.tsx
3743
+ init_cjs_shims();
3744
+ var import_ink6 = require("ink");
3745
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3746
+ function formatTime(ts) {
3747
+ const d = new Date(ts);
3748
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
3749
+ }
3750
+ function formatTokens3(n) {
3751
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
3752
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
3753
+ return String(n);
3754
+ }
3755
+ function makeBar(pct, width) {
3756
+ const filled = Math.round(pct / 100 * width);
3757
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
3758
+ }
3759
+ function OptimizationPanel({ stats, focused }) {
3760
+ const totalOpts = stats.length;
3761
+ const totalSaved = stats.reduce((s, r) => s + (r.tokens_before - r.tokens_after), 0);
3762
+ let avgReduction = 0;
3763
+ if (totalOpts > 0) {
3764
+ const totalBefore = stats.reduce((s, r) => s + r.tokens_before, 0);
3765
+ avgReduction = totalBefore > 0 ? (totalBefore - (totalBefore - totalSaved)) / totalBefore * 100 : 0;
3766
+ }
3767
+ const recent = stats.slice(-5).reverse();
3768
+ const borderColor = focused ? theme.neuralCyan : theme.dimGray;
3769
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3770
+ import_ink6.Box,
3771
+ {
3772
+ flexDirection: "column",
3773
+ borderStyle: "round",
3774
+ borderColor,
3775
+ paddingX: 1,
3776
+ width: "50%",
3777
+ flexGrow: 1,
3778
+ children: [
3779
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { bold: true, color: theme.neuralCyan, children: "Optimization Stats" }),
3780
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { children: [
3781
+ "Total: ",
3782
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: theme.white, children: totalOpts }),
3783
+ " optimizations"
3784
+ ] }),
3785
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { children: [
3786
+ "Saved: ",
3787
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: theme.white, children: formatTokens3(totalSaved) }),
3788
+ " tokens"
3789
+ ] }),
3790
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { children: [
3791
+ "Avg: ",
3792
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: theme.white, children: [
3793
+ avgReduction.toFixed(1),
3794
+ "%"
3795
+ ] }),
3796
+ " ",
3797
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: theme.neuralCyan, children: makeBar(avgReduction, 10) })
3798
+ ] }),
3799
+ recent.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", marginTop: 1, children: [
3800
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: theme.dimGray, children: "Recent:" }),
3801
+ recent.map((r) => {
3802
+ const pct = r.tokens_before > 0 ? ((r.tokens_before - r.tokens_after) / r.tokens_before * 100).toFixed(0) : "0";
3803
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { children: [
3804
+ " ",
3805
+ formatTime(r.timestamp),
3806
+ " | ",
3807
+ formatTokens3(r.tokens_before),
3808
+ "\u2192",
3809
+ formatTokens3(r.tokens_after),
3810
+ " | ",
3811
+ pct,
3812
+ "%"
3813
+ ] }, r.id);
3814
+ })
3815
+ ] })
3816
+ ]
3817
+ }
3818
+ );
3819
+ }
3820
+
3821
+ // src/cli/dashboard/app.tsx
3822
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3823
+ var PANELS = ["optimization", "graph", "memory", "debt", "output"];
3824
+ function Dashboard({ dbPath, projectRoot, refreshInterval }) {
3825
+ const { exit } = (0, import_ink7.useApp)();
3826
+ const data = useDashboardData(dbPath, projectRoot, refreshInterval);
3827
+ const [mode, setMode] = (0, import_react4.useState)("monitor");
3828
+ const [focusIndex, setFocusIndex] = (0, import_react4.useState)(0);
3829
+ const [graphScroll, setGraphScroll] = (0, import_react4.useState)(0);
3830
+ const [debtScroll, setDebtScroll] = (0, import_react4.useState)(0);
3831
+ const [outputScroll, setOutputScroll] = (0, import_react4.useState)(0);
3832
+ const focusedPanel = PANELS[focusIndex] ?? "optimization";
3833
+ const executor = useCommandExecutor({
3834
+ getMemory: data.getMemory,
3835
+ forceRefresh: data.forceRefresh,
3836
+ dbPath,
3837
+ projectRoot
3838
+ });
3839
+ (0, import_ink7.useInput)(
3840
+ (input, key) => {
3841
+ if (mode === "command") {
3842
+ if (key.escape) {
3843
+ setMode("monitor");
3844
+ }
3845
+ return;
3846
+ }
3847
+ if (input === "q") {
3848
+ exit();
3849
+ return;
3850
+ }
3851
+ if (input === ":") {
3852
+ setMode("command");
3853
+ return;
3854
+ }
3855
+ if (key.tab) {
3856
+ setFocusIndex((prev) => (prev + 1) % PANELS.length);
3857
+ return;
3858
+ }
3859
+ if (key.upArrow) {
3860
+ if (focusedPanel === "graph") setGraphScroll((prev) => Math.max(0, prev - 1));
3861
+ if (focusedPanel === "debt") setDebtScroll((prev) => Math.max(0, prev - 1));
3862
+ if (focusedPanel === "output") setOutputScroll((prev) => Math.max(0, prev - 1));
3863
+ }
3864
+ if (key.downArrow) {
3865
+ if (focusedPanel === "graph") setGraphScroll((prev) => prev + 1);
3866
+ if (focusedPanel === "debt") setDebtScroll((prev) => prev + 1);
3867
+ if (focusedPanel === "output") setOutputScroll((prev) => prev + 1);
3868
+ }
3869
+ },
3870
+ { isActive: mode === "monitor" || mode === "command" }
3871
+ );
3872
+ const outputMaxVisible = 8;
3873
+ (0, import_react4.useEffect)(() => {
3874
+ setOutputScroll(Math.max(0, executor.outputLines.length - outputMaxVisible));
3875
+ }, [executor.outputLines.length]);
3876
+ if (data.loading) {
3877
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", padding: 1, children: [
3878
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: theme.neuralCyan, children: "Sparn Dashboard" }),
3879
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: theme.dimGray, children: "Loading data..." })
3880
+ ] });
3881
+ }
3882
+ if (data.error) {
3883
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", padding: 1, children: [
3884
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: theme.neuralCyan, children: "Sparn Dashboard" }),
3885
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: theme.errorRed, children: [
3886
+ "Error: ",
3887
+ data.error
3888
+ ] }),
3889
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: theme.dimGray, children: "Press q to quit" })
3890
+ ] });
3891
+ }
3892
+ const timeStr = data.lastRefresh.toLocaleTimeString();
3893
+ const modeHint = mode === "command" ? "Esc:monitor Enter:run" : "q:quit Tab:focus \u2191\u2193:scroll ::cmd";
3894
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
3895
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { justifyContent: "space-between", paddingX: 1, children: [
3896
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { bold: true, color: theme.neuralCyan, children: "Sparn Dashboard" }),
3897
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: theme.dimGray, children: [
3898
+ modeHint,
3899
+ " | ",
3900
+ timeStr
3901
+ ] })
3902
+ ] }),
3903
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
3904
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3905
+ OptimizationPanel,
3906
+ {
3907
+ stats: data.optimizationStats,
3908
+ focused: focusedPanel === "optimization"
3909
+ }
3910
+ ),
3911
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3912
+ GraphViewPanel,
3913
+ {
3914
+ analysis: data.graphAnalysis,
3915
+ nodes: data.graphNodes,
3916
+ focused: focusedPanel === "graph",
3917
+ scrollOffset: graphScroll
3918
+ }
3919
+ )
3920
+ ] }),
3921
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
3922
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3923
+ MemoryStatusPanel,
3924
+ {
3925
+ totalEntries: data.totalEntries,
3926
+ dbSizeBytes: data.dbSizeBytes,
3927
+ stateDistribution: data.stateDistribution,
3928
+ daemon: data.daemon,
3929
+ focused: focusedPanel === "memory"
3930
+ }
3931
+ ),
3932
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3933
+ DebtTrackerPanel,
3934
+ {
3935
+ debts: data.debts,
3936
+ debtStats: data.debtStats,
3937
+ focused: focusedPanel === "debt",
3938
+ scrollOffset: debtScroll
3939
+ }
3940
+ )
3941
+ ] }),
3942
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3943
+ OutputPanel,
3944
+ {
3945
+ lines: executor.outputLines,
3946
+ scrollOffset: outputScroll,
3947
+ focused: focusedPanel === "output"
3948
+ }
3949
+ ),
3950
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3951
+ CommandInput,
3952
+ {
3953
+ onSubmit: executor.execute,
3954
+ isRunning: executor.isRunning,
3955
+ runningCommand: executor.runningCommand,
3956
+ active: mode === "command"
3957
+ }
3958
+ )
3959
+ ] });
3960
+ }
3961
+ function renderDashboard(options) {
3962
+ (0, import_ink7.render)(
3963
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3964
+ Dashboard,
3965
+ {
3966
+ dbPath: options.dbPath,
3967
+ projectRoot: options.projectRoot,
3968
+ refreshInterval: options.refreshInterval ?? 2e3
3969
+ }
3970
+ )
3971
+ );
3972
+ }
3973
+ // Annotate the CommonJS export names for ESM import in node:
3974
+ 0 && (module.exports = {
3975
+ renderDashboard
3976
+ });
3977
+ //# sourceMappingURL=dashboard.cjs.map