fivocell 3.1.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) 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 +302 -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 +132 -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/ai-bridge.d.ts +20 -0
  106. package/dist/ai-bridge.d.ts.map +1 -0
  107. package/dist/ai-bridge.js +250 -0
  108. package/dist/ai-bridge.js.map +1 -0
  109. package/dist/behavior-intelligence.d.ts.map +1 -1
  110. package/dist/behavior-intelligence.js +12 -1
  111. package/dist/behavior-intelligence.js.map +1 -1
  112. package/dist/cli.js +501 -4
  113. package/dist/cli.js.map +1 -1
  114. package/dist/code-scanner.d.ts.map +1 -1
  115. package/dist/code-scanner.js +426 -69
  116. package/dist/code-scanner.js.map +1 -1
  117. package/dist/core/community-store.d.ts +128 -0
  118. package/dist/core/community-store.d.ts.map +1 -0
  119. package/dist/core/community-store.js +329 -0
  120. package/dist/core/community-store.js.map +1 -0
  121. package/dist/core/database.d.ts +0 -3
  122. package/dist/core/database.d.ts.map +1 -1
  123. package/dist/core/database.js +287 -15
  124. package/dist/core/database.js.map +1 -1
  125. package/dist/core/enhanced-blind-spots.d.ts +27 -0
  126. package/dist/core/enhanced-blind-spots.d.ts.map +1 -0
  127. package/dist/core/enhanced-blind-spots.js +591 -0
  128. package/dist/core/enhanced-blind-spots.js.map +1 -0
  129. package/dist/core/knowledge-graph-store.d.ts +69 -0
  130. package/dist/core/knowledge-graph-store.d.ts.map +1 -0
  131. package/dist/core/knowledge-graph-store.js +269 -0
  132. package/dist/core/knowledge-graph-store.js.map +1 -0
  133. package/dist/core/live-watcher.d.ts +52 -0
  134. package/dist/core/live-watcher.d.ts.map +1 -0
  135. package/dist/core/live-watcher.js +369 -0
  136. package/dist/core/live-watcher.js.map +1 -0
  137. package/dist/core/project-registry.d.ts +24 -0
  138. package/dist/core/project-registry.d.ts.map +1 -0
  139. package/dist/core/project-registry.js +70 -0
  140. package/dist/core/project-registry.js.map +1 -0
  141. package/dist/core/prompt-builder.d.ts +50 -0
  142. package/dist/core/prompt-builder.d.ts.map +1 -1
  143. package/dist/core/prompt-builder.js +533 -79
  144. package/dist/core/prompt-builder.js.map +1 -1
  145. package/dist/core/security.d.ts +23 -0
  146. package/dist/core/security.d.ts.map +1 -0
  147. package/dist/core/security.js +117 -0
  148. package/dist/core/security.js.map +1 -0
  149. package/dist/core/session-memory.d.ts +64 -0
  150. package/dist/core/session-memory.d.ts.map +1 -1
  151. package/dist/core/session-memory.js +111 -0
  152. package/dist/core/session-memory.js.map +1 -1
  153. package/dist/core/usage-intelligence-store.d.ts +126 -0
  154. package/dist/core/usage-intelligence-store.d.ts.map +1 -0
  155. package/dist/core/usage-intelligence-store.js +405 -0
  156. package/dist/core/usage-intelligence-store.js.map +1 -0
  157. package/dist/daemon/server.d.ts.map +1 -1
  158. package/dist/daemon/server.js +936 -17
  159. package/dist/daemon/server.js.map +1 -1
  160. package/dist/knowledge-graph-builder.d.ts +16 -0
  161. package/dist/knowledge-graph-builder.d.ts.map +1 -0
  162. package/dist/knowledge-graph-builder.js +186 -0
  163. package/dist/knowledge-graph-builder.js.map +1 -0
  164. package/dist/pc-scanner.d.ts +1 -0
  165. package/dist/pc-scanner.d.ts.map +1 -1
  166. package/dist/pc-scanner.js +58 -30
  167. package/dist/pc-scanner.js.map +1 -1
  168. package/dist/stack-detector.d.ts +34 -0
  169. package/dist/stack-detector.d.ts.map +1 -0
  170. package/dist/stack-detector.js +471 -0
  171. package/dist/stack-detector.js.map +1 -0
  172. package/dist/team-intel.d.ts +31 -0
  173. package/dist/team-intel.d.ts.map +1 -0
  174. package/dist/team-intel.js +310 -0
  175. package/dist/team-intel.js.map +1 -0
  176. package/package.json +1 -1
@@ -1,33 +1,49 @@
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
- const whoYouAre = buildWhoYouAre(profile, skills, collaborators);
18
- const rightNow = buildRightNow(recentActivity, profile);
20
+ const stackDNA = readStackDNA(db, projectPath);
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);
26
+ const whoYouAre = buildWhoYouAre(profile, skills, collaborators, stackDNA, blindSpots);
27
+ const rightNow = buildRightNow(recentActivity, profile, lastSession);
19
28
  const yourStyle = buildYourStyle(topPatterns, profile, skills);
20
29
  const watchOut = buildWatchOut(blindSpots, profile);
21
30
  const predictions = buildPredictions(blindSpots, recentActivity, toolStats);
22
31
  const toolHint = toolName ? buildToolHint(toolName) : undefined;
