Compare commits

...

12 Commits

Author SHA1 Message Date
Affaan Mustafa
1b4597a3d6 fix: audit consumer projects from cwd 2026-03-29 21:38:21 -04:00
Affaan Mustafa
dd675d4258 Merge pull request #1007 from AndriyKalashnykov/chore/pin-actions-and-update-claude-md
chore: pin actions to commit SHAs and add Skills section to CLAUDE.md
2026-03-29 21:16:09 -04:00
Affaan Mustafa
db12d3d838 Merge pull request #1004 from ohashi-mizuki/fix/pre-push-skip-branch-deletion
fix: skip pre-push checks on branch deletion
2026-03-29 21:16:01 -04:00
Andriy Kalashnykov
46f37ae4fb chore: pin actions to commit SHAs and add Skills section to CLAUDE.md
Pin all GitHub Actions to commit SHAs instead of mutable version tags
across ci.yml, release.yml, maintenance.yml, and all reusable workflows.
This prevents supply-chain attacks via tag hijacking.

Add the required Skills section to CLAUDE.md mapping project files
(README.md, .github/workflows/*.yml) to their respective review skills.
2026-03-29 17:16:56 -04:00
ohashi-mizuki
0c166e14da fix: skip pre-push checks on branch deletion
The pre-push hook runs lint/typecheck/test/build checks on every push,
including `git push origin --delete <branch>`. Branch deletion does not
push any code, so verification checks are unnecessary and block the
delete operation.

Detect deletion pushes by reading stdin (local sha is all zeros for
deletes) and exit early.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:09:26 +09:00
Affaan Mustafa
527c79350c Merge pull request #1000 from affaan-m/fix/codex-context7-compat-tests
fix(codex): broaden context7 config checks
2026-03-29 00:26:32 -04:00
Affaan Mustafa
bec1ebf76d Merge pull request #999 from affaan-m/fix/clv2-config-override-rebase
fix(clv2): honor CLV2_CONFIG in start-observer
2026-03-29 00:22:23 -04:00
Affaan Mustafa
be76918850 fix(clv2): honor CLV2_CONFIG in start-observer 2026-03-29 00:21:55 -04:00
Affaan Mustafa
99a154a908 Merge pull request #998 from affaan-m/fix/token-budget-advisor-trigger-clarity
fix(skills): clarify token-budget-advisor triggers
2026-03-29 00:20:29 -04:00
Affaan Mustafa
ebf0f135bb fix(skills): clarify token-budget-advisor triggers 2026-03-29 00:20:04 -04:00
Affaan Mustafa
2d27da52e2 Merge pull request #997 from affaan-m/fix/readme-agent-count-tree
docs(readme): fix agent count in repo tree
2026-03-29 00:20:01 -04:00
Affaan Mustafa
65c4a0f6ba docs(readme): fix agent count in repo tree 2026-03-29 00:19:52 -04:00
15 changed files with 415 additions and 114 deletions

View File

@@ -34,10 +34,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node }}
@@ -68,7 +68,7 @@ jobs:
- name: Cache npm
if: matrix.pm == 'npm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
@@ -83,7 +83,7 @@ jobs:
- name: Cache pnpm
if: matrix.pm == 'pnpm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -104,7 +104,7 @@ jobs:
- name: Cache yarn
if: matrix.pm == 'yarn'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
@@ -113,7 +113,7 @@ jobs:
- name: Cache bun
if: matrix.pm == 'bun'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
@@ -146,7 +146,7 @@ jobs:
# Upload test artifacts on failure
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
path: |
@@ -160,10 +160,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
@@ -205,10 +205,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
@@ -223,10 +223,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'

View File

@@ -15,8 +15,8 @@ jobs:
name: Check Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
- name: Check for outdated packages
@@ -26,8 +26,8 @@ jobs:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20.x'
- name: Run security audit
@@ -43,7 +43,7 @@ jobs:
name: Stale Issues/PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
stale-issue-message: 'This issue is stale due to inactivity.'
stale-pr-message: 'This PR is stale due to inactivity.'

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

View File

@@ -27,10 +27,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ inputs.node-version }}
@@ -59,7 +59,7 @@ jobs:
- name: Cache npm
if: inputs.package-manager == 'npm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}
@@ -74,7 +74,7 @@ jobs:
- name: Cache pnpm
if: inputs.package-manager == 'pnpm'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -95,7 +95,7 @@ jobs:
- name: Cache yarn
if: inputs.package-manager == 'yarn'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ inputs.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
@@ -104,7 +104,7 @@ jobs:
- name: Cache bun
if: inputs.package-manager == 'bun'
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
@@ -134,7 +134,7 @@ jobs:
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-results-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
path: |

View File

@@ -17,10 +17,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ inputs.node-version }}

View File

@@ -4,22 +4,23 @@ Run a deterministic repository harness audit and return a prioritized scorecard.
## Usage
`/harness-audit [scope] [--format text|json]`
`/harness-audit [scope] [--format text|json] [--root path]`
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
- `--format`: output style (`text` default, `json` for automation)
- `--root`: audit a specific path instead of the current working directory
## Deterministic Engine
Always run:
```bash
node scripts/harness-audit.js <scope> --format <text|json>
node scripts/harness-audit.js <scope> --format <text|json> [--root <path>]
```
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
Rubric version: `2026-03-16`.
Rubric version: `2026-03-30`.
The script computes 7 fixed categories (`0-10` normalized each):
@@ -32,6 +33,7 @@ The script computes 7 fixed categories (`0-10` normalized each):
7. Cost Efficiency
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
The script audits the current working directory by default and auto-detects whether the target is the ECC repo itself or a consumer project using ECC.
## Output Contract

View File

@@ -59,3 +59,14 @@ Follow the formats in CONTRIBUTING.md:
- Hooks: JSON with matcher and hooks array
File naming: lowercase with hyphens (e.g., `python-reviewer.md`, `tdd-workflow.md`)
## Skills
Use the following skills when working on related files:
| File(s) | Skill |
|---------|-------|
| `README.md` | `/readme` |
| `.github/workflows/*.yml` | `/ci-workflow` |
When spawning subagents, always pass conventions from the respective skill into the agent's prompt.

View File

@@ -295,7 +295,7 @@ everything-claude-code/
| |-- plugin.json # Plugin metadata and component paths
| |-- marketplace.json # Marketplace catalog for /plugin marketplace add
|
|-- agents/ # 29 specialized subagents for delegation
|-- agents/ # 30 specialized subagents for delegation
| |-- planner.md # Feature implementation planning
| |-- architect.md # System design decisions
| |-- tdd-guide.md # Test-driven development

View File

@@ -4,22 +4,23 @@ Run a deterministic repository harness audit and return a prioritized scorecard.
## Usage
`/harness-audit [scope] [--format text|json]`
`/harness-audit [scope] [--format text|json] [--root path]`
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
- `--format`: output style (`text` default, `json` for automation)
- `--root`: audit a specific path instead of the current working directory
## Deterministic Engine
Always run:
```bash
node scripts/harness-audit.js <scope> --format <text|json>
node scripts/harness-audit.js <scope> --format <text|json> [--root <path>]
```
This script is the source of truth for scoring and checks. Do not invent additional dimensions or ad-hoc points.
Rubric version: `2026-03-16`.
Rubric version: `2026-03-30`.
The script computes 7 fixed categories (`0-10` normalized each):
@@ -32,6 +33,7 @@ The script computes 7 fixed categories (`0-10` normalized each):
7. Cost Efficiency
Scores are derived from explicit file/rule checks and are reproducible for the same commit.
The script audits the current working directory by default and auto-detects whether the target is the ECC repo itself or a consumer project using ECC.
## Output Contract

View File

@@ -16,6 +16,20 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
exit 0
fi
# Skip checks for branch deletion pushes (e.g., git push origin --delete <branch>).
# The pre-push hook receives lines on stdin: <local ref> <local sha> <remote ref> <remote sha>.
# For deletions, the local sha is the zero OID.
is_delete_only=true
while read -r _local_ref local_sha _remote_ref _remote_sha; do
if [[ "$local_sha" != "0000000000000000000000000000000000000000" ]]; then
is_delete_only=false
break
fi
done
if [[ "$is_delete_only" == "true" ]]; then
exit 0
fi
ran_any_check=0
log() {

View File

@@ -3,8 +3,6 @@
const fs = require('fs');
const path = require('path');
const REPO_ROOT = path.join(__dirname, '..');
const CATEGORIES = [
'Tool Coverage',
'Context Efficiency',
@@ -29,6 +27,7 @@ function parseArgs(argv) {
scope: 'repo',
format: 'text',
help: false,
root: path.resolve(process.env.AUDIT_ROOT || process.cwd()),
};
for (let index = 0; index < args.length; index += 1) {
@@ -51,6 +50,12 @@ function parseArgs(argv) {
continue;
}
if (arg === '--root') {
parsed.root = path.resolve(args[index + 1] || process.cwd());
index += 1;
continue;
}
if (arg.startsWith('--format=')) {
parsed.format = arg.split('=')[1].toLowerCase();
continue;
@@ -61,6 +66,11 @@ function parseArgs(argv) {
continue;
}
if (arg.startsWith('--root=')) {
parsed.root = path.resolve(arg.slice('--root='.length));
continue;
}
if (arg.startsWith('-')) {
throw new Error(`Unknown argument: ${arg}`);
}
@@ -75,16 +85,16 @@ function parseArgs(argv) {
return parsed;
}
function fileExists(relativePath) {
return fs.existsSync(path.join(REPO_ROOT, relativePath));
function fileExists(rootDir, relativePath) {
return fs.existsSync(path.join(rootDir, relativePath));
}
function readText(relativePath) {
return fs.readFileSync(path.join(REPO_ROOT, relativePath), 'utf8');
function readText(rootDir, relativePath) {
return fs.readFileSync(path.join(rootDir, relativePath), 'utf8');
}
function countFiles(relativeDir, extension) {
const dirPath = path.join(REPO_ROOT, relativeDir);
function countFiles(rootDir, relativeDir, extension) {
const dirPath = path.join(rootDir, relativeDir);
if (!fs.existsSync(dirPath)) {
return 0;
}
@@ -109,19 +119,90 @@ function countFiles(relativeDir, extension) {
return count;
}
function safeRead(relativePath) {
function safeRead(rootDir, relativePath) {
try {
return readText(relativePath);
return readText(rootDir, relativePath);
} catch (_error) {
return '';
}
}
function getChecks() {
const packageJson = JSON.parse(readText('package.json'));
const commandPrimary = safeRead('commands/harness-audit.md').trim();
const commandParity = safeRead('.opencode/commands/harness-audit.md').trim();
const hooksJson = safeRead('hooks/hooks.json');
function safeParseJson(text) {
if (!text || !text.trim()) {
return null;
}
try {
return JSON.parse(text);
} catch (_error) {
return null;
}
}
function hasFileWithExtension(rootDir, relativeDir, extensions) {
const dirPath = path.join(rootDir, relativeDir);
if (!fs.existsSync(dirPath)) {
return false;
}
const allowed = Array.isArray(extensions) ? extensions : [extensions];
const stack = [dirPath];
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);
continue;
}
if (allowed.some((extension) => entry.name.endsWith(extension))) {
return true;
}
}
}
return false;
}
function detectTargetMode(rootDir) {
const packageJson = safeParseJson(safeRead(rootDir, 'package.json'));
if (packageJson?.name === 'everything-claude-code') {
return 'repo';
}
if (
fileExists(rootDir, 'scripts/harness-audit.js') &&
fileExists(rootDir, '.claude-plugin/plugin.json') &&
fileExists(rootDir, 'agents') &&
fileExists(rootDir, 'skills')
) {
return 'repo';
}
return 'consumer';
}
function findPluginInstall(rootDir) {
const homeDir = process.env.HOME || '';
const candidates = [
path.join(rootDir, '.claude', 'plugins', 'everything-claude-code', '.claude-plugin', 'plugin.json'),
path.join(rootDir, '.claude', 'plugins', 'everything-claude-code', 'plugin.json'),
homeDir && path.join(homeDir, '.claude', 'plugins', 'everything-claude-code', '.claude-plugin', 'plugin.json'),
homeDir && path.join(homeDir, '.claude', 'plugins', 'everything-claude-code', 'plugin.json'),
].filter(Boolean);
return candidates.find(candidate => fs.existsSync(candidate)) || null;
}
function getRepoChecks(rootDir) {
const packageJson = JSON.parse(readText(rootDir, 'package.json'));
const commandPrimary = safeRead(rootDir, 'commands/harness-audit.md').trim();
const commandParity = safeRead(rootDir, '.opencode/commands/harness-audit.md').trim();
const hooksJson = safeRead(rootDir, 'hooks/hooks.json');
return [
{
@@ -131,7 +212,7 @@ function getChecks() {
scopes: ['repo', 'hooks'],
path: 'hooks/hooks.json',
description: 'Hook configuration file exists',
pass: fileExists('hooks/hooks.json'),
pass: fileExists(rootDir, 'hooks/hooks.json'),
fix: 'Create hooks/hooks.json and define baseline hook events.',
},
{
@@ -141,7 +222,7 @@ function getChecks() {
scopes: ['repo', 'hooks'],
path: 'scripts/hooks/',
description: 'At least 8 hook implementation scripts exist',
pass: countFiles('scripts/hooks', '.js') >= 8,
pass: countFiles(rootDir, 'scripts/hooks', '.js') >= 8,
fix: 'Add missing hook implementations in scripts/hooks/.',
},
{
@@ -151,7 +232,7 @@ function getChecks() {
scopes: ['repo', 'agents'],
path: 'agents/',
description: 'At least 10 agent definitions exist',
pass: countFiles('agents', '.md') >= 10,
pass: countFiles(rootDir, 'agents', '.md') >= 10,
fix: 'Add or restore agent definitions under agents/.',
},
{
@@ -161,7 +242,7 @@ function getChecks() {
scopes: ['repo', 'skills'],
path: 'skills/',
description: 'At least 20 skill definitions exist',
pass: countFiles('skills', 'SKILL.md') >= 20,
pass: countFiles(rootDir, 'skills', 'SKILL.md') >= 20,
fix: 'Add missing skill directories with SKILL.md definitions.',
},
{
@@ -181,7 +262,7 @@ function getChecks() {
scopes: ['repo', 'skills'],
path: 'skills/strategic-compact/SKILL.md',
description: 'Strategic compaction guidance is present',
pass: fileExists('skills/strategic-compact/SKILL.md'),
pass: fileExists(rootDir, 'skills/strategic-compact/SKILL.md'),
fix: 'Add strategic context compaction guidance at skills/strategic-compact/SKILL.md.',
},
{
@@ -191,7 +272,7 @@ function getChecks() {
scopes: ['repo', 'hooks'],
path: 'scripts/hooks/suggest-compact.js',
description: 'Suggest-compact automation hook exists',
pass: fileExists('scripts/hooks/suggest-compact.js'),
pass: fileExists(rootDir, 'scripts/hooks/suggest-compact.js'),
fix: 'Implement scripts/hooks/suggest-compact.js for context pressure hints.',
},
{
@@ -201,7 +282,7 @@ function getChecks() {
scopes: ['repo', 'commands'],
path: 'commands/model-route.md',
description: 'Model routing command exists',
pass: fileExists('commands/model-route.md'),
pass: fileExists(rootDir, 'commands/model-route.md'),
fix: 'Add model-route command guidance in commands/model-route.md.',
},
{
@@ -211,7 +292,7 @@ function getChecks() {
scopes: ['repo'],
path: 'docs/token-optimization.md',
description: 'Token optimization documentation exists',
pass: fileExists('docs/token-optimization.md'),
pass: fileExists(rootDir, 'docs/token-optimization.md'),
fix: 'Add docs/token-optimization.md with concrete context-cost controls.',
},
{
@@ -221,7 +302,7 @@ function getChecks() {
scopes: ['repo'],
path: 'tests/run-all.js',
description: 'Central test runner exists',
pass: fileExists('tests/run-all.js'),
pass: fileExists(rootDir, 'tests/run-all.js'),
fix: 'Add tests/run-all.js to enforce complete suite execution.',
},
{
@@ -241,7 +322,7 @@ function getChecks() {
scopes: ['repo', 'hooks'],
path: 'tests/hooks/hooks.test.js',
description: 'Hook coverage test file exists',
pass: fileExists('tests/hooks/hooks.test.js'),
pass: fileExists(rootDir, 'tests/hooks/hooks.test.js'),
fix: 'Add tests/hooks/hooks.test.js for hook behavior validation.',
},
{
@@ -251,7 +332,7 @@ function getChecks() {
scopes: ['repo'],
path: 'scripts/doctor.js',
description: 'Installation drift doctor script exists',
pass: fileExists('scripts/doctor.js'),
pass: fileExists(rootDir, 'scripts/doctor.js'),
fix: 'Add scripts/doctor.js for install-state integrity checks.',
},
{
@@ -261,7 +342,7 @@ function getChecks() {
scopes: ['repo', 'hooks'],
path: 'hooks/memory-persistence/',
description: 'Memory persistence hooks directory exists',
pass: fileExists('hooks/memory-persistence'),
pass: fileExists(rootDir, 'hooks/memory-persistence'),
fix: 'Add hooks/memory-persistence with lifecycle hook definitions.',
},
{
@@ -271,7 +352,7 @@ function getChecks() {
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'),
pass: fileExists(rootDir, 'scripts/hooks/session-start.js') && fileExists(rootDir, 'scripts/hooks/session-end.js'),
fix: 'Implement scripts/hooks/session-start.js and scripts/hooks/session-end.js.',
},
{
@@ -281,7 +362,7 @@ function getChecks() {
scopes: ['repo', 'skills'],
path: 'skills/continuous-learning-v2/SKILL.md',
description: 'Continuous learning v2 skill exists',
pass: fileExists('skills/continuous-learning-v2/SKILL.md'),
pass: fileExists(rootDir, 'skills/continuous-learning-v2/SKILL.md'),
fix: 'Add skills/continuous-learning-v2/SKILL.md for memory evolution flow.',
},
{
@@ -291,7 +372,7 @@ function getChecks() {
scopes: ['repo', 'skills'],
path: 'skills/eval-harness/SKILL.md',
description: 'Eval harness skill exists',
pass: fileExists('skills/eval-harness/SKILL.md'),
pass: fileExists(rootDir, 'skills/eval-harness/SKILL.md'),
fix: 'Add skills/eval-harness/SKILL.md for pass/fail regression evaluation.',
},
{
@@ -301,7 +382,7 @@ function getChecks() {
scopes: ['repo', 'commands'],
path: 'commands/eval.md',
description: 'Eval and verification commands exist',
pass: fileExists('commands/eval.md') && fileExists('commands/verify.md') && fileExists('commands/checkpoint.md'),
pass: fileExists(rootDir, 'commands/eval.md') && fileExists(rootDir, 'commands/verify.md') && fileExists(rootDir, 'commands/checkpoint.md'),
fix: 'Add eval/checkpoint/verify commands to standardize verification loops.',
},
{
@@ -311,7 +392,7 @@ function getChecks() {
scopes: ['repo'],
path: 'tests/',
description: 'At least 10 test files exist',
pass: countFiles('tests', '.test.js') >= 10,
pass: countFiles(rootDir, 'tests', '.test.js') >= 10,
fix: 'Increase automated test coverage across scripts/hooks/lib.',
},
{
@@ -321,7 +402,7 @@ function getChecks() {
scopes: ['repo', 'skills'],
path: 'skills/security-review/SKILL.md',
description: 'Security review skill exists',
pass: fileExists('skills/security-review/SKILL.md'),
pass: fileExists(rootDir, 'skills/security-review/SKILL.md'),
fix: 'Add skills/security-review/SKILL.md for security checklist coverage.',
},
{
@@ -331,7 +412,7 @@ function getChecks() {
scopes: ['repo', 'agents'],
path: 'agents/security-reviewer.md',
description: 'Security reviewer agent exists',
pass: fileExists('agents/security-reviewer.md'),
pass: fileExists(rootDir, 'agents/security-reviewer.md'),
fix: 'Add agents/security-reviewer.md for delegated security audits.',
},
{
@@ -351,7 +432,7 @@ function getChecks() {
scopes: ['repo', 'commands'],
path: 'commands/security-scan.md',
description: 'Security scan command exists',
pass: fileExists('commands/security-scan.md'),
pass: fileExists(rootDir, 'commands/security-scan.md'),
fix: 'Add commands/security-scan.md with scan and remediation workflow.',
},
{
@@ -361,7 +442,7 @@ function getChecks() {
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'),
pass: fileExists(rootDir, 'skills/cost-aware-llm-pipeline/SKILL.md'),
fix: 'Add skills/cost-aware-llm-pipeline/SKILL.md for budget-aware routing.',
},
{
@@ -371,7 +452,7 @@ function getChecks() {
scopes: ['repo'],
path: 'docs/token-optimization.md',
description: 'Cost optimization documentation exists',
pass: fileExists('docs/token-optimization.md'),
pass: fileExists(rootDir, 'docs/token-optimization.md'),
fix: 'Create docs/token-optimization.md with target settings and tradeoffs.',
},
{
@@ -381,12 +462,136 @@ function getChecks() {
scopes: ['repo', 'commands'],
path: 'commands/model-route.md',
description: 'Model route command exists for complexity-aware routing',
pass: fileExists('commands/model-route.md'),
pass: fileExists(rootDir, 'commands/model-route.md'),
fix: 'Add commands/model-route.md and route policies for cheap-default execution.',
},
];
}
function getConsumerChecks(rootDir) {
const packageJson = safeParseJson(safeRead(rootDir, 'package.json'));
const gitignore = safeRead(rootDir, '.gitignore');
const projectHooks = safeRead(rootDir, '.claude/settings.json');
const pluginInstall = findPluginInstall(rootDir);
return [
{
id: 'consumer-plugin-install',
category: 'Tool Coverage',
points: 4,
scopes: ['repo'],
path: '~/.claude/plugins/everything-claude-code/',
description: 'Everything Claude Code is installed for the active user or project',
pass: Boolean(pluginInstall),
fix: 'Install the ECC plugin for this user or project before auditing project-specific harness quality.',
},
{
id: 'consumer-project-overrides',
category: 'Tool Coverage',
points: 3,
scopes: ['repo', 'hooks', 'skills', 'commands', 'agents'],
path: '.claude/',
description: 'Project-specific harness overrides exist under .claude/',
pass: countFiles(rootDir, '.claude/agents', '.md') > 0 ||
countFiles(rootDir, '.claude/skills', 'SKILL.md') > 0 ||
countFiles(rootDir, '.claude/commands', '.md') > 0 ||
fileExists(rootDir, '.claude/settings.json') ||
fileExists(rootDir, '.claude/hooks.json'),
fix: 'Add project-local .claude hooks, commands, skills, or settings that tailor ECC to this repo.',
},
{
id: 'consumer-instructions',
category: 'Context Efficiency',
points: 3,
scopes: ['repo'],
path: 'AGENTS.md',
description: 'The project has explicit agent or instruction context',
pass: fileExists(rootDir, 'AGENTS.md') || fileExists(rootDir, 'CLAUDE.md') || fileExists(rootDir, '.claude/CLAUDE.md'),
fix: 'Add AGENTS.md or CLAUDE.md so the harness has project-specific instructions.',
},
{
id: 'consumer-project-config',
category: 'Context Efficiency',
points: 2,
scopes: ['repo', 'hooks'],
path: '.mcp.json',
description: 'The project declares local MCP or Claude settings',
pass: fileExists(rootDir, '.mcp.json') || fileExists(rootDir, '.claude/settings.json') || fileExists(rootDir, '.claude/settings.local.json'),
fix: 'Add .mcp.json or .claude/settings.json so project-local tool configuration is explicit.',
},
{
id: 'consumer-test-suite',
category: 'Quality Gates',
points: 4,
scopes: ['repo'],
path: 'tests/',
description: 'The project has an automated test entrypoint',
pass: typeof packageJson?.scripts?.test === 'string' || countFiles(rootDir, 'tests', '.test.js') > 0 || hasFileWithExtension(rootDir, '.', ['.spec.js', '.spec.ts', '.test.ts']),
fix: 'Add a test script or checked-in tests so harness recommendations can be verified automatically.',
},
{
id: 'consumer-ci-workflow',
category: 'Quality Gates',
points: 3,
scopes: ['repo'],
path: '.github/workflows/',
description: 'The project has CI workflows checked in',
pass: hasFileWithExtension(rootDir, '.github/workflows', ['.yml', '.yaml']),
fix: 'Add at least one CI workflow so harness and test checks run outside local development.',
},
{
id: 'consumer-memory-notes',
category: 'Memory Persistence',
points: 2,
scopes: ['repo'],
path: '.claude/memory.md',
description: 'Project memory or durable notes are checked in',
pass: fileExists(rootDir, '.claude/memory.md') || countFiles(rootDir, 'docs/adr', '.md') > 0,
fix: 'Add durable project memory such as .claude/memory.md or ADRs under docs/adr/.',
},
{
id: 'consumer-eval-coverage',
category: 'Eval Coverage',
points: 2,
scopes: ['repo'],
path: 'evals/',
description: 'The project has evals or multiple automated tests',
pass: countFiles(rootDir, 'evals', null) > 0 || countFiles(rootDir, 'tests', '.test.js') >= 3,
fix: 'Add eval fixtures or at least a few focused automated tests for critical flows.',
},
{
id: 'consumer-security-policy',
category: 'Security Guardrails',
points: 2,
scopes: ['repo'],
path: 'SECURITY.md',
description: 'The project exposes a security policy or automated dependency scanning',
pass: fileExists(rootDir, 'SECURITY.md') || fileExists(rootDir, '.github/dependabot.yml') || fileExists(rootDir, '.github/codeql.yml'),
fix: 'Add SECURITY.md or dependency/code scanning configuration to document the project security posture.',
},
{
id: 'consumer-secret-hygiene',
category: 'Security Guardrails',
points: 2,
scopes: ['repo'],
path: '.gitignore',
description: 'The project ignores common secret env files',
pass: gitignore.includes('.env'),
fix: 'Ignore .env-style files in .gitignore so secrets do not land in the repo.',
},
{
id: 'consumer-hook-guardrails',
category: 'Security Guardrails',
points: 2,
scopes: ['repo', 'hooks'],
path: '.claude/settings.json',
description: 'Project-local hook settings reference tool/prompt guardrails',
pass: projectHooks.includes('PreToolUse') || projectHooks.includes('beforeSubmitPrompt') || fileExists(rootDir, '.claude/hooks.json'),
fix: 'Add project-local hook settings or hook definitions for prompt/tool guardrails.',
},
];
}
function summarizeCategoryScores(checks) {
const scores = {};
for (const category of CATEGORIES) {
@@ -407,8 +612,11 @@ function summarizeCategoryScores(checks) {
return scores;
}
function buildReport(scope) {
const checks = getChecks().filter(check => check.scopes.includes(scope));
function buildReport(scope, options = {}) {
const rootDir = path.resolve(options.rootDir || process.cwd());
const targetMode = options.targetMode || detectTargetMode(rootDir);
const checks = (targetMode === 'repo' ? getRepoChecks(rootDir) : getConsumerChecks(rootDir))
.filter(check => check.scopes.includes(scope));
const categoryScores = summarizeCategoryScores(checks);
const maxScore = checks.reduce((sum, check) => sum + check.points, 0);
const overallScore = checks
@@ -428,8 +636,10 @@ function buildReport(scope) {
return {
scope,
root_dir: rootDir,
target_mode: targetMode,
deterministic: true,
rubric_version: '2026-03-16',
rubric_version: '2026-03-30',
overall_score: overallScore,
max_score: maxScore,
categories: categoryScores,
@@ -446,7 +656,8 @@ function buildReport(scope) {
}
function printText(report) {
console.log(`Harness Audit (${report.scope}): ${report.overall_score}/${report.max_score}`);
console.log(`Harness Audit (${report.scope}, ${report.target_mode}): ${report.overall_score}/${report.max_score}`);
console.log(`Root: ${report.root_dir}`);
console.log('');
for (const category of CATEGORIES) {
@@ -474,8 +685,10 @@ function printText(report) {
function showHelp(exitCode = 0) {
console.log(`
Usage: node scripts/harness-audit.js [scope] [--scope <repo|hooks|skills|commands|agents>] [--format <text|json>]
[--root <path>]
Deterministic harness audit based on explicit file/rule checks.
Audits the current working directory by default and auto-detects ECC repo mode vs consumer-project mode.
`);
process.exit(exitCode);
}
@@ -489,7 +702,7 @@ function main() {
return;
}
const report = buildReport(args.scope);
const report = buildReport(args.scope, { rootDir: args.root });
if (args.format === 'json') {
console.log(JSON.stringify(report, null, 2));

View File

@@ -36,7 +36,11 @@ PYTHON_CMD="${CLV2_PYTHON_CMD:-}"
# ─────────────────────────────────────────────
CONFIG_DIR="${HOME}/.claude/homunculus"
CONFIG_FILE="${SKILL_ROOT}/config.json"
if [ -n "${CLV2_CONFIG:-}" ]; then
CONFIG_FILE="$CLV2_CONFIG"
else
CONFIG_FILE="${SKILL_ROOT}/config.json"
fi
# PID file is project-scoped so each project can have its own observer
PID_FILE="${PROJECT_DIR}/.observer.pid"
LOG_FILE="${PROJECT_DIR}/observer.log"

View File

@@ -1,20 +1,18 @@
---
name: token-budget-advisor
description: >-
Intercepts the response flow to offer the user an informed choice about
how much depth/tokens to consume — BEFORE responding. Use this skill
when the user wants to control token consumption, adjust response depth,
choose between short/long answers, or optimize their prompt.
TRIGGER when: "token budget", "response token budget", "token count",
"token usage", "response length", "response depth", "brief answer",
"short answer", "detailed answer", "full answer",
"respuesta corta vs larga", "cuántos tokens", "ahorrar tokens",
"responde al 50%", "dame la versión corta", "quiero controlar cuánto usas",
"75%", "100%", "at 25%", "at 50%", "at 75%", "at 100%",
"give me the full answer", or any variant where the user wants
to control length, depth, or token usage — even without mentioning tokens.
Offers the user an informed choice about how much response depth to
consume before answering. Use this skill when the user explicitly
wants to control response length, depth, or token budget.
TRIGGER when: "token budget", "token count", "token usage", "token limit",
"response length", "answer depth", "short version", "brief answer",
"detailed answer", "exhaustive answer", "respuesta corta vs larga",
"cuántos tokens", "ahorrar tokens", "responde al 50%", "dame la versión
corta", "quiero controlar cuánto usas", or clear variants where the
user is explicitly asking to control answer size or depth.
DO NOT TRIGGER when: user has already specified a level in the current
session (maintain it) or the request is clearly a one-word answer.
session (maintain it), the request is clearly a one-word answer, or
"token" refers to auth/session/payment tokens rather than response size.
origin: community
---
@@ -35,12 +33,14 @@ Intercept the response flow to offer the user a choice about response depth **be
### Step 1 — Estimate input tokens
Use the repository's canonical estimation guidance from `skills/context-budget`.
Use the repository's canonical context-budget heuristics to estimate the prompt's token count mentally.
- Prose-first prompts: `input_tokens ≈ word_count × 1.3`
- Code-heavy or mixed prompts: `input_tokens ≈ char_count / 4`
Use the same calibration guidance as [context-budget](../context-budget/SKILL.md):
For mixed content, prefer the code-heavy estimate as the conservative default.
- prose: `words × 1.3`
- code-heavy or mixed/code blocks: `chars / 4`
For mixed content, use the dominant content type and keep the estimate heuristic.
### Step 2 — Estimate response size by complexity
@@ -72,7 +72,7 @@ Choose your depth level:
[3] Detailed (75%) -> ~[tokens] Full answer with alternatives
[4] Exhaustive (100%) -> ~[tokens] Everything, no limits
Which level? (1-4 or say "25%", "50%", "75%", "100%")
Which level? (1-4 or say "25% depth", "50% depth", "75% depth", "100% depth")
Precision: heuristic estimate ~85-90% accuracy (±15%).
```
@@ -98,10 +98,10 @@ If the user already signals a level, respond at that level immediately without a
| What they say | Level |
|----------------------------------------------------|-------|
| "1" / "25%" / "short answer" / "brief" / "tldr" / "one-liner" | 25% |
| "2" / "50%" / "moderate detail" / "balanced answer" | 50% |
| "3" / "75%" / "detailed answer" / "thorough explanation" | 75% |
| "4" / "100%" / "exhaustive" / "everything" / "full answer" | 100% |
| "1" / "25% depth" / "short version" / "brief answer" / "tldr" | 25% |
| "2" / "50% depth" / "moderate depth" / "balanced answer" | 50% |
| "3" / "75% depth" / "detailed answer" / "thorough answer" | 75% |
| "4" / "100% depth" / "exhaustive answer" / "full deep dive" | 100% |
If the user set a level earlier in the session, **maintain it silently** for subsequent responses unless they change it.
@@ -113,19 +113,21 @@ This skill uses heuristic estimation — no real tokenizer. Accuracy ~85-90%, va
### Triggers
- "Give me the brief answer first."
- "How many tokens will your response use?"
- "Give me the short version first."
- "How many tokens will your answer use?"
- "Respond at 50% depth."
- "I want the full answer."
- "Dame la version corta."
- "I want the exhaustive answer, not the summary."
- "Dame la version corta y luego la detallada."
### Does Not Trigger
- "Explain OAuth token refresh flow." (`token` here is domain language, not a budget request)
- "Why is this JWT token invalid?" (security/domain usage, not response sizing)
- "What is 2 + 2?" (trivially short answer)
- "What is a JWT token?"
- "The checkout flow uses a payment token."
- "Is this normal?"
- "Complete the refactor."
- Follow-up questions after the user already chose a depth for the session
## Source
Standalone skill from [TBA — Token Budget Advisor for Claude Code](https://github.com/Xabilimon1/Token-Budget-Advisor-Claude-Code-).
The upstream project includes an optional estimator script, but this ECC skill intentionally stays zero-dependency and heuristic-only.
Original project also ships a Python estimator script, but this repository keeps the skill self-contained and heuristic-only.

View File

@@ -3,14 +3,28 @@
*/
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'harness-audit.js');
function run(args = []) {
function createTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function cleanup(dirPath) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
function run(args = [], options = {}) {
const stdout = execFileSync('node', [SCRIPT, ...args], {
cwd: path.join(__dirname, '..', '..'),
cwd: options.cwd || path.join(__dirname, '..', '..'),
env: {
...process.env,
HOME: options.homeDir || process.env.HOME,
},
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
@@ -48,7 +62,8 @@ function runTests() {
const parsed = JSON.parse(run(['repo', '--format', 'json']));
assert.strictEqual(parsed.deterministic, true);
assert.strictEqual(parsed.rubric_version, '2026-03-16');
assert.strictEqual(parsed.rubric_version, '2026-03-30');
assert.strictEqual(parsed.target_mode, 'repo');
assert.ok(parsed.overall_score >= 0);
assert.ok(parsed.max_score > 0);
assert.ok(parsed.overall_score <= parsed.max_score);
@@ -75,10 +90,48 @@ function runTests() {
if (test('text format includes summary header', () => {
const output = run(['repo']);
assert.ok(output.includes('Harness Audit (repo):'));
assert.ok(output.includes('Harness Audit (repo, repo):'));
assert.ok(output.includes('Top 3 Actions:') || output.includes('Checks:'));
})) passed++; else failed++;
if (test('audits consumer projects from cwd instead of the ECC repo root', () => {
const homeDir = createTempDir('harness-audit-home-');
const projectRoot = createTempDir('harness-audit-project-');
try {
fs.mkdirSync(path.join(homeDir, '.claude', 'plugins', 'everything-claude-code', '.claude-plugin'), { recursive: true });
fs.writeFileSync(
path.join(homeDir, '.claude', 'plugins', 'everything-claude-code', '.claude-plugin', 'plugin.json'),
JSON.stringify({ name: 'everything-claude-code' }, null, 2)
);
fs.mkdirSync(path.join(projectRoot, '.github', 'workflows'), { recursive: true });
fs.mkdirSync(path.join(projectRoot, 'tests'), { recursive: true });
fs.mkdirSync(path.join(projectRoot, '.claude'), { recursive: true });
fs.writeFileSync(path.join(projectRoot, 'AGENTS.md'), '# Project instructions\n');
fs.writeFileSync(path.join(projectRoot, '.mcp.json'), JSON.stringify({ mcpServers: {} }, null, 2));
fs.writeFileSync(path.join(projectRoot, '.gitignore'), 'node_modules\n.env\n');
fs.writeFileSync(path.join(projectRoot, '.github', 'workflows', 'ci.yml'), 'name: ci\n');
fs.writeFileSync(path.join(projectRoot, 'tests', 'app.test.js'), 'test placeholder\n');
fs.writeFileSync(path.join(projectRoot, '.claude', 'settings.json'), JSON.stringify({ hooks: ['PreToolUse'] }, null, 2));
fs.writeFileSync(
path.join(projectRoot, 'package.json'),
JSON.stringify({ name: 'consumer-project', scripts: { test: 'node tests/app.test.js' } }, null, 2)
);
const parsed = JSON.parse(run(['repo', '--format', 'json'], { cwd: projectRoot, homeDir }));
assert.strictEqual(parsed.target_mode, 'consumer');
assert.strictEqual(parsed.root_dir, fs.realpathSync(projectRoot));
assert.ok(parsed.overall_score > 0, 'Consumer project should receive non-zero score when harness signals exist');
assert.ok(parsed.checks.some(check => check.id === 'consumer-plugin-install' && check.pass));
assert.ok(parsed.checks.every(check => !check.path.startsWith('agents/') && !check.path.startsWith('skills/')));
} finally {
cleanup(homeDir);
cleanup(projectRoot);
}
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}