fivocell 4.0.0 → 4.1.1

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 (166) hide show
  1. package/dist/__tests__/behavior-intelligence-bug.test.d.ts +2 -0
  2. package/dist/__tests__/behavior-intelligence-bug.test.d.ts.map +1 -0
  3. package/dist/__tests__/behavior-intelligence-bug.test.js +21 -0
  4. package/dist/__tests__/behavior-intelligence-bug.test.js.map +1 -0
  5. package/dist/__tests__/code-scanner-arrow-return-type.test.d.ts +2 -0
  6. package/dist/__tests__/code-scanner-arrow-return-type.test.d.ts.map +1 -0
  7. package/dist/__tests__/code-scanner-arrow-return-type.test.js +76 -0
  8. package/dist/__tests__/code-scanner-arrow-return-type.test.js.map +1 -0
  9. package/dist/__tests__/code-scanner-blindspot.test.d.ts +2 -0
  10. package/dist/__tests__/code-scanner-blindspot.test.d.ts.map +1 -0
  11. package/dist/__tests__/code-scanner-blindspot.test.js +18 -0
  12. package/dist/__tests__/code-scanner-blindspot.test.js.map +1 -0
  13. package/dist/__tests__/code-scanner-error-recovery.test.d.ts +2 -0
  14. package/dist/__tests__/code-scanner-error-recovery.test.d.ts.map +1 -0
  15. package/dist/__tests__/code-scanner-error-recovery.test.js +21 -0
  16. package/dist/__tests__/code-scanner-error-recovery.test.js.map +1 -0
  17. package/dist/__tests__/code-scanner-n1-detection.test.d.ts +2 -0
  18. package/dist/__tests__/code-scanner-n1-detection.test.d.ts.map +1 -0
  19. package/dist/__tests__/code-scanner-n1-detection.test.js +113 -0
  20. package/dist/__tests__/code-scanner-n1-detection.test.js.map +1 -0
  21. package/dist/__tests__/code-scanner-nesting.test.d.ts +2 -0
  22. package/dist/__tests__/code-scanner-nesting.test.d.ts.map +1 -0
  23. package/dist/__tests__/code-scanner-nesting.test.js +113 -0
  24. package/dist/__tests__/code-scanner-nesting.test.js.map +1 -0
  25. package/dist/__tests__/code-scanner-null-check.test.d.ts +2 -0
  26. package/dist/__tests__/code-scanner-null-check.test.d.ts.map +1 -0
  27. package/dist/__tests__/code-scanner-null-check.test.js +126 -0
  28. package/dist/__tests__/code-scanner-null-check.test.js.map +1 -0
  29. package/dist/__tests__/code-scanner-sql-fix.test.d.ts +2 -0
  30. package/dist/__tests__/code-scanner-sql-fix.test.d.ts.map +1 -0
  31. package/dist/__tests__/code-scanner-sql-fix.test.js +21 -0
  32. package/dist/__tests__/code-scanner-sql-fix.test.js.map +1 -0
  33. package/dist/__tests__/code-scanner-trust-score.test.d.ts +1 -0
  34. package/dist/__tests__/code-scanner-trust-score.test.d.ts.map +1 -0
  35. package/dist/__tests__/code-scanner-trust-score.test.js +39 -0
  36. package/dist/__tests__/code-scanner-trust-score.test.js.map +1 -0
  37. package/dist/__tests__/code-scanner-validation.test.d.ts +2 -0
  38. package/dist/__tests__/code-scanner-validation.test.d.ts.map +1 -0
  39. package/dist/__tests__/code-scanner-validation.test.js +131 -0
  40. package/dist/__tests__/code-scanner-validation.test.js.map +1 -0
  41. package/dist/__tests__/community-store.test.d.ts +2 -0
  42. package/dist/__tests__/community-store.test.d.ts.map +1 -0
  43. package/dist/__tests__/community-store.test.js +231 -0
  44. package/dist/__tests__/community-store.test.js.map +1 -0
  45. package/dist/__tests__/enhanced-blind-spots.test.d.ts +2 -0
  46. package/dist/__tests__/enhanced-blind-spots.test.d.ts.map +1 -0
  47. package/dist/__tests__/enhanced-blind-spots.test.js +347 -0
  48. package/dist/__tests__/enhanced-blind-spots.test.js.map +1 -0
  49. package/dist/__tests__/knowledge-graph-store.test.d.ts +2 -0
  50. package/dist/__tests__/knowledge-graph-store.test.d.ts.map +1 -0
  51. package/dist/__tests__/knowledge-graph-store.test.js +252 -0
  52. package/dist/__tests__/knowledge-graph-store.test.js.map +1 -0
  53. package/dist/__tests__/live-watcher.test.d.ts +2 -0
  54. package/dist/__tests__/live-watcher.test.d.ts.map +1 -0
  55. package/dist/__tests__/live-watcher.test.js +312 -0
  56. package/dist/__tests__/live-watcher.test.js.map +1 -0
  57. package/dist/__tests__/mcp-cell-tools.test.d.ts +2 -0
  58. package/dist/__tests__/mcp-cell-tools.test.d.ts.map +1 -0
  59. package/dist/__tests__/mcp-cell-tools.test.js +176 -0
  60. package/dist/__tests__/mcp-cell-tools.test.js.map +1 -0
  61. package/dist/__tests__/multi-project.test.d.ts +2 -0
  62. package/dist/__tests__/multi-project.test.d.ts.map +1 -0
  63. package/dist/__tests__/multi-project.test.js +145 -0
  64. package/dist/__tests__/multi-project.test.js.map +1 -0
  65. package/dist/__tests__/pc-scanner-paths.test.d.ts +2 -0
  66. package/dist/__tests__/pc-scanner-paths.test.d.ts.map +1 -0
  67. package/dist/__tests__/pc-scanner-paths.test.js +16 -0
  68. package/dist/__tests__/pc-scanner-paths.test.js.map +1 -0
  69. package/dist/__tests__/prompt-builder-realdata.test.d.ts +2 -0
  70. package/dist/__tests__/prompt-builder-realdata.test.d.ts.map +1 -0
  71. package/dist/__tests__/prompt-builder-realdata.test.js +94 -0
  72. package/dist/__tests__/prompt-builder-realdata.test.js.map +1 -0
  73. package/dist/__tests__/prompt-builder-sessions.test.d.ts +2 -0
  74. package/dist/__tests__/prompt-builder-sessions.test.d.ts.map +1 -0
  75. package/dist/__tests__/prompt-builder-sessions.test.js +124 -0
  76. package/dist/__tests__/prompt-builder-sessions.test.js.map +1 -0
  77. package/dist/__tests__/security.test.d.ts +1 -0
  78. package/dist/__tests__/security.test.d.ts.map +1 -0
  79. package/dist/__tests__/security.test.js +161 -0
  80. package/dist/__tests__/security.test.js.map +1 -0
  81. package/dist/__tests__/session-bridge.test.d.ts +2 -0
  82. package/dist/__tests__/session-bridge.test.d.ts.map +1 -0
  83. package/dist/__tests__/session-bridge.test.js +158 -0
  84. package/dist/__tests__/session-bridge.test.js.map +1 -0
  85. package/dist/__tests__/session-memory-tables.test.d.ts +2 -0
  86. package/dist/__tests__/session-memory-tables.test.d.ts.map +1 -0
  87. package/dist/__tests__/session-memory-tables.test.js +169 -0
  88. package/dist/__tests__/session-memory-tables.test.js.map +1 -0
  89. package/dist/__tests__/staleness-detection.test.d.ts +2 -0
  90. package/dist/__tests__/staleness-detection.test.d.ts.map +1 -0
  91. package/dist/__tests__/staleness-detection.test.js +105 -0
  92. package/dist/__tests__/staleness-detection.test.js.map +1 -0
  93. package/dist/__tests__/team-collaboration.test.d.ts +2 -0
  94. package/dist/__tests__/team-collaboration.test.d.ts.map +1 -0
  95. package/dist/__tests__/team-collaboration.test.js +224 -0
  96. package/dist/__tests__/team-collaboration.test.js.map +1 -0
  97. package/dist/__tests__/tool-specific-format.test.d.ts +2 -0
  98. package/dist/__tests__/tool-specific-format.test.d.ts.map +1 -0
  99. package/dist/__tests__/tool-specific-format.test.js +130 -0
  100. package/dist/__tests__/tool-specific-format.test.js.map +1 -0
  101. package/dist/__tests__/usage-intelligence-store.test.d.ts +2 -0
  102. package/dist/__tests__/usage-intelligence-store.test.d.ts.map +1 -0
  103. package/dist/__tests__/usage-intelligence-store.test.js +266 -0
  104. package/dist/__tests__/usage-intelligence-store.test.js.map +1 -0
  105. package/dist/behavior-intelligence.d.ts.map +1 -1
  106. package/dist/behavior-intelligence.js +12 -1
  107. package/dist/behavior-intelligence.js.map +1 -1
  108. package/dist/cli.js +403 -4
  109. package/dist/cli.js.map +1 -1
  110. package/dist/code-scanner.d.ts.map +1 -1
  111. package/dist/code-scanner.js +344 -102
  112. package/dist/code-scanner.js.map +1 -1
  113. package/dist/core/community-store.d.ts +128 -0
  114. package/dist/core/community-store.d.ts.map +1 -0
  115. package/dist/core/community-store.js +329 -0
  116. package/dist/core/community-store.js.map +1 -0
  117. package/dist/core/database.d.ts +0 -3
  118. package/dist/core/database.d.ts.map +1 -1
  119. package/dist/core/database.js +287 -15
  120. package/dist/core/database.js.map +1 -1
  121. package/dist/core/enhanced-blind-spots.d.ts +27 -0
  122. package/dist/core/enhanced-blind-spots.d.ts.map +1 -0
  123. package/dist/core/enhanced-blind-spots.js +710 -0
  124. package/dist/core/enhanced-blind-spots.js.map +1 -0
  125. package/dist/core/knowledge-graph-store.d.ts +69 -0
  126. package/dist/core/knowledge-graph-store.d.ts.map +1 -0
  127. package/dist/core/knowledge-graph-store.js +269 -0
  128. package/dist/core/knowledge-graph-store.js.map +1 -0
  129. package/dist/core/live-watcher.d.ts +52 -0
  130. package/dist/core/live-watcher.d.ts.map +1 -0
  131. package/dist/core/live-watcher.js +369 -0
  132. package/dist/core/live-watcher.js.map +1 -0
  133. package/dist/core/project-registry.d.ts +24 -0
  134. package/dist/core/project-registry.d.ts.map +1 -0
  135. package/dist/core/project-registry.js +70 -0
  136. package/dist/core/project-registry.js.map +1 -0
  137. package/dist/core/prompt-builder.d.ts +49 -0
  138. package/dist/core/prompt-builder.d.ts.map +1 -1
  139. package/dist/core/prompt-builder.js +540 -67
  140. package/dist/core/prompt-builder.js.map +1 -1
  141. package/dist/core/security.d.ts +23 -0
  142. package/dist/core/security.d.ts.map +1 -0
  143. package/dist/core/security.js +117 -0
  144. package/dist/core/security.js.map +1 -0
  145. package/dist/core/session-memory.d.ts +64 -0
  146. package/dist/core/session-memory.d.ts.map +1 -1
  147. package/dist/core/session-memory.js +111 -0
  148. package/dist/core/session-memory.js.map +1 -1
  149. package/dist/core/syntax-engine.d.ts.map +1 -1
  150. package/dist/core/syntax-engine.js +5 -2
  151. package/dist/core/syntax-engine.js.map +1 -1
  152. package/dist/core/usage-intelligence-store.d.ts +126 -0
  153. package/dist/core/usage-intelligence-store.d.ts.map +1 -0
  154. package/dist/core/usage-intelligence-store.js +405 -0
  155. package/dist/core/usage-intelligence-store.js.map +1 -0
  156. package/dist/daemon/server.d.ts.map +1 -1
  157. package/dist/daemon/server.js +936 -17
  158. package/dist/daemon/server.js.map +1 -1
  159. package/dist/knowledge-graph-builder.d.ts.map +1 -1
  160. package/dist/knowledge-graph-builder.js +18 -2
  161. package/dist/knowledge-graph-builder.js.map +1 -1
  162. package/dist/pc-scanner.d.ts +1 -0
  163. package/dist/pc-scanner.d.ts.map +1 -1
  164. package/dist/pc-scanner.js +64 -33
  165. package/dist/pc-scanner.js.map +1 -1
  166. package/package.json +1 -1
@@ -1,27 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_EXPIRED_DAYS = exports.DEFAULT_STALE_DAYS = void 0;
3
4
  exports.buildContext = buildContext;
4
5
  exports.formatContextForInjection = formatContextForInjection;
6
+ exports.formatContextForTool = formatContextForTool;
5
7
  exports.estimateTokens = estimateTokens;
8
+ exports.getStalenessStatus = getStalenessStatus;
6
9
  const database_1 = require("./database");
7
10
  const logger_1 = require("./logger");
8
11
  function buildContext(projectPath, toolName) {
9
12
  const db = (0, database_1.getDb)();
10
- const profile = readProfile(db);
11
- const blindSpots = readBlindSpots(db);
12
- const topPatterns = readTopPatterns(db);
13
+ const profile = readProfile(db, projectPath);
14
+ const blindSpots = readBlindSpots(db, projectPath);
15
+ const topPatterns = readTopPatterns(db, projectPath);
13
16
  const recentActivity = readRecentActivity(db, projectPath);
14
17
  const skills = readSkills(db);
15
18
  const collaborators = readCollaborators(db);
16
19
  const toolStats = toolName ? readToolStats(db, toolName) : null;
17
20
  const stackDNA = readStackDNA(db, projectPath);
18
21
  const dbPredictions = readPredictions(db, projectPath);
22
+ const lastSession = readLastSession(db, projectPath);
23
+ const triedApproaches = readTriedApproaches(db, projectPath);
24
+ const mostTouchedFiles = readMostTouchedFiles(db, projectPath);
25
+ const openQuestions = readOpenQuestions(db, projectPath);
19
26
  const whoYouAre = buildWhoYouAre(profile, skills, collaborators, stackDNA, blindSpots);
20
- const rightNow = buildRightNow(recentActivity, profile);
27
+ const rightNow = buildRightNow(recentActivity, profile, lastSession);
21
28
  const yourStyle = buildYourStyle(topPatterns, profile, skills);
22
29
  const watchOut = buildWatchOut(blindSpots, profile);
23
30
  const predictions = buildPredictions(blindSpots, recentActivity, toolStats);
24
31
  const toolHint = toolName ? buildToolHint(toolName) : undefined;
32
+ const staleness = buildStalenessInfo(profile, recentActivity);
25
33
  return {
26
34
  whoYouAre,
27
35
  rightNow,
@@ -31,6 +39,11 @@ function buildContext(projectPath, toolName) {
31
39
  dbPredictions,
32
40
  injectedAt: new Date().toISOString(),
33
41
  toolHint,
42
+ lastSession,
43
+ triedApproaches,
44
+ mostTouchedFiles,
45
+ openQuestions,
46
+ staleness,
34
47
  };
35
48
  }
36
49
  function formatContextForInjection(ctx, compact = false) {
@@ -39,20 +52,135 @@ function formatContextForInjection(ctx, compact = false) {
39
52
  }
40
53
  return fullContext(ctx);
41
54
  }
55
+ function formatContextForTool(ctx, toolName) {
56
+ if (!toolName)
57
+ return atCellBlock(ctx);
58
+ const t = toolName.toLowerCase().trim();
59
+ if (t === 'cell' || t === '@cell' || t === 'cell-block') {
60
+ return atCellBlock(ctx);
61
+ }
62
+ if (t === 'claude-code' || t === 'claude' || t === 'claude-code-cli') {
63
+ return atCellBlock(ctx);
64
+ }
65
+ if (t === 'cursor' || t === 'cursor-ide' || t === 'cursor-ai') {
66
+ return formatForCursor(ctx);
67
+ }
68
+ if (t === 'windsurf' || t === 'codeium' || t === 'windsurf-ide') {
69
+ return formatForWindsurf(ctx);
70
+ }
71
+ if (t === 'copilot' || t === 'github-copilot') {
72
+ return formatForCopilot(ctx);
73
+ }
74
+ if (t === 'antigravity' || t === 'aider' || t === 'continue-dev') {
75
+ return formatForAntigravity(ctx);
76
+ }
77
+ return atCellBlock(ctx);
78
+ }
79
+ // Compact `@cell` block format: matches the @cell Context convention from CLAUDE.md
80
+ function atCellBlock(ctx) {
81
+ const lines = [];
82
+ lines.push('# ─── @cell Context — Auto-generated by FIVO Cell ───');
83
+ // Developer
84
+ const developer = [];
85
+ if (ctx.whoYouAre)
86
+ developer.push(ctx.whoYouAre.split('\n')[0] || '');
87
+ lines.push('## Developer Context');
88
+ lines.push(developer.filter(Boolean).join(' | '));
89
+ // Current
90
+ lines.push('');
91
+ lines.push('### Current');
92
+ lines.push(ctx.rightNow ? ctx.rightNow.split('\n').filter(Boolean).slice(0, 3).join(' | ') : 'No recent activity detected');
93
+ // Style
94
+ lines.push('');
95
+ lines.push('### Style');
96
+ const styleLine = ctx.yourStyle ? ctx.yourStyle.split('\n').filter(Boolean).slice(0, 4).join(' | ') : '';
97
+ lines.push(styleLine || 'unknown');
98
+ // Blind spots
99
+ lines.push('');
100
+ lines.push('### Blind Spots');
101
+ lines.push(ctx.watchOut ? ctx.watchOut.split('\n').filter(Boolean).slice(0, 4).join(' | ') : 'none detected');
102
+ // Predictions
103
+ lines.push('');
104
+ lines.push('### Predictions');
105
+ lines.push(ctx.predictions ? ctx.predictions.split('\n').filter(Boolean).slice(0, 2).join(' | ') : 'none');
106
+ // Last session
107
+ if (ctx.lastSession) {
108
+ lines.push('');
109
+ lines.push('### Last Session');
110
+ const ago = ctx.lastSession.endTime ? formatRelativeTime(ctx.lastSession.endTime) : 'active';
111
+ const tool = ctx.lastSession.toolName || 'unknown';
112
+ const task = ctx.lastSession.contextSnapshot ? ctx.lastSession.contextSnapshot.split('\n')[0] : '';
113
+ lines.push(`${ago} · ${tool}${task ? ' · ' + task : ''}`);
114
+ }
115
+ // Tried approaches
116
+ if (ctx.triedApproaches.length > 0) {
117
+ lines.push('');
118
+ lines.push('### Tried (skip these)');
119
+ lines.push(ctx.triedApproaches.slice(0, 3).map(a => `${a.approach}(${a.status})`).join(' | '));
120
+ }
121
+ // Tool hint
122
+ if (ctx.toolHint) {
123
+ lines.push('');
124
+ lines.push('### Tool Note');
125
+ lines.push(ctx.toolHint.split('\n').filter(Boolean).slice(0, 2).join(' | '));
126
+ }
127
+ // Staleness warning
128
+ if (ctx.staleness?.warning) {
129
+ lines.unshift('> ' + ctx.staleness.warning);
130
+ }
131
+ lines.push('# ─── End @cell Context ───');
132
+ return lines.join('\n');
133
+ }
42
134
  function estimateTokens(text) {
43
135
  return Math.ceil(text.length / 4);
44
136
  }
45
137
  function fullContext(ctx) {
46
138
  let out = '';
47
- out += '═══ CELL CONTEXT ═══\n\n';
139
+ out += '━━━ CELL CONTEXT ━━━\n\n';
140
+ if (ctx.staleness?.warning) {
141
+ out += '⚠ ' + ctx.staleness.warning + '\n\n';
142
+ }
48
143
  out += '▸ WHO YOU ARE\n';
49
144
  out += ctx.whoYouAre + '\n\n';
50
145
  out += '▸ RIGHT NOW\n';
51
146
  out += ctx.rightNow + '\n\n';
147
+ if (ctx.lastSession) {
148
+ const ago = ctx.lastSession.endTime ? formatRelativeTime(ctx.lastSession.endTime) : 'active';
149
+ out += '▸ LAST SESSION (' + ago + ')\n';
150
+ out += `Tool: ${ctx.lastSession.toolName}\n`;
151
+ if (ctx.lastSession.contextSnapshot) {
152
+ out += `Working on: ${ctx.lastSession.contextSnapshot}\n`;
153
+ }
154
+ if (ctx.lastSession.keyDecisions.length > 0) {
155
+ out += 'Key decisions: ' + ctx.lastSession.keyDecisions.slice(0, 3).join(' · ') + '\n';
156
+ }
157
+ out += '\n';
158
+ }
159
+ if (ctx.triedApproaches.length > 0) {
160
+ out += '▸ TRIED ALREADY (don\'t suggest these)\n';
161
+ for (const a of ctx.triedApproaches.slice(0, 5)) {
162
+ out += `→ ${a.approach} — ${a.status}${a.count > 1 ? ` (${a.count}x)` : ''}\n`;
163
+ }
164
+ out += '\n';
165
+ }
166
+ if (ctx.openQuestions.length > 0) {
167
+ out += '▸ OPEN QUESTIONS\n';
168
+ for (const q of ctx.openQuestions.slice(0, 5)) {
169
+ out += `→ [${q.priority}] ${q.question}\n`;
170
+ }
171
+ out += '\n';
172
+ }
52
173
  out += '▸ YOUR STYLE\n';
53
174
  out += ctx.yourStyle + '\n\n';
54
175
  out += '▸ WATCH OUT\n';
55
176
  out += ctx.watchOut + '\n\n';
177
+ if (ctx.mostTouchedFiles.length > 0) {
178
+ out += '▸ HOT FILES (recently active)\n';
179
+ for (const f of ctx.mostTouchedFiles.slice(0, 5)) {
180
+ out += `→ ${f.filePath} (${f.touchCount} touches, ${f.sessionCount} sessions)\n`;
181
+ }
182
+ out += '\n';
183
+ }
56
184
  out += '▸ PREDICTIONS\n';
57
185
  out += ctx.predictions;
58
186
  if (ctx.toolHint) {
@@ -62,10 +190,21 @@ function fullContext(ctx) {
62
190
  }
63
191
  function compactContext(ctx) {
64
192
  let out = '';
193
+ if (ctx.staleness?.warning) {
194
+ out += '⚠ ' + ctx.staleness.warning + '\n\n';
195
+ }
65
196
  out += '## Developer Context\n';
66
197
  out += ctx.whoYouAre.replace(/\n/g, ' | ') + '\n\n';
67
198
  out += '### Current\n';
68
199
  out += ctx.rightNow.replace(/\n/g, ' | ') + '\n\n';
200
+ if (ctx.triedApproaches.length > 0) {
201
+ out += '### Tried (skip these)\n';
202
+ out += ctx.triedApproaches.slice(0, 3).map(a => `${a.approach}(${a.status})`).join(' | ') + '\n\n';
203
+ }
204
+ if (ctx.openQuestions.length > 0) {
205
+ out += '### Open Questions\n';
206
+ out += ctx.openQuestions.slice(0, 3).map(q => `[${q.priority}] ${q.question}`).join(' | ') + '\n\n';
207
+ }
69
208
  out += '### Style\n';
70
209
  out += ctx.yourStyle.replace(/\n/g, ' | ') + '\n\n';
71
210
  out += '### Blind Spots\n';
@@ -77,16 +216,142 @@ function compactContext(ctx) {
77
216
  }
78
217
  return out;
79
218
  }
80
- function readProfile(db) {
219
+ function formatForCursor(ctx) {
220
+ const lines = [];
221
+ lines.push('@context fivo-cell');
222
+ if (ctx.staleness?.warning)
223
+ lines.push('! ' + ctx.staleness.warning);
224
+ if (ctx.lastSession) {
225
+ lines.push('last: ' + ctx.lastSession.toolName + ' (' + (ctx.lastSession.endTime ? formatRelativeTime(ctx.lastSession.endTime) : 'active') + ')');
226
+ if (ctx.lastSession.contextSnapshot)
227
+ lines.push('working: ' + ctx.lastSession.contextSnapshot);
228
+ }
229
+ lines.push('');
230
+ lines.push('style: ' + (ctx.yourStyle.split('\n')[0] || ''));
231
+ if (ctx.triedApproaches.length > 0) {
232
+ lines.push('avoid: ' + ctx.triedApproaches.slice(0, 3).map(a => `${a.approach}[${a.status}]`).join(', '));
233
+ }
234
+ if (ctx.openQuestions.length > 0) {
235
+ lines.push('q: ' + ctx.openQuestions.slice(0, 2).map(q => `${q.question.slice(0, 50)}…`).join(' | '));
236
+ }
237
+ if (ctx.mostTouchedFiles.length > 0) {
238
+ lines.push('hot: ' + ctx.mostTouchedFiles.slice(0, 3).map(f => f.filePath).join(', '));
239
+ }
240
+ if (ctx.watchOut) {
241
+ const topBlindSpot = ctx.watchOut.split('\n').find(l => l.trim().startsWith('-'));
242
+ if (topBlindSpot)
243
+ lines.push('watch: ' + topBlindSpot.replace(/^-\s*/, '').slice(0, 80));
244
+ }
245
+ return lines.join('\n');
246
+ }
247
+ function formatForWindsurf(ctx) {
248
+ const lines = [];
249
+ lines.push('# Cell Context');
250
+ if (ctx.staleness?.warning)
251
+ lines.push('> ⚠ ' + ctx.staleness.warning);
252
+ if (ctx.lastSession) {
253
+ lines.push('## Last Session');
254
+ lines.push(`- **Tool**: ${ctx.lastSession.toolName}`);
255
+ if (ctx.lastSession.contextSnapshot)
256
+ lines.push(`- **Working on**: ${ctx.lastSession.contextSnapshot}`);
257
+ if (ctx.lastSession.keyDecisions.length > 0)
258
+ lines.push(`- **Decisions**: ${ctx.lastSession.keyDecisions.slice(0, 2).join(', ')}`);
259
+ }
260
+ if (ctx.openQuestions.length > 0) {
261
+ lines.push('## Open Questions');
262
+ for (const q of ctx.openQuestions.slice(0, 3)) {
263
+ lines.push(`- [${q.priority}] ${q.question}`);
264
+ }
265
+ }
266
+ lines.push('## Style');
267
+ lines.push(ctx.yourStyle.split('\n')[0] || '');
268
+ lines.push('## Suggestions');
269
+ if (ctx.watchOut) {
270
+ const spots = ctx.watchOut.split('\n').filter(l => l.trim().startsWith('-')).slice(0, 3);
271
+ spots.forEach(s => lines.push('- ' + s.replace(/^-\s*/, '')));
272
+ }
273
+ if (ctx.mostTouchedFiles.length > 0) {
274
+ lines.push('## Active Files');
275
+ ctx.mostTouchedFiles.slice(0, 3).forEach(f => lines.push(`- ${f.filePath} (${f.touchCount} touches)`));
276
+ }
277
+ return lines.join('\n');
278
+ }
279
+ function formatForCopilot(ctx) {
280
+ const parts = [];
281
+ if (ctx.staleness?.warning)
282
+ parts.push('⚠ ' + ctx.staleness.warning.replace(/`/g, ''));
283
+ parts.push('Project: ' + (ctx.lastSession?.project || 'unknown'));
284
+ if (ctx.lastSession?.contextSnapshot)
285
+ parts.push('Working: ' + ctx.lastSession.contextSnapshot.slice(0, 60));
286
+ if (ctx.triedApproaches.length > 0) {
287
+ const a = ctx.triedApproaches[0];
288
+ parts.push(`Avoid: ${a.approach} (${a.status})`);
289
+ }
290
+ const styleLine = ctx.yourStyle.split('\n')[0] || '';
291
+ if (styleLine)
292
+ parts.push('Style: ' + styleLine.slice(0, 80));
293
+ return parts.join(' · ');
294
+ }
295
+ function formatForAntigravity(ctx) {
296
+ const lines = [];
297
+ lines.push('## Cell Context — Structured');
298
+ if (ctx.staleness?.warning)
299
+ lines.push('> ⚠ ' + ctx.staleness.warning);
300
+ lines.push('');
301
+ lines.push('```yaml');
302
+ lines.push(`project: ${ctx.lastSession?.project || 'unknown'}`);
303
+ lines.push(`tool_hint: ${ctx.toolHint || 'general'}`);
304
+ if (ctx.lastSession) {
305
+ lines.push(`last_session:`);
306
+ lines.push(` tool: ${ctx.lastSession.toolName}`);
307
+ lines.push(` when: ${ctx.lastSession.endTime || 'active'}`);
308
+ if (ctx.lastSession.contextSnapshot)
309
+ lines.push(` context: "${ctx.lastSession.contextSnapshot}"`);
310
+ }
311
+ if (ctx.triedApproaches.length > 0) {
312
+ lines.push(`tried_approaches:`);
313
+ ctx.triedApproaches.slice(0, 3).forEach(a => {
314
+ lines.push(` - approach: "${a.approach}"`);
315
+ lines.push(` status: ${a.status}`);
316
+ });
317
+ }
318
+ if (ctx.openQuestions.length > 0) {
319
+ lines.push(`open_questions:`);
320
+ ctx.openQuestions.slice(0, 3).forEach(q => {
321
+ lines.push(` - priority: ${q.priority}`);
322
+ lines.push(` question: "${q.question}"`);
323
+ });
324
+ }
325
+ lines.push('```');
326
+ return lines.join('\n');
327
+ }
328
+ function readProfile(db, projectPath) {
81
329
  try {
82
- const row = db.prepare('SELECT * FROM user_profile WHERE id = 1').get();
330
+ const row = projectPath
331
+ ? db.prepare('SELECT * FROM developer_profiles WHERE project = ? ORDER BY scanned_at DESC LIMIT 1').get(projectPath)
332
+ : db.prepare('SELECT * FROM developer_profiles ORDER BY scanned_at DESC LIMIT 1').get();
83
333
  if (row) {
84
334
  return {
85
- name: row.name || '',
86
- products: safeJson(row.products),
87
- stack: safeJson(row.stack),
88
- workStyle: safeJson(row.work_style),
89
- preferences: safeJson(row.preferences),
335
+ name: row.project || '',
336
+ project: row.project || '',
337
+ naming_style: row.naming_style || '',
338
+ quote_style: row.quote_style || '',
339
+ semicolon_style: row.semicolon_style || '',
340
+ indent_style: row.indent_style || '',
341
+ async_style: row.async_style || '',
342
+ error_handling: row.error_handling || '',
343
+ function_style: row.function_style || '',
344
+ import_style: row.import_style || '',
345
+ export_style: row.export_style || '',
346
+ comment_style: row.comment_style || '',
347
+ architecture_style: row.architecture_style || '',
348
+ test_pattern: row.test_pattern || '',
349
+ top_patterns: safeJson(row.top_patterns),
350
+ strengths: safeJson(row.strengths),
351
+ improvements: safeJson(row.improvements),
352
+ files_scanned: row.files_scanned || 0,
353
+ total_lines: row.total_lines || 0,
354
+ scanned_at: row.scanned_at || '',
90
355
  };
91
356
  }
92
357
  }
@@ -95,17 +360,23 @@ function readProfile(db) {
95
360
  }
96
361
  return {};
97
362
  }
98
- function readBlindSpots(db) {
363
+ function readBlindSpots(db, projectPath) {
99
364
  try {
100
- return db.prepare("SELECT * FROM behavior_events WHERE event_type IN ('blind_spot','repeat_mistake') ORDER BY created_at DESC LIMIT 10").all();
365
+ if (projectPath) {
366
+ return db.prepare("SELECT pattern_type, pattern_value, count, trust_score FROM code_patterns WHERE category = 'blind_spots' AND project = ? ORDER BY trust_score DESC, count DESC LIMIT 10").all(projectPath);
367
+ }
368
+ return db.prepare("SELECT pattern_type, pattern_value, count, trust_score FROM code_patterns WHERE category = 'blind_spots' ORDER BY trust_score DESC, count DESC LIMIT 10").all();
101
369
  }
102
370
  catch {
103
371
  return [];
104
372
  }
105
373
  }
106
- function readTopPatterns(db) {
374
+ function readTopPatterns(db, projectPath) {
107
375
  try {
108
- return db.prepare('SELECT category, rule, confidence, reuse_count FROM patterns ORDER BY confidence DESC, reuse_count DESC LIMIT 15').all();
376
+ if (projectPath) {
377
+ return db.prepare("SELECT pattern_type, pattern_value, category, count, trust_score FROM code_patterns WHERE project = ? AND category != 'blind_spots' ORDER BY trust_score DESC, count DESC LIMIT 15").all(projectPath);
378
+ }
379
+ return db.prepare("SELECT pattern_type, pattern_value, category, count, trust_score FROM code_patterns WHERE category != 'blind_spots' ORDER BY trust_score DESC, count DESC LIMIT 15").all();
109
380
  }
110
381
  catch {
111
382
  return [];
@@ -114,9 +385,9 @@ function readTopPatterns(db) {
114
385
  function readRecentActivity(db, projectPath) {
115
386
  try {
116
387
  if (projectPath) {
117
- return db.prepare('SELECT * FROM signals WHERE project = ? ORDER BY created_at DESC LIMIT 10').all(projectPath);
388
+ return db.prepare('SELECT * FROM scans WHERE project = ? ORDER BY completed_at DESC LIMIT 5').all(projectPath);
118
389
  }
119
- return db.prepare('SELECT * FROM signals ORDER BY created_at DESC LIMIT 10').all();
390
+ return db.prepare('SELECT * FROM scans ORDER BY completed_at DESC LIMIT 5').all();
120
391
  }
121
392
  catch {
122
393
  return [];
@@ -170,9 +441,118 @@ function readPredictions(db, projectPath) {
170
441
  return [];
171
442
  }
172
443
  }
444
+ function readLastSession(db, projectPath) {
445
+ try {
446
+ const query = projectPath
447
+ ? 'SELECT * FROM sessions WHERE project = ? AND end_time IS NOT NULL ORDER BY end_time DESC LIMIT 1'
448
+ : 'SELECT * FROM sessions WHERE end_time IS NOT NULL ORDER BY end_time DESC LIMIT 1';
449
+ const params = projectPath ? [projectPath] : [];
450
+ const row = db.prepare(query).get(...(params));
451
+ if (!row)
452
+ return undefined;
453
+ return {
454
+ id: Number(row.id),
455
+ toolName: String(row.tool_name || ''),
456
+ project: String(row.project || ''),
457
+ startTime: String(row.start_time || ''),
458
+ endTime: row.end_time ? String(row.end_time) : undefined,
459
+ filesTouched: safeJson(String(row.files_touched)) || [],
460
+ keyDecisions: safeJson(String(row.key_decisions)) || [],
461
+ contextSnapshot: String(row.context_snapshot || ''),
462
+ };
463
+ }
464
+ catch {
465
+ return undefined;
466
+ }
467
+ }
468
+ function readTriedApproaches(db, projectPath) {
469
+ try {
470
+ const query = projectPath
471
+ ? `SELECT approach, status, COUNT(*) as count, MAX(reason) as last_reason, MAX(created_at) as last_seen
472
+ FROM session_approaches
473
+ WHERE project = ?
474
+ GROUP BY LOWER(approach)
475
+ ORDER BY count DESC, last_seen DESC
476
+ LIMIT 10`
477
+ : `SELECT approach, status, COUNT(*) as count, MAX(reason) as last_reason, MAX(created_at) as last_seen
478
+ FROM session_approaches
479
+ GROUP BY LOWER(approach)
480
+ ORDER BY count DESC, last_seen DESC
481
+ LIMIT 10`;
482
+ const params = projectPath ? [projectPath] : [];
483
+ const rows = db.prepare(query).all(...(params));
484
+ return rows.map(r => ({
485
+ approach: r.approach,
486
+ status: r.status,
487
+ count: r.count,
488
+ lastReason: r.last_reason,
489
+ lastSeen: r.last_seen,
490
+ }));
491
+ }
492
+ catch {
493
+ return [];
494
+ }
495
+ }
496
+ function readMostTouchedFiles(db, projectPath) {
497
+ try {
498
+ const query = projectPath
499
+ ? `SELECT file_path, SUM(touch_count) as total_touches, COUNT(DISTINCT session_id) as session_count, MAX(last_touched) as last_touched
500
+ FROM session_files
501
+ WHERE project = ?
502
+ GROUP BY file_path
503
+ ORDER BY total_touches DESC, last_touched DESC
504
+ LIMIT 10`
505
+ : `SELECT file_path, SUM(touch_count) as total_touches, COUNT(DISTINCT session_id) as session_count, MAX(last_touched) as last_touched
506
+ FROM session_files
507
+ GROUP BY file_path
508
+ ORDER BY total_touches DESC, last_touched DESC
509
+ LIMIT 10`;
510
+ const params = projectPath ? [projectPath] : [];
511
+ const rows = db.prepare(query).all(...(params));
512
+ return rows.map(r => ({
513
+ filePath: r.file_path,
514
+ touchCount: r.total_touches,
515
+ sessionCount: r.session_count,
516
+ lastTouched: r.last_touched,
517
+ }));
518
+ }
519
+ catch {
520
+ return [];
521
+ }
522
+ }
523
+ function readOpenQuestions(db, projectPath) {
524
+ try {
525
+ const query = projectPath
526
+ ? `SELECT * FROM session_questions
527
+ WHERE project = ? AND resolved = 0
528
+ ORDER BY CASE priority WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, created_at DESC
529
+ LIMIT 10`
530
+ : `SELECT * FROM session_questions
531
+ WHERE resolved = 0
532
+ ORDER BY CASE priority WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, created_at DESC
533
+ LIMIT 10`;
534
+ const params = projectPath ? [projectPath] : [];
535
+ const rows = db.prepare(query).all(...(params));
536
+ return rows.map(r => ({
537
+ id: Number(r.id),
538
+ question: String(r.question || ''),
539
+ priority: String(r.priority || 'medium'),
540
+ resolved: Boolean(r.resolved),
541
+ answer: r.answer ? String(r.answer) : null,
542
+ createdAt: String(r.created_at || ''),
543
+ }));
544
+ }
545
+ catch {
546
+ return [];
547
+ }
548
+ }
173
549
  function buildWhoYouAre(profile, skills, collaborators, stackDNA, blindSpots) {
174
550
  const lines = [];
175
- // Stack fingerprint
551
+ if (profile.project) {
552
+ const files = profile.files_scanned || 0;
553
+ const lines1 = profile.total_lines || 0;
554
+ lines.push(`Project: ${profile.project} (${files} files, ${lines1} lines)`);
555
+ }
176
556
  if (stackDNA && typeof stackDNA === 'object') {
177
557
  const s = stackDNA;
178
558
  const parts = [];
@@ -200,37 +580,76 @@ function buildWhoYouAre(profile, skills, collaborators, stackDNA, blindSpots) {
200
580
  const names = collaborators.slice(0, 3).map((c) => c.name).filter(Boolean);
201
581
  lines.push(`Team: ${names.join(', ')} (${collaborators.length} total)`);
202
582
  }
203
- // Blind spots summary
583
+ const strengths = profile.strengths || [];
584
+ if (strengths.length > 0) {
585
+ lines.push(`Strengths: ${strengths.slice(0, 3).join(' · ')}`);
586
+ }
204
587
  if (blindSpots && blindSpots.length > 0) {
205
- const top = blindSpots.slice(0, 3).map(b => b.pattern_value || b.description || '').filter(Boolean);
588
+ const top = blindSpots.slice(0, 3).map(b => `${b.pattern_value || b.description || 'unknown'} (${b.count || 0}x)`).filter(Boolean);
206
589
  if (top.length > 0)
207
- lines.push(`⚠ ${top.join(' | ')}`);
590
+ lines.push(`⚠ Blind spots: ${top.join(' | ')}`);
208
591
  }
209
592
  if (lines.length === 0) {
210
593
  lines.push('Developer profile — run `cell scan` to build profile');
211
594
  }
212
595
  return lines.join('\n');
213
596
  }
214
- function buildRightNow(activity, profile) {
597
+ function buildRightNow(activity, profile, lastSession) {
215
598
  const lines = [];
216
- if (activity.length > 0) {
217
- const lastSignal = activity[0];
218
- lines.push(`Last activity: ${lastSignal.signal_type || 'unknown'} on ${lastSignal.file_path || 'unknown file'}`);
219
- lines.push(`Last active: ${formatRelativeTime(lastSignal.created_at)}`);
599
+ if (lastSession) {
600
+ const ago = lastSession.endTime ? formatRelativeTime(lastSession.endTime) : 'in progress';
601
+ lines.push(`Last session: ${ago} (${lastSession.toolName})`);
602
+ if (lastSession.contextSnapshot) {
603
+ lines.push(`Working on: ${lastSession.contextSnapshot}`);
604
+ }
605
+ if (lastSession.keyDecisions.length > 0) {
606
+ lines.push(`Decisions: ${lastSession.keyDecisions.slice(0, 3).join(' · ')}`);
607
+ }
608
+ if (lastSession.filesTouched.length > 0) {
609
+ lines.push(`Current file: ${lastSession.filesTouched[0]}`);
610
+ }
611
+ }
612
+ else if (activity.length > 0) {
613
+ const lastScan = activity[0];
614
+ lines.push(`Last scan: ${lastScan.files_scanned || 0} files scanned`);
615
+ if (lastScan.completed_at) {
616
+ lines.push(`Last active: ${formatRelativeTime(lastScan.completed_at)}`);
617
+ }
618
+ if (lastScan.project) {
619
+ lines.push(`Project: ${lastScan.project}`);
620
+ }
220
621
  }
221
622
  else {
222
623
  lines.push('No recent activity detected');
223
624
  lines.push('Run `cell scan` to refresh');
224
625
  }
225
- const products = profile.products || [];
226
- if (products.length > 0) {
227
- const latest = products[products.length - 1];
228
- lines.push(`Latest project: ${latest.name || 'unknown'} ${latest.latestVersion ? '(' + latest.latestVersion + ')' : ''}`);
626
+ if (profile.scanned_at) {
627
+ lines.push(`Profile last built: ${formatRelativeTime(profile.scanned_at)}`);
229
628
  }
230
629
  return lines.join('\n');
231
630
  }
232
631
  function buildYourStyle(patterns, profile, skills) {
233
632
  const lines = [];
633
+ const styleBits = [];
634
+ if (profile.quote_style)
635
+ styleBits.push(`Quotes: ${profile.quote_style}`);
636
+ if (profile.semicolon_style)
637
+ styleBits.push(`Semicolons: ${profile.semicolon_style}`);
638
+ if (profile.indent_style)
639
+ styleBits.push(`Indent: ${profile.indent_style}`);
640
+ if (profile.naming_style)
641
+ styleBits.push(`Naming: ${profile.naming_style}`);
642
+ if (profile.async_style)
643
+ styleBits.push(`Async: ${profile.async_style}`);
644
+ if (profile.error_handling)
645
+ styleBits.push(`Errors: ${profile.error_handling}`);
646
+ if (profile.function_style)
647
+ styleBits.push(`Functions: ${profile.function_style}`);
648
+ if (profile.test_pattern)
649
+ styleBits.push(`Tests: ${profile.test_pattern}`);
650
+ if (styleBits.length > 0) {
651
+ lines.push(styleBits.join(' · '));
652
+ }
234
653
  const categories = new Map();
235
654
  for (const p of patterns) {
236
655
  const cat = String(p.category || '');
@@ -240,65 +659,65 @@ function buildYourStyle(patterns, profile, skills) {
240
659
  if (sortedCats.length > 0) {
241
660
  lines.push(`Top patterns: ${sortedCats.map(([c, n]) => `${c}(${n})`).join(', ')}`);
242
661
  }
243
- const topRules = patterns.slice(0, 5).map((p) => p.rule).filter(Boolean);
244
- if (topRules.length > 0) {
245
- lines.push('Style preferences:');
246
- for (const r of topRules) {
247
- lines.push(` - ${r}`);
248
- }
249
- }
250
662
  const doList = [];
251
- const dontList = [];
252
663
  for (const p of patterns) {
253
- const rule = String(p.rule || '');
664
+ const rule = String(p.pattern_value || '');
254
665
  const cat = String(p.category || '');
255
- if (cat === 'typescript_strict' || rule.includes('TypeScript') || rule.includes('type')) {
666
+ if (cat === 'typescript_strict' || rule.toLowerCase().includes('type')) {
256
667
  doList.push('DO: TypeScript strict mode');
257
668
  }
258
- if (cat === 'async_await' || rule.includes('async')) {
669
+ if (cat === 'async' || rule.toLowerCase().includes('async') || profile.async_style === 'uses-async') {
259
670
  doList.push('DO: Use async/await, not .then()');
260
671
  }
261
- if (cat === 'null_safety' || rule.includes('optional chaining')) {
672
+ if (cat === 'null_safety' || rule.toLowerCase().includes('optional')) {
262
673
  doList.push('DO: Use optional chaining');
263
674
  }
264
- if (cat === 'destructuring' || rule.includes('destructuring')) {
675
+ if (cat === 'destructuring' || rule.toLowerCase().includes('destructur')) {
265
676
  doList.push('DO: Use destructuring over index access');
266
677
  }
267
678
  }
268
- const unique = new Set(doList);
269
- if (unique.size > 0)
270
- lines.push([...unique].join('\n'));
271
- dontList.push('DON\'T: Suggest MongoDB (uses PostgreSQL)');
272
- dontList.push('DON\'T: Suggest large monolithic functions');
273
- dontList.push('DON\'T: Suggest var over const/let');
679
+ const unique = [...new Set(doList)];
680
+ if (unique.length > 0) {
681
+ lines.push('');
682
+ lines.push('DO list:');
683
+ unique.forEach(d => lines.push(' ' + d));
684
+ }
685
+ lines.push('');
686
+ lines.push("DON'T list:");
687
+ lines.push(' - Use var (use const/let)');
688
+ lines.push(' - Skip try/catch on async');
689
+ lines.push(' - Skip null checks on API responses');
274
690
  return lines.join('\n');
275
691
  }
276
692
  function buildWatchOut(blindSpots, profile) {
277
693
  const lines = [];
278
- const spots = blindSpots.filter((b) => b.severity === 'high' || b.severity === 'critical');
279
- if (spots.length === 0 && blindSpots.length === 0) {
280
- lines.push('Run `cell scan` to detect blind spots');
281
- lines.push('Common checks: error handling, null safety, test coverage');
694
+ if (blindSpots.length === 0) {
695
+ lines.push('No blind spots detected yet.');
696
+ lines.push('Run `cell scan` to detect error handling, null safety, and validation gaps');
282
697
  return lines.join('\n');
283
698
  }
284
- lines.push('Top blind spots:');
699
+ lines.push(`Top blind spots (${blindSpots.length} total):`);
285
700
  const top = blindSpots.slice(0, 5);
286
701
  for (const s of top) {
287
- const desc = String(s.description || '');
702
+ const desc = String(s.pattern_value || s.description || 'unknown');
703
+ const count = s.count || 0;
704
+ const trust = s.trust_score || 0;
288
705
  if (desc)
289
- lines.push(` - [${s.severity || 'unknown'}] ${desc}`);
706
+ lines.push(` - [${count}x, trust ${trust}] ${desc}`);
707
+ }
708
+ if (profile.improvements && Array.isArray(profile.improvements) && profile.improvements.length > 0) {
709
+ lines.push('');
710
+ lines.push('Suggested improvements:');
711
+ const improvements = profile.improvements;
712
+ improvements.slice(0, 3).forEach((imp) => lines.push(' - ' + imp));
290
713
  }
291
- lines.push('Always flag if:');
292
- lines.push(' - async function missing try/catch');
293
- lines.push(' - API response used without null check');
294
- lines.push(' - Database operation without validation');
295
714
  return lines.join('\n');
296
715
  }
297
716
  function buildPredictions(blindSpots, activity, toolStats) {
298
717
  const lines = [];
299
- const highSeverity = blindSpots.filter((b) => b.severity === 'high' || b.severity === 'critical').length;
300
- if (highSeverity > 2) {
301
- lines.push(`⚠ ${highSeverity} high-severity blind spots detected`);
718
+ const highTrustBlindSpots = blindSpots.filter((b) => b.trust_score >= 80).length;
719
+ if (highTrustBlindSpots > 0) {
720
+ lines.push(`⚠ ${highTrustBlindSpots} high-confidence blind spots likely to recur`);
302
721
  }
303
722
  if (toolStats) {
304
723
  const acceptRate = toolStats.acceptance_rate;
@@ -310,9 +729,9 @@ function buildPredictions(blindSpots, activity, toolStats) {
310
729
  }
311
730
  }
312
731
  if (activity.length > 0) {
313
- const lastType = String(activity[0].signal_type || '');
314
- if (lastType === 'reject' || lastType === 'retry') {
315
- lines.push('⚠ Last action was rejected/retried — be more careful with suggestions');
732
+ const lastScan = activity[0];
733
+ if (lastScan.completed_at) {
734
+ lines.push(`Last scan: ${formatRelativeTime(lastScan.completed_at)}`);
316
735
  }
317
736
  }
318
737
  if (lines.length === 0) {
@@ -361,4 +780,58 @@ function safeJson(val) {
361
780
  return val;
362
781
  }
363
782
  }
783
+ exports.DEFAULT_STALE_DAYS = 7;
784
+ exports.DEFAULT_EXPIRED_DAYS = 30;
785
+ function getStalenessStatus(timestamp, staleDays = exports.DEFAULT_STALE_DAYS, expiredDays = exports.DEFAULT_EXPIRED_DAYS) {
786
+ if (!timestamp)
787
+ return 'unknown';
788
+ const t = new Date(timestamp).getTime();
789
+ if (!Number.isFinite(t))
790
+ return 'unknown';
791
+ const ageMs = Date.now() - t;
792
+ if (ageMs < 0)
793
+ return 'fresh';
794
+ const days = ageMs / (1000 * 60 * 60 * 24);
795
+ if (days < staleDays)
796
+ return 'fresh';
797
+ if (days < expiredDays)
798
+ return 'stale';
799
+ return 'expired';
800
+ }
801
+ function daysSince(timestamp) {
802
+ if (!timestamp)
803
+ return null;
804
+ const t = new Date(timestamp).getTime();
805
+ if (!Number.isFinite(t))
806
+ return null;
807
+ const days = (Date.now() - t) / (1000 * 60 * 60 * 24);
808
+ return Math.max(0, Math.round(days * 10) / 10);
809
+ }
810
+ function buildStalenessInfo(profile, recentActivity) {
811
+ const profileTs = profile.scanned_at || undefined;
812
+ const scanTs = recentActivity[0]?.completed_at || undefined;
813
+ const profileLevel = getStalenessStatus(profileTs);
814
+ const scanLevel = getStalenessStatus(scanTs);
815
+ const daysSinceProfile = daysSince(profileTs);
816
+ const daysSinceScan = daysSince(scanTs);
817
+ const shouldRescan = profileLevel === 'stale' || profileLevel === 'expired' || scanLevel === 'stale' || scanLevel === 'expired';
818
+ let warning = null;
819
+ if (profileLevel === 'expired') {
820
+ warning = `Profile is ${daysSinceProfile ?? '?'} days old — run \`cell scan\` to refresh (context may be outdated)`;
821
+ }
822
+ else if (profileLevel === 'stale') {
823
+ warning = `Profile is ${daysSinceProfile ?? '?'} days old — consider running \`cell scan\` to keep context fresh`;
824
+ }
825
+ else if (scanLevel === 'expired' || scanLevel === 'stale') {
826
+ warning = `Last scan is ${daysSinceScan ?? '?'} days old — run \`cell scan\` to refresh`;
827
+ }
828
+ return {
829
+ profile: profileLevel,
830
+ scan: scanLevel,
831
+ daysSinceProfile,
832
+ daysSinceScan,
833
+ shouldRescan,
834
+ warning,
835
+ };
836
+ }
364
837
  //# sourceMappingURL=prompt-builder.js.map