From e10154b5e4c6ac7ae9b5b393bb091ad798f266ad Mon Sep 17 00:00:00 2001 From: "A.K.M. Adib" Date: Wed, 1 Apr 2026 10:03:24 -0400 Subject: [PATCH] refactor duplicated code --- packages/core/src/tools/exit-plan-mode.ts | 26 +++++++++-------------- packages/core/src/utils/planUtils.ts | 23 +++++++++++++++++++- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/core/src/tools/exit-plan-mode.ts b/packages/core/src/tools/exit-plan-mode.ts index 3b50c70fe5..fb429b00cc 100644 --- a/packages/core/src/tools/exit-plan-mode.ts +++ b/packages/core/src/tools/exit-plan-mode.ts @@ -18,7 +18,11 @@ import type { MessageBus } from '../confirmation-bus/message-bus.js'; import path from 'node:path'; import type { Config } from '../config/config.js'; import { EXIT_PLAN_MODE_TOOL_NAME } from './tool-names.js'; -import { validatePlanPath, validatePlanContent } from '../utils/planUtils.js'; +import { + validatePlanPath, + validatePlanContent, + getPlanVersions, +} from '../utils/planUtils.js'; import { ApprovalMode } from '../policy/types.js'; import { resolveToRealPath, isSubpath } from '../utils/paths.js'; import { logPlanExecution } from '../telemetry/loggers.js'; @@ -29,7 +33,6 @@ import { getPlanModeExitMessage } from '../utils/approvalModeUtils.js'; import * as Diff from 'diff'; import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js'; import { debugLogger } from '../utils/debugLogger.js'; -import * as fs from 'node:fs'; import * as fsPromises from 'node:fs/promises'; export interface ExitPlanModeParams { @@ -160,12 +163,7 @@ export class ExitPlanModeInvocation extends BaseToolInvocation< // decision is 'ask_user' let diffContent: string | undefined; try { - const files = await fsPromises.readdir(path.dirname(resolvedPlanPath)); - const base = path.basename(resolvedPlanPath); - const versions = files - .filter((f) => f.startsWith(`${base}.v`)) - .map((f) => parseInt(f.slice(base.length + 2), 10)) - .filter((v) => !isNaN(v)); + const versions = await getPlanVersions(resolvedPlanPath); const latestVersion = versions.length > 0 ? Math.max(...versions) : 0; if (latestVersion > 0) { @@ -270,14 +268,10 @@ Read and follow the plan strictly during implementation.`, }; } else { try { - const files = await fsPromises.readdir(path.dirname(resolvedPlanPath)); - const base = path.basename(resolvedPlanPath); - const versions = files - .filter((f) => f.startsWith(`${base}.v`)) - .map((f) => parseInt(f.slice(base.length + 2), 10)) - .filter((v) => !isNaN(v)); - const nextVersion = (versions.length > 0 ? Math.max(...versions) : 0) + 1; - const backupPath = `${resolvedPlanPath}.v${nextVersion}`; + const versions = await getPlanVersions(resolvedPlanPath); + const nextVersion = + (versions.length > 0 ? Math.max(...versions) : 0) + 1; + const backupPath = `${resolvedPlanPath}.v${nextVersion}`; const content = await fsPromises.readFile(resolvedPlanPath, 'utf8'); await fsPromises.writeFile(backupPath, content, 'utf8'); } catch (err) { diff --git a/packages/core/src/utils/planUtils.ts b/packages/core/src/utils/planUtils.ts index 559434b1e3..38a92847aa 100644 --- a/packages/core/src/utils/planUtils.ts +++ b/packages/core/src/utils/planUtils.ts @@ -5,8 +5,10 @@ */ import path from 'node:path'; +import fsPromises from 'node:fs/promises'; import { isEmpty, fileExists } from './fileUtils.js'; import { isSubpath, resolveToRealPath } from './paths.js'; +import { debugLogger } from './debugLogger.js'; /** * Standard error messages for the plan approval workflow. @@ -41,7 +43,6 @@ export async function validatePlanPath( if (!isSubpath(realPlansDir, realPath)) { return PlanErrorMessages.PATH_ACCESS_DENIED(planPath, realPlansDir); } - if (!(await fileExists(resolvedPath))) { return PlanErrorMessages.FILE_NOT_FOUND(planPath); } @@ -49,6 +50,26 @@ export async function validatePlanPath( return null; } +/** + * Returns a list of version numbers for a plan file by scanning the directory. + * @param planPath The path to the plan file. + * @returns A promise that resolves to an array of version numbers. + */ +export async function getPlanVersions(planPath: string): Promise { + const dir = path.dirname(planPath); + const base = path.basename(planPath); + try { + const files = await fsPromises.readdir(dir); + return files + .filter((f) => f.startsWith(`${base}.v`)) + .map((f) => parseInt(f.slice(base.length + 2), 10)) + .filter((v) => !isNaN(v)); + } catch (err) { + debugLogger.error(`Failed to read plan versions in ${dir}:`, err); + return []; + } +} + /** * Validates that a plan file has non-empty content. * @param planPath The path to the plan file.