Files
oh-my-claudecode/scripts/code-simplifier.mjs
Bellman fc226e7547 chore(release): v4.7.10 - Bedrock routing, team hardening, session search (#1549)
* 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>
2026-03-11 10:12:37 +09:00

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);
});