heyiam 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/db.js CHANGED
@@ -5,6 +5,7 @@ import crypto from 'node:crypto';
5
5
  import { mkdirSync, statSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
+ import { projectDirFromPath } from './format-utils.js';
8
9
  // ── Constants ────────────────────────────────────────────────
9
10
  function getDataDir() {
10
11
  return process.env.HEYIAM_DATA_DIR || join(homedir(), '.local', 'share', 'heyiam');
@@ -12,7 +13,7 @@ function getDataDir() {
12
13
  export function getDbPath() {
13
14
  return join(getDataDir(), 'sessions.db');
14
15
  }
15
- const CURRENT_SCHEMA_VERSION = 5;
16
+ const CURRENT_SCHEMA_VERSION = 6;
16
17
  // ── Singleton ────────────────────────────────────────────────
17
18
  let _db = null;
18
19
  export function getDatabase(dbPath = getDbPath()) {
@@ -53,6 +54,9 @@ function runMigrations(db) {
53
54
  if (currentVersion < 5) {
54
55
  migrateToV5(db);
55
56
  }
57
+ if (currentVersion < 6) {
58
+ migrateToV6(db);
59
+ }
56
60
  }
57
61
  function migrateToV2(db) {
58
62
  // Drop old tables if upgrading from v1
@@ -169,14 +173,48 @@ function migrateToV4(db) {
169
173
  });
170
174
  tx();
171
175
  }
176
+ /**
177
+ * One-time repair: backfill rows where project_dir drifted from file_path.
178
+ *
179
+ * Claude Code v2.1+ relocates a session to a new project dir when the cwd
180
+ * changes mid-session. Earlier versions of this CLI healed file_path in
181
+ * isolation, leaving project_dir pointing at the original directory and
182
+ * making sessions appear under the wrong project in the UI.
183
+ */
184
+ function migrateToV6(db) {
185
+ const tx = db.transaction(() => {
186
+ const rows = db.prepare('SELECT id, file_path, is_subagent, project_dir FROM sessions WHERE file_path IS NOT NULL').all();
187
+ const update = db.prepare('UPDATE sessions SET project_dir = ? WHERE id = ?');
188
+ let healed = 0;
189
+ for (const r of rows) {
190
+ if (r.file_path.includes('://'))
191
+ continue;
192
+ const derived = projectDirFromPath(r.file_path, r.is_subagent === 1);
193
+ if (derived && derived !== r.project_dir) {
194
+ update.run(derived, r.id);
195
+ healed++;
196
+ }
197
+ }
198
+ if (healed > 0) {
199
+ console.log('[migrate v6] Repaired project_dir for ' + healed + ' session(s)');
200
+ }
201
+ db.prepare('UPDATE schema_version SET version = ?').run(6);
202
+ });
203
+ tx();
204
+ }
172
205
  // ── Staleness Check ──────────────────────────────────────────
173
206
  export function isSessionStale(db, sessionId, filePath) {
174
- const row = db.prepare('SELECT file_mtime, file_size FROM sessions WHERE id = ?').get(sessionId);
207
+ const row = db.prepare('SELECT file_mtime, file_size, file_path FROM sessions WHERE id = ?').get(sessionId);
175
208
  if (!row)
176
209
  return true; // Not in DB — needs indexing
177
210
  // Skip stat for non-filesystem paths (e.g. cursor:// URLs) — F23 fix
178
211
  if (filePath.includes('://'))
179
212
  return true;
213
+ // Claude Code v2.1+ renames a session file when cwd changes mid-session.
214
+ // A rename preserves mtime/size, so without this check the row's stale
215
+ // project_dir would never get refreshed.
216
+ if (row.file_path && row.file_path !== filePath)
217
+ return true;
180
218
  try {
181
219
  const stat = statSync(filePath);
182
220
  // F21: Use Math.floor to avoid floating-point comparison issues
@@ -379,9 +417,19 @@ export function deleteSession(db, sessionId) {
379
417
  * we discover the originally-indexed path has been deleted by its source
380
418
  * tool (e.g., Claude Code's 30-day cleanup) and we've fallen back to an
381
419
  * archived copy.
420
+ *
421
+ * Also re-derives project_dir from the new path. Claude Code v2.1+ relocates
422
+ * sessions when the cwd changes mid-session, and project_dir is a denormalized
423
+ * cache of dirname(file_path) — they must move together or the UI groups
424
+ * sessions under their old project.
382
425
  */
383
426
  export function updateSessionPath(db, sessionId, newPath) {
384
- db.prepare('UPDATE sessions SET file_path = ? WHERE id = ?').run(newPath, sessionId);
427
+ const row = db.prepare('SELECT is_subagent FROM sessions WHERE id = ?').get(sessionId);
428
+ if (!row)
429
+ return;
430
+ const projectDir = projectDirFromPath(newPath, row.is_subagent === 1);
431
+ db.prepare('UPDATE sessions SET file_path = ?, project_dir = ? WHERE id = ?')
432
+ .run(newPath, projectDir, sessionId);
385
433
  }
386
434
  // ── Rebuild Index ────────────────────────────────────────────
387
435
  export function rebuildIndex(db, onProgress) {
@@ -1,7 +1,26 @@
1
+ import { basename, dirname } from 'node:path';
1
2
  /** Escape HTML special characters for safe embedding. */
2
3
  export function escapeHtml(str) {
3
4
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4
5
  }
6
+ /**
7
+ * Derive the project_dir segment from a Claude Code session file path.
8
+ *
9
+ * Layouts:
10
+ * .../projects/{projectDir}/{sessionId}.jsonl — parent session
11
+ * .../projects/{projectDir}/{parentSessionId}/subagents/X.jsonl — subagent
12
+ *
13
+ * Claude Code v2.1+ relocates session files when cwd changes mid-session,
14
+ * so we need to re-derive project_dir from the current file path rather than
15
+ * trusting a value stored at first-index time.
16
+ */
17
+ export function projectDirFromPath(filePath, isSubagent) {
18
+ if (isSubagent) {
19
+ const sessionDataDir = dirname(dirname(filePath));
20
+ return basename(dirname(sessionDataDir));
21
+ }
22
+ return basename(dirname(filePath));
23
+ }
5
24
  /** Format LOC as human-readable (e.g. "2.4k"). */
6
25
  export function formatLoc(loc) {
7
26
  if (loc >= 1000)
package/dist/sync.js CHANGED
@@ -12,7 +12,7 @@ import { analyzeSession } from './analyzer.js';
12
12
  import { isSessionStale, indexSession, rebuildIndex, optimizeFtsIndex, } from './db.js';
13
13
  import { renderCompact } from './context-export.js';
14
14
  import { getArchiveDir } from './settings.js';
15
- import { displayNameFromDir } from './format-utils.js';
15
+ import { displayNameFromDir, projectDirFromPath } from './format-utils.js';
16
16
  export { displayNameFromDir } from './format-utils.js';
17
17
  let _syncState = {
18
18
  status: 'idle',
@@ -216,18 +216,8 @@ async function handleFileChange(db, filePath) {
216
216
  const parentDirName = basename(dirname(filePath));
217
217
  // Detect subagent: path contains /subagents/ directory
218
218
  const isSubagent = parentDirName === 'subagents';
219
- let projectDir;
220
- let parentSessionId;
221
- if (isSubagent) {
222
- // .../projects/{projectDir}/{parentSessionId}/subagents/{agentId}.jsonl
223
- const sessionDataDir = dirname(dirname(filePath)); // up past subagents/
224
- parentSessionId = basename(sessionDataDir);
225
- projectDir = basename(dirname(sessionDataDir));
226
- }
227
- else {
228
- // .../projects/{projectDir}/{sessionId}.jsonl
229
- projectDir = parentDirName;
230
- }
219
+ const projectDir = projectDirFromPath(filePath, isSubagent);
220
+ const parentSessionId = isSubagent ? basename(dirname(dirname(filePath))) : undefined;
231
221
  const projectName = displayNameFromDir(projectDir);
232
222
  const meta = {
233
223
  path: filePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyiam",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Turn AI coding sessions into portfolio case studies",
5
5
  "type": "module",
6
6
  "license": "MIT",