mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-16 06:53:27 +08:00
Merge branch 'main' into feat/auto-update-command
This commit is contained in:
@@ -196,7 +196,9 @@ function findPluginInstall(rootDir) {
|
||||
];
|
||||
const candidateRoots = [
|
||||
path.join(rootDir, '.claude', 'plugins'),
|
||||
path.join(rootDir, '.claude', 'plugins', 'marketplaces'),
|
||||
homeDir && path.join(homeDir, '.claude', 'plugins'),
|
||||
homeDir && path.join(homeDir, '.claude', 'plugins', 'marketplaces'),
|
||||
].filter(Boolean);
|
||||
const candidates = candidateRoots.flatMap((pluginsDir) =>
|
||||
pluginDirs.flatMap((pluginDir) => [
|
||||
|
||||
265
scripts/hooks/gateguard-fact-force.js
Normal file
265
scripts/hooks/gateguard-fact-force.js
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* PreToolUse Hook: GateGuard Fact-Forcing Gate
|
||||
*
|
||||
* Forces Claude to investigate before editing files or running commands.
|
||||
* Instead of asking "are you sure?" (which LLMs always answer "yes"),
|
||||
* this hook demands concrete facts: importers, public API, data schemas.
|
||||
*
|
||||
* The act of investigation creates awareness that self-evaluation never did.
|
||||
*
|
||||
* Gates:
|
||||
* - Edit/Write: list importers, affected API, verify data schemas, quote instruction
|
||||
* - Bash (destructive): list targets, rollback plan, quote instruction
|
||||
* - Bash (routine): quote current instruction (once per session)
|
||||
*
|
||||
* Compatible with run-with-flags.js via module.exports.run().
|
||||
* Cross-platform (Windows, macOS, Linux).
|
||||
*
|
||||
* Full package with config support: pip install gateguard-ai
|
||||
* Repo: https://github.com/zunoworks/gateguard
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Session state — scoped per session to avoid cross-session races.
|
||||
// Uses CLAUDE_SESSION_ID (set by Claude Code) or falls back to PID-based isolation.
|
||||
const STATE_DIR = process.env.GATEGUARD_STATE_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.gateguard');
|
||||
const SESSION_ID = process.env.CLAUDE_SESSION_ID || process.env.ECC_SESSION_ID || `pid-${process.ppid || process.pid}`;
|
||||
const STATE_FILE = path.join(STATE_DIR, `state-${SESSION_ID.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
||||
|
||||
// State expires after 30 minutes of inactivity
|
||||
const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
||||
|
||||
// Maximum checked entries to prevent unbounded growth
|
||||
const MAX_CHECKED_ENTRIES = 500;
|
||||
const MAX_SESSION_KEYS = 50;
|
||||
const ROUTINE_BASH_SESSION_KEY = '__bash_session__';
|
||||
|
||||
const DESTRUCTIVE_BASH = /\b(rm\s+-rf|git\s+reset\s+--hard|git\s+checkout\s+--|git\s+clean\s+-f|drop\s+table|delete\s+from|truncate|git\s+push\s+--force|dd\s+if=)\b/i;
|
||||
|
||||
// --- State management (per-session, atomic writes, bounded) ---
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
if (fs.existsSync(STATE_FILE)) {
|
||||
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
||||
const lastActive = state.last_active || 0;
|
||||
if (Date.now() - lastActive > SESSION_TIMEOUT_MS) {
|
||||
try { fs.unlinkSync(STATE_FILE); } catch (_) { /* ignore */ }
|
||||
return { checked: [], last_active: Date.now() };
|
||||
}
|
||||
return state;
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
return { checked: [], last_active: Date.now() };
|
||||
}
|
||||
|
||||
function pruneCheckedEntries(checked) {
|
||||
if (checked.length <= MAX_CHECKED_ENTRIES) {
|
||||
return checked;
|
||||
}
|
||||
|
||||
const preserved = checked.includes(ROUTINE_BASH_SESSION_KEY) ? [ROUTINE_BASH_SESSION_KEY] : [];
|
||||
const sessionKeys = checked.filter(k => k.startsWith('__') && k !== ROUTINE_BASH_SESSION_KEY);
|
||||
const fileKeys = checked.filter(k => !k.startsWith('__'));
|
||||
const remainingSessionSlots = Math.max(MAX_SESSION_KEYS - preserved.length, 0);
|
||||
const cappedSession = sessionKeys.slice(-remainingSessionSlots);
|
||||
const remainingFileSlots = Math.max(MAX_CHECKED_ENTRIES - preserved.length - cappedSession.length, 0);
|
||||
const cappedFiles = fileKeys.slice(-remainingFileSlots);
|
||||
return [...preserved, ...cappedSession, ...cappedFiles];
|
||||
}
|
||||
|
||||
function saveState(state) {
|
||||
try {
|
||||
state.last_active = Date.now();
|
||||
state.checked = pruneCheckedEntries(state.checked);
|
||||
fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
// Atomic write: temp file + rename prevents partial reads
|
||||
const tmpFile = STATE_FILE + '.tmp.' + process.pid;
|
||||
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2), 'utf8');
|
||||
fs.renameSync(tmpFile, STATE_FILE);
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
function markChecked(key) {
|
||||
const state = loadState();
|
||||
if (!state.checked.includes(key)) {
|
||||
state.checked.push(key);
|
||||
saveState(state);
|
||||
}
|
||||
}
|
||||
|
||||
function isChecked(key) {
|
||||
const state = loadState();
|
||||
const found = state.checked.includes(key);
|
||||
saveState(state);
|
||||
return found;
|
||||
}
|
||||
|
||||
// Prune stale session files older than 1 hour
|
||||
(function pruneStaleFiles() {
|
||||
try {
|
||||
const files = fs.readdirSync(STATE_DIR);
|
||||
const now = Date.now();
|
||||
for (const f of files) {
|
||||
if (!f.startsWith('state-') || !f.endsWith('.json')) continue;
|
||||
const fp = path.join(STATE_DIR, f);
|
||||
const stat = fs.statSync(fp);
|
||||
if (now - stat.mtimeMs > SESSION_TIMEOUT_MS * 2) {
|
||||
fs.unlinkSync(fp);
|
||||
}
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
})();
|
||||
|
||||
// --- Sanitize file path against injection ---
|
||||
|
||||
function sanitizePath(filePath) {
|
||||
// Strip control chars (including null), bidi overrides, and newlines
|
||||
return filePath.replace(/[\x00-\x1f\x7f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, ' ').trim().slice(0, 500);
|
||||
}
|
||||
|
||||
// --- Gate messages ---
|
||||
|
||||
function editGateMsg(filePath) {
|
||||
const safe = sanitizePath(filePath);
|
||||
return [
|
||||
'[Fact-Forcing Gate]',
|
||||
'',
|
||||
`Before editing ${safe}, present these facts:`,
|
||||
'',
|
||||
'1. List ALL files that import/require this file (use Grep)',
|
||||
'2. List the public functions/classes affected by this change',
|
||||
'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',
|
||||
'',
|
||||
'Present the facts, then retry the same operation.'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function writeGateMsg(filePath) {
|
||||
const safe = sanitizePath(filePath);
|
||||
return [
|
||||
'[Fact-Forcing Gate]',
|
||||
'',
|
||||
`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)',
|
||||
'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',
|
||||
'',
|
||||
'Present the facts, then retry the same operation.'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function destructiveBashMsg() {
|
||||
return [
|
||||
'[Fact-Forcing Gate]',
|
||||
'',
|
||||
'Destructive command detected. Before running, present:',
|
||||
'',
|
||||
'1. List all files/data this command will modify or delete',
|
||||
'2. Write a one-line rollback procedure',
|
||||
'3. Quote the user\'s current instruction verbatim',
|
||||
'',
|
||||
'Present the facts, then retry the same operation.'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function routineBashMsg() {
|
||||
return [
|
||||
'[Fact-Forcing Gate]',
|
||||
'',
|
||||
'Quote the user\'s current instruction verbatim.',
|
||||
'Then retry the same operation.'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
// --- Deny helper ---
|
||||
|
||||
function denyResult(reason) {
|
||||
return {
|
||||
stdout: JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
permissionDecision: 'deny',
|
||||
permissionDecisionReason: reason
|
||||
}
|
||||
}),
|
||||
exitCode: 0
|
||||
};
|
||||
}
|
||||
|
||||
// --- Core logic (exported for run-with-flags.js) ---
|
||||
|
||||
function run(rawInput) {
|
||||
let data;
|
||||
try {
|
||||
data = typeof rawInput === 'string' ? JSON.parse(rawInput) : rawInput;
|
||||
} catch (_) {
|
||||
return rawInput; // allow on parse error
|
||||
}
|
||||
|
||||
const rawToolName = data.tool_name || '';
|
||||
const toolInput = data.tool_input || {};
|
||||
// Normalize: case-insensitive matching via lookup map
|
||||
const TOOL_MAP = { 'edit': 'Edit', 'write': 'Write', 'multiedit': 'MultiEdit', 'bash': 'Bash' };
|
||||
const toolName = TOOL_MAP[rawToolName.toLowerCase()] || rawToolName;
|
||||
|
||||
if (toolName === 'Edit' || toolName === 'Write') {
|
||||
const filePath = toolInput.file_path || '';
|
||||
if (!filePath) {
|
||||
return rawInput; // allow
|
||||
}
|
||||
|
||||
if (!isChecked(filePath)) {
|
||||
markChecked(filePath);
|
||||
return denyResult(toolName === 'Edit' ? editGateMsg(filePath) : writeGateMsg(filePath));
|
||||
}
|
||||
|
||||
return rawInput; // allow
|
||||
}
|
||||
|
||||
if (toolName === 'MultiEdit') {
|
||||
const edits = toolInput.edits || [];
|
||||
for (const edit of edits) {
|
||||
const filePath = edit.file_path || '';
|
||||
if (filePath && !isChecked(filePath)) {
|
||||
markChecked(filePath);
|
||||
return denyResult(editGateMsg(filePath));
|
||||
}
|
||||
}
|
||||
return rawInput; // allow
|
||||
}
|
||||
|
||||
if (toolName === 'Bash') {
|
||||
const command = toolInput.command || '';
|
||||
|
||||
if (DESTRUCTIVE_BASH.test(command)) {
|
||||
// Gate destructive commands on first attempt; allow retry after facts presented
|
||||
const key = '__destructive__' + crypto.createHash('sha256').update(command).digest('hex').slice(0, 16);
|
||||
if (!isChecked(key)) {
|
||||
markChecked(key);
|
||||
return denyResult(destructiveBashMsg());
|
||||
}
|
||||
return rawInput; // allow retry after facts presented
|
||||
}
|
||||
|
||||
if (!isChecked(ROUTINE_BASH_SESSION_KEY)) {
|
||||
markChecked(ROUTINE_BASH_SESSION_KEY);
|
||||
return denyResult(routineBashMsg());
|
||||
}
|
||||
|
||||
return rawInput; // allow
|
||||
}
|
||||
|
||||
return rawInput; // allow
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
@@ -27,7 +27,7 @@ Usage: install.sh [--target <${LEGACY_INSTALL_TARGETS.join('|')}>] [--dry-run] [
|
||||
install.sh [--dry-run] [--json] --config <path>
|
||||
|
||||
Targets:
|
||||
claude (default) - Install rules to ~/.claude/rules/
|
||||
claude (default) - Install ECC into ~/.claude/ (hooks, commands, agents, rules, skills)
|
||||
cursor - Install rules, hooks, and bundled Cursor configs to ./.cursor/
|
||||
antigravity - Install rules, workflows, skills, and agents to ./.agent/
|
||||
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
createFlatRuleOperations,
|
||||
createInstallTargetAdapter,
|
||||
createManagedOperation,
|
||||
isForeignPlatformPath,
|
||||
} = require('./helpers');
|
||||
|
||||
function toCursorRuleFileName(fileName, sourceRelativeFile) {
|
||||
if (path.basename(sourceRelativeFile).toLowerCase() === 'readme.md') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fileName.endsWith('.md')
|
||||
? `${fileName.slice(0, -3)}.mdc`
|
||||
: fileName;
|
||||
}
|
||||
|
||||
module.exports = createInstallTargetAdapter({
|
||||
id: 'cursor-project',
|
||||
target: 'cursor',
|
||||
@@ -17,6 +29,7 @@ module.exports = createInstallTargetAdapter({
|
||||
const modules = Array.isArray(input.modules)
|
||||
? input.modules
|
||||
: (input.module ? [input.module] : []);
|
||||
const seenDestinationPaths = new Set();
|
||||
const {
|
||||
repoRoot,
|
||||
projectRoot,
|
||||
@@ -28,23 +41,98 @@ module.exports = createInstallTargetAdapter({
|
||||
homeDir,
|
||||
};
|
||||
const targetRoot = adapter.resolveRoot(planningInput);
|
||||
|
||||
return modules.flatMap(module => {
|
||||
const entries = modules.flatMap((module, moduleIndex) => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths
|
||||
.filter(p => !isForeignPlatformPath(p, adapter.target))
|
||||
.flatMap(sourceRelativePath => {
|
||||
if (sourceRelativePath === 'rules') {
|
||||
return createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
});
|
||||
}
|
||||
.map((sourceRelativePath, pathIndex) => ({
|
||||
module,
|
||||
sourceRelativePath,
|
||||
moduleIndex,
|
||||
pathIndex,
|
||||
}));
|
||||
}).sort((left, right) => {
|
||||
const getPriority = value => {
|
||||
if (value === 'rules') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)];
|
||||
if (value === '.cursor') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 2;
|
||||
};
|
||||
|
||||
const leftPriority = getPriority(left.sourceRelativePath);
|
||||
const rightPriority = getPriority(right.sourceRelativePath);
|
||||
if (leftPriority !== rightPriority) {
|
||||
return leftPriority - rightPriority;
|
||||
}
|
||||
|
||||
if (left.moduleIndex !== right.moduleIndex) {
|
||||
return left.moduleIndex - right.moduleIndex;
|
||||
}
|
||||
|
||||
return left.pathIndex - right.pathIndex;
|
||||
});
|
||||
|
||||
function takeUniqueOperations(operations) {
|
||||
return operations.filter(operation => {
|
||||
if (!operation || !operation.destinationPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seenDestinationPaths.has(operation.destinationPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
seenDestinationPaths.add(operation.destinationPath);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return entries.flatMap(({ module, sourceRelativePath }) => {
|
||||
if (sourceRelativePath === 'rules') {
|
||||
return takeUniqueOperations(createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
destinationNameTransform: toCursorRuleFileName,
|
||||
}));
|
||||
}
|
||||
|
||||
if (sourceRelativePath === '.cursor') {
|
||||
const cursorRoot = path.join(repoRoot, '.cursor');
|
||||
if (!fs.existsSync(cursorRoot) || !fs.statSync(cursorRoot).isDirectory()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const childOperations = fs.readdirSync(cursorRoot, { withFileTypes: true })
|
||||
.sort((left, right) => left.name.localeCompare(right.name))
|
||||
.filter(entry => entry.name !== 'rules')
|
||||
.map(entry => createManagedOperation({
|
||||
moduleId: module.id,
|
||||
sourceRelativePath: path.join('.cursor', entry.name),
|
||||
destinationPath: path.join(targetRoot, entry.name),
|
||||
strategy: 'preserve-relative-path',
|
||||
}));
|
||||
|
||||
const ruleOperations = createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath: '.cursor/rules',
|
||||
destinationDir: path.join(targetRoot, 'rules'),
|
||||
destinationNameTransform: toCursorRuleFileName,
|
||||
});
|
||||
|
||||
return takeUniqueOperations([...childOperations, ...ruleOperations]);
|
||||
}
|
||||
|
||||
return takeUniqueOperations([
|
||||
adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput),
|
||||
]);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -181,7 +181,13 @@ function createNamespacedFlatRuleOperations(adapter, moduleId, sourceRelativePat
|
||||
return operations;
|
||||
}
|
||||
|
||||
function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, destinationDir }) {
|
||||
function createFlatRuleOperations({
|
||||
moduleId,
|
||||
repoRoot,
|
||||
sourceRelativePath,
|
||||
destinationDir,
|
||||
destinationNameTransform,
|
||||
}) {
|
||||
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
||||
const sourceRoot = path.join(repoRoot || '', normalizedSourcePath);
|
||||
|
||||
@@ -201,19 +207,33 @@ function createFlatRuleOperations({ moduleId, repoRoot, sourceRelativePath, dest
|
||||
if (entry.isDirectory()) {
|
||||
const relativeFiles = listRelativeFiles(entryPath);
|
||||
for (const relativeFile of relativeFiles) {
|
||||
const flattenedFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
||||
const defaultFileName = `${namespace}-${normalizeRelativePath(relativeFile).replace(/\//g, '-')}`;
|
||||
const sourceRelativeFile = path.join(normalizedSourcePath, namespace, relativeFile);
|
||||
const flattenedFileName = typeof destinationNameTransform === 'function'
|
||||
? destinationNameTransform(defaultFileName, sourceRelativeFile)
|
||||
: defaultFileName;
|
||||
if (!flattenedFileName) {
|
||||
continue;
|
||||
}
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, namespace, relativeFile),
|
||||
sourceRelativePath: sourceRelativeFile,
|
||||
destinationPath: path.join(destinationDir, flattenedFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const sourceRelativeFile = path.join(normalizedSourcePath, entry.name);
|
||||
const destinationFileName = typeof destinationNameTransform === 'function'
|
||||
? destinationNameTransform(entry.name, sourceRelativeFile)
|
||||
: entry.name;
|
||||
if (!destinationFileName) {
|
||||
continue;
|
||||
}
|
||||
operations.push(createManagedOperation({
|
||||
moduleId,
|
||||
sourceRelativePath: path.join(normalizedSourcePath, entry.name),
|
||||
destinationPath: path.join(destinationDir, entry.name),
|
||||
sourceRelativePath: sourceRelativeFile,
|
||||
destinationPath: path.join(destinationDir, destinationFileName),
|
||||
strategy: 'flatten-copy',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ CODEX_MARKETPLACE_JSON=".agents/plugins/marketplace.json"
|
||||
CODEX_PLUGIN_JSON=".codex-plugin/plugin.json"
|
||||
OPENCODE_PACKAGE_JSON=".opencode/package.json"
|
||||
OPENCODE_PACKAGE_LOCK_JSON=".opencode/package-lock.json"
|
||||
OPENCODE_ECC_HOOKS_PLUGIN=".opencode/plugins/ecc-hooks.ts"
|
||||
README_FILE="README.md"
|
||||
ZH_CN_README_FILE="docs/zh-CN/README.md"
|
||||
SELECTIVE_INSTALL_ARCHITECTURE_DOC="docs/SELECTIVE-INSTALL-ARCHITECTURE.md"
|
||||
@@ -55,7 +56,7 @@ if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then
|
||||
fi
|
||||
|
||||
# Verify versioned manifests exist
|
||||
for FILE in "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"; do
|
||||
for FILE in "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$OPENCODE_ECC_HOOKS_PLUGIN" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"; do
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
echo "Error: $FILE not found"
|
||||
exit 1
|
||||
@@ -217,6 +218,24 @@ update_codex_marketplace_version() {
|
||||
' "$CODEX_MARKETPLACE_JSON" "$VERSION"
|
||||
}
|
||||
|
||||
update_opencode_hook_banner_version() {
|
||||
node -e '
|
||||
const fs = require("fs");
|
||||
const file = process.argv[1];
|
||||
const version = process.argv[2];
|
||||
const current = fs.readFileSync(file, "utf8");
|
||||
const updated = current.replace(
|
||||
/(## Active Plugin: Everything Claude Code v)[0-9]+\.[0-9]+\.[0-9]+/,
|
||||
`$1${version}`
|
||||
);
|
||||
if (updated === current) {
|
||||
console.error(`Error: could not update OpenCode hook banner version in ${file}`);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.writeFileSync(file, updated);
|
||||
' "$OPENCODE_ECC_HOOKS_PLUGIN" "$VERSION"
|
||||
}
|
||||
|
||||
# Update all shipped package/plugin manifests
|
||||
update_version "$ROOT_PACKAGE_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_package_lock_version "$PACKAGE_LOCK_JSON"
|
||||
@@ -231,6 +250,7 @@ update_codex_marketplace_version
|
||||
update_version "$CODEX_PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_version "$OPENCODE_PACKAGE_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
|
||||
update_package_lock_version "$OPENCODE_PACKAGE_LOCK_JSON"
|
||||
update_opencode_hook_banner_version
|
||||
update_readme_version_row "$README_FILE" "Version" "Plugin" "Plugin" "Reference config"
|
||||
update_readme_version_row "$ZH_CN_README_FILE" "版本" "插件" "插件" "参考配置"
|
||||
update_selective_install_repo_version "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"
|
||||
@@ -243,7 +263,7 @@ node tests/scripts/build-opencode.test.js
|
||||
node tests/plugin-manifest.test.js
|
||||
|
||||
# Stage, commit, tag, and push
|
||||
git add "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"
|
||||
git add "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$OPENCODE_ECC_HOOKS_PLUGIN" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"
|
||||
git commit -m "chore: bump plugin version to $VERSION"
|
||||
git tag "v$VERSION"
|
||||
git push origin main "v$VERSION"
|
||||
|
||||
Reference in New Issue
Block a user