Files
affaan-m--everything-claude…/scripts/lib/agent-data-home.js
T
wehub-resource-sync d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:55:55 +08:00

200 lines
6.3 KiB
JavaScript

/**
* Resolve ECC agent data home (memory persistence root) across harnesses.
*
* Docstring policy: public entry points here are documented; small internal
* helpers (e.g. `expandHomePath`, `readProjectConfigAt`) are left undocumented on
* purpose, consistent with ECC script modules elsewhere. Automated PR reviewers
* (e.g. CodeRabbit) may still flag low JSDoc coverage against a high threshold on
* the diff—that check is informational for this repo, not a bar every helper in
* touched files must meet. Prefer clarity in code and tests over blanket JSDoc on
* private helpers unless maintainers adopt a project-wide coverage rule.
*
* @see https://github.com/affaan-m/ECC/issues/2065
*/
const fs = require('fs');
const path = require('path');
const AGENT_DATA_HOME_ENV = 'ECC_AGENT_DATA_HOME';
const DEFAULT_CLAUDE_DIR_NAME = '.claude';
const DEFAULT_CURSOR_ECC_DIR_SEGMENTS = ['.cursor', 'ecc'];
const PROJECT_CONFIG_RELATIVE = path.join('.cursor', 'ecc-agent-data.json');
/**
* Home directory for tilde expansion and default agent-data paths.
*
* Intentionally mirrors `getHomeDir()` in `scripts/lib/utils.js` (HOME/USERPROFILE,
* then `os.homedir()`). Do not import `utils.getHomeDir` here: `utils.js` already
* requires this module (`resolveAgentDataHome`), which would create a circular
* dependency and risk divergent defaults for `~/.cursor/ecc` vs `~/.claude`.
*
* If consolidation is needed later, prefer one of:
*
* | Approach | Tradeoff |
* | --- | --- |
* | Shared `scripts/lib/home-dir.js` imported by both | Clean; breaks the cycle |
* | Keep duplicate + cross-reference comment (this file) | Zero require risk |
* | Move all resolution here; thin-wrap from `utils` | Larger refactor |
*/
function getHomeDirFromEnv() {
const explicitHome = process.env.HOME || process.env.USERPROFILE;
if (explicitHome && String(explicitHome).trim().length > 0) {
return path.resolve(explicitHome);
}
return require('os').homedir();
}
function expandHomePath(value, baseDir) {
if (!value || typeof value !== 'string') return null;
const trimmed = value.trim();
if (!trimmed) return null;
if (trimmed.startsWith('~')) {
const remainder = trimmed.slice(1).replace(/^[/\\]+/, '');
return remainder ? path.join(getHomeDirFromEnv(), remainder) : getHomeDirFromEnv();
}
if (path.isAbsolute(trimmed)) {
return path.resolve(trimmed);
}
const base = baseDir && String(baseDir).trim()
? path.resolve(baseDir)
: process.cwd();
return path.resolve(base, trimmed);
}
/**
* Project root for a config file under .cursor/ecc-agent-data.json.
*/
function resolveProjectRootFromConfigPath(configPath) {
const configDir = path.dirname(path.resolve(configPath));
if (path.basename(configDir) === '.cursor') {
return path.dirname(configDir);
}
return configDir;
}
/**
* True when the current process is a Cursor hook subprocess.
* Cursor documents CURSOR_VERSION and CURSOR_PROJECT_DIR for hook scripts.
*/
function isCursorHookRuntime() {
if (process.env.CURSOR_VERSION && String(process.env.CURSOR_VERSION).trim()) {
return true;
}
if (process.env.CURSOR_PROJECT_DIR && String(process.env.CURSOR_PROJECT_DIR).trim()) {
return true;
}
return false;
}
function getDefaultCursorAgentDataHome() {
return path.join(getHomeDirFromEnv(), ...DEFAULT_CURSOR_ECC_DIR_SEGMENTS);
}
function getDefaultClaudeAgentDataHome() {
return path.join(getHomeDirFromEnv(), DEFAULT_CLAUDE_DIR_NAME);
}
function readProjectConfigAt(configPath) {
if (!configPath || typeof configPath !== 'string') return null;
if (!fs.existsSync(configPath)) return null;
try {
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
const candidate = parsed.agentDataHome || parsed.ECC_AGENT_DATA_HOME;
if (typeof candidate !== 'string' || !candidate.trim()) return null;
const projectRoot = resolveProjectRootFromConfigPath(configPath);
return expandHomePath(candidate, projectRoot);
} catch (error) {
console.error(
`[ECC] Failed to read or parse agent data config at ${configPath}: ${error.message}`
);
return null;
}
}
function readProjectConfig(projectDir) {
if (!projectDir || typeof projectDir !== 'string') return null;
return readProjectConfigAt(path.join(path.resolve(projectDir), PROJECT_CONFIG_RELATIVE));
}
function resolveProjectDir() {
const candidates = [
process.env.CURSOR_PROJECT_DIR,
process.env.CLAUDE_PROJECT_DIR,
process.cwd(),
];
for (const candidate of candidates) {
if (!candidate || typeof candidate !== 'string') continue;
const resolved = path.resolve(candidate);
if (fs.existsSync(path.join(resolved, '.cursor'))) {
return resolved;
}
}
return process.cwd();
}
/**
* Resolve agent data home without mutating process.env.
*/
function resolveAgentDataHome(options = {}) {
const fromEnv = expandHomePath(process.env[AGENT_DATA_HOME_ENV]);
if (fromEnv) return fromEnv;
const projectDir = options.projectDir || resolveProjectDir();
const fromProject = readProjectConfig(projectDir);
if (fromProject) return fromProject;
if (options.preferCursorDefault === true || isCursorHookRuntime()) {
return getDefaultCursorAgentDataHome();
}
return getDefaultClaudeAgentDataHome();
}
/**
* Set ECC_AGENT_DATA_HOME on the current process when unset (hook subprocess safety net).
* @returns {string} Resolved agent data home
*/
function ensureAgentDataHomeEnv(options = {}) {
const resolved = resolveAgentDataHome(options);
if (!expandHomePath(process.env[AGENT_DATA_HOME_ENV])) {
process.env[AGENT_DATA_HOME_ENV] = resolved;
}
return resolved;
}
/**
* Build Cursor sessionStart hook output env payload.
*/
function getCursorSessionEnvPayload(options = {}) {
const agentDataHome = resolveAgentDataHome({
...options,
preferCursorDefault: true,
});
return {
ECC_AGENT_DATA_HOME: agentDataHome,
};
}
module.exports = {
AGENT_DATA_HOME_ENV,
DEFAULT_CLAUDE_DIR_NAME,
DEFAULT_CURSOR_ECC_DIR_SEGMENTS,
PROJECT_CONFIG_RELATIVE,
expandHomePath,
resolveProjectRootFromConfigPath,
isCursorHookRuntime,
getDefaultCursorAgentDataHome,
getDefaultClaudeAgentDataHome,
readProjectConfig,
readProjectConfigAt,
resolveProjectDir,
resolveAgentDataHome,
ensureAgentDataHomeEnv,
getCursorSessionEnvPayload,
};