From 08a554dc4729dbe02fa7ae76f80041166856e0fb Mon Sep 17 00:00:00 2001 From: Jamkris Date: Tue, 24 Mar 2026 19:53:42 +0900 Subject: [PATCH] feat: migrate and document scripts including harness-audit --- scripts/README.md | 42 ++++ scripts/harness-audit.js | 481 +++++++++++++++++++++++++++++++++++++++ scripts/ko-KR/README.md | 42 ++++ 3 files changed, 565 insertions(+) create mode 100644 scripts/README.md create mode 100755 scripts/harness-audit.js create mode 100644 scripts/ko-KR/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..7356c1d --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,42 @@ +# Scripts Reference + +**Language:** English | [한국어](ko-KR/README.md) + +This directory contains utility scripts for maintaining the Everything Gemini Code repository. + +> **Note:** +> Many legacy installation scripts (`ecc.js`, `claw.js`, `install-plan.js`, etc.) from the previous Claude Code era have been completely removed. Gemini CLI handles environments, context, and installations natively through its Extension architecture (`gemini extensions`). + +--- + +## Available Scripts + +### Governance & Quality + +| Script | Description | +|--------|-------------| +| `harness-audit.js` | Audits the repository against core Gemini CLI harness rules. Checks for the presence of required context optimization documents, eval coverage, required hooks, and security guards.

**Usage:** `node scripts/harness-audit.js --scope repo` | + +### Documentation Generation + +| Script | Description | +|--------|-------------| +| `generate-docs.js` | Scans the `skills/` directory and updates the main `README.md` to list available skills and commands automatically.

**Usage:** `node scripts/generate-docs.js` | +| `generate-command-docs.js` | Scans `.toml` files in the `commands/` directory and creates or updates a Markdown reference index.

**Usage:** `node scripts/generate-command-docs.js` | + +### Migrations & Formatting + +| Script | Description | +|--------|-------------| +| `migrate-commands.js` | A migration tool to assist converting legacy `.md` commands into `.toml` commands required by Gemini CLI.

**Usage:** `node scripts/migrate-commands.js ` | +| `setup-package-manager.js` | Ensures that the correct package manager (e.g., `npm`) and engines are used for the repository to maintain lockfile stability.

