mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
Compare commits
12 Commits
fix/codex-
...
fix/stop-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
284d2995ba | ||
|
|
dd675d4258 | ||
|
|
db12d3d838 | ||
|
|
46f37ae4fb | ||
|
|
0c166e14da | ||
|
|
527c79350c | ||
|
|
bec1ebf76d | ||
|
|
be76918850 | ||
|
|
99a154a908 | ||
|
|
ebf0f135bb | ||
|
|
2d27da52e2 | ||
|
|
65c4a0f6ba |
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -34,10 +34,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node }}
|
- name: Setup Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache npm
|
- name: Cache npm
|
||||||
if: matrix.pm == 'npm'
|
if: matrix.pm == 'npm'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache pnpm
|
- name: Cache pnpm
|
||||||
if: matrix.pm == 'pnpm'
|
if: matrix.pm == 'pnpm'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache yarn
|
- name: Cache yarn
|
||||||
if: matrix.pm == 'yarn'
|
if: matrix.pm == 'yarn'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache bun
|
- name: Cache bun
|
||||||
if: matrix.pm == 'bun'
|
if: matrix.pm == 'bun'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
# Upload test artifacts on failure
|
# Upload test artifacts on failure
|
||||||
- name: Upload test artifacts
|
- name: Upload test artifacts
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
|
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
|
||||||
path: |
|
path: |
|
||||||
@@ -160,10 +160,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
@@ -205,10 +205,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
@@ -223,10 +223,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
|
|||||||
10
.github/workflows/maintenance.yml
vendored
10
.github/workflows/maintenance.yml
vendored
@@ -15,8 +15,8 @@ jobs:
|
|||||||
name: Check Dependencies
|
name: Check Dependencies
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
- name: Check for outdated packages
|
- name: Check for outdated packages
|
||||||
@@ -26,8 +26,8 @@ jobs:
|
|||||||
name: Security Audit
|
name: Security Audit
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
- name: Run security audit
|
- name: Run security audit
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
name: Stale Issues/PRs
|
name: Stale Issues/PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
|
||||||
with:
|
with:
|
||||||
stale-issue-message: 'This issue is stale due to inactivity.'
|
stale-issue-message: 'This issue is stale due to inactivity.'
|
||||||
stale-pr-message: 'This PR is stale due to inactivity.'
|
stale-pr-message: 'This PR is stale due to inactivity.'
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/reusable-release.yml
vendored
2
.github/workflows/reusable-release.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/reusable-test.yml
vendored
14
.github/workflows/reusable-test.yml
vendored
@@ -27,10 +27,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.node-version }}
|
node-version: ${{ inputs.node-version }}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache npm
|
- name: Cache npm
|
||||||
if: inputs.package-manager == 'npm'
|
if: inputs.package-manager == 'npm'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ inputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache pnpm
|
- name: Cache pnpm
|
||||||
if: inputs.package-manager == 'pnpm'
|
if: inputs.package-manager == 'pnpm'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache yarn
|
- name: Cache yarn
|
||||||
if: inputs.package-manager == 'yarn'
|
if: inputs.package-manager == 'yarn'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-node-${{ inputs.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache bun
|
- name: Cache bun
|
||||||
if: inputs.package-manager == 'bun'
|
if: inputs.package-manager == 'bun'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload test artifacts
|
- name: Upload test artifacts
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
|
name: test-results-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
4
.github/workflows/reusable-validate.yml
vendored
4
.github/workflows/reusable-validate.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.node-version }}
|
node-version: ${{ inputs.node-version }}
|
||||||
|
|
||||||
|
|||||||
11
CLAUDE.md
11
CLAUDE.md
@@ -59,3 +59,14 @@ Follow the formats in CONTRIBUTING.md:
|
|||||||
- Hooks: JSON with matcher and hooks array
|
- Hooks: JSON with matcher and hooks array
|
||||||
|
|
||||||
File naming: lowercase with hyphens (e.g., `python-reviewer.md`, `tdd-workflow.md`)
|
File naming: lowercase with hyphens (e.g., `python-reviewer.md`, `tdd-workflow.md`)
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
Use the following skills when working on related files:
|
||||||
|
|
||||||
|
| File(s) | Skill |
|
||||||
|
|---------|-------|
|
||||||
|
| `README.md` | `/readme` |
|
||||||
|
| `.github/workflows/*.yml` | `/ci-workflow` |
|
||||||
|
|
||||||
|
When spawning subagents, always pass conventions from the respective skill into the agent's prompt.
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ everything-claude-code/
|
|||||||
| |-- plugin.json # Plugin metadata and component paths
|
| |-- plugin.json # Plugin metadata and component paths
|
||||||
| |-- marketplace.json # Marketplace catalog for /plugin marketplace add
|
| |-- marketplace.json # Marketplace catalog for /plugin marketplace add
|
||||||
|
|
|
|
||||||
|-- agents/ # 29 specialized subagents for delegation
|
|-- agents/ # 30 specialized subagents for delegation
|
||||||
| |-- planner.md # Feature implementation planning
|
| |-- planner.md # Feature implementation planning
|
||||||
| |-- architect.md # System design decisions
|
| |-- architect.md # System design decisions
|
||||||
| |-- tdd-guide.md # Test-driven development
|
| |-- tdd-guide.md # Test-driven development
|
||||||
|
|||||||
@@ -259,7 +259,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:check-console-log\" \"scripts/hooks/check-console-log.js\" \"standard,strict\""
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:check-console-log','scripts/hooks/check-console-log.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Check for console.log in modified files after each response"
|
"description": "Check for console.log in modified files after each response"
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:session-end\" \"scripts/hooks/session-end.js\" \"minimal,standard,strict\"",
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:session-end','scripts/hooks/session-end.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
|
||||||
"async": true,
|
"async": true,
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
}
|
}
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:evaluate-session\" \"scripts/hooks/evaluate-session.js\" \"minimal,standard,strict\"",
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:evaluate-session','scripts/hooks/evaluate-session.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
|
||||||
"async": true,
|
"async": true,
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:cost-tracker\" \"scripts/hooks/cost-tracker.js\" \"minimal,standard,strict\"",
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:cost-tracker','scripts/hooks/cost-tracker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
|
||||||
"async": true,
|
"async": true,
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:desktop-notify\" \"scripts/hooks/desktop-notify.js\" \"standard,strict\"",
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[Stop] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'stop:desktop-notify','scripts/hooks/desktop-notify.js','standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[Stop] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
|
||||||
"async": true,
|
"async": true,
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
}
|
}
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"session:end:marker\" \"scripts/hooks/session-end-marker.js\" \"minimal,standard,strict\"",
|
"command": "node -e \"const fs=require('fs');const p=require('path');const {spawnSync}=require('child_process');const raw=fs.readFileSync(0,'utf8');const root=(()=>{var e=process.env.CLAUDE_PLUGIN_ROOT;if(e&&e.trim())return e.trim();var p=require('path'),f=require('fs'),h=require('os').homedir(),d=p.join(h,'.claude'),q=p.join('scripts','lib','utils.js');if(f.existsSync(p.join(d,q)))return d;for(var l of [p.join(d,'plugins','everything-claude-code'),p.join(d,'plugins','everything-claude-code@everything-claude-code'),p.join(d,'plugins','marketplace','everything-claude-code')])if(f.existsSync(p.join(l,q)))return l;try{var b=p.join(d,'plugins','cache','everything-claude-code');for(var o of f.readdirSync(b,{withFileTypes:true})){if(!o.isDirectory())continue;for(var v of f.readdirSync(p.join(b,o.name),{withFileTypes:true})){if(!v.isDirectory())continue;var c=p.join(b,o.name,v.name);if(f.existsSync(p.join(c,q)))return c}}}catch(x){}return d})();const script=p.join(root,'scripts','hooks','run-with-flags.js');if(!fs.existsSync(script)){process.stderr.write('[SessionEnd] WARNING: could not resolve ECC plugin root; skipping hook'+String.fromCharCode(10));process.stdout.write(raw);process.exit(0);}const result=spawnSync(process.execPath,[script,'session:end:marker','scripts/hooks/session-end-marker.js','minimal,standard,strict'],{input:raw,encoding:'utf8',env:process.env,cwd:process.cwd(),timeout:30000});if(typeof result.stdout==='string'&&result.stdout)process.stdout.write(result.stdout);if(result.stderr)process.stderr.write(result.stderr);if(result.error||result.status===null||result.signal){const reason=result.error?result.error.message:(result.signal?'signal '+result.signal:'missing exit status');process.stderr.write('[SessionEnd] ERROR: inline hook resolver failed: '+reason+String.fromCharCode(10));process.exit(1);}process.exit(Number.isInteger(result.status)?result.status:0);\"",
|
||||||
"async": true,
|
"async": true,
|
||||||
"timeout": 10
|
"timeout": 10
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,20 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Skip checks for branch deletion pushes (e.g., git push origin --delete <branch>).
|
||||||
|
# The pre-push hook receives lines on stdin: <local ref> <local sha> <remote ref> <remote sha>.
|
||||||
|
# For deletions, the local sha is the zero OID.
|
||||||
|
is_delete_only=true
|
||||||
|
while read -r _local_ref local_sha _remote_ref _remote_sha; do
|
||||||
|
if [[ "$local_sha" != "0000000000000000000000000000000000000000" ]]; then
|
||||||
|
is_delete_only=false
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$is_delete_only" == "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
ran_any_check=0
|
ran_any_check=0
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ PYTHON_CMD="${CLV2_PYTHON_CMD:-}"
|
|||||||
# ─────────────────────────────────────────────
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
CONFIG_DIR="${HOME}/.claude/homunculus"
|
CONFIG_DIR="${HOME}/.claude/homunculus"
|
||||||
CONFIG_FILE="${SKILL_ROOT}/config.json"
|
if [ -n "${CLV2_CONFIG:-}" ]; then
|
||||||
|
CONFIG_FILE="$CLV2_CONFIG"
|
||||||
|
else
|
||||||
|
CONFIG_FILE="${SKILL_ROOT}/config.json"
|
||||||
|
fi
|
||||||
# PID file is project-scoped so each project can have its own observer
|
# PID file is project-scoped so each project can have its own observer
|
||||||
PID_FILE="${PROJECT_DIR}/.observer.pid"
|
PID_FILE="${PROJECT_DIR}/.observer.pid"
|
||||||
LOG_FILE="${PROJECT_DIR}/observer.log"
|
LOG_FILE="${PROJECT_DIR}/observer.log"
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
---
|
---
|
||||||
name: token-budget-advisor
|
name: token-budget-advisor
|
||||||
description: >-
|
description: >-
|
||||||
Intercepts the response flow to offer the user an informed choice about
|
Offers the user an informed choice about how much response depth to
|
||||||
how much depth/tokens to consume — BEFORE responding. Use this skill
|
consume before answering. Use this skill when the user explicitly
|
||||||
when the user wants to control token consumption, adjust response depth,
|
wants to control response length, depth, or token budget.
|
||||||
choose between short/long answers, or optimize their prompt.
|
TRIGGER when: "token budget", "token count", "token usage", "token limit",
|
||||||
TRIGGER when: "token budget", "response token budget", "token count",
|
"response length", "answer depth", "short version", "brief answer",
|
||||||
"token usage", "response length", "response depth", "brief answer",
|
"detailed answer", "exhaustive answer", "respuesta corta vs larga",
|
||||||
"short answer", "detailed answer", "full answer",
|
"cuántos tokens", "ahorrar tokens", "responde al 50%", "dame la versión
|
||||||
"respuesta corta vs larga", "cuántos tokens", "ahorrar tokens",
|
corta", "quiero controlar cuánto usas", or clear variants where the
|
||||||
"responde al 50%", "dame la versión corta", "quiero controlar cuánto usas",
|
user is explicitly asking to control answer size or depth.
|
||||||
"75%", "100%", "at 25%", "at 50%", "at 75%", "at 100%",
|
|
||||||
"give me the full answer", or any variant where the user wants
|
|
||||||
to control length, depth, or token usage — even without mentioning tokens.
|
|
||||||
DO NOT TRIGGER when: user has already specified a level in the current
|
DO NOT TRIGGER when: user has already specified a level in the current
|
||||||
session (maintain it) or the request is clearly a one-word answer.
|
session (maintain it), the request is clearly a one-word answer, or
|
||||||
|
"token" refers to auth/session/payment tokens rather than response size.
|
||||||
origin: community
|
origin: community
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,12 +33,14 @@ Intercept the response flow to offer the user a choice about response depth **be
|
|||||||
|
|
||||||
### Step 1 — Estimate input tokens
|
### Step 1 — Estimate input tokens
|
||||||
|
|
||||||
Use the repository's canonical estimation guidance from `skills/context-budget`.
|
Use the repository's canonical context-budget heuristics to estimate the prompt's token count mentally.
|
||||||
|
|
||||||
- Prose-first prompts: `input_tokens ≈ word_count × 1.3`
|
Use the same calibration guidance as [context-budget](../context-budget/SKILL.md):
|
||||||
- Code-heavy or mixed prompts: `input_tokens ≈ char_count / 4`
|
|
||||||
|
|
||||||
For mixed content, prefer the code-heavy estimate as the conservative default.
|
- prose: `words × 1.3`
|
||||||
|
- code-heavy or mixed/code blocks: `chars / 4`
|
||||||
|
|
||||||
|
For mixed content, use the dominant content type and keep the estimate heuristic.
|
||||||
|
|
||||||
### Step 2 — Estimate response size by complexity
|
### Step 2 — Estimate response size by complexity
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ Choose your depth level:
|
|||||||
[3] Detailed (75%) -> ~[tokens] Full answer with alternatives
|
[3] Detailed (75%) -> ~[tokens] Full answer with alternatives
|
||||||
[4] Exhaustive (100%) -> ~[tokens] Everything, no limits
|
[4] Exhaustive (100%) -> ~[tokens] Everything, no limits
|
||||||
|
|
||||||
Which level? (1-4 or say "25%", "50%", "75%", "100%")
|
Which level? (1-4 or say "25% depth", "50% depth", "75% depth", "100% depth")
|
||||||
|
|
||||||
Precision: heuristic estimate ~85-90% accuracy (±15%).
|
Precision: heuristic estimate ~85-90% accuracy (±15%).
|
||||||
```
|
```
|
||||||
@@ -98,10 +98,10 @@ If the user already signals a level, respond at that level immediately without a
|
|||||||
|
|
||||||
| What they say | Level |
|
| What they say | Level |
|
||||||
|----------------------------------------------------|-------|
|
|----------------------------------------------------|-------|
|
||||||
| "1" / "25%" / "short answer" / "brief" / "tldr" / "one-liner" | 25% |
|
| "1" / "25% depth" / "short version" / "brief answer" / "tldr" | 25% |
|
||||||
| "2" / "50%" / "moderate detail" / "balanced answer" | 50% |
|
| "2" / "50% depth" / "moderate depth" / "balanced answer" | 50% |
|
||||||
| "3" / "75%" / "detailed answer" / "thorough explanation" | 75% |
|
| "3" / "75% depth" / "detailed answer" / "thorough answer" | 75% |
|
||||||
| "4" / "100%" / "exhaustive" / "everything" / "full answer" | 100% |
|
| "4" / "100% depth" / "exhaustive answer" / "full deep dive" | 100% |
|
||||||
|
|
||||||
If the user set a level earlier in the session, **maintain it silently** for subsequent responses unless they change it.
|
If the user set a level earlier in the session, **maintain it silently** for subsequent responses unless they change it.
|
||||||
|
|
||||||
@@ -113,19 +113,21 @@ This skill uses heuristic estimation — no real tokenizer. Accuracy ~85-90%, va
|
|||||||
|
|
||||||
### Triggers
|
### Triggers
|
||||||
|
|
||||||
- "Give me the brief answer first."
|
- "Give me the short version first."
|
||||||
- "How many tokens will your response use?"
|
- "How many tokens will your answer use?"
|
||||||
- "Respond at 50% depth."
|
- "Respond at 50% depth."
|
||||||
- "I want the full answer."
|
- "I want the exhaustive answer, not the summary."
|
||||||
- "Dame la version corta."
|
- "Dame la version corta y luego la detallada."
|
||||||
|
|
||||||
### Does Not Trigger
|
### Does Not Trigger
|
||||||
|
|
||||||
- "Explain OAuth token refresh flow." (`token` here is domain language, not a budget request)
|
- "What is a JWT token?"
|
||||||
- "Why is this JWT token invalid?" (security/domain usage, not response sizing)
|
- "The checkout flow uses a payment token."
|
||||||
- "What is 2 + 2?" (trivially short answer)
|
- "Is this normal?"
|
||||||
|
- "Complete the refactor."
|
||||||
|
- Follow-up questions after the user already chose a depth for the session
|
||||||
|
|
||||||
## Source
|
## Source
|
||||||
|
|
||||||
Standalone skill from [TBA — Token Budget Advisor for Claude Code](https://github.com/Xabilimon1/Token-Budget-Advisor-Claude-Code-).
|
Standalone skill from [TBA — Token Budget Advisor for Claude Code](https://github.com/Xabilimon1/Token-Budget-Advisor-Claude-Code-).
|
||||||
The upstream project includes an optional estimator script, but this ECC skill intentionally stays zero-dependency and heuristic-only.
|
Original project also ships a Python estimator script, but this repository keeps the skill self-contained and heuristic-only.
|
||||||
|
|||||||
@@ -1965,7 +1965,29 @@ async function runTests() {
|
|||||||
passed++;
|
passed++;
|
||||||
else failed++;
|
else failed++;
|
||||||
if (
|
if (
|
||||||
test('script references use CLAUDE_PLUGIN_ROOT variable or safe SessionStart inline resolver', () => {
|
test('Stop and SessionEnd hooks use safe inline resolvers when plugin root may be unset', () => {
|
||||||
|
const hooksPath = path.join(__dirname, '..', '..', 'hooks', 'hooks.json');
|
||||||
|
const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf8'));
|
||||||
|
|
||||||
|
const lifecycleHooks = [
|
||||||
|
...(hooks.hooks.Stop || []).flatMap(entry => entry.hooks || []),
|
||||||
|
...(hooks.hooks.SessionEnd || []).flatMap(entry => entry.hooks || []),
|
||||||
|
].filter(hook => hook.type === 'command');
|
||||||
|
|
||||||
|
assert.ok(lifecycleHooks.length > 0, 'Should define Stop/SessionEnd command hooks');
|
||||||
|
|
||||||
|
for (const hook of lifecycleHooks) {
|
||||||
|
assert.ok(hook.command.startsWith('node -e "'), `Expected inline node resolver: ${hook.command.substring(0, 80)}...`);
|
||||||
|
assert.ok(hook.command.includes('run-with-flags.js'), 'Inline resolver should invoke run-with-flags.js');
|
||||||
|
assert.ok(hook.command.includes('CLAUDE_PLUGIN_ROOT'), 'Inline resolver should consult CLAUDE_PLUGIN_ROOT');
|
||||||
|
assert.ok(hook.command.includes('plugins'), 'Inline resolver should probe known plugin roots');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
passed++;
|
||||||
|
else failed++;
|
||||||
|
if (
|
||||||
|
test('script references use CLAUDE_PLUGIN_ROOT variable or safe inline resolvers', () => {
|
||||||
const hooksPath = path.join(__dirname, '..', '..', 'hooks', 'hooks.json');
|
const hooksPath = path.join(__dirname, '..', '..', 'hooks', 'hooks.json');
|
||||||
const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf8'));
|
const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf8'));
|
||||||
|
|
||||||
@@ -1973,9 +1995,8 @@ async function runTests() {
|
|||||||
for (const entry of hookArray) {
|
for (const entry of hookArray) {
|
||||||
for (const hook of entry.hooks) {
|
for (const hook of entry.hooks) {
|
||||||
if (hook.type === 'command' && hook.command.includes('scripts/hooks/')) {
|
if (hook.type === 'command' && hook.command.includes('scripts/hooks/')) {
|
||||||
// Check for the literal string "${CLAUDE_PLUGIN_ROOT}" in the command
|
const isInlineResolver = hook.command.startsWith('node -e') && hook.command.includes('run-with-flags.js');
|
||||||
const isSessionStartInlineResolver = hook.command.startsWith('node -e') && hook.command.includes('session:start') && hook.command.includes('run-with-flags.js');
|
const hasPluginRoot = hook.command.includes('${CLAUDE_PLUGIN_ROOT}') || isInlineResolver;
|
||||||
const hasPluginRoot = hook.command.includes('${CLAUDE_PLUGIN_ROOT}') || isSessionStartInlineResolver;
|
|
||||||
assert.ok(hasPluginRoot, `Script paths should use CLAUDE_PLUGIN_ROOT: ${hook.command.substring(0, 80)}...`);
|
assert.ok(hasPluginRoot, `Script paths should use CLAUDE_PLUGIN_ROOT: ${hook.command.substring(0, 80)}...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,6 +179,17 @@ function cleanupTestDir(testDir) {
|
|||||||
fs.rmSync(testDir, { recursive: true, force: true });
|
fs.rmSync(testDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stagePluginRuntime(homeDir, relativePaths) {
|
||||||
|
const pluginRoot = path.join(homeDir, '.claude', 'plugins', 'everything-claude-code');
|
||||||
|
for (const relativePath of relativePaths) {
|
||||||
|
const sourcePath = path.join(REPO_ROOT, relativePath);
|
||||||
|
const destinationPath = path.join(pluginRoot, relativePath);
|
||||||
|
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
||||||
|
fs.copyFileSync(sourcePath, destinationPath);
|
||||||
|
}
|
||||||
|
return pluginRoot;
|
||||||
|
}
|
||||||
|
|
||||||
function getHookCommandByDescription(hooks, lifecycle, descriptionText) {
|
function getHookCommandByDescription(hooks, lifecycle, descriptionText) {
|
||||||
const hookGroup = hooks.hooks[lifecycle]?.find(
|
const hookGroup = hooks.hooks[lifecycle]?.find(
|
||||||
entry => entry.description && entry.description.includes(descriptionText)
|
entry => entry.description && entry.description.includes(descriptionText)
|
||||||
@@ -267,6 +278,35 @@ async function runTests() {
|
|||||||
assert.strictEqual(payload.hookSpecificOutput.hookEventName, 'SessionStart');
|
assert.strictEqual(payload.hookSpecificOutput.hookEventName, 'SessionStart');
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (await asyncTest('Stop hook resolves plugin root when CLAUDE_PLUGIN_ROOT is unset', async () => {
|
||||||
|
const homeDir = createTestDir();
|
||||||
|
try {
|
||||||
|
stagePluginRuntime(homeDir, [
|
||||||
|
path.join('scripts', 'hooks', 'run-with-flags.js'),
|
||||||
|
path.join('scripts', 'hooks', 'check-console-log.js'),
|
||||||
|
path.join('scripts', 'lib', 'hook-flags.js'),
|
||||||
|
path.join('scripts', 'lib', 'utils.js'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hookCommand = getHookCommandByDescription(
|
||||||
|
hooks,
|
||||||
|
'Stop',
|
||||||
|
'Check for console.log in modified files after each response'
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await runHookCommand(hookCommand, {}, {
|
||||||
|
HOME: homeDir,
|
||||||
|
CLAUDE_PLUGIN_ROOT: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(result.code, 0, `Expected stop hook to exit 0, got ${result.code}: ${result.stderr}`);
|
||||||
|
assert.ok(!result.stderr.includes("Cannot find module '/scripts/hooks/run-with-flags.js'"), 'Should not resolve to the filesystem root');
|
||||||
|
assert.ok(!result.stderr.includes('could not resolve ECC plugin root'), 'Should discover the plugin root from known install paths');
|
||||||
|
} finally {
|
||||||
|
cleanupTestDir(homeDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (await asyncTest('PreCompact hook logs to stderr', async () => {
|
if (await asyncTest('PreCompact hook logs to stderr', async () => {
|
||||||
const result = await runHookWithInput(path.join(scriptsDir, 'pre-compact.js'), {});
|
const result = await runHookWithInput(path.join(scriptsDir, 'pre-compact.js'), {});
|
||||||
assert.ok(result.stderr.includes('[PreCompact]'), 'Should output to stderr with prefix');
|
assert.ok(result.stderr.includes('[PreCompact]'), 'Should output to stderr with prefix');
|
||||||
|
|||||||
Reference in New Issue
Block a user