mirror of
https://fastgit.cc/github.com/Yeachan-Heo/oh-my-claudecode
synced 2026-04-20 21:00:50 +08:00
* feat(setup): add rtk token optimization integration * revert(setup): remove RTK integration from PR #1489 * fix(hooks): fail open on critical context exhaustion * fix(skill): harden omc-teams tmux and agent validation * Add Ralph critic selection for verification * fix(doctor): accept supported omc config keys * feat(openclaw): normalize native clawhip signals * fix: skip legacy agent sync when plugin agents exist * fix(hud): preserve stale usage limits on API failures * Reduce startup OMC guidance context * fix: clean up stale transient files on session end (#1510) - session-end: cleanupTransientState now removes agent-replay-*.jsonl, last-tool-error.json, hud-state.json, hud-stdin-cache.json, idle-notif-cooldown.json, *-stop-breaker.json, cancel-signal files inside session dirs, and empty session directories - keyword-detector: activateState adds project_path field for parity with TypeScript activateUltrawork in bridge.ts - persistent-mode: readStateFileWithSession adds fallback scan of all session directories when session-scoped path lookup fails * fix(hud): reduce transient usage API retry hammering (#1513) * fix(hooks): add missing continue: false to persistent-mode.cjs Stop hook The .cjs Stop hook outputs `{ decision: "block" }` without `continue: false` when blocking stops for active modes. The original fix (#1216) added `continue: false` to the .mjs scripts but the .cjs version was never updated, and subsequent commits (#1306, #1330, #1374, #1480, #1482) did not carry over this fix. Since hooks.json references the .cjs version, the plugin Stop hook has been missing hard-block capability, allowing sessions to end mid-execution. - Add `continue: false` to all 12 blocking outputs in persistent-mode.cjs - Add `continue: false` to context-guard-stop.mjs (1 output) - Add `continue: false` to code-simplifier.mjs and its template (1 each) Closes #1516 * feat(doc-specialist): add first-pass context hub guidance (#1519) * Add skill pipeline handoff metadata (#1520) * chore: remove stale root development artifacts (#1526) * docs(rebrand): update scoped guidance docs (#1527) * docs: rebrand main README to oh-my-openagent (#1528) * docs: rebrand localized readmes to oh-my-openagent (#1529) * revert: undo unauthorized rebrand PRs 1527-1529 * docs: fix outdated install references in REFERENCE.md - Update platform support table: "curl or npm" → "Claude Code Plugin" for macOS/Linux, consistent with the deprecation notice at the top of the Installation section - Replace deprecated curl uninstall one-liner with the plugin uninstall command (/plugin uninstall oh-my-claudecode@oh-my-claudecode) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove stale analytics references * fix(team): port startup hardening from omx * fix(team): finish runtime hardening port * fix(team): resolve worktree mailbox trigger paths * fix(hud): avoid repeated watch mode initialization * feat: add session history search * fix(team): require real startup evidence * fix(routing): normalize explicit model IDs in all code paths (#1415) The prior fix (PR #1464) only normalized auto-injected models but missed two paths where full model IDs like 'claude-sonnet-4-6' leaked through: 1. enforceModel() passed explicitly-provided models as-is without normalizing to CC aliases (sonnet/opus/haiku) 2. buildLaunchArgs() in model-contract.ts passed model directly to --model flag for team worker launches Extract normalizeToCcAlias() helper and apply it in both paths. Add 5 tests covering explicit ID normalization and Bedrock ID handling. Closes #1415 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(release): bump version to v4.7.10 Version bumps: - package.json: 4.7.9 → 4.7.10 - .claude-plugin/plugin.json: 4.7.9 → 4.7.10 - .claude-plugin/marketplace.json: 4.7.9 → 4.7.10 - CHANGELOG.md: Updated release header Includes rebuilt dist artifacts for: - Bedrock model routing fixes - Team runtime hardening - Session history search feature - Lazy agent loading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: ChoKhoOu <38033263+ChoKhoOu@users.noreply.github.com> Co-authored-by: Yong <yong@levvels.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Carlos Cubas <carloscubas82@gmail.com>
194 lines
4.9 KiB
JavaScript
194 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* OMC Code Simplifier Stop Hook (Node.js)
|
|
*
|
|
* Intercepts Stop events to automatically delegate recently modified source files
|
|
* to the code-simplifier agent for cleanup and simplification.
|
|
*
|
|
* Opt-in via ~/.omc/config.json: { "codeSimplifier": { "enabled": true } }
|
|
* Default: disabled (must explicitly opt in)
|
|
*/
|
|
|
|
import {
|
|
existsSync,
|
|
readFileSync,
|
|
writeFileSync,
|
|
mkdirSync,
|
|
unlinkSync,
|
|
} from 'fs';
|
|
import { join } from 'path';
|
|
import { homedir } from 'os';
|
|
import { execSync } from 'child_process';
|
|
import { readStdin } from './lib/stdin.mjs';
|
|
|
|
const DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs'];
|
|
const DEFAULT_MAX_FILES = 10;
|
|
const MARKER_FILENAME = 'code-simplifier-triggered.marker';
|
|
|
|
function readJsonFile(filePath) {
|
|
try {
|
|
if (!existsSync(filePath)) return null;
|
|
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function readOmcConfig() {
|
|
return readJsonFile(join(homedir(), '.omc', 'config.json'));
|
|
}
|
|
|
|
function isEnabled(config) {
|
|
return config?.codeSimplifier?.enabled === true;
|
|
}
|
|
|
|
function getModifiedFiles(cwd, extensions, maxFiles) {
|
|
try {
|
|
const output = execSync('git diff HEAD --name-only', {
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
timeout: 5000,
|
|
});
|
|
|
|
return output
|
|
.trim()
|
|
.split('\n')
|
|
.filter((f) => f.trim().length > 0)
|
|
.filter((f) => extensions.some((ext) => f.endsWith(ext)))
|
|
.slice(0, maxFiles);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function buildMessage(files) {
|
|
const fileList = files.map((f) => ` - ${f}`).join('\n');
|
|
const fileArgs = files.join('\\n');
|
|
return (
|
|
`[CODE SIMPLIFIER] Recently modified files detected. Delegate to the ` +
|
|
`code-simplifier agent to simplify the following files for clarity, ` +
|
|
`consistency, and maintainability (without changing behavior):\n\n` +
|
|
`${fileList}\n\n` +
|
|
`Use: Task(subagent_type="oh-my-claudecode:code-simplifier", ` +
|
|
`prompt="Simplify the recently modified files:\\n${fileArgs}")`
|
|
);
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const input = await readStdin();
|
|
let data = {};
|
|
try {
|
|
data = JSON.parse(input);
|
|
} catch {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
return;
|
|
}
|
|
|
|
const cwd = data.cwd || data.directory || process.cwd();
|
|
const stateDir = join(cwd, '.omc', 'state');
|
|
const config = readOmcConfig();
|
|
|
|
if (!isEnabled(config)) {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
return;
|
|
}
|
|
|
|
const markerPath = join(stateDir, MARKER_FILENAME);
|
|
|
|
// If already triggered this turn, clear marker and allow stop
|
|
if (existsSync(markerPath)) {
|
|
try {
|
|
unlinkSync(markerPath);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
return;
|
|
}
|
|
|
|
const extensions = config?.codeSimplifier?.extensions ?? DEFAULT_EXTENSIONS;
|
|
const maxFiles = config?.codeSimplifier?.maxFiles ?? DEFAULT_MAX_FILES;
|
|
const files = getModifiedFiles(cwd, extensions, maxFiles);
|
|
|
|
if (files.length === 0) {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
return;
|
|
}
|
|
|
|
// Write trigger marker to prevent re-triggering within this turn cycle
|
|
try {
|
|
if (!existsSync(stateDir)) {
|
|
mkdirSync(stateDir, { recursive: true });
|
|
}
|
|
writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
|
|
} catch {
|
|
// best-effort — proceed even if marker write fails
|
|
}
|
|
|
|
process.stdout.write(
|
|
JSON.stringify({ continue: false, decision: 'block', reason: buildMessage(files) }) + '\n',
|
|
);
|
|
} catch (error) {
|
|
try {
|
|
process.stderr.write(`[code-simplifier] Error: ${error?.message || error}\n`);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
} catch {
|
|
process.exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
process.on('uncaughtException', (error) => {
|
|
try {
|
|
process.stderr.write(`[code-simplifier] Uncaught: ${error?.message || error}\n`);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('unhandledRejection', (error) => {
|
|
try {
|
|
process.stderr.write(`[code-simplifier] Unhandled: ${error?.message || error}\n`);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
process.exit(0);
|
|
});
|
|
|
|
// Safety timeout: force exit after 10 seconds to prevent hook from hanging
|
|
const safetyTimeout = setTimeout(() => {
|
|
try {
|
|
process.stderr.write('[code-simplifier] Safety timeout reached, forcing exit\n');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
try {
|
|
process.stdout.write(JSON.stringify({ continue: true }) + '\n');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
process.exit(0);
|
|
}, 10000);
|
|
|
|
main().finally(() => {
|
|
clearTimeout(safetyTimeout);
|
|
});
|