Codemod db pass-through calls to domain modules: tasks + agents (C1 part 1/3)

Rewrite ~525 call sites from `db.<method>()` to `db.<module>.<method>()`
for tasks and agents domains (including `getAgent`/`findAgent`/`getTask`
which live on the `entityCrud` field). Delete the corresponding
pass-through methods on AppDatabaseBase. Structural types that narrowed
`db` to a bag of flat methods (CapabilityMatchDb, EscalationDeps,
SweepStaleReviewsDeps) are reshaped to match the new namespace. Internal
callback references inside database.ts/database-base.ts now invoke the
owning module directly (e.g. `this.agents.saveAgent`) rather than the
deleted pass-through.

The codemod lives at scripts/codemod/remove-appdatabase-passthrough.mjs
and filters by owning module + optional explicit method list. It is
idempotent and does not re-rewrite already-namespaced calls.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 13:17:01 -04:00
1 parent 411294afa6
commit 5e9b7acbc6
95 files changed
+1123 -793

No files matched your search

@@ -0,0 +1,412 @@
#!/usr/bin/env node
/**
* Codemod: rewrite `db.<passthroughMethod>(` → `db.<module>.<passthroughMethod>(`
* for all AppDatabase pass-through methods removed by the C1 refactor.
*
* Regex-based (no ts-morph dep). Operates on a fixed method→module map
* derived mechanically from src/main/database.ts and
* src/main/database-base.ts. Only rewrites `db.<method>(...)` with a
* word-boundary before `db`; never touches `db.<submodule>.<method>(...)`.
*
* Usage:
* node scripts/codemod/remove-appdatabase-passthrough.mjs \
* --modules=tasks,agents \
* [--dry-run]
*
* The --modules flag restricts rewrites to passthroughs owned by those
* sub-module fields; omitting it rewrites every passthrough in the map.
*/
import { readFileSync, writeFileSync } from "node:fs";
import { globSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { execSync } from "node:child_process";
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, "..", "..");
// Canonical map: passthrough method name → AppDatabaseBase field name
// Source: src/main/database.ts + src/main/database-base.ts as of C1 start.
const METHOD_TO_MODULE = {
// activityMod
listComments: "activityMod",
addActivity: "activityMod",
addComment: "activityMod",
listRecentTaskComments: "activityMod",
// messages
sendAgentMessage: "messages",
getAgentMessage: "messages",
listAgentMessages: "messages",
searchMessages: "messages",
markMessageReadForReader: "messages",
listAgentInboxUnread: "messages",
// social
saveSocialAccount: "social",
deleteSocialAccount: "social",
listSocialAccounts: "social",
getSocialAccount: "social",
saveBrowserAction: "social",
updateBrowserAction: "social",
listBrowserActions: "social",
getBrowserAction: "social",
getBrowserActionByApprovalId: "social",
// skills
upsertCompanySkill: "skills",
listCompanySkills: "skills",
getCompanySkill: "skills",
deleteCompanySkill: "skills",
assignSkillToAgent: "skills",
removeSkillFromAgent: "skills",
listAgentSkillAssignments: "skills",
listSkillAgentAssignments: "skills",
updateCompanySkillMetadata: "skills",
// budget
saveBudgetPolicy: "budget",
deleteBudgetPolicy: "budget",
listBudgetPolicies: "budget",
getBudgetPolicy: "budget",
getActivePoliciesForRun: "budget",
getCalendarMonthWindow: "budget",
getSpentInWindow: "budget",
upsertBudgetIncident: "budget",
listBudgetIncidents: "budget",
resolveBudgetIncident: "budget",
resolveIncidentsForPolicy: "budget",
resolveWarnIncidentsForPolicy: "budget",
autoCloseExpiredIncidents: "budget",
// entityCrud
saveGoal: "entityCrud",
listGoals: "entityCrud",
getGoalForUpdate: "entityCrud",
saveProject: "entityCrud",
listProjects: "entityCrud",
getProjectForUpdate: "entityCrud",
saveSecret: "entityCrud",
resolveSecretEnv: "entityCrud",
deleteGoal: "entityCrud",
deleteProject: "entityCrud",
deleteSecret: "entityCrud",
countActiveGoalsForCompany: "entityCrud",
createHireRequest: "entityCrud",
// documentsMod
saveDocument: "documentsMod",
listImportantDocsForReview: "documentsMod",
listDocuments: "documentsMod",
getDocumentForUpdate: "documentsMod",
updateDocumentStatus: "documentsMod",
deleteDocument: "documentsMod",
createDocumentRevision: "documentsMod",
getDocumentLatestRevisionId: "documentsMod",
listDocumentRevisions: "documentsMod",
createWorkProduct: "documentsMod",
updateWorkProduct: "documentsMod",
listWorkProductsForTask: "documentsMod",
listWorkProductsForCompany: "documentsMod",
findWorkProductByExternalId: "documentsMod",
resolveWorkProductTarget: "documentsMod",
deleteWorkProduct: "documentsMod",
archiveWorkProductsByExternalPaths: "documentsMod",
linkDocumentToTask: "documentsMod",
unlinkDocumentFromTask: "documentsMod",
listDocumentsForTask: "documentsMod",
listConfigRevisions: "documentsMod",
getConfigRevision: "documentsMod",
rollbackConfigRevision: "documentsMod",
recordConfigRevision: "documentsMod",
// knowledgeMod
saveMeeting: "knowledgeMod",
deleteMeeting: "knowledgeMod",
listMeetings: "knowledgeMod",
getMeeting: "knowledgeMod",
saveKnowledgeEntry: "knowledgeMod",
deleteKnowledgeEntry: "knowledgeMod",
listKnowledgeEntries: "knowledgeMod",
saveSprint: "knowledgeMod",
deleteSprint: "knowledgeMod",
listSprints: "knowledgeMod",
collectRunDeliverableSignals: "knowledgeMod",
// automationMod
saveAutomationRule: "automationMod",
deleteAutomationRule: "automationMod",
listAutomationRuleNames: "automationMod",
toggleAutomationRule: "automationMod",
recordAutomationExecution: "automationMod",
getAutomationLog: "automationMod",
listActiveAutomationRules: "automationMod",
saveWorkflow: "automationMod",
getWorkflowRecord: "automationMod",
updateWorkflowExecution: "automationMod",
deleteWorkflow: "automationMod",
listTriggerableWorkflows: "automationMod",
// wakeup
enqueueWakeupRequest: "wakeup",
promoteWakeupRequests: "wakeup",
listWakeupRequests: "wakeup",
getPreferredWakeTrigger: "wakeup",
completePendingWakeupRequests: "wakeup",
purgeExpiredWakeupRequests: "wakeup",
// backupMod
exportBackup: "backupMod",
createAutomaticBackup: "backupMod",
restoreFromBackup: "backupMod",
// threads
createThread: "threads",
listThreads: "threads",
getThread: "threads",
addThreadEvent: "threads",
listThreadEvents: "threads",
setThreadWaitState: "threads",
resolveThreadWaitState: "threads",
listThreadWaitStates: "threads",
hasUnresolvedWaitStateForAgent: "threads",
addParticipant: "threads",
listParticipants: "threads",
removeParticipant: "threads",
subscribe: "threads",
unsubscribe: "threads",
listSubscriptions: "threads",
listSubscribedThreads: "threads",
// settings
getCurrentCompanyId: "settings",
setCurrentCompany: "settings",
hasCompany: "settings",
belongsToCompany: "settings",
updateSettings: "settings",
getLogRetentionDays: "settings",
purgeExpiredRunLogs: "settings",
canOpenPath: "settings",
getDisabledModels: "settings",
setDisabledModels: "settings",
hasModelPoolSettings: "settings",
getLastBudgetResetMonth: "settings",
setLastBudgetResetMonth: "settings",
// snap
listSnapshot: "snap",
listRendererSnapshot: "snap",
// company
saveCompany: "company",
deleteCompany: "company",
getCompanyStatus: "company",
getCompanyBasic: "company",
getCompanyExecutionMode: "company",
getCompanyApprovalSettings: "company",
listActiveCompanyIds: "company",
getCompanyMetrics: "company",
getStandupReport: "company",
getDashboardSnapshot: "company",
// connectorsCrud
saveConnector: "connectorsCrud",
updateConnectorHealth: "connectorsCrud",
getConnector: "connectorsCrud",
findConnector: "connectorsCrud",
// agents (note: getAgent/findAgent live on entityCrud)
saveAgent: "agents",
deleteAgent: "agents",
getAgent: "entityCrud",
findAgent: "entityCrud",
setHeartbeat: "agents",
markHeartbeatAt: "agents",
resetMonthlyBudgets: "agents",
listHeartbeatEligibleAgents: "agents",
getChainOfCommand: "agents",
getDirectReports: "agents",
getCeoForCompany: "agents",
listActiveAgentsForCompany: "agents",
listAgentsForApi: "agents",
listDirectReportsForStandup: "agents",
listReportStatusesForStandup: "agents",
listExistingAgentsForHireCheck: "agents",
hasAgentAssignedTasks: "agents",
updateAgentStatus: "agents",
isAgentPendingApproval: "agents",
findMentionedAgentIds: "agents",
getIdleAgentsForDepartment: "agents",
getManagerForAgent: "agents",
getAgentPermissions: "agents",
setAgentPermission: "agents",
hasAgentPermission: "agents",
getRuntimeState: "agents",
setRuntimeState: "agents",
getHeartbeatPolicy: "agents",
setHeartbeatPolicy: "agents",
countActiveRunsForAgent: "agents",
listDepartmentAgentIds: "agents",
listCompanyBroadcastAgentIds: "agents",
listIncidentPingableAgentIds: "agents",
findAgentName: "agents",
listAgentNamesByIds: "agents",
listActiveAgentIdsFromIds: "agents",
// tasks (note: getTask lives on entityCrud)
saveTask: "tasks",
getTask: "entityCrud",
deleteTask: "tasks",
getSessionById: "tasks",
incrementSessionUsage: "tasks",
archiveSession: "tasks",
createNewSession: "tasks",
ensureAgentSession: "tasks",
setAgentSessionId: "tasks",
getRecentRunsForTask: "tasks",
getRecentActivityForTask: "tasks",
checkoutTask: "tasks",
releaseTaskLock: "tasks",
renewTaskLease: "tasks",
listAgentTasks: "tasks",
listTasksByStatuses: "tasks",
listAssigneeTasksForStandup: "tasks",
listTasksForAssignees: "tasks",
listReviewTasksForManager: "tasks",
countActiveTasksForCompany: "tasks",
countAdvancingTasksForCompany: "tasks",
listChildTasks: "tasks",
advanceTaskStatus: "tasks",
setTaskRequiresUserReview: "tasks",
hasPendingDeliverableReviews: "tasks",
hasAgentPendingDeliverableReview: "tasks",
hasAgentTaskAwaitingReview: "tasks",
autoAssignTask: "tasks",
getTaskRetryCount: "tasks",
incrementTaskRetryCount: "tasks",
resetTaskRetryCount: "tasks",
retryInterruptedTask: "tasks",
dismissInterruptedTask: "tasks",
markInterruptedTaskFailed: "tasks",
setTaskType: "tasks",
listProjectTaskAssigneeIds: "tasks",
listReviewTasksForSweep: "tasks",
getTaskType: "tasks",
// runs
getRun: "runs",
getRunAuthState: "runs",
createRun: "runs",
appendRunLog: "runs",
readRunLog: "runs",
readRunLogChunk: "runs",
markRunStatus: "runs",
finishRun: "runs",
recordCostEntry: "runs",
hasActiveRun: "runs",
listQueuedRuns: "runs",
getInterruptedRunsForRecovery: "runs",
appendRunEvent: "runs",
listRunEvents: "runs",
listRunEventsAfter: "runs",
updateRunActor: "runs",
// approvalsMod
requestApproval: "approvalsMod",
decideApproval: "approvalsMod",
getApprovalRecord: "approvalsMod",
findApprovalById: "approvalsMod",
deleteApproval: "approvalsMod",
getBlockingApproval: "approvalsMod",
resubmitApproval: "approvalsMod",
addApprovalComment: "approvalsMod",
listApprovalComments: "approvalsMod",
linkTaskToApproval: "approvalsMod",
unlinkTaskFromApproval: "approvalsMod",
listApprovalsForTask: "approvalsMod",
listTasksForApproval: "approvalsMod",
linkManyTasksToApproval: "approvalsMod",
listAgentPendingApprovals: "approvalsMod",
listAgentResolvedApprovals: "approvalsMod",
listPendingHireApprovalPayloads: "approvalsMod",
listApprovalsForApi: "approvalsMod",
findPendingApprovalForTask: "approvalsMod",
hasPendingReviewApprovalForAgent: "approvalsMod",
listCompanyApprovalsByType: "approvalsMod",
// workspacesMod
saveWorkspace: "workspacesMod",
getWorkspace: "workspacesMod",
getProjectPrimaryWorkspace: "workspacesMod",
listWorkspaceLocalPaths: "workspacesMod",
getAnyCompanyWorkspace: "workspacesMod",
deleteWorkspace: "workspacesMod",
acquireWorkspaceLock: "workspacesMod",
claimWorkspaceLock: "workspacesMod",
releaseWorkspaceLock: "workspacesMod",
releaseAllWorkspaceLocks: "workspacesMod",
releaseStaleWorkspaceLocks: "workspacesMod",
isWorkspaceLocked: "workspacesMod",
};
const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const modulesArg = args.find((a) => a.startsWith("--modules="));
const methodsArg = args.find((a) => a.startsWith("--methods="));
const includedModules = modulesArg
? new Set(modulesArg.split("=")[1].split(","))
: null;
const extraMethods = methodsArg
? new Set(methodsArg.split("=")[1].split(","))
: new Set();
const targetMethods = Object.entries(METHOD_TO_MODULE)
.filter(([name, mod]) => {
if (!includedModules) return true;
if (includedModules.has(mod)) return true;
if (extraMethods.has(name)) return true;
return false;
})
.map(([m]) => m);
if (targetMethods.length === 0) {
console.error("No methods matched module filter.");
process.exit(2);
}
// Collect files via git ls-files (fast, respects .gitignore)
const filesOut = execSync("git ls-files 'src/**/*.ts' 'src/**/*.tsx' 'tests/**/*.ts'", {
cwd: REPO_ROOT,
encoding: "utf8",
});
const files = filesOut
.split("\n")
.filter(Boolean)
.filter((f) => !f.startsWith("src/main/database.ts"))
.filter((f) => !f.startsWith("src/main/database-base.ts"))
.filter((f) => !f.startsWith("src/main/database/"));
// Build one giant alternation regex, non-capturing, then use a back-reference
// group only for the method name so we can look up the module.
const methodAlt = targetMethods
.sort((a, b) => b.length - a.length) // longest-first to avoid partial shadow
.join("|");
// Match `<optional qualifier>db.<method>(`. The qualifier must end with
// a `.` so `adb.saveTask(` (same token) is not matched. Examples that
// DO match: `db.saveTask(`, `this.db.saveTask(`, `shared.db.saveTask(`,
// `ctx.db.saveTask(`. The pre-anchor `(^|[^\w.])` keeps us from
// splitting an identifier token containing `db` somewhere in the middle.
const pattern = new RegExp(
`(^|[^\\w.])((?:[\\w$]+\\.)*)db\\.(${methodAlt})\\(`,
"g",
);
let totalReplacements = 0;
let changedFiles = 0;
const perFile = {};
for (const relPath of files) {
const abs = join(REPO_ROOT, relPath);
const src = readFileSync(abs, "utf8");
let count = 0;
const out = src.replace(pattern, (match, pre, qualifier, method) => {
const mod = METHOD_TO_MODULE[method];
if (!mod) return match;
count += 1;
return `${pre}${qualifier}db.${mod}.${method}(`;
});
if (count > 0) {
perFile[relPath] = count;
totalReplacements += count;
changedFiles += 1;
if (!dryRun) writeFileSync(abs, out);
}
}
const hdr = dryRun ? "DRY RUN" : "APPLIED";
console.log(`${hdr}: ${totalReplacements} rewrites across ${changedFiles} files`);
for (const [f, n] of Object.entries(perFile).sort((a, b) => b[1] - a[1])) {
console.log(` ${n}\t${f}`);
}