mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
* fix: resolve PostToolUse hook crashes and 5s latency (#1220) Three compounding bugs caused hook failures: 1. Missing break statements in worker-service.ts switch — if async code threw before process.exit(), execution fell through to subsequent cases. Added break to all 7 cases missing them. 2. Unhandled promise rejection on main() — added .catch() that logs the error and exits 0 (per project exit code strategy: don't block Claude Code or leave Windows Terminal tabs open). 3. Redundant start commands in hooks.json — PostToolUse, UserPromptSubmit, and Stop groups each had a standalone start command that was redundant (the hook case already calls ensureWorkerStarted internally). The redundant start also caused 5s latency via bun-runner.js collectStdin() timeout since Claude Code never closes stdin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add CLAUDE_PLUGIN_ROOT fallback for Stop hooks (#1215) Upstream Claude Code bug (anthropics/claude-code#24529) leaves CLAUDE_PLUGIN_ROOT unset for Stop hooks on macOS and ALL hooks on Linux. Two-layer defense: 1. Shell-level: hooks.json commands now use inline fallback _R="${CLAUDE_PLUGIN_ROOT}"; [ -z "$_R" ] && _R="$HOME/..."; falling back to the known marketplace install path. 2. Script-level: bun-runner.js self-resolves plugin root from its own filesystem location via import.meta.url, and fixes broken /scripts/... paths that result from empty expansion. Added test to verify all hook commands include the fallback path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import { describe, it, expect } from 'bun:test';
|
|
import { readFileSync, existsSync } from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const projectRoot = path.resolve(__dirname, '../..');
|
|
|
|
/**
|
|
* Regression tests for plugin distribution completeness.
|
|
* Ensures all required files (skills, hooks, manifests) are present
|
|
* and correctly structured for end-user installs.
|
|
*
|
|
* Prevents issue #1187 (missing skills/ directory after install).
|
|
*/
|
|
describe('Plugin Distribution - Skills', () => {
|
|
const skillPath = path.join(projectRoot, 'plugin/skills/mem-search/SKILL.md');
|
|
|
|
it('should include plugin/skills/mem-search/SKILL.md', () => {
|
|
expect(existsSync(skillPath)).toBe(true);
|
|
});
|
|
|
|
it('should have valid YAML frontmatter with name and description', () => {
|
|
const content = readFileSync(skillPath, 'utf-8');
|
|
|
|
// Must start with YAML frontmatter
|
|
expect(content.startsWith('---\n')).toBe(true);
|
|
|
|
// Extract frontmatter
|
|
const frontmatterEnd = content.indexOf('\n---\n', 4);
|
|
expect(frontmatterEnd).toBeGreaterThan(0);
|
|
|
|
const frontmatter = content.slice(4, frontmatterEnd);
|
|
expect(frontmatter).toContain('name:');
|
|
expect(frontmatter).toContain('description:');
|
|
});
|
|
|
|
it('should reference the 3-layer search workflow', () => {
|
|
const content = readFileSync(skillPath, 'utf-8');
|
|
// The skill must document the search → timeline → get_observations workflow
|
|
expect(content).toContain('search');
|
|
expect(content).toContain('timeline');
|
|
expect(content).toContain('get_observations');
|
|
});
|
|
});
|
|
|
|
describe('Plugin Distribution - Required Files', () => {
|
|
const requiredFiles = [
|
|
'plugin/hooks/hooks.json',
|
|
'plugin/.claude-plugin/plugin.json',
|
|
'plugin/skills/mem-search/SKILL.md',
|
|
];
|
|
|
|
for (const filePath of requiredFiles) {
|
|
it(`should include ${filePath}`, () => {
|
|
const fullPath = path.join(projectRoot, filePath);
|
|
expect(existsSync(fullPath)).toBe(true);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('Plugin Distribution - hooks.json Integrity', () => {
|
|
it('should have valid JSON in hooks.json', () => {
|
|
const hooksPath = path.join(projectRoot, 'plugin/hooks/hooks.json');
|
|
const content = readFileSync(hooksPath, 'utf-8');
|
|
const parsed = JSON.parse(content);
|
|
expect(parsed.hooks).toBeDefined();
|
|
});
|
|
|
|
it('should reference CLAUDE_PLUGIN_ROOT in all hook commands', () => {
|
|
const hooksPath = path.join(projectRoot, 'plugin/hooks/hooks.json');
|
|
const parsed = JSON.parse(readFileSync(hooksPath, 'utf-8'));
|
|
|
|
for (const [eventName, matchers] of Object.entries(parsed.hooks)) {
|
|
for (const matcher of matchers as any[]) {
|
|
for (const hook of matcher.hooks) {
|
|
if (hook.type === 'command') {
|
|
expect(hook.command).toContain('${CLAUDE_PLUGIN_ROOT}');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should include CLAUDE_PLUGIN_ROOT fallback in all hook commands (#1215)', () => {
|
|
const hooksPath = path.join(projectRoot, 'plugin/hooks/hooks.json');
|
|
const parsed = JSON.parse(readFileSync(hooksPath, 'utf-8'));
|
|
const expectedFallbackPath = '$HOME/.claude/plugins/marketplaces/thedotmack/plugin';
|
|
|
|
for (const [eventName, matchers] of Object.entries(parsed.hooks)) {
|
|
for (const matcher of matchers as any[]) {
|
|
for (const hook of matcher.hooks) {
|
|
if (hook.type === 'command') {
|
|
expect(hook.command).toContain(expectedFallbackPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Plugin Distribution - package.json Files Field', () => {
|
|
it('should include "plugin" in root package.json files field', () => {
|
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
expect(packageJson.files).toBeDefined();
|
|
expect(packageJson.files).toContain('plugin');
|
|
});
|
|
});
|
|
|
|
describe('Plugin Distribution - Build Script Verification', () => {
|
|
it('should verify distribution files in build-hooks.js', () => {
|
|
const buildScriptPath = path.join(projectRoot, 'scripts/build-hooks.js');
|
|
const content = readFileSync(buildScriptPath, 'utf-8');
|
|
|
|
// Build script must check for critical distribution files
|
|
expect(content).toContain('plugin/skills/mem-search/SKILL.md');
|
|
expect(content).toContain('plugin/hooks/hooks.json');
|
|
expect(content).toContain('plugin/.claude-plugin/plugin.json');
|
|
});
|
|
});
|