diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..d89207d --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "edgeone-makers", + "owner": { + "name": "EdgeOne" + }, + "description": "EdgeOne Makers platform development skills marketplace.", + "plugins": [ + { + "name": "edgeone-makers-tools", + "source": "./", + "description": "EdgeOne Makers platform development skills — AI Agents, Cloud Functions, Edge Functions, Storage, Middleware, and Deployment.", + "version": "1.0.0", + "author": { + "name": "EdgeOne" + }, + "category": "edge-one", + "tags": ["edgeone", "makers", "edge-functions", "cloud-functions", "agents", "deployment"] + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..a125659 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "edgeone-makers-tools", + "description": "EdgeOne Makers platform development skills — AI Agents, Cloud Functions, Edge Functions, Storage, Middleware, and Deployment.", + "version": "1.0.0", + "author": { + "name": "EdgeOne" + }, + "hooks": "./hooks/hooks.json" +} diff --git a/.codebuddy-plugin/marketplace.json b/.codebuddy-plugin/marketplace.json new file mode 100644 index 0000000..b0d7768 --- /dev/null +++ b/.codebuddy-plugin/marketplace.json @@ -0,0 +1,30 @@ +{ + "name": "edgeone-makers", + "owner": { + "name": "EdgeOne" + }, + "description": "EdgeOne Makers platform development skills marketplace.", + "plugins": [ + { + "name": "edgeone-makers-tools", + "source": "./", + "description": "EdgeOne Makers platform development skills — AI Agents, Cloud Functions, Edge Functions, Storage, Middleware, and Deployment.", + "version": "1.0.0", + "author": { + "name": "EdgeOne" + }, + "strict": false, + "skills": [ + "./skills/makers-agents", + "./skills/makers-deploy", + "./skills/makers-edge-functions", + "./skills/makers-cloud-functions", + "./skills/makers-storage", + "./skills/makers-middleware", + "./skills/makers-cli", + "./skills/makers-recipes" + ], + "hooks": "./hooks/hooks.json" + } + ] +} diff --git a/.cursor-plugin/manifest.json b/.cursor-plugin/manifest.json deleted file mode 100644 index 9e7ad70..0000000 --- a/.cursor-plugin/manifest.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "edgeone-makers-tools", - "description": "EdgeOne Makers platform development skills — AI Agents, Cloud Functions, Edge Functions, Storage, Middleware, and Deployment.", - "version": "1.0.0", - "author": "EdgeOne", - "skills": [ - "skills/makers-agents", - "skills/makers-deploy", - "skills/makers-edge-functions", - "skills/makers-cloud-functions", - "skills/makers-storage", - "skills/makers-middleware", - "skills/makers-cli", - "skills/makers-recipes" - ] -} diff --git a/.claude-plugin/manifest.json b/.cursor-plugin/plugin.json similarity index 78% rename from .claude-plugin/manifest.json rename to .cursor-plugin/plugin.json index 39d81d2..67296b5 100644 --- a/.claude-plugin/manifest.json +++ b/.cursor-plugin/plugin.json @@ -2,6 +2,7 @@ "name": "edgeone-makers-tools", "description": "EdgeOne Makers platform development skills — AI Agents, Cloud Functions, Edge Functions, Storage, Middleware, and Deployment.", "version": "1.0.0", - "author": "EdgeOne", - "hooks": "../hooks/hooks.json" + "author": { + "name": "EdgeOne" + } } diff --git a/.gitignore b/.gitignore index 02b9779..5664071 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ docs/ # Export script and output scripts/export-clean.sh -edgeone-makers-tools-clean/ \ No newline at end of file +edgeone-makers-tools-clean/ + +# EdgeOne Makers hook signal log and state +.edgeone/signal-log.jsonl diff --git a/README.md b/README.md index ec46f32..2808053 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ internally). > [`BRANCH.md`](https://github.com/TencentEdgeOne/edgeone-makers-tools/blob/skillhub/BRANCH.md) > on the `skillhub` branch for the maintenance flow. +### Option C — Claude Code plugin marketplace + +```text +/plugin marketplace add TencentEdgeOne/edgeone-makers-tools +/plugin install edgeone-makers-tools@edgeone-makers +``` + After installation, your AI coding agent will automatically detect relevant tasks and load the right skill. ## Skills diff --git a/hooks/hooks.json b/hooks/hooks.json index 3b009c8..70fc38c 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -1,37 +1,13 @@ { "hooks": { - "UserPromptSubmit": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/on-prompt.sh", - "timeout": 5 - } - ] - } - ], "PreToolUse": [ { - "matcher": "Write|Edit", - "hooks": [ - { - "type": "command", - "if": "Write(agents/*)|Edit(agents/*)", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/on-agent-write.sh", - "timeout": 5 - } - ] - }, - { - "matcher": "Bash", + "matcher": "Edit|Write|replace_in_file|write_to_file", "hooks": [ { "type": "command", - "if": "Bash(edgeone *)", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/on-edgeone-cmd.sh", - "timeout": 5 + "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/validate-write.mjs\"", + "timeout": 3 } ] } diff --git a/hooks/on-agent-write.sh b/hooks/on-agent-write.sh deleted file mode 100644 index db9f3df..0000000 --- a/hooks/on-agent-write.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# PreToolUse hook: when writing to agents/ directory, remind about platform rules -set -euo pipefail - -jq -n '{ - hookSpecificOutput: { - hookEventName: "PreToolUse", - additionalContext: "Writing to agents/ directory. Ensure: 1) Entry is `export async function onRequest(context)` (Node) or `async def handler(ctx)` (Python). 2) Use context.env, never process.env/os.environ. 3) Use context.conversation_id directly. 4) For tools: Claude SDK uses toClaudeMcpServer(), OpenAI Agents uses all(), LangGraph/DeepAgents uses toLangChainTools(tool), CrewAI uses toCrewAITools(BaseTool)." - } -}' diff --git a/hooks/on-edgeone-cmd.sh b/hooks/on-edgeone-cmd.sh deleted file mode 100644 index 5d6cc41..0000000 --- a/hooks/on-edgeone-cmd.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# PreToolUse hook: when running edgeone CLI commands, ensure PAGES_SOURCE is set -set -euo pipefail - -INPUT=$(cat) -COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""') - -# Check if PAGES_SOURCE is missing from the command -if echo "$COMMAND" | grep -q "edgeone" && ! echo "$COMMAND" | grep -q "PAGES_SOURCE"; then - jq -n '{ - hookSpecificOutput: { - hookEventName: "PreToolUse", - additionalContext: "Running edgeone CLI command. Ensure PAGES_SOURCE=skills is set (export PAGES_SOURCE=skills or prefix the command)." - } - }' -else - exit 0 -fi diff --git a/hooks/on-prompt.sh b/hooks/on-prompt.sh deleted file mode 100644 index 45f9509..0000000 --- a/hooks/on-prompt.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# UserPromptSubmit hook: detect agent-related prompts and inject skill routing context -set -euo pipefail - -INPUT=$(cat) -PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""') - -# Check if the prompt mentions agent/AI development keywords -if echo "$PROMPT" | grep -qiE 'agent|makers|deepagent|langgraph|crewai|openai.?agents|claude.?sdk|context\.store|context\.tools|context\.sandbox|edgeone.*deploy|SSE.*stream'; then - jq -n '{ - hookSpecificOutput: { - hookEventName: "UserPromptSubmit", - additionalContext: "This project uses EdgeOne Makers for AI Agent development. Read skills/makers-agents/SKILL.md for platform conventions, then read the matching framework reference based on the decision tree." - } - }' -else - exit 0 -fi diff --git a/hooks/signal-log.mjs b/hooks/signal-log.mjs new file mode 100644 index 0000000..d9e7210 --- /dev/null +++ b/hooks/signal-log.mjs @@ -0,0 +1,43 @@ +import { appendFileSync, mkdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +const REQUIRED_SIGNAL_FIELDS = ['hook', 'trigger', 'matchedSkill', 'reason']; + +export function defaultSignalLogPath(env = process.env, cwd = process.cwd()) { + return env.EDGEONE_MAKERS_SIGNAL_LOG || join(cwd, '.edgeone', 'signal-log.jsonl'); +} + +function normalizeSignalLogEntry(entry, now = new Date()) { + for (const field of REQUIRED_SIGNAL_FIELDS) { + if (!entry?.[field]) { + throw new Error(`Missing signal log field: ${field}`); + } + } + + const normalized = { + timestamp: now.toISOString(), + hook: entry.hook, + trigger: entry.trigger, + matchedSkill: entry.matchedSkill, + reason: entry.reason, + }; + + if (entry.platform) normalized.platform = entry.platform; + if (entry.toolName) normalized.toolName = entry.toolName; + + return normalized; +} + +export function shouldWriteSignalLog(options = {}) { + return Boolean(options.enableSignalLog || options.signalLogPath); +} + +export function writeSignalLog(entry, options = {}) { + const normalized = normalizeSignalLogEntry(entry, options.now); + const logPath = options.logPath || options.signalLogPath || defaultSignalLogPath(); + + mkdirSync(dirname(logPath), { recursive: true }); + appendFileSync(logPath, `${JSON.stringify(normalized)}\n`); + + return normalized; +} diff --git a/hooks/signal-log.test.mjs b/hooks/signal-log.test.mjs new file mode 100644 index 0000000..a5431c6 --- /dev/null +++ b/hooks/signal-log.test.mjs @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; + +import { writeSignalLog } from './signal-log.mjs'; + +test('plugin-skill-injection-optimization.SIGNAL_LOGGING.1 appends signal entries to JSONL', async () => { + const tmp = await mkdtemp(join(tmpdir(), 'makers-signal-log-')); + const logPath = join(tmp, '.edgeone', 'signal-log.jsonl'); + + try { + writeSignalLog( + { + hook: 'PreToolUse', + trigger: 'pathPatterns', + matchedSkill: 'makers-edge-functions', + reason: 'functions/index.ts matched functions/**', + platform: 'claude-code', + toolName: 'Read', + }, + { logPath, now: new Date('2026-06-24T00:00:00.000Z') }, + ); + + const [line] = (await readFile(logPath, 'utf8')).trim().split('\n'); + + assert.deepEqual(JSON.parse(line), { + timestamp: '2026-06-24T00:00:00.000Z', + hook: 'PreToolUse', + trigger: 'pathPatterns', + matchedSkill: 'makers-edge-functions', + reason: 'functions/index.ts matched functions/**', + platform: 'claude-code', + toolName: 'Read', + }); + } finally { + await rm(tmp, { recursive: true, force: true }); + } +}); + +test('plugin-skill-injection-optimization.SIGNAL_LOGGING.2 requires the core signal fields', () => { + assert.throws( + () => + writeSignalLog({ + hook: 'PreToolUse', + trigger: 'pathPatterns', + matchedSkill: 'makers-edge-functions', + }), + /Missing signal log field: reason/, + ); +}); diff --git a/hooks/validate-write.mjs b/hooks/validate-write.mjs new file mode 100644 index 0000000..4df7b1d --- /dev/null +++ b/hooks/validate-write.mjs @@ -0,0 +1,230 @@ +#!/usr/bin/env node +import { readFileSync, readdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +import { shouldWriteSignalLog, writeSignalLog } from './signal-log.mjs'; + +const HOOKS_DIR = dirname(fileURLToPath(import.meta.url)); +const DEFAULT_SKILLS_DIR = join(HOOKS_DIR, '..', 'skills'); +const WRITE_TOOL_NAMES = new Set(['Edit', 'Write', 'replace_in_file', 'write_to_file']); +const WRITE_CONTENT_KEYS = ['content', 'new_string', 'new_str', 'newString', 'text']; +const PATH_KEYS = ['file_path', 'filePath', 'path', 'target_file']; + +let cachedRules = null; + +function escapeRegExp(value) { + return value.replace(/[|\\{}()[\]^$+?.*]/g, '\\$&'); +} + +function globToRegExp(pattern) { + const source = String(pattern) + .replace(/\\/g, '/') + .split('/') + .map((segment) => (segment === '**' ? '.*' : escapeRegExp(segment).replace(/\\\*/g, '[^/]*'))) + .join('/'); + return new RegExp(`(^|/)${source}$`); +} + +function parseFrontmatter(content) { + const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content); + return match ? match[1] : ''; +} + +function parseYamlScalar(value) { + const trimmed = String(value || '').trim(); + if (!trimmed) return ''; + if (trimmed.startsWith('"') && trimmed.endsWith('"')) { + try { + return JSON.parse(trimmed); + } catch { + return trimmed.slice(1, -1); + } + } + if (trimmed.startsWith("'") && trimmed.endsWith("'")) { + return trimmed.slice(1, -1).replace(/''/g, "'"); + } + return trimmed; +} + +function parseFrontmatterString(frontmatter, key) { + const match = new RegExp(`^${key}:\\s*(.+)$`, 'm').exec(frontmatter); + return match ? parseYamlScalar(match[1]) : ''; +} + +function parseFrontmatterList(frontmatter, key) { + const lines = frontmatter.split(/\r?\n/); + const values = []; + let inList = false; + for (const line of lines) { + if (!inList) { + inList = new RegExp(`^${key}:\\s*$`).test(line); + continue; + } + if (!line.trim()) continue; + if (/^\S/.test(line)) break; + const item = /^\s+-\s*(.+?)\s*$/.exec(line); + if (item) values.push(parseYamlScalar(item[1])); + } + return values; +} + +function assignObjectField(object, text) { + const field = /^([A-Za-z][\w-]*):\s*(.*?)\s*$/.exec(text); + if (!field) return; + object[field[1]] = parseYamlScalar(field[2]); +} + +function parseFrontmatterObjectList(frontmatter, key, requiredFields = ['pattern', 'message']) { + const lines = frontmatter.split(/\r?\n/); + const values = []; + let current = null; + let inList = false; + for (const line of lines) { + if (!inList) { + inList = new RegExp(`^${key}:\\s*$`).test(line); + continue; + } + if (!line.trim()) continue; + if (/^\S/.test(line)) break; + const item = /^\s+-\s*(.*?)\s*$/.exec(line); + if (item) { + current = {}; + values.push(current); + if (item[1]) assignObjectField(current, item[1]); + continue; + } + if (current) assignObjectField(current, line.trim()); + } + return values.filter((value) => requiredFields.every((field) => value[field])); +} + +function parseSkillValidateRule(skillPath) { + const frontmatter = parseFrontmatter(readFileSync(skillPath, 'utf8')); + const skill = parseFrontmatterString(frontmatter, 'name'); + if (!skill) return null; + const validate = parseFrontmatterObjectList(frontmatter, 'validate'); + if (validate.length === 0) return null; + return { + skill, + pathPatterns: parseFrontmatterList(frontmatter, 'pathPatterns'), + validate, + }; +} + +export function loadSkillValidateRules(skillsDir = DEFAULT_SKILLS_DIR) { + if (skillsDir === DEFAULT_SKILLS_DIR && cachedRules) return cachedRules; + const rules = readdirSync(skillsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => join(skillsDir, entry.name, 'SKILL.md')) + .map((skillPath) => parseSkillValidateRule(skillPath)) + .filter(Boolean); + if (skillsDir === DEFAULT_SKILLS_DIR) cachedRules = rules; + return rules; +} + +function getToolName(payload) { + return String(payload?.tool_name || payload?.toolName || '').trim(); +} + +function getToolInput(payload) { + return payload?.tool_input || payload?.toolInput || {}; +} + +function getToolPath(toolInput) { + const raw = PATH_KEYS.map((key) => toolInput[key]).find((value) => typeof value === 'string'); + return String(raw || '').replace(/\\/g, '/'); +} + +function getToolWriteContent(payload) { + if (!WRITE_TOOL_NAMES.has(getToolName(payload))) return ''; + const toolInput = getToolInput(payload); + for (const key of WRITE_CONTENT_KEYS) { + if (typeof toolInput[key] === 'string') return toolInput[key]; + } + return ''; +} + +function findSkillForPath(filePath, rules) { + if (!filePath) return null; + for (const rule of rules) { + if (rule.pathPatterns.some((pattern) => globToRegExp(pattern).test(filePath))) return rule; + } + return null; +} + +function selectValidationMatches(content, rule) { + const seen = new Set(); + const matches = []; + for (const item of rule.validate) { + if (new RegExp(item.pattern).test(content) && !seen.has(item.message)) { + seen.add(item.message); + matches.push(item); + } + } + return matches; +} + +function renderValidationReminder(messages) { + return `Validation reminder:\n${messages.map((message) => `- ${message}`).join('\n')}`; +} + +export function buildValidateWriteOutput(payload, options = {}) { + if (!WRITE_TOOL_NAMES.has(getToolName(payload))) return null; + const content = getToolWriteContent(payload); + if (!content) return null; + + const rule = findSkillForPath( + getToolPath(getToolInput(payload)), + options.rules || loadSkillValidateRules(), + ); + if (!rule) return null; + + const matches = selectValidationMatches(content, rule); + if (matches.length === 0) return null; + + if (shouldWriteSignalLog(options)) { + for (const match of matches) { + writeSignalLog( + { + hook: 'PreToolUse', + trigger: 'validate', + matchedSkill: rule.skill, + reason: match.message, + toolName: getToolName(payload), + }, + options, + ); + } + } + + return { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: renderValidationReminder(matches.map((match) => match.message)), + }, + }; +} + +async function readStdin() { + let input = ''; + for await (const chunk of process.stdin) { + input += chunk; + } + return input; +} + +export async function main() { + const rawInput = await readStdin(); + const payload = rawInput.trim() ? JSON.parse(rawInput) : {}; + const output = buildValidateWriteOutput(payload, { enableSignalLog: true }); + if (!output) return; + process.stdout.write(`${JSON.stringify(output, null, 2)}\n`); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + }); +} diff --git a/hooks/validate-write.test.mjs b/hooks/validate-write.test.mjs new file mode 100644 index 0000000..3c6bfe0 --- /dev/null +++ b/hooks/validate-write.test.mjs @@ -0,0 +1,202 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import test from 'node:test'; + +import { buildValidateWriteOutput, loadSkillValidateRules } from './validate-write.mjs'; + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.1 loads validate rules from Skill frontmatter', async () => { + const rules = await loadSkillValidateRules(); + const edgeFunctions = rules.find((rule) => rule.skill === 'edgeone-makers-edge-functions'); + + assert.ok(edgeFunctions); + assert.deepEqual(edgeFunctions.validate, [ + { + pattern: 'process\\.env', + message: 'Use context.env in EdgeOne Makers runtime code.', + }, + { + pattern: 'new\\s+Headers\\s*\\(', + message: 'Use plain object headers for this runtime surface.', + }, + { + pattern: 'fs\\.writeFile', + message: 'Edge Functions do not support filesystem writes.', + }, + ]); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.2 warns on Edit content without blocking writes', () => { + const output = buildValidateWriteOutput({ + tool_name: 'Edit', + tool_input: { + file_path: 'functions/index.ts', + new_string: 'export default () => process.env.API_KEY;', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Use context.env in EdgeOne Makers runtime code.', + }, + }); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.4 warns on Write content using new Headers', () => { + const output = buildValidateWriteOutput({ + tool_name: 'Write', + tool_input: { + file_path: 'functions/index.ts', + content: 'return new Response(body, { headers: new Headers() });', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Use plain object headers for this runtime surface.', + }, + }); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.5 warns on Edge Function filesystem writes', () => { + const output = buildValidateWriteOutput({ + tool_name: 'Write', + tool_input: { + file_path: 'functions/index.ts', + content: 'fs.writeFile("/tmp/out.txt", "data", () => {});', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Edge Functions do not support filesystem writes.', + }, + }); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.6 does not warn for read-only tool use', () => { + assert.equal( + buildValidateWriteOutput({ + tool_name: 'Read', + tool_input: { + file_path: 'functions/index.ts', + content: 'process.env.API_KEY', + }, + }), + null, + ); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.6 does not warn for frontend paths outside validate scope', () => { + assert.equal( + buildValidateWriteOutput({ + tool_name: 'Edit', + tool_input: { + file_path: 'src/components/Button.tsx', + new_string: 'export default () => process.env.API_KEY;', + }, + }), + null, + ); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.6 does not warn for skills without validate rules', () => { + assert.equal( + buildValidateWriteOutput({ + tool_name: 'Edit', + tool_input: { + file_path: 'agents/chat.ts', + new_string: 'const session = context.store.openaiSession(context.conversation_id);', + }, + }), + null, + ); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.6 matches edge-functions path after pathPatterns fix', () => { + const output = buildValidateWriteOutput({ + tool_name: 'Write', + tool_input: { + file_path: 'edge-functions/api/hello.js', + content: 'export default () => process.env.API_KEY;', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Use context.env in EdgeOne Makers runtime code.', + }, + }); +}); + +test('plugin-skill-injection-optimization.DOMESTIC_IDE_ADAPTATION.5 supports CodeBuddy replace_in_file validate with new_str', () => { + const output = buildValidateWriteOutput({ + tool_name: 'replace_in_file', + tool_input: { + filePath: 'functions/index.ts', + new_str: 'export default () => process.env.API_KEY;', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Use context.env in EdgeOne Makers runtime code.', + }, + }); +}); + +test('plugin-skill-injection-optimization.DOMESTIC_IDE_ADAPTATION.5 supports CodeBuddy write_to_file validate with filePath', () => { + const output = buildValidateWriteOutput({ + tool_name: 'write_to_file', + tool_input: { + filePath: 'functions/index.ts', + content: 'return new Response(body, { headers: new Headers() });', + }, + }); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: 'Validation reminder:\n- Use plain object headers for this runtime surface.', + }, + }); +}); + +test('plugin-skill-injection-optimization.SIGNAL_LOGGING.3 logs validate matches with readable reasons', async () => { + const tmp = await mkdtemp(join(tmpdir(), 'makers-validate-log-')); + const signalLogPath = join(tmp, '.edgeone', 'signal-log.jsonl'); + + try { + buildValidateWriteOutput( + { + tool_name: 'Write', + tool_input: { + file_path: 'functions/index.ts', + content: 'export default () => process.env.API_KEY;', + }, + }, + { + signalLogPath, + now: new Date('2026-07-03T00:00:00.000Z'), + }, + ); + + const [line] = (await readFile(signalLogPath, 'utf8')).trim().split('\n'); + assert.deepEqual(JSON.parse(line), { + timestamp: '2026-07-03T00:00:00.000Z', + hook: 'PreToolUse', + trigger: 'validate', + matchedSkill: 'edgeone-makers-edge-functions', + reason: 'Use context.env in EdgeOne Makers runtime code.', + toolName: 'Write', + }); + } finally { + await rm(tmp, { recursive: true, force: true }); + } +}); diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 3262b2c..748c38e 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -38,6 +38,8 @@ This skill covers five supported frameworks (DeepAgents, LangGraph, CrewAI, Open - Calling sandbox or platform tools via `context.sandbox` / `context.tools` - Splitting AI inference (`agents/`) from data CRUD (`cloud-functions/`) +> Cross-reference: if your code uses `context.store` or KV APIs, also read `skills/makers-storage/SKILL.md`. + **Do NOT use for:** - Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-pages-dev` - Deployment workflows → use `edgeone-pages-deploy` @@ -268,30 +270,16 @@ PAGES_SOURCE=skills edgeone makers dev This tells the platform that the command was triggered from an AI skill context. -### package.json scripts (⚠️ avoid dev script recursion) - -> ⛔ **NEVER set `"dev": "edgeone makers dev"` or `"dev": "edgeone makers dev"` in package.json** — this causes infinite recursion. -> When CLI starts, it reads `scripts.dev` to launch the frontend dev server. If that script is -> `edgeone makers dev` itself, it recurses. CLI detects this and skips the frontend server entirely, -> causing static files (e.g., `public/index.html`) to return 404. +### Local development -Correct pattern — **do not include a `dev` script**: +```bash +# 1. Link to remote project (pulls project ID + env vars) +PAGES_SOURCE=skills edgeone makers link -```json -{ - "scripts": { - "build": "edgeone makers build", - "deploy": "edgeone makers deploy" - } -} +# 2. Pull remote environment variables to local .env +PAGES_SOURCE=skills edgeone makers env pull ``` -CLI will automatically serve `public/` as static files during `edgeone makers dev`. No `dev` script needed. - -- `edgeone makers dev` — starts agent runtime + detects & launches frontend dev server (reads `scripts.dev` or auto-serves `public/`) -- `edgeone makers build` — builds agents + frontend into `.edgeone/` output -- `edgeone makers deploy` — builds and deploys to EdgeOne Makers - ### Environment variables for deployment **AI Gateway variables** (`AI_GATEWAY_API_KEY`, `AI_GATEWAY_BASE_URL`) are **auto-provisioned** by the CLI during deployment — no manual setup needed, as long as `.env.example` declares them: diff --git a/skills/makers-agents/references/node-frameworks/deepagents.md b/skills/makers-agents/references/node-frameworks/deepagents.md index fd09a18..1a6ef55 100644 --- a/skills/makers-agents/references/node-frameworks/deepagents.md +++ b/skills/makers-agents/references/node-frameworks/deepagents.md @@ -11,6 +11,7 @@ npm install deepagents@^1.9.0 @langchain/openai @langchain/core zod ``` +> **Note**: `deepagents` is a platform-provided package bundled with the EdgeOne Makers agent runtime. It is automatically available in the deployed environment. `edgeone.json`: ```json { diff --git a/skills/makers-agents/references/python-frameworks/deepagents.md b/skills/makers-agents/references/python-frameworks/deepagents.md index df450df..2bc43cb 100644 --- a/skills/makers-agents/references/python-frameworks/deepagents.md +++ b/skills/makers-agents/references/python-frameworks/deepagents.md @@ -20,6 +20,7 @@ pydantic>=2.0.0 pip install -r requirements.txt ``` +> **Note**: `deepagents` is a platform-provided package bundled with the EdgeOne Makers agent runtime. It is automatically available in the deployed environment. `edgeone.json`: ```json { diff --git a/skills/makers-agents/references/review-checklist.md b/skills/makers-agents/references/review-checklist.md index 02d8309..fa34554 100644 --- a/skills/makers-agents/references/review-checklist.md +++ b/skills/makers-agents/references/review-checklist.md @@ -194,6 +194,8 @@ - [ ] `process.env` is allowed on the frontend (consistent with frontend frameworks), but **do not** expose backend secrets like `AI_GATEWAY_API_KEY` to the browser +- [ ] Local testing is done through the Makers proxy URL printed by `edgeone makers dev`; the raw frontend dev-server port does not serve `agents/` routes + --- ## Remediation Table: from "generic Vercel style" → "EdgeOne Makers style" diff --git a/skills/makers-cli/SKILL.md b/skills/makers-cli/SKILL.md index 4a1dcc7..ea6e420 100644 --- a/skills/makers-cli/SKILL.md +++ b/skills/makers-cli/SKILL.md @@ -54,9 +54,9 @@ Or inline: `PAGES_SOURCE=skills edgeone makers dev` ```bash npm install -g edgeone edgeone login -edgeone makers link -edgeone makers env pull -edgeone makers dev +PAGES_SOURCE=skills edgeone makers link +PAGES_SOURCE=skills edgeone makers env pull +PAGES_SOURCE=skills edgeone makers dev ``` ### Deploy diff --git a/skills/makers-edge-functions/SKILL.md b/skills/makers-edge-functions/SKILL.md index 96020c4..7345171 100644 --- a/skills/makers-edge-functions/SKILL.md +++ b/skills/makers-edge-functions/SKILL.md @@ -3,16 +3,16 @@ name: edgeone-makers-edge-functions description: >- V8-based lightweight edge functions on EdgeOne Makers. Covers routing, KV storage access, request/response handling, and environment variables at the edge. -metadata: - author: edgeone - version: "1.0.0" ---- - ---- -name: edgeone-makers-edge-functions -description: >- - V8-based lightweight edge functions on EdgeOne Makers. Covers routing, KV storage access, - request/response handling, and environment variables at the edge. +pathPatterns: + - edge-functions/** + - functions/** +validate: + - pattern: "process\\.env" + message: "Use context.env in EdgeOne Makers runtime code." + - pattern: "new\\s+Headers\\s*\\(" + message: "Use plain object headers for this runtime surface." + - pattern: "fs\\.writeFile" + message: "Edge Functions do not support filesystem writes." metadata: author: edgeone version: "1.0.0" @@ -108,6 +108,8 @@ export function onRequest(context) { ## KV Storage (Edge Functions only) +> Cross-reference: if your code uses `context.store` or KV APIs, also read `skills/makers-storage/SKILL.md`. + ⚠️ **Prerequisites**: You must enable KV Storage in the EdgeOne Makers console, create a namespace, and bind it to your project before using KV. See [kv-storage.md](kv-storage.md) for full setup instructions (same directory). The KV namespace is a **global variable** (name is set when binding in the console) — it is **NOT** on `context.env`. diff --git a/skills/makers-middleware/SKILL.md b/skills/makers-middleware/SKILL.md index c0a25a0..751cc3d 100644 --- a/skills/makers-middleware/SKILL.md +++ b/skills/makers-middleware/SKILL.md @@ -8,16 +8,6 @@ metadata: version: "1.0.0" --- ---- -name: edgeone-makers-middleware -description: >- - Edge middleware for EdgeOne Makers — request interception, redirects, rewrites, - auth guards, A/B testing, and header injection at the edge (V8 runtime). -metadata: - author: edgeone - version: "1.0.0" ---- - # Middleware Lightweight request interception running at the edge (V8 runtime). Use for redirects, rewrites, auth guards, A/B testing, and header injection.