mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-23 16:41:22 +08:00
- #2290 suggest-compact: honor ECC_CONTEXT_WINDOW_TOKENS / CLAUDE_CODE_AUTO_COMPACT_WINDOW so 400k-window models (Opus 4.x) no longer report ~double context usage; add override + isolation tests in transcript-context.test.js. - #2282 install: bare-language syntax is legacy-only by design, but the error now distinguishes a supported-but-wrong-mode target (gemini/codex/…) from a genuinely unknown one and points to --profile/--modules/--skills. - #2276 cost-report: the command + cost-tracking skill targeted a SQLite DB no tracker writes. Repoint both at the real ~/.claude/metrics/costs.jsonl (JSONL, estimated_cost_usd), reduce cumulative-per-session snapshots to latest-per-session, and use node instead of sqlite3 for cross-platform support. - #2272 gateguard: make the 'confirm no existing file' checklist item tool-agnostic (Glob/Grep or find/grep via Bash) so hosts without a Glob tool don't get a dead tool call. Full suite 2839/2839; lint green.
This commit is contained in:
@@ -25,11 +25,7 @@
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
extractCommandSubstitutions,
|
||||
extractSubshellGroups,
|
||||
extractBraceGroups
|
||||
} = require('../lib/shell-substitution');
|
||||
const { extractCommandSubstitutions, extractSubshellGroups, extractBraceGroups } = require('../lib/shell-substitution');
|
||||
|
||||
// Session state — scoped per session to avoid cross-session races.
|
||||
const STATE_DIR = process.env.GATEGUARD_STATE_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.gateguard');
|
||||
@@ -88,10 +84,10 @@ function getExtraDestructiveRegex() {
|
||||
extraDestructiveCacheRegex = null;
|
||||
if (!extraDestructiveWarnLogged) {
|
||||
try {
|
||||
process.stderr.write(
|
||||
`[gateguard-fact-force] ignoring invalid GATEGUARD_BASH_EXTRA_DESTRUCTIVE regex: ${err.message}\n`
|
||||
);
|
||||
} catch (_) { /* stderr write failure is non-fatal */ }
|
||||
process.stderr.write(`[gateguard-fact-force] ignoring invalid GATEGUARD_BASH_EXTRA_DESTRUCTIVE regex: ${err.message}\n`);
|
||||
} catch (_) {
|
||||
/* stderr write failure is non-fatal */
|
||||
}
|
||||
extraDestructiveWarnLogged = true;
|
||||
}
|
||||
}
|
||||
@@ -112,9 +108,7 @@ function isRoutineBashGateDisabled() {
|
||||
* @returns {string}
|
||||
*/
|
||||
function stripQuotedStrings(input) {
|
||||
return input
|
||||
.replace(/'(?:[^'\\]|\\.)*'/g, "''")
|
||||
.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
||||
return input.replace(/'(?:[^'\\]|\\.)*'/g, "''").replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +162,6 @@ function tokenize(segment) {
|
||||
return segment.split(/\s+/).filter(Boolean);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tokenize a short allowlisted shell command while preserving quoted
|
||||
* arguments. This is intentionally smaller than a full shell parser: the
|
||||
@@ -236,7 +229,10 @@ function tokenizeAllowlistedShellWords(input) {
|
||||
*/
|
||||
function commandBasename(token) {
|
||||
if (!token) return '';
|
||||
return token.replace(/^.*[\\/]/, '').replace(/\.exe$/i, '').toLowerCase();
|
||||
return token
|
||||
.replace(/^.*[\\/]/, '')
|
||||
.replace(/\.exe$/i, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -553,7 +549,10 @@ function isDestructiveBash(command) {
|
||||
// that false-negative while also catching `&&`, `;`, `|`, and `||` compound forms.
|
||||
const bodies = collectExecutableBodies(raw);
|
||||
for (const body of bodies) {
|
||||
for (const rawSeg of body.split(/[;|&]+/).map(s => s.trim()).filter(Boolean)) {
|
||||
for (const rawSeg of body
|
||||
.split(/[;|&]+/)
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean)) {
|
||||
if (isDestructiveFindExec(rawSeg)) return true;
|
||||
}
|
||||
}
|
||||
@@ -573,7 +572,9 @@ function isDestructiveBash(command) {
|
||||
// --- State management (per-session, atomic writes, bounded) ---
|
||||
|
||||
function normalizeEnvValue(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function isGateGuardDisabled() {
|
||||
@@ -886,8 +887,7 @@ function isReadOnlyGitIntrospection(command) {
|
||||
if (args.length === 2) {
|
||||
const [first, second] = args;
|
||||
// ref + flag
|
||||
if (!first.startsWith('--') && /^[a-zA-Z0-9._:/ -]+$/.test(first) &&
|
||||
(second === '--stat' || second === '--name-only')) {
|
||||
if (!first.startsWith('--') && /^[a-zA-Z0-9._:/ -]+$/.test(first) && (second === '--stat' || second === '--name-only')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -932,7 +932,7 @@ function writeGateMsg(filePath) {
|
||||
`Before creating ${safe}, present these facts:`,
|
||||
'',
|
||||
'1. Name the file(s) and line(s) that will call this new file',
|
||||
'2. Confirm no existing file serves the same purpose (use Glob)',
|
||||
'2. Confirm no existing file serves the same purpose (search the tree — Glob/Grep, or find/grep via Bash)',
|
||||
'3. If this file reads/writes data files, show field names, structure, and date format (use redacted or synthetic values, not raw production data)',
|
||||
"4. Quote the user's current instruction verbatim",
|
||||
'',
|
||||
@@ -983,11 +983,7 @@ function routineBashMsg() {
|
||||
|
||||
function withRecoveryHint(message, hookIds = [EDIT_WRITE_HOOK_ID]) {
|
||||
const disableTargets = hookIds.map(hookId => `\`${hookId}\``).join(' or ');
|
||||
return [
|
||||
message,
|
||||
'',
|
||||
`Recovery: if GateGuard is blocking setup or repair work, run this session with \`ECC_GATEGUARD=off\` or add ${disableTargets} to \`ECC_DISABLED_HOOKS\`.`
|
||||
].join('\n');
|
||||
return [message, '', `Recovery: if GateGuard is blocking setup or repair work, run this session with \`ECC_GATEGUARD=off\` or add ${disableTargets} to \`ECC_DISABLED_HOOKS\`.`].join('\n');
|
||||
}
|
||||
|
||||
function isSubagentInvocation(data) {
|
||||
@@ -995,12 +991,7 @@ function isSubagentInvocation(data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
data.agent_id,
|
||||
data.agentId,
|
||||
data.parent_tool_use_id,
|
||||
data.parentToolUseId
|
||||
];
|
||||
const candidates = [data.agent_id, data.agentId, data.parent_tool_use_id, data.parentToolUseId];
|
||||
|
||||
return candidates.some(candidate => typeof candidate === 'string' && candidate.trim());
|
||||
}
|
||||
|
||||
+132
-154
@@ -5,20 +5,12 @@ const { execFileSync } = require('child_process');
|
||||
|
||||
const { toCursorAgentRelativePath } = require('./cursor-agent-names');
|
||||
const { LEGACY_INSTALL_TARGETS, parseInstallArgs } = require('./install/request');
|
||||
const {
|
||||
SUPPORTED_INSTALL_TARGETS,
|
||||
listLegacyCompatibilityLanguages,
|
||||
resolveLegacyCompatibilitySelection,
|
||||
resolveInstallPlan,
|
||||
} = require('./install-manifests');
|
||||
const { SUPPORTED_INSTALL_TARGETS, listLegacyCompatibilityLanguages, resolveLegacyCompatibilitySelection, resolveInstallPlan } = require('./install-manifests');
|
||||
const { getInstallTargetAdapter } = require('./install-targets/registry');
|
||||
|
||||
const LANGUAGE_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
||||
const CLAUDE_ECC_NAMESPACE = 'ecc';
|
||||
const EXCLUDED_GENERATED_SOURCE_SUFFIXES = [
|
||||
'/ecc-install-state.json',
|
||||
'/ecc/install-state.json',
|
||||
];
|
||||
const EXCLUDED_GENERATED_SOURCE_SUFFIXES = ['/ecc-install-state.json', '/ecc/install-state.json'];
|
||||
|
||||
function getSourceRoot() {
|
||||
return path.join(__dirname, '../..');
|
||||
@@ -26,9 +18,7 @@ function getSourceRoot() {
|
||||
|
||||
function getPackageVersion(sourceRoot) {
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(path.join(sourceRoot, 'package.json'), 'utf8')
|
||||
);
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(sourceRoot, 'package.json'), 'utf8'));
|
||||
return packageJson.version || null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
@@ -37,9 +27,7 @@ function getPackageVersion(sourceRoot) {
|
||||
|
||||
function getManifestVersion(sourceRoot) {
|
||||
try {
|
||||
const modulesManifest = JSON.parse(
|
||||
fs.readFileSync(path.join(sourceRoot, 'manifests', 'install-modules.json'), 'utf8')
|
||||
);
|
||||
const modulesManifest = JSON.parse(fs.readFileSync(path.join(sourceRoot, 'manifests', 'install-modules.json'), 'utf8'));
|
||||
return modulesManifest.version || 1;
|
||||
} catch (_error) {
|
||||
return 1;
|
||||
@@ -52,7 +40,7 @@ function getRepoCommit(sourceRoot) {
|
||||
cwd: sourceRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
timeout: 5000,
|
||||
timeout: 5000
|
||||
}).trim();
|
||||
} catch (_error) {
|
||||
return null;
|
||||
@@ -64,32 +52,35 @@ function readDirectoryNames(dirPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fs.readdirSync(dirPath, { withFileTypes: true })
|
||||
return fs
|
||||
.readdirSync(dirPath, { withFileTypes: true })
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => entry.name)
|
||||
.sort();
|
||||
}
|
||||
|
||||
function listAvailableLanguages(sourceRoot = getSourceRoot()) {
|
||||
return [...new Set([
|
||||
...listLegacyCompatibilityLanguages(),
|
||||
...readDirectoryNames(path.join(sourceRoot, 'rules'))
|
||||
.filter(name => name !== 'common'),
|
||||
])].sort();
|
||||
return [...new Set([...listLegacyCompatibilityLanguages(), ...readDirectoryNames(path.join(sourceRoot, 'rules')).filter(name => name !== 'common')])].sort();
|
||||
}
|
||||
|
||||
function validateLegacyTarget(target) {
|
||||
if (!LEGACY_INSTALL_TARGETS.includes(target)) {
|
||||
if (LEGACY_INSTALL_TARGETS.includes(target)) {
|
||||
return;
|
||||
}
|
||||
// A target can be fully supported yet not installable via the bare-language
|
||||
// positional syntax (which is legacy-only). Guide the user to the right mode
|
||||
// instead of implying the target is unknown (#2282).
|
||||
if (SUPPORTED_INSTALL_TARGETS.includes(target)) {
|
||||
throw new Error(
|
||||
`Unknown install target: ${target}. Expected one of ${LEGACY_INSTALL_TARGETS.join(', ')}`
|
||||
`Target '${target}' is supported, but the bare-language install syntax only accepts ${LEGACY_INSTALL_TARGETS.join(', ')}. ` +
|
||||
`Install '${target}' with a component selection instead, e.g. \`install.sh --target ${target} --profile full\` ` +
|
||||
`(or --modules <id,...> / --skills <id,...>).`
|
||||
);
|
||||
}
|
||||
throw new Error(`Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}`);
|
||||
}
|
||||
|
||||
const IGNORED_DIRECTORY_NAMES = new Set([
|
||||
'node_modules',
|
||||
'.git',
|
||||
]);
|
||||
const IGNORED_DIRECTORY_NAMES = new Set(['node_modules', '.git']);
|
||||
|
||||
function listFilesRecursive(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
@@ -141,7 +132,7 @@ function buildCopyFileOperation({ moduleId, sourcePath, sourceRelativePath, dest
|
||||
destinationPath,
|
||||
strategy,
|
||||
ownership: 'managed',
|
||||
scaffoldOnly: false,
|
||||
scaffoldOnly: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,20 +147,20 @@ function addRecursiveCopyOperations(operations, options) {
|
||||
for (const relativeFile of relativeFiles) {
|
||||
const sourceRelativePath = path.join(options.sourceRelativeDir, relativeFile);
|
||||
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
||||
const destinationRelativePath = typeof options.destinationRelativePathTransform === 'function'
|
||||
? options.destinationRelativePathTransform(relativeFile, sourceRelativePath)
|
||||
: relativeFile;
|
||||
const destinationRelativePath = typeof options.destinationRelativePathTransform === 'function' ? options.destinationRelativePathTransform(relativeFile, sourceRelativePath) : relativeFile;
|
||||
if (!destinationRelativePath) {
|
||||
continue;
|
||||
}
|
||||
const destinationPath = path.join(options.destinationDir, destinationRelativePath);
|
||||
operations.push(buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: options.strategy || 'preserve-relative-path',
|
||||
}));
|
||||
operations.push(
|
||||
buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: options.strategy || 'preserve-relative-path'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return relativeFiles.length;
|
||||
@@ -181,13 +172,15 @@ function addFileCopyOperation(operations, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
operations.push(buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath: options.sourceRelativePath,
|
||||
destinationPath: options.destinationPath,
|
||||
strategy: options.strategy || 'preserve-relative-path',
|
||||
}));
|
||||
operations.push(
|
||||
buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath: options.sourceRelativePath,
|
||||
destinationPath: options.destinationPath,
|
||||
strategy: options.strategy || 'preserve-relative-path'
|
||||
})
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -218,7 +211,7 @@ function addCursorAgentDataScaffoldOperations(operations, options) {
|
||||
sourceRoot: options.sourceRoot,
|
||||
sourceRelativePath: path.join('scaffolds', 'cursor', 'ecc-agent-data.json'),
|
||||
destinationPath: path.join(options.targetRoot, 'ecc-agent-data.json'),
|
||||
strategy: 'preserve-relative-path',
|
||||
strategy: 'preserve-relative-path'
|
||||
});
|
||||
|
||||
addFileCopyOperation(operations, {
|
||||
@@ -226,21 +219,17 @@ function addCursorAgentDataScaffoldOperations(operations, options) {
|
||||
sourceRoot: options.sourceRoot,
|
||||
sourceRelativePath: path.join('scaffolds', 'cursor', 'rules', 'ecc-agent-data-home.mdc'),
|
||||
destinationPath: path.join(options.targetRoot, 'rules', 'ecc-agent-data-home.mdc'),
|
||||
strategy: 'preserve-relative-path',
|
||||
strategy: 'preserve-relative-path'
|
||||
});
|
||||
|
||||
addJsonMergeOperation(operations, {
|
||||
moduleId: options.moduleId,
|
||||
sourceRoot: options.sourceRoot,
|
||||
sourceRelativePath: path.join('scaffolds', 'cursor', 'hooks.json'),
|
||||
destinationPath: path.join(options.targetRoot, 'hooks.json'),
|
||||
destinationPath: path.join(options.targetRoot, 'hooks.json')
|
||||
});
|
||||
|
||||
const cursorSessionHookDeps = [
|
||||
path.join('scripts', 'hooks', 'cursor-session-env.js'),
|
||||
path.join('scripts', 'lib', 'agent-data-home.js'),
|
||||
path.join('scripts', 'lib', 'utils.js'),
|
||||
];
|
||||
const cursorSessionHookDeps = [path.join('scripts', 'hooks', 'cursor-session-env.js'), path.join('scripts', 'lib', 'agent-data-home.js'), path.join('scripts', 'lib', 'utils.js')];
|
||||
|
||||
for (const sourceRelativePath of cursorSessionHookDeps) {
|
||||
addFileCopyOperation(operations, {
|
||||
@@ -248,7 +237,7 @@ function addCursorAgentDataScaffoldOperations(operations, options) {
|
||||
sourceRoot: options.sourceRoot,
|
||||
sourceRelativePath,
|
||||
destinationPath: path.join(options.targetRoot, sourceRelativePath),
|
||||
strategy: 'preserve-relative-path',
|
||||
strategy: 'preserve-relative-path'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -267,7 +256,7 @@ function addJsonMergeOperation(operations, options) {
|
||||
strategy: 'merge-json',
|
||||
ownership: 'managed',
|
||||
scaffoldOnly: false,
|
||||
mergePayload: readJsonObject(sourcePath, options.sourceRelativePath),
|
||||
mergePayload: readJsonObject(sourcePath, options.sourceRelativePath)
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -279,7 +268,8 @@ function addMatchingRuleOperations(operations, options) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(sourceDir, { withFileTypes: true })
|
||||
const files = fs
|
||||
.readdirSync(sourceDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isFile() && options.matcher(entry.name))
|
||||
.map(entry => entry.name)
|
||||
.sort();
|
||||
@@ -287,18 +277,17 @@ function addMatchingRuleOperations(operations, options) {
|
||||
for (const fileName of files) {
|
||||
const sourceRelativePath = path.join(options.sourceRelativeDir, fileName);
|
||||
const sourcePath = path.join(options.sourceRoot, sourceRelativePath);
|
||||
const destinationPath = path.join(
|
||||
options.destinationDir,
|
||||
options.rename ? options.rename(fileName) : fileName
|
||||
);
|
||||
const destinationPath = path.join(options.destinationDir, options.rename ? options.rename(fileName) : fileName);
|
||||
|
||||
operations.push(buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: options.strategy || 'flatten-copy',
|
||||
}));
|
||||
operations.push(
|
||||
buildCopyFileOperation({
|
||||
moduleId: options.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: options.strategy || 'flatten-copy'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return files.length;
|
||||
@@ -324,7 +313,7 @@ function planClaudeStyleLegacyInstall(context, { adapterId, adapterRootInput, ru
|
||||
moduleId: 'legacy-claude-rules',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('rules', 'common'),
|
||||
destinationDir: path.join(rulesDir, 'common'),
|
||||
destinationDir: path.join(rulesDir, 'common')
|
||||
});
|
||||
|
||||
for (const language of context.languages) {
|
||||
@@ -343,7 +332,7 @@ function planClaudeStyleLegacyInstall(context, { adapterId, adapterRootInput, ru
|
||||
moduleId: 'legacy-claude-rules',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('rules', language),
|
||||
destinationDir: path.join(rulesDir, language),
|
||||
destinationDir: path.join(rulesDir, language)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,7 +345,7 @@ function planClaudeStyleLegacyInstall(context, { adapterId, adapterRootInput, ru
|
||||
installStatePath,
|
||||
operations,
|
||||
warnings,
|
||||
selectedModules: ['legacy-claude-rules'],
|
||||
selectedModules: ['legacy-claude-rules']
|
||||
};
|
||||
}
|
||||
|
||||
@@ -364,7 +353,7 @@ function planClaudeLegacyInstall(context) {
|
||||
return planClaudeStyleLegacyInstall(context, {
|
||||
adapterId: 'claude',
|
||||
adapterRootInput: { homeDir: context.homeDir },
|
||||
rulesDir: context.claudeRulesDir || null,
|
||||
rulesDir: context.claudeRulesDir || null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,7 +361,7 @@ function planClaudeProjectLegacyInstall(context) {
|
||||
return planClaudeStyleLegacyInstall(context, {
|
||||
adapterId: 'claude-project',
|
||||
adapterRootInput: { repoRoot: context.projectRoot },
|
||||
rulesDir: null,
|
||||
rulesDir: null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -388,14 +377,12 @@ function planCursorLegacyInstall(context) {
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'rules'),
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
matcher: fileName => /^common-.*\.md$/.test(fileName),
|
||||
matcher: fileName => /^common-.*\.md$/.test(fileName)
|
||||
});
|
||||
|
||||
for (const language of context.languages) {
|
||||
if (!LANGUAGE_NAME_PATTERN.test(language)) {
|
||||
warnings.push(
|
||||
`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`
|
||||
);
|
||||
warnings.push(`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -404,7 +391,7 @@ function planCursorLegacyInstall(context) {
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'rules'),
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
matcher: fileName => fileName.startsWith(`${language}-`) && fileName.endsWith('.md'),
|
||||
matcher: fileName => fileName.startsWith(`${language}-`) && fileName.endsWith('.md')
|
||||
});
|
||||
|
||||
if (matches === 0) {
|
||||
@@ -417,44 +404,44 @@ function planCursorLegacyInstall(context) {
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'agents'),
|
||||
destinationDir: path.join(targetRoot, 'agents'),
|
||||
destinationRelativePathTransform: toCursorAgentRelativePath,
|
||||
destinationRelativePathTransform: toCursorAgentRelativePath
|
||||
});
|
||||
addRecursiveCopyOperations(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'skills'),
|
||||
destinationDir: path.join(targetRoot, 'skills'),
|
||||
destinationDir: path.join(targetRoot, 'skills')
|
||||
});
|
||||
addRecursiveCopyOperations(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'commands'),
|
||||
destinationDir: path.join(targetRoot, 'commands'),
|
||||
destinationDir: path.join(targetRoot, 'commands')
|
||||
});
|
||||
addRecursiveCopyOperations(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: path.join('.cursor', 'hooks'),
|
||||
destinationDir: path.join(targetRoot, 'hooks'),
|
||||
destinationDir: path.join(targetRoot, 'hooks')
|
||||
});
|
||||
|
||||
addFileCopyOperation(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativePath: path.join('.cursor', 'hooks.json'),
|
||||
destinationPath: path.join(targetRoot, 'hooks.json'),
|
||||
destinationPath: path.join(targetRoot, 'hooks.json')
|
||||
});
|
||||
addJsonMergeOperation(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativePath: '.mcp.json',
|
||||
destinationPath: path.join(targetRoot, 'mcp.json'),
|
||||
destinationPath: path.join(targetRoot, 'mcp.json')
|
||||
});
|
||||
|
||||
addCursorAgentDataScaffoldOperations(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
targetRoot,
|
||||
targetRoot
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -466,7 +453,7 @@ function planCursorLegacyInstall(context) {
|
||||
installStatePath,
|
||||
operations,
|
||||
warnings,
|
||||
selectedModules: ['legacy-cursor-install'],
|
||||
selectedModules: ['legacy-cursor-install']
|
||||
};
|
||||
}
|
||||
|
||||
@@ -478,9 +465,7 @@ function planAntigravityLegacyInstall(context) {
|
||||
const warnings = [];
|
||||
|
||||
if (isDirectoryNonEmpty(path.join(targetRoot, 'rules'))) {
|
||||
warnings.push(
|
||||
`Destination ${path.join(targetRoot, 'rules')}/ already exists and files may be overwritten`
|
||||
);
|
||||
warnings.push(`Destination ${path.join(targetRoot, 'rules')}/ already exists and files may be overwritten`);
|
||||
}
|
||||
|
||||
addMatchingRuleOperations(operations, {
|
||||
@@ -489,14 +474,12 @@ function planAntigravityLegacyInstall(context) {
|
||||
sourceRelativeDir: path.join('rules', 'common'),
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
matcher: fileName => fileName.endsWith('.md'),
|
||||
rename: fileName => `common-${fileName}`,
|
||||
rename: fileName => `common-${fileName}`
|
||||
});
|
||||
|
||||
for (const language of context.languages) {
|
||||
if (!LANGUAGE_NAME_PATTERN.test(language)) {
|
||||
warnings.push(
|
||||
`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`
|
||||
);
|
||||
warnings.push(`Invalid language name '${language}'. Only alphanumeric, dash, and underscore are allowed`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -512,7 +495,7 @@ function planAntigravityLegacyInstall(context) {
|
||||
sourceRelativeDir: path.join('rules', language),
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
matcher: fileName => fileName.endsWith('.md'),
|
||||
rename: fileName => `${language}-${fileName}`,
|
||||
rename: fileName => `${language}-${fileName}`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -520,19 +503,19 @@ function planAntigravityLegacyInstall(context) {
|
||||
moduleId: 'legacy-antigravity-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: 'commands',
|
||||
destinationDir: path.join(targetRoot, 'workflows'),
|
||||
destinationDir: path.join(targetRoot, 'workflows')
|
||||
});
|
||||
addRecursiveCopyOperations(operations, {
|
||||
moduleId: 'legacy-antigravity-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: 'agents',
|
||||
destinationDir: path.join(targetRoot, 'skills'),
|
||||
destinationDir: path.join(targetRoot, 'skills')
|
||||
});
|
||||
addRecursiveCopyOperations(operations, {
|
||||
moduleId: 'legacy-antigravity-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativeDir: 'skills',
|
||||
destinationDir: path.join(targetRoot, 'skills'),
|
||||
destinationDir: path.join(targetRoot, 'skills')
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -544,7 +527,7 @@ function planAntigravityLegacyInstall(context) {
|
||||
installStatePath,
|
||||
operations,
|
||||
warnings,
|
||||
selectedModules: ['legacy-antigravity-install'],
|
||||
selectedModules: ['legacy-antigravity-install']
|
||||
};
|
||||
}
|
||||
|
||||
@@ -561,7 +544,7 @@ function createLegacyInstallPlan(options = {}) {
|
||||
projectRoot,
|
||||
homeDir,
|
||||
languages: Array.isArray(options.languages) ? options.languages : [],
|
||||
claudeRulesDir: options.claudeRulesDir || process.env.CLAUDE_RULES_DIR || null,
|
||||
claudeRulesDir: options.claudeRulesDir || process.env.CLAUDE_RULES_DIR || null
|
||||
};
|
||||
|
||||
let plan;
|
||||
@@ -578,7 +561,7 @@ function createLegacyInstallPlan(options = {}) {
|
||||
const source = {
|
||||
repoVersion: getPackageVersion(sourceRoot),
|
||||
repoCommit: getRepoCommit(sourceRoot),
|
||||
manifestVersion: getManifestVersion(sourceRoot),
|
||||
manifestVersion: getManifestVersion(sourceRoot)
|
||||
};
|
||||
|
||||
const statePreview = createStatePreview({
|
||||
@@ -589,14 +572,14 @@ function createLegacyInstallPlan(options = {}) {
|
||||
profile: null,
|
||||
modules: [],
|
||||
legacyLanguages: context.languages,
|
||||
legacyMode: true,
|
||||
legacyMode: true
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: plan.selectedModules,
|
||||
skippedModules: [],
|
||||
skippedModules: []
|
||||
},
|
||||
operations: plan.operations,
|
||||
source,
|
||||
source
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -605,7 +588,7 @@ function createLegacyInstallPlan(options = {}) {
|
||||
adapter: {
|
||||
id: plan.adapter.id,
|
||||
target: plan.adapter.target,
|
||||
kind: plan.adapter.kind,
|
||||
kind: plan.adapter.kind
|
||||
},
|
||||
targetRoot: plan.targetRoot,
|
||||
installRoot: plan.installRoot,
|
||||
@@ -613,7 +596,7 @@ function createLegacyInstallPlan(options = {}) {
|
||||
warnings: plan.warnings,
|
||||
languages: context.languages,
|
||||
operations: plan.operations,
|
||||
statePreview,
|
||||
statePreview
|
||||
};
|
||||
}
|
||||
|
||||
@@ -621,19 +604,15 @@ function createLegacyCompatInstallPlan(options = {}) {
|
||||
const sourceRoot = options.sourceRoot || getSourceRoot();
|
||||
const projectRoot = options.projectRoot || process.cwd();
|
||||
const target = options.target || 'claude';
|
||||
const includeComponentIds = Array.isArray(options.includeComponentIds)
|
||||
? [...options.includeComponentIds]
|
||||
: [];
|
||||
const excludeComponentIds = Array.isArray(options.excludeComponentIds)
|
||||
? [...options.excludeComponentIds]
|
||||
: [];
|
||||
const includeComponentIds = Array.isArray(options.includeComponentIds) ? [...options.includeComponentIds] : [];
|
||||
const excludeComponentIds = Array.isArray(options.excludeComponentIds) ? [...options.excludeComponentIds] : [];
|
||||
|
||||
validateLegacyTarget(target);
|
||||
|
||||
const selection = resolveLegacyCompatibilitySelection({
|
||||
repoRoot: sourceRoot,
|
||||
target,
|
||||
legacyLanguages: options.legacyLanguages || [],
|
||||
legacyLanguages: options.legacyLanguages || []
|
||||
});
|
||||
|
||||
return createManifestInstallPlan({
|
||||
@@ -651,25 +630,24 @@ function createLegacyCompatInstallPlan(options = {}) {
|
||||
requestModuleIds: [],
|
||||
requestIncludeComponentIds: includeComponentIds,
|
||||
requestExcludeComponentIds: excludeComponentIds,
|
||||
mode: 'legacy-compat',
|
||||
mode: 'legacy-compat'
|
||||
});
|
||||
}
|
||||
|
||||
function materializeScaffoldOperation(sourceRoot, operation) {
|
||||
if (operation.kind === 'merge-json') {
|
||||
return [{
|
||||
kind: 'merge-json',
|
||||
moduleId: operation.moduleId,
|
||||
sourceRelativePath: operation.sourceRelativePath,
|
||||
destinationPath: operation.destinationPath,
|
||||
strategy: operation.strategy || 'merge-json',
|
||||
ownership: operation.ownership || 'managed',
|
||||
scaffoldOnly: Object.hasOwn(operation, 'scaffoldOnly') ? operation.scaffoldOnly : false,
|
||||
mergePayload: readJsonObject(
|
||||
path.join(sourceRoot, operation.sourceRelativePath),
|
||||
operation.sourceRelativePath
|
||||
),
|
||||
}];
|
||||
return [
|
||||
{
|
||||
kind: 'merge-json',
|
||||
moduleId: operation.moduleId,
|
||||
sourceRelativePath: operation.sourceRelativePath,
|
||||
destinationPath: operation.destinationPath,
|
||||
strategy: operation.strategy || 'merge-json',
|
||||
ownership: operation.ownership || 'managed',
|
||||
scaffoldOnly: Object.hasOwn(operation, 'scaffoldOnly') ? operation.scaffoldOnly : false,
|
||||
mergePayload: readJsonObject(path.join(sourceRoot, operation.sourceRelativePath), operation.sourceRelativePath)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const sourcePath = path.join(sourceRoot, operation.sourceRelativePath);
|
||||
@@ -683,13 +661,15 @@ function materializeScaffoldOperation(sourceRoot, operation) {
|
||||
|
||||
const stat = fs.statSync(sourcePath);
|
||||
if (stat.isFile()) {
|
||||
return [buildCopyFileOperation({
|
||||
moduleId: operation.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath: operation.sourceRelativePath,
|
||||
destinationPath: operation.destinationPath,
|
||||
strategy: operation.strategy,
|
||||
})];
|
||||
return [
|
||||
buildCopyFileOperation({
|
||||
moduleId: operation.moduleId,
|
||||
sourcePath,
|
||||
sourceRelativePath: operation.sourceRelativePath,
|
||||
destinationPath: operation.destinationPath,
|
||||
strategy: operation.strategy
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
const relativeFiles = listFilesRecursive(sourcePath).filter(relativeFile => {
|
||||
@@ -703,7 +683,7 @@ function materializeScaffoldOperation(sourceRoot, operation) {
|
||||
sourcePath: path.join(sourcePath, relativeFile),
|
||||
sourceRelativePath,
|
||||
destinationPath: path.join(operation.destinationPath, relativeFile),
|
||||
strategy: operation.strategy,
|
||||
strategy: operation.strategy
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -712,21 +692,19 @@ function createManifestInstallPlan(options = {}) {
|
||||
const sourceRoot = options.sourceRoot || getSourceRoot();
|
||||
const projectRoot = options.projectRoot || process.cwd();
|
||||
const target = options.target || 'claude';
|
||||
const legacyLanguages = Array.isArray(options.legacyLanguages)
|
||||
? [...options.legacyLanguages]
|
||||
: [];
|
||||
const requestProfileId = Object.hasOwn(options, 'requestProfileId')
|
||||
? options.requestProfileId
|
||||
: (options.profileId || null);
|
||||
const requestModuleIds = Object.hasOwn(options, 'requestModuleIds')
|
||||
? [...options.requestModuleIds]
|
||||
: (Array.isArray(options.moduleIds) ? [...options.moduleIds] : []);
|
||||
const legacyLanguages = Array.isArray(options.legacyLanguages) ? [...options.legacyLanguages] : [];
|
||||
const requestProfileId = Object.hasOwn(options, 'requestProfileId') ? options.requestProfileId : options.profileId || null;
|
||||
const requestModuleIds = Object.hasOwn(options, 'requestModuleIds') ? [...options.requestModuleIds] : Array.isArray(options.moduleIds) ? [...options.moduleIds] : [];
|
||||
const requestIncludeComponentIds = Object.hasOwn(options, 'requestIncludeComponentIds')
|
||||
? [...options.requestIncludeComponentIds]
|
||||
: (Array.isArray(options.includeComponentIds) ? [...options.includeComponentIds] : []);
|
||||
: Array.isArray(options.includeComponentIds)
|
||||
? [...options.includeComponentIds]
|
||||
: [];
|
||||
const requestExcludeComponentIds = Object.hasOwn(options, 'requestExcludeComponentIds')
|
||||
? [...options.requestExcludeComponentIds]
|
||||
: (Array.isArray(options.excludeComponentIds) ? [...options.excludeComponentIds] : []);
|
||||
: Array.isArray(options.excludeComponentIds)
|
||||
? [...options.excludeComponentIds]
|
||||
: [];
|
||||
const plan = resolveInstallPlan({
|
||||
repoRoot: sourceRoot,
|
||||
projectRoot,
|
||||
@@ -735,14 +713,14 @@ function createManifestInstallPlan(options = {}) {
|
||||
moduleIds: options.moduleIds || [],
|
||||
includeComponentIds: options.includeComponentIds || [],
|
||||
excludeComponentIds: options.excludeComponentIds || [],
|
||||
target,
|
||||
target
|
||||
});
|
||||
const adapter = getInstallTargetAdapter(target);
|
||||
const operations = plan.operations.flatMap(operation => materializeScaffoldOperation(sourceRoot, operation));
|
||||
const source = {
|
||||
repoVersion: getPackageVersion(sourceRoot),
|
||||
repoCommit: getRepoCommit(sourceRoot),
|
||||
manifestVersion: getManifestVersion(sourceRoot),
|
||||
manifestVersion: getManifestVersion(sourceRoot)
|
||||
};
|
||||
const statePreview = createStatePreview({
|
||||
adapter,
|
||||
@@ -754,14 +732,14 @@ function createManifestInstallPlan(options = {}) {
|
||||
includeComponents: requestIncludeComponentIds,
|
||||
excludeComponents: requestExcludeComponentIds,
|
||||
legacyLanguages,
|
||||
legacyMode: Boolean(options.legacyMode),
|
||||
legacyMode: Boolean(options.legacyMode)
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: plan.selectedModuleIds,
|
||||
skippedModules: plan.skippedModuleIds,
|
||||
skippedModules: plan.skippedModuleIds
|
||||
},
|
||||
operations,
|
||||
source,
|
||||
source
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -770,7 +748,7 @@ function createManifestInstallPlan(options = {}) {
|
||||
adapter: {
|
||||
id: adapter.id,
|
||||
target: adapter.target,
|
||||
kind: adapter.kind,
|
||||
kind: adapter.kind
|
||||
},
|
||||
targetRoot: plan.targetRoot,
|
||||
installRoot: plan.targetRoot,
|
||||
@@ -787,7 +765,7 @@ function createManifestInstallPlan(options = {}) {
|
||||
skippedModuleIds: plan.skippedModuleIds,
|
||||
excludedModuleIds: plan.excludedModuleIds,
|
||||
operations,
|
||||
statePreview,
|
||||
statePreview
|
||||
};
|
||||
}
|
||||
|
||||
@@ -800,5 +778,5 @@ module.exports = {
|
||||
createLegacyInstallPlan,
|
||||
getSourceRoot,
|
||||
listAvailableLanguages,
|
||||
parseInstallArgs,
|
||||
parseInstallArgs
|
||||
};
|
||||
|
||||
@@ -98,9 +98,7 @@ function readLatestContextTokens(transcriptPath, options = {}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tailBytes = Number.isInteger(options.tailBytes) && options.tailBytes > 0
|
||||
? options.tailBytes
|
||||
: DEFAULT_TRANSCRIPT_TAIL_BYTES;
|
||||
const tailBytes = Number.isInteger(options.tailBytes) && options.tailBytes > 0 ? options.tailBytes : DEFAULT_TRANSCRIPT_TAIL_BYTES;
|
||||
|
||||
const tail = readFileTail(transcriptPath, tailBytes);
|
||||
if (!tail) {
|
||||
@@ -124,9 +122,7 @@ function readLatestContextTokens(transcriptPath, options = {}) {
|
||||
|
||||
const tokens = extractUsageTokens(record);
|
||||
if (tokens > 0) {
|
||||
const model = record.message && typeof record.message.model === 'string'
|
||||
? record.message.model
|
||||
: '';
|
||||
const model = record.message && typeof record.message.model === 'string' ? record.message.model : '';
|
||||
return { tokens, model };
|
||||
}
|
||||
}
|
||||
@@ -141,6 +137,15 @@ function readLatestContextTokens(transcriptPath, options = {}) {
|
||||
* suffix); otherwise the standard 200k window.
|
||||
*/
|
||||
function resolveContextWindowTokens(tokens, model) {
|
||||
// Explicit window override wins: 400k models (e.g. Opus 4.x) match neither the
|
||||
// 200k default nor the 1M marker and would otherwise report ~double usage (#2290).
|
||||
// Honor ECC's own knob and Claude Code's native CLAUDE_CODE_AUTO_COMPACT_WINDOW.
|
||||
const env = (typeof process !== 'undefined' && process.env) || {};
|
||||
const envWindow = Number.parseInt(env.ECC_CONTEXT_WINDOW_TOKENS || env.CLAUDE_CODE_AUTO_COMPACT_WINDOW || '', 10);
|
||||
if (Number.isInteger(envWindow) && envWindow > 0) {
|
||||
return envWindow;
|
||||
}
|
||||
|
||||
if (typeof model === 'string' && model.includes(LARGE_WINDOW_MODEL_MARKER)) {
|
||||
return LARGE_CONTEXT_WINDOW_TOKENS;
|
||||
}
|
||||
@@ -169,9 +174,7 @@ function resolveContextThreshold(env, windowTokens) {
|
||||
}
|
||||
}
|
||||
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS
|
||||
? DEFAULT_CONTEXT_THRESHOLD_LARGE
|
||||
: DEFAULT_CONTEXT_THRESHOLD_STANDARD;
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS ? DEFAULT_CONTEXT_THRESHOLD_LARGE : DEFAULT_CONTEXT_THRESHOLD_STANDARD;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,9 +184,7 @@ function resolveContextThreshold(env, windowTokens) {
|
||||
function resolveContextInterval(env) {
|
||||
const raw = env && env.COMPACT_CONTEXT_INTERVAL;
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Number.isInteger(parsed) && parsed > 0 && parsed <= MAX_TOKEN_SETTING
|
||||
? parsed
|
||||
: DEFAULT_CONTEXT_INTERVAL_TOKENS;
|
||||
return Number.isInteger(parsed) && parsed > 0 && parsed <= MAX_TOKEN_SETTING ? parsed : DEFAULT_CONTEXT_INTERVAL_TOKENS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,9 +206,7 @@ function computeContextBucket(tokens, threshold, interval) {
|
||||
* Human-readable label for a context window size (e.g. "200k", "1M").
|
||||
*/
|
||||
function formatWindowLabel(windowTokens) {
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS
|
||||
? '1M'
|
||||
: `${Math.round(windowTokens / 1000)}k`;
|
||||
return windowTokens >= LARGE_CONTEXT_WINDOW_TOKENS ? '1M' : `${Math.round(windowTokens / 1000)}k`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user