mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-24 09:05:50 +00:00
* fix: 终端内容溢出 viewport 时的重影 bug 主屏幕模式下 frame 持续溢出 viewport 时,cursor-restore LF 把内容滚入 scrollback 导致相对光标追踪漂移,可见区 diff 落到错误行产生重影(重复 banner / 错位)。 扩展 log-update overflow 分支为无条件 fullReset(含 \x1b[3J 清 scrollback), 并将主屏 self-healing 清屏从 ERASE_SCREEN (CSI 2 J) 换成 ERASE_DOWN (CSI J), 避免 xterm.js / VSCode 集成终端的 scrollback 边界副作用。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 删除 3 个孤立诊断脚本 - scripts/verify-autofix-pr.ts: 一次性 autofix-pr 验证脚本,全仓零引用 - scripts/smoke-test-commands.ts: 开发期冒烟测试脚本,无任何 import - scripts/probe-subscription-endpoints.ts: 手动 endpoint 探针,无引用 均不在 package.json scripts、build.ts、vite.config.ts、CI workflows 中。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 self-hosted-runner stub 及其 cli.tsx fast-path - 删除 src/self-hosted-runner/main.ts(自动生成的 Promise.resolve() stub) - 同步移除 src/entrypoints/cli.tsx 中 feature('SELF_HOSTED_RUNNER') 守卫的 fast-path 分支 - 该 flag 不在 build.ts DEFAULT_BUILD_FEATURES 也不在 dev 默认列表,所有默认配置下整段为构建期死代码 删除 stub 单独会留下未解析的动态 import,必须协同拆除。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 删除 agentSdkTypes 中三个 not-implemented stub 移除 watchScheduledTasks、buildMissedTaskNotification、connectRemoteControl 三个 stub 函数(函数体仅 throw new Error('not implemented')),以及仅被这些 stub 引用的孤儿类型(ScheduledTasksHandle、ConnectRemoteControlOptions、RemoteControlHandle、InboundPrompt 等)。 全仓零外部引用。buildMissedTaskNotification 在 src/utils/cronScheduler.ts 有真实可用实现,未受影响。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 Cursor.ts 中未引用的 kill ring 访问器 - 删除 getKillRingItem、getKillRingSize、clearKillRing、canYankPop(全仓零引用的独立 export) - 移除 VIM_WORD_CHAR_REGEX 的 export 关键字(仍由 isVimWordChar 内部使用,保留常量本体) kill ring 特性本身仍活跃(getLastKill/pushToKillRing/yankPop 在 useSearchInput/useTextInput 使用),仅这几个孤儿 helper 未接入。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 insights.ts 中未引用的导出 - 删除 deduplicateSessionBranches(全仓零调用,含 JSDoc) - 删除 buildExportData(全仓零调用,原 S3 上传路径实际用 HTML 而非 JSON) - InsightsExport 仅移除 export 关键字(保留类型本体,仍作为内部返回类型) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 autonomyCommandSpec.ts 中未引用的导出 - 删除 AUTONOMY_CLI(CLI 子命令描述对象,零引用;handler 仅用 AUTONOMY_USAGE) - 删除 AUTONOMY_COMMAND_DESCRIPTION(值已在 main.tsx:5181 内联) - ParsedAutonomyCommand 仅移除 export 关键字(保留类型作为 parseAutonomyArgs 返回类型) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 binaryCheck/claudeAiLimits/codeIndexing 中未引用的导出 - binaryCheck.ts: 删除 clearBinaryCache(零调用,binaryCache 仍由 isBinaryInstalled 使用) - claudeAiLimits.ts: 删除 RATE_LIMIT_DISPLAY_NAMES 常量 + getRateLimitDisplayName(互为唯一消费者) - codeIndexing.ts: 删除 detectCodeIndexingFromMcpTool(同胞 detectCodeIndexingFromCommand/McpServerName 仍活跃) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除多处仅内部使用的 export 关键字 下列符号均仅在本文件内被引用,export 关键字冗余;保留符号本体不动: - internalLogging.ts: getContainerId(line 88 内部调用) - api/errors.ts: isMediaSizeError(line 151 内部调用) - api/withRetry.ts: parseMaxTokensContextOverflowError(line 389/724 内部调用) - statsCache.ts: STATS_CACHE_VERSION(7 处内部使用) - startupProfiler.ts: logStartupPerf(line 128 内部调用) - bashCommandHelpers.ts: CommandIdentityCheckers(3 处内部参数类型) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 清理注释代码块与 legacy shim 注释代码(已死的、引用不存在符号的注释块): - Onboarding.tsx: 注释化的 preflight if-block(引用不存在的 preflightStep) - ultraplan.tsx: 两处引用不存在符号的注释(ULTRAPLAN_INSTRUCTIONS、getUltraplanModel) - types/hooks.ts: 禁用的 type-fest IsEqual 类型断言块 - types/global.d.ts: 已被真实模块取代的 Ultraplan ambient declares - types/textInputTypes.ts: 注释化的 onMessage interface 成员 legacy shim: - cli/bg.ts: 删除 handleBgFlag 别名 export(同胞 handleBgStart 已被所有调用点使用) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 ccshareResume stub 及 main.tsx 的 ccshare fast-path - 删除 src/utils/ccshareResume.ts(parseCcshareId 恒返回 null、loadCcshare 恒抛错的 stub) - 同步移除 src/main.tsx 中 USER_TYPE === 'ant' 守卫下的 if (ccshareId) {...} else {...} 双分支 - 提升 else 块(文件路径 resume 处理)为直接进入 if (options.resume) 块内 ccshare 是 Anthropic 内部特性(go/ccshare URL),stub 未实现导致 ccshareId 恒为 null,整个 ccshare 分支永不进入;保留的文件路径 resume 路径不变。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 environment-runner stub 及其 cli.tsx fast-path 与 self-hosted-runner 相同模式的 sibling(工作流 1 verifier 建议同步处理): - 删除 src/environment-runner/main.ts(自动生成的 Promise.resolve() stub) - 同步移除 src/entrypoints/cli.tsx 中 feature('BYOC_ENVIRONMENT_RUNNER') 守卫的 fast-path 分支 - 清理两个空目录(src/self-hosted-runner/、src/environment-runner/) BYOC_ENVIRONMENT_RUNNER flag 不在 build.ts DEFAULT_BUILD_FEATURES 也不在 dev 默认列表,所有默认配置下整段为构建期死代码。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 删除孤立诊断脚本 probe-local-wiring.ts #!/usr/bin/env bun shebang 的手动诊断脚本,全仓零引用,不在 package.json/build.ts/vite.config.ts/CI workflows 中。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 ultrareview preflight stub 及其测试 - 删除 src/services/api/ultrareviewPreflight.ts(自动生成的 stub) - 删除 src/commands/review/UltrareviewPreflightDialog.tsx(依赖前者的 UI stub) - 删除 src/services/api/__tests__/ultrareviewPreflight.test.ts(测试已删代码) - 同步移除 ultrareviewCommand.test.tsx 中对 UltrareviewPreflightDialog 的 mock Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 cachedMCConfig stub 及 prompts.ts 的 CACHED_MICROCOMPACT 死代码 - 删除 src/services/compact/cachedMCConfig.ts(自动生成的 stub) - 同步移除 src/constants/prompts.ts 中依赖该 stub 的代码: - getCachedMCConfigForFRC 变量(feature('CACHED_MICROCOMPACT') 守卫的 require) - getFunctionResultClearingSection 函数(约 18 行) - systemPrompt 数组中的 frc section 调用与注册 CACHED_MICROCOMPACT 不在 build.ts DEFAULT_BUILD_FEATURES 也不在 dev 默认列表,所有默认配置下整段为构建期死代码。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 goalAudit stub 及其测试引用 - 删除 src/services/goal/goalAudit.ts(导出 COMPLETION_AUDIT_RULES/BLOCKED_AUDIT_RULES/isGoalTerminal 等未引用的 stub) - 同步移除 tests/integration/goal-lifecycle.test.ts 中对 goalAudit 的 import 和一个测试用例(budget_limited is terminal) Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 删除 agentSdkTypes 第二批 not-implemented stub 移除运行时函数体仅为 throw new Error 或 placeholder 的 stub: - createSdkMcpToolDefinition、createSdkMcpServer - query 函数重载与实现 - unstable_v2_* 系列函数 - session 操作 stub(getSessionMessages/listSessions/getSessionInfo/renameSession/tagSession/forkSession) - AbortError 类 保留所有 export type 重导出和类型别名(仍是公共类型面)。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 Tool.ts 中 backwards-compat 重导出 shim 删除 "// Re-export progress types for backwards compatibility" 注释块及其重导出语句。所有消费方已直接从 src/types/tools.js 导入,无需重导出转发。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 bootstrap/state.ts 中 4 个未引用的 export - clearRegisteredHooks(STATE.registeredHooks 仍由其他函数管理) - getInvokedSkills(getInvokedSkillsForAgent 是活跃入口) - getSessionSource(setSessionSource 仍活跃,sessionSource state 字段保留) - markScrollActivity(scrollDraining/getIsScrollDraining/waitForScrollDrain 仍活跃) 仅删除孤儿访问器,不动模块级 state 副作用。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 src/ 下多处未引用的导出 涉及 18 个文件,每处均为独立的 unreferenced export 删除或 export 关键字冗余移除: - bridge/bridgeStatusUtil.ts、components/TrustDialog/utils.ts、context/stats.tsx - keybindings/loadUserBindings.ts、memdir/paths.ts、remote/sdkMessageAdapter.ts - services/acp/utils.ts(删除 nodeToWebReadable,全仓零引用) - services/api/metricsOptOut.ts、services/lsp/LSPDiagnosticRegistry.ts、services/lsp/manager.ts - services/mcp/utils.ts、services/skillLearning/projectContext.ts - services/teamMemorySync/secretScanner.ts、services/teamMemorySync/watcher.ts - skills/loadSkillsDir.ts、utils/attachments.ts、utils/filePersistence/filePersistence.ts - utils/messageQueueManager.ts Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * chore: 移除 packages/ 下多处未引用的导出 涉及 11 个 workspace 包文件,每处均为独立的 unreferenced export 删除或 export 关键字冗余移除: - @ant/ink/core/termio/csi.ts(eraseLine) - acp-link/manager/types.ts、acp-link/ws-message.ts - builtin-tools/AgentTool/agentMemory.ts、BashTool/bashSecurity.ts、BashTool/sedEditParser.ts - builtin-tools/ConfigTool/supportedSettings.ts、FileEditTool/utils.ts - remote-control-server/store.ts、transport/event-bus.ts、types/messages.ts Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * Revert "fix: 终端内容溢出 viewport 时的重影 bug" This reverts commit3d18e1da58. * revert: 移除主屏幕周期性 self-healing 重绘 回退f69c7051中引入的 ink.tsx self-healing 机制(lastMainScreenHealTime 字段 + 每 5 秒触发全量重绘 + needsEraseBeforePaint 主屏幕分支)。该机制在 workflow 面板持续刷新场景下表现为可见的"重复刷新",且修复效果不稳定。 alt-screen 的 needsEraseBeforePaint 路径和 prevFrameContaminated 字段保留, 它们仍服务于 handleResize / layout shift / selection 高亮。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * fix: /workflows 面板默认只显示运行中 run,根治 tab 行乱码 之前几次渲染层修复都失败,因为没动 tab 列表的数据源:打开 /workflows 会 自动 hydrate 最多 20 个历史 done/killed run,全部塞进一行 TabsBar,超出 终端宽度后 Ink 把字符画到屏外造成重影乱码。 - selectors.ts 加 filterActiveRuns(只留 status === 'running')和 capTabsForDisplay(超额 fold 成 +N)两个 pure function - WorkflowsPanel 接线 activeRuns:focus clamp、focused、nextTab/prevTab、 TabsBar 全部基于过滤后的 activeRuns - TabsBar 复用 truncateLabel 限制每个 tab 名 18 字符 + 最多 6 个 tab, 多余显示 +N,从结构上钉死单行总宽度 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * fix: /workflows 面板 phase 状态在脚本省略 phase() 时显示错乱 ultracode canonical pipeline 脚本常在 agent() 直接传 opts.phase 而不调 phase() hook,导致 phase_started 从未发出;同时 phase_done 只在下次 phase() 触发,上一 个 phase 在 run.phases 里一直停在 running。mergePhases 之前把 actual 当权威, 于是出现 "Map 8/8 全 done 还显示 running、Find 1/4 running 反而显示 pending"。 改为派生层修复:mergePhases 新增 derivePhaseStatus——actual.status==='done' 权威;否则有 agents 就按 agents 状态推(全 done→done,否则 running);否则看 actual 是否 running。再补一层遍历,让只在 agents 上出现的 phase 也进 sidebar。 不改 store 状态语义,已有 state.json 无需迁移。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> * docs: 更新 readme * fix: ACP 模式未读取 settings.local.json entry.ts 在 ACP 握手期调用的 applySafeConfigEnvironmentVariables 触发了 loadSettingsFromDisk,此时 getOriginalCwd() 还是进程启动 cwd(非项目目录), 导致 localSettings/projectSettings 按错误路径解析为空并被 session cache 锁住, 后续 createSession 里 setOriginalCwd 也无法纠正。在 setOriginalCwd 与 chdir 之后清缓存并重新应用,让 settings.local.json 和项目级 env 对 readSettingsPermissionMode 及下游可见。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win> --------- Co-authored-by: glm-5.2 <zai-org@claude-code-best.win>
263 lines
9.7 KiB
TypeScript
263 lines
9.7 KiB
TypeScript
import memoize from 'lodash-es/memoize.js'
|
||
import { homedir } from 'os'
|
||
import { isAbsolute, join, normalize, sep } from 'path'
|
||
import {
|
||
getIsNonInteractiveSession,
|
||
getProjectRoot,
|
||
} from '../bootstrap/state.js'
|
||
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
|
||
import {
|
||
getClaudeConfigHomeDir,
|
||
isEnvDefinedFalsy,
|
||
isEnvTruthy,
|
||
} from '../utils/envUtils.js'
|
||
import { findCanonicalGitRoot } from '../utils/git.js'
|
||
import { sanitizePath } from '../utils/path.js'
|
||
import {
|
||
getInitialSettings,
|
||
getSettingsForSource,
|
||
} from '../utils/settings/settings.js'
|
||
|
||
/**
|
||
* Whether auto-memory features are enabled (memdir, agent memory, past session search).
|
||
* Enabled by default. Priority chain (first defined wins):
|
||
* 1. CLAUDE_CODE_DISABLE_AUTO_MEMORY env var (1/true → OFF, 0/false → ON)
|
||
* 2. CLAUDE_CODE_SIMPLE (--bare) → OFF
|
||
* 3. CCR without persistent storage → OFF (no CLAUDE_CODE_REMOTE_MEMORY_DIR)
|
||
* 4. autoMemoryEnabled in settings.json (supports project-level opt-out)
|
||
* 5. Default: enabled
|
||
*/
|
||
export function isAutoMemoryEnabled(): boolean {
|
||
const envVal = process.env.CLAUDE_CODE_DISABLE_AUTO_MEMORY
|
||
if (isEnvTruthy(envVal)) {
|
||
return false
|
||
}
|
||
if (isEnvDefinedFalsy(envVal)) {
|
||
return true
|
||
}
|
||
// --bare / SIMPLE: prompts.ts already drops the memory section from the
|
||
// system prompt via its SIMPLE early-return; this gate stops the other half
|
||
// (extractMemories turn-end fork, autoDream, /remember, /dream, team sync).
|
||
if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
|
||
return false
|
||
}
|
||
if (
|
||
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
|
||
!process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR
|
||
) {
|
||
return false
|
||
}
|
||
const settings = getInitialSettings()
|
||
if (settings.autoMemoryEnabled !== undefined) {
|
||
return settings.autoMemoryEnabled
|
||
}
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* Whether the extract-memories background agent will run this session.
|
||
*
|
||
* The main agent's prompt always has full save instructions regardless of
|
||
* this gate — when the main agent writes memories, the background agent
|
||
* skips that range (hasMemoryWritesSince in extractMemories.ts); when it
|
||
* doesn't, the background agent catches anything missed.
|
||
*
|
||
* Callers must also gate on feature('EXTRACT_MEMORIES') — that check cannot
|
||
* live inside this helper because feature() only tree-shakes when used
|
||
* directly in an `if` condition.
|
||
*/
|
||
export function isExtractModeActive(): boolean {
|
||
if (!getFeatureValue_CACHED_MAY_BE_STALE('tengu_passport_quail', false)) {
|
||
return false
|
||
}
|
||
return (
|
||
!getIsNonInteractiveSession() ||
|
||
getFeatureValue_CACHED_MAY_BE_STALE('tengu_slate_thimble', false)
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Returns the base directory for persistent memory storage.
|
||
* Resolution order:
|
||
* 1. CLAUDE_CODE_REMOTE_MEMORY_DIR env var (explicit override, set in CCR)
|
||
* 2. ~/.claude (default config home)
|
||
*/
|
||
export function getMemoryBaseDir(): string {
|
||
if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) {
|
||
return process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR
|
||
}
|
||
return getClaudeConfigHomeDir()
|
||
}
|
||
|
||
const AUTO_MEM_DIRNAME = 'memory'
|
||
const AUTO_MEM_ENTRYPOINT_NAME = 'MEMORY.md'
|
||
|
||
/**
|
||
* Normalize and validate a candidate auto-memory directory path.
|
||
*
|
||
* SECURITY: Rejects paths that would be dangerous as a read-allowlist root
|
||
* or that normalize() doesn't fully resolve:
|
||
* - relative (!isAbsolute): "../foo" — would be interpreted relative to CWD
|
||
* - root/near-root (length < 3): "/" → "" after strip; "/a" too short
|
||
* - Windows drive-root (C: regex): "C:\" → "C:" after strip
|
||
* - UNC paths (\\server\share): network paths — opaque trust boundary
|
||
* - null byte: survives normalize(), can truncate in syscalls
|
||
*
|
||
* Returns the normalized path with exactly one trailing separator,
|
||
* or undefined if the path is unset/empty/rejected.
|
||
*/
|
||
function validateMemoryPath(
|
||
raw: string | undefined,
|
||
expandTilde: boolean,
|
||
): string | undefined {
|
||
if (!raw) {
|
||
return undefined
|
||
}
|
||
let candidate = raw
|
||
// Settings.json paths support ~/ expansion (user-friendly). The env var
|
||
// override does not (it's set programmatically by Cowork/SDK, which should
|
||
// always pass absolute paths). Bare "~", "~/", "~/.", "~/..", etc. are NOT
|
||
// expanded — they would make isAutoMemPath() match all of $HOME or its
|
||
// parent (same class of danger as "/" or "C:\").
|
||
if (
|
||
expandTilde &&
|
||
(candidate.startsWith('~/') || candidate.startsWith('~\\'))
|
||
) {
|
||
const rest = candidate.slice(2)
|
||
// Reject trivial remainders that would expand to $HOME or an ancestor.
|
||
// normalize('') = '.', normalize('.') = '.', normalize('foo/..') = '.',
|
||
// normalize('..') = '..', normalize('foo/../..') = '..'
|
||
const restNorm = normalize(rest || '.')
|
||
if (restNorm === '.' || restNorm === '..') {
|
||
return undefined
|
||
}
|
||
candidate = join(homedir(), rest)
|
||
}
|
||
// normalize() may preserve a trailing separator; strip before adding
|
||
// exactly one to match the trailing-sep contract of getAutoMemPath()
|
||
const normalized = normalize(candidate).replace(/[/\\]+$/, '')
|
||
if (
|
||
!isAbsolute(normalized) ||
|
||
normalized.length < 3 ||
|
||
/^[A-Za-z]:$/.test(normalized) ||
|
||
normalized.startsWith('\\\\') ||
|
||
normalized.startsWith('//') ||
|
||
normalized.includes('\0')
|
||
) {
|
||
return undefined
|
||
}
|
||
return (normalized + sep).normalize('NFC')
|
||
}
|
||
|
||
/**
|
||
* Direct override for the full auto-memory directory path via env var.
|
||
* When set, getAutoMemPath()/getAutoMemEntrypoint() return this path directly
|
||
* instead of computing `{base}/projects/{sanitized-cwd}/memory/`.
|
||
*
|
||
* Used by Cowork to redirect memory to a space-scoped mount where the
|
||
* per-session cwd (which contains the VM process name) would otherwise
|
||
* produce a different project-key for every session.
|
||
*/
|
||
function getAutoMemPathOverride(): string | undefined {
|
||
return validateMemoryPath(
|
||
process.env.CLAUDE_COWORK_MEMORY_PATH_OVERRIDE,
|
||
false,
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Settings.json override for the full auto-memory directory path.
|
||
* Supports ~/ expansion for user convenience.
|
||
*
|
||
* SECURITY: projectSettings (.claude/settings.json committed to the repo) is
|
||
* intentionally excluded — a malicious repo could otherwise set
|
||
* autoMemoryDirectory: "~/.ssh" and gain silent write access to sensitive
|
||
* directories via the filesystem.ts write carve-out (which fires when
|
||
* isAutoMemPath() matches and hasAutoMemPathOverride() is false). This follows
|
||
* the same pattern as hasSkipDangerousModePermissionPrompt() etc.
|
||
*/
|
||
function getAutoMemPathSetting(): string | undefined {
|
||
const dir =
|
||
getSettingsForSource('policySettings')?.autoMemoryDirectory ??
|
||
getSettingsForSource('flagSettings')?.autoMemoryDirectory ??
|
||
getSettingsForSource('localSettings')?.autoMemoryDirectory ??
|
||
getSettingsForSource('userSettings')?.autoMemoryDirectory
|
||
return validateMemoryPath(dir, true)
|
||
}
|
||
|
||
/**
|
||
* Check if CLAUDE_COWORK_MEMORY_PATH_OVERRIDE is set to a valid override.
|
||
* Use this as a signal that the SDK caller has explicitly opted into
|
||
* the auto-memory mechanics — e.g. to decide whether to inject the
|
||
* memory prompt when a custom system prompt replaces the default.
|
||
*/
|
||
export function hasAutoMemPathOverride(): boolean {
|
||
return getAutoMemPathOverride() !== undefined
|
||
}
|
||
|
||
/**
|
||
* Returns the canonical git repo root if available, otherwise falls back to
|
||
* the stable project root. Uses findCanonicalGitRoot so all worktrees of the
|
||
* same repo share one auto-memory directory (anthropics/claude-code#24382).
|
||
*/
|
||
function getAutoMemBase(): string {
|
||
return findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot()
|
||
}
|
||
|
||
/**
|
||
* Returns the auto-memory directory path.
|
||
*
|
||
* Resolution order:
|
||
* 1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE env var (full-path override, used by Cowork)
|
||
* 2. autoMemoryDirectory in settings.json (trusted sources only: policy/local/user)
|
||
* 3. <memoryBase>/projects/<sanitized-git-root>/memory/
|
||
* where memoryBase is resolved by getMemoryBaseDir()
|
||
*
|
||
* Memoized: render-path callers (collapseReadSearchGroups → isAutoManagedMemoryFile)
|
||
* fire per tool-use message per Messages re-render; each miss costs
|
||
* getSettingsForSource × 4 → parseSettingsFile (realpathSync + readFileSync).
|
||
* Keyed on projectRoot so tests that change its mock mid-block recompute;
|
||
* env vars / settings.json / CLAUDE_CONFIG_DIR are session-stable in
|
||
* production and covered by per-test cache.clear.
|
||
*/
|
||
export const getAutoMemPath = memoize(
|
||
(): string => {
|
||
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
|
||
if (override) {
|
||
return override
|
||
}
|
||
const projectsDir = join(getMemoryBaseDir(), 'projects')
|
||
return (
|
||
join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
|
||
).normalize('NFC')
|
||
},
|
||
() => getProjectRoot(),
|
||
)
|
||
|
||
/**
|
||
* Returns the auto-memory entrypoint (MEMORY.md inside the auto-memory dir).
|
||
* Follows the same resolution order as getAutoMemPath().
|
||
*/
|
||
export function getAutoMemEntrypoint(): string {
|
||
return join(getAutoMemPath(), AUTO_MEM_ENTRYPOINT_NAME)
|
||
}
|
||
|
||
/**
|
||
* Check if an absolute path is within the auto-memory directory.
|
||
*
|
||
* When CLAUDE_COWORK_MEMORY_PATH_OVERRIDE is set, this matches against the
|
||
* env-var override directory. Note that a true return here does NOT imply
|
||
* write permission in that case — the filesystem.ts write carve-out is gated
|
||
* on !hasAutoMemPathOverride() (it exists to bypass DANGEROUS_DIRECTORIES).
|
||
*
|
||
* The settings.json autoMemoryDirectory DOES get the write carve-out: it's the
|
||
* user's explicit choice from a trusted settings source (projectSettings is
|
||
* excluded — see getAutoMemPathSetting), and hasAutoMemPathOverride() remains
|
||
* false for it.
|
||
*/
|
||
export function isAutoMemPath(absolutePath: string): boolean {
|
||
// SECURITY: Normalize to prevent path traversal bypasses via .. segments
|
||
const normalizedPath = normalize(absolutePath)
|
||
return normalizedPath.startsWith(getAutoMemPath())
|
||
}
|