Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, screen, shell } = require('electron');
const { Worker } = require('worker_threads');

Check warning on line 2 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'Worker' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 2 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'Worker' is assigned a value but never used. Allowed unused vars must match /^_/u
const { execFile } = require('child_process');
const path = require('path');
const fs = require('fs');
Expand All @@ -16,7 +16,7 @@
}

// getFolderIndexMtimeMs moved to session-cache.js
const { startMcpServer, shutdownMcpServer, shutdownAll: shutdownAllMcp, resolvePendingDiff, rekeyMcpServer, cleanStaleLockFiles } = require('./mcp-bridge');

Check warning on line 19 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'cleanStaleLockFiles' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 19 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'cleanStaleLockFiles' is assigned a value but never used. Allowed unused vars must match /^_/u
const { fetchAndTransformUsage } = require('./claude-auth');
log.transports.file.level = app.isPackaged ? 'info' : 'debug';
log.transports.console.level = app.isPackaged ? 'info' : 'debug';
Expand All @@ -36,7 +36,7 @@
);

// Shell profiles → shell-profiles.js
const { discoverShellProfiles, getShellProfiles, resolveShell, isWindows, isWslShell, windowsToWslPath, shellArgs } = require('./shell-profiles');

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'isWindows' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'discoverShellProfiles' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'isWindows' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 39 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'discoverShellProfiles' is assigned a value but never used. Allowed unused vars must match /^_/u
const { startScheduler } = require('./schedule-runner');
const { encodeProjectPath } = require('./encode-project-path');

Expand Down Expand Up @@ -72,8 +72,8 @@
const {
getMeta, getAllMeta, toggleStar, setName, setArchived,
isCachePopulated, getAllCached, getCachedByFolder, getCachedByParent, getCachedFolder, getCachedSession, upsertCachedSessions,
deleteCachedSession, deleteCachedFolder,
deleteCachedSession, deleteCachedFolder, replaceSessionMetrics, touchCachedModified,
getFolderMeta, getAllFolderMeta, setFolderMeta,

Check warning on line 76 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'getFolderMeta' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 76 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'getFolderMeta' is assigned a value but never used. Allowed unused vars must match /^_/u
upsertSearchEntries, updateSearchTitle, deleteSearchSession, deleteSearchFolder, deleteSearchType,
searchByType, isSearchIndexPopulated, searchFtsRecreated,
getSetting, setSetting, deleteSetting,
Expand Down Expand Up @@ -284,13 +284,13 @@
getMainWindow: () => mainWindow,
log,
db: {
deleteCachedFolder, getCachedByFolder, upsertCachedSessions, deleteCachedSession,
deleteCachedFolder, getCachedByFolder, upsertCachedSessions, deleteCachedSession, replaceSessionMetrics, touchCachedModified,
deleteSearchFolder, deleteSearchSession, upsertSearchEntries,
setFolderMeta, getAllFolderMeta, getAllMeta, getAllCached, getSetting, getMeta, setName,
},
});
const { readSessionFile, readFolderFromFilesystem, refreshFolder, populateCacheFromFilesystem,

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'populateCacheFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'readFolderFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'readSessionFile' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'populateCacheFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'readFolderFromFilesystem' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 292 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'readSessionFile' is assigned a value but never used. Allowed unused vars must match /^_/u
buildProjectsFromCache, notifyRendererProjectsChanged, sendStatus, populateCacheViaWorker } = sessionCache;

Check warning on line 293 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'sendStatus' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 293 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'sendStatus' is assigned a value but never used. Allowed unused vars must match /^_/u
const { resolveJsonlPath, enumerateSessionFiles } = require('./read-session-file');


Expand Down Expand Up @@ -1369,7 +1369,7 @@
// WSL profiles only work for plain terminals — Claude CLI sessions need the
// Windows shell because session data lives on the Windows filesystem.
const requestedProfile = resolveShell(effectiveProfileId);
const useWslProfile = isWslShell(requestedProfile.path) && isPlainTerminal;

Check warning on line 1372 in main.js

View workflow job for this annotation

GitHub Actions / test (20)

