mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-31 22:23:27 +08:00
Compare commits
13 Commits
9dc76fd27b
...
v1.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1797e79129 | ||
|
|
1f8b3eaba7 | ||
|
|
d1f44e89e2 | ||
|
|
5fe40f4a63 | ||
|
|
c4a5a69dbd | ||
|
|
5e1472263d | ||
|
|
61485f91ad | ||
|
|
57eb9361db | ||
|
|
98643ef6e6 | ||
|
|
f94707d429 | ||
|
|
48b883d741 | ||
|
|
32e9c293f0 | ||
|
|
cd129edef0 |
@@ -14,7 +14,7 @@
|
||||
"name": "everything-claude-code",
|
||||
"source": "./",
|
||||
"description": "The most comprehensive Claude Code plugin — 14+ agents, 56+ skills, 33+ commands, and production-ready hooks for TDD, security scanning, code review, and continuous learning",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"email": "me@affaanmustafa.com"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "everything-claude-code",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"description": "Complete collection of battle-tested Claude Code configs from an Anthropic hackathon winner - agents, skills, hooks, and rules evolved over 10+ months of intensive daily use",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -29,13 +29,14 @@ function transformToClaude(cursorInput, overrides = {}) {
|
||||
return {
|
||||
tool_input: {
|
||||
command: cursorInput.command || cursorInput.args?.command || '',
|
||||
file_path: cursorInput.path || cursorInput.file || '',
|
||||
file_path: cursorInput.path || cursorInput.file || cursorInput.args?.filePath || '',
|
||||
...overrides.tool_input,
|
||||
},
|
||||
tool_output: {
|
||||
output: cursorInput.output || cursorInput.result || '',
|
||||
...overrides.tool_output,
|
||||
},
|
||||
transcript_path: cursorInput.transcript_path || cursorInput.transcriptPath || cursorInput.session?.transcript_path || '',
|
||||
_cursor: {
|
||||
conversation_id: cursorInput.conversation_id,
|
||||
hook_event_name: cursorInput.hook_event_name,
|
||||
@@ -59,4 +60,22 @@ function runExistingHook(scriptName, stdinData) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { readStdin, getPluginRoot, transformToClaude, runExistingHook };
|
||||
function hookEnabled(hookId, allowedProfiles = ['standard', 'strict']) {
|
||||
const rawProfile = String(process.env.ECC_HOOK_PROFILE || 'standard').toLowerCase();
|
||||
const profile = ['minimal', 'standard', 'strict'].includes(rawProfile) ? rawProfile : 'standard';
|
||||
|
||||
const disabled = new Set(
|
||||
String(process.env.ECC_DISABLED_HOOKS || '')
|
||||
.split(',')
|
||||
.map(v => v.trim().toLowerCase())
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
if (disabled.has(String(hookId || '').toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return allowedProfiles.includes(profile);
|
||||
}
|
||||
|
||||
module.exports = { readStdin, getPluginRoot, transformToClaude, runExistingHook, hookEnabled };
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
const { readStdin } = require('./adapter');
|
||||
const { readStdin, hookEnabled } = require('./adapter');
|
||||
|
||||
readStdin().then(raw => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = input.command || '';
|
||||
const output = input.output || input.result || '';
|
||||
const input = JSON.parse(raw || '{}');
|
||||
const cmd = String(input.command || input.args?.command || '');
|
||||
const output = String(input.output || input.result || '');
|
||||
|
||||
// PR creation logging
|
||||
if (/gh pr create/.test(cmd)) {
|
||||
if (hookEnabled('post:bash:pr-created', ['standard', 'strict']) && /\bgh\s+pr\s+create\b/.test(cmd)) {
|
||||
const m = output.match(/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/);
|
||||
if (m) {
|
||||
console.error('[ECC] PR created: ' + m[0]);
|
||||
@@ -17,10 +17,12 @@ readStdin().then(raw => {
|
||||
}
|
||||
}
|
||||
|
||||
// Build completion notice
|
||||
if (/(npm run build|pnpm build|yarn build)/.test(cmd)) {
|
||||
if (hookEnabled('post:bash:build-complete', ['standard', 'strict']) && /(npm run build|pnpm build|yarn build)/.test(cmd)) {
|
||||
console.error('[ECC] Build completed');
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
}).catch(() => process.exit(0));
|
||||
|
||||
@@ -1,27 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
const { readStdin } = require('./adapter');
|
||||
readStdin().then(raw => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = input.command || '';
|
||||
const { readStdin, hookEnabled } = require('./adapter');
|
||||
|
||||
// 1. Block dev server outside tmux
|
||||
if (process.platform !== 'win32' && /(npm run dev\b|pnpm( run)? dev\b|yarn dev\b|bun run dev\b)/.test(cmd)) {
|
||||
console.error('[ECC] BLOCKED: Dev server must run in tmux for log access');
|
||||
console.error('[ECC] Use: tmux new-session -d -s dev "npm run dev"');
|
||||
process.exit(2);
|
||||
function splitShellSegments(command) {
|
||||
const segments = [];
|
||||
let current = '';
|
||||
let quote = null;
|
||||
|
||||
for (let i = 0; i < command.length; i++) {
|
||||
const ch = command[i];
|
||||
if (quote) {
|
||||
if (ch === quote) quote = null;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Tmux reminder for long-running commands
|
||||
if (process.platform !== 'win32' && !process.env.TMUX &&
|
||||
/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\b|docker\b|pytest|vitest|playwright)/.test(cmd)) {
|
||||
if (ch === '"' || ch === "'") {
|
||||
quote = ch;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
const next = command[i + 1] || '';
|
||||
if (ch === ';' || (ch === '&' && next === '&') || (ch === '|' && next === '|') || (ch === '&' && next !== '&')) {
|
||||
if (current.trim()) segments.push(current.trim());
|
||||
current = '';
|
||||
if ((ch === '&' && next === '&') || (ch === '|' && next === '|')) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
current += ch;
|
||||
}
|
||||
|
||||
if (current.trim()) segments.push(current.trim());
|
||||
return segments;
|
||||
}
|
||||
|
||||
readStdin().then(raw => {
|
||||
try {
|
||||
const input = JSON.parse(raw || '{}');
|
||||
const cmd = String(input.command || input.args?.command || '');
|
||||
|
||||
if (hookEnabled('pre:bash:dev-server-block', ['standard', 'strict']) && process.platform !== 'win32') {
|
||||
const segments = splitShellSegments(cmd);
|
||||
const tmuxLauncher = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/;
|
||||
const devPattern = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn\s+dev|bun\s+run\s+dev)\b/;
|
||||
const hasBlockedDev = segments.some(segment => devPattern.test(segment) && !tmuxLauncher.test(segment));
|
||||
if (hasBlockedDev) {
|
||||
console.error('[ECC] BLOCKED: Dev server must run in tmux for log access');
|
||||
console.error('[ECC] Use: tmux new-session -d -s dev "npm run dev"');
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
hookEnabled('pre:bash:tmux-reminder', ['strict']) &&
|
||||
process.platform !== 'win32' &&
|
||||
!process.env.TMUX &&
|
||||
/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\b|docker\b|pytest|vitest|playwright)/.test(cmd)
|
||||
) {
|
||||
console.error('[ECC] Consider running in tmux for session persistence');
|
||||
}
|
||||
|
||||
// 3. Git push review reminder
|
||||
if (/git push/.test(cmd)) {
|
||||
if (hookEnabled('pre:bash:git-push-reminder', ['strict']) && /\bgit\s+push\b/.test(cmd)) {
|
||||
console.error('[ECC] Review changes before push: git diff origin/main...HEAD');
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
}).catch(() => process.exit(0));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const { readStdin, runExistingHook, transformToClaude } = require('./adapter');
|
||||
const { readStdin, runExistingHook, transformToClaude, hookEnabled } = require('./adapter');
|
||||
readStdin().then(raw => {
|
||||
const input = JSON.parse(raw);
|
||||
const input = JSON.parse(raw || '{}');
|
||||
const claudeInput = transformToClaude(input);
|
||||
runExistingHook('session-end.js', claudeInput);
|
||||
runExistingHook('evaluate-session.js', claudeInput);
|
||||
if (hookEnabled('session:end:marker', ['minimal', 'standard', 'strict'])) {
|
||||
runExistingHook('session-end-marker.js', claudeInput);
|
||||
}
|
||||
process.stdout.write(raw);
|
||||
}).catch(() => process.exit(0));
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const { readStdin, runExistingHook, transformToClaude } = require('./adapter');
|
||||
const { readStdin, runExistingHook, transformToClaude, hookEnabled } = require('./adapter');
|
||||
readStdin().then(raw => {
|
||||
const input = JSON.parse(raw);
|
||||
const input = JSON.parse(raw || '{}');
|
||||
const claudeInput = transformToClaude(input);
|
||||
runExistingHook('session-start.js', claudeInput);
|
||||
if (hookEnabled('session:start', ['minimal', 'standard', 'strict'])) {
|
||||
runExistingHook('session-start.js', claudeInput);
|
||||
}
|
||||
process.stdout.write(raw);
|
||||
}).catch(() => process.exit(0));
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
const { readStdin, runExistingHook, transformToClaude } = require('./adapter');
|
||||
const { readStdin, runExistingHook, transformToClaude, hookEnabled } = require('./adapter');
|
||||
readStdin().then(raw => {
|
||||
const claudeInput = JSON.parse(raw || '{}');
|
||||
runExistingHook('check-console-log.js', transformToClaude(claudeInput));
|
||||
const input = JSON.parse(raw || '{}');
|
||||
const claudeInput = transformToClaude(input);
|
||||
|
||||
if (hookEnabled('stop:check-console-log', ['standard', 'strict'])) {
|
||||
runExistingHook('check-console-log.js', claudeInput);
|
||||
}
|
||||
if (hookEnabled('stop:session-end', ['minimal', 'standard', 'strict'])) {
|
||||
runExistingHook('session-end.js', claudeInput);
|
||||
}
|
||||
if (hookEnabled('stop:evaluate-session', ['minimal', 'standard', 'strict'])) {
|
||||
runExistingHook('evaluate-session.js', claudeInput);
|
||||
}
|
||||
if (hookEnabled('stop:cost-tracker', ['minimal', 'standard', 'strict'])) {
|
||||
runExistingHook('cost-tracker.js', claudeInput);
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
}).catch(() => process.exit(0));
|
||||
|
||||
20
.github/release.yml
vendored
Normal file
20
.github/release.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
changelog:
|
||||
categories:
|
||||
- title: Core Harness
|
||||
labels:
|
||||
- enhancement
|
||||
- feature
|
||||
- title: Reliability & Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- fix
|
||||
- title: Docs & Guides
|
||||
labels:
|
||||
- docs
|
||||
- title: Tooling & CI
|
||||
labels:
|
||||
- ci
|
||||
- chore
|
||||
exclude:
|
||||
labels:
|
||||
- skip-changelog
|
||||
185
.github/workflows/monthly-metrics.yml
vendored
Normal file
185
.github/workflows/monthly-metrics.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
name: Monthly Metrics Snapshot
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 14 1 * *' # Monthly on the 1st at 14:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
snapshot:
|
||||
name: Update metrics issue
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update monthly metrics issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const title = "Monthly Metrics Snapshot";
|
||||
const label = "metrics-snapshot";
|
||||
const monthKey = new Date().toISOString().slice(0, 7);
|
||||
|
||||
function parseLastPage(linkHeader) {
|
||||
if (!linkHeader) return null;
|
||||
const match = linkHeader.match(/&page=(\d+)>; rel="last"/);
|
||||
return match ? Number(match[1]) : null;
|
||||
}
|
||||
|
||||
function fmt(value) {
|
||||
if (value === null || value === undefined) return "n/a";
|
||||
return Number(value).toLocaleString("en-US");
|
||||
}
|
||||
|
||||
async function getNpmDownloads(range, pkg) {
|
||||
try {
|
||||
const res = await fetch(`https://api.npmjs.org/downloads/point/${range}/${pkg}`);
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.downloads ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getContributorsCount() {
|
||||
try {
|
||||
const resp = await github.rest.repos.listContributors({
|
||||
owner,
|
||||
repo,
|
||||
per_page: 1,
|
||||
anon: "false"
|
||||
});
|
||||
return parseLastPage(resp.headers.link) ?? resp.data.length;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getReleasesCount() {
|
||||
try {
|
||||
const resp = await github.rest.repos.listReleases({
|
||||
owner,
|
||||
repo,
|
||||
per_page: 1
|
||||
});
|
||||
return parseLastPage(resp.headers.link) ?? resp.data.length;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTraffic(metric) {
|
||||
try {
|
||||
const route = metric === "clones"
|
||||
? "GET /repos/{owner}/{repo}/traffic/clones"
|
||||
: "GET /repos/{owner}/{repo}/traffic/views";
|
||||
const resp = await github.request(route, { owner, repo });
|
||||
return resp.data?.count ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const [
|
||||
mainWeek,
|
||||
shieldWeek,
|
||||
mainMonth,
|
||||
shieldMonth,
|
||||
repoData,
|
||||
contributors,
|
||||
releases,
|
||||
views14d,
|
||||
clones14d
|
||||
] = await Promise.all([
|
||||
getNpmDownloads("last-week", "ecc-universal"),
|
||||
getNpmDownloads("last-week", "ecc-agentshield"),
|
||||
getNpmDownloads("last-month", "ecc-universal"),
|
||||
getNpmDownloads("last-month", "ecc-agentshield"),
|
||||
github.rest.repos.get({ owner, repo }),
|
||||
getContributorsCount(),
|
||||
getReleasesCount(),
|
||||
getTraffic("views"),
|
||||
getTraffic("clones")
|
||||
]);
|
||||
|
||||
const stars = repoData.data.stargazers_count;
|
||||
const forks = repoData.data.forks_count;
|
||||
|
||||
const tableHeader = [
|
||||
"| Month (UTC) | ecc-universal (week) | ecc-agentshield (week) | ecc-universal (30d) | ecc-agentshield (30d) | Stars | Forks | Contributors | GitHub App installs (manual) | Views (14d) | Clones (14d) | Releases |",
|
||||
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|"
|
||||
].join("\n");
|
||||
|
||||
const row = `| ${monthKey} | ${fmt(mainWeek)} | ${fmt(shieldWeek)} | ${fmt(mainMonth)} | ${fmt(shieldMonth)} | ${fmt(stars)} | ${fmt(forks)} | ${fmt(contributors)} | n/a | ${fmt(views14d)} | ${fmt(clones14d)} | ${fmt(releases)} |`;
|
||||
|
||||
const intro = [
|
||||
"# Monthly Metrics Snapshot",
|
||||
"",
|
||||
"Automated monthly snapshot for sponsor/partner reporting.",
|
||||
"",
|
||||
"- `GitHub App installs (manual)` is intentionally manual until a stable public API path is available.",
|
||||
"- Traffic metrics are 14-day rolling windows from the GitHub traffic API and can show `n/a` if unavailable.",
|
||||
"",
|
||||
tableHeader
|
||||
].join("\n");
|
||||
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner, repo, name: label });
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: label,
|
||||
color: "0e8a16",
|
||||
description: "Automated monthly project metrics snapshots"
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const issuesResp = await github.rest.issues.listForRepo({
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
labels: label,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
let issue = issuesResp.data.find((item) => item.title === title);
|
||||
|
||||
if (!issue) {
|
||||
const created = await github.rest.issues.create({
|
||||
owner,
|
||||
repo,
|
||||
title,
|
||||
labels: [label],
|
||||
body: `${intro}\n${row}\n`
|
||||
});
|
||||
console.log(`Created issue #${created.data.number}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentBody = issue.body || "";
|
||||
if (currentBody.includes(`| ${monthKey} |`)) {
|
||||
console.log(`Issue #${issue.number} already has snapshot row for ${monthKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = currentBody.includes("| Month (UTC) |")
|
||||
? `${currentBody.trimEnd()}\n${row}\n`
|
||||
: `${intro}\n${row}\n`;
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body
|
||||
});
|
||||
console.log(`Updated issue #${issue.number}`);
|
||||
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@@ -37,23 +37,31 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
- name: Generate release highlights
|
||||
id: highlights
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s" HEAD)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD)
|
||||
fi
|
||||
echo "commits<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$COMMITS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
TAG_VERSION="${TAG_NAME#v}"
|
||||
cat > release_body.md <<EOF
|
||||
## ECC ${TAG_VERSION}
|
||||
|
||||
### What This Release Focuses On
|
||||
- Harness reliability and hook stability across Claude Code, Cursor, OpenCode, and Codex
|
||||
- Stronger eval-driven workflows and quality gates
|
||||
- Better operator UX for autonomous loop execution
|
||||
|
||||
### Notable Changes
|
||||
- Session persistence and hook lifecycle fixes
|
||||
- Expanded skills and command coverage for harness performance work
|
||||
- Improved release-note generation and changelog hygiene
|
||||
|
||||
### Notes
|
||||
- For migration tips and compatibility notes, see README and CHANGELOG.
|
||||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: |
|
||||
## Changes
|
||||
${{ steps.changelog.outputs.commits }}
|
||||
generate_release_notes: false
|
||||
body_path: release_body.md
|
||||
generate_release_notes: true
|
||||
|
||||
29
.github/workflows/reusable-release.yml
vendored
29
.github/workflows/reusable-release.yml
vendored
@@ -34,26 +34,23 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
- name: Generate release highlights
|
||||
env:
|
||||
TAG_NAME: ${{ inputs.tag }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s" HEAD)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD)
|
||||
fi
|
||||
# Use unique delimiter to prevent truncation if commit messages contain EOF
|
||||
DELIMITER="COMMITS_END_$(date +%s)"
|
||||
echo "commits<<${DELIMITER}" >> $GITHUB_OUTPUT
|
||||
echo "$COMMITS" >> $GITHUB_OUTPUT
|
||||
echo "${DELIMITER}" >> $GITHUB_OUTPUT
|
||||
TAG_VERSION="${TAG_NAME#v}"
|
||||
cat > release_body.md <<EOF
|
||||
## ECC ${TAG_VERSION}
|
||||
|
||||
### What This Release Focuses On
|
||||
- Harness reliability and cross-platform compatibility
|
||||
- Eval-driven quality improvements
|
||||
- Better workflow and operator ergonomics
|
||||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ inputs.tag }}
|
||||
body: |
|
||||
## Changes
|
||||
${{ steps.changelog.outputs.commits }}
|
||||
body_path: release_body.md
|
||||
generate_release_notes: ${{ inputs.generate-notes }}
|
||||
|
||||
@@ -67,7 +67,7 @@ opencode
|
||||
| go-build-resolver | Go build errors |
|
||||
| database-reviewer | Database optimization |
|
||||
|
||||
### Commands (24)
|
||||
### Commands (31)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
@@ -97,6 +97,11 @@ opencode
|
||||
| `/evolve` | Cluster instincts |
|
||||
| `/promote` | Promote project instincts |
|
||||
| `/projects` | List known projects |
|
||||
| `/harness-audit` | Audit harness reliability and eval readiness |
|
||||
| `/loop-start` | Start controlled agentic loops |
|
||||
| `/loop-status` | Check loop state and checkpoints |
|
||||
| `/quality-gate` | Run quality gates on file/repo scope |
|
||||
| `/model-route` | Route tasks by model and budget |
|
||||
|
||||
### Plugin Hooks
|
||||
|
||||
@@ -130,6 +135,18 @@ OpenCode's plugin system maps to Claude Code hooks:
|
||||
|
||||
OpenCode has 20+ additional events not available in Claude Code.
|
||||
|
||||
### Hook Runtime Controls
|
||||
|
||||
OpenCode plugin hooks honor the same runtime controls used by Claude Code/Cursor:
|
||||
|
||||
```bash
|
||||
export ECC_HOOK_PROFILE=standard
|
||||
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||||
```
|
||||
|
||||
- `ECC_HOOK_PROFILE`: `minimal`, `standard` (default), `strict`
|
||||
- `ECC_DISABLED_HOOKS`: comma-separated hook IDs to disable
|
||||
|
||||
## Skills
|
||||
|
||||
The default OpenCode config loads 11 curated ECC skills via the `instructions` array:
|
||||
|
||||
58
.opencode/commands/harness-audit.md
Normal file
58
.opencode/commands/harness-audit.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Harness Audit Command
|
||||
|
||||
Audit the current repository's agent harness setup and return a prioritized scorecard.
|
||||
|
||||
## Usage
|
||||
|
||||
`/harness-audit [scope] [--format text|json]`
|
||||
|
||||
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
||||
- `--format`: output style (`text` default, `json` for automation)
|
||||
|
||||
## What to Evaluate
|
||||
|
||||
Score each category from `0` to `10`:
|
||||
|
||||
1. Tool Coverage
|
||||
2. Context Efficiency
|
||||
3. Quality Gates
|
||||
4. Memory Persistence
|
||||
5. Eval Coverage
|
||||
6. Security Guardrails
|
||||
7. Cost Efficiency
|
||||
|
||||
## Output Contract
|
||||
|
||||
Return:
|
||||
|
||||
1. `overall_score` out of 70
|
||||
2. Category scores and concrete findings
|
||||
3. Top 3 actions with exact file paths
|
||||
4. Suggested ECC skills to apply next
|
||||
|
||||
## Checklist
|
||||
|
||||
- Inspect `hooks/hooks.json`, `scripts/hooks/`, and hook tests.
|
||||
- Inspect `skills/`, command coverage, and agent coverage.
|
||||
- Verify cross-harness parity for `.cursor/`, `.opencode/`, `.codex/`.
|
||||
- Flag broken or stale references.
|
||||
|
||||
## Example Result
|
||||
|
||||
```text
|
||||
Harness Audit (repo): 52/70
|
||||
- Quality Gates: 9/10
|
||||
- Eval Coverage: 6/10
|
||||
- Cost Efficiency: 4/10
|
||||
|
||||
Top 3 Actions:
|
||||
1) Add cost tracking hook in scripts/hooks/cost-tracker.js
|
||||
2) Add pass@k docs and templates in skills/eval-harness/SKILL.md
|
||||
3) Add command parity for /harness-audit in .opencode/commands/
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `repo|hooks|skills|commands|agents` (optional scope)
|
||||
- `--format text|json` (optional output format)
|
||||
32
.opencode/commands/loop-start.md
Normal file
32
.opencode/commands/loop-start.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Loop Start Command
|
||||
|
||||
Start a managed autonomous loop pattern with safety defaults.
|
||||
|
||||
## Usage
|
||||
|
||||
`/loop-start [pattern] [--mode safe|fast]`
|
||||
|
||||
- `pattern`: `sequential`, `continuous-pr`, `rfc-dag`, `infinite`
|
||||
- `--mode`:
|
||||
- `safe` (default): strict quality gates and checkpoints
|
||||
- `fast`: reduced gates for speed
|
||||
|
||||
## Flow
|
||||
|
||||
1. Confirm repository state and branch strategy.
|
||||
2. Select loop pattern and model tier strategy.
|
||||
3. Enable required hooks/profile for the chosen mode.
|
||||
4. Create loop plan and write runbook under `.claude/plans/`.
|
||||
5. Print commands to start and monitor the loop.
|
||||
|
||||
## Required Safety Checks
|
||||
|
||||
- Verify tests pass before first loop iteration.
|
||||
- Ensure `ECC_HOOK_PROFILE` is not disabled globally.
|
||||
- Ensure loop has explicit stop condition.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `<pattern>` optional (`sequential|continuous-pr|rfc-dag|infinite`)
|
||||
- `--mode safe|fast` optional
|
||||
24
.opencode/commands/loop-status.md
Normal file
24
.opencode/commands/loop-status.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Loop Status Command
|
||||
|
||||
Inspect active loop state, progress, and failure signals.
|
||||
|
||||
## Usage
|
||||
|
||||
`/loop-status [--watch]`
|
||||
|
||||
## What to Report
|
||||
|
||||
- active loop pattern
|
||||
- current phase and last successful checkpoint
|
||||
- failing checks (if any)
|
||||
- estimated time/cost drift
|
||||
- recommended intervention (continue/pause/stop)
|
||||
|
||||
## Watch Mode
|
||||
|
||||
When `--watch` is present, refresh status periodically and surface state changes.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `--watch` optional
|
||||
26
.opencode/commands/model-route.md
Normal file
26
.opencode/commands/model-route.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Model Route Command
|
||||
|
||||
Recommend the best model tier for the current task by complexity and budget.
|
||||
|
||||
## Usage
|
||||
|
||||
`/model-route [task-description] [--budget low|med|high]`
|
||||
|
||||
## Routing Heuristic
|
||||
|
||||
- `haiku`: deterministic, low-risk mechanical changes
|
||||
- `sonnet`: default for implementation and refactors
|
||||
- `opus`: architecture, deep review, ambiguous requirements
|
||||
|
||||
## Required Output
|
||||
|
||||
- recommended model
|
||||
- confidence level
|
||||
- why this model fits
|
||||
- fallback model if first attempt fails
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `[task-description]` optional free-text
|
||||
- `--budget low|med|high` optional
|
||||
29
.opencode/commands/quality-gate.md
Normal file
29
.opencode/commands/quality-gate.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Quality Gate Command
|
||||
|
||||
Run the ECC quality pipeline on demand for a file or project scope.
|
||||
|
||||
## Usage
|
||||
|
||||
`/quality-gate [path|.] [--fix] [--strict]`
|
||||
|
||||
- default target: current directory (`.`)
|
||||
- `--fix`: allow auto-format/fix where configured
|
||||
- `--strict`: fail on warnings where supported
|
||||
|
||||
## Pipeline
|
||||
|
||||
1. Detect language/tooling for target.
|
||||
2. Run formatter checks.
|
||||
3. Run lint/type checks when available.
|
||||
4. Produce a concise remediation list.
|
||||
|
||||
## Notes
|
||||
|
||||
This command mirrors hook behavior but is operator-invoked.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `[path|.]` optional target path
|
||||
- `--fix` optional
|
||||
- `--strict` optional
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"description": "Everything Claude Code (ECC) plugin for OpenCode - agents, commands, hooks, and skills",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -21,6 +21,8 @@ export const ECCHooksPlugin = async ({
|
||||
directory,
|
||||
worktree,
|
||||
}: PluginInput) => {
|
||||
type HookProfile = "minimal" | "standard" | "strict"
|
||||
|
||||
// Track files edited in current session for console.log audit
|
||||
const editedFiles = new Set<string>()
|
||||
|
||||
@@ -28,6 +30,40 @@ export const ECCHooksPlugin = async ({
|
||||
const log = (level: "debug" | "info" | "warn" | "error", message: string) =>
|
||||
client.app.log({ body: { service: "ecc", level, message } })
|
||||
|
||||
const normalizeProfile = (value: string | undefined): HookProfile => {
|
||||
if (value === "minimal" || value === "strict") return value
|
||||
return "standard"
|
||||
}
|
||||
|
||||
const currentProfile = normalizeProfile(process.env.ECC_HOOK_PROFILE)
|
||||
const disabledHooks = new Set(
|
||||
(process.env.ECC_DISABLED_HOOKS || "")
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
|
||||
const profileOrder: Record<HookProfile, number> = {
|
||||
minimal: 0,
|
||||
standard: 1,
|
||||
strict: 2,
|
||||
}
|
||||
|
||||
const profileAllowed = (required: HookProfile | HookProfile[]): boolean => {
|
||||
if (Array.isArray(required)) {
|
||||
return required.some((entry) => profileOrder[currentProfile] >= profileOrder[entry])
|
||||
}
|
||||
return profileOrder[currentProfile] >= profileOrder[required]
|
||||
}
|
||||
|
||||
const hookEnabled = (
|
||||
hookId: string,
|
||||
requiredProfile: HookProfile | HookProfile[] = "standard"
|
||||
): boolean => {
|
||||
if (disabledHooks.has(hookId)) return false
|
||||
return profileAllowed(requiredProfile)
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Prettier Auto-Format Hook
|
||||
@@ -41,7 +77,7 @@ export const ECCHooksPlugin = async ({
|
||||
editedFiles.add(event.path)
|
||||
|
||||
// Auto-format JS/TS files
|
||||
if (event.path.match(/\.(ts|tsx|js|jsx)$/)) {
|
||||
if (hookEnabled("post:edit:format", ["standard", "strict"]) && event.path.match(/\.(ts|tsx|js|jsx)$/)) {
|
||||
try {
|
||||
await $`prettier --write ${event.path} 2>/dev/null`
|
||||
log("info", `[ECC] Formatted: ${event.path}`)
|
||||
@@ -51,7 +87,7 @@ export const ECCHooksPlugin = async ({
|
||||
}
|
||||
|
||||
// Console.log warning check
|
||||
if (event.path.match(/\.(ts|tsx|js|jsx)$/)) {
|
||||
if (hookEnabled("post:edit:console-warn", ["standard", "strict"]) && event.path.match(/\.(ts|tsx|js|jsx)$/)) {
|
||||
try {
|
||||
const result = await $`grep -n "console\\.log" ${event.path} 2>/dev/null`.text()
|
||||
if (result.trim()) {
|
||||
@@ -80,6 +116,7 @@ export const ECCHooksPlugin = async ({
|
||||
) => {
|
||||
// Check if a TypeScript file was edited
|
||||
if (
|
||||
hookEnabled("post:edit:typecheck", ["standard", "strict"]) &&
|
||||
input.tool === "edit" &&
|
||||
input.args?.filePath?.match(/\.tsx?$/)
|
||||
) {
|
||||
@@ -98,7 +135,11 @@ export const ECCHooksPlugin = async ({
|
||||
}
|
||||
|
||||
// PR creation logging
|
||||
if (input.tool === "bash" && input.args?.toString().includes("gh pr create")) {
|
||||
if (
|
||||
hookEnabled("post:bash:pr-created", ["standard", "strict"]) &&
|
||||
input.tool === "bash" &&
|
||||
input.args?.toString().includes("gh pr create")
|
||||
) {
|
||||
log("info", "[ECC] PR created - check GitHub Actions status")
|
||||
}
|
||||
},
|
||||
@@ -115,6 +156,7 @@ export const ECCHooksPlugin = async ({
|
||||
) => {
|
||||
// Git push review reminder
|
||||
if (
|
||||
hookEnabled("pre:bash:git-push-reminder", "strict") &&
|
||||
input.tool === "bash" &&
|
||||
input.args?.toString().includes("git push")
|
||||
) {
|
||||
@@ -126,6 +168,7 @@ export const ECCHooksPlugin = async ({
|
||||
|
||||
// Block creation of unnecessary documentation files
|
||||
if (
|
||||
hookEnabled("pre:write:doc-file-warning", ["standard", "strict"]) &&
|
||||
input.tool === "write" &&
|
||||
input.args?.filePath &&
|
||||
typeof input.args.filePath === "string"
|
||||
@@ -146,7 +189,7 @@ export const ECCHooksPlugin = async ({
|
||||
}
|
||||
|
||||
// Long-running command reminder
|
||||
if (input.tool === "bash") {
|
||||
if (hookEnabled("pre:bash:tmux-reminder", "strict") && input.tool === "bash") {
|
||||
const cmd = String(input.args?.command || input.args || "")
|
||||
if (
|
||||
cmd.match(/^(npm|pnpm|yarn|bun)\s+(install|build|test|run)/) ||
|
||||
@@ -169,7 +212,9 @@ export const ECCHooksPlugin = async ({
|
||||
* Action: Loads context and displays welcome message
|
||||
*/
|
||||
"session.created": async () => {
|
||||
log("info", "[ECC] Session started - Everything Claude Code hooks active")
|
||||
if (!hookEnabled("session:start", ["minimal", "standard", "strict"])) return
|
||||
|
||||
log("info", `[ECC] Session started - profile=${currentProfile}`)
|
||||
|
||||
// Check for project-specific context files
|
||||
try {
|
||||
@@ -190,6 +235,7 @@ export const ECCHooksPlugin = async ({
|
||||
* Action: Runs console.log audit on all edited files
|
||||
*/
|
||||
"session.idle": async () => {
|
||||
if (!hookEnabled("stop:check-console-log", ["minimal", "standard", "strict"])) return
|
||||
if (editedFiles.size === 0) return
|
||||
|
||||
log("info", "[ECC] Session idle - running console.log audit")
|
||||
@@ -244,6 +290,7 @@ export const ECCHooksPlugin = async ({
|
||||
* Action: Final cleanup and state saving
|
||||
*/
|
||||
"session.deleted": async () => {
|
||||
if (!hookEnabled("session:end-marker", ["minimal", "standard", "strict"])) return
|
||||
log("info", "[ECC] Session ended - cleaning up")
|
||||
editedFiles.clear()
|
||||
},
|
||||
@@ -285,8 +332,10 @@ export const ECCHooksPlugin = async ({
|
||||
*/
|
||||
"shell.env": async () => {
|
||||
const env: Record<string, string> = {
|
||||
ECC_VERSION: "1.6.0",
|
||||
ECC_VERSION: "1.8.0",
|
||||
ECC_PLUGIN: "true",
|
||||
ECC_HOOK_PROFILE: currentProfile,
|
||||
ECC_DISABLED_HOOKS: process.env.ECC_DISABLED_HOOKS || "",
|
||||
PROJECT_ROOT: worktree || directory,
|
||||
}
|
||||
|
||||
@@ -343,7 +392,7 @@ export const ECCHooksPlugin = async ({
|
||||
const contextBlock = [
|
||||
"# ECC Context (preserve across compaction)",
|
||||
"",
|
||||
"## Active Plugin: Everything Claude Code v1.6.0",
|
||||
"## Active Plugin: Everything Claude Code v1.8.0",
|
||||
"- Hooks: file.edited, tool.execute.before/after, session.created/idle/deleted, shell.env, compacting, permission.ask",
|
||||
"- Tools: run-tests, check-coverage, security-audit, format-code, lint-check, git-summary",
|
||||
"- Agents: 13 specialized (planner, architect, tdd-guide, code-reviewer, security-reviewer, build-error-resolver, e2e-runner, refactor-cleaner, doc-updater, go-reviewer, go-build-resolver, database-reviewer, python-reviewer)",
|
||||
|
||||
@@ -1,66 +1,68 @@
|
||||
/**
|
||||
* ECC Custom Tool: Format Code
|
||||
*
|
||||
* Language-aware code formatter that auto-detects the project's formatter.
|
||||
* Supports: Biome/Prettier (JS/TS), Black (Python), gofmt (Go), rustfmt (Rust)
|
||||
* Returns the formatter command that should be run for a given file.
|
||||
* This avoids shell execution assumptions while still giving precise guidance.
|
||||
*/
|
||||
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { z } from "zod"
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
|
||||
type Formatter = "biome" | "prettier" | "black" | "gofmt" | "rustfmt"
|
||||
|
||||
export default tool({
|
||||
name: "format-code",
|
||||
description: "Format a file using the project's configured formatter. Auto-detects Biome, Prettier, Black, gofmt, or rustfmt.",
|
||||
parameters: z.object({
|
||||
filePath: z.string().describe("Path to the file to format"),
|
||||
formatter: z.string().optional().describe("Override formatter: biome, prettier, black, gofmt, rustfmt (default: auto-detect)"),
|
||||
}),
|
||||
execute: async ({ filePath, formatter }, { $ }) => {
|
||||
const ext = filePath.split(".").pop()?.toLowerCase() || ""
|
||||
|
||||
// Auto-detect formatter based on file extension and config files
|
||||
let detected = formatter
|
||||
if (!detected) {
|
||||
if (["ts", "tsx", "js", "jsx", "json", "css", "scss"].includes(ext)) {
|
||||
// Check for Biome first, then Prettier
|
||||
try {
|
||||
await $`test -f biome.json || test -f biome.jsonc`
|
||||
detected = "biome"
|
||||
} catch {
|
||||
detected = "prettier"
|
||||
}
|
||||
} else if (["py", "pyi"].includes(ext)) {
|
||||
detected = "black"
|
||||
} else if (ext === "go") {
|
||||
detected = "gofmt"
|
||||
} else if (ext === "rs") {
|
||||
detected = "rustfmt"
|
||||
}
|
||||
}
|
||||
description:
|
||||
"Detect formatter for a file and return the exact command to run (Biome, Prettier, Black, gofmt, rustfmt).",
|
||||
args: {
|
||||
filePath: tool.schema.string().describe("Path to the file to format"),
|
||||
formatter: tool.schema
|
||||
.enum(["biome", "prettier", "black", "gofmt", "rustfmt"])
|
||||
.optional()
|
||||
.describe("Optional formatter override"),
|
||||
},
|
||||
async execute(args, context) {
|
||||
const cwd = context.worktree || context.directory
|
||||
const ext = args.filePath.split(".").pop()?.toLowerCase() || ""
|
||||
const detected = args.formatter || detectFormatter(cwd, ext)
|
||||
|
||||
if (!detected) {
|
||||
return { formatted: false, message: `No formatter detected for .${ext} files` }
|
||||
return JSON.stringify({
|
||||
success: false,
|
||||
message: `No formatter detected for .${ext} files`,
|
||||
})
|
||||
}
|
||||
|
||||
const commands: Record<string, string> = {
|
||||
biome: `npx @biomejs/biome format --write ${filePath}`,
|
||||
prettier: `npx prettier --write ${filePath}`,
|
||||
black: `black ${filePath}`,
|
||||
gofmt: `gofmt -w ${filePath}`,
|
||||
rustfmt: `rustfmt ${filePath}`,
|
||||
}
|
||||
|
||||
const cmd = commands[detected]
|
||||
if (!cmd) {
|
||||
return { formatted: false, message: `Unknown formatter: ${detected}` }
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await $`${cmd}`.text()
|
||||
return { formatted: true, formatter: detected, output: result }
|
||||
} catch (error: unknown) {
|
||||
const err = error as { stderr?: string }
|
||||
return { formatted: false, formatter: detected, error: err.stderr || "Format failed" }
|
||||
}
|
||||
const command = buildFormatterCommand(detected, args.filePath)
|
||||
return JSON.stringify({
|
||||
success: true,
|
||||
formatter: detected,
|
||||
command,
|
||||
instructions: `Run this command:\n\n${command}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function detectFormatter(cwd: string, ext: string): Formatter | null {
|
||||
if (["ts", "tsx", "js", "jsx", "json", "css", "scss", "md", "yaml", "yml"].includes(ext)) {
|
||||
if (fs.existsSync(path.join(cwd, "biome.json")) || fs.existsSync(path.join(cwd, "biome.jsonc"))) {
|
||||
return "biome"
|
||||
}
|
||||
return "prettier"
|
||||
}
|
||||
if (["py", "pyi"].includes(ext)) return "black"
|
||||
if (ext === "go") return "gofmt"
|
||||
if (ext === "rs") return "rustfmt"
|
||||
return null
|
||||
}
|
||||
|
||||
function buildFormatterCommand(formatter: Formatter, filePath: string): string {
|
||||
const commands: Record<Formatter, string> = {
|
||||
biome: `npx @biomejs/biome format --write ${filePath}`,
|
||||
prettier: `npx prettier --write ${filePath}`,
|
||||
black: `black ${filePath}`,
|
||||
gofmt: `gofmt -w ${filePath}`,
|
||||
rustfmt: `rustfmt ${filePath}`,
|
||||
}
|
||||
return commands[formatter]
|
||||
}
|
||||
|
||||
@@ -1,56 +1,54 @@
|
||||
/**
|
||||
* ECC Custom Tool: Git Summary
|
||||
*
|
||||
* Provides a comprehensive git status including branch info, status,
|
||||
* recent log, and diff against base branch.
|
||||
* Returns branch/status/log/diff details for the active repository.
|
||||
*/
|
||||
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { z } from "zod"
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
import { execSync } from "child_process"
|
||||
|
||||
export default tool({
|
||||
name: "git-summary",
|
||||
description: "Get comprehensive git summary: branch, status, recent log, and diff against base branch.",
|
||||
parameters: z.object({
|
||||
depth: z.number().optional().describe("Number of recent commits to show (default: 5)"),
|
||||
includeDiff: z.boolean().optional().describe("Include diff against base branch (default: true)"),
|
||||
baseBranch: z.string().optional().describe("Base branch for comparison (default: main)"),
|
||||
}),
|
||||
execute: async ({ depth = 5, includeDiff = true, baseBranch = "main" }, { $ }) => {
|
||||
const results: Record<string, string> = {}
|
||||
description:
|
||||
"Generate git summary with branch, status, recent commits, and optional diff stats.",
|
||||
args: {
|
||||
depth: tool.schema
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Number of recent commits to include (default: 5)"),
|
||||
includeDiff: tool.schema
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Include diff stats against base branch (default: true)"),
|
||||
baseBranch: tool.schema
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Base branch for diff comparison (default: main)"),
|
||||
},
|
||||
async execute(args, context) {
|
||||
const cwd = context.worktree || context.directory
|
||||
const depth = args.depth ?? 5
|
||||
const includeDiff = args.includeDiff ?? true
|
||||
const baseBranch = args.baseBranch ?? "main"
|
||||
|
||||
try {
|
||||
results.branch = (await $`git branch --show-current`.text()).trim()
|
||||
} catch {
|
||||
results.branch = "unknown"
|
||||
}
|
||||
|
||||
try {
|
||||
results.status = (await $`git status --short`.text()).trim()
|
||||
} catch {
|
||||
results.status = "unable to get status"
|
||||
}
|
||||
|
||||
try {
|
||||
results.log = (await $`git log --oneline -${depth}`.text()).trim()
|
||||
} catch {
|
||||
results.log = "unable to get log"
|
||||
const result: Record<string, string> = {
|
||||
branch: run("git branch --show-current", cwd) || "unknown",
|
||||
status: run("git status --short", cwd) || "clean",
|
||||
log: run(`git log --oneline -${depth}`, cwd) || "no commits found",
|
||||
}
|
||||
|
||||
if (includeDiff) {
|
||||
try {
|
||||
results.stagedDiff = (await $`git diff --cached --stat`.text()).trim()
|
||||
} catch {
|
||||
results.stagedDiff = ""
|
||||
}
|
||||
|
||||
try {
|
||||
results.branchDiff = (await $`git diff ${baseBranch}...HEAD --stat`.text()).trim()
|
||||
} catch {
|
||||
results.branchDiff = `unable to diff against ${baseBranch}`
|
||||
}
|
||||
result.stagedDiff = run("git diff --cached --stat", cwd) || ""
|
||||
result.branchDiff = run(`git diff ${baseBranch}...HEAD --stat`, cwd) || `unable to diff against ${baseBranch}`
|
||||
}
|
||||
|
||||
return results
|
||||
return JSON.stringify(result)
|
||||
},
|
||||
})
|
||||
|
||||
function run(command: string, cwd: string): string {
|
||||
try {
|
||||
return execSync(command, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim()
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,85 @@
|
||||
/**
|
||||
* ECC Custom Tool: Lint Check
|
||||
*
|
||||
* Multi-language linter that auto-detects the project's linting tool.
|
||||
* Supports: ESLint/Biome (JS/TS), Pylint/Ruff (Python), golangci-lint (Go)
|
||||
* Detects the appropriate linter and returns a runnable lint command.
|
||||
*/
|
||||
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import { z } from "zod"
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
|
||||
type Linter = "biome" | "eslint" | "ruff" | "pylint" | "golangci-lint"
|
||||
|
||||
export default tool({
|
||||
name: "lint-check",
|
||||
description: "Run linter on files or directories. Auto-detects ESLint, Biome, Ruff, Pylint, or golangci-lint.",
|
||||
parameters: z.object({
|
||||
target: z.string().optional().describe("File or directory to lint (default: current directory)"),
|
||||
fix: z.boolean().optional().describe("Auto-fix issues if supported (default: false)"),
|
||||
linter: z.string().optional().describe("Override linter: eslint, biome, ruff, pylint, golangci-lint (default: auto-detect)"),
|
||||
}),
|
||||
execute: async ({ target = ".", fix = false, linter }, { $ }) => {
|
||||
// Auto-detect linter
|
||||
let detected = linter
|
||||
if (!detected) {
|
||||
try {
|
||||
await $`test -f biome.json || test -f biome.jsonc`
|
||||
detected = "biome"
|
||||
} catch {
|
||||
try {
|
||||
await $`test -f .eslintrc.json || test -f .eslintrc.js || test -f .eslintrc.cjs || test -f eslint.config.js || test -f eslint.config.mjs`
|
||||
detected = "eslint"
|
||||
} catch {
|
||||
try {
|
||||
await $`test -f pyproject.toml && grep -q "ruff" pyproject.toml`
|
||||
detected = "ruff"
|
||||
} catch {
|
||||
try {
|
||||
await $`test -f .golangci.yml || test -f .golangci.yaml`
|
||||
detected = "golangci-lint"
|
||||
} catch {
|
||||
// Fall back based on file extensions in target
|
||||
detected = "eslint"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
description:
|
||||
"Detect linter for a target path and return command for check/fix runs.",
|
||||
args: {
|
||||
target: tool.schema
|
||||
.string()
|
||||
.optional()
|
||||
.describe("File or directory to lint (default: current directory)"),
|
||||
fix: tool.schema
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Enable auto-fix mode"),
|
||||
linter: tool.schema
|
||||
.enum(["biome", "eslint", "ruff", "pylint", "golangci-lint"])
|
||||
.optional()
|
||||
.describe("Optional linter override"),
|
||||
},
|
||||
async execute(args, context) {
|
||||
const cwd = context.worktree || context.directory
|
||||
const target = args.target || "."
|
||||
const fix = args.fix ?? false
|
||||
const detected = args.linter || detectLinter(cwd)
|
||||
|
||||
const fixFlag = fix ? " --fix" : ""
|
||||
const commands: Record<string, string> = {
|
||||
biome: `npx @biomejs/biome lint${fix ? " --write" : ""} ${target}`,
|
||||
eslint: `npx eslint${fixFlag} ${target}`,
|
||||
ruff: `ruff check${fixFlag} ${target}`,
|
||||
pylint: `pylint ${target}`,
|
||||
"golangci-lint": `golangci-lint run${fixFlag} ${target}`,
|
||||
}
|
||||
|
||||
const cmd = commands[detected]
|
||||
if (!cmd) {
|
||||
return { success: false, message: `Unknown linter: ${detected}` }
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await $`${cmd}`.text()
|
||||
return { success: true, linter: detected, output: result, issues: 0 }
|
||||
} catch (error: unknown) {
|
||||
const err = error as { stdout?: string; stderr?: string }
|
||||
return {
|
||||
success: false,
|
||||
linter: detected,
|
||||
output: err.stdout || "",
|
||||
errors: err.stderr || "",
|
||||
}
|
||||
}
|
||||
const command = buildLintCommand(detected, target, fix)
|
||||
return JSON.stringify({
|
||||
success: true,
|
||||
linter: detected,
|
||||
command,
|
||||
instructions: `Run this command:\n\n${command}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
function detectLinter(cwd: string): Linter {
|
||||
if (fs.existsSync(path.join(cwd, "biome.json")) || fs.existsSync(path.join(cwd, "biome.jsonc"))) {
|
||||
return "biome"
|
||||
}
|
||||
|
||||
const eslintConfigs = [
|
||||
".eslintrc.json",
|
||||
".eslintrc.js",
|
||||
".eslintrc.cjs",
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
]
|
||||
if (eslintConfigs.some((name) => fs.existsSync(path.join(cwd, name)))) {
|
||||
return "eslint"
|
||||
}
|
||||
|
||||
const pyprojectPath = path.join(cwd, "pyproject.toml")
|
||||
if (fs.existsSync(pyprojectPath)) {
|
||||
try {
|
||||
const content = fs.readFileSync(pyprojectPath, "utf-8")
|
||||
if (content.includes("ruff")) return "ruff"
|
||||
} catch {
|
||||
// ignore read errors and keep fallback logic
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(path.join(cwd, ".golangci.yml")) || fs.existsSync(path.join(cwd, ".golangci.yaml"))) {
|
||||
return "golangci-lint"
|
||||
}
|
||||
|
||||
return "eslint"
|
||||
}
|
||||
|
||||
function buildLintCommand(linter: Linter, target: string, fix: boolean): string {
|
||||
if (linter === "biome") return `npx @biomejs/biome lint${fix ? " --write" : ""} ${target}`
|
||||
if (linter === "eslint") return `npx eslint${fix ? " --fix" : ""} ${target}`
|
||||
if (linter === "ruff") return `ruff check${fix ? " --fix" : ""} ${target}`
|
||||
if (linter === "pylint") return `pylint ${target}`
|
||||
return `golangci-lint run ${target}`
|
||||
}
|
||||
|
||||
46
CHANGELOG.md
Normal file
46
CHANGELOG.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
## 1.8.0 - 2026-03-04
|
||||
|
||||
### Highlights
|
||||
|
||||
- Harness-first release focused on reliability, eval discipline, and autonomous loop operations.
|
||||
- Hook runtime now supports profile-based control and targeted hook disabling.
|
||||
- NanoClaw v2 adds model routing, skill hot-load, branching, search, compaction, export, and metrics.
|
||||
|
||||
### Core
|
||||
|
||||
- Added new commands: `/harness-audit`, `/loop-start`, `/loop-status`, `/quality-gate`, `/model-route`.
|
||||
- Added new skills:
|
||||
- `agent-harness-construction`
|
||||
- `agentic-engineering`
|
||||
- `ralphinho-rfc-pipeline`
|
||||
- `ai-first-engineering`
|
||||
- `enterprise-agent-ops`
|
||||
- `nanoclaw-repl`
|
||||
- `continuous-agent-loop`
|
||||
- Added new agents:
|
||||
- `harness-optimizer`
|
||||
- `loop-operator`
|
||||
|
||||
### Hook Reliability
|
||||
|
||||
- Fixed SessionStart root resolution with robust fallback search.
|
||||
- Moved session summary persistence to `Stop` where transcript payload is available.
|
||||
- Added quality-gate and cost-tracker hooks.
|
||||
- Replaced fragile inline hook one-liners with dedicated script files.
|
||||
- Added `ECC_HOOK_PROFILE` and `ECC_DISABLED_HOOKS` controls.
|
||||
|
||||
### Cross-Platform
|
||||
|
||||
- Improved Windows-safe path handling in doc warning logic.
|
||||
- Hardened observer loop behavior to avoid non-interactive hangs.
|
||||
|
||||
### Notes
|
||||
|
||||
- `autonomous-loops` is kept as a compatibility alias for one release; `continuous-agent-loop` is the canonical name.
|
||||
|
||||
### Credits
|
||||
|
||||
- inspired by [zarazhangrui](https://github.com/zarazhangrui)
|
||||
- homunculus-inspired by [humanplane](https://github.com/humanplane)
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -62,7 +62,7 @@ cp -r skills/my-skill ~/.claude/skills/ # for skills
|
||||
# Then test with Claude Code
|
||||
|
||||
# 5. Submit PR
|
||||
git add . && git commit -m "feat: add my-skill" && git push
|
||||
git add . && git commit -m "feat: add my-skill" && git push -u origin feat/my-contribution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
105
README.md
105
README.md
@@ -5,6 +5,9 @@
|
||||
[](https://github.com/affaan-m/everything-claude-code/stargazers)
|
||||
[](https://github.com/affaan-m/everything-claude-code/network/members)
|
||||
[](https://github.com/affaan-m/everything-claude-code/graphs/contributors)
|
||||
[](https://www.npmjs.com/package/ecc-universal)
|
||||
[](https://www.npmjs.com/package/ecc-agentshield)
|
||||
[](https://github.com/marketplace/ecc-tools)
|
||||
[](LICENSE)
|
||||

|
||||

|
||||
@@ -35,6 +38,24 @@ Works across **Claude Code**, **Codex**, **Cowork**, and other AI agent harnesse
|
||||
|
||||
---
|
||||
|
||||
## Traction & Distribution
|
||||
|
||||
Use these live signals when presenting ECC to sponsors, platforms, or ecosystem partners:
|
||||
|
||||
- **Main package installs:** [`ecc-universal` on npm](https://www.npmjs.com/package/ecc-universal)
|
||||
- **Security companion installs:** [`ecc-agentshield` on npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||
- **GitHub App distribution:** [ECC Tools marketplace listing](https://github.com/marketplace/ecc-tools)
|
||||
- **Automated monthly metrics issue:** powered by `.github/workflows/monthly-metrics.yml`
|
||||
- **Repo adoption signal:** stars/forks/contributors badges at the top of this README
|
||||
|
||||
Download counts for Claude Code plugin installs are not currently exposed as a public API. For partner reporting, combine npm metrics with GitHub App installs and repository traffic/fork growth.
|
||||
|
||||
For a sponsor-call metrics checklist and command snippets, see [`docs/business/metrics-and-sponsorship.md`](docs/business/metrics-and-sponsorship.md).
|
||||
|
||||
[**Sponsor ECC**](https://github.com/sponsors/affaan-m) | [Sponsor Tiers](SPONSORS.md) | [Sponsorship Program](SPONSORING.md)
|
||||
|
||||
---
|
||||
|
||||
## The Guides
|
||||
|
||||
This repo is the raw code only. The guides explain everything.
|
||||
@@ -71,6 +92,16 @@ This repo is the raw code only. The guides explain everything.
|
||||
|
||||
## What's New
|
||||
|
||||
### v1.8.0 — Harness Performance System (Mar 2026)
|
||||
|
||||
- **Harness-first release** — ECC is now explicitly framed as an agent harness performance system, not just a config pack.
|
||||
- **Hook reliability overhaul** — SessionStart root fallback, Stop-phase session summaries, and script-based hooks replacing fragile inline one-liners.
|
||||
- **Hook runtime controls** — `ECC_HOOK_PROFILE=minimal|standard|strict` and `ECC_DISABLED_HOOKS=...` for runtime gating without editing hook files.
|
||||
- **New harness commands** — `/harness-audit`, `/loop-start`, `/loop-status`, `/quality-gate`, `/model-route`.
|
||||
- **NanoClaw v2** — model routing, skill hot-load, session branch/search/export/compact/metrics.
|
||||
- **Cross-harness parity** — behavior tightened across Claude Code, Cursor, OpenCode, and Codex app/CLI.
|
||||
- **997 internal tests passing** — full suite green after hook/runtime refactor and compatibility updates.
|
||||
|
||||
### v1.7.0 — Cross-Platform Expansion & Presentation Builder (Feb 2026)
|
||||
|
||||
- **Codex app + CLI support** — Direct `AGENTS.md`-based Codex support, installer targeting, and Codex docs
|
||||
@@ -147,6 +178,8 @@ cd everything-claude-code
|
||||
# ./install.sh typescript python golang
|
||||
# or target cursor:
|
||||
# ./install.sh --target cursor typescript
|
||||
# or target antigravity:
|
||||
# ./install.sh --target antigravity typescript
|
||||
```
|
||||
|
||||
For manual install instructions see the README in the `rules/` folder.
|
||||
@@ -164,13 +197,13 @@ For manual install instructions see the README in the `rules/` folder.
|
||||
/plugin list everything-claude-code@everything-claude-code
|
||||
```
|
||||
|
||||
✨ **That's it!** You now have access to 13 agents, 56 skills, and 32 commands.
|
||||
✨ **That's it!** You now have access to 16 agents, 65 skills, and 40 commands.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Cross-Platform Support
|
||||
|
||||
This plugin now fully supports **Windows, macOS, and Linux**. All hooks and scripts have been rewritten in Node.js for maximum compatibility.
|
||||
This plugin now fully supports **Windows, macOS, and Linux**, alongside tight integration across major IDEs (Cursor, OpenCode, Antigravity) and CLI harnesses. All hooks and scripts have been rewritten in Node.js for maximum compatibility.
|
||||
|
||||
### Package Manager Detection
|
||||
|
||||
@@ -201,6 +234,18 @@ node scripts/setup-package-manager.js --detect
|
||||
|
||||
Or use the `/setup-pm` command in Claude Code.
|
||||
|
||||
### Hook Runtime Controls
|
||||
|
||||
Use runtime flags to tune strictness or disable specific hooks temporarily:
|
||||
|
||||
```bash
|
||||
# Hook strictness profile (default: standard)
|
||||
export ECC_HOOK_PROFILE=standard
|
||||
|
||||
# Comma-separated hook IDs to disable
|
||||
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 What's Inside
|
||||
@@ -283,6 +328,8 @@ everything-claude-code/
|
||||
| |-- liquid-glass-design/ # iOS 26 Liquid Glass design system (NEW)
|
||||
| |-- foundation-models-on-device/ # Apple on-device LLM with FoundationModels (NEW)
|
||||
| |-- swift-concurrency-6-2/ # Swift 6.2 Approachable Concurrency (NEW)
|
||||
| |-- autonomous-loops/ # Autonomous loop patterns: sequential pipelines, PR loops, DAG orchestration (NEW)
|
||||
| |-- plankton-code-quality/ # Write-time code quality enforcement with Plankton hooks (NEW)
|
||||
|
|
||||
|-- commands/ # Slash commands for quick execution
|
||||
| |-- tdd.md # /tdd - Test-driven development
|
||||
@@ -442,9 +489,9 @@ Use `/security-scan` in Claude Code to run it, or add to CI with the [GitHub Act
|
||||
|
||||
[GitHub](https://github.com/affaan-m/agentshield) | [npm](https://www.npmjs.com/package/ecc-agentshield)
|
||||
|
||||
### 🔬 Plankton — Code Quality Integration
|
||||
### 🔬 Plankton — Write-Time Code Quality Enforcement
|
||||
|
||||
[Plankton](https://github.com/alexfazio/plankton) is a recommended companion for code quality enforcement. It provides automated code review, linting orchestration, and quality gates that pair well with the ECC skill and hook system. Use it alongside AgentShield for security + quality coverage.
|
||||
Plankton (credit: @alxfazio) is a recommended companion for write-time code quality enforcement. It runs formatters and 20+ linters on every file edit via PostToolUse hooks, then spawns Claude subprocesses (routed to Haiku/Sonnet/Opus by violation complexity) to fix issues the main agent missed. Three-phase architecture: auto-format silently (40-50% of issues), collect remaining violations as structured JSON, delegate fixes to a subprocess. Includes config protection hooks that prevent agents from modifying linter configs to pass instead of fixing code. Supports Python, TypeScript, Shell, YAML, JSON, TOML, Markdown, and Dockerfile. Use alongside AgentShield for security + quality coverage. See `skills/plankton-code-quality/` for full integration guide.
|
||||
|
||||
### 🧠 Continuous Learning v2
|
||||
|
||||
@@ -743,12 +790,13 @@ Each component is fully independent.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Does this work with Cursor / OpenCode / Codex?</b></summary>
|
||||
<summary><b>Does this work with Cursor / OpenCode / Codex / Antigravity?</b></summary>
|
||||
|
||||
Yes. ECC is cross-platform:
|
||||
- **Cursor**: Pre-translated configs in `.cursor/`. See [Cursor IDE Support](#cursor-ide-support).
|
||||
- **OpenCode**: Full plugin support in `.opencode/`. See [OpenCode Support](#-opencode-support).
|
||||
- **Codex**: First-class support with adapter drift guards and SessionStart fallback. See PR [#257](https://github.com/affaan-m/everything-claude-code/pull/257).
|
||||
- **Codex**: First-class support for both macOS app and CLI, with adapter drift guards and SessionStart fallback. See PR [#257](https://github.com/affaan-m/everything-claude-code/pull/257).
|
||||
- **Antigravity**: Tightly integrated setup for workflows, skills, and flatten rules in `.agent/`.
|
||||
- **Claude Code**: Native — this is the primary target.
|
||||
</details>
|
||||
|
||||
@@ -856,20 +904,25 @@ alwaysApply: false
|
||||
|
||||
---
|
||||
|
||||
## Codex CLI Support
|
||||
## Codex macOS App + CLI Support
|
||||
|
||||
ECC provides **first-class Codex CLI support** with a reference configuration, Codex-specific AGENTS.md supplement, and 16 ported skills.
|
||||
ECC provides **first-class Codex support** for both the macOS app and CLI, with a reference configuration, Codex-specific AGENTS.md supplement, and shared skills.
|
||||
|
||||
### Quick Start (Codex)
|
||||
### Quick Start (Codex App + CLI)
|
||||
|
||||
```bash
|
||||
# Copy the reference config to your home directory
|
||||
cp .codex/config.toml ~/.codex/config.toml
|
||||
|
||||
# Run Codex in the repo — AGENTS.md is auto-detected
|
||||
# Run Codex CLI in the repo — AGENTS.md is auto-detected
|
||||
codex
|
||||
```
|
||||
|
||||
Codex macOS app:
|
||||
- Open this repository as your workspace.
|
||||
- The root `AGENTS.md` is auto-detected.
|
||||
- Optional: copy `.codex/config.toml` to `~/.codex/config.toml` for CLI/app behavior consistency.
|
||||
|
||||
### What's Included
|
||||
|
||||
| Component | Count | Details |
|
||||
@@ -905,7 +958,7 @@ Skills at `.agents/skills/` are auto-loaded by Codex:
|
||||
|
||||
### Key Limitation
|
||||
|
||||
Codex CLI does **not yet support hooks** ([GitHub Issue #2109](https://github.com/openai/codex/issues/2109), 430+ upvotes). Security enforcement is instruction-based via `persistent_instructions` in config.toml and the sandbox permission system.
|
||||
Codex does **not yet provide Claude-style hook execution parity**. ECC enforcement there is instruction-based via `AGENTS.md` and `persistent_instructions`, plus sandbox permissions.
|
||||
|
||||
---
|
||||
|
||||
@@ -929,9 +982,9 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
||||
|
||||
| Feature | Claude Code | OpenCode | Status |
|
||||
|---------|-------------|----------|--------|
|
||||
| Agents | ✅ 13 agents | ✅ 12 agents | **Claude Code leads** |
|
||||
| Commands | ✅ 33 commands | ✅ 24 commands | **Claude Code leads** |
|
||||
| Skills | ✅ 50+ skills | ✅ 37 skills | **Claude Code leads** |
|
||||
| Agents | ✅ 16 agents | ✅ 12 agents | **Claude Code leads** |
|
||||
| Commands | ✅ 40 commands | ✅ 31 commands | **Claude Code leads** |
|
||||
| Skills | ✅ 65 skills | ✅ 37 skills | **Claude Code leads** |
|
||||
| Hooks | ✅ 8 event types | ✅ 11 events | **OpenCode has more!** |
|
||||
| Rules | ✅ 29 rules | ✅ 13 instructions | **Claude Code leads** |
|
||||
| MCP Servers | ✅ 14 servers | ✅ Full | **Full parity** |
|
||||
@@ -951,7 +1004,7 @@ OpenCode's plugin system is MORE sophisticated than Claude Code with 20+ event t
|
||||
|
||||
**Additional OpenCode events**: `file.edited`, `file.watcher.updated`, `message.updated`, `lsp.client.diagnostics`, `tui.toast.show`, and more.
|
||||
|
||||
### Available Commands (32)
|
||||
### Available Commands (31+)
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
@@ -989,6 +1042,11 @@ OpenCode's plugin system is MORE sophisticated than Claude Code with 20+ event t
|
||||
| `/projects` | List known projects and instinct stats |
|
||||
| `/learn-eval` | Extract and evaluate patterns before saving |
|
||||
| `/setup-pm` | Configure package manager |
|
||||
| `/harness-audit` | Audit harness reliability, eval readiness, and risk posture |
|
||||
| `/loop-start` | Start controlled agentic loop execution pattern |
|
||||
| `/loop-status` | Inspect active loop status and checkpoints |
|
||||
| `/quality-gate` | Run quality gate checks for paths or entire repo |
|
||||
| `/model-route` | Route tasks to models by complexity and budget |
|
||||
|
||||
### Plugin Installation
|
||||
|
||||
@@ -1025,11 +1083,11 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
||||
|
||||
| Feature | Claude Code | Cursor IDE | Codex CLI | OpenCode |
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **Agents** | 13 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Commands** | 33 | Shared | Instruction-based | 24 |
|
||||
| **Skills** | 50+ | Shared | 10 (native format) | 37 |
|
||||
| **Agents** | 16 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Commands** | 40 | Shared | Instruction-based | 31 |
|
||||
| **Skills** | 65 | Shared | 10 (native format) | 37 |
|
||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
||||
| **Hook Scripts** | 9 scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||
| **Rules** | 29 (common + lang) | 29 (YAML frontmatter) | Instruction-based | 13 instructions |
|
||||
| **Custom Tools** | Via hooks | Via hooks | N/A | 6 native tools |
|
||||
| **MCP Servers** | 14 | Shared (mcp.json) | 4 (command-based) | Full |
|
||||
@@ -1037,7 +1095,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
||||
| **Context File** | CLAUDE.md + AGENTS.md | AGENTS.md | AGENTS.md | AGENTS.md |
|
||||
| **Secret Detection** | Hook-based | beforeSubmitPrompt hook | Sandbox-based | Hook-based |
|
||||
| **Auto-Format** | PostToolUse hook | afterFileEdit hook | N/A | file.edited hook |
|
||||
| **Version** | Plugin | Plugin | Reference config | 1.6.0 |
|
||||
| **Version** | Plugin | Plugin | Reference config | 1.8.0 |
|
||||
|
||||
**Key architectural decisions:**
|
||||
- **AGENTS.md** at root is the universal cross-tool file (read by all 4 tools)
|
||||
@@ -1053,6 +1111,11 @@ I've been using Claude Code since the experimental rollout. Won the Anthropic x
|
||||
|
||||
These configs are battle-tested across multiple production applications.
|
||||
|
||||
## Inspiration Credits
|
||||
|
||||
- inspired by [zarazhangrui](https://github.com/zarazhangrui)
|
||||
- homunculus-inspired by [humanplane](https://github.com/humanplane)
|
||||
|
||||
---
|
||||
|
||||
## Token Optimization
|
||||
@@ -1157,7 +1220,7 @@ These configs work for my workflow. You should:
|
||||
|
||||
This project is free and open source. Sponsors help keep it maintained and growing.
|
||||
|
||||
[**Become a Sponsor**](https://github.com/sponsors/affaan-m) | [Sponsor Tiers](SPONSORS.md)
|
||||
[**Become a Sponsor**](https://github.com/sponsors/affaan-m) | [Sponsor Tiers](SPONSORS.md) | [Sponsorship Program](SPONSORING.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
43
SPONSORING.md
Normal file
43
SPONSORING.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Sponsoring ECC
|
||||
|
||||
ECC is maintained as an open-source agent harness performance system across Claude Code, Cursor, OpenCode, and Codex app/CLI.
|
||||
|
||||
## Why Sponsor
|
||||
|
||||
Sponsorship directly funds:
|
||||
|
||||
- Faster bug-fix and release cycles
|
||||
- Cross-platform parity work across harnesses
|
||||
- Public docs, skills, and reliability tooling that remain free for the community
|
||||
|
||||
## Sponsorship Tiers
|
||||
|
||||
These are practical starting points and can be adjusted for partnership scope.
|
||||
|
||||
| Tier | Price | Best For | Includes |
|
||||
|------|-------|----------|----------|
|
||||
| Pilot Partner | $200/mo | First sponsor engagement | Monthly metrics update, roadmap preview, prioritized maintainer feedback |
|
||||
| Growth Partner | $500/mo | Teams actively adopting ECC | Pilot benefits + monthly office-hours sync + workflow integration guidance |
|
||||
| Strategic Partner | $1,000+/mo | Platform/ecosystem partnerships | Growth benefits + coordinated launch support + deeper maintainer collaboration |
|
||||
|
||||
## Sponsor Reporting
|
||||
|
||||
Metrics shared monthly can include:
|
||||
|
||||
- npm downloads (`ecc-universal`, `ecc-agentshield`)
|
||||
- Repository adoption (stars, forks, contributors)
|
||||
- GitHub App install trend
|
||||
- Release cadence and reliability milestones
|
||||
|
||||
For exact command snippets and a repeatable pull process, see [`docs/business/metrics-and-sponsorship.md`](docs/business/metrics-and-sponsorship.md).
|
||||
|
||||
## Expectations and Scope
|
||||
|
||||
- Sponsorship supports maintenance and acceleration; it does not transfer project ownership.
|
||||
- Feature requests are prioritized based on sponsor tier, ecosystem impact, and maintenance risk.
|
||||
- Security and reliability fixes take precedence over net-new features.
|
||||
|
||||
## Sponsor Here
|
||||
|
||||
- GitHub Sponsors: [https://github.com/sponsors/affaan-m](https://github.com/sponsors/affaan-m)
|
||||
- Project site: [https://ecc.tools](https://ecc.tools)
|
||||
12
SPONSORS.md
12
SPONSORS.md
@@ -29,6 +29,17 @@ Your sponsorship helps:
|
||||
- **Better support** — Sponsors get priority responses
|
||||
- **Shape the roadmap** — Pro+ sponsors vote on features
|
||||
|
||||
## Sponsor Readiness Signals
|
||||
|
||||
Use these proof points in sponsor conversations:
|
||||
|
||||
- Live npm install/download metrics for `ecc-universal` and `ecc-agentshield`
|
||||
- GitHub App distribution via Marketplace installs
|
||||
- Public adoption signals: stars, forks, contributors, release cadence
|
||||
- Cross-harness support: Claude Code, Cursor, OpenCode, Codex app/CLI
|
||||
|
||||
See [`docs/business/metrics-and-sponsorship.md`](docs/business/metrics-and-sponsorship.md) for a copy/paste metrics pull workflow.
|
||||
|
||||
## Sponsor Tiers
|
||||
|
||||
| Tier | Price | Benefits |
|
||||
@@ -37,6 +48,7 @@ Your sponsorship helps:
|
||||
| Builder | $10/mo | Premium tools access |
|
||||
| Pro | $25/mo | Priority support, office hours |
|
||||
| Team | $100/mo | 5 seats, team configs |
|
||||
| Harness Partner | $200/mo | Monthly roadmap sync, prioritized maintainer feedback, release-note mention |
|
||||
| Business | $500/mo | 25 seats, consulting credit |
|
||||
| Enterprise | $2K/mo | Unlimited seats, custom tools |
|
||||
|
||||
|
||||
@@ -146,6 +146,6 @@ claude /schedule-reply "Reply to Sarah about the board meeting"
|
||||
## Prerequisites
|
||||
|
||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
||||
- Gmail CLI (e.g., [gog](https://github.com/pterm/gog))
|
||||
- Gmail CLI (e.g., gog by @pterm)
|
||||
- Node.js 18+ (for calendar-suggest.js)
|
||||
- Optional: Slack MCP server, Matrix bridge (LINE), Chrome + Playwright (Messenger)
|
||||
|
||||
@@ -222,3 +222,16 @@ When available, also check project-specific conventions from `CLAUDE.md` or proj
|
||||
- State management conventions (Zustand, Redux, Context)
|
||||
|
||||
Adapt your review to the project's established patterns. When in doubt, match what the rest of the codebase does.
|
||||
|
||||
## v1.8 AI-Generated Code Review Addendum
|
||||
|
||||
When reviewing AI-generated changes, prioritize:
|
||||
|
||||
1. Behavioral regressions and edge-case handling
|
||||
2. Security assumptions and trust boundaries
|
||||
3. Hidden coupling or accidental architecture drift
|
||||
4. Unnecessary model-cost-inducing complexity
|
||||
|
||||
Cost-awareness check:
|
||||
- Flag workflows that escalate to higher-cost models without clear reasoning need.
|
||||
- Recommend defaulting to lower-cost tiers for deterministic refactors.
|
||||
|
||||
@@ -7,7 +7,7 @@ model: sonnet
|
||||
|
||||
# Database Reviewer
|
||||
|
||||
You are an expert PostgreSQL database specialist focused on query optimization, schema design, security, and performance. Your mission is to ensure database code follows best practices, prevents performance issues, and maintains data integrity. Incorporates patterns from [Supabase's postgres-best-practices](https://github.com/supabase/agent-skills).
|
||||
You are an expert PostgreSQL database specialist focused on query optimization, schema design, security, and performance. Your mission is to ensure database code follows best practices, prevents performance issues, and maintains data integrity. Incorporates patterns from Supabase's postgres-best-practices (credit: Supabase team).
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
@@ -88,4 +88,4 @@ For detailed index patterns, schema design examples, connection management, conc
|
||||
|
||||
**Remember**: Database issues are often the root cause of application performance problems. Optimize queries and schema design early. Use EXPLAIN ANALYZE to verify assumptions. Always index foreign keys and RLS policy columns.
|
||||
|
||||
*Patterns adapted from [Supabase Agent Skills](https://github.com/supabase/agent-skills) under MIT license.*
|
||||
*Patterns adapted from Supabase Agent Skills (credit: Supabase team) under MIT license.*
|
||||
|
||||
35
agents/harness-optimizer.md
Normal file
35
agents/harness-optimizer.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: harness-optimizer
|
||||
description: Analyze and improve the local agent harness configuration for reliability, cost, and throughput.
|
||||
tools: ["Read", "Grep", "Glob", "Bash", "Edit"]
|
||||
model: sonnet
|
||||
color: teal
|
||||
---
|
||||
|
||||
You are the harness optimizer.
|
||||
|
||||
## Mission
|
||||
|
||||
Raise agent completion quality by improving harness configuration, not by rewriting product code.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Run `/harness-audit` and collect baseline score.
|
||||
2. Identify top 3 leverage areas (hooks, evals, routing, context, safety).
|
||||
3. Propose minimal, reversible configuration changes.
|
||||
4. Apply changes and run validation.
|
||||
5. Report before/after deltas.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Prefer small changes with measurable effect.
|
||||
- Preserve cross-platform behavior.
|
||||
- Avoid introducing fragile shell quoting.
|
||||
- Keep compatibility across Claude Code, Cursor, OpenCode, and Codex.
|
||||
|
||||
## Output
|
||||
|
||||
- baseline scorecard
|
||||
- applied changes
|
||||
- measured improvements
|
||||
- remaining risks
|
||||
36
agents/loop-operator.md
Normal file
36
agents/loop-operator.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: loop-operator
|
||||
description: Operate autonomous agent loops, monitor progress, and intervene safely when loops stall.
|
||||
tools: ["Read", "Grep", "Glob", "Bash", "Edit"]
|
||||
model: sonnet
|
||||
color: orange
|
||||
---
|
||||
|
||||
You are the loop operator.
|
||||
|
||||
## Mission
|
||||
|
||||
Run autonomous loops safely with clear stop conditions, observability, and recovery actions.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Start loop from explicit pattern and mode.
|
||||
2. Track progress checkpoints.
|
||||
3. Detect stalls and retry storms.
|
||||
4. Pause and reduce scope when failure repeats.
|
||||
5. Resume only after verification passes.
|
||||
|
||||
## Required Checks
|
||||
|
||||
- quality gates are active
|
||||
- eval baseline exists
|
||||
- rollback path exists
|
||||
- branch/worktree isolation is configured
|
||||
|
||||
## Escalation
|
||||
|
||||
Escalate when any condition is true:
|
||||
- no progress across two consecutive checkpoints
|
||||
- repeated failures with identical stack traces
|
||||
- cost drift outside budget window
|
||||
- merge conflicts blocking queue advancement
|
||||
@@ -78,3 +78,14 @@ npm run test:coverage
|
||||
- [ ] Coverage is 80%+
|
||||
|
||||
For detailed mocking patterns and framework-specific examples, see `skill: tdd-workflow`.
|
||||
|
||||
## v1.8 Eval-Driven TDD Addendum
|
||||
|
||||
Integrate eval-driven development into TDD flow:
|
||||
|
||||
1. Define capability + regression evals before implementation.
|
||||
2. Run baseline and capture failure signatures.
|
||||
3. Implement minimum passing change.
|
||||
4. Re-run tests and evals; report pass@1 and pass@3.
|
||||
|
||||
Release-critical paths should target pass^3 stability before merge.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
description: Start the NanoClaw agent REPL — a persistent, session-aware AI assistant powered by the claude CLI.
|
||||
description: Start NanoClaw v2 — ECC's persistent, zero-dependency REPL with model routing, skill hot-load, branching, compaction, export, and metrics.
|
||||
---
|
||||
|
||||
# Claw Command
|
||||
|
||||
Start an interactive AI agent session that persists conversation history to disk and optionally loads ECC skill context.
|
||||
Start an interactive AI agent session with persistent markdown history and operational controls.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -23,57 +23,29 @@ npm run claw
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CLAW_SESSION` | `default` | Session name (alphanumeric + hyphens) |
|
||||
| `CLAW_SKILLS` | *(empty)* | Comma-separated skill names to load as system context |
|
||||
| `CLAW_SKILLS` | *(empty)* | Comma-separated skills loaded at startup |
|
||||
| `CLAW_MODEL` | `sonnet` | Default model for the session |
|
||||
|
||||
## REPL Commands
|
||||
|
||||
Inside the REPL, type these commands directly at the prompt:
|
||||
|
||||
```
|
||||
/clear Clear current session history
|
||||
/history Print full conversation history
|
||||
/sessions List all saved sessions
|
||||
/help Show available commands
|
||||
exit Quit the REPL
|
||||
```text
|
||||
/help Show help
|
||||
/clear Clear current session history
|
||||
/history Print full conversation history
|
||||
/sessions List saved sessions
|
||||
/model [name] Show/set model
|
||||
/load <skill-name> Hot-load a skill into context
|
||||
/branch <session-name> Branch current session
|
||||
/search <query> Search query across sessions
|
||||
/compact Compact old turns, keep recent context
|
||||
/export <md|json|txt> [path] Export session
|
||||
/metrics Show session metrics
|
||||
exit Quit
|
||||
```
|
||||
|
||||
## How It Works
|
||||
## Notes
|
||||
|
||||
1. Reads `CLAW_SESSION` env var to select a named session (default: `default`)
|
||||
2. Loads conversation history from `~/.claude/claw/{session}.md`
|
||||
3. Optionally loads ECC skill context from `CLAW_SKILLS` env var
|
||||
4. Enters a blocking prompt loop — each user message is sent to `claude -p` with full history
|
||||
5. Responses are appended to the session file for persistence across restarts
|
||||
|
||||
## Session Storage
|
||||
|
||||
Sessions are stored as Markdown files in `~/.claude/claw/`:
|
||||
|
||||
```
|
||||
~/.claude/claw/default.md
|
||||
~/.claude/claw/my-project.md
|
||||
```
|
||||
|
||||
Each turn is formatted as:
|
||||
|
||||
```markdown
|
||||
### [2025-01-15T10:30:00.000Z] User
|
||||
What does this function do?
|
||||
---
|
||||
### [2025-01-15T10:30:05.000Z] Assistant
|
||||
This function calculates...
|
||||
---
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Start default session
|
||||
node scripts/claw.js
|
||||
|
||||
# Named session
|
||||
CLAW_SESSION=my-project node scripts/claw.js
|
||||
|
||||
# With skill context
|
||||
CLAW_SKILLS=tdd-workflow,security-review node scripts/claw.js
|
||||
```
|
||||
- NanoClaw remains zero-dependency.
|
||||
- Sessions are stored at `~/.claude/claw/<session>.md`.
|
||||
- Compaction keeps the most recent turns and writes a compaction header.
|
||||
- Export supports markdown, JSON turns, and plain text.
|
||||
|
||||
58
commands/harness-audit.md
Normal file
58
commands/harness-audit.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Harness Audit Command
|
||||
|
||||
Audit the current repository's agent harness setup and return a prioritized scorecard.
|
||||
|
||||
## Usage
|
||||
|
||||
`/harness-audit [scope] [--format text|json]`
|
||||
|
||||
- `scope` (optional): `repo` (default), `hooks`, `skills`, `commands`, `agents`
|
||||
- `--format`: output style (`text` default, `json` for automation)
|
||||
|
||||
## What to Evaluate
|
||||
|
||||
Score each category from `0` to `10`:
|
||||
|
||||
1. Tool Coverage
|
||||
2. Context Efficiency
|
||||
3. Quality Gates
|
||||
4. Memory Persistence
|
||||
5. Eval Coverage
|
||||
6. Security Guardrails
|
||||
7. Cost Efficiency
|
||||
|
||||
## Output Contract
|
||||
|
||||
Return:
|
||||
|
||||
1. `overall_score` out of 70
|
||||
2. Category scores and concrete findings
|
||||
3. Top 3 actions with exact file paths
|
||||
4. Suggested ECC skills to apply next
|
||||
|
||||
## Checklist
|
||||
|
||||
- Inspect `hooks/hooks.json`, `scripts/hooks/`, and hook tests.
|
||||
- Inspect `skills/`, command coverage, and agent coverage.
|
||||
- Verify cross-harness parity for `.cursor/`, `.opencode/`, `.codex/`.
|
||||
- Flag broken or stale references.
|
||||
|
||||
## Example Result
|
||||
|
||||
```text
|
||||
Harness Audit (repo): 52/70
|
||||
- Quality Gates: 9/10
|
||||
- Eval Coverage: 6/10
|
||||
- Cost Efficiency: 4/10
|
||||
|
||||
Top 3 Actions:
|
||||
1) Add cost tracking hook in scripts/hooks/cost-tracker.js
|
||||
2) Add pass@k docs and templates in skills/eval-harness/SKILL.md
|
||||
3) Add command parity for /harness-audit in .opencode/commands/
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `repo|hooks|skills|commands|agents` (optional scope)
|
||||
- `--format text|json` (optional output format)
|
||||
32
commands/loop-start.md
Normal file
32
commands/loop-start.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Loop Start Command
|
||||
|
||||
Start a managed autonomous loop pattern with safety defaults.
|
||||
|
||||
## Usage
|
||||
|
||||
`/loop-start [pattern] [--mode safe|fast]`
|
||||
|
||||
- `pattern`: `sequential`, `continuous-pr`, `rfc-dag`, `infinite`
|
||||
- `--mode`:
|
||||
- `safe` (default): strict quality gates and checkpoints
|
||||
- `fast`: reduced gates for speed
|
||||
|
||||
## Flow
|
||||
|
||||
1. Confirm repository state and branch strategy.
|
||||
2. Select loop pattern and model tier strategy.
|
||||
3. Enable required hooks/profile for the chosen mode.
|
||||
4. Create loop plan and write runbook under `.claude/plans/`.
|
||||
5. Print commands to start and monitor the loop.
|
||||
|
||||
## Required Safety Checks
|
||||
|
||||
- Verify tests pass before first loop iteration.
|
||||
- Ensure `ECC_HOOK_PROFILE` is not disabled globally.
|
||||
- Ensure loop has explicit stop condition.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `<pattern>` optional (`sequential|continuous-pr|rfc-dag|infinite`)
|
||||
- `--mode safe|fast` optional
|
||||
24
commands/loop-status.md
Normal file
24
commands/loop-status.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Loop Status Command
|
||||
|
||||
Inspect active loop state, progress, and failure signals.
|
||||
|
||||
## Usage
|
||||
|
||||
`/loop-status [--watch]`
|
||||
|
||||
## What to Report
|
||||
|
||||
- active loop pattern
|
||||
- current phase and last successful checkpoint
|
||||
- failing checks (if any)
|
||||
- estimated time/cost drift
|
||||
- recommended intervention (continue/pause/stop)
|
||||
|
||||
## Watch Mode
|
||||
|
||||
When `--watch` is present, refresh status periodically and surface state changes.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `--watch` optional
|
||||
26
commands/model-route.md
Normal file
26
commands/model-route.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Model Route Command
|
||||
|
||||
Recommend the best model tier for the current task by complexity and budget.
|
||||
|
||||
## Usage
|
||||
|
||||
`/model-route [task-description] [--budget low|med|high]`
|
||||
|
||||
## Routing Heuristic
|
||||
|
||||
- `haiku`: deterministic, low-risk mechanical changes
|
||||
- `sonnet`: default for implementation and refactors
|
||||
- `opus`: architecture, deep review, ambiguous requirements
|
||||
|
||||
## Required Output
|
||||
|
||||
- recommended model
|
||||
- confidence level
|
||||
- why this model fits
|
||||
- fallback model if first attempt fails
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `[task-description]` optional free-text
|
||||
- `--budget low|med|high` optional
|
||||
@@ -85,13 +85,13 @@ EOF",
|
||||
|
||||
### Phase 0: Prompt Enhancement (Optional)
|
||||
|
||||
`[Mode: Prepare]` - If ace-tool MCP available, call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for subsequent Codex calls**
|
||||
`[Mode: Prepare]` - If ace-tool MCP available, call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for subsequent Codex calls**. If unavailable, use `$ARGUMENTS` as-is.
|
||||
|
||||
### Phase 1: Research
|
||||
|
||||
`[Mode: Research]` - Understand requirements and gather context
|
||||
|
||||
1. **Code Retrieval** (if ace-tool MCP available): Call `mcp__ace-tool__search_context` to retrieve existing APIs, data models, service architecture
|
||||
1. **Code Retrieval** (if ace-tool MCP available): Call `mcp__ace-tool__search_context` to retrieve existing APIs, data models, service architecture. If unavailable, use built-in tools: `Glob` for file discovery, `Grep` for symbol/API search, `Read` for context gathering, `Task` (Explore agent) for deeper exploration.
|
||||
2. Requirement completeness score (0-10): >=7 continue, <7 stop and supplement
|
||||
|
||||
### Phase 2: Ideation
|
||||
|
||||
@@ -136,7 +136,7 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Retrieval]`
|
||||
|
||||
**Must use MCP tool for quick context retrieval, do NOT manually read files one by one**
|
||||
**If ace-tool MCP is available**, use it for quick context retrieval:
|
||||
|
||||
Based on "Key Files" list in plan, call `mcp__ace-tool__search_context`:
|
||||
|
||||
@@ -151,7 +151,12 @@ mcp__ace-tool__search_context({
|
||||
- Extract target paths from plan's "Key Files" table
|
||||
- Build semantic query covering: entry files, dependency modules, related type definitions
|
||||
- If results insufficient, add 1-2 recursive retrievals
|
||||
- **NEVER** use Bash + find/ls to manually explore project structure
|
||||
|
||||
**If ace-tool MCP is NOT available**, use Claude Code built-in tools as fallback:
|
||||
1. **Glob**: Find target files from plan's "Key Files" table (e.g., `Glob("src/components/**/*.tsx")`)
|
||||
2. **Grep**: Search for key symbols, function names, type definitions across the codebase
|
||||
3. **Read**: Read the discovered files to gather complete context
|
||||
4. **Task (Explore agent)**: For broader exploration, use `Task` with `subagent_type: "Explore"`
|
||||
|
||||
**After Retrieval**:
|
||||
- Organize retrieved code snippets
|
||||
|
||||
@@ -85,13 +85,13 @@ EOF",
|
||||
|
||||
### Phase 0: Prompt Enhancement (Optional)
|
||||
|
||||
`[Mode: Prepare]` - If ace-tool MCP available, call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for subsequent Gemini calls**
|
||||
`[Mode: Prepare]` - If ace-tool MCP available, call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for subsequent Gemini calls**. If unavailable, use `$ARGUMENTS` as-is.
|
||||
|
||||
### Phase 1: Research
|
||||
|
||||
`[Mode: Research]` - Understand requirements and gather context
|
||||
|
||||
1. **Code Retrieval** (if ace-tool MCP available): Call `mcp__ace-tool__search_context` to retrieve existing components, styles, design system
|
||||
1. **Code Retrieval** (if ace-tool MCP available): Call `mcp__ace-tool__search_context` to retrieve existing components, styles, design system. If unavailable, use built-in tools: `Glob` for file discovery, `Grep` for component/style search, `Read` for context gathering, `Task` (Explore agent) for deeper exploration.
|
||||
2. Requirement completeness score (0-10): >=7 continue, <7 stop and supplement
|
||||
|
||||
### Phase 2: Ideation
|
||||
|
||||
@@ -71,7 +71,7 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
#### 1.1 Prompt Enhancement (MUST execute first)
|
||||
|
||||
**MUST call `mcp__ace-tool__enhance_prompt` tool**:
|
||||
**If ace-tool MCP is available**, call `mcp__ace-tool__enhance_prompt` tool:
|
||||
|
||||
```
|
||||
mcp__ace-tool__enhance_prompt({
|
||||
@@ -83,9 +83,11 @@ mcp__ace-tool__enhance_prompt({
|
||||
|
||||
Wait for enhanced prompt, **replace original $ARGUMENTS with enhanced result** for all subsequent phases.
|
||||
|
||||
**If ace-tool MCP is NOT available**: Skip this step and use the original `$ARGUMENTS` as-is for all subsequent phases.
|
||||
|
||||
#### 1.2 Context Retrieval
|
||||
|
||||
**Call `mcp__ace-tool__search_context` tool**:
|
||||
**If ace-tool MCP is available**, call `mcp__ace-tool__search_context` tool:
|
||||
|
||||
```
|
||||
mcp__ace-tool__search_context({
|
||||
@@ -96,7 +98,12 @@ mcp__ace-tool__search_context({
|
||||
|
||||
- Build semantic query using natural language (Where/What/How)
|
||||
- **NEVER answer based on assumptions**
|
||||
- If MCP unavailable: fallback to Glob + Grep for file discovery and key symbol location
|
||||
|
||||
**If ace-tool MCP is NOT available**, use Claude Code built-in tools as fallback:
|
||||
1. **Glob**: Find relevant files by pattern (e.g., `Glob("**/*.ts")`, `Glob("src/**/*.py")`)
|
||||
2. **Grep**: Search for key symbols, function names, class definitions (e.g., `Grep("className|functionName")`)
|
||||
3. **Read**: Read the discovered files to gather complete context
|
||||
4. **Task (Explore agent)**: For deeper exploration, use `Task` with `subagent_type: "Explore"` to search across the codebase
|
||||
|
||||
#### 1.3 Completeness Check
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ Structured development workflow with quality gates, MCP services, and multi-mode
|
||||
- Task to develop: $ARGUMENTS
|
||||
- Structured 6-phase workflow with quality gates
|
||||
- Multi-model collaboration: Codex (backend) + Gemini (frontend) + Claude (orchestration)
|
||||
- MCP service integration (ace-tool) for enhanced capabilities
|
||||
- MCP service integration (ace-tool, optional) for enhanced capabilities
|
||||
|
||||
## Your Role
|
||||
|
||||
You are the **Orchestrator**, coordinating a multi-model collaborative system (Research → Ideation → Plan → Execute → Optimize → Review). Communicate concisely and professionally for experienced developers.
|
||||
|
||||
**Collaborative Models**:
|
||||
- **ace-tool MCP** – Code retrieval + Prompt enhancement
|
||||
- **ace-tool MCP** (optional) – Code retrieval + Prompt enhancement
|
||||
- **Codex** – Backend logic, algorithms, debugging (**Backend authority, trustworthy**)
|
||||
- **Gemini** – Frontend UI/UX, visual design (**Frontend expert, backend opinions for reference only**)
|
||||
- **Claude (self)** – Orchestration, planning, execution, delivery
|
||||
@@ -111,8 +111,8 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Research]` - Understand requirements and gather context:
|
||||
|
||||
1. **Prompt Enhancement**: Call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for all subsequent Codex/Gemini calls**
|
||||
2. **Context Retrieval**: Call `mcp__ace-tool__search_context`
|
||||
1. **Prompt Enhancement** (if ace-tool MCP available): Call `mcp__ace-tool__enhance_prompt`, **replace original $ARGUMENTS with enhanced result for all subsequent Codex/Gemini calls**. If unavailable, use `$ARGUMENTS` as-is.
|
||||
2. **Context Retrieval** (if ace-tool MCP available): Call `mcp__ace-tool__search_context`. If unavailable, use built-in tools: `Glob` for file discovery, `Grep` for symbol search, `Read` for context gathering, `Task` (Explore agent) for deeper exploration.
|
||||
3. **Requirement Completeness Score** (0-10):
|
||||
- Goal clarity (0-3), Expected outcome (0-3), Scope boundaries (0-2), Constraints (0-2)
|
||||
- ≥7: Continue | <7: Stop, ask clarifying questions
|
||||
|
||||
29
commands/quality-gate.md
Normal file
29
commands/quality-gate.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Quality Gate Command
|
||||
|
||||
Run the ECC quality pipeline on demand for a file or project scope.
|
||||
|
||||
## Usage
|
||||
|
||||
`/quality-gate [path|.] [--fix] [--strict]`
|
||||
|
||||
- default target: current directory (`.`)
|
||||
- `--fix`: allow auto-format/fix where configured
|
||||
- `--strict`: fail on warnings where supported
|
||||
|
||||
## Pipeline
|
||||
|
||||
1. Detect language/tooling for target.
|
||||
2. Run formatter checks.
|
||||
3. Run lint/type checks when available.
|
||||
4. Produce a concise remediation list.
|
||||
|
||||
## Notes
|
||||
|
||||
This command mirrors hook behavior but is operator-invoked.
|
||||
|
||||
## Arguments
|
||||
|
||||
$ARGUMENTS:
|
||||
- `[path|.]` optional target path
|
||||
- `--fix` optional
|
||||
- `--strict` optional
|
||||
74
docs/business/metrics-and-sponsorship.md
Normal file
74
docs/business/metrics-and-sponsorship.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Metrics and Sponsorship Playbook
|
||||
|
||||
This file is a practical script for sponsor calls and ecosystem partner reviews.
|
||||
|
||||
## What to Track
|
||||
|
||||
Use four categories in every update:
|
||||
|
||||
1. **Distribution** — npm packages and GitHub App installs
|
||||
2. **Adoption** — stars, forks, contributors, release cadence
|
||||
3. **Product surface** — commands/skills/agents and cross-platform support
|
||||
4. **Reliability** — test pass counts and production bug turnaround
|
||||
|
||||
## Pull Live Metrics
|
||||
|
||||
### npm downloads
|
||||
|
||||
```bash
|
||||
# Weekly downloads
|
||||
curl -s https://api.npmjs.org/downloads/point/last-week/ecc-universal
|
||||
curl -s https://api.npmjs.org/downloads/point/last-week/ecc-agentshield
|
||||
|
||||
# Last 30 days
|
||||
curl -s https://api.npmjs.org/downloads/point/last-month/ecc-universal
|
||||
curl -s https://api.npmjs.org/downloads/point/last-month/ecc-agentshield
|
||||
```
|
||||
|
||||
### GitHub repository adoption
|
||||
|
||||
```bash
|
||||
gh api repos/affaan-m/everything-claude-code \
|
||||
--jq '{stars:.stargazers_count,forks:.forks_count,contributors_url:.contributors_url,open_issues:.open_issues_count}'
|
||||
```
|
||||
|
||||
### GitHub traffic (maintainer access required)
|
||||
|
||||
```bash
|
||||
gh api repos/affaan-m/everything-claude-code/traffic/views
|
||||
gh api repos/affaan-m/everything-claude-code/traffic/clones
|
||||
```
|
||||
|
||||
### GitHub App installs
|
||||
|
||||
GitHub App install count is currently most reliable in the Marketplace/App dashboard.
|
||||
Use the latest value from:
|
||||
|
||||
- [ECC Tools Marketplace](https://github.com/marketplace/ecc-tools)
|
||||
|
||||
## What Cannot Be Measured Publicly (Yet)
|
||||
|
||||
- Claude plugin install/download counts are not currently exposed via a public API.
|
||||
- For partner conversations, use npm metrics + GitHub App installs + repo traffic as the proxy bundle.
|
||||
|
||||
## Suggested Sponsor Packaging
|
||||
|
||||
Use these as starting points in negotiation:
|
||||
|
||||
- **Pilot Partner:** `$200/month`
|
||||
- Best for first partnership validation and simple monthly sponsor updates.
|
||||
- **Growth Partner:** `$500/month`
|
||||
- Includes roadmap check-ins and implementation feedback loop.
|
||||
- **Strategic Partner:** `$1,000+/month`
|
||||
- Multi-touch collaboration, launch support, and deeper operational alignment.
|
||||
|
||||
## 60-Second Talking Track
|
||||
|
||||
Use this on calls:
|
||||
|
||||
> ECC is now positioned as an agent harness performance system, not a config repo.
|
||||
> We track adoption through npm distribution, GitHub App installs, and repository growth.
|
||||
> Claude plugin installs are structurally undercounted publicly, so we use a blended metrics model.
|
||||
> The project supports Claude Code, Cursor, OpenCode, and Codex app/CLI with production-grade hook reliability and a large passing test suite.
|
||||
|
||||
For launch-ready social copy snippets, see [`social-launch-copy.md`](./social-launch-copy.md).
|
||||
62
docs/business/social-launch-copy.md
Normal file
62
docs/business/social-launch-copy.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Social Launch Copy (X + LinkedIn)
|
||||
|
||||
Use these templates as launch-ready starting points. Replace placeholders before posting.
|
||||
|
||||
## X Post: Release Announcement
|
||||
|
||||
```text
|
||||
ECC v1.8.0 is live.
|
||||
|
||||
We moved from “config pack” to an agent harness performance system:
|
||||
- hook reliability fixes
|
||||
- new harness commands
|
||||
- cross-tool parity (Claude Code, Cursor, OpenCode, Codex)
|
||||
|
||||
Start here: <repo-link>
|
||||
```
|
||||
|
||||
## X Post: Proof + Metrics
|
||||
|
||||
```text
|
||||
If you evaluate agent tooling, use blended distribution metrics:
|
||||
- npm installs (`ecc-universal`, `ecc-agentshield`)
|
||||
- GitHub App installs
|
||||
- repo adoption (stars/forks/contributors)
|
||||
|
||||
We now track this monthly in-repo for sponsor transparency.
|
||||
```
|
||||
|
||||
## X Quote Tweet: Eval Skills Article
|
||||
|
||||
```text
|
||||
Strong point on eval discipline.
|
||||
|
||||
In ECC we turned this into production checks via:
|
||||
- /harness-audit
|
||||
- /quality-gate
|
||||
- Stop-phase session summaries
|
||||
|
||||
This is where harness performance compounds over time.
|
||||
```
|
||||
|
||||
## X Quote Tweet: Plankton / deslop workflow
|
||||
|
||||
```text
|
||||
This workflow direction is right: optimize the harness, not just prompts.
|
||||
|
||||
Our v1.8.0 focus was reliability + parity + measurable quality gates across toolchains.
|
||||
```
|
||||
|
||||
## LinkedIn Post: Partner-Friendly Summary
|
||||
|
||||
```text
|
||||
We shipped ECC v1.8.0 with one objective: improve agent harness performance in production.
|
||||
|
||||
Highlights:
|
||||
- more reliable hook lifecycle behavior
|
||||
- new harness-level quality commands
|
||||
- parity across Claude Code, Cursor, OpenCode, and Codex
|
||||
- stronger sponsor-facing metrics tracking
|
||||
|
||||
If your team runs AI coding agents daily, this is designed for operational use.
|
||||
```
|
||||
14
docs/continuous-learning-v2-spec.md
Normal file
14
docs/continuous-learning-v2-spec.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Continuous Learning v2 Spec
|
||||
|
||||
This document captures the v2 continuous-learning architecture:
|
||||
|
||||
1. Hook-based observation capture
|
||||
2. Background observer analysis loop
|
||||
3. Instinct scoring and persistence
|
||||
4. Evolution of instincts into reusable skills/commands
|
||||
|
||||
Primary implementation lives in:
|
||||
- `skills/continuous-learning-v2/`
|
||||
- `scripts/hooks/`
|
||||
|
||||
Use this file as the stable reference path for docs and translations.
|
||||
@@ -7,7 +7,7 @@ model: opus
|
||||
|
||||
# データベースレビューアー
|
||||
|
||||
あなたはクエリ最適化、スキーマ設計、セキュリティ、パフォーマンスに焦点を当てたエキスパートPostgreSQLデータベーススペシャリストです。あなたのミッションは、データベースコードがベストプラクティスに従い、パフォーマンス問題を防ぎ、データ整合性を維持することを確実にすることです。このエージェントは[SupabaseのPostgreSQLベストプラクティス](https://github.com/supabase/agent-skills)からのパターンを組み込んでいます。
|
||||
あなたはクエリ最適化、スキーマ設計、セキュリティ、パフォーマンスに焦点を当てたエキスパートPostgreSQLデータベーススペシャリストです。あなたのミッションは、データベースコードがベストプラクティスに従い、パフォーマンス問題を防ぎ、データ整合性を維持することを確実にすることです。このエージェントは[SupabaseのPostgreSQLベストプラクティス](Supabase Agent Skills (credit: Supabase team))からのパターンを組み込んでいます。
|
||||
|
||||
## 主な責務
|
||||
|
||||
@@ -651,4 +651,4 @@ ORDER BY rank DESC;
|
||||
|
||||
**覚えておくこと**: データベースの問題は、アプリケーションパフォーマンス問題の根本原因であることが多いです。クエリとスキーマ設計を早期に最適化してください。仮定を検証するためにEXPLAIN ANALYZEを使用してください。常に外部キーとRLSポリシー列にインデックスを作成してください。
|
||||
|
||||
*パターンはMITライセンスの下で[Supabase Agent Skills](https://github.com/supabase/agent-skills)から適応されています。*
|
||||
*パターンはMITライセンスの下で[Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team))から適応されています。*
|
||||
|
||||
@@ -85,13 +85,13 @@ EOF",
|
||||
|
||||
### フェーズ 0: プロンプト強化(オプション)
|
||||
|
||||
`[Mode: Prepare]` - ace-tool MCPが利用可能な場合、`mcp__ace-tool__enhance_prompt`を呼び出し、**後続のCodex呼び出しのために元の$ARGUMENTSを強化結果で置き換える**
|
||||
`[Mode: Prepare]` - ace-tool MCPが利用可能な場合、`mcp__ace-tool__enhance_prompt`を呼び出し、**後続のCodex呼び出しのために元の$ARGUMENTSを強化結果で置き換える**。利用できない場合は`$ARGUMENTS`をそのまま使用。
|
||||
|
||||
### フェーズ 1: 調査
|
||||
|
||||
`[Mode: Research]` - 要件の理解とコンテキストの収集
|
||||
|
||||
1. **コード取得**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__search_context`を呼び出して既存のAPI、データモデル、サービスアーキテクチャを取得
|
||||
1. **コード取得**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__search_context`を呼び出して既存のAPI、データモデル、サービスアーキテクチャを取得。利用できない場合は組み込みツールを使用: `Glob`でファイル検索、`Grep`でシンボル/API検索、`Read`でコンテキスト収集、`Task`(Exploreエージェント)でより深い探索。
|
||||
2. 要件の完全性スコア(0-10): >=7で継続、<7で停止して補足
|
||||
|
||||
### フェーズ 2: アイデア創出
|
||||
|
||||
@@ -136,7 +136,7 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Retrieval]`
|
||||
|
||||
**MCPツールを使用したクイックコンテキスト取得が必須です。ファイルを1つずつ手動で読まないでください**
|
||||
**ace-tool MCPが利用可能な場合**、クイックコンテキスト取得に使用:
|
||||
|
||||
計画の「キーファイル」リストに基づいて、`mcp__ace-tool__search_context`を呼び出します:
|
||||
|
||||
@@ -151,7 +151,12 @@ mcp__ace-tool__search_context({
|
||||
- 計画の「キーファイル」テーブルから対象パスを抽出
|
||||
- カバー範囲のセマンティッククエリを構築: エントリファイル、依存モジュール、関連する型定義
|
||||
- 結果が不十分な場合、1-2回の再帰的取得を追加
|
||||
- **決して**Bash + find/lsを使用してプロジェクト構造を手動で探索しない
|
||||
|
||||
**ace-tool MCPが利用できない場合**、Claude Code組み込みツールでフォールバック:
|
||||
1. **Glob**: 計画の「キーファイル」テーブルから対象ファイルを検索 (例: `Glob("src/components/**/*.tsx")`)
|
||||
2. **Grep**: キーシンボル、関数名、型定義をコードベース全体で検索
|
||||
3. **Read**: 発見したファイルを読み取り、完全なコンテキストを収集
|
||||
4. **Task (Explore エージェント)**: より広範な探索が必要な場合、`Task` を `subagent_type: "Explore"` で使用
|
||||
|
||||
**取得後**:
|
||||
- 取得したコードスニペットを整理
|
||||
|
||||
@@ -85,13 +85,13 @@ EOF",
|
||||
|
||||
### フェーズ 0: プロンプト強化(オプション)
|
||||
|
||||
`[Mode: Prepare]` - ace-tool MCPが利用可能な場合、`mcp__ace-tool__enhance_prompt`を呼び出し、**後続のGemini呼び出しのために元の$ARGUMENTSを強化結果で置き換える**
|
||||
`[Mode: Prepare]` - ace-tool MCPが利用可能な場合、`mcp__ace-tool__enhance_prompt`を呼び出し、**後続のGemini呼び出しのために元の$ARGUMENTSを強化結果で置き換える**。利用できない場合は`$ARGUMENTS`をそのまま使用。
|
||||
|
||||
### フェーズ 1: 調査
|
||||
|
||||
`[Mode: Research]` - 要件の理解とコンテキストの収集
|
||||
|
||||
1. **コード取得**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__search_context`を呼び出して既存のコンポーネント、スタイル、デザインシステムを取得
|
||||
1. **コード取得**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__search_context`を呼び出して既存のコンポーネント、スタイル、デザインシステムを取得。利用できない場合は組み込みツールを使用: `Glob`でファイル検索、`Grep`でコンポーネント/スタイル検索、`Read`でコンテキスト収集、`Task`(Exploreエージェント)でより深い探索。
|
||||
2. 要件の完全性スコア(0-10): >=7で継続、<7で停止して補足
|
||||
|
||||
### フェーズ 2: アイデア創出
|
||||
|
||||
@@ -71,7 +71,7 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
#### 1.1 プロンプト強化(最初に実行する必要があります)
|
||||
|
||||
**`mcp__ace-tool__enhance_prompt`ツールを呼び出す必要があります**:
|
||||
**ace-tool MCPが利用可能な場合**、`mcp__ace-tool__enhance_prompt`ツールを呼び出す:
|
||||
|
||||
```
|
||||
mcp__ace-tool__enhance_prompt({
|
||||
@@ -83,9 +83,11 @@ mcp__ace-tool__enhance_prompt({
|
||||
|
||||
強化されたプロンプトを待ち、**後続のすべてのフェーズのために元の$ARGUMENTSを強化結果で置き換える**。
|
||||
|
||||
**ace-tool MCPが利用できない場合**: このステップをスキップし、後続のすべてのフェーズで元の`$ARGUMENTS`をそのまま使用する。
|
||||
|
||||
#### 1.2 コンテキスト取得
|
||||
|
||||
**`mcp__ace-tool__search_context`ツールを呼び出す**:
|
||||
**ace-tool MCPが利用可能な場合**、`mcp__ace-tool__search_context`ツールを呼び出す:
|
||||
|
||||
```
|
||||
mcp__ace-tool__search_context({
|
||||
@@ -96,7 +98,12 @@ mcp__ace-tool__search_context({
|
||||
|
||||
- 自然言語を使用してセマンティッククエリを構築(Where/What/How)
|
||||
- **仮定に基づいて回答しない**
|
||||
- MCPが利用できない場合: Glob + Grepにフォールバックしてファイル検出とキーシンボル位置を特定
|
||||
|
||||
**ace-tool MCPが利用できない場合**、Claude Code組み込みツールでフォールバック:
|
||||
1. **Glob**: パターンで関連ファイルを検索 (例: `Glob("**/*.ts")`, `Glob("src/**/*.py")`)
|
||||
2. **Grep**: キーシンボル、関数名、クラス定義を検索 (例: `Grep("className|functionName")`)
|
||||
3. **Read**: 発見したファイルを読み取り、完全なコンテキストを収集
|
||||
4. **Task (Explore エージェント)**: より深い探索が必要な場合、`Task` を `subagent_type: "Explore"` で使用
|
||||
|
||||
#### 1.3 完全性チェック
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
- 開発するタスク: $ARGUMENTS
|
||||
- 品質ゲートを備えた構造化された6フェーズワークフロー
|
||||
- マルチモデル連携: Codex(バックエンド) + Gemini(フロントエンド) + Claude(オーケストレーション)
|
||||
- MCPサービス統合(ace-tool)による機能強化
|
||||
- MCPサービス統合(ace-tool、オプション)による機能強化
|
||||
|
||||
## 役割
|
||||
|
||||
あなたは**オーケストレーター**として、マルチモデル協調システムを調整します(調査 → アイデア創出 → 計画 → 実装 → 最適化 → レビュー)。経験豊富な開発者向けに簡潔かつ専門的にコミュニケーションします。
|
||||
|
||||
**連携モデル**:
|
||||
- **ace-tool MCP** – コード取得 + プロンプト強化
|
||||
- **ace-tool MCP**(オプション) – コード取得 + プロンプト強化
|
||||
- **Codex** – バックエンドロジック、アルゴリズム、デバッグ(**バックエンドの権威、信頼できる**)
|
||||
- **Gemini** – フロントエンドUI/UX、ビジュアルデザイン(**フロントエンドエキスパート、バックエンドの意見は参考のみ**)
|
||||
- **Claude(自身)** – オーケストレーション、計画、実装、配信
|
||||
@@ -111,8 +111,8 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Research]` - 要件の理解とコンテキストの収集:
|
||||
|
||||
1. **プロンプト強化**: `mcp__ace-tool__enhance_prompt`を呼び出し、**後続のすべてのCodex/Gemini呼び出しのために元の$ARGUMENTSを強化結果で置き換える**
|
||||
2. **コンテキスト取得**: `mcp__ace-tool__search_context`を呼び出す
|
||||
1. **プロンプト強化**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__enhance_prompt`を呼び出し、**後続のすべてのCodex/Gemini呼び出しのために元の$ARGUMENTSを強化結果で置き換える**。利用できない場合は`$ARGUMENTS`をそのまま使用。
|
||||
2. **コンテキスト取得**(ace-tool MCPが利用可能な場合): `mcp__ace-tool__search_context`を呼び出す。利用できない場合は組み込みツールを使用: `Glob`でファイル検索、`Grep`でシンボル検索、`Read`でコンテキスト収集、`Task`(Exploreエージェント)でより深い探索。
|
||||
3. **要件完全性スコア**(0-10):
|
||||
- 目標の明確性(0-3)、期待される結果(0-3)、スコープの境界(0-2)、制約(0-2)
|
||||
- ≥7: 継続 | <7: 停止、明確化の質問を尋ねる
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
claude plugin marketplace add https://github.com/anthropics/claude-plugins-official
|
||||
|
||||
# コミュニティマーケットプレイスを追加
|
||||
claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
# mgrep plugin by @mixedbread-ai
|
||||
claud plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
```
|
||||
|
||||
### 推奨マーケットプレイス
|
||||
@@ -67,7 +68,8 @@ claude plugin install typescript-lsp@claude-plugins-official
|
||||
```bash
|
||||
# マーケットプレイスを追加
|
||||
claude plugin marketplace add https://github.com/anthropics/claude-plugins-official
|
||||
claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
# mgrep plugin by @mixedbread-ai
|
||||
claud plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
|
||||
# /pluginsを開き、必要なものをインストール
|
||||
```
|
||||
|
||||
@@ -107,4 +107,4 @@ Homunculus v2はより洗練されたアプローチを採用:
|
||||
4. **ドメインタグ付け** - コードスタイル、テスト、git、デバッグなど
|
||||
5. **進化パス** - 関連する本能をスキル/コマンドにクラスタ化
|
||||
|
||||
詳細: `/Users/affoon/Documents/tasks/12-continuous-learning-v2.md`を参照。
|
||||
詳細: `docs/continuous-learning-v2-spec.md`を参照。
|
||||
|
||||
@@ -160,7 +160,7 @@ include(FetchContent)
|
||||
set(GTEST_VERSION v1.17.0) # プロジェクトポリシーに合わせて調整します。
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip
|
||||
URL Google Test framework (official repository) https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
|
||||
@@ -143,4 +143,4 @@ SELECT pg_reload_conf();
|
||||
|
||||
---
|
||||
|
||||
*[Supabase Agent Skills](https://github.com/supabase/agent-skills)(MITライセンス)に基づく*
|
||||
*[Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team))(MITライセンス)に基づく*
|
||||
|
||||
13
docs/releases/1.8.0/linkedin-post.md
Normal file
13
docs/releases/1.8.0/linkedin-post.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# LinkedIn Draft - ECC v1.8.0
|
||||
|
||||
ECC v1.8.0 is now focused on harness performance at the system level.
|
||||
|
||||
This release improves:
|
||||
- hook reliability and lifecycle behavior
|
||||
- eval-driven engineering workflows
|
||||
- operator tooling for autonomous loops
|
||||
- cross-platform support for Claude Code, Cursor, OpenCode, and Codex
|
||||
|
||||
We also shipped NanoClaw v2 with stronger session operations for real workflow usage.
|
||||
|
||||
If your AI coding workflow feels inconsistent, start by treating the harness as a first-class engineering system.
|
||||
16
docs/releases/1.8.0/reference-attribution.md
Normal file
16
docs/releases/1.8.0/reference-attribution.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Reference Attribution and Licensing Notes
|
||||
|
||||
ECC v1.8.0 references research and workflow inspiration from:
|
||||
|
||||
- `plankton`
|
||||
- `ralphinho`
|
||||
- `infinite-agentic-loop`
|
||||
- `continuous-claude`
|
||||
- public profiles: [zarazhangrui](https://github.com/zarazhangrui), [humanplane](https://github.com/humanplane)
|
||||
|
||||
## Policy
|
||||
|
||||
1. No direct code copying from unlicensed or incompatible sources.
|
||||
2. ECC implementations are re-authored for this repository’s architecture and licensing model.
|
||||
3. Referenced material is used for ideas, patterns, and conceptual framing only unless licensing explicitly permits reuse.
|
||||
4. Any future direct reuse requires explicit license verification and source attribution in-file and in release notes.
|
||||
20
docs/releases/1.8.0/release-notes.md
Normal file
20
docs/releases/1.8.0/release-notes.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# ECC v1.8.0 Release Notes
|
||||
|
||||
## Positioning
|
||||
|
||||
ECC v1.8.0 positions the project as an agent harness performance system, not just a config bundle.
|
||||
|
||||
## Key Improvements
|
||||
|
||||
- Stabilized hooks and lifecycle behavior.
|
||||
- Expanded eval and loop operations surface.
|
||||
- Upgraded NanoClaw for operational use.
|
||||
- Improved cross-harness parity (Claude Code, Cursor, OpenCode, Codex).
|
||||
|
||||
## Upgrade Focus
|
||||
|
||||
1. Validate hook profile defaults in your environment.
|
||||
2. Run `/harness-audit` to baseline your project.
|
||||
3. Use `/quality-gate` and updated eval workflows to enforce consistency.
|
||||
4. Review attribution and licensing notes for referenced ecosystems: [reference-attribution.md](./reference-attribution.md).
|
||||
5. For partner/sponsor optics, use live distribution metrics and talking points: [../business/metrics-and-sponsorship.md](../../business/metrics-and-sponsorship.md).
|
||||
5
docs/releases/1.8.0/x-quote-eval-skills.md
Normal file
5
docs/releases/1.8.0/x-quote-eval-skills.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# X Quote Draft - Eval Skills Post
|
||||
|
||||
Strong eval skills are now built deeper into ECC.
|
||||
|
||||
v1.8.0 expands eval-harness patterns, pass@k guidance, and release-level verification loops so teams can measure reliability, not guess it.
|
||||
5
docs/releases/1.8.0/x-quote-plankton-deslop.md
Normal file
5
docs/releases/1.8.0/x-quote-plankton-deslop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# X Quote Draft - Plankton / De-slop Workflow
|
||||
|
||||
The quality gate model matters.
|
||||
|
||||
In v1.8.0 we pushed harder on write-time quality enforcement, deterministic checks, and cleaner loop recovery so agents converge faster with less noise.
|
||||
11
docs/releases/1.8.0/x-thread.md
Normal file
11
docs/releases/1.8.0/x-thread.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# X Thread Draft - ECC v1.8.0
|
||||
|
||||
1/ ECC v1.8.0 is live. This release is about one thing: better agent harness performance.
|
||||
|
||||
2/ We shipped hook reliability fixes, loop operations commands, and stronger eval workflows.
|
||||
|
||||
3/ NanoClaw v2 now supports model routing, skill hot-load, branching, search, compaction, export, and metrics.
|
||||
|
||||
4/ If your agents are underperforming, start with `/harness-audit` and tighten quality gates.
|
||||
|
||||
5/ Cross-harness parity remains a priority: Claude Code, Cursor, OpenCode, Codex.
|
||||
@@ -150,6 +150,6 @@ claude /schedule-reply "Reply to Sarah about the board meeting"
|
||||
## 先决条件
|
||||
|
||||
* [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
||||
* Gmail CLI (例如 [gog](https://github.com/pterm/gog))
|
||||
* Gmail CLI (例如 [gog](https://gog by @pterm))
|
||||
* Node.js 18+ (用于 calendar-suggest.js)
|
||||
* 可选:Slack MCP 服务器、Matrix 桥接 (LINE)、Chrome + Playwright (Messenger)
|
||||
|
||||
@@ -7,7 +7,7 @@ model: sonnet
|
||||
|
||||
# 数据库审查员
|
||||
|
||||
您是一位专注于查询优化、模式设计、安全性和性能的 PostgreSQL 数据库专家。您的任务是确保数据库代码遵循最佳实践、防止性能问题并保持数据完整性。融合了 [Supabase 的 postgres-best-practices](https://github.com/supabase/agent-skills) 中的模式。
|
||||
您是一位专注于查询优化、模式设计、安全性和性能的 PostgreSQL 数据库专家。您的任务是确保数据库代码遵循最佳实践、防止性能问题并保持数据完整性。融合了 [Supabase 的 postgres-best-practices](Supabase Agent Skills (credit: Supabase team)) 中的模式。
|
||||
|
||||
## 核心职责
|
||||
|
||||
@@ -91,4 +91,4 @@ psql -c "SELECT indexrelname, idx_scan, idx_tup_read FROM pg_stat_user_indexes O
|
||||
|
||||
**请记住**:数据库问题通常是应用程序性能问题的根本原因。尽早优化查询和模式设计。使用 EXPLAIN ANALYZE 来验证假设。始终对外键和 RLS 策略列建立索引。
|
||||
|
||||
*模式改编自 [Supabase Agent Skills](https://github.com/supabase/agent-skills),遵循 MIT 许可证。*
|
||||
*模式改编自 [Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team)),遵循 MIT 许可证。*
|
||||
|
||||
@@ -86,13 +86,13 @@ EOF",
|
||||
|
||||
### 阶段 0:提示词增强(可选)
|
||||
|
||||
`[Mode: Prepare]` - 如果 ace-tool MCP 可用,调用 `mcp__ace-tool__enhance_prompt`,**用增强后的结果替换原始的 $ARGUMENTS,用于后续的 Codex 调用**
|
||||
`[Mode: Prepare]` - 如果 ace-tool MCP 可用,调用 `mcp__ace-tool__enhance_prompt`,**用增强后的结果替换原始的 $ARGUMENTS,用于后续的 Codex 调用**。不可用时,直接使用 `$ARGUMENTS`。
|
||||
|
||||
### 阶段 1:研究
|
||||
|
||||
`[Mode: Research]` - 理解需求并收集上下文
|
||||
|
||||
1. **代码检索**(如果 ace-tool MCP 可用):调用 `mcp__ace-tool__search_context` 以检索现有的 API、数据模型、服务架构
|
||||
1. **代码检索**(如果 ace-tool MCP 可用):调用 `mcp__ace-tool__search_context` 以检索现有的 API、数据模型、服务架构。不可用时,使用内置工具:`Glob` 进行文件发现,`Grep` 进行符号/API 搜索,`Read` 进行上下文收集,`Task`(Explore 代理)进行更深入的探索。
|
||||
2. 需求完整性评分(0-10):>=7 继续,<7 停止并补充
|
||||
|
||||
### 阶段 2:构思
|
||||
|
||||
@@ -138,23 +138,28 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Retrieval]`
|
||||
|
||||
**必须使用 MCP 工具进行快速上下文检索,切勿手动逐个读取文件**
|
||||
**如果 ace-tool MCP 可用**,使用它进行快速上下文检索:
|
||||
|
||||
基于计划中的“关键文件”列表,调用 `mcp__ace-tool__search_context`:
|
||||
基于计划中的”关键文件”列表,调用 `mcp__ace-tool__search_context`:
|
||||
|
||||
```
|
||||
mcp__ace-tool__search_context({
|
||||
query: "<semantic query based on plan content, including key files, modules, function names>",
|
||||
project_root_path: "$PWD"
|
||||
query: “<semantic query based on plan content, including key files, modules, function names>”,
|
||||
project_root_path: “$PWD”
|
||||
})
|
||||
```
|
||||
|
||||
**检索策略**:
|
||||
|
||||
* 从计划的“关键文件”表中提取目标路径
|
||||
* 从计划的”关键文件”表中提取目标路径
|
||||
* 构建语义查询覆盖:入口文件、依赖模块、相关类型定义
|
||||
* 如果结果不足,添加 1-2 次递归检索
|
||||
* **切勿**使用 Bash + find/ls 手动探索项目结构
|
||||
|
||||
**如果 ace-tool MCP 不可用**,使用 Claude Code 内置工具作为回退:
|
||||
1. **Glob**:从计划的”关键文件”表中查找目标文件(例如 `Glob(“src/components/**/*.tsx”)`)
|
||||
2. **Grep**:在代码库中搜索关键符号、函数名、类型定义
|
||||
3. **Read**:读取发现的文件以收集完整上下文
|
||||
4. **Task(Explore 代理)**:如需更广泛的探索,使用 `Task` 并设置 `subagent_type: “Explore”`
|
||||
|
||||
**检索后**:
|
||||
|
||||
|
||||
@@ -86,13 +86,13 @@ EOF",
|
||||
|
||||
### 阶段 0: 提示词增强(可选)
|
||||
|
||||
`[Mode: Prepare]` - 如果 ace-tool MCP 可用,调用 `mcp__ace-tool__enhance_prompt`,**将原始的 $ARGUMENTS 替换为增强后的结果,用于后续的 Gemini 调用**
|
||||
`[Mode: Prepare]` - 如果 ace-tool MCP 可用,调用 `mcp__ace-tool__enhance_prompt`,**将原始的 $ARGUMENTS 替换为增强后的结果,用于后续的 Gemini 调用**。不可用时,直接使用 `$ARGUMENTS`。
|
||||
|
||||
### 阶段 1: 研究
|
||||
|
||||
`[Mode: Research]` - 理解需求并收集上下文
|
||||
|
||||
1. **代码检索**(如果 ace-tool MCP 可用): 调用 `mcp__ace-tool__search_context` 来检索现有的组件、样式、设计系统
|
||||
1. **代码检索**(如果 ace-tool MCP 可用): 调用 `mcp__ace-tool__search_context` 来检索现有的组件、样式、设计系统。不可用时,使用内置工具:`Glob` 进行文件发现,`Grep` 进行组件/样式搜索,`Read` 进行上下文收集,`Task`(Explore 代理)进行更深入的探索。
|
||||
2. 需求完整性评分(0-10): >=7 继续,<7 停止并补充
|
||||
|
||||
### 阶段 2: 构思
|
||||
|
||||
@@ -73,7 +73,7 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
#### 1.1 提示增强(必须先执行)
|
||||
|
||||
**必须调用 `mcp__ace-tool__enhance_prompt` 工具**:
|
||||
**如果 ace-tool MCP 可用**,调用 `mcp__ace-tool__enhance_prompt` 工具:
|
||||
|
||||
```
|
||||
mcp__ace-tool__enhance_prompt({
|
||||
@@ -85,9 +85,11 @@ mcp__ace-tool__enhance_prompt({
|
||||
|
||||
等待增强后的提示,**将所有后续阶段的原始 $ARGUMENTS 替换为增强结果**。
|
||||
|
||||
**如果 ace-tool MCP 不可用**:跳过此步骤,在所有后续阶段中直接使用原始 `$ARGUMENTS`。
|
||||
|
||||
#### 1.2 上下文检索
|
||||
|
||||
**调用 `mcp__ace-tool__search_context` 工具**:
|
||||
**如果 ace-tool MCP 可用**,调用 `mcp__ace-tool__search_context` 工具:
|
||||
|
||||
```
|
||||
mcp__ace-tool__search_context({
|
||||
@@ -98,7 +100,12 @@ mcp__ace-tool__search_context({
|
||||
|
||||
* 使用自然语言构建语义查询(Where/What/How)
|
||||
* **绝不基于假设回答**
|
||||
* 如果 MCP 不可用:回退到 Glob + Grep 进行文件发现和关键符号定位
|
||||
|
||||
**如果 ace-tool MCP 不可用**,使用 Claude Code 内置工具作为回退:
|
||||
1. **Glob**:按模式查找相关文件(例如 `Glob("**/*.ts")`、`Glob("src/**/*.py")`)
|
||||
2. **Grep**:搜索关键符号、函数名、类定义(例如 `Grep("className|functionName")`)
|
||||
3. **Read**:读取发现的文件以收集完整上下文
|
||||
4. **Task(Explore 代理)**:如需更深入的探索,使用 `Task` 并设置 `subagent_type: "Explore"`
|
||||
|
||||
#### 1.3 完整性检查
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 待开发任务:$ARGUMENTS
|
||||
* 结构化的 6 阶段工作流程,包含质量门控
|
||||
* 多模型协作:Codex(后端) + Gemini(前端) + Claude(编排)
|
||||
* MCP 服务集成(ace-tool)以增强能力
|
||||
* MCP 服务集成(ace-tool,可选)以增强能力
|
||||
|
||||
## 你的角色
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
**协作模型**:
|
||||
|
||||
* **ace-tool MCP** – 代码检索 + 提示词增强
|
||||
* **ace-tool MCP**(可选) – 代码检索 + 提示词增强
|
||||
* **Codex** – 后端逻辑、算法、调试(**后端权威,可信赖**)
|
||||
* **Gemini** – 前端 UI/UX、视觉设计(**前端专家,后端意见仅供参考**)
|
||||
* **Claude(自身)** – 编排、规划、执行、交付
|
||||
@@ -114,8 +114,8 @@ TaskOutput({ task_id: "<task_id>", block: true, timeout: 600000 })
|
||||
|
||||
`[Mode: Research]` - 理解需求并收集上下文:
|
||||
|
||||
1. **提示词增强**:调用 `mcp__ace-tool__enhance_prompt`,**将所有后续对 Codex/Gemini 的调用中的原始 $ARGUMENTS 替换为增强后的结果**
|
||||
2. **上下文检索**:调用 `mcp__ace-tool__search_context`
|
||||
1. **提示词增强**(如果 ace-tool MCP 可用):调用 `mcp__ace-tool__enhance_prompt`,**将所有后续对 Codex/Gemini 的调用中的原始 $ARGUMENTS 替换为增强后的结果**。不可用时,直接使用 `$ARGUMENTS`。
|
||||
2. **上下文检索**(如果 ace-tool MCP 可用):调用 `mcp__ace-tool__search_context`。不可用时,使用内置工具:`Glob` 进行文件发现,`Grep` 进行符号搜索,`Read` 进行上下文收集,`Task`(Explore 代理)进行更深入的探索。
|
||||
3. **需求完整性评分** (0-10):
|
||||
* 目标清晰度 (0-3),预期成果 (0-3),范围边界 (0-2),约束条件 (0-2)
|
||||
* ≥7:继续 | <7:停止,询问澄清问题
|
||||
|
||||
@@ -9,7 +9,7 @@ version: 2.0.0
|
||||
|
||||
一个高级学习系统,通过原子化的“本能”——带有置信度评分的小型习得行为——将你的 Claude Code 会话转化为可重用的知识。
|
||||
|
||||
部分灵感来源于 [humanplane](https://github.com/humanplane) 的 Homunculus 项目。
|
||||
部分灵感来源于 humanplane (credit: @humanplane) 的 Homunculus 项目。
|
||||
|
||||
## 何时激活
|
||||
|
||||
|
||||
@@ -117,4 +117,4 @@ Homunculus v2 采用了更复杂的方法:
|
||||
4. **领域标记** - 代码风格、测试、git、调试等
|
||||
5. **演进路径** - 将相关本能聚类为技能/命令
|
||||
|
||||
完整规格请参见:`/Users/affoon/Documents/tasks/12-continuous-learning-v2.md`
|
||||
完整规格请参见:`docs/continuous-learning-v2-spec.md`
|
||||
|
||||
@@ -161,7 +161,7 @@ include(FetchContent)
|
||||
set(GTEST_VERSION v1.17.0) # Adjust to project policy.
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip
|
||||
URL Google Test framework (official repository) https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
|
||||
@@ -151,4 +151,4 @@ SELECT pg_reload_conf();
|
||||
|
||||
***
|
||||
|
||||
*基于 [Supabase Agent Skills](https://github.com/supabase/agent-skills) (MIT License)*
|
||||
*基于 [Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team)) (MIT License)*
|
||||
|
||||
@@ -137,7 +137,7 @@ alias claude-research='claude --system-prompt "$(cat ~/.claude/contexts/research
|
||||
用 mgrep 替换 grep——与传统 grep 或 ripgrep 相比,平均减少约 50% 的令牌:
|
||||
|
||||

|
||||
*在我们的 50 项任务基准测试中,mgrep + Claude Code 使用的 token 数量比基于 grep 的工作流少约 2 倍,且判断质量相似或更好。来源:https://github.com/mixedbread-ai/mgrep*
|
||||
*在我们的 50 项任务基准测试中,mgrep + Claude Code 使用的 token 数量比基于 grep 的工作流少约 2 倍,且判断质量相似或更好。来源:mgrep by @mixedbread-ai*
|
||||
|
||||
**模块化代码库的好处:**
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ MCP 将 Claude 直接连接到外部服务。它不是 API 的替代品——而
|
||||
|
||||
```bash
|
||||
# Add a marketplace
|
||||
# mgrep plugin by @mixedbread-ai
|
||||
claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
|
||||
# Open Claude, run /plugins, find new marketplace, install from there
|
||||
|
||||
@@ -7,7 +7,7 @@ model: opus
|
||||
|
||||
# 資料庫審查員
|
||||
|
||||
您是一位專注於查詢優化、結構描述設計、安全性和效能的 PostgreSQL 資料庫專家。您的任務是確保資料庫程式碼遵循最佳實務、預防效能問題並維護資料完整性。此 Agent 整合了來自 [Supabase 的 postgres-best-practices](https://github.com/supabase/agent-skills) 的模式。
|
||||
您是一位專注於查詢優化、結構描述設計、安全性和效能的 PostgreSQL 資料庫專家。您的任務是確保資料庫程式碼遵循最佳實務、預防效能問題並維護資料完整性。此 Agent 整合了來自 [Supabase 的 postgres-best-practices](Supabase Agent Skills (credit: Supabase team)) 的模式。
|
||||
|
||||
## 核心職責
|
||||
|
||||
@@ -375,4 +375,4 @@ RETURNING *;
|
||||
|
||||
**記住**:資料庫問題通常是應用程式效能問題的根本原因。儘早優化查詢和結構描述設計。使用 EXPLAIN ANALYZE 驗證假設。總是為外鍵和 RLS 政策欄位建立索引。
|
||||
|
||||
*模式改編自 [Supabase Agent Skills](https://github.com/supabase/agent-skills),MIT 授權。*
|
||||
*模式改編自 [Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team)),MIT 授權。*
|
||||
|
||||
@@ -107,4 +107,4 @@ Homunculus v2 採用更複雜的方法:
|
||||
4. **領域標記** - code-style、testing、git、debugging 等
|
||||
5. **演化路徑** - 將相關本能聚類為技能/指令
|
||||
|
||||
參見:`/Users/affoon/Documents/tasks/12-continuous-learning-v2.md` 完整規格。
|
||||
參見:`docs/continuous-learning-v2-spec.md` 完整規格。
|
||||
|
||||
@@ -143,4 +143,4 @@ SELECT pg_reload_conf();
|
||||
|
||||
---
|
||||
|
||||
*基於 [Supabase Agent Skills](https://github.com/supabase/agent-skills)(MIT 授權)*
|
||||
*基於 [Supabase Agent Skills](Supabase Agent Skills (credit: Supabase team))(MIT 授權)*
|
||||
|
||||
@@ -32,6 +32,7 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
|
||||
|------|---------|-------------|
|
||||
| **PR logger** | `Bash` | Logs PR URL and review command after `gh pr create` |
|
||||
| **Build analysis** | `Bash` | Background analysis after build commands (async, non-blocking) |
|
||||
| **Quality gate** | `Edit\|Write\|MultiEdit` | Runs fast quality checks after edits |
|
||||
| **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits |
|
||||
| **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts`/`.tsx` files |
|
||||
| **console.log warning** | `Edit` | Warns about `console.log` statements in edited files |
|
||||
@@ -43,8 +44,10 @@ User request → Claude picks a tool → PreToolUse hook runs → Tool executes
|
||||
| **Session start** | `SessionStart` | Loads previous context and detects package manager |
|
||||
| **Pre-compact** | `PreCompact` | Saves state before context compaction |
|
||||
| **Console.log audit** | `Stop` | Checks all modified files for `console.log` after each response |
|
||||
| **Session end** | `SessionEnd` | Persists session state for next session |
|
||||
| **Pattern extraction** | `SessionEnd` | Evaluates session for extractable patterns (continuous learning) |
|
||||
| **Session summary** | `Stop` | Persists session state when transcript path is available |
|
||||
| **Pattern extraction** | `Stop` | Evaluates session for extractable patterns (continuous learning) |
|
||||
| **Cost tracker** | `Stop` | Emits lightweight run-cost telemetry markers |
|
||||
| **Session end marker** | `SessionEnd` | Lifecycle marker and cleanup log |
|
||||
|
||||
## Customizing Hooks
|
||||
|
||||
@@ -66,6 +69,23 @@ Remove or comment out the hook entry in `hooks.json`. If installed as a plugin,
|
||||
}
|
||||
```
|
||||
|
||||
### Runtime Hook Controls (Recommended)
|
||||
|
||||
Use environment variables to control hook behavior without editing `hooks.json`:
|
||||
|
||||
```bash
|
||||
# minimal | standard | strict (default: standard)
|
||||
export ECC_HOOK_PROFILE=standard
|
||||
|
||||
# Disable specific hook IDs (comma-separated)
|
||||
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||||
```
|
||||
|
||||
Profiles:
|
||||
- `minimal` — keep essential lifecycle and safety hooks only.
|
||||
- `standard` — default; balanced quality + safety checks.
|
||||
- `strict` — enables additional reminders and stricter guardrails.
|
||||
|
||||
### Writing Your Own Hook
|
||||
|
||||
Hooks are shell commands that receive tool input as JSON on stdin and must output JSON on stdout.
|
||||
@@ -189,7 +209,7 @@ Async hooks run in the background. They cannot block tool execution.
|
||||
|
||||
## Cross-Platform Notes
|
||||
|
||||
All hooks in this plugin use Node.js (`node -e` or `node script.js`) for maximum compatibility across Windows, macOS, and Linux. Avoid bash-specific syntax in hooks.
|
||||
Hook logic is implemented in Node.js scripts for cross-platform behavior on Windows, macOS, and Linux. A small number of shell wrappers are retained for continuous-learning observer hooks; those wrappers are profile-gated and have Windows-safe fallback behavior.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(process.platform!=='win32'&&/(npm run dev\\b|pnpm( run)? dev\\b|yarn dev\\b|bun run dev\\b)/.test(cmd)){console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');console.error('[Hook] Use: tmux new-session -d -s dev \\\"npm run dev\\\"');console.error('[Hook] Then: tmux attach -t dev');process.exit(2)}}catch{}console.log(d)})\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:bash:dev-server-block\" \"scripts/hooks/pre-bash-dev-server-block.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Block dev servers outside tmux - ensures you can access logs"
|
||||
@@ -17,7 +17,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(process.platform!=='win32'&&!process.env.TMUX&&/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\\b|docker\\b|pytest|vitest|playwright)/.test(cmd)){console.error('[Hook] Consider running in tmux for session persistence');console.error('[Hook] tmux new -s dev | tmux attach -t dev')}}catch{}console.log(d)})\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:bash:tmux-reminder\" \"scripts/hooks/pre-bash-tmux-reminder.js\" \"strict\""
|
||||
}
|
||||
],
|
||||
"description": "Reminder to use tmux for long-running commands"
|
||||
@@ -27,7 +27,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/git push/.test(cmd)){console.error('[Hook] Review changes before push...');console.error('[Hook] Continuing with push (remove this hook to add interactive review)')}}catch{}console.log(d)})\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:bash:git-push-reminder\" \"scripts/hooks/pre-bash-git-push-reminder.js\" \"strict\""
|
||||
}
|
||||
],
|
||||
"description": "Reminder before git push to review changes"
|
||||
@@ -37,7 +37,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/doc-file-warning.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:write:doc-file-warning\" \"scripts/hooks/doc-file-warning.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Doc file warning: warn about non-standard documentation files (exit code 0; warns only)"
|
||||
@@ -47,7 +47,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/suggest-compact.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:edit-write:suggest-compact\" \"scripts/hooks/suggest-compact.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Suggest manual compaction at logical intervals"
|
||||
@@ -57,7 +57,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh",
|
||||
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags-shell.sh\" \"pre:observe\" \"skills/continuous-learning-v2/hooks/observe.sh\" \"standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -71,7 +71,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/pre-compact.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"pre:compact\" \"scripts/hooks/pre-compact.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Save state before context compaction"
|
||||
@@ -83,7 +83,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/session-start.js\""
|
||||
"command": "bash -lc 'input=$(cat); for root in \"${CLAUDE_PLUGIN_ROOT:-}\" \"$HOME/.claude/plugins/everything-claude-code\" \"$HOME/.claude/plugins/everything-claude-code@everything-claude-code\" \"$HOME/.claude/plugins/marketplace/everything-claude-code\"; do if [ -n \"$root\" ] && [ -f \"$root/scripts/hooks/run-with-flags.js\" ]; then printf \"%s\" \"$input\" | node \"$root/scripts/hooks/run-with-flags.js\" \"session:start\" \"scripts/hooks/session-start.js\" \"minimal,standard,strict\"; exit $?; fi; done; for parent in \"$HOME/.claude/plugins\" \"$HOME/.claude/plugins/marketplace\"; do if [ -d \"$parent\" ]; then candidate=$(find \"$parent\" -maxdepth 2 -type f -path \"*/scripts/hooks/run-with-flags.js\" 2>/dev/null | head -n 1); if [ -n \"$candidate\" ]; then root=$(dirname \"$(dirname \"$(dirname \"$candidate\")\")\"); printf \"%s\" \"$input\" | node \"$root/scripts/hooks/run-with-flags.js\" \"session:start\" \"scripts/hooks/session-start.js\" \"minimal,standard,strict\"; exit $?; fi; fi; done; echo \"[SessionStart] WARNING: could not resolve ECC plugin root; skipping session-start hook\" >&2; printf \"%s\" \"$input\"; exit 0'"
|
||||
}
|
||||
],
|
||||
"description": "Load previous context and detect package manager on new session"
|
||||
@@ -95,7 +95,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/gh pr create/.test(cmd)){const out=i.tool_output?.output||'';const m=out.match(/https:\\/\\/github.com\\/[^/]+\\/[^/]+\\/pull\\/\\d+/);if(m){console.error('[Hook] PR created: '+m[0]);const repo=m[0].replace(/https:\\/\\/github.com\\/([^/]+\\/[^/]+)\\/pull\\/\\d+/,'$1');const pr=m[0].replace(/.+\\/pull\\/(\\d+)/,'$1');console.error('[Hook] To review: gh pr review '+pr+' --repo '+repo)}}}catch{}console.log(d)})\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:bash:pr-created\" \"scripts/hooks/post-bash-pr-created.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Log PR URL and provide review command after PR creation"
|
||||
@@ -105,19 +105,31 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/(npm run build|pnpm build|yarn build)/.test(cmd)){console.error('[Hook] Build completed - async analysis running in background')}}catch{}console.log(d)})\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:bash:build-complete\" \"scripts/hooks/post-bash-build-complete.js\" \"standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 30
|
||||
}
|
||||
],
|
||||
"description": "Example: async hook for build analysis (runs in background without blocking)"
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:quality-gate\" \"scripts/hooks/quality-gate.js\" \"standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 30
|
||||
}
|
||||
],
|
||||
"description": "Run quality gate checks after file edits"
|
||||
},
|
||||
{
|
||||
"matcher": "Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/post-edit-format.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:edit:format\" \"scripts/hooks/post-edit-format.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Auto-format JS/TS files after edits (auto-detects Biome or Prettier)"
|
||||
@@ -127,7 +139,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/post-edit-typecheck.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:edit:typecheck\" \"scripts/hooks/post-edit-typecheck.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "TypeScript check after editing .ts/.tsx files"
|
||||
@@ -137,7 +149,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/post-edit-console-warn.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"post:edit:console-warn\" \"scripts/hooks/post-edit-console-warn.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Warn about console.log statements after edits"
|
||||
@@ -147,7 +159,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/skills/continuous-learning-v2/hooks/observe.sh",
|
||||
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags-shell.sh\" \"post:observe\" \"skills/continuous-learning-v2/hooks/observe.sh\" \"standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
@@ -161,10 +173,46 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/check-console-log.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:check-console-log\" \"scripts/hooks/check-console-log.js\" \"standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Check for console.log in modified files after each response"
|
||||
},
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:session-end\" \"scripts/hooks/session-end.js\" \"minimal,standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
],
|
||||
"description": "Persist session state after each response (Stop carries transcript_path)"
|
||||
},
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:evaluate-session\" \"scripts/hooks/evaluate-session.js\" \"minimal,standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
],
|
||||
"description": "Evaluate session for extractable patterns"
|
||||
},
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:cost-tracker\" \"scripts/hooks/cost-tracker.js\" \"minimal,standard,strict\"",
|
||||
"async": true,
|
||||
"timeout": 10
|
||||
}
|
||||
],
|
||||
"description": "Track token and cost metrics per session"
|
||||
}
|
||||
],
|
||||
"SessionEnd": [
|
||||
@@ -173,20 +221,10 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/session-end.js\""
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"session:end:marker\" \"scripts/hooks/session-end-marker.js\" \"minimal,standard,strict\""
|
||||
}
|
||||
],
|
||||
"description": "Persist session state on end"
|
||||
},
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/evaluate-session.js\""
|
||||
}
|
||||
],
|
||||
"description": "Evaluate session for extractable patterns"
|
||||
"description": "Session end lifecycle marker (non-blocking)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
77
install.sh
77
install.sh
@@ -11,8 +11,9 @@
|
||||
# ./install.sh --target cursor typescript python golang
|
||||
#
|
||||
# Targets:
|
||||
# claude (default) — Install rules to ~/.claude/rules/
|
||||
# cursor — Install rules, agents, skills, commands, and MCP to ./.cursor/
|
||||
# claude (default) — Install rules to ~/.claude/rules/
|
||||
# cursor — Install rules, agents, skills, commands, and MCP to ./.cursor/
|
||||
# antigravity — Install configs to .agent/
|
||||
#
|
||||
# This script copies rules into the target directory keeping the common/ and
|
||||
# language-specific subdirectories intact so that:
|
||||
@@ -44,18 +45,19 @@ if [[ "${1:-}" == "--target" ]]; then
|
||||
shift 2
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" != "claude" && "$TARGET" != "cursor" ]]; then
|
||||
echo "Error: unknown target '$TARGET'. Must be 'claude' or 'cursor'." >&2
|
||||
if [[ "$TARGET" != "claude" && "$TARGET" != "cursor" && "$TARGET" != "antigravity" ]]; then
|
||||
echo "Error: unknown target '$TARGET'. Must be 'claude', 'cursor', or 'antigravity'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Usage ---
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 [--target <claude|cursor>] <language> [<language> ...]"
|
||||
echo "Usage: $0 [--target <claude|cursor|antigravity>] <language> [<language> ...]"
|
||||
echo ""
|
||||
echo "Targets:"
|
||||
echo " claude (default) — Install rules to ~/.claude/rules/"
|
||||
echo " cursor — Install rules, agents, skills, commands, and MCP to ./.cursor/"
|
||||
echo " claude (default) — Install rules to ~/.claude/rules/"
|
||||
echo " cursor — Install rules, agents, skills, commands, and MCP to ./.cursor/"
|
||||
echo " antigravity — Install configs to .agent/"
|
||||
echo ""
|
||||
echo "Available languages:"
|
||||
for dir in "$RULES_DIR"/*/; do
|
||||
@@ -181,3 +183,64 @@ if [[ "$TARGET" == "cursor" ]]; then
|
||||
|
||||
echo "Done. Cursor configs installed to $DEST_DIR/"
|
||||
fi
|
||||
|
||||
# --- Antigravity target ---
|
||||
if [[ "$TARGET" == "antigravity" ]]; then
|
||||
DEST_DIR=".agent"
|
||||
|
||||
if [[ -d "$DEST_DIR/rules" ]] && [[ "$(ls -A "$DEST_DIR/rules" 2>/dev/null)" ]]; then
|
||||
echo "Note: $DEST_DIR/rules/ already exists. Existing files will be overwritten."
|
||||
echo " Back up any local customizations before proceeding."
|
||||
fi
|
||||
|
||||
# --- Rules ---
|
||||
echo "Installing common rules -> $DEST_DIR/rules/"
|
||||
mkdir -p "$DEST_DIR/rules"
|
||||
if [[ -d "$RULES_DIR/common" ]]; then
|
||||
for f in "$RULES_DIR/common"/*.md; do
|
||||
if [[ -f "$f" ]]; then
|
||||
cp "$f" "$DEST_DIR/rules/common-$(basename "$f")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for lang in "$@"; do
|
||||
# Validate language name to prevent path traversal
|
||||
if [[ ! "$lang" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
||||
echo "Error: invalid language name '$lang'. Only alphanumeric, dash, and underscore allowed." >&2
|
||||
continue
|
||||
fi
|
||||
lang_dir="$RULES_DIR/$lang"
|
||||
if [[ ! -d "$lang_dir" ]]; then
|
||||
echo "Warning: rules/$lang/ does not exist, skipping." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Installing $lang rules -> $DEST_DIR/rules/"
|
||||
for f in "$lang_dir"/*.md; do
|
||||
if [[ -f "$f" ]]; then
|
||||
cp "$f" "$DEST_DIR/rules/${lang}-$(basename "$f")"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# --- Workflows (Commands) ---
|
||||
if [[ -d "$SCRIPT_DIR/commands" ]]; then
|
||||
echo "Installing commands -> $DEST_DIR/workflows/"
|
||||
mkdir -p "$DEST_DIR/workflows"
|
||||
cp -r "$SCRIPT_DIR/commands/." "$DEST_DIR/workflows/"
|
||||
fi
|
||||
|
||||
# --- Skills and Agents ---
|
||||
mkdir -p "$DEST_DIR/skills"
|
||||
if [[ -d "$SCRIPT_DIR/agents" ]]; then
|
||||
echo "Installing agents -> $DEST_DIR/skills/"
|
||||
cp -r "$SCRIPT_DIR/agents/." "$DEST_DIR/skills/"
|
||||
fi
|
||||
if [[ -d "$SCRIPT_DIR/skills" ]]; then
|
||||
echo "Installing skills -> $DEST_DIR/skills/"
|
||||
cp -r "$SCRIPT_DIR/skills/." "$DEST_DIR/skills/"
|
||||
fi
|
||||
|
||||
echo "Done. Antigravity configs installed to $DEST_DIR/"
|
||||
fi
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "1.4.1",
|
||||
"version": "1.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ecc-universal",
|
||||
"version": "1.4.1",
|
||||
"version": "1.8.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ecc-universal",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"description": "Complete collection of battle-tested Claude Code configs — agents, skills, hooks, commands, and rules evolved over 10+ months of intensive daily use by an Anthropic hackathon winner",
|
||||
"keywords": [
|
||||
"claude-code",
|
||||
@@ -83,7 +83,7 @@
|
||||
"postinstall": "echo '\\n ecc-universal installed!\\n Run: npx ecc-install typescript\\n Docs: https://github.com/affaan-m/everything-claude-code\\n'",
|
||||
"lint": "eslint . && markdownlint '**/*.md' --ignore node_modules",
|
||||
"claw": "node scripts/claw.js",
|
||||
"test": "node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node tests/run-all.js"
|
||||
"test": "node scripts/ci/validate-agents.js && node scripts/ci/validate-commands.js && node scripts/ci/validate-rules.js && node scripts/ci/validate-skills.js && node scripts/ci/validate-hooks.js && node scripts/ci/validate-no-personal-paths.js && node tests/run-all.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
|
||||
@@ -14,7 +14,7 @@ Marketplaces are repositories of installable plugins.
|
||||
# Add official Anthropic marketplace
|
||||
claude plugin marketplace add https://github.com/anthropics/claude-plugins-official
|
||||
|
||||
# Add community marketplaces
|
||||
# Add community marketplaces (mgrep by @mixedbread-ai)
|
||||
claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
|
||||
|-------------|--------|
|
||||
| claude-plugins-official | `anthropics/claude-plugins-official` |
|
||||
| claude-code-plugins | `anthropics/claude-code` |
|
||||
| Mixedbread-Grep | `mixedbread-ai/mgrep` |
|
||||
| Mixedbread-Grep (@mixedbread-ai) | `mixedbread-ai/mgrep` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
63
scripts/ci/validate-no-personal-paths.js
Executable file
63
scripts/ci/validate-no-personal-paths.js
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Prevent shipping user-specific absolute paths in public docs/skills/commands.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ROOT = path.join(__dirname, '../..');
|
||||
const TARGETS = [
|
||||
'README.md',
|
||||
'skills',
|
||||
'commands',
|
||||
'agents',
|
||||
'docs',
|
||||
'.opencode/commands',
|
||||
];
|
||||
|
||||
const BLOCK_PATTERNS = [
|
||||
/\/Users\/affoon\b/g,
|
||||
/C:\\Users\\affoon\b/gi,
|
||||
];
|
||||
|
||||
function collectFiles(targetPath, out) {
|
||||
if (!fs.existsSync(targetPath)) return;
|
||||
const stat = fs.statSync(targetPath);
|
||||
if (stat.isFile()) {
|
||||
out.push(targetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of fs.readdirSync(targetPath)) {
|
||||
if (entry === 'node_modules' || entry === '.git') continue;
|
||||
collectFiles(path.join(targetPath, entry), out);
|
||||
}
|
||||
}
|
||||
|
||||
const files = [];
|
||||
for (const target of TARGETS) {
|
||||
collectFiles(path.join(ROOT, target), files);
|
||||
}
|
||||
|
||||
let failures = 0;
|
||||
for (const file of files) {
|
||||
if (!/\.(md|json|js|ts|sh|toml|yml|yaml)$/i.test(file)) continue;
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
for (const pattern of BLOCK_PATTERNS) {
|
||||
const match = content.match(pattern);
|
||||
if (match) {
|
||||
console.error(`ERROR: personal path detected in ${path.relative(ROOT, file)}`);
|
||||
failures += match.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Validated: no personal absolute paths in shipped docs/skills/commands');
|
||||
@@ -8,14 +8,47 @@ const path = require('path');
|
||||
|
||||
const RULES_DIR = path.join(__dirname, '../../rules');
|
||||
|
||||
/**
|
||||
* Recursively collect markdown rule files.
|
||||
* Uses explicit traversal for portability across Node versions.
|
||||
* @param {string} dir - Directory to scan
|
||||
* @returns {string[]} Relative file paths from RULES_DIR
|
||||
*/
|
||||
function collectRuleFiles(dir) {
|
||||
const files = [];
|
||||
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return files;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const absolute = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...collectRuleFiles(absolute));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.name.endsWith('.md')) {
|
||||
files.push(path.relative(RULES_DIR, absolute));
|
||||
}
|
||||
|
||||
// Non-markdown files are ignored.
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function validateRules() {
|
||||
if (!fs.existsSync(RULES_DIR)) {
|
||||
console.log('No rules directory found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(RULES_DIR, { recursive: true })
|
||||
.filter(f => f.endsWith('.md'));
|
||||
const files = collectRuleFiles(RULES_DIR);
|
||||
let hasErrors = false;
|
||||
let validatedCount = 0;
|
||||
|
||||
|
||||
407
scripts/claw.js
407
scripts/claw.js
@@ -1,14 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* NanoClaw — Barebones Agent REPL for Everything Claude Code
|
||||
* NanoClaw v2 — Barebones Agent REPL for Everything Claude Code
|
||||
*
|
||||
* A persistent, session-aware AI agent loop that delegates to `claude -p`.
|
||||
* Zero external dependencies. Markdown-as-database. Synchronous REPL.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/claw.js
|
||||
* CLAW_SESSION=my-project node scripts/claw.js
|
||||
* CLAW_SKILLS=tdd-workflow,security-review node scripts/claw.js
|
||||
* Zero external dependencies. Session-aware REPL around `claude -p`.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -19,29 +13,25 @@ const os = require('os');
|
||||
const { spawnSync } = require('child_process');
|
||||
const readline = require('readline');
|
||||
|
||||
// ─── Session name validation ────────────────────────────────────────────────
|
||||
|
||||
const SESSION_NAME_RE = /^[a-zA-Z0-9][-a-zA-Z0-9]*$/;
|
||||
const DEFAULT_MODEL = process.env.CLAW_MODEL || 'sonnet';
|
||||
const DEFAULT_COMPACT_KEEP_TURNS = 20;
|
||||
|
||||
function isValidSessionName(name) {
|
||||
return typeof name === 'string' && name.length > 0 && SESSION_NAME_RE.test(name);
|
||||
}
|
||||
|
||||
// ─── Storage Adapter (Markdown-as-Database) ─────────────────────────────────
|
||||
|
||||
function getClawDir() {
|
||||
return path.join(os.homedir(), '.claude', 'claw');
|
||||
}
|
||||
|
||||
function getSessionPath(name) {
|
||||
return path.join(getClawDir(), name + '.md');
|
||||
return path.join(getClawDir(), `${name}.md`);
|
||||
}
|
||||
|
||||
function listSessions(dir) {
|
||||
const clawDir = dir || getClawDir();
|
||||
if (!fs.existsSync(clawDir)) {
|
||||
return [];
|
||||
}
|
||||
if (!fs.existsSync(clawDir)) return [];
|
||||
return fs.readdirSync(clawDir)
|
||||
.filter(f => f.endsWith('.md'))
|
||||
.map(f => f.replace(/\.md$/, ''));
|
||||
@@ -50,7 +40,7 @@ function listSessions(dir) {
|
||||
function loadHistory(filePath) {
|
||||
try {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
} catch (_err) {
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -58,29 +48,27 @@ function loadHistory(filePath) {
|
||||
function appendTurn(filePath, role, content, timestamp) {
|
||||
const ts = timestamp || new Date().toISOString();
|
||||
const entry = `### [${ts}] ${role}\n${content}\n---\n`;
|
||||
const dir = path.dirname(filePath);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.appendFileSync(filePath, entry, 'utf8');
|
||||
}
|
||||
|
||||
// ─── Context & Delegation Pipeline ──────────────────────────────────────────
|
||||
function normalizeSkillList(raw) {
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw.map(s => String(s).trim()).filter(Boolean);
|
||||
return String(raw).split(',').map(s => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function loadECCContext(skillList) {
|
||||
const raw = skillList !== undefined ? skillList : (process.env.CLAW_SKILLS || '');
|
||||
if (!raw.trim()) {
|
||||
return '';
|
||||
}
|
||||
const requested = normalizeSkillList(skillList !== undefined ? skillList : process.env.CLAW_SKILLS || '');
|
||||
if (requested.length === 0) return '';
|
||||
|
||||
const names = raw.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const chunks = [];
|
||||
|
||||
for (const name of names) {
|
||||
for (const name of requested) {
|
||||
const skillPath = path.join(process.cwd(), 'skills', name, 'SKILL.md');
|
||||
try {
|
||||
const content = fs.readFileSync(skillPath, 'utf8');
|
||||
chunks.push(content);
|
||||
} catch (_err) {
|
||||
// Gracefully skip missing skills
|
||||
chunks.push(fs.readFileSync(skillPath, 'utf8'));
|
||||
} catch {
|
||||
// Skip missing skills silently to keep REPL usable.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,38 +77,159 @@ function loadECCContext(skillList) {
|
||||
|
||||
function buildPrompt(systemPrompt, history, userMessage) {
|
||||
const parts = [];
|
||||
if (systemPrompt) {
|
||||
parts.push('=== SYSTEM CONTEXT ===\n' + systemPrompt + '\n');
|
||||
}
|
||||
if (history) {
|
||||
parts.push('=== CONVERSATION HISTORY ===\n' + history + '\n');
|
||||
}
|
||||
parts.push('=== USER MESSAGE ===\n' + userMessage);
|
||||
if (systemPrompt) parts.push(`=== SYSTEM CONTEXT ===\n${systemPrompt}\n`);
|
||||
if (history) parts.push(`=== CONVERSATION HISTORY ===\n${history}\n`);
|
||||
parts.push(`=== USER MESSAGE ===\n${userMessage}`);
|
||||
return parts.join('\n');
|
||||
}
|
||||
|
||||
function askClaude(systemPrompt, history, userMessage) {
|
||||
function askClaude(systemPrompt, history, userMessage, model) {
|
||||
const fullPrompt = buildPrompt(systemPrompt, history, userMessage);
|
||||
const args = [];
|
||||
if (model) {
|
||||
args.push('--model', model);
|
||||
}
|
||||
args.push('-p', fullPrompt);
|
||||
|
||||
const result = spawnSync('claude', ['-p', fullPrompt], {
|
||||
const result = spawnSync('claude', args, {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env, CLAUDECODE: '' },
|
||||
timeout: 300000 // 5 minute timeout
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
return '[Error: ' + result.error.message + ']';
|
||||
return `[Error: ${result.error.message}]`;
|
||||
}
|
||||
|
||||
if (result.status !== 0 && result.stderr) {
|
||||
return '[Error: claude exited with code ' + result.status + ': ' + result.stderr.trim() + ']';
|
||||
return `[Error: claude exited with code ${result.status}: ${result.stderr.trim()}]`;
|
||||
}
|
||||
|
||||
return (result.stdout || '').trim();
|
||||
}
|
||||
|
||||
// ─── REPL Commands ──────────────────────────────────────────────────────────
|
||||
function parseTurns(history) {
|
||||
const turns = [];
|
||||
const regex = /### \[([^\]]+)\] ([^\n]+)\n([\s\S]*?)\n---\n/g;
|
||||
let match;
|
||||
while ((match = regex.exec(history)) !== null) {
|
||||
turns.push({ timestamp: match[1], role: match[2], content: match[3] });
|
||||
}
|
||||
return turns;
|
||||
}
|
||||
|
||||
function estimateTokenCount(text) {
|
||||
return Math.ceil((text || '').length / 4);
|
||||
}
|
||||
|
||||
function getSessionMetrics(filePath) {
|
||||
const history = loadHistory(filePath);
|
||||
const turns = parseTurns(history);
|
||||
const charCount = history.length;
|
||||
const tokenEstimate = estimateTokenCount(history);
|
||||
const userTurns = turns.filter(t => t.role === 'User').length;
|
||||
const assistantTurns = turns.filter(t => t.role === 'Assistant').length;
|
||||
|
||||
return {
|
||||
turns: turns.length,
|
||||
userTurns,
|
||||
assistantTurns,
|
||||
charCount,
|
||||
tokenEstimate,
|
||||
};
|
||||
}
|
||||
|
||||
function searchSessions(query, dir) {
|
||||
const q = String(query || '').toLowerCase().trim();
|
||||
if (!q) return [];
|
||||
|
||||
const sessionDir = dir || getClawDir();
|
||||
const sessions = listSessions(sessionDir);
|
||||
const results = [];
|
||||
for (const name of sessions) {
|
||||
const p = path.join(sessionDir, `${name}.md`);
|
||||
const content = loadHistory(p);
|
||||
if (!content) continue;
|
||||
|
||||
const idx = content.toLowerCase().indexOf(q);
|
||||
if (idx >= 0) {
|
||||
const start = Math.max(0, idx - 40);
|
||||
const end = Math.min(content.length, idx + q.length + 40);
|
||||
const snippet = content.slice(start, end).replace(/\n/g, ' ');
|
||||
results.push({ session: name, snippet });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function compactSession(filePath, keepTurns = DEFAULT_COMPACT_KEEP_TURNS) {
|
||||
const history = loadHistory(filePath);
|
||||
if (!history) return false;
|
||||
|
||||
const turns = parseTurns(history);
|
||||
if (turns.length <= keepTurns) return false;
|
||||
|
||||
const retained = turns.slice(-keepTurns);
|
||||
const compactedHeader = `# NanoClaw Compaction\nCompacted at: ${new Date().toISOString()}\nRetained turns: ${keepTurns}/${turns.length}\n\n---\n`;
|
||||
const compactedTurns = retained.map(t => `### [${t.timestamp}] ${t.role}\n${t.content}\n---\n`).join('');
|
||||
fs.writeFileSync(filePath, compactedHeader + compactedTurns, 'utf8');
|
||||
return true;
|
||||
}
|
||||
|
||||
function exportSession(filePath, format, outputPath) {
|
||||
const history = loadHistory(filePath);
|
||||
const sessionName = path.basename(filePath, '.md');
|
||||
const fmt = String(format || 'md').toLowerCase();
|
||||
|
||||
if (!history) {
|
||||
return { ok: false, message: 'No session history to export.' };
|
||||
}
|
||||
|
||||
const dir = path.dirname(filePath);
|
||||
let out = outputPath;
|
||||
if (!out) {
|
||||
out = path.join(dir, `${sessionName}.export.${fmt === 'markdown' ? 'md' : fmt}`);
|
||||
}
|
||||
|
||||
if (fmt === 'md' || fmt === 'markdown') {
|
||||
fs.writeFileSync(out, history, 'utf8');
|
||||
return { ok: true, path: out };
|
||||
}
|
||||
|
||||
if (fmt === 'json') {
|
||||
const turns = parseTurns(history);
|
||||
fs.writeFileSync(out, JSON.stringify({ session: sessionName, turns }, null, 2), 'utf8');
|
||||
return { ok: true, path: out };
|
||||
}
|
||||
|
||||
if (fmt === 'txt' || fmt === 'text') {
|
||||
const turns = parseTurns(history);
|
||||
const txt = turns.map(t => `[${t.timestamp}] ${t.role}:\n${t.content}\n`).join('\n');
|
||||
fs.writeFileSync(out, txt, 'utf8');
|
||||
return { ok: true, path: out };
|
||||
}
|
||||
|
||||
return { ok: false, message: `Unsupported export format: ${format}` };
|
||||
}
|
||||
|
||||
function branchSession(currentSessionPath, newSessionName, targetDir = getClawDir()) {
|
||||
if (!isValidSessionName(newSessionName)) {
|
||||
return { ok: false, message: `Invalid branch session name: ${newSessionName}` };
|
||||
}
|
||||
|
||||
const target = path.join(targetDir, `${newSessionName}.md`);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
|
||||
const content = loadHistory(currentSessionPath);
|
||||
fs.writeFileSync(target, content, 'utf8');
|
||||
return { ok: true, path: target, session: newSessionName };
|
||||
}
|
||||
|
||||
function skillExists(skillName) {
|
||||
const p = path.join(process.cwd(), 'skills', skillName, 'SKILL.md');
|
||||
return fs.existsSync(p);
|
||||
}
|
||||
|
||||
function handleClear(sessionPath) {
|
||||
fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
|
||||
@@ -132,72 +241,73 @@ function handleHistory(sessionPath) {
|
||||
const history = loadHistory(sessionPath);
|
||||
if (!history) {
|
||||
console.log('(no history)');
|
||||
} else {
|
||||
console.log(history);
|
||||
return;
|
||||
}
|
||||
console.log(history);
|
||||
}
|
||||
|
||||
function handleSessions(dir) {
|
||||
const sessions = listSessions(dir);
|
||||
if (sessions.length === 0) {
|
||||
console.log('(no sessions)');
|
||||
} else {
|
||||
console.log('Sessions:');
|
||||
for (const s of sessions) {
|
||||
console.log(' - ' + s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Sessions:');
|
||||
for (const s of sessions) {
|
||||
console.log(` - ${s}`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleHelp() {
|
||||
console.log('NanoClaw REPL Commands:');
|
||||
console.log(' /clear Clear current session history');
|
||||
console.log(' /history Print full conversation history');
|
||||
console.log(' /sessions List all saved sessions');
|
||||
console.log(' /help Show this help message');
|
||||
console.log(' exit Quit the REPL');
|
||||
console.log(' /help Show this help');
|
||||
console.log(' /clear Clear current session history');
|
||||
console.log(' /history Print full conversation history');
|
||||
console.log(' /sessions List saved sessions');
|
||||
console.log(' /model [name] Show/set model');
|
||||
console.log(' /load <skill-name> Load a skill into active context');
|
||||
console.log(' /branch <session-name> Branch current session into a new session');
|
||||
console.log(' /search <query> Search query across sessions');
|
||||
console.log(' /compact Keep recent turns, compact older context');
|
||||
console.log(' /export <md|json|txt> [path] Export current session');
|
||||
console.log(' /metrics Show session metrics');
|
||||
console.log(' exit Quit the REPL');
|
||||
}
|
||||
|
||||
// ─── Main REPL ──────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
const sessionName = process.env.CLAW_SESSION || 'default';
|
||||
|
||||
if (!isValidSessionName(sessionName)) {
|
||||
console.error('Error: Invalid session name "' + sessionName + '". Use alphanumeric characters and hyphens only.');
|
||||
const initialSessionName = process.env.CLAW_SESSION || 'default';
|
||||
if (!isValidSessionName(initialSessionName)) {
|
||||
console.error(`Error: Invalid session name "${initialSessionName}". Use alphanumeric characters and hyphens only.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const clawDir = getClawDir();
|
||||
fs.mkdirSync(clawDir, { recursive: true });
|
||||
fs.mkdirSync(getClawDir(), { recursive: true });
|
||||
|
||||
const sessionPath = getSessionPath(sessionName);
|
||||
const eccContext = loadECCContext();
|
||||
const state = {
|
||||
sessionName: initialSessionName,
|
||||
sessionPath: getSessionPath(initialSessionName),
|
||||
model: DEFAULT_MODEL,
|
||||
skills: normalizeSkillList(process.env.CLAW_SKILLS || ''),
|
||||
};
|
||||
|
||||
const requestedSkills = (process.env.CLAW_SKILLS || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const loadedCount = requestedSkills.filter(name =>
|
||||
fs.existsSync(path.join(process.cwd(), 'skills', name, 'SKILL.md'))
|
||||
).length;
|
||||
let eccContext = loadECCContext(state.skills);
|
||||
|
||||
console.log('NanoClaw v1.0 — Session: ' + sessionName);
|
||||
const loadedCount = state.skills.filter(skillExists).length;
|
||||
|
||||
console.log(`NanoClaw v2 — Session: ${state.sessionName}`);
|
||||
console.log(`Model: ${state.model}`);
|
||||
if (loadedCount > 0) {
|
||||
console.log('Loaded ' + loadedCount + ' skill(s) as context.');
|
||||
console.log(`Loaded ${loadedCount} skill(s) as context.`);
|
||||
}
|
||||
console.log('Type /help for commands, exit to quit.\n');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
||||
const prompt = () => {
|
||||
rl.question('claw> ', (input) => {
|
||||
const line = input.trim();
|
||||
|
||||
if (!line) {
|
||||
prompt();
|
||||
return;
|
||||
}
|
||||
if (!line) return prompt();
|
||||
|
||||
if (line === 'exit') {
|
||||
console.log('Goodbye.');
|
||||
@@ -205,37 +315,123 @@ function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (line === '/help') {
|
||||
handleHelp();
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/clear') {
|
||||
handleClear(sessionPath);
|
||||
prompt();
|
||||
return;
|
||||
handleClear(state.sessionPath);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/history') {
|
||||
handleHistory(sessionPath);
|
||||
prompt();
|
||||
return;
|
||||
handleHistory(state.sessionPath);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/sessions') {
|
||||
handleSessions();
|
||||
prompt();
|
||||
return;
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/help') {
|
||||
handleHelp();
|
||||
prompt();
|
||||
return;
|
||||
if (line.startsWith('/model')) {
|
||||
const model = line.replace('/model', '').trim();
|
||||
if (!model) {
|
||||
console.log(`Current model: ${state.model}`);
|
||||
} else {
|
||||
state.model = model;
|
||||
console.log(`Model set to: ${state.model}`);
|
||||
}
|
||||
return prompt();
|
||||
}
|
||||
|
||||
// Regular message — send to Claude
|
||||
const history = loadHistory(sessionPath);
|
||||
appendTurn(sessionPath, 'User', line);
|
||||
const response = askClaude(eccContext, history, line);
|
||||
console.log('\n' + response + '\n');
|
||||
appendTurn(sessionPath, 'Assistant', response);
|
||||
if (line.startsWith('/load ')) {
|
||||
const skill = line.replace('/load', '').trim();
|
||||
if (!skill) {
|
||||
console.log('Usage: /load <skill-name>');
|
||||
return prompt();
|
||||
}
|
||||
if (!skillExists(skill)) {
|
||||
console.log(`Skill not found: ${skill}`);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (!state.skills.includes(skill)) {
|
||||
state.skills.push(skill);
|
||||
}
|
||||
eccContext = loadECCContext(state.skills);
|
||||
console.log(`Loaded skill: ${skill}`);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line.startsWith('/branch ')) {
|
||||
const target = line.replace('/branch', '').trim();
|
||||
const result = branchSession(state.sessionPath, target);
|
||||
if (!result.ok) {
|
||||
console.log(result.message);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
state.sessionName = result.session;
|
||||
state.sessionPath = result.path;
|
||||
console.log(`Branched to session: ${state.sessionName}`);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line.startsWith('/search ')) {
|
||||
const query = line.replace('/search', '').trim();
|
||||
const matches = searchSessions(query);
|
||||
if (matches.length === 0) {
|
||||
console.log('(no matches)');
|
||||
return prompt();
|
||||
}
|
||||
console.log(`Found ${matches.length} match(es):`);
|
||||
for (const match of matches) {
|
||||
console.log(`- ${match.session}: ${match.snippet}`);
|
||||
}
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/compact') {
|
||||
const changed = compactSession(state.sessionPath);
|
||||
console.log(changed ? 'Session compacted.' : 'No compaction needed.');
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line.startsWith('/export ')) {
|
||||
const parts = line.split(/\s+/).filter(Boolean);
|
||||
const format = parts[1];
|
||||
const outputPath = parts[2];
|
||||
if (!format) {
|
||||
console.log('Usage: /export <md|json|txt> [path]');
|
||||
return prompt();
|
||||
}
|
||||
const result = exportSession(state.sessionPath, format, outputPath);
|
||||
if (!result.ok) {
|
||||
console.log(result.message);
|
||||
} else {
|
||||
console.log(`Exported: ${result.path}`);
|
||||
}
|
||||
return prompt();
|
||||
}
|
||||
|
||||
if (line === '/metrics') {
|
||||
const m = getSessionMetrics(state.sessionPath);
|
||||
console.log(`Session: ${state.sessionName}`);
|
||||
console.log(`Model: ${state.model}`);
|
||||
console.log(`Turns: ${m.turns} (user ${m.userTurns}, assistant ${m.assistantTurns})`);
|
||||
console.log(`Chars: ${m.charCount}`);
|
||||
console.log(`Estimated tokens: ${m.tokenEstimate}`);
|
||||
return prompt();
|
||||
}
|
||||
|
||||
// Regular message
|
||||
const history = loadHistory(state.sessionPath);
|
||||
appendTurn(state.sessionPath, 'User', line);
|
||||
const response = askClaude(eccContext, history, line, state.model);
|
||||
console.log(`\n${response}\n`);
|
||||
appendTurn(state.sessionPath, 'Assistant', response);
|
||||
prompt();
|
||||
});
|
||||
};
|
||||
@@ -243,8 +439,6 @@ function main() {
|
||||
prompt();
|
||||
}
|
||||
|
||||
// ─── Exports & CLI Entry ────────────────────────────────────────────────────
|
||||
|
||||
module.exports = {
|
||||
getClawDir,
|
||||
getSessionPath,
|
||||
@@ -252,14 +446,21 @@ module.exports = {
|
||||
loadHistory,
|
||||
appendTurn,
|
||||
loadECCContext,
|
||||
askClaude,
|
||||
buildPrompt,
|
||||
askClaude,
|
||||
isValidSessionName,
|
||||
handleClear,
|
||||
handleHistory,
|
||||
handleSessions,
|
||||
handleHelp,
|
||||
main
|
||||
parseTurns,
|
||||
estimateTokenCount,
|
||||
getSessionMetrics,
|
||||
searchSessions,
|
||||
compactSession,
|
||||
exportSession,
|
||||
branchSession,
|
||||
main,
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
12
scripts/hooks/check-hook-enabled.js
Executable file
12
scripts/hooks/check-hook-enabled.js
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const { isHookEnabled } = require('../lib/hook-flags');
|
||||
|
||||
const [, , hookId, profilesCsv] = process.argv;
|
||||
if (!hookId) {
|
||||
process.stdout.write('yes');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.stdout.write(isHookEnabled(hookId, { profiles: profilesCsv }) ? 'yes' : 'no');
|
||||
78
scripts/hooks/cost-tracker.js
Executable file
78
scripts/hooks/cost-tracker.js
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Cost Tracker Hook
|
||||
*
|
||||
* Appends lightweight session usage metrics to ~/.claude/metrics/costs.jsonl.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const {
|
||||
ensureDir,
|
||||
appendFile,
|
||||
getClaudeDir,
|
||||
} = require('../lib/utils');
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
function toNumber(value) {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function estimateCost(model, inputTokens, outputTokens) {
|
||||
// Approximate per-1M-token blended rates. Conservative defaults.
|
||||
const table = {
|
||||
'haiku': { in: 0.8, out: 4.0 },
|
||||
'sonnet': { in: 3.0, out: 15.0 },
|
||||
'opus': { in: 15.0, out: 75.0 },
|
||||
};
|
||||
|
||||
const normalized = String(model || '').toLowerCase();
|
||||
let rates = table.sonnet;
|
||||
if (normalized.includes('haiku')) rates = table.haiku;
|
||||
if (normalized.includes('opus')) rates = table.opus;
|
||||
|
||||
const cost = (inputTokens / 1_000_000) * rates.in + (outputTokens / 1_000_000) * rates.out;
|
||||
return Math.round(cost * 1e6) / 1e6;
|
||||
}
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = raw.trim() ? JSON.parse(raw) : {};
|
||||
const usage = input.usage || input.token_usage || {};
|
||||
const inputTokens = toNumber(usage.input_tokens || usage.prompt_tokens || 0);
|
||||
const outputTokens = toNumber(usage.output_tokens || usage.completion_tokens || 0);
|
||||
|
||||
const model = String(input.model || input._cursor?.model || process.env.CLAUDE_MODEL || 'unknown');
|
||||
const sessionId = String(process.env.CLAUDE_SESSION_ID || 'default');
|
||||
|
||||
const metricsDir = path.join(getClaudeDir(), 'metrics');
|
||||
ensureDir(metricsDir);
|
||||
|
||||
const row = {
|
||||
timestamp: new Date().toISOString(),
|
||||
session_id: sessionId,
|
||||
model,
|
||||
input_tokens: inputTokens,
|
||||
output_tokens: outputTokens,
|
||||
estimated_cost_usd: estimateCost(model, inputTokens, outputTokens),
|
||||
};
|
||||
|
||||
appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
|
||||
} catch {
|
||||
// Keep hook non-blocking.
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
@@ -1,28 +1,63 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Doc file warning hook (PreToolUse - Write)
|
||||
* Warns about non-standard documentation files.
|
||||
* Exit code 0 always (warns only, never blocks).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let data = '';
|
||||
process.stdin.on('data', c => (data += c));
|
||||
|
||||
function isAllowedDocPath(filePath) {
|
||||
const normalized = filePath.replace(/\\/g, '/');
|
||||
const basename = path.basename(filePath);
|
||||
|
||||
if (!/\.(md|txt)$/i.test(filePath)) return true;
|
||||
|
||||
if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL|MEMORY|WORKLOG)\.md$/i.test(basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\.claude\/(commands|plans|projects)\//.test(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/(^|\/)(docs|skills|\.history|memory)\//.test(normalized)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/\.plan\.md$/i.test(basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', c => {
|
||||
if (data.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - data.length;
|
||||
data += c.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(data);
|
||||
const filePath = input.tool_input?.file_path || '';
|
||||
const filePath = String(input.tool_input?.file_path || '');
|
||||
|
||||
if (
|
||||
/\.(md|txt)$/.test(filePath) &&
|
||||
!/(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\.md$/i.test(filePath) &&
|
||||
!/\.claude[/\\]plans[/\\]/.test(filePath) &&
|
||||
!/(^|[/\\])(docs|skills|\.history)[/\\]/.test(filePath)
|
||||
) {
|
||||
if (filePath && !isAllowedDocPath(filePath)) {
|
||||
console.error('[Hook] WARNING: Non-standard documentation file detected');
|
||||
console.error('[Hook] File: ' + filePath);
|
||||
console.error(`[Hook] File: ${filePath}`);
|
||||
console.error('[Hook] Consider consolidating into README.md or docs/ directory');
|
||||
}
|
||||
} catch {
|
||||
/* ignore parse errors */
|
||||
// ignore parse errors
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
27
scripts/hooks/post-bash-build-complete.js
Executable file
27
scripts/hooks/post-bash-build-complete.js
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = String(input.tool_input?.command || '');
|
||||
if (/(npm run build|pnpm build|yarn build)/.test(cmd)) {
|
||||
console.error('[Hook] Build completed - async analysis running in background');
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors and pass through
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
36
scripts/hooks/post-bash-pr-created.js
Executable file
36
scripts/hooks/post-bash-pr-created.js
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = String(input.tool_input?.command || '');
|
||||
|
||||
if (/\bgh\s+pr\s+create\b/.test(cmd)) {
|
||||
const out = String(input.tool_output?.output || '');
|
||||
const match = out.match(/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/);
|
||||
if (match) {
|
||||
const prUrl = match[0];
|
||||
const repo = prUrl.replace(/https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/\d+/, '$1');
|
||||
const prNum = prUrl.replace(/.+\/pull\/(\d+)/, '$1');
|
||||
console.error(`[Hook] PR created: ${prUrl}`);
|
||||
console.error(`[Hook] To review: gh pr review ${prNum} --repo ${repo}`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors and pass through
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
73
scripts/hooks/pre-bash-dev-server-block.js
Executable file
73
scripts/hooks/pre-bash-dev-server-block.js
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
|
||||
function splitShellSegments(command) {
|
||||
const segments = [];
|
||||
let current = '';
|
||||
let quote = null;
|
||||
|
||||
for (let i = 0; i < command.length; i++) {
|
||||
const ch = command[i];
|
||||
if (quote) {
|
||||
if (ch === quote) quote = null;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '"' || ch === "'") {
|
||||
quote = ch;
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
const next = command[i + 1] || '';
|
||||
if (ch === ';' || (ch === '&' && next === '&') || (ch === '|' && next === '|') || (ch === '&' && next !== '&')) {
|
||||
if (current.trim()) segments.push(current.trim());
|
||||
current = '';
|
||||
if ((ch === '&' && next === '&') || (ch === '|' && next === '|')) i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
current += ch;
|
||||
}
|
||||
|
||||
if (current.trim()) segments.push(current.trim());
|
||||
return segments;
|
||||
}
|
||||
|
||||
let raw = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = String(input.tool_input?.command || '');
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
const segments = splitShellSegments(cmd);
|
||||
const tmuxLauncher = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/;
|
||||
const devPattern = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn\s+dev|bun\s+run\s+dev)\b/;
|
||||
|
||||
const hasBlockedDev = segments.some(segment => devPattern.test(segment) && !tmuxLauncher.test(segment));
|
||||
|
||||
if (hasBlockedDev) {
|
||||
console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');
|
||||
console.error('[Hook] Use: tmux new-session -d -s dev "npm run dev"');
|
||||
console.error('[Hook] Then: tmux attach -t dev');
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors and pass through
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
28
scripts/hooks/pre-bash-git-push-reminder.js
Executable file
28
scripts/hooks/pre-bash-git-push-reminder.js
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = String(input.tool_input?.command || '');
|
||||
if (/\bgit\s+push\b/.test(cmd)) {
|
||||
console.error('[Hook] Review changes before push...');
|
||||
console.error('[Hook] Continuing with push (remove this hook to add interactive review)');
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors and pass through
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
33
scripts/hooks/pre-bash-tmux-reminder.js
Executable file
33
scripts/hooks/pre-bash-tmux-reminder.js
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const cmd = String(input.tool_input?.command || '');
|
||||
|
||||
if (
|
||||
process.platform !== 'win32' &&
|
||||
!process.env.TMUX &&
|
||||
/(npm (install|test)|pnpm (install|test)|yarn (install|test)?|bun (install|test)|cargo build|make\b|docker\b|pytest|vitest|playwright)/.test(cmd)
|
||||
) {
|
||||
console.error('[Hook] Consider running in tmux for session persistence');
|
||||
console.error('[Hook] tmux new -s dev | tmux attach -t dev');
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors and pass through
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
@@ -1,61 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* PreToolUse Hook: Warn about non-standard documentation files
|
||||
*
|
||||
* Cross-platform (Windows, macOS, Linux)
|
||||
*
|
||||
* Runs before Write tool use. If the file is a .md or .txt file that isn't
|
||||
* a standard documentation file (README, CLAUDE, AGENTS, etc.) or in an
|
||||
* expected directory (docs/, skills/, .claude/plans/), warns the user.
|
||||
*
|
||||
* Exit code 0 — warn only, does not block.
|
||||
* Backward-compatible doc warning hook entrypoint.
|
||||
* Kept for consumers that still reference pre-write-doc-warn.js directly.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
'use strict';
|
||||
|
||||
const MAX_STDIN = 1024 * 1024; // 1MB limit
|
||||
let data = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', chunk => {
|
||||
if (data.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - data.length;
|
||||
data += chunk.length > remaining ? chunk.slice(0, remaining) : chunk;
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(data);
|
||||
const filePath = input.tool_input?.file_path || '';
|
||||
|
||||
// Only check .md and .txt files
|
||||
if (!/\.(md|txt)$/.test(filePath)) {
|
||||
process.stdout.write(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow standard documentation files
|
||||
const basename = path.basename(filePath);
|
||||
if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\.md$/i.test(basename)) {
|
||||
process.stdout.write(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow files in .claude/plans/, docs/, and skills/ directories
|
||||
const normalized = filePath.replace(/\\/g, '/');
|
||||
if (/\.claude\/plans\//.test(normalized) || /(^|\/)(docs|skills)\//.test(normalized)) {
|
||||
process.stdout.write(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warn about non-standard documentation files
|
||||
console.error('[Hook] WARNING: Non-standard documentation file detected');
|
||||
console.error('[Hook] File: ' + filePath);
|
||||
console.error('[Hook] Consider consolidating into README.md or docs/ directory');
|
||||
} catch {
|
||||
// Parse error — pass through
|
||||
}
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
require('./doc-file-warning.js');
|
||||
|
||||
98
scripts/hooks/quality-gate.js
Executable file
98
scripts/hooks/quality-gate.js
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Quality Gate Hook
|
||||
*
|
||||
* Runs lightweight quality checks after file edits.
|
||||
* - Targets one file when file_path is provided
|
||||
* - Falls back to no-op when language/tooling is unavailable
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const MAX_STDIN = 1024 * 1024;
|
||||
let raw = '';
|
||||
|
||||
function run(command, args, cwd = process.cwd()) {
|
||||
return spawnSync(command, args, {
|
||||
cwd,
|
||||
encoding: 'utf8',
|
||||
env: process.env,
|
||||
});
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
process.stderr.write(`${msg}\n`);
|
||||
}
|
||||
|
||||
function maybeRunQualityGate(filePath) {
|
||||
if (!filePath || !fs.existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const fix = String(process.env.ECC_QUALITY_GATE_FIX || '').toLowerCase() === 'true';
|
||||
const strict = String(process.env.ECC_QUALITY_GATE_STRICT || '').toLowerCase() === 'true';
|
||||
|
||||
if (['.ts', '.tsx', '.js', '.jsx', '.json', '.md'].includes(ext)) {
|
||||
// Prefer biome if present
|
||||
if (fs.existsSync(path.join(process.cwd(), 'biome.json')) || fs.existsSync(path.join(process.cwd(), 'biome.jsonc'))) {
|
||||
const args = ['biome', 'check', filePath];
|
||||
if (fix) args.push('--write');
|
||||
const result = run('npx', args);
|
||||
if (result.status !== 0 && strict) {
|
||||
log(`[QualityGate] Biome check failed for ${filePath}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to prettier when installed
|
||||
const prettierArgs = ['prettier', '--check', filePath];
|
||||
if (fix) {
|
||||
prettierArgs[1] = '--write';
|
||||
}
|
||||
const prettier = run('npx', prettierArgs);
|
||||
if (prettier.status !== 0 && strict) {
|
||||
log(`[QualityGate] Prettier check failed for ${filePath}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ext === '.go' && fix) {
|
||||
run('gofmt', ['-w', filePath]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ext === '.py') {
|
||||
const args = ['format'];
|
||||
if (!fix) args.push('--check');
|
||||
args.push(filePath);
|
||||
const r = run('ruff', args);
|
||||
if (r.status !== 0 && strict) {
|
||||
log(`[QualityGate] Ruff check failed for ${filePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', chunk => {
|
||||
if (raw.length < MAX_STDIN) {
|
||||
const remaining = MAX_STDIN - raw.length;
|
||||
raw += chunk.substring(0, remaining);
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
const input = JSON.parse(raw);
|
||||
const filePath = String(input.tool_input?.file_path || '');
|
||||
maybeRunQualityGate(filePath);
|
||||
} catch {
|
||||
// Ignore parse errors.
|
||||
}
|
||||
|
||||
process.stdout.write(raw);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user