mirror of https://github.com/garrytan/gstack.git
Merge 8234ba1263 into a3259400a3
This commit is contained in:
commit
32965df418
|
|
@ -14,7 +14,7 @@ hooks:
|
||||||
- matcher: "Bash"
|
- matcher: "Bash"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking for destructive commands..."
|
statusMessage: "Checking for destructive commands..."
|
||||||
---
|
---
|
||||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ hooks:
|
||||||
- matcher: "Bash"
|
- matcher: "Bash"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking for destructive commands..."
|
statusMessage: "Checking for destructive commands..."
|
||||||
sensitive: true
|
sensitive: true
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
---
|
---
|
||||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
sensitive: true
|
sensitive: true
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ hooks:
|
||||||
- matcher: "Bash"
|
- matcher: "Bash"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking for destructive commands..."
|
statusMessage: "Checking for destructive commands..."
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
---
|
---
|
||||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,17 @@ hooks:
|
||||||
- matcher: "Bash"
|
- matcher: "Bash"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/careful/bin/check-careful.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/careful/bin/check-careful.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking for destructive commands..."
|
statusMessage: "Checking for destructive commands..."
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: "bash $HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking freeze boundary..."
|
statusMessage: "Checking freeze boundary..."
|
||||||
sensitive: true
|
sensitive: true
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
gbrain:
|
gbrain:
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ hooks:
|
||||||
- matcher: "Edit"
|
- matcher: "Edit"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
- matcher: "Write"
|
- matcher: "Write"
|
||||||
hooks:
|
hooks:
|
||||||
- type: command
|
- type: command
|
||||||
command: 'bash -c ''S="${CLAUDE_SKILL_DIR}/../freeze/bin/check-freeze.sh"; [ -x "$S" ] || S="${CLAUDE_SKILL_DIR}/../gstack-freeze/bin/check-freeze.sh"; [ -x "$S" ] && bash "$S" || exit 0'''
|
command: 'bash -c ''D="$PWD"; while :; do S="$D/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; [ "$D" = "/" ] && break; D="$(dirname "$D")"; done; S="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"; [ -x "$S" ] && exec bash "$S"; exit 0'''
|
||||||
statusMessage: "Checking debug scope boundary..."
|
statusMessage: "Checking debug scope boundary..."
|
||||||
gbrain:
|
gbrain:
|
||||||
schema: 1
|
schema: 1
|
||||||
|
|
|
||||||
|
|
@ -1841,7 +1841,7 @@ describe('Codex generation (--host codex)', () => {
|
||||||
expect(frontmatter).toContain('YC Office Hours');
|
expect(frontmatter).toContain('YC Office Hours');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hook skills have safety prose and no hooks: in frontmatter', () => {
|
test('Codex hook skills have safety prose and no hooks: in frontmatter', () => {
|
||||||
const HOOK_SKILLS = ['gstack-careful', 'gstack-freeze', 'gstack-guard'];
|
const HOOK_SKILLS = ['gstack-careful', 'gstack-freeze', 'gstack-guard'];
|
||||||
for (const skillName of HOOK_SKILLS) {
|
for (const skillName of HOOK_SKILLS) {
|
||||||
const content = fs.readFileSync(path.join(AGENTS_DIR, skillName, 'SKILL.md'), 'utf-8');
|
const content = fs.readFileSync(path.join(AGENTS_DIR, skillName, 'SKILL.md'), 'utf-8');
|
||||||
|
|
@ -1854,6 +1854,59 @@ describe('Codex generation (--host codex)', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Claude hook commands do not depend on CLAUDE_SKILL_DIR', () => {
|
||||||
|
// #1871: Claude Code 2.1.162 does not populate CLAUDE_SKILL_DIR for
|
||||||
|
// skill-frontmatter PreToolUse hooks. If these commands use that variable,
|
||||||
|
// they expand to paths like /../freeze/bin/check-freeze.sh and fail before
|
||||||
|
// the safety hook can do its job.
|
||||||
|
const HOOK_SKILLS = ['careful', 'freeze', 'guard', 'investigate'];
|
||||||
|
for (const skillName of HOOK_SKILLS) {
|
||||||
|
const content = fs.readFileSync(path.join(ROOT, skillName, 'SKILL.md'), 'utf-8');
|
||||||
|
const fmEnd = content.indexOf('\n---', 4);
|
||||||
|
const frontmatter = content.slice(4, fmEnd);
|
||||||
|
expect(frontmatter).toContain('hooks:');
|
||||||
|
expect(frontmatter).not.toContain('CLAUDE_SKILL_DIR');
|
||||||
|
expect(frontmatter).not.toContain('CLAUDE_PLUGIN_ROOT');
|
||||||
|
expect(frontmatter).not.toContain('git rev-parse --show-toplevel');
|
||||||
|
expect(frontmatter).toContain('while :; do');
|
||||||
|
expect(frontmatter).toContain('$D/.claude/skills/gstack/');
|
||||||
|
expect(frontmatter).toContain('$HOME/.claude/skills/gstack/');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Claude hook commands resolve project-local install from nested non-git cwd', () => {
|
||||||
|
const { execFileSync } = require('child_process');
|
||||||
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-hook-local-'));
|
||||||
|
const projectRoot = path.join(tmpRoot, 'project');
|
||||||
|
const nestedCwd = path.join(projectRoot, 'nested', 'plain-dir');
|
||||||
|
const scriptPath = path.join(projectRoot, '.claude', 'skills', 'gstack', 'careful', 'bin', 'check-careful.sh');
|
||||||
|
|
||||||
|
fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
|
||||||
|
fs.mkdirSync(nestedCwd, { recursive: true });
|
||||||
|
fs.writeFileSync(scriptPath, '#!/usr/bin/env bash\nprintf local-careful');
|
||||||
|
fs.chmodSync(scriptPath, 0o755);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(path.join(ROOT, 'careful', 'SKILL.md'), 'utf-8');
|
||||||
|
const fmEnd = content.indexOf('\n---', 4);
|
||||||
|
const frontmatter = content.slice(4, fmEnd);
|
||||||
|
const commandLine = frontmatter.split('\n').find(line => line.trim().startsWith('command: '));
|
||||||
|
expect(commandLine).toBeTruthy();
|
||||||
|
let command = commandLine!.trim().slice('command: '.length);
|
||||||
|
expect(command.startsWith("'")).toBe(true);
|
||||||
|
command = command.slice(1, -1).replaceAll("''", "'");
|
||||||
|
|
||||||
|
const output = execFileSync('bash', ['-c', command], {
|
||||||
|
cwd: nestedCwd,
|
||||||
|
input: '{}',
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
expect(output).toBe('local-careful');
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('all Codex SKILL.md files have auto-generated header', () => {
|
test('all Codex SKILL.md files have auto-generated header', () => {
|
||||||
for (const skill of CODEX_SKILLS) {
|
for (const skill of CODEX_SKILLS) {
|
||||||
const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8');
|
const content = fs.readFileSync(path.join(AGENTS_DIR, skill.codexName, 'SKILL.md'), 'utf-8');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue