mirror of https://github.com/garrytan/gstack.git
fix: remove legacy team hook on migration
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
472f1b3df5
commit
5f1e896f90
|
|
@ -215,6 +215,13 @@ HOOK_EOF
|
|||
" 2>/dev/null
|
||||
GENERATED+=(".claude/settings.json")
|
||||
echo " + .claude/settings.json — PreToolUse hook registered"
|
||||
|
||||
# Remove the obsolete Bash hook only after its replacement is generated and
|
||||
# registered successfully. Other project hooks are left untouched.
|
||||
if [ -e "$HOOKS_DIR/check-gstack.sh" ] || [ -L "$HOOKS_DIR/check-gstack.sh" ]; then
|
||||
rm "$HOOKS_DIR/check-gstack.sh"
|
||||
echo " - .claude/hooks/check-gstack.sh — removed legacy enforcement hook"
|
||||
fi
|
||||
else
|
||||
echo " ! bun not found — manually add the PreToolUse hook to .claude/settings.json"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<li>The generated hook resolves the user home in <code>HOME</code>, <code>USERPROFILE</code>, then <code>os.homedir()</code> order so POSIX, Git Bash, PowerShell, and cmd runners use the expected global install root.</li>
|
||||
<li>The hook contract follows <code>docs/spikes/claude-code-hook-mutation.md</code>: pass-through writes <code>{}</code>; denial writes a <code>hookSpecificOutput</code> object for <code>PreToolUse</code> with <code>permissionDecision: "deny"</code>.</li>
|
||||
<li>Missing or unreadable gstack is an intentional denial with exit code zero. Install instructions are present in stderr and <code>permissionDecisionReason</code>, so the runner does not report a hook execution error.</li>
|
||||
<li>Rerunning required mode replaces a matching legacy <code>check-gstack.sh</code> registration and removes duplicate matching registrations.</li>
|
||||
<li>Rerunning required mode replaces a matching legacy <code>check-gstack.sh</code> registration, removes duplicate matching registrations, and deletes the obsolete Bash hook only after the CommonJS hook is generated and registered successfully. Unrelated project hooks remain unchanged.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Deviations</h2>
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ describe('gstack-team-init', () => {
|
|||
});
|
||||
|
||||
expectStructuredDeny(result, MISSING_PROJECT_REASON);
|
||||
});
|
||||
}, 30_000);
|
||||
|
||||
test('required: stale project paths deny through the platform default shell', () => {
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
|
|
@ -445,7 +445,7 @@ describe('gstack-team-init', () => {
|
|||
expectStructuredDeny(result, HOOK_LOAD_REASON);
|
||||
expect(result.stderr).not.toContain(projectDir);
|
||||
}
|
||||
});
|
||||
}, 30_000);
|
||||
|
||||
test('required: hook runs in PowerShell with USERPROFILE home fallback', () => {
|
||||
if (process.platform !== 'win32') return;
|
||||
|
|
@ -472,7 +472,7 @@ describe('gstack-team-init', () => {
|
|||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stderr).toBe('');
|
||||
expect(JSON.parse(result.stdout)).toEqual({});
|
||||
});
|
||||
}, 30_000);
|
||||
|
||||
test('required: missing gstack returns an intentional deny, not a hook error', () => {
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
|
|
@ -507,10 +507,14 @@ describe('gstack-team-init', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('required: rerun upgrades a legacy Bash hook without duplicates', () => {
|
||||
test('required: rerun removes a legacy Bash hook without touching other hooks', () => {
|
||||
const hooksDir = path.join(tmpDir, '.claude', 'hooks');
|
||||
const legacyHook = path.join(hooksDir, 'check-gstack.sh');
|
||||
const unrelatedHook = path.join(hooksDir, 'check-project.cjs');
|
||||
const unrelatedHookContents = "console.log('project hook');\n";
|
||||
fs.mkdirSync(hooksDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(hooksDir, 'check-gstack.sh'), '#!/bin/bash\n');
|
||||
fs.writeFileSync(legacyHook, '#!/bin/bash\n');
|
||||
fs.writeFileSync(unrelatedHook, unrelatedHookContents);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.claude', 'settings.json'),
|
||||
JSON.stringify({
|
||||
|
|
@ -527,17 +531,31 @@ describe('gstack-team-init', () => {
|
|||
},
|
||||
}),
|
||||
);
|
||||
execSync('git add .claude', { cwd: tmpDir });
|
||||
execSync('git commit -m "add legacy hook"', { cwd: tmpDir });
|
||||
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
expect(fs.existsSync(legacyHook)).toBe(false);
|
||||
expect(fs.readFileSync(unrelatedHook, 'utf-8')).toBe(unrelatedHookContents);
|
||||
|
||||
run(`${TEAM_INIT} required`, { cwd: tmpDir });
|
||||
const settings = JSON.parse(
|
||||
fs.readFileSync(path.join(tmpDir, '.claude', 'settings.json'), 'utf-8'),
|
||||
);
|
||||
expect(fs.existsSync(legacyHook)).toBe(false);
|
||||
expect(fs.readFileSync(unrelatedHook, 'utf-8')).toBe(unrelatedHookContents);
|
||||
expect(
|
||||
execSync('git status --short -- .claude/hooks/check-gstack.sh', {
|
||||
cwd: tmpDir,
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
).toBe(' D .claude/hooks/check-gstack.sh\n');
|
||||
expect(settings.hooks.PreToolUse).toHaveLength(1);
|
||||
expect(settings.hooks.PreToolUse[0].hooks).toHaveLength(1);
|
||||
expect(settings.hooks.PreToolUse[0].hooks[0].command).toContain(
|
||||
'check-gstack.cjs',
|
||||
);
|
||||
});
|
||||
}, 30_000);
|
||||
|
||||
test('idempotent: running twice does not duplicate CLAUDE.md section', () => {
|
||||
run(`${TEAM_INIT} optional`, { cwd: tmpDir });
|
||||
|
|
|
|||
Loading…
Reference in New Issue