mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 10:53:34 +08:00
fix: harden claude plugin manifest surfaces
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
||||
"name": "ecc",
|
||||
"description": "Battle-tested Claude Code configurations from an Anthropic hackathon winner — agents, skills, hooks, rules, and legacy command shims evolved over 10+ months of intensive daily use",
|
||||
"owner": {
|
||||
"name": "Affaan Mustafa",
|
||||
"email": "me@affaanmustafa.com"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: Quick commit with natural language file targeting — describe what to commit in plain English
|
||||
argument-hint: [target description] (blank = all changes)
|
||||
description: "Quick commit with natural language file targeting — describe what to commit in plain English"
|
||||
argument-hint: "[target description] (blank = all changes)"
|
||||
---
|
||||
|
||||
# Smart Commit
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: Create a GitHub PR from current branch with unpushed commits — discovers templates, analyzes changes, pushes
|
||||
argument-hint: [base-branch] (default: main)
|
||||
description: "Create a GitHub PR from current branch with unpushed commits — discovers templates, analyzes changes, pushes"
|
||||
argument-hint: "[base-branch] (default: main)"
|
||||
---
|
||||
|
||||
# Create Pull Request
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
description: Interactive PRD generator - problem-first, hypothesis-driven product spec with back-and-forth questioning
|
||||
argument-hint: [feature/product idea] (blank = start with questions)
|
||||
description: "Interactive PRD generator - problem-first, hypothesis-driven product spec with back-and-forth questioning"
|
||||
argument-hint: "[feature/product idea] (blank = start with questions)"
|
||||
---
|
||||
|
||||
# Product Requirements Document Generator
|
||||
|
||||
@@ -12,6 +12,53 @@ const COMMANDS_DIR = path.join(ROOT_DIR, 'commands');
|
||||
const AGENTS_DIR = path.join(ROOT_DIR, 'agents');
|
||||
const SKILLS_DIR = path.join(ROOT_DIR, 'skills');
|
||||
|
||||
function validateFrontmatter(file, content) {
|
||||
if (!content.startsWith('---\n')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const endIndex = content.indexOf('\n---\n', 4);
|
||||
if (endIndex === -1) {
|
||||
return [`${file} - frontmatter block is missing a closing --- delimiter`];
|
||||
}
|
||||
|
||||
const block = content.slice(4, endIndex);
|
||||
const errors = [];
|
||||
|
||||
for (const rawLine of block.split('\n')) {
|
||||
const line = rawLine.trim();
|
||||
if (!line || line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
||||
if (!match) {
|
||||
errors.push(`${file} - invalid frontmatter line: ${rawLine}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = match[2].trim();
|
||||
const isQuoted = (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
);
|
||||
|
||||
if (!isQuoted && value.startsWith('[') && !value.endsWith(']')) {
|
||||
errors.push(
|
||||
`${file} - frontmatter value for "${match[1]}" starts with "[" but is not a closed YAML sequence; wrap it in quotes`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isQuoted && value.startsWith('{') && !value.endsWith('}')) {
|
||||
errors.push(
|
||||
`${file} - frontmatter value for "${match[1]}" starts with "{" but is not a closed YAML mapping; wrap it in quotes`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function validateCommands() {
|
||||
if (!fs.existsSync(COMMANDS_DIR)) {
|
||||
console.log('No commands directory found, skipping validation');
|
||||
@@ -68,6 +115,11 @@ function validateCommands() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const error of validateFrontmatter(file, content)) {
|
||||
console.error(`ERROR: ${error}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Strip fenced code blocks before checking cross-references.
|
||||
// Examples/templates inside ``` blocks are not real references.
|
||||
const contentNoCodeBlocks = content.replace(/```[\s\S]*?```/g, '');
|
||||
|
||||
@@ -68,6 +68,7 @@ function assertSafeRepoRelativePath(relativePath, label) {
|
||||
console.log('\n=== .claude-plugin/plugin.json ===\n');
|
||||
|
||||
const claudePluginPath = path.join(repoRoot, '.claude-plugin', 'plugin.json');
|
||||
const claudeMarketplacePath = path.join(repoRoot, '.claude-plugin', 'marketplace.json');
|
||||
|
||||
test('claude plugin.json exists', () => {
|
||||
assert.ok(fs.existsSync(claudePluginPath), 'Expected .claude-plugin/plugin.json to exist');
|
||||
@@ -131,6 +132,30 @@ test('claude plugin.json does NOT have explicit hooks declaration', () => {
|
||||
);
|
||||
});
|
||||
|
||||
console.log('\n=== .claude-plugin/marketplace.json ===\n');
|
||||
|
||||
test('claude marketplace.json exists', () => {
|
||||
assert.ok(fs.existsSync(claudeMarketplacePath), 'Expected .claude-plugin/marketplace.json to exist');
|
||||
});
|
||||
|
||||
const claudeMarketplace = loadJsonObject(claudeMarketplacePath, '.claude-plugin/marketplace.json');
|
||||
|
||||
test('claude marketplace.json keeps only Claude-supported top-level keys', () => {
|
||||
const unsupportedTopLevelKeys = ['$schema', 'description'];
|
||||
for (const key of unsupportedTopLevelKeys) {
|
||||
assert.ok(
|
||||
!(key in claudeMarketplace),
|
||||
`.claude-plugin/marketplace.json must not declare unsupported top-level key "${key}"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('claude marketplace.json has plugins array with a short ecc plugin entry', () => {
|
||||
assert.ok(Array.isArray(claudeMarketplace.plugins) && claudeMarketplace.plugins.length > 0, 'Expected plugins array');
|
||||
assert.strictEqual(claudeMarketplace.name, 'ecc');
|
||||
assert.strictEqual(claudeMarketplace.plugins[0].name, 'ecc');
|
||||
});
|
||||
|
||||
// ── Codex plugin manifest ─────────────────────────────────────────────────────
|
||||
// Per official docs: https://platform.openai.com/docs/codex/plugins
|
||||
// - .codex-plugin/plugin.json is the required manifest
|
||||
|
||||
Reference in New Issue
Block a user