**Usage:** `node scripts/setup-package-manager.js` | +| `skill-create-output.js` | Validates and formats LLM outputs when auto-generating new `SKILL.md` contents from git diffs. | + +--- + +## Contributing a Script + +If you add a new script to this directory: +1. Ensure it does not attempt to duplicate Gemini CLI features (like replacing file states or creating custom REPLs). +2. It must be a standalone Node.js script. +3. Update this `README.md` and the Korean counterpart `ko-KR/README.md` with its usage instructions. diff --git a/scripts/harness-audit.js b/scripts/harness-audit.js new file mode 100755 index 0000000..e2450d4 --- /dev/null +++ b/scripts/harness-audit.js @@ -0,0 +1,481 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const REPO_ROOT = path.join(__dirname, '..'); + +const CATEGORIES = [ + 'Tool Coverage', + 'Context Efficiency', + 'Quality Gates', + 'Memory Persistence', + 'Eval Coverage', + 'Security Guardrails', + 'Cost Efficiency', +]; + +function normalizeScope(scope) { + const value = (scope || 'repo').toLowerCase(); + if (!['repo', 'hooks', 'skills', 'commands', 'agents'].includes(value)) { + throw new Error(`Invalid scope: ${scope}`); + } + return value; +} + +function parseArgs(argv) { + const args = argv.slice(2); + const parsed = { + scope: 'repo', + format: 'text', + help: false, + }; + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + + if (arg === '--help' || arg === '-h') { + parsed.help = true; + continue; + } + + if (arg === '--format') { + parsed.format = (args[index + 1] || '').toLowerCase(); + index += 1; + continue; + } + + if (arg === '--scope') { + parsed.scope = normalizeScope(args[index + 1]); + index += 1; + continue; + } + + if (arg.startsWith('--format=')) { + parsed.format = arg.split('=')[1].toLowerCase(); + continue; + } + + if (arg.startsWith('--scope=')) { + parsed.scope = normalizeScope(arg.split('=')[1]); + continue; + } + + if (arg.startsWith('-')) { + throw new Error(`Unknown argument: ${arg}`); + } + + parsed.scope = normalizeScope(arg); + } + + if (!['text', 'json'].includes(parsed.format)) { + throw new Error(`Invalid format: ${parsed.format}. Use text or json.`); + } + + return parsed; +} + +function fileExists(relativePath) { + return fs.existsSync(path.join(REPO_ROOT, relativePath)); +} + +function readText(relativePath) { + return fs.readFileSync(path.join(REPO_ROOT, relativePath), 'utf8'); +} + +function countFiles(relativeDir, extension) { + const dirPath = path.join(REPO_ROOT, relativeDir); + if (!fs.existsSync(dirPath)) { + return 0; + } + + const stack = [dirPath]; + let count = 0; + + while (stack.length > 0) { + const current = stack.pop(); + const entries = fs.readdirSync(current, { withFileTypes: true }); + + for (const entry of entries) { + const nextPath = path.join(current, entry.name); + if (entry.isDirectory()) { + stack.push(nextPath); + } else if (!extension || entry.name.endsWith(extension)) { + count += 1; + } + } + } + + return count; +} + +function safeRead(relativePath) { + try { + return readText(relativePath); + } catch (_error) { + return ''; + } +} + +function getChecks() { + const packageJson = JSON.parse(readText('package.json')); + // Note: For Gemini CLI, commands use toml format + const hooksJson = safeRead('hooks/hooks.json'); + + return [ + { + id: 'tool-hooks-config', + category: 'Tool Coverage', + points: 2, + scopes: ['repo', 'hooks'], + path: 'hooks/hooks.json', + description: 'Hook configuration file exists', + pass: fileExists('hooks/hooks.json'), + fix: 'Create hooks/hooks.json and define baseline hook events.', + }, + { + id: 'tool-hooks-impl-count', + category: 'Tool Coverage', + points: 2, + scopes: ['repo', 'hooks'], + path: 'scripts/hooks/', + description: 'At least 8 hook implementation scripts exist', + pass: countFiles('scripts/hooks', '.js') >= 8, + fix: 'Add missing hook implementations in scripts/hooks/.', + }, + { + id: 'tool-agent-count', + category: 'Tool Coverage', + points: 2, + scopes: ['repo', 'agents'], + path: 'agents/', + description: 'At least 10 agent definitions exist', + pass: countFiles('agents', '.md') >= 10, + fix: 'Add or restore agent definitions under agents/.', + }, + { + id: 'tool-skill-count', + category: 'Tool Coverage', + points: 2, + scopes: ['repo', 'skills'], + path: 'skills/', + description: 'At least 20 skill definitions exist', + pass: countFiles('skills', 'SKILL.md') >= 20, + fix: 'Add missing skill directories with SKILL.md definitions.', + }, + { + id: 'context-strategic-compact', + category: 'Context Efficiency', + points: 3, + scopes: ['repo', 'skills'], + path: 'skills/strategic-compact/SKILL.md', + description: 'Strategic compaction guidance is present', + pass: fileExists('skills/strategic-compact/SKILL.md'), + fix: 'Add strategic context compaction guidance at skills/strategic-compact/SKILL.md.', + }, + { + id: 'context-suggest-compact-hook', + category: 'Context Efficiency', + points: 3, + scopes: ['repo', 'hooks'], + path: 'scripts/hooks/suggest-compact.js', + description: 'Suggest-compact automation hook exists', + pass: fileExists('scripts/hooks/suggest-compact.js'), + fix: 'Implement scripts/hooks/suggest-compact.js for context pressure hints.', + }, + { + id: 'context-model-route', + category: 'Context Efficiency', + points: 2, + scopes: ['repo', 'commands'], + path: 'commands/model-route.toml', + description: 'Model routing command exists', + pass: fileExists('commands/model-route.toml'), + fix: 'Add model-route command guidance in commands/model-route.toml.', + }, + { + id: 'context-token-doc', + category: 'Context Efficiency', + points: 2, + scopes: ['repo'], + path: 'docs/token-optimization.md', + description: 'Token optimization documentation exists', + pass: fileExists('docs/token-optimization.md'), + fix: 'Add docs/token-optimization.md with concrete context-cost controls.', + }, + { + id: 'quality-test-runner', + category: 'Quality Gates', + points: 3, + scopes: ['repo'], + path: 'tests/run-all.js', + description: 'Central test runner exists', + pass: fileExists('tests/run-all.js'), + fix: 'Add tests/run-all.js to enforce complete suite execution.', + }, + { + id: 'quality-ci-validations', + category: 'Quality Gates', + points: 3, + scopes: ['repo'], + path: 'package.json', + description: 'Test script runs validator chain before tests', + pass: typeof packageJson.scripts?.test === 'string' && packageJson.scripts.test.includes('tests/run-all.js'), + fix: 'Update package.json test script to run tests/run-all.js.', + }, + { + id: 'quality-hook-tests', + category: 'Quality Gates', + points: 2, + scopes: ['repo', 'hooks'], + path: 'tests/hooks/hooks.test.js', + description: 'Hook coverage test file exists', + pass: fileExists('tests/hooks/hooks.test.js'), + fix: 'Add tests/hooks/hooks.test.js for hook behavior validation.', + }, + { + id: 'memory-hooks-dir', + category: 'Memory Persistence', + points: 4, + scopes: ['repo', 'hooks'], + path: 'hooks/memory-persistence/', + description: 'Memory persistence hooks directory exists', + pass: fileExists('hooks/memory-persistence'), + fix: 'Add hooks/memory-persistence with lifecycle hook definitions.', + }, + { + id: 'memory-session-hooks', + category: 'Memory Persistence', + points: 4, + scopes: ['repo', 'hooks'], + path: 'scripts/hooks/session-start.js', + description: 'Session start/end persistence scripts exist', + pass: fileExists('scripts/hooks/session-start.js') && fileExists('scripts/hooks/session-end.js'), + fix: 'Implement scripts/hooks/session-start.js and scripts/hooks/session-end.js.', + }, + { + id: 'memory-learning-skill', + category: 'Memory Persistence', + points: 2, + scopes: ['repo', 'skills'], + path: 'skills/continuous-learning-v2/SKILL.md', + description: 'Continuous learning v2 skill exists', + pass: fileExists('skills/continuous-learning-v2/SKILL.md'), + fix: 'Add skills/continuous-learning-v2/SKILL.md for memory evolution flow.', + }, + { + id: 'eval-skill', + category: 'Eval Coverage', + points: 4, + scopes: ['repo', 'skills'], + path: 'skills/eval-harness/SKILL.md', + description: 'Eval harness skill exists', + pass: fileExists('skills/eval-harness/SKILL.md'), + fix: 'Add skills/eval-harness/SKILL.md for pass/fail regression evaluation.', + }, + { + id: 'eval-commands', + category: 'Eval Coverage', + points: 4, + scopes: ['repo', 'commands'], + path: 'commands/eval.toml', + description: 'Eval and verification commands exist', + pass: fileExists('commands/eval.toml') && fileExists('commands/verify.toml') && fileExists('commands/checkpoint.toml'), + fix: 'Add eval/checkpoint/verify commands to standardize verification loops.', + }, + { + id: 'eval-tests-presence', + category: 'Eval Coverage', + points: 2, + scopes: ['repo'], + path: 'tests/', + description: 'At least 10 test files exist', + pass: countFiles('tests', '.test.js') >= 10, + fix: 'Increase automated test coverage across scripts/hooks/lib.', + }, + { + id: 'security-review-skill', + category: 'Security Guardrails', + points: 3, + scopes: ['repo', 'skills'], + path: 'skills/security-review/SKILL.md', + description: 'Security review skill exists', + pass: fileExists('skills/security-review/SKILL.md'), + fix: 'Add skills/security-review/SKILL.md for security checklist coverage.', + }, + { + id: 'security-agent', + category: 'Security Guardrails', + points: 3, + scopes: ['repo', 'agents'], + path: 'agents/security-reviewer.md', + description: 'Security reviewer agent exists', + pass: fileExists('agents/security-reviewer.md'), + fix: 'Add agents/security-reviewer.md for delegated security audits.', + }, + { + id: 'security-prompt-hook', + category: 'Security Guardrails', + points: 2, + scopes: ['repo', 'hooks'], + path: 'hooks/hooks.json', + description: 'Hooks include prompt submission guardrail event references', + pass: hooksJson.includes('beforeSubmitPrompt') || hooksJson.includes('PreToolUse'), + fix: 'Add prompt/tool preflight security guards in hooks/hooks.json.', + }, + { + id: 'security-scan-command', + category: 'Security Guardrails', + points: 2, + scopes: ['repo', 'commands'], + path: 'commands/security-scan.toml', + description: 'Security scan command exists', + pass: fileExists('commands/security-scan.toml'), + fix: 'Add commands/security-scan.toml with scan and remediation workflow.', + }, + { + id: 'cost-skill', + category: 'Cost Efficiency', + points: 4, + scopes: ['repo', 'skills'], + path: 'skills/cost-aware-llm-pipeline/SKILL.md', + description: 'Cost-aware LLM skill exists', + pass: fileExists('skills/cost-aware-llm-pipeline/SKILL.md'), + fix: 'Add skills/cost-aware-llm-pipeline/SKILL.md for budget-aware routing.', + }, + { + id: 'cost-doc', + category: 'Cost Efficiency', + points: 3, + scopes: ['repo'], + path: 'docs/token-optimization.md', + description: 'Cost optimization documentation exists', + pass: fileExists('docs/token-optimization.md'), + fix: 'Create docs/token-optimization.md with target settings and tradeoffs.', + }, + ]; +} + +function summarizeCategoryScores(checks) { + const scores = {}; + for (const category of CATEGORIES) { + const inCategory = checks.filter(check => check.category === category); + const max = inCategory.reduce((sum, check) => sum + check.points, 0); + const earned = inCategory + .filter(check => check.pass) + .reduce((sum, check) => sum + check.points, 0); + + const normalized = max === 0 ? 0 : Math.round((earned / max) * 10); + scores[category] = { + score: normalized, + earned, + max, + }; + } + + return scores; +} + +function buildReport(scope) { + const checks = getChecks().filter(check => check.scopes.includes(scope)); + const categoryScores = summarizeCategoryScores(checks); + const maxScore = checks.reduce((sum, check) => sum + check.points, 0); + const overallScore = checks + .filter(check => check.pass) + .reduce((sum, check) => sum + check.points, 0); + + const failedChecks = checks.filter(check => !check.pass); + const topActions = failedChecks + .sort((left, right) => right.points - left.points) + .slice(0, 3) + .map(check => ({ + action: check.fix, + path: check.path, + category: check.category, + points: check.points, + })); + + return { + scope, + deterministic: true, + rubric_version: '2026-03-24', + overall_score: overallScore, + max_score: maxScore, + categories: categoryScores, + checks: checks.map(check => ({ + id: check.id, + category: check.category, + points: check.points, + path: check.path, + description: check.description, + pass: check.pass, + })), + top_actions: topActions, + }; +} + +function printText(report) { + console.log(`Gemini Harness Audit (${report.scope}): ${report.overall_score}/${report.max_score}`); + console.log(''); + + for (const category of CATEGORIES) { + const data = report.categories[category]; + if (!data || data.max === 0) { + continue; + } + + console.log(`- ${category}: ${data.score}/10 (${data.earned}/${data.max} pts)`); + } + + const failed = report.checks.filter(check => !check.pass); + console.log(''); + console.log(`Checks: ${report.checks.length} total, ${failed.length} failing`); + + if (failed.length > 0) { + console.log(''); + console.log('Top Actions to resolve:'); + report.top_actions.forEach((action, index) => { + console.log(`${index + 1}) [${action.category}] ${action.action} (${action.path})`); + }); + } +} + +function showHelp(exitCode = 0) { + console.log(` +Usage: node scripts/harness-audit.js [scope] [--scope ] [--format ] + +Deterministic Gemini harness audit based on explicit file/rule checks. +`); + process.exit(exitCode); +} + +function main() { + try { + const args = parseArgs(process.argv); + + if (args.help) { + showHelp(0); + return; + } + + const report = buildReport(args.scope); + + if (args.format === 'json') { + console.log(JSON.stringify(report, null, 2)); + } else { + printText(report); + } + } catch (error) { + console.error(`Error: ${error.message}`); + process.exit(1); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { + buildReport, + parseArgs, +}; diff --git a/scripts/ko-KR/README.md b/scripts/ko-KR/README.md new file mode 100644 index 0000000..9a4a684 --- /dev/null +++ b/scripts/ko-KR/README.md @@ -0,0 +1,42 @@ +# 스크립트 레퍼런스 + +**언어:** [English](../README.md) | 한국어 + +이 디렉토리는 Everything Gemini Code 저장소를 유지 관리하기 위한 유틸리티 스크립트 모음입니다. + +> **참고:** +> 이전 Claude Code 시절의 레거시 설치 스크립트(`ecc.js`, `claw.js`, `install-plan.js` 등)는 완전히 제거되었습니다. Gemini CLI는 자체 Extension 시스템(`gemini extensions`)을 통해 환경, 컨텍스트 및 설치 상태를 기본적으로 관리합니다. + +--- + +## 사용 가능한 스크립트 + +### 거버넌스 및 품질 + +| 스크립트 | 설명 | +|----------|------| +| `harness-audit.js` | 핵심 Gemini CLI 규칙에 대한 저장소 상태를 검사(Audit)합니다. 토큰/컨텍스트 최적화 가이드 제공 여부, 훅 구성 파일, Eval 커버리지, 보안 가드레일 등을 평가합니다.

**사용법:** `node scripts/harness-audit.js --scope repo` | + +### 문서 자동 생성 + +| 스크립트 | 설명 | +|----------|------| +| `generate-docs.js` | `skills/` 디렉토리를 스캔하여 메인 `README.md`에 지원하는 스킬 및 커맨드 목록을 자동으로 렌더링하고 동기화합니다.

**사용법:** `node scripts/generate-docs.js` | +| `generate-command-docs.js` | `commands/` 디렉토리 내의 `.toml` 명령 파일들을 스캔하여 마크다운 레퍼런스 인덱스 문서를 생성하거나 업데이트합니다.

**사용법:** `node scripts/generate-command-docs.js` | + +### 마이그레이션 및 포매팅 + +| 스크립트 | 설명 | +|----------|------| +| `migrate-commands.js` | 과거 Claude 기반의 `.md` 형식 명령어들을 Gemini CLI에서 요구하는 `.toml` 형식으로 변환하는 과정을 도와줍니다.

**사용법:** `node scripts/migrate-commands.js ` | +| `setup-package-manager.js` | 저장소 관리에 일관된 패키지 매니저(예: `npm`, `pnpm` 등)와 올바른 Node 버전이 사용되도록 강제하여 잠금 파일(`package-lock.json`) 변경 시 충돌을 방지합니다.

**사용법:** `node scripts/setup-package-manager.js` | +| `skill-create-output.js` | Git 변경 내역을 바탕으로 새로운 `SKILL.md`를 자동 생성할 때 LLM의 응답 출력 포맷을 검증하고, 불필요한 마크다운을 린팅하는 포매터입니다. | + +--- + +## 새 스크립트 기여 방법 + +이 디렉토리에 새로운 스크립트를 추가하시려면 아래 규칙을 따라주세요. +1. Gemini CLI의 기본 동작(명령어 교체 상태 관리, 파일 조작 핫스와핑, 전용 REPL 등)을 흉내내거나 중복 구현하려 시도하지 마세요. +2. 독립적으로 실행 가능한 Node.js 단위 스크립트여야 합니다. +3. 스크립트의 실행 방법과 목적을 이 `ko-KR/README.md` 문서와 영문 `README.md` 문서에 각각 업데이트해 주세요.