Skip to content

Hooks

What edgeone-makers-tools runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
edgeone-makers-tools
1.8k12 skills1 hook

Claude Code

> /plugin marketplace add tencentedgeone/edgeone-makers-tools
> /plugin install edgeone-makers-tools@edgeone-makers

What fires, and when

PreToolUse

  • MatchesEdit|Write|replace_in_file|write_to_filenode "${CLAUDE_PLUGIN_ROOT}/hooks/validate-write.mjs"
Read hooks/hooks.json

Where it lives

  • hooks/signal-log.mjsGitHub
    Read the script
    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;
    }
    
  • hooks/signal-log.test.mjsGitHub
    Read the script
    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/,
      );
    });
    
  • hooks/validate-write.mjsRunsGitHub
    Read the script
    #!/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));
    // Single-skill layout: capabilities (each carrying its own validate rules in
    // frontmatter) live under the one skill's references/ directory.
    const DEFAULT_SKILLS_DIR = join(HOOKS_DIR, '..', 'skills', 'edgeone-makers-tools', 'references');
    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,
      };
    }
    
    /**
     * 读取各能力声明的 validate 规则。
     *
     * 读不到就返回空数组,绝不抛错:本函数跑在 PreToolUse 钩子里,
     * 模型每写一个文件都会经过它。references/ 不存在(部分安装、
     * CLAUDE_PLUGIN_ROOT 解析错位)时若抛 ENOENT,用户每次写文件都会看到一次报错。
     * 校验器失效的正确表现是「不提醒」,而不是「报错」。
     */
    export function loadSkillValidateRules(skillsDir = DEFAULT_SKILLS_DIR) {
      if (skillsDir === DEFAULT_SKILLS_DIR && cachedRules) return cachedRules;
      let entries;
      try {
        entries = readdirSync(skillsDir, { withFileTypes: true });
      } catch {
        return [];
      }
      const rules = entries
        .filter((entry) => entry.isDirectory())
        .map((entry) => join(skillsDir, entry.name, 'SKILL.md'))
        .map((skillPath) => {
          try {
            return parseSkillValidateRule(skillPath);
          } catch {
            return null;
          }
        })
        .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 '';
    }
    
    /**
     * 返回所有 pathPatterns 命中该路径的规则。
     *
     * 不能只取第一条:规则按目录字母序加载,而 `agents/**` 与
     * `cloud-functions/**` 这类前缀天然会重叠。只取首条等于让「哪条铁律生效」
     * 由目录名的字母序偶然决定,多个能力共管同一路径时会静默丢提醒。
     */
    function findSkillsForPath(filePath, rules) {
      if (!filePath) return [];
      return rules.filter((rule) =>
        rule.pathPatterns.some((pattern) => globToRegExp(pattern).test(filePath)),
      );
    }
    
    /**
     * 收集全部命中的校验项,message 去重并保留首次出现顺序。
     * 每项带上来源 skill,供 signal log 归因。
     */
    function selectValidationMatches(content, matchedRules) {
      const seen = new Set();
      const matches = [];
      for (const rule of matchedRules) {
        for (const item of rule.validate) {
          if (!new RegExp(item.pattern).test(content)) continue;
          if (seen.has(item.message)) continue;
          seen.add(item.message);
          matches.push({ ...item, skill: rule.skill });
        }
      }
      return ma
  • hooks/validate-write.test.mjsGitHub
    Read the script
    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, 'edge-functions capability should declare validate rules');
      assert.deepEqual(edgeFunctions.pathPatterns, ['edge-functions/**', 'functions/**']);
    
      // 断言不变量而不是冻结的数组:每加一条规则都让测试失败,只会逼着人改断言。
      assert.ok(edgeFunctions.validate.length >= 3);
      for (const item of edgeFunctions.validate) {
        assert.equal(typeof item.pattern, 'string');
        assert.equal(typeof item.message, 'string');
        assert.ok(item.message.length > 0);
        assert.doesNotThrow(() => new RegExp(item.pattern));
      }
    
      // 三条原始红线必须始终在场,新增规则不得把它们挤掉。
      const messages = edgeFunctions.validate.map((item) => item.message);
      assert.ok(messages.includes('Use context.env in EdgeOne Makers runtime code.'));
      assert.ok(messages.includes('Use plain object headers for this runtime surface.'));
      assert.ok(messages.includes('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');

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

@tencentedgeone
Ships withedgeone-makers-tools

Official AI Agent Skills for developing and deploying projects on EdgeOne Makers.

Stars on GitHub

Momentum

stars in total
1.8k
Star history of tencentedgeone/edgeone-pages-skills on GitHub, a chart by star-history.com

More plugins that ship hooks.

superpowers

superpowers

A development methodology for coding agents that makes the agent brainstorm before building, follow plans, run parallel work and finish branches in a disciplined way. You get fewer skipped steps and more consistent results from your coding assistant.

15 skills1 hook311k
@obra@obra
Install Plugin
ponytail

ponytail

A Claude Code plugin with skills for auditing a whole repo, reviewing a change, listing deferred shortcut comments, and showing measured savings and a quick command reference.

6 skills10 hooks188k
@dietrichgebert@dietrichgebert
Install Plugin
agent-skills

agent-skills

Walks a software change from a written spec through design, testing, code review, CI/CD, and release, applying the matching engineering skill at each stage. Useful for developers using Claude Code on full development workflows.

25 skills+3112k
@addyosmani@addyosmani
Install Plugin
planning-with-files

planning-with-files

Persistent file-based planning for AI coding agents on long-running, multi-step tasks. It keeps task_plan.md, findings.md and progress.md on disk, so work can resume after /clear or context compaction.

6 skills+228k
@othmanadi@othmanadi
Install Plugin
gsd-core

gsd-core

A spec-driven development workflow for Claude Code that guides a project through planning, phases, milestone audits and fixes. Includes skills to generate tests from UAT criteria and to audit completed work.

72 skills+311k
@open-gsd@open-gsd
Install Plugin
superpowers-zh

superpowers-zh

Chinese-language skill pack for Claude Code that adds workflow guidance for brainstorming, TDD, debugging, and code review, plus Chinese conventions for commits, documentation, and domestic Git platforms like Gitee and Coding.net.

21 skills1 hook8.5k
@jnmetacode@jnmetacode
Install Plugin
socraticode

socraticode

Gives Claude Code semantic search, dependency graph and architecture context over a codebase, so an AI assistant can explore and understand a project's code. Includes skills for indexing a project and managing its file watching.

2 skills+33.4k
@giancarloerra@giancarloerra
Install Plugin
cocoindex-code

cocoindex-code

A lightweight code search engine that uses AST-based semantic search with local embeddings and incremental indexing. It helps Claude Code users find code faster and more efficiently.

1 skill1 MCP+13k
@cocoindex-io@cocoindex-io
Install Plugin

Questions about the hooks in edgeone-makers-tools.

Which coding agents can use the hooks in edgeone-makers-tools?
The hooks in edgeone-makers-tools install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.