'useWslProfile' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 1372 in main.js

View workflow job for this annotation

GitHub Actions / test (22)

'useWslProfile' is assigned a value but never used. Allowed unused vars must match /^_/u
const shellProfile = (isWslShell(requestedProfile.path) && !isPlainTerminal)
? resolveShell('auto')
: requestedProfile;
Expand Down
89 changes: 89 additions & 0 deletions test/main-ctx-db-wiring.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');

// Regression guard for a class of silent runtime bug: session-cache.js consumes
// DB functions through `ctx.db.<name>`, but main.js hand-builds the `db: { ... }`
// object passed to sessionCache.init() as an explicit allow-list. When a new DB
// function is wired into session-cache but NOT added to that literal, the call
// site gets `undefined` and throws at runtime — inside the worker's message
// handler, where the throw lands on stderr (not electron-log) and silently
// aborts the cold-start indexing loop. Witnessed 2026-06-02: replaceSessionMetrics
// (and the pre-existing touchCachedModified) were missing → session_metrics never
// populated → the stats screen showed 0 tokens / 0 tool calls. No unit test booted
// main.js's init wiring, so task check stayed green. This static check closes that gap.

const root = path.join(__dirname, '..');

function read(f) {
return fs.readFileSync(path.join(root, f), 'utf8');
}

// Extract the keys of the `db: { ... }` object literal handed to sessionCache.init.
function mainCtxDbKeys() {
const src = read('main.js');
const start = src.indexOf('db: {');
assert.ok(start !== -1, 'main.js should pass a db: { ... } object to sessionCache.init');
// Walk to the matching closing brace of the literal.
const open = src.indexOf('{', start);
let depth = 0, end = -1;
for (let i = open; i < src.length; i++) {
if (src[i] === '{') depth++;
else if (src[i] === '}') { depth--; if (depth === 0) { end = i; break; } }
}
assert.ok(end !== -1, 'db: { ... } literal should be balanced');
const body = src.slice(open + 1, end);
// Shorthand keys: bare identifiers separated by commas/newlines (ignore any value parts).
return new Set(
body
.split(',')
.map(s => s.trim().split(':')[0].trim())
.filter(Boolean)
.filter(k => /^[A-Za-z_$][\w$]*$/.test(k))
);
}

// Every ctx.db.<name> that session-cache.js dereferences.
function sessionCacheDbDeps() {
const src = read('session-cache.js');
const deps = new Set();
const re = /ctx\.db\.([A-Za-z_$][\w$]*)/g;
let m;
while ((m = re.exec(src))) deps.add(m[1]);
return deps;
}

// Pure static analysis only — does NOT require('../db'), because better-sqlite3 is
// compiled against Electron's Node ABI and throws when loaded under plain node:test
// (same constraint db-daily-activity.test.js documents).

test('main.js ctx.db allow-list covers every ctx.db.* session-cache.js reads', () => {
const provided = mainCtxDbKeys();
const required = sessionCacheDbDeps();
const missing = [...required].filter(name => !provided.has(name));
assert.deepEqual(
missing,
[],
`main.js db: {} is missing functions session-cache.js needs: ${missing.join(', ')}`
);
});

test('ctx.db forwards the two functions from the 2026-06-02 stats incident', () => {
const provided = mainCtxDbKeys();
assert.ok(provided.has('replaceSessionMetrics'), 'ctx.db must forward replaceSessionMetrics');
assert.ok(provided.has('touchCachedModified'), 'ctx.db must forward touchCachedModified');
});

// db.js must actually export those names too (a name forwarded in main.js but not
// exported by db.js is undefined just the same). Checked by source-grep, not require,
// to stay native-module-free.
test('db.js exports replaceSessionMetrics and touchCachedModified', () => {
const dbSrc = read('db.js');
for (const name of ['replaceSessionMetrics', 'touchCachedModified']) {
assert.ok(
new RegExp(`(^|[^\\w])${name}([^\\w]|$)`, 'm').test(dbSrc.split('module.exports')[1] || ''),
`db.js module.exports should include ${name}`
);
}
});
Loading