32
+ const staleness = buildStalenessInfo(profile, recentActivity);
23
33
  return {
24
34
  whoYouAre,
25
35
  rightNow,
26
36
  yourStyle,
27
37
  watchOut,
28
38
  predictions,
39
+ dbPredictions,
29
40
  injectedAt: new Date().toISOString(),
30
41
  toolHint,
42
+ lastSession,
43
+ triedApproaches,
44
+ mostTouchedFiles,
45
+ openQuestions,
46
+ staleness,
31
47
  };
32
48
  }
33
49
  function formatContextForInjection(ctx, compact = false) {
@@ -36,20 +52,77 @@ function formatContextForInjection(ctx, compact = false) {
36
52
  }
37
53
  return fullContext(ctx);
38
54
  }
55
+ function formatContextForTool(ctx, toolName) {
56
+ if (!toolName)
57
+ return fullContext(ctx);
58
+ const t = toolName.toLowerCase().trim();
59
+ if (t === 'claude-code' || t === 'claude' || t === 'claude-code-cli') {
60
+ return fullContext(ctx);
61
+ }
62
+ if (t === 'cursor' || t === 'cursor-ide' || t === 'cursor-ai') {
63
+ return formatForCursor(ctx);
64
+ }
65
+ if (t === 'windsurf' || t === 'codeium' || t === 'windsurf-ide') {
66
+ return formatForWindsurf(ctx);
67
+ }
68
+ if (t === 'copilot' || t === 'github-copilot') {
69
+ return formatForCopilot(ctx);
70
+ }
71
+ if (t === 'antigravity' || t === 'aider' || t === 'continue-dev') {
72
+ return formatForAntigravity(ctx);
73
+ }
74
+ return fullContext(ctx);
75
+ }
39
76
  function estimateTokens(text) {
40
77
  return Math.ceil(text.length / 4);
41
78
  }
42
79
  function fullContext(ctx) {
43
80
  let out = '';
44
- out += '═══ CELL CONTEXT ═══\n\n';
81
+ out += '━━━ CELL CONTEXT ━━━\n\n';
82
+ if (ctx.staleness?.warning) {
83
+ out += '⚠ ' + ctx.staleness.warning + '\n\n';
84
+ }
45
85
  out += '▸ WHO YOU ARE\n';
46
86
  out += ctx.whoYouAre + '\n\n';
47
87
  out += '▸ RIGHT NOW\n';
48
88
  out += ctx.rightNow + '\n\n';
89
+ if (ctx.lastSession) {
90
+ const ago = ctx.lastSession.endTime ? formatRelativeTime(ctx.lastSession.endTime) : 'active';
91
+ out += '▸ LAST SESSION (' + ago + ')\n';
92
+ out += `Tool: ${ctx.lastSession.toolName}\n`;
93
+ if (ctx.lastSession.contextSnapshot) {
94
+ out += `Working on: ${ctx.lastSession.contextSnapshot}\n`;
95
+ }
96
+ if (ctx.lastSession.keyDecisions.length > 0) {
97
+ out += 'Key decisions: ' + ctx.lastSession.keyDecisions.slice(0, 3).join(' · ') + '\n';
98
+ }
99
+ out += '\n';
100
+ }
101
+ if (ctx.triedApproaches.length > 0) {
102
+ out += '▸ TRIED ALREADY (don\'t suggest these)\n';
103
+ for (const a of ctx.triedApproaches.slice(0, 5)) {
104
+ out += `→ ${a.approach} — ${a.status}${a.count > 1 ? ` (${a.count}x)` : ''}\n`;
105
+ }
106
+ out += '\n';
107
+ }
108
+ if (ctx.openQuestions.length > 0) {
109
+ out += '▸ OPEN QUESTIONS\n';
110
+ for (const q of ctx.openQuestions.slice(0, 5)) {
111
+ out += `→ [${q.priority}] ${q.question}\n`;
112
+ }
113
+ out += '\n';
114
+ }
49
115
  out += '▸ YOUR STYLE\n';
50
116
  out += ctx.yourStyle + '\n\n';
51
117
  out += '▸ WATCH OUT\n';
52
118
  out += ctx.watchOut + '\n\n';
119
+ if (ctx.mostTouchedFiles.length > 0) {
120
+ out += '▸ HOT FILES (recently active)\n';
121
+ for (const f of ctx.mostTouchedFiles.slice(0, 5)) {
122
+ out += `→ ${f.filePath} (${f.touchCount} touches, ${f.sessionCount} sessions)\n`;
123
+ }
124
+ out += '\n';
125
+ }
53
126
  out += '▸ PREDICTIONS\n';
54
127
  out += ctx.predictions;
55
128
  if (ctx.toolHint) {
@@ -59,10 +132,21 @@ function fullContext(ctx) {
59
132
  }
60
133
  function compactContext(ctx) {
61
134
  let out = '';
135
+ if (ctx.staleness?.warning) {
136
+ out += '⚠ ' + ctx.staleness.warning + '\n\n';
137
+ }
62
138
  out += '## Developer Context\n';
63
139
  out += ctx.whoYouAre.replace(/\n/g, ' | ') + '\n\n';
64
140
  out += '### Current\n';
65
141
  out += ctx.rightNow.replace(/\n/g, ' | ') + '\n\n';
142
+ if (ctx.triedApproaches.length > 0) {
143
+ out += '### Tried (skip these)\n';
144
+ out += ctx.triedApproaches.slice(0, 3).map(a => `${a.approach}(${a.status})`).join(' | ') + '\n\n';
145
+ }
146
+ if (ctx.openQuestions.length > 0) {
147
+ out += '### Open Questions\n';
148
+ out += ctx.openQuestions.slice(0, 3).map(q => `[${q.priority}] ${q.question}`).join(' | ') + '\n\n';
149
+ }
66
150
  out += '### Style\n';
67
151
  out += ctx.yourStyle.replace(/\n/g, ' | ') + '\n\n';
68
152
  out += '### Blind Spots\n';
@@ -74,16 +158,142 @@ function compactContext(ctx) {
74
158
  }
75
159
  return out;
76
160
  }
77
- function readProfile(db) {
161
+ function formatForCursor(ctx) {
162
+ const lines = [];
163
+ lines.push('@context fivo-cell');
164
+ if (ctx.staleness?.warning)
165
+ lines.push('! ' + ctx.staleness.warning);
166
+ if (ctx.lastSession) {
167
+ lines.push('last: ' + ctx.lastSession.toolName + ' (' + (ctx.lastSession.endTime ? formatRelativeTime(ctx.lastSession.endTime) : 'active') + ')');
168
+ if (ctx.lastSession.contextSnapshot)
169
+ lines.push('working: ' + ctx.lastSession.contextSnapshot);
170
+ }
171
+ lines.push('');
172
+ lines.push('style: ' + (ctx.yourStyle.split('\n')[0] || ''));
173
+ if (ctx.triedApproaches.length > 0) {
174
+ lines.push('avoid: ' + ctx.triedApproaches.slice(0, 3).map(a => `${a.approach}[${a.status}]`).join(', '));
175
+ }
176
+ if (ctx.openQuestions.length > 0) {
177
+ lines.push('q: ' + ctx.openQuestions.slice(0, 2).map(q => `${q.question.slice(0, 50)}…`).join(' | '));
178
+ }
179
+ if (ctx.mostTouchedFiles.length > 0) {
180
+ lines.push('hot: ' + ctx.mostTouchedFiles.slice(0, 3).map(f => f.filePath).join(', '));
181
+ }
182
+ if (ctx.watchOut) {
183
+ const topBlindSpot = ctx.watchOut.split('\n').find(l => l.trim().startsWith('-'));
184
+ if (topBlindSpot)
185
+ lines.push('watch: ' + topBlindSpot.replace(/^-\s*/, '').slice(0, 80));
186
+ }
187
+ return lines.join('\n');
188
+ }
189
+ function formatForWindsurf(ctx) {
190
+ const lines = [];
191
+ lines.push('# Cell Context');
192
+ if (ctx.staleness?.warning)
193
+ lines.push('> ⚠ ' + ctx.staleness.warning);
194
+ if (ctx.lastSession) {
195
+ lines.push('## Last Session');
196
+ lines.push(`- **Tool**: ${ctx.lastSession.toolName}`);
197
+ if (ctx.lastSession.contextSnapshot)
198
+ lines.push(`- **Working on**: ${ctx.lastSession.contextSnapshot}`);
199
+ if (ctx.lastSession.keyDecisions.length > 0)
200
+ lines.push(`- **Decisions**: ${ctx.lastSession.keyDecisions.slice(0, 2).join(', ')}`);
201
+ }
202
+ if (ctx.openQuestions.length > 0) {
203
+ lines.push('## Open Questions');
204
+ for (const q of ctx.openQuestions.slice(0, 3)) {
205
+ lines.push(`- [${q.priority}] ${q.question}`);
206
+ }
207
+ }
208
+ lines.push('## Style');
209
+ lines.push(ctx.yourStyle.split('\n')[0] || '');
210
+ lines.push('## Suggestions');
211
+ if (ctx.watchOut) {
212
+ const spots = ctx.watchOut.split('\n').filter(l => l.trim().startsWith('-')).slice(0, 3);
213
+ spots.forEach(s => lines.push('- ' + s.replace(/^-\s*/, '')));
214
+ }
215
+ if (ctx.mostTouchedFiles.length > 0) {
216
+ lines.push('## Active Files');
217
+ ctx.mostTouchedFiles.slice(0, 3).forEach(f => lines.push(`- ${f.filePath} (${f.touchCount} touches)`));
218
+ }
219
+ return lines.join('\n');
220
+ }
221
+ function formatForCopilot(ctx) {
222
+ const parts = [];
223
+ if (ctx.staleness?.warning)
224
+ parts.push('⚠ ' + ctx.staleness.warning.replace(/`/g, ''));
225
+ parts.push('Project: ' + (ctx.lastSession?.project || 'unknown'));
226
+ if (ctx.lastSession?.contextSnapshot)
227
+ parts.push('Working: ' + ctx.lastSession.contextSnapshot.slice(0, 60));
228
+ if (ctx.triedApproaches.length > 0) {
229
+ const a = ctx.triedApproaches[0];
230
+ parts.push(`Avoid: ${a.approach} (${a.status})`);
231
+ }
232
+ const styleLine = ctx.yourStyle.split('\n')[0] || '';
233
+ if (styleLine)
234
+ parts.push('Style: ' + styleLine.slice(0, 80));
235
+ return parts.join(' · ');
236
+ }
237
+ function formatForAntigravity(ctx) {
238
+ const lines = [];
239
+ lines.push('## Cell Context — Structured');
240
+ if (ctx.staleness?.warning)
241
+ lines.push('> ⚠ ' + ctx.staleness.warning);
242
+ lines.push('');
243
+ lines.push('```yaml');
244
+ lines.push(`project: ${ctx.lastSession?.project || 'unknown'}`);
245
+ lines.push(`tool_hint: ${ctx.toolHint || 'general'}`);
246
+ if (ctx.lastSession) {
247
+ lines.push(`last_session:`);
248
+ lines.push(` tool: ${ctx.lastSession.toolName}`);
249
+ lines.push(` when: ${ctx.lastSession.endTime || 'active'}`);
250
+ if (ctx.lastSession.contextSnapshot)
251
+ lines.push(` context: "${ctx.lastSession.contextSnapshot}"`);
252
+ }
253
+ if (ctx.triedApproaches.length > 0) {
254
+ lines.push(`tried_approaches:`);
255
+ ctx.triedApproaches.slice(0, 3).forEach(a => {
256
+ lines.push(` - approach: "${a.approach}"`);
257
+ lines.push(` status: ${a.status}`);
258
+ });
259
+ }
260
+ if (ctx.openQuestions.length > 0) {
261
+ lines.push(`open_questions:`);
262
+ ctx.openQuestions.slice(0, 3).forEach(q => {
263
+ lines.push(` - priority: ${q.priority}`);
264
+ lines.push(` question: "${q.question}"`);
265
+ });
266
+ }
267
+ lines.push('```');
268
+ return lines.join('\n');
269
+ }
270
+ function readProfile(db, projectPath) {
78
271
  try {
79
- const row = db.prepare('SELECT * FROM user_profile WHERE id = 1').get();
272
+ const row = projectPath
273
+ ? db.prepare('SELECT * FROM developer_profiles WHERE project = ? ORDER BY scanned_at DESC LIMIT 1').get(projectPath)
274
+ : db.prepare('SELECT * FROM developer_profiles ORDER BY scanned_at DESC LIMIT 1').get();
80
275
  if (row) {
81
276
  return {
82
- name: row.name || '',
83
- products: safeJson(row.products),
84
- stack: safeJson(row.stack),
85
- workStyle: safeJson(row.work_style),
86
- preferences: safeJson(row.preferences),
277
+ name: row.project || '',
278
+ project: row.project || '',
279
+ naming_style: row.naming_style || '',
280
+ quote_style: row.quote_style || '',
281
+ semicolon_style: row.semicolon_style || '',
282
+ indent_style: row.indent_style || '',
283
+ async_style: row.async_style || '',
284
+ error_handling: row.error_handling || '',
285
+ function_style: row.function_style || '',
286
+ import_style: row.import_style || '',
287
+ export_style: row.export_style || '',
288
+ comment_style: row.comment_style || '',
289
+ architecture_style: row.architecture_style || '',
290
+ test_pattern: row.test_pattern || '',
291
+ top_patterns: safeJson(row.top_patterns),
292
+ strengths: safeJson(row.strengths),
293
+ improvements: safeJson(row.improvements),
294
+ files_scanned: row.files_scanned || 0,
295
+ total_lines: row.total_lines || 0,
296
+ scanned_at: row.scanned_at || '',
87
297
  };
88
298
  }
89
299
  }
@@ -92,17 +302,23 @@ function readProfile(db) {
92
302
  }
93
303
  return {};
94
304
  }
95
- function readBlindSpots(db) {
305
+ function readBlindSpots(db, projectPath) {
96
306
  try {
97
- return db.prepare("SELECT * FROM behavior_events WHERE event_type IN ('blind_spot','repeat_mistake') ORDER BY created_at DESC LIMIT 10").all();
307
+ if (projectPath) {
308
+ 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);
309
+ }
310
+ 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();
98
311
  }
99
312
  catch {
100
313
  return [];
101
314
  }
102
315
  }
103
- function readTopPatterns(db) {
316
+ function readTopPatterns(db, projectPath) {
104
317
  try {
105
- return db.prepare('SELECT category, rule, confidence, reuse_count FROM patterns ORDER BY confidence DESC, reuse_count DESC LIMIT 15').all();
318
+ if (projectPath) {
319
+ 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);
320
+ }
321
+ 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();
106
322
  }
107
323
  catch {
108
324
  return [];
@@ -111,9 +327,9 @@ function readTopPatterns(db) {
111
327
  function readRecentActivity(db, projectPath) {
112
328
  try {
113
329
  if (projectPath) {
114
- return db.prepare('SELECT * FROM signals WHERE project = ? ORDER BY created_at DESC LIMIT 10').all(projectPath);
330
+ return db.prepare('SELECT * FROM scans WHERE project = ? ORDER BY completed_at DESC LIMIT 5').all(projectPath);
115
331
  }
116
- return db.prepare('SELECT * FROM signals ORDER BY created_at DESC LIMIT 10').all();
332
+ return db.prepare('SELECT * FROM scans ORDER BY completed_at DESC LIMIT 5').all();
117
333
  }
118
334
  catch {
119
335
  return [];
@@ -143,21 +359,161 @@ function readToolStats(db, toolName) {
143
359
  return null;
144
360
  }
145
361
  }
146
- function buildWhoYouAre(profile, skills, collaborators) {
362
+ function readStackDNA(db, projectPath) {
363
+ try {
364
+ const query = projectPath
365
+ ? "SELECT pattern_value FROM code_patterns WHERE pattern_type = 'project_dna' AND project = ?"
366
+ : "SELECT pattern_value FROM code_patterns WHERE pattern_type = 'project_dna'";
367
+ const params = projectPath ? [projectPath] : [];
368
+ const row = db.prepare(query).get(...(params));
369
+ if (row?.pattern_value)
370
+ return JSON.parse(row.pattern_value);
371
+ return null;
372
+ }
373
+ catch {
374
+ return null;
375
+ }
376
+ }
377
+ function readPredictions(db, projectPath) {
378
+ try {
379
+ const errors = db.prepare("SELECT error_message, COUNT(*) as cnt FROM error_log GROUP BY error_message HAVING cnt > 1 ORDER BY cnt DESC LIMIT 5").all();
380
+ return errors.map(e => `${e.error_message} — ${e.cnt}x repeated`);
381
+ }
382
+ catch {
383
+ return [];
384
+ }
385
+ }
386
+ function readLastSession(db, projectPath) {
387
+ try {
388
+ const query = projectPath
389
+ ? 'SELECT * FROM sessions WHERE project = ? AND end_time IS NOT NULL ORDER BY end_time DESC LIMIT 1'
390
+ : 'SELECT * FROM sessions WHERE end_time IS NOT NULL ORDER BY end_time DESC LIMIT 1';
391
+ const params = projectPath ? [projectPath] : [];
392
+ const row = db.prepare(query).get(...(params));
393
+ if (!row)
394
+ return undefined;
395
+ return {
396
+ id: Number(row.id),
397
+ toolName: String(row.tool_name || ''),
398
+ project: String(row.project || ''),
399
+ startTime: String(row.start_time || ''),
400
+ endTime: row.end_time ? String(row.end_time) : undefined,
401
+ filesTouched: safeJson(String(row.files_touched)) || [],
402
+ keyDecisions: safeJson(String(row.key_decisions)) || [],
403
+ contextSnapshot: String(row.context_snapshot || ''),
404
+ };
405
+ }
406
+ catch {
407
+ return undefined;
408
+ }
409
+ }
410
+ function readTriedApproaches(db, projectPath) {
411
+ try {
412
+ const query = projectPath
413
+ ? `SELECT approach, status, COUNT(*) as count, MAX(reason) as last_reason, MAX(created_at) as last_seen
414
+ FROM session_approaches
415
+ WHERE project = ?
416
+ GROUP BY LOWER(approach)
417
+ ORDER BY count DESC, last_seen DESC
418
+ LIMIT 10`
419
+ : `SELECT approach, status, COUNT(*) as count, MAX(reason) as last_reason, MAX(created_at) as last_seen
420
+ FROM session_approaches
421
+ GROUP BY LOWER(approach)
422
+ ORDER BY count DESC, last_seen DESC
423
+ LIMIT 10`;
424
+ const params = projectPath ? [projectPath] : [];
425
+ const rows = db.prepare(query).all(...(params));
426
+ return rows.map(r => ({
427
+ approach: r.approach,
428
+ status: r.status,
429
+ count: r.count,
430
+ lastReason: r.last_reason,
431
+ lastSeen: r.last_seen,
432
+ }));
433
+ }
434
+ catch {
435
+ return [];
436
+ }
437
+ }
438
+ function readMostTouchedFiles(db, projectPath) {
439
+ try {
440
+ const query = projectPath
441
+ ? `SELECT file_path, SUM(touch_count) as total_touches, COUNT(DISTINCT session_id) as session_count, MAX(last_touched) as last_touched
442
+ FROM session_files
443
+ WHERE project = ?
444
+ GROUP BY file_path
445
+ ORDER BY total_touches DESC, last_touched DESC
446
+ LIMIT 10`
447
+ : `SELECT file_path, SUM(touch_count) as total_touches, COUNT(DISTINCT session_id) as session_count, MAX(last_touched) as last_touched
448
+ FROM session_files
449
+ GROUP BY file_path
450
+ ORDER BY total_touches DESC, last_touched DESC
451
+ LIMIT 10`;
452
+ const params = projectPath ? [projectPath] : [];
453
+ const rows = db.prepare(query).all(...(params));
454
+ return rows.map(r => ({
455
+ filePath: r.file_path,
456
+ touchCount: r.total_touches,
457
+ sessionCount: r.session_count,
458
+ lastTouched: r.last_touched,
459
+ }));
460
+ }
461
+ catch {
462
+ return [];
463
+ }
464
+ }
465
+ function readOpenQuestions(db, projectPath) {
466
+ try {
467
+ const query = projectPath
468
+ ? `SELECT * FROM session_questions
469
+ WHERE project = ? AND resolved = 0
470
+ ORDER BY CASE priority WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, created_at DESC
471
+ LIMIT 10`
472
+ : `SELECT * FROM session_questions
473
+ WHERE resolved = 0
474
+ ORDER BY CASE priority WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, created_at DESC
475
+ LIMIT 10`;
476
+ const params = projectPath ? [projectPath] : [];
477
+ const rows = db.prepare(query).all(...(params));
478
+ return rows.map(r => ({
479
+ id: Number(r.id),
480
+ question: String(r.question || ''),
481
+ priority: String(r.priority || 'medium'),
482
+ resolved: Boolean(r.resolved),
483
+ answer: r.answer ? String(r.answer) : null,
484
+ createdAt: String(r.created_at || ''),
485
+ }));
486
+ }
487
+ catch {
488
+ return [];
489
+ }
490
+ }
491
+ function buildWhoYouAre(profile, skills, collaborators, stackDNA, blindSpots) {
147
492
  const lines = [];
148
- const products = profile.products || [];
149
- if (products.length > 0) {
150
- const names = products.map((p) => p.name).filter(Boolean);
151
- lines.push(`Products: ${names.join(', ')}`);
152
- const mainStack = products.find((p) => Array.isArray(p.commonStack) && p.commonStack.length > 0);
153
- if (mainStack) {
154
- const stackItems = mainStack.commonStack
155
- .slice(0, 5)
156
- .map((s) => s.name)
157
- .filter(Boolean);
158
- if (stackItems.length > 0)
159
- lines.push(`Stack: ${stackItems.join(', ')}`);
160
- }
493
+ if (profile.project) {
494
+ const files = profile.files_scanned || 0;
495
+ const lines1 = profile.total_lines || 0;
496
+ lines.push(`Project: ${profile.project} (${files} files, ${lines1} lines)`);
497
+ }
498
+ if (stackDNA && typeof stackDNA === 'object') {
499
+ const s = stackDNA;
500
+ const parts = [];
501
+ if (s.languages && s.languages.length > 0)
502
+ parts.push(`Languages: ${s.languages.join(', ')}`);
503
+ if (s.frontend && s.frontend !== 'none')
504
+ parts.push(`Frontend: ${s.frontend}`);
505
+ if (s.backend && s.backend !== 'none')
506
+ parts.push(`Backend: ${s.backend}`);
507
+ if (s.database && s.database[0] !== 'none')
508
+ parts.push(`DB: ${s.database.join(', ')}`);
509
+ if (s.orm && s.orm !== 'none')
510
+ parts.push(`ORM: ${s.orm}`);
511
+ if (s.testing && s.testing[0] !== 'none')
512
+ parts.push(`Test: ${s.testing.join(', ')}`);
513
+ if (s.validation && s.validation !== 'none')
514
+ parts.push(`Validation: ${s.validation}`);
515
+ if (parts.length > 0)
516
+ lines.push(parts.join(' · '));
161
517
  }
162
518
  const skillLanguages = skills.filter((s) => s.level !== 'learning' && s.level !== 'new').map((s) => s.language);
163
519
  if (skillLanguages.length > 0)
@@ -166,32 +522,76 @@ function buildWhoYouAre(profile, skills, collaborators) {
166
522
  const names = collaborators.slice(0, 3).map((c) => c.name).filter(Boolean);
167
523
  lines.push(`Team: ${names.join(', ')} (${collaborators.length} total)`);
168
524
  }
525
+ const strengths = profile.strengths || [];
526
+ if (strengths.length > 0) {
527
+ lines.push(`Strengths: ${strengths.slice(0, 3).join(' · ')}`);
528
+ }
529
+ if (blindSpots && blindSpots.length > 0) {
530
+ const top = blindSpots.slice(0, 3).map(b => `${b.pattern_value || b.description || 'unknown'} (${b.count || 0}x)`).filter(Boolean);
531
+ if (top.length > 0)
532
+ lines.push(`⚠ Blind spots: ${top.join(' | ')}`);
533
+ }
169
534
  if (lines.length === 0) {
170
535
  lines.push('Developer profile — run `cell scan` to build profile');
171
- lines.push('Stack: TypeScript, Node.js, React, Express');
172
536
  }
173
537
  return lines.join('\n');
174
538
  }
175
- function buildRightNow(activity, profile) {
539
+ function buildRightNow(activity, profile, lastSession) {
176
540
  const lines = [];
177
- if (activity.length > 0) {
178
- const lastSignal = activity[0];
179
- lines.push(`Last activity: ${lastSignal.signal_type || 'unknown'} on ${lastSignal.file_path || 'unknown file'}`);
180
- lines.push(`Last active: ${formatRelativeTime(lastSignal.created_at)}`);
541
+ if (lastSession) {
542
+ const ago = lastSession.endTime ? formatRelativeTime(lastSession.endTime) : 'in progress';
543
+ lines.push(`Last session: ${ago} (${lastSession.toolName})`);
544
+ if (lastSession.contextSnapshot) {
545
+ lines.push(`Working on: ${lastSession.contextSnapshot}`);
546
+ }
547
+ if (lastSession.keyDecisions.length > 0) {
548
+ lines.push(`Decisions: ${lastSession.keyDecisions.slice(0, 3).join(' · ')}`);
549
+ }
550
+ if (lastSession.filesTouched.length > 0) {
551
+ lines.push(`Current file: ${lastSession.filesTouched[0]}`);
552
+ }
553
+ }
554
+ else if (activity.length > 0) {
555
+ const lastScan = activity[0];
556
+ lines.push(`Last scan: ${lastScan.files_scanned || 0} files scanned`);
557
+ if (lastScan.completed_at) {
558
+ lines.push(`Last active: ${formatRelativeTime(lastScan.completed_at)}`);
559
+ }
560
+ if (lastScan.project) {
561
+ lines.push(`Project: ${lastScan.project}`);
562
+ }
181
563
  }
182
564
  else {
183
565
  lines.push('No recent activity detected');
184
566
  lines.push('Run `cell scan` to refresh');
185
567
  }
186
- const products = profile.products || [];
187
- if (products.length > 0) {
188
- const latest = products[products.length - 1];
189
- lines.push(`Latest project: ${latest.name || 'unknown'} ${latest.latestVersion ? '(' + latest.latestVersion + ')' : ''}`);
568
+ if (profile.scanned_at) {
569
+ lines.push(`Profile last built: ${formatRelativeTime(profile.scanned_at)}`);
190
570
  }
191
571
  return lines.join('\n');
192
572
  }
193
573
  function buildYourStyle(patterns, profile, skills) {
194
574
  const lines = [];
575
+ const styleBits = [];
576
+ if (profile.quote_style)
577
+ styleBits.push(`Quotes: ${profile.quote_style}`);
578
+ if (profile.semicolon_style)
579
+ styleBits.push(`Semicolons: ${profile.semicolon_style}`);
580
+ if (profile.indent_style)
581
+ styleBits.push(`Indent: ${profile.indent_style}`);
582
+ if (profile.naming_style)
583
+ styleBits.push(`Naming: ${profile.naming_style}`);
584
+ if (profile.async_style)
585
+ styleBits.push(`Async: ${profile.async_style}`);
586
+ if (profile.error_handling)
587
+ styleBits.push(`Errors: ${profile.error_handling}`);
588
+ if (profile.function_style)
589
+ styleBits.push(`Functions: ${profile.function_style}`);
590
+ if (profile.test_pattern)
591
+ styleBits.push(`Tests: ${profile.test_pattern}`);
592
+ if (styleBits.length > 0) {
593
+ lines.push(styleBits.join(' · '));
594
+ }
195
595
  const categories = new Map();
196
596
  for (const p of patterns) {
197
597
  const cat = String(p.category || '');
@@ -201,65 +601,65 @@ function buildYourStyle(patterns, profile, skills) {
201
601
  if (sortedCats.length > 0) {
202
602
  lines.push(`Top patterns: ${sortedCats.map(([c, n]) => `${c}(${n})`).join(', ')}`);
203
603
  }
204
- const topRules = patterns.slice(0, 5).map((p) => p.rule).filter(Boolean);
205
- if (topRules.length > 0) {
206
- lines.push('Style preferences:');
207
- for (const r of topRules) {
208
- lines.push(` - ${r}`);
209
- }
210
- }
211
604
  const doList = [];
212
- const dontList = [];
213
605
  for (const p of patterns) {
214
- const rule = String(p.rule || '');
606
+ const rule = String(p.pattern_value || '');
215
607
  const cat = String(p.category || '');
216
- if (cat === 'typescript_strict' || rule.includes('TypeScript') || rule.includes('type')) {
608
+ if (cat === 'typescript_strict' || rule.toLowerCase().includes('type')) {
217
609
  doList.push('DO: TypeScript strict mode');
218
610
  }
219
- if (cat === 'async_await' || rule.includes('async')) {
611
+ if (cat === 'async' || rule.toLowerCase().includes('async') || profile.async_style === 'uses-async') {
220
612
  doList.push('DO: Use async/await, not .then()');
221
613
  }
222
- if (cat === 'null_safety' || rule.includes('optional chaining')) {
614
+ if (cat === 'null_safety' || rule.toLowerCase().includes('optional')) {
223
615
  doList.push('DO: Use optional chaining');
224
616
  }
225
- if (cat === 'destructuring' || rule.includes('destructuring')) {
617
+ if (cat === 'destructuring' || rule.toLowerCase().includes('destructur')) {
226
618
  doList.push('DO: Use destructuring over index access');
227
619
  }
228
620
  }
229
- const unique = new Set(doList);
230
- if (unique.size > 0)
231
- lines.push([...unique].join('\n'));
232
- dontList.push('DON\'T: Suggest MongoDB (uses PostgreSQL)');
233
- dontList.push('DON\'T: Suggest large monolithic functions');
234
- dontList.push('DON\'T: Suggest var over const/let');
621
+ const unique = [...new Set(doList)];
622
+ if (unique.length > 0) {
623
+ lines.push('');
624
+ lines.push('DO list:');
625
+ unique.forEach(d => lines.push(' ' + d));
626
+ }
627
+ lines.push('');
628
+ lines.push("DON'T list:");
629
+ lines.push(' - Use var (use const/let)');
630
+ lines.push(' - Skip try/catch on async');
631
+ lines.push(' - Skip null checks on API responses');
235
632
  return lines.join('\n');
236
633
  }
237
634
  function buildWatchOut(blindSpots, profile) {
238
635
  const lines = [];
239
- const spots = blindSpots.filter((b) => b.severity === 'high' || b.severity === 'critical');
240
- if (spots.length === 0 && blindSpots.length === 0) {
241
- lines.push('Run `cell scan` to detect blind spots');
242
- lines.push('Common checks: error handling, null safety, test coverage');
636
+ if (blindSpots.length === 0) {
637
+ lines.push('No blind spots detected yet.');
638
+ lines.push('Run `cell scan` to detect error handling, null safety, and validation gaps');
243
639
  return lines.join('\n');
244
640
  }
245
- lines.push('Top blind spots:');
641
+ lines.push(`Top blind spots (${blindSpots.length} total):`);
246
642
  const top = blindSpots.slice(0, 5);
247
643
  for (const s of top) {
248
- const desc = String(s.description || '');
644
+ const desc = String(s.pattern_value || s.description || 'unknown');
645
+ const count = s.count || 0;
646
+ const trust = s.trust_score || 0;
249
647
  if (desc)
250
- lines.push(` - [${s.severity || 'unknown'}] ${desc}`);
648
+ lines.push(` - [${count}x, trust ${trust}] ${desc}`);
649
+ }
650
+ if (profile.improvements && Array.isArray(profile.improvements) && profile.improvements.length > 0) {
651
+ lines.push('');
652
+ lines.push('Suggested improvements:');
653
+ const improvements = profile.improvements;
654
+ improvements.slice(0, 3).forEach((imp) => lines.push(' - ' + imp));
251
655
  }
252
- lines.push('Always flag if:');
253
- lines.push(' - async function missing try/catch');
254
- lines.push(' - API response used without null check');
255
- lines.push(' - Database operation without validation');
256
656
  return lines.join('\n');
257
657
  }
258
658
  function buildPredictions(blindSpots, activity, toolStats) {
259
659
  const lines = [];
260
- const highSeverity = blindSpots.filter((b) => b.severity === 'high' || b.severity === 'critical').length;
261
- if (highSeverity > 2) {
262
- lines.push(`⚠ ${highSeverity} high-severity blind spots detected`);
660
+ const highTrustBlindSpots = blindSpots.filter((b) => b.trust_score >= 80).length;
661
+ if (highTrustBlindSpots > 0) {
662
+ lines.push(`⚠ ${highTrustBlindSpots} high-confidence blind spots likely to recur`);
263
663
  }
264
664
  if (toolStats) {
265
665
  const acceptRate = toolStats.acceptance_rate;
@@ -271,9 +671,9 @@ function buildPredictions(blindSpots, activity, toolStats) {
271
671
  }
272
672
  }
273
673
  if (activity.length > 0) {
274
- const lastType = String(activity[0].signal_type || '');
275
- if (lastType === 'reject' || lastType === 'retry') {
276
- lines.push('⚠ Last action was rejected/retried — be more careful with suggestions');
674
+ const lastScan = activity[0];
675
+ if (lastScan.completed_at) {
676
+ lines.push(`Last scan: ${formatRelativeTime(lastScan.completed_at)}`);
277
677
  }
278
678
  }
279
679
  if (lines.length === 0) {
@@ -322,4 +722,58 @@ function safeJson(val) {
322
722
  return val;
323
723
  }
324
724
  }
725
+ exports.DEFAULT_STALE_DAYS = 7;
726
+ exports.DEFAULT_EXPIRED_DAYS = 30;
727
+ function getStalenessStatus(timestamp, staleDays = exports.DEFAULT_STALE_DAYS, expiredDays = exports.DEFAULT_EXPIRED_DAYS) {
728
+ if (!timestamp)
729
+ return 'unknown';
730
+ const t = new Date(timestamp).getTime();
731
+ if (!Number.isFinite(t))
732
+ return 'unknown';
733
+ const ageMs = Date.now() - t;
734
+ if (ageMs < 0)
735
+ return 'fresh';
736
+ const days = ageMs / (1000 * 60 * 60 * 24);
737
+ if (days < staleDays)
738
+ return 'fresh';
739
+ if (days < expiredDays)
740
+ return 'stale';
741
+ return 'expired';
742
+ }
743
+ function daysSince(timestamp) {
744
+ if (!timestamp)
745
+ return null;
746
+ const t = new Date(timestamp).getTime();
747
+ if (!Number.isFinite(t))
748
+ return null;
749
+ const days = (Date.now() - t) / (1000 * 60 * 60 * 24);
750
+ return Math.max(0, Math.round(days * 10) / 10);
751
+ }
752
+ function buildStalenessInfo(profile, recentActivity) {
753
+ const profileTs = profile.scanned_at || undefined;
754
+ const scanTs = recentActivity[0]?.completed_at || undefined;
755
+ const profileLevel = getStalenessStatus(profileTs);
756
+ const scanLevel = getStalenessStatus(scanTs);
757
+ const daysSinceProfile = daysSince(profileTs);
758
+ const daysSinceScan = daysSince(scanTs);
759
+ const shouldRescan = profileLevel === 'stale' || profileLevel === 'expired' || scanLevel === 'stale' || scanLevel === 'expired';
760
+ let warning = null;
761
+ if (profileLevel === 'expired') {
762
+ warning = `Profile is ${daysSinceProfile ?? '?'} days old — run \`cell scan\` to refresh (context may be outdated)`;
763
+ }
764
+ else if (profileLevel === 'stale') {
765
+ warning = `Profile is ${daysSinceProfile ?? '?'} days old — consider running \`cell scan\` to keep context fresh`;
766
+ }
767
+ else if (scanLevel === 'expired' || scanLevel === 'stale') {
768
+ warning = `Last scan is ${daysSinceScan ?? '?'} days old — run \`cell scan\` to refresh`;
769
+ }
770
+ return {
771
+ profile: profileLevel,
772
+ scan: scanLevel,
773
+ daysSinceProfile,
774
+ daysSinceScan,
775
+ shouldRescan,
776
+ warning,
777
+ };
778
+ }
325
779
  //# sourceMappingURL=prompt-builder.js.map