From aeb1487edb2413fb14bb8963b4e1d940ead6ba2b Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 02:26:07 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20skill=20=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E6=A3=80=E6=B5=8B=EF=BC=8C=E5=AE=9E=E6=B5=8B=2011=20?= =?UTF-8?q?=E5=A4=84=E6=96=AD=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 58 ++++++++++++++++++++++ scripts/lib/skill-graph.test.mjs | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 scripts/lib/skill-graph.mjs create mode 100644 scripts/lib/skill-graph.test.mjs diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs new file mode 100644 index 0000000..0728476 --- /dev/null +++ b/scripts/lib/skill-graph.mjs @@ -0,0 +1,58 @@ +import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; +import { dirname, join, relative, resolve } from 'node:path'; + +const MD_LINK = /\[[^\]]*\]\(([^)\s]+)\)/g; + +/** 递归列出所有 .md,返回相对 root 的 posix 路径,已排序。 */ +export function listMarkdownFiles(root) { + const out = []; + function walk(dir) { + const entries = readdirSync(dir, { withFileTypes: true }); + entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + for (const entry of entries) { + if (entry.name.startsWith('.')) continue; + const full = join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.md')) out.push(relative(root, full).split('\\').join('/')); + } + } + walk(root); + return out; +} + +/** skills/ 的直接子目录名,已排序。 */ +export function listSkillDirs(root) { + return readdirSync(root, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.')) + .map((entry) => entry.name) + .sort(); +} + +/** + * 找出指向不存在文件的相对 markdown 链接。 + * + * 关键:以链接所在文件的目录为基准解析(模型就是这样读的), + * 而不是以仓库根目录为基准(作者往往这样心算)。 + * 跳过 http(s)/mailto/纯锚点;#anchor 与 ?query 在解析前剥离。 + */ +export function findBrokenLinks(root) { + const broken = []; + for (const file of listMarkdownFiles(root)) { + const abs = join(root, file); + readFileSync(abs, 'utf8') + .split(/\r?\n/) + .forEach((line, index) => { + for (const match of line.matchAll(MD_LINK)) { + const raw = match[1]; + if (/^(https?:|mailto:|#)/.test(raw)) continue; + const target = raw.split('#')[0].split('?')[0]; + if (!target || !target.endsWith('.md')) continue; + const resolved = resolve(dirname(abs), target); + if (!existsSync(resolved) || !statSync(resolved).isFile()) { + broken.push({ file, line: index + 1, target }); + } + } + }); + } + return broken; +} diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs new file mode 100644 index 0000000..daf71d8 --- /dev/null +++ b/scripts/lib/skill-graph.test.mjs @@ -0,0 +1,82 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import test from 'node:test'; + +import { findBrokenLinks } from './skill-graph.mjs'; + +/** 在临时目录里造一棵假的 skills 树,键是相对路径。 */ +async function makeSkills(tree) { + const root = await mkdtemp(join(tmpdir(), 'skill-graph-')); + for (const [relativePath, content] of Object.entries(tree)) { + const full = join(root, relativePath); + await mkdir(join(full, '..'), { recursive: true }); + await writeFile(full, content, 'utf8'); + } + return root; +} + +test('skill-graph.findBrokenLinks reports a link whose target does not exist', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n\nSee [kv](kv-storage.md) here.\n', + }); + try { + assert.deepEqual(findBrokenLinks(root), [ + { file: 'makers-a/SKILL.md', line: 5, target: 'kv-storage.md' }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findBrokenLinks accepts a link that resolves relative to its own file', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n\nSee [kv](references/kv.md).\n', + 'makers-a/references/kv.md': '# KV\n', + }); + try { + assert.deepEqual(findBrokenLinks(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findBrokenLinks resolves a one-level cross-skill link', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n\n[x](../makers-b/references/x.md)\n', + 'makers-b/references/x.md': '# X\n', + }); + try { + assert.deepEqual(findBrokenLinks(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findBrokenLinks flags the extra skills/ level bug', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n\n[x](../skills/makers-b/references/x.md)\n', + 'makers-b/references/x.md': '# X\n', + }); + try { + const broken = findBrokenLinks(root); + assert.equal(broken.length, 1); + assert.equal(broken[0].target, '../skills/makers-b/references/x.md'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findBrokenLinks ignores http links and strips anchors', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': + '---\nname: a\n---\n\n[web](https://example.com/a.md) [anchor](references/kv.md#section)\n', + 'makers-a/references/kv.md': '# KV\n', + }); + try { + assert.deepEqual(findBrokenLinks(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 9d1c532bf64e15b0f6c9143854ce1124fdfcc190 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 02:47:18 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=E6=8A=BD=E5=87=BA=E9=80=90=E8=A1=8C?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E5=85=AC=E5=85=B1=E5=B1=82=EF=BC=8C=E5=AE=B9?= =?UTF-8?q?=E9=94=99=E5=9D=8F=E8=B7=AF=E5=BE=84=E4=B8=8E=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 96 +++++++++++++++++++++++++------- scripts/lib/skill-graph.test.mjs | 58 ++++++++++++++++++- 2 files changed, 131 insertions(+), 23 deletions(-) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index 0728476..e834a26 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -3,13 +3,28 @@ import { dirname, join, relative, resolve } from 'node:path'; const MD_LINK = /\[[^\]]*\]\(([^)\s]+)\)/g; +/** + * 非本地链接:任意 URL scheme(http:/https:/mailto:/tel:/data: ……)与纯锚点。 + * 用通用 scheme 匹配而不是逐个枚举,免得以后漏掉一种就误报。 + */ +const NON_LOCAL_LINK = /^([a-z][a-z0-9+.-]*:|#)/i; + +/** root 必须是存在的目录,否则 readdirSync 只会抛 ENOENT/ENOTDIR 裸栈。 */ +function assertDirectory(root) { + if (!existsSync(root)) { + throw new Error(`skill-graph: root directory not found: ${root}`); + } + if (!statSync(root).isDirectory()) { + throw new Error(`skill-graph: root is not a directory: ${root}`); + } +} + /** 递归列出所有 .md,返回相对 root 的 posix 路径,已排序。 */ export function listMarkdownFiles(root) { + assertDirectory(root); const out = []; function walk(dir) { - const entries = readdirSync(dir, { withFileTypes: true }); - entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); - for (const entry of entries) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { if (entry.name.startsWith('.')) continue; const full = join(dir, entry.name); if (entry.isDirectory()) walk(full); @@ -17,42 +32,81 @@ export function listMarkdownFiles(root) { } } walk(root); - return out; + // 逐目录排序 + 深度优先并不等于全局有序(目录 a/ 与文件 a-b.md 会乱序), + // 所以统一在这里排一次,让上面那句“已排序”成立。 + return out.sort(); } /** skills/ 的直接子目录名,已排序。 */ export function listSkillDirs(root) { + assertDirectory(root); return readdirSync(root, { withFileTypes: true }) .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.')) .map((entry) => entry.name) .sort(); } +/** + * 逐行遍历 root 下所有 markdown,对每行调用 visit(file, line, lineNumber)。 + * file 是相对 root 的 posix 路径,lineNumber 从 1 开始。 + * + * 单个文件读失败(权限等)不该让整轮扫描失效——记进 unreadable 后跳过, + * 其余文件照常检查。返回读不到的文件清单。 + */ +export function forEachMarkdownLine(root, visit) { + const unreadable = []; + for (const file of listMarkdownFiles(root)) { + let text; + try { + text = readFileSync(join(root, file), 'utf8'); + } catch (error) { + unreadable.push({ file, error: error instanceof Error ? error.message : String(error) }); + continue; + } + text.split(/\r?\n/).forEach((line, index) => visit(file, line, index + 1)); + } + return unreadable; +} + +/** + * 逐个取出一行里的本地链接目标,已剥掉 #anchor 与 ?query。 + * + * 注意:不跟踪 ``` 围栏状态,所以代码块里的链接同样会被取出来。 + * 目前真实仓库里没有“围栏内的坏链接”,不算问题;若以后新增示范用的 + * 错误链接片段(例如讲解断链时贴个反例),这里会误报,届时再加围栏开关。 + */ +export function* localLinkTargets(line) { + for (const match of line.matchAll(MD_LINK)) { + const raw = match[1]; + if (NON_LOCAL_LINK.test(raw)) continue; + const target = raw.split('#')[0].split('?')[0]; + if (target) yield target; + } +} + /** * 找出指向不存在文件的相对 markdown 链接。 * * 关键:以链接所在文件的目录为基准解析(模型就是这样读的), * 而不是以仓库根目录为基准(作者往往这样心算)。 - * 跳过 http(s)/mailto/纯锚点;#anchor 与 ?query 在解析前剥离。 + * + * 返回项形如 { file, line, target };读不到的文件另记一条 + * { file, line: 0, target: null, error },让上层能区分“链接坏了”和“文件没读到”, + * 而不是把权限问题静默当成“没有断链”。 */ export function findBrokenLinks(root) { const broken = []; - for (const file of listMarkdownFiles(root)) { - const abs = join(root, file); - readFileSync(abs, 'utf8') - .split(/\r?\n/) - .forEach((line, index) => { - for (const match of line.matchAll(MD_LINK)) { - const raw = match[1]; - if (/^(https?:|mailto:|#)/.test(raw)) continue; - const target = raw.split('#')[0].split('?')[0]; - if (!target || !target.endsWith('.md')) continue; - const resolved = resolve(dirname(abs), target); - if (!existsSync(resolved) || !statSync(resolved).isFile()) { - broken.push({ file, line: index + 1, target }); - } - } - }); + const unreadable = forEachMarkdownLine(root, (file, line, lineNumber) => { + for (const target of localLinkTargets(line)) { + if (!target.endsWith('.md')) continue; + const resolved = resolve(dirname(join(root, file)), target); + if (!existsSync(resolved) || !statSync(resolved).isFile()) { + broken.push({ file, line: lineNumber, target }); + } + } + }); + for (const entry of unreadable) { + broken.push({ file: entry.file, line: 0, target: null, error: entry.error }); } return broken; } diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index daf71d8..82c3ce6 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -1,10 +1,10 @@ import assert from 'node:assert/strict'; -import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; +import { chmod, mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import test from 'node:test'; -import { findBrokenLinks } from './skill-graph.mjs'; +import { findBrokenLinks, listMarkdownFiles } from './skill-graph.mjs'; /** 在临时目录里造一棵假的 skills 树,键是相对路径。 */ async function makeSkills(tree) { @@ -80,3 +80,57 @@ test('skill-graph.findBrokenLinks ignores http links and strips anchors', async await rm(root, { recursive: true, force: true }); } }); + +test('skill-graph.findBrokenLinks fails with a readable message on a bad root', async () => { + const root = await makeSkills({ 'makers-a/SKILL.md': '---\nname: a\n---\n' }); + try { + assert.throws(() => findBrokenLinks(join(root, 'no-such-dir')), { + message: /root directory not found/, + }); + // 传文件而不是目录:原来会抛 ENOTDIR 裸栈。 + assert.throws(() => findBrokenLinks(join(root, 'makers-a/SKILL.md')), { + message: /not a directory/, + }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findBrokenLinks keeps scanning when one file is unreadable', async () => { + const root = await makeSkills({ + 'makers-a/locked.md': '# locked\n', + 'makers-b/SKILL.md': '---\nname: b\n---\n\n[gone](missing.md)\n', + }); + const locked = join(root, 'makers-a/locked.md'); + try { + await chmod(locked, 0o000); + const broken = findBrokenLinks(root); + + // 后面那个文件的断链仍被发现,没有被前面的权限错误带走整轮扫描。 + assert.ok( + broken.some((item) => item.file === 'makers-b/SKILL.md' && item.target === 'missing.md'), + ); + // 读不到的文件被单独记录,而不是静默当成“没有断链”。 + const unreadable = broken.find((item) => item.file === 'makers-a/locked.md'); + assert.equal(unreadable.line, 0); + assert.equal(unreadable.target, null); + assert.match(unreadable.error, /EACCES/); + } finally { + // 先恢复权限,否则 rm 清不掉这棵树。 + await chmod(locked, 0o644); + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.listMarkdownFiles returns globally sorted paths', async () => { + const root = await makeSkills({ + 'a/z.md': '# z\n', + 'a-b.md': '# a-b\n', + 'b/c.md': '# c\n', + }); + try { + assert.deepEqual(listMarkdownFiles(root), ['a-b.md', 'a/z.md', 'b/c.md']); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 170b0cc6295ecd8da7ecd1c81370dbc13a408f0a Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 02:53:40 +0800 Subject: [PATCH 03/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=82=AC=E7=A9=BA=20sk?= =?UTF-8?q?ill=20=E5=90=8D=E4=B8=8E=E4=BA=8C=E7=BA=A7=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 61 ++++++++++++++++++++++++++ scripts/lib/skill-graph.test.mjs | 74 +++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index e834a26..71c8938 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -110,3 +110,64 @@ export function findBrokenLinks(root) { } return broken; } + +const SKILL_NAME_TOKEN = /\bedgeone-(?:makers|pages)-[a-z0-9-]+/g; + +/** marketplace / plugin 的产品 slug,不是 skill 名,不参与悬空判定。 */ +const NON_SKILL_SLUGS = new Set(['edgeone-makers-tools']); + +/** 各 SKILL.md frontmatter 声明的 name 集合。 */ +export function listDeclaredSkillNames(root) { + const names = new Set(); + for (const dir of listSkillDirs(root)) { + const skillPath = join(root, dir, 'SKILL.md'); + if (!existsSync(skillPath)) continue; + const match = /^name:\s*(.+)$/m.exec(readFileSync(skillPath, 'utf8')); + if (match) names.add(match[1].trim().replace(/^["']|["']$/g, '')); + } + return names; +} + +/** + * 找出正文/description 里出现、但没有任何 skill 声明的 skill 名。 + * 模型会尝试加载这种名字,失败后重试成环。 + * + * 读不到的文件不在这里单独记账:findBrokenLinks 走的是同一批文件, + * 已经会把它们报出来,doctor 那层不需要同一个问题听三遍。 + */ +export function findDanglingSkillNames(root) { + const declared = listDeclaredSkillNames(root); + const dangling = []; + forEachMarkdownLine(root, (file, line, lineNumber) => { + for (const match of line.matchAll(SKILL_NAME_TOKEN)) { + const name = match[0]; + if (declared.has(name) || NON_SKILL_SLUGS.has(name)) continue; + dangling.push({ file, line: lineNumber, name }); + } + }); + return dangling; +} + +/** + * 爬升两级以上的相对路径。 + * `(^|\/)` 这道前置守卫是为了别把 `..../../` 这类含 `....` 目录名的路径误判成两级爬升。 + */ +const DEEP_PARENT_LINK = /(^|\/)\.\.\/\.\.\//; + +/** + * 找出爬升两级以上的相对链接。 + * Anthropic 要求 reference 只下沉一层;`../../` 让模型在目录间反复横跳。 + * + * 复用 localLinkTargets:它剥的是 #anchor / ?query,不动 `../` 前缀, + * 所以判定与上报的 target 都保留原样的爬升层数,且与 findBrokenLinks + * 的 target 归一化方式一致。 + */ +export function findDeepReferenceLinks(root) { + const deep = []; + forEachMarkdownLine(root, (file, line, lineNumber) => { + for (const target of localLinkTargets(line)) { + if (DEEP_PARENT_LINK.test(target)) deep.push({ file, line: lineNumber, target }); + } + }); + return deep; +} diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index 82c3ce6..11962a9 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -4,7 +4,12 @@ import { join } from 'node:path'; import { tmpdir } from 'node:os'; import test from 'node:test'; -import { findBrokenLinks, listMarkdownFiles } from './skill-graph.mjs'; +import { + findBrokenLinks, + findDanglingSkillNames, + findDeepReferenceLinks, + listMarkdownFiles, +} from './skill-graph.mjs'; /** 在临时目录里造一棵假的 skills 树,键是相对路径。 */ async function makeSkills(tree) { @@ -134,3 +139,70 @@ test('skill-graph.listMarkdownFiles returns globally sorted paths', async () => await rm(root, { recursive: true, force: true }); } }); + +test('skill-graph.findDanglingSkillNames flags a name no skill declares', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: edgeone-makers-a\n---\n\nUse edgeone-pages-dev instead.\n', + }); + try { + assert.deepEqual(findDanglingSkillNames(root), [ + { file: 'makers-a/SKILL.md', line: 5, name: 'edgeone-pages-dev' }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDanglingSkillNames catches a dangling name inside description', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': + '---\nname: edgeone-makers-a\ndescription: >-\n Do NOT trigger for X (use edgeone-makers-dev instead).\n---\n\nBody.\n', + }); + try { + const dangling = findDanglingSkillNames(root); + assert.equal(dangling.length, 1); + assert.equal(dangling[0].name, 'edgeone-makers-dev'); + assert.equal(dangling[0].line, 4); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDanglingSkillNames accepts declared names and the marketplace slug', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': + '---\nname: edgeone-makers-a\n---\n\nSee edgeone-makers-b and edgeone-makers-tools.\n', + 'makers-b/SKILL.md': '---\nname: edgeone-makers-b\n---\n\nHi.\n', + }); + try { + assert.deepEqual(findDanglingSkillNames(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDeepReferenceLinks flags links that climb two levels', async () => { + const root = await makeSkills({ + 'makers-a/references/x.md': 'See [y](../../makers-b/references/y.md).\n', + 'makers-b/references/y.md': '# Y\n', + }); + try { + assert.deepEqual(findDeepReferenceLinks(root), [ + { file: 'makers-a/references/x.md', line: 1, target: '../../makers-b/references/y.md' }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDeepReferenceLinks allows single-level parent links', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n\n[y](../makers-b/references/y.md)\n', + 'makers-b/references/y.md': '# Y\n', + }); + try { + assert.deepEqual(findDeepReferenceLinks(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 61183cb80bde488eb9cda1438ef4dbeb9f034f25 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:15:00 +0800 Subject: [PATCH 04/29] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8D=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=20SKILL.md=20=E6=8A=9B=E9=94=99=EF=BC=8C=E8=A1=A5?= =?UTF-8?q?=E6=B3=A8=E8=A3=B8=E5=90=8D=E7=9B=B2=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 29 ++++++++++++++++++++++--- scripts/lib/skill-graph.test.mjs | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index 71c8938..2ec7266 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -111,18 +111,41 @@ export function findBrokenLinks(root) { return broken; } +/** + * skill 名 token。 + * + * 只认 `edgeone-` 前缀是刻意保守:仓库里裸的 `makers-*` 大量撞车 + * (`makers-conversation-id` 是个 HTTP 头,还有 `makers-ai-voice-chat` 这类项目名), + * 放宽会把误报刷成噪音。 + * + * 代价是有盲区:`makers-cli` / `makers-cloud-functions` / `makers-migration` + * 这三个 skill 的 frontmatter 声明的就是裸名,正文里引用它们时本函数看不见。 + * 所以返回的条数是**下界**,别当成全覆盖。 + */ const SKILL_NAME_TOKEN = /\bedgeone-(?:makers|pages)-[a-z0-9-]+/g; /** marketplace / plugin 的产品 slug,不是 skill 名,不参与悬空判定。 */ const NON_SKILL_SLUGS = new Set(['edgeone-makers-tools']); -/** 各 SKILL.md frontmatter 声明的 name 集合。 */ +/** + * 各 SKILL.md frontmatter 声明的 name 集合。 + * + * 读不到就跳过而不是抛:这函数是 doctor 六项检查之一的输入, + * 一个权限异常的文件不该把整份报告换成裸栈。漏读的文件由 + * findBrokenLinks 那条 unreadable 记录负责报出来。 + */ export function listDeclaredSkillNames(root) { const names = new Set(); for (const dir of listSkillDirs(root)) { const skillPath = join(root, dir, 'SKILL.md'); - if (!existsSync(skillPath)) continue; - const match = /^name:\s*(.+)$/m.exec(readFileSync(skillPath, 'utf8')); + if (!existsSync(skillPath) || !statSync(skillPath).isFile()) continue; + let text; + try { + text = readFileSync(skillPath, 'utf8'); + } catch { + continue; + } + const match = /^name:\s*(.+)$/m.exec(text); if (match) names.add(match[1].trim().replace(/^["']|["']$/g, '')); } return names; diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index 11962a9..c0e6763 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -206,3 +206,40 @@ test('skill-graph.findDeepReferenceLinks allows single-level parent links', asyn await rm(root, { recursive: true, force: true }); } }); + +test('skill-graph.findDeepReferenceLinks ignores a dotted dir name that only looks like two levels', async () => { + const root = await makeSkills({ + // `..../` 是个普通目录名,不是爬升两级;DEEP_PARENT_LINK 的 (^|/) 守卫就为这个。 + 'makers-a/SKILL.md': '---\nname: a\n---\n\n[x](..../../a.md)\n', + }); + try { + assert.deepEqual(findDeepReferenceLinks(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDanglingSkillNames survives an unreadable or non-file SKILL.md', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: edgeone-makers-a\n---\n', + 'makers-b/SKILL.md': '---\nname: edgeone-makers-b\n---\n\nUse edgeone-pages-dev.\n', + }); + const locked = join(root, 'makers-a/SKILL.md'); + try { + await chmod(locked, 0o000); + // SKILL.md 是个目录:原来会抛 EISDIR。 + await mkdir(join(root, 'makers-c/SKILL.md'), { recursive: true }); + + // 不抛——doctor 的六项检查不该被一个权限异常的文件换成裸栈。 + const dangling = findDanglingSkillNames(root); + + // 其余文件照常检查,没有被前面的权限错误带走整轮扫描。 + assert.deepEqual(dangling, [ + { file: 'makers-b/SKILL.md', line: 5, name: 'edgeone-pages-dev' }, + ]); + } finally { + // 先恢复权限,否则 rm 清不掉这棵树。 + await chmod(locked, 0o644); + await rm(root, { recursive: true, force: true }); + } +}); From 0e848b19993998caaa9a2c65595ff65b76edfd12 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:29:10 +0800 Subject: [PATCH 05/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20reference=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=BC=BA=E5=A4=B1=E4=B8=8E=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=A1=8C=E6=95=B0=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 84 +++++++++++++++++++++++++---- scripts/lib/skill-graph.test.mjs | 90 ++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 9 deletions(-) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index 2ec7266..c63b8e6 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -3,6 +3,9 @@ import { dirname, join, relative, resolve } from 'node:path'; const MD_LINK = /\[[^\]]*\]\(([^)\s]+)\)/g; +/** 统一的换行切分,兼容 CRLF;无 g 标志,可安全共享。 */ +const LINE_BREAK = /\r?\n/; + /** * 非本地链接:任意 URL scheme(http:/https:/mailto:/tel:/data: ……)与纯锚点。 * 用通用 scheme 匹配而不是逐个枚举,免得以后漏掉一种就误报。 @@ -63,11 +66,43 @@ export function forEachMarkdownLine(root, visit) { unreadable.push({ file, error: error instanceof Error ? error.message : String(error) }); continue; } - text.split(/\r?\n/).forEach((line, index) => visit(file, line, index + 1)); + text.split(LINE_BREAK).forEach((line, index) => visit(file, line, index + 1)); } return unreadable; } +/** + * 读单个 markdown 全文,读不到返回 null。 + * + * 本模块唯一一处“带守卫的读”:existsSync + isFile 挡掉不存在与 EISDIR + * (SKILL.md 是个目录就会踩到),try/catch 挡掉 EACCES 这类权限错。 + * 调用方把 null 当成“跳过这个文件”而不是抛——doctor 调 collect() 时没有 + * try/catch,一个权限异常的文件不该把整份六项报告换成裸栈。 + * + * forEachMarkdownLine 故意不走这里:它要把错误信息本身交给 findBrokenLinks + * 记成 unreadable 条目,而这里的契约是静默跳过,两种契约不该合并。 + */ +function readMarkdownText(root, file) { + const full = join(root, file); + if (!existsSync(full) || !statSync(full).isFile()) return null; + try { + return readFileSync(full, 'utf8'); + } catch { + return null; + } +} + +/** + * 读单个 markdown 并按行切好,读不到返回 null。 + * + * 需要整份行数组的检测(数行数、看开头 N 行)用这个; + * 只关心逐行内容的检测继续用 forEachMarkdownLine。 + */ +function readMarkdownLines(root, file) { + const text = readMarkdownText(root, file); + return text === null ? null : text.split(LINE_BREAK); +} + /** * 逐个取出一行里的本地链接目标,已剥掉 #anchor 与 ?query。 * @@ -137,14 +172,8 @@ const NON_SKILL_SLUGS = new Set(['edgeone-makers-tools']); export function listDeclaredSkillNames(root) { const names = new Set(); for (const dir of listSkillDirs(root)) { - const skillPath = join(root, dir, 'SKILL.md'); - if (!existsSync(skillPath) || !statSync(skillPath).isFile()) continue; - let text; - try { - text = readFileSync(skillPath, 'utf8'); - } catch { - continue; - } + const text = readMarkdownText(root, join(dir, 'SKILL.md')); + if (text === null) continue; const match = /^name:\s*(.+)$/m.exec(text); if (match) names.add(match[1].trim().replace(/^["']|["']$/g, '')); } @@ -194,3 +223,40 @@ export function findDeepReferenceLinks(root) { }); return deep; } + +export const TOC_LINE_THRESHOLD = 100; +export const MAX_FILE_LINES = 500; +const TOC_SCAN_LINES = 25; +const ANCHOR_LIST_ITEM = /^\s*(?:[-*]|\d+\.)\s*\[[^\]]+\]\(#/; + +/** + * 超 100 行、且开头 25 行内没有锚点目录的 reference。 + * 依据:Claude 可能只 head -100 部分读取,没有目录就拿不到全貌。 + * + * SKILL.md 不在此列:它是入口,模型总是整份读,且有 frontmatter 而非目录。 + */ +export function findMissingTocs(root) { + const missing = []; + for (const file of listMarkdownFiles(root)) { + if (file.endsWith('SKILL.md')) continue; + const lines = readMarkdownLines(root, file); + if (lines === null) continue; + if (lines.length <= TOC_LINE_THRESHOLD) continue; + if (lines.slice(0, TOC_SCAN_LINES).some((line) => ANCHOR_LIST_ITEM.test(line))) continue; + missing.push({ file, lines: lines.length }); + } + return missing; +} + +/** 超过 500 行的 md 文件(SKILL.md 与 reference 同一上限)。 */ +export function findOversizedFiles(root) { + const over = []; + for (const file of listMarkdownFiles(root)) { + const lines = readMarkdownLines(root, file); + if (lines === null) continue; + if (lines.length > MAX_FILE_LINES) { + over.push({ file, lines: lines.length, cap: MAX_FILE_LINES }); + } + } + return over; +} diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index c0e6763..cf3b806 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -8,6 +8,8 @@ import { findBrokenLinks, findDanglingSkillNames, findDeepReferenceLinks, + findMissingTocs, + findOversizedFiles, listMarkdownFiles, } from './skill-graph.mjs'; @@ -243,3 +245,91 @@ test('skill-graph.findDanglingSkillNames survives an unreadable or non-file SKIL await rm(root, { recursive: true, force: true }); } }); + +test('skill-graph.findMissingTocs flags a long reference without a table of contents', async () => { + const body = Array.from({ length: 120 }, (_, i) => `line ${i}`).join('\n'); + const root = await makeSkills({ 'makers-a/references/long.md': `# Long\n\n${body}\n` }); + try { + assert.deepEqual(findMissingTocs(root), [ + { file: 'makers-a/references/long.md', lines: 123 }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs accepts a long reference that opens with anchor links', async () => { + const body = Array.from({ length: 120 }, (_, i) => `line ${i}`).join('\n'); + const root = await makeSkills({ + 'makers-a/references/long.md': `# Long\n\n- [One](#one)\n- [Two](#two)\n\n${body}\n`, + }); + try { + assert.deepEqual(findMissingTocs(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs ignores short references and SKILL.md', async () => { + const body = Array.from({ length: 120 }, (_, i) => `line ${i}`).join('\n'); + const root = await makeSkills({ + 'makers-a/references/short.md': '# Short\n\nonly a few lines\n', + 'makers-a/SKILL.md': `---\nname: a\n---\n\n${body}\n`, + }); + try { + assert.deepEqual(findMissingTocs(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findOversizedFiles flags files above the 500-line cap', async () => { + const skillBody = Array.from({ length: 520 }, (_, i) => `s ${i}`).join('\n'); + const refBody = Array.from({ length: 520 }, (_, i) => `r ${i}`).join('\n'); + const root = await makeSkills({ + 'makers-a/SKILL.md': `---\nname: a\n---\n${skillBody}\n`, + 'makers-a/references/big.md': `# Big\n${refBody}\n`, + }); + try { + const over = findOversizedFiles(root); + assert.deepEqual(over.map((x) => x.file).sort(), [ + 'makers-a/SKILL.md', + 'makers-a/references/big.md', + ]); + assert.ok(over.every((x) => x.lines > 500 && x.cap === 500)); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs and findOversizedFiles survive an unreadable file', async () => { + const body = Array.from({ length: 600 }, (_, i) => `line ${i}`).join('\n'); + const root = await makeSkills({ + // 这个读不到:既够长会被目录检测盯上,也超 500 行会被行数检测盯上。 + 'makers-a/references/locked.md': `# Locked\n\n${body}\n`, + 'makers-b/references/plain.md': `# Plain\n\n${body}\n`, + }); + const locked = join(root, 'makers-a/references/locked.md'); + try { + await chmod(locked, 0o000); + + // 不抛——doctor 调 collect() 没有 try/catch,一个权限异常的文件 + // 不该把整份六项报告换成裸栈。 + const missing = findMissingTocs(root); + const over = findOversizedFiles(root); + + // 读不到的文件被跳过,不凭空报一条。 + assert.deepEqual( + missing.map((x) => x.file), + ['makers-b/references/plain.md'], + ); + assert.deepEqual( + over.map((x) => x.file), + ['makers-b/references/plain.md'], + ); + } finally { + // 先恢复权限,否则 rm 清不掉这棵树。 + await chmod(locked, 0o644); + await rm(root, { recursive: true, force: true }); + } +}); From 46bab0a9c5fc08b7cacca324e7ecf4ab09b4aae6 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:40:48 +0800 Subject: [PATCH 06/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=5Fmeta.json=20?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=B8=85=E5=8D=95=E4=B8=80=E8=87=B4=E6=80=A7?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 18 ++++++++++++++ scripts/lib/skill-graph.test.mjs | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index c63b8e6..11d5fa4 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -260,3 +260,21 @@ export function findOversizedFiles(root) { } return over; } + +/** + * _meta.json 的 files 数组 ↔ skills/ 下真实 md 文件。 + * missing = 磁盘有但没声明(不会发布到 SkillHub) + * extra = 声明了但磁盘没有(发布时会缺文件) + * 只比较 skills/ 前缀项,根级 SKILL.md / CLAUDE.md 不在管辖范围。 + * + * files 由调用方传入而不是在这里读 _meta.json:本模块的契约是 + * “给一个 root,返回纯数据”,不认识仓库根在哪。读文件是 doctor 那层的事。 + */ +export function checkFileManifest(root, files) { + const disk = new Set(listMarkdownFiles(root).map((file) => `skills/${file}`)); + const declared = new Set((files || []).filter((file) => String(file).startsWith('skills/'))); + return { + missing: [...disk].filter((file) => !declared.has(file)).sort(), + extra: [...declared].filter((file) => !disk.has(file)).sort(), + }; +} diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index cf3b806..1e793b5 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -5,6 +5,7 @@ import { tmpdir } from 'node:os'; import test from 'node:test'; import { + checkFileManifest, findBrokenLinks, findDanglingSkillNames, findDeepReferenceLinks, @@ -333,3 +334,42 @@ test('skill-graph.findMissingTocs and findOversizedFiles survive an unreadable f await rm(root, { recursive: true, force: true }); } }); + +test('skill-graph.checkFileManifest reports files on disk but absent from the manifest', async () => { + const root = await makeSkills({ + 'makers-a/SKILL.md': '---\nname: a\n---\n', + 'makers-a/references/x.md': '# X\n', + }); + try { + assert.deepEqual(checkFileManifest(root, ['skills/makers-a/SKILL.md']), { + missing: ['skills/makers-a/references/x.md'], + extra: [], + }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.checkFileManifest reports manifest entries with no file on disk', async () => { + const root = await makeSkills({ 'makers-a/SKILL.md': '---\nname: a\n---\n' }); + try { + assert.deepEqual( + checkFileManifest(root, ['skills/makers-a/SKILL.md', 'skills/makers-ghost/SKILL.md']), + { missing: [], extra: ['skills/makers-ghost/SKILL.md'] }, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.checkFileManifest ignores manifest entries outside skills/', async () => { + const root = await makeSkills({ 'makers-a/SKILL.md': '---\nname: a\n---\n' }); + try { + assert.deepEqual( + checkFileManifest(root, ['SKILL.md', 'CLAUDE.md', 'skills/makers-a/SKILL.md']), + { missing: [], extra: [] }, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 108f2592dcea8f157309dec69cee8d750beff2a1 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:47:56 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20doctor=20=E8=87=AA?= =?UTF-8?q?=E6=A3=80=E5=85=A5=E5=8F=A3=EF=BC=8C=E6=B1=87=E6=80=BB=E5=85=AD?= =?UTF-8?q?=E9=A1=B9=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/doctor.mjs | 97 +++++++++++++++++++++++++++++++++++++++++ scripts/doctor.test.mjs | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 scripts/doctor.mjs create mode 100644 scripts/doctor.test.mjs diff --git a/scripts/doctor.mjs b/scripts/doctor.mjs new file mode 100644 index 0000000..e7c678a --- /dev/null +++ b/scripts/doctor.mjs @@ -0,0 +1,97 @@ +#!/usr/bin/env node +/** + * Skills 自检入口。六项检查: + * 断链 / 悬空 skill 名 / 二级引用 / reference 目录 / 行数上限 / 发布清单一致性 + * 退出码 0 = 全绿,1 = 有失败项。 + * + * 分层:skill-graph.mjs 只认「给一个 root,返回纯数据」,不知道仓库根在哪; + * 认识 _meta.json 与 skills/ 位置的是这一层。 + */ +import { readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +import { + checkFileManifest, + findBrokenLinks, + findDanglingSkillNames, + findDeepReferenceLinks, + findMissingTocs, + findOversizedFiles, +} from './lib/skill-graph.mjs'; + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +const CHECKS = [ + { key: 'brokenLinks', check: 'broken-links', label: '断链' }, + { key: 'danglingNames', check: 'dangling-skill-names', label: '悬空 skill 名' }, + { key: 'deepLinks', check: 'deep-reference-links', label: '二级引用' }, + { key: 'missingTocs', check: 'missing-toc', label: '缺目录的长 reference' }, + { key: 'oversized', check: 'oversized-file', label: '超行数上限' }, +]; + +export function collect(skillsDir, metaPath = join(REPO_ROOT, '_meta.json')) { + const meta = JSON.parse(readFileSync(metaPath, 'utf8')); + return { + brokenLinks: findBrokenLinks(skillsDir), + danglingNames: findDanglingSkillNames(skillsDir), + deepLinks: findDeepReferenceLinks(skillsDir), + missingTocs: findMissingTocs(skillsDir), + oversized: findOversizedFiles(skillsDir), + manifest: checkFileManifest(skillsDir, meta.files), + }; +} + +export function summarize(results) { + const failures = []; + for (const { key, check, label } of CHECKS) { + const items = results[key] || []; + if (items.length > 0) failures.push({ check, label, count: items.length, items }); + } + const manifest = results.manifest || { missing: [], extra: [] }; + if (manifest.missing.length > 0 || manifest.extra.length > 0) { + failures.push({ + check: 'manifest-parity', + label: '_meta.json 与磁盘不一致', + count: 1, + items: [manifest], + }); + } + return { ok: failures.length === 0, failures }; +} + +function describeItem(item) { + // 读不到的文件是 findBrokenLinks 混在 broken 数组里的另一种条目 + // ({ file, line: 0, target: null, error }),跟「链接坏了」不是一回事,必须先单独认出来: + // 它的 line 是 0,会让下面 `if (item.line)` 落空,最该说清楚的那条反而退化成一坨裸 JSON。 + if (typeof item.error === 'string') return `无法读取 ${item.file}:${item.error}`; + if (Array.isArray(item.missing)) { + const parts = []; + if (item.missing.length) parts.push(`未声明 ${item.missing.length} 个:${item.missing.join(', ')}`); + if (item.extra.length) parts.push(`多声明 ${item.extra.length} 个:${item.extra.join(', ')}`); + return parts.join(';'); + } + if (item.line) return `${item.file}:${item.line} → ${item.target || item.name}`; + if (item.lines) return `${item.file}(${item.lines} 行)`; + return JSON.stringify(item); +} + +export function formatReport(summary) { + if (summary.ok) return '✅ doctor:六项检查全部通过'; + const lines = ['❌ doctor:发现问题']; + for (const failure of summary.failures) { + lines.push(`\n[${failure.check}] ${failure.label} — ${failure.count} 处`); + for (const item of failure.items) lines.push(` - ${describeItem(item)}`); + } + return lines.join('\n'); +} + +export function main(skillsDir = join(REPO_ROOT, 'skills')) { + const summary = summarize(collect(skillsDir)); + console.log(formatReport(summary)); + return summary.ok ? 0 : 1; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + process.exit(main()); +} diff --git a/scripts/doctor.test.mjs b/scripts/doctor.test.mjs new file mode 100644 index 0000000..a31d440 --- /dev/null +++ b/scripts/doctor.test.mjs @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { formatReport, summarize } from './doctor.mjs'; + +const EMPTY = { + brokenLinks: [], + danglingNames: [], + deepLinks: [], + missingTocs: [], + oversized: [], + manifest: { missing: [], extra: [] }, +}; + +test('doctor.summarize reports ok when every check is empty', () => { + const summary = summarize(EMPTY); + assert.equal(summary.ok, true); + assert.equal(summary.failures.length, 0); +}); + +test('doctor.summarize collects one failure per non-empty check', () => { + const summary = summarize({ + ...EMPTY, + brokenLinks: [{ file: 'a/SKILL.md', line: 1, target: 'x.md' }], + missingTocs: [{ file: 'a/references/b.md', lines: 200 }], + }); + assert.equal(summary.ok, false); + assert.deepEqual( + summary.failures.map((failure) => failure.check), + ['broken-links', 'missing-toc'], + ); + assert.equal(summary.failures[0].count, 1); +}); + +test('doctor.summarize treats a manifest gap as a single failure', () => { + const summary = summarize({ + ...EMPTY, + manifest: { missing: ['skills/makers-migration/SKILL.md'], extra: [] }, + }); + assert.equal(summary.ok, false); + assert.deepEqual(summary.failures.map((f) => f.check), ['manifest-parity']); + assert.equal(summary.failures[0].count, 1); +}); + +test('doctor.formatReport names each failing check and its locations', () => { + const text = formatReport( + summarize({ ...EMPTY, brokenLinks: [{ file: 'a/SKILL.md', line: 9, target: 'x.md' }] }), + ); + assert.match(text, /broken-links/); + assert.match(text, /a\/SKILL\.md:9/); +}); + +test('doctor.formatReport says everything passed when ok', () => { + assert.match(formatReport(summarize(EMPTY)), /通过/); +}); + +test('doctor.formatReport renders an unreadable file as its own message, not raw JSON', () => { + const text = formatReport( + summarize({ + ...EMPTY, + brokenLinks: [ + { + file: 'a/references/locked.md', + line: 0, + target: null, + error: "EACCES: permission denied, open 'a/references/locked.md'", + }, + ], + }), + ); + assert.match(text, /无法读取/); + assert.match(text, /a\/references\/locked\.md/); + assert.match(text, /EACCES/); + // line: 0 会让 `if (item.line)` 落空,退化成 JSON.stringify 的裸对象。 + assert.ok(!text.includes('{"'), `报告里出现了裸 JSON 片段:\n${text}`); +}); From 8bf48855ec884e65959de62ec95853844875ce39 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:53:55 +0800 Subject: [PATCH 08/29] =?UTF-8?q?=E8=AE=A2=E6=AD=A3=E8=AF=BB=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=B3=A8=E9=87=8A=EF=BC=8C=E6=94=B6=E7=B4=A7=20SKILL.?= =?UTF-8?q?md=20=E8=B1=81=E5=85=8D=E5=B9=B6=E8=A1=A5=E8=BE=B9=E7=95=8C?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/skill-graph.mjs | 29 +++++-- scripts/lib/skill-graph.test.mjs | 127 +++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index 11d5fa4..11850b8 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -74,8 +74,13 @@ export function forEachMarkdownLine(root, visit) { /** * 读单个 markdown 全文,读不到返回 null。 * - * 本模块唯一一处“带守卫的读”:existsSync + isFile 挡掉不存在与 EISDIR - * (SKILL.md 是个目录就会踩到),try/catch 挡掉 EACCES 这类权限错。 + * 本模块唯一一处“带守卫的读”,isFile() 这道守卫挡掉三类东西: + * - 不存在的路径 + * - 目录(SKILL.md 是个目录时裸读会抛 EISDIR) + * - 管道/设备等非普通文件——树里放一个命名 FIFO 叫 *.md,裸读会**永久阻塞** + * (实测 5s 未返回,只能 SIGKILL)。CI 里挂死比抛栈更难查,所以先 stat 再读。 + * try/catch 另外挡掉 EACCES 这类权限错。 + * * 调用方把 null 当成“跳过这个文件”而不是抛——doctor 调 collect() 时没有 * try/catch,一个权限异常的文件不该把整份六项报告换成裸栈。 * @@ -95,8 +100,9 @@ function readMarkdownText(root, file) { /** * 读单个 markdown 并按行切好,读不到返回 null。 * - * 需要整份行数组的检测(数行数、看开头 N 行)用这个; - * 只关心逐行内容的检测继续用 forEachMarkdownLine。 + * 拆成两个函数纯粹是为了各取所需:listDeclaredSkillNames 要整段 text 跑正则, + * 两个新检测要行数组(数行数、看开头 N 行)。包一层不花钱,也免得前者 + * 多做一次无意义的 split。只关心逐行内容的检测继续用 forEachMarkdownLine。 */ function readMarkdownLines(root, file) { const text = readMarkdownText(root, file); @@ -234,11 +240,15 @@ const ANCHOR_LIST_ITEM = /^\s*(?:[-*]|\d+\.)\s*\[[^\]]+\]\(#/; * 依据:Claude 可能只 head -100 部分读取,没有目录就拿不到全貌。 * * SKILL.md 不在此列:它是入口,模型总是整份读,且有 frontmatter 而非目录。 + * 用 `=== 'SKILL.md' || endsWith('/SKILL.md')` 而不是 endsWith('SKILL.md'): + * 后者会把 references/MY-SKILL.md 这种也一并豁免,从此永远拿不到目录且无人报错。 + * + * lines 的计法见 findOversizedFiles 的说明(是 split 段数,非 wc -l)。 */ export function findMissingTocs(root) { const missing = []; for (const file of listMarkdownFiles(root)) { - if (file.endsWith('SKILL.md')) continue; + if (file === 'SKILL.md' || file.endsWith('/SKILL.md')) continue; const lines = readMarkdownLines(root, file); if (lines === null) continue; if (lines.length <= TOC_LINE_THRESHOLD) continue; @@ -248,7 +258,14 @@ export function findMissingTocs(root) { return missing; } -/** 超过 500 行的 md 文件(SKILL.md 与 reference 同一上限)。 */ +/** + * 超过 500 行的 md 文件(SKILL.md 与 reference 同一上限)。 + * + * 注意 lines 是 split(/\r?\n/) 的**段数**,不是 wc -l:以换行结尾的文件 + * 末尾会多出一个空串,所以 lines === wc -l + 1 + * (crewai.md:wc -l 601,这里报 602)。于是对这类文件实际卡的是 499 行正文。 + * 不改:602 这个基线已被 Task 3/11/16 三处断言,动它会连带失配。 + */ export function findOversizedFiles(root) { const over = []; for (const file of listMarkdownFiles(root)) { diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index 1e793b5..b323754 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -373,3 +373,130 @@ test('skill-graph.checkFileManifest ignores manifest entries outside skills/', a await rm(root, { recursive: true, force: true }); } }); + +/** 造一个正文足够长(必然超过目录阈值)的 reference,可选地在开头插几行。 */ +function longReference(head = '') { + const body = Array.from({ length: 120 }, (_, i) => `line ${i}`).join('\n'); + return `# Long\n\n${head}${body}\n`; +} + +test('skill-graph.findMissingTocs accepts an ordered-list table of contents', async () => { + // `1.` 这一支单独钉住:去掉 ANCHOR_LIST_ITEM 里的 \d+\. 分支后本用例才会红。 + const root = await makeSkills({ + 'makers-a/references/long.md': longReference('1. [One](#one)\n2. [Two](#two)\n\n'), + }); + try { + assert.deepEqual(findMissingTocs(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs accepts a star-bulleted table of contents', async () => { + // `*` 这一支单独钉住:去掉 [-*] 里的 * 后本用例才会红。 + const root = await makeSkills({ + 'makers-a/references/long.md': longReference('* [One](#one)\n* [Two](#two)\n\n'), + }); + try { + assert.deepEqual(findMissingTocs(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs still flags a long file whose prose merely links an anchor', async () => { + // 最危险的方向:漏报意味着 Task 15 永远不会给它补目录。 + // 正文里的行内锚点链接不是目录——它不在行首,ANCHOR_LIST_ITEM 的 ^ 与列表符号要求就为这个。 + const root = await makeSkills({ + 'makers-a/references/long.md': longReference('See [below](#tail) for details.\n\n'), + }); + try { + assert.deepEqual( + findMissingTocs(root).map((x) => x.file), + ['makers-a/references/long.md'], + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs still flags a long file whose only list link is not an anchor', async () => { + // 列表项里放的是普通文件链接而非 #锚点,不算目录:钉住 ANCHOR_LIST_ITEM 结尾的 \(# 要求。 + const root = await makeSkills({ + 'makers-a/references/long.md': longReference('- [One](one.md)\n- [Two](two.md)\n\n'), + 'makers-a/references/one.md': '# One\n', + 'makers-a/references/two.md': '# Two\n', + }); + try { + assert.deepEqual( + findMissingTocs(root).map((x) => x.file), + ['makers-a/references/long.md'], + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs still flags a long file whose mid-line dash precedes an anchor link', async () => { + // 钉住 ANCHOR_LIST_ITEM 的 ^:没有 ^ 时,这行里的「 - [Two](#two)」会被当成行首列表项, + // 于是一段普通散文被误认成目录(漏报),Task 15 就永远不会给它补目录。 + const root = await makeSkills({ + 'makers-a/references/long.md': longReference('Compare A - [Two](#two) is inline prose.\n\n'), + }); + try { + assert.deepEqual( + findMissingTocs(root).map((x) => x.file), + ['makers-a/references/long.md'], + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs treats 100 lines as short and 101 as long', async () => { + // 钉住 <= TOC_LINE_THRESHOLD 这道边界(而非 < )。 + // 行数是 split 段数:99 个 \n + 结尾换行 = 100 段。 + const root = await makeSkills({ + 'makers-a/references/at-100.md': `${Array.from({ length: 99 }, (_, i) => `a ${i}`).join('\n')}\n`, + 'makers-b/references/at-101.md': `${Array.from({ length: 100 }, (_, i) => `b ${i}`).join('\n')}\n`, + }); + try { + assert.deepEqual(findMissingTocs(root), [ + { file: 'makers-b/references/at-101.md', lines: 101 }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findOversizedFiles treats 500 lines as fine and 501 as oversized', async () => { + // 钉住 > MAX_FILE_LINES 这道边界(而非 >= )。 + const root = await makeSkills({ + 'makers-a/references/at-500.md': `${Array.from({ length: 499 }, (_, i) => `a ${i}`).join('\n')}\n`, + 'makers-b/references/at-501.md': `${Array.from({ length: 500 }, (_, i) => `b ${i}`).join('\n')}\n`, + }); + try { + assert.deepEqual(findOversizedFiles(root), [ + { file: 'makers-b/references/at-501.md', lines: 501, cap: 500 }, + ]); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findMissingTocs only exempts a real SKILL.md, not a lookalike filename', async () => { + // endsWith('SKILL.md') 会把 MY-SKILL.md 一起豁免,而 Task 15 拿这个检测当验收门, + // 于是这种文件永远补不上目录也没人报。 + const root = await makeSkills({ + 'makers-a/references/MY-SKILL.md': longReference(), + 'makers-a/SKILL.md': `---\nname: a\n---\n\n${longReference()}`, + }); + try { + assert.deepEqual( + findMissingTocs(root).map((x) => x.file), + ['makers-a/references/MY-SKILL.md'], + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 56381af173566a7dcc443b541295caca5249cd9c Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 03:58:08 +0800 Subject: [PATCH 09/29] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20npm=20test=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E5=B9=B6=E5=9C=A8=20CI=20=E4=B8=AD=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 3 +++ package.json | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 package.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8c7539..cce7eda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,9 @@ jobs: with: node-version: '20' + - name: Run tests + run: npm test + - name: Build multi-platform output run: node scripts/build.mjs diff --git a/package.json b/package.json new file mode 100644 index 0000000..11c67e3 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "edgeone-makers-tools", + "version": "1.2.0", + "private": true, + "type": "module", + "description": "EdgeOne Makers platform development skills.", + "scripts": { + "test": "node --test hooks/*.test.mjs scripts/*.test.mjs scripts/lib/*.test.mjs", + "doctor": "node scripts/doctor.mjs", + "build": "node scripts/build.mjs" + } +} From 6cd60879197fc31d2c91ec353b678e8b3e7bd6fc Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 04:39:14 +0800 Subject: [PATCH 10/29] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20KV=20=E5=89=8D?= =?UTF-8?q?=E7=BD=AE=E6=9D=A1=E4=BB=B6=E7=9A=84=E6=96=AD=E9=93=BE=EF=BC=8C?= =?UTF-8?q?=E6=8C=87=E5=90=91=20makers-storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 2 +- skills/makers-edge-functions/SKILL.md | 6 +++--- skills/makers-recipes/SKILL.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 0dc498c..70ef3a8 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -38,7 +38,7 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../makers-storage/SKILL.md). **Do NOT use for:** - Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-pages-dev` diff --git a/skills/makers-edge-functions/SKILL.md b/skills/makers-edge-functions/SKILL.md index 7345171..72ff1ae 100644 --- a/skills/makers-edge-functions/SKILL.md +++ b/skills/makers-edge-functions/SKILL.md @@ -108,9 +108,9 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../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). +⚠️ **Prerequisites**: You must enable KV Storage in the EdgeOne Makers console, create a namespace, and bind it to your project before using KV. See [makers-storage/references/kv.md](../makers-storage/references/kv.md) for full setup instructions. The KV namespace is a **global variable** (name is set when binding in the console) — it is **NOT** on `context.env`. @@ -134,7 +134,7 @@ export async function onRequest(context) { } ``` -For full KV Storage API reference and usage guide, see: [kv-storage.md](kv-storage.md) (same directory). +For full KV Storage API reference and usage guide, see [makers-storage/references/kv.md](../makers-storage/references/kv.md). ## Supported Runtime APIs diff --git a/skills/makers-recipes/SKILL.md b/skills/makers-recipes/SKILL.md index 25da228..5fc18a0 100644 --- a/skills/makers-recipes/SKILL.md +++ b/skills/makers-recipes/SKILL.md @@ -141,7 +141,7 @@ my-app/ ## Edge API + KV counter -⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [kv-storage.md](kv-storage.md) (same directory) +⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [makers-storage/references/kv.md](../makers-storage/references/kv.md). ``` my-app/ From 4b5221830e339369e4657dcbae8a13cd5b773f91 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 04:39:52 +0800 Subject: [PATCH 11/29] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20makers-migration=20?= =?UTF-8?q?=E8=B7=A8=20skill=20=E5=BC=95=E7=94=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-migration/SKILL.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/skills/makers-migration/SKILL.md b/skills/makers-migration/SKILL.md index 449d501..09dd4a1 100644 --- a/skills/makers-migration/SKILL.md +++ b/skills/makers-migration/SKILL.md @@ -148,7 +148,7 @@ openai>=1.50.0 6. Replace custom tools with `ctx.tools.to_crewai_tools(BaseTool)` 7. Return SSE via `ctx.utils.stream_sse(gen())` -> See [makers-agents/skills/python-frameworks/crewai.md](../skills/makers-agents/references/python-frameworks/crewai.md) for the complete pattern. +> See [makers-agents/references/python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) for the complete pattern. > Detailed before/after: [references/crewai-to-makers.md](references/crewai-to-makers.md) --- @@ -197,9 +197,9 @@ openai>=1.50.0 6. Set `thread_id`: `{ configurable: { thread_id: context.conversation_id } }` 7. Replace response with SSE streaming pattern -> Node: [makers-agents/skills/node-frameworks/langgraph.md](../skills/makers-agents/references/node-frameworks/langgraph.md) -> Python: [makers-agents/skills/python-frameworks/langgraph.md](../skills/makers-agents/references/python-frameworks/langgraph.md) -> DeepAgents: [makers-agents/skills/node-frameworks/deepagents.md](../skills/makers-agents/references/node-frameworks/deepagents.md) +> Node: [makers-agents/references/node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) +> Python: [makers-agents/references/python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) +> DeepAgents: [makers-agents/references/node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) > Detailed before/after: [references/langgraph-to-makers.md](references/langgraph-to-makers.md), [references/deepagents-to-makers.md](references/deepagents-to-makers.md) --- @@ -246,8 +246,8 @@ openai>=1.50.0 4. Use `context.store.openaiSession(conversationId)` for session (Node) 5. Map stream events to SSE: `output_text_delta` → `ai_response`, `tool_called` → `tool_call` -> Node: [makers-agents/skills/node-frameworks/openai-agents.md](../skills/makers-agents/references/node-frameworks/openai-agents.md) -> Python: [makers-agents/skills/python-frameworks/openai-agents.md](../skills/makers-agents/references/python-frameworks/openai-agents.md) +> Node: [makers-agents/references/node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) +> Python: [makers-agents/references/python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) > Detailed before/after: [references/openai-agents-to-makers.md](references/openai-agents-to-makers.md) --- @@ -296,8 +296,8 @@ openai>=1.50.0 5. Node only: swallow `EPIPE` on `process.stdout` 6. Set writable config dirs: `CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk`, `CLAUDE_CODE_TMPDIR=/tmp` -> Node: [makers-agents/skills/node-frameworks/claude-sdk.md](../skills/makers-agents/references/node-frameworks/claude-sdk.md) -> Python: [makers-agents/skills/python-frameworks/claude-sdk.md](../skills/makers-agents/references/python-frameworks/claude-sdk.md) +> Node: [makers-agents/references/node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) +> Python: [makers-agents/references/python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) > Detailed before/after: [references/claude-agent-sdk-to-makers.md](references/claude-agent-sdk-to-makers.md) --- From b202a309e4320611a71b1946bef90ca91de8f091 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 04:58:22 +0800 Subject: [PATCH 12/29] =?UTF-8?q?makers-migration=20=E7=9A=84=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E5=BC=95=E7=94=A8=E6=94=B9=E4=B8=BA=E5=8D=95=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-migration/SKILL.md | 17 +++++++++++++++++ .../references/claude-agent-sdk-to-makers.md | 4 ++-- .../references/crewai-to-makers.md | 2 +- .../references/deepagents-to-makers.md | 2 +- .../references/langgraph-to-makers.md | 2 +- .../references/openai-agents-to-makers.md | 2 +- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/skills/makers-migration/SKILL.md b/skills/makers-migration/SKILL.md index 09dd4a1..b2bd53a 100644 --- a/skills/makers-migration/SKILL.md +++ b/skills/makers-migration/SKILL.md @@ -452,6 +452,23 @@ After migration, verify these items before deploying: ## See Also - Agent development guide: [makers-agents/SKILL.md](../makers-agents/SKILL.md) + +### Framework reference index + +Reference files in this skill link back here instead of climbing two directory +levels. Full framework patterns live in `makers-agents`: + +| Framework | Node | Python | +|---|---|---| +| LangGraph | [node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) | [python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) | +| DeepAgents | [node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) | [python-frameworks/deepagents.md](../makers-agents/references/python-frameworks/deepagents.md) | +| OpenAI Agents | [node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) | [python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) | +| Claude Agent SDK | [node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) | [python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) | +| CrewAI | — (Python only) | [python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) | + +Platform capabilities: [capabilities/sandbox.md](../makers-agents/references/capabilities/sandbox.md) · +[capabilities/store.md](../makers-agents/references/capabilities/store.md) · +[capabilities/tools.md](../makers-agents/references/capabilities/tools.md) - Platform conventions: [makers-agents/references/platform/](../makers-agents/references/platform/) - CLI commands: [makers-cli/SKILL.md](../makers-cli/SKILL.md) - Deploy guide: [makers-deploy/SKILL.md](../makers-deploy/SKILL.md) diff --git a/skills/makers-migration/references/claude-agent-sdk-to-makers.md b/skills/makers-migration/references/claude-agent-sdk-to-makers.md index 989d4a7..5e11b6b 100644 --- a/skills/makers-migration/references/claude-agent-sdk-to-makers.md +++ b/skills/makers-migration/references/claude-agent-sdk-to-makers.md @@ -147,7 +147,7 @@ export async function onRequest(context: any) { 2. **Writable config/temp dirs** — the SDK subprocess needs writable `~/.claude` and temp; set `CLAUDE_CONFIG_DIR='/tmp/claude-agent-sdk'` and `CLAUDE_CODE_TMPDIR='/tmp'` in `options.env`. 3. **No `process.env`** — agent endpoints disable `process.env`; always use `context.env`. 4. **Tools** — `context.tools.toClaudeMcpServer('edgeone', { alwaysLoad: true })` returns `{ name, tools, allowedTools }`; pass the created server as `edgeone` and set `allowedTools`. -5. **Sandbox** — if you use `context.sandbox`, its `/tmp` is per-request and easily lost; keep a process-level file cache and re-upload every request. See [makers-agents capabilities/sandbox.md](../../makers-agents/references/capabilities/sandbox.md). +5. **Sandbox** — if you use `context.sandbox`, its `/tmp` is per-request and easily lost; keep a process-level file cache and re-upload every request. See the platform capabilities line of [Framework reference index](../SKILL.md#framework-reference-index). --- @@ -163,7 +163,7 @@ Native `claude_agent_sdk` (Python) uses `query()` / `ClaudeAgentOptions`. On Mak - Stream via `ctx.utils.stream_sse(gen())` - `buildCommand: ""`, `outputDirectory: ""` -See [makers-agents python-frameworks/claude-sdk.md](../../makers-agents/references/python-frameworks/claude-sdk.md). +See the Claude Agent SDK row of [Framework reference index](../SKILL.md#framework-reference-index). --- diff --git a/skills/makers-migration/references/crewai-to-makers.md b/skills/makers-migration/references/crewai-to-makers.md index 29441c4..be84fbe 100644 --- a/skills/makers-migration/references/crewai-to-makers.md +++ b/skills/makers-migration/references/crewai-to-makers.md @@ -117,7 +117,7 @@ crewai>=1.14.5 openai>=1.50.0 ``` -> For tool streaming, subscribe to `crewai_event_bus` `LLMStreamChunkEvent` → `ai_response` and `TaskCompletedEvent` → `tool_result` (see [makers-agents python-frameworks/crewai.md](../../makers-agents/references/python-frameworks/crewai.md) §6). For platform tools use `ctx.tools.to_crewai_tools(BaseTool)`. +> For tool streaming, subscribe to `crewai_event_bus` `LLMStreamChunkEvent` → `ai_response` and `TaskCompletedEvent` → `tool_result` (see the CrewAI row of [Framework reference index](../SKILL.md#framework-reference-index), §6). For platform tools use `ctx.tools.to_crewai_tools(BaseTool)`. --- diff --git a/skills/makers-migration/references/deepagents-to-makers.md b/skills/makers-migration/references/deepagents-to-makers.md index c95ff40..3455736 100644 --- a/skills/makers-migration/references/deepagents-to-makers.md +++ b/skills/makers-migration/references/deepagents-to-makers.md @@ -136,7 +136,7 @@ Same `createDeepAgent({ model, systemPrompt, tools, maxTurns })` API. On Makers: - Stream via `ctx.utils.stream_sse(gen())` - `buildCommand: ""`, `outputDirectory: ""` -See [makers-agents python-frameworks/deepagents.md](../../makers-agents/references/python-frameworks/deepagents.md). +See the DeepAgents row of [Framework reference index](../SKILL.md#framework-reference-index). --- diff --git a/skills/makers-migration/references/langgraph-to-makers.md b/skills/makers-migration/references/langgraph-to-makers.md index 9ec8b92..db9e983 100644 --- a/skills/makers-migration/references/langgraph-to-makers.md +++ b/skills/makers-migration/references/langgraph-to-makers.md @@ -162,7 +162,7 @@ Native LangGraph-Python uses the same `StateGraph`/`MemorySaver`/`tool` API. On - Stream via `ctx.utils.stream_sse(gen())` - `buildCommand: ""`, `outputDirectory: ""` in `edgeone.json` -See [makers-agents python-frameworks/langgraph.md](../../makers-agents/references/python-frameworks/langgraph.md). +See the LangGraph row of [Framework reference index](../SKILL.md#framework-reference-index). --- diff --git a/skills/makers-migration/references/openai-agents-to-makers.md b/skills/makers-migration/references/openai-agents-to-makers.md index 41da189..64671a3 100644 --- a/skills/makers-migration/references/openai-agents-to-makers.md +++ b/skills/makers-migration/references/openai-agents-to-makers.md @@ -144,7 +144,7 @@ Native `@openai/agents` (Python) uses `Agent` + `Runner.run()`. On Makers: - Stream via `ctx.utils.stream_sse(gen())`, mapping `output_text_delta` → `ai_response`, `tool_called` → `tool_call` - `buildCommand: ""`, `outputDirectory: ""` -See [makers-agents python-frameworks/openai-agents.md](../../makers-agents/references/python-frameworks/openai-agents.md). +See the OpenAI Agents row of [Framework reference index](../SKILL.md#framework-reference-index). --- From 7a540b2f7cab3a581b4ec3f3a32a58ec21011459 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 04:59:57 +0800 Subject: [PATCH 13/29] =?UTF-8?q?=E6=B8=85=E9=99=A4=204=20=E4=B8=AA?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=20skill=20=E5=90=8D?= =?UTF-8?q?=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 9 +++++---- skills/makers-deploy/SKILL.md | 5 +++-- skills/makers-migration/SKILL.md | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 70ef3a8..16f8e67 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -11,8 +11,9 @@ description: >- agent endpoint", "wire LangGraph into Makers", "stream LLM responses with SSE", "review my agent template", "use context.store / context.sandbox / context.tools". Do NOT trigger for plain Edge Functions, Cloud Functions, or middleware - (those don't run AI logic — use edgeone-pages-dev instead). - Do NOT trigger for deployment workflows (use edgeone-pages-deploy). + (those don't run AI logic — use edgeone-makers-edge-functions, + makers-cloud-functions, or edgeone-makers-middleware instead). + Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. metadata: @@ -41,8 +42,8 @@ This skill covers five supported frameworks (DeepAgents, LangGraph, CrewAI, Open > Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../makers-storage/SKILL.md). **Do NOT use for:** -- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-pages-dev` -- Deployment workflows → use `edgeone-pages-deploy` +- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-makers-edge-functions`, `makers-cloud-functions`, or `edgeone-makers-middleware` +- Deployment workflows → use `edgeone-makers-deploy` - Generic AI framework development outside an EdgeOne Makers project - Other platforms (Cloudflare Workers AI, Vercel AI SDK, AWS Bedrock) diff --git a/skills/makers-deploy/SKILL.md b/skills/makers-deploy/SKILL.md index 2d0f3e3..2693caf 100644 --- a/skills/makers-deploy/SKILL.md +++ b/skills/makers-deploy/SKILL.md @@ -10,8 +10,9 @@ description: >- "搭建并部署", "开发并上线", "build and deploy", "create and deploy". ⚠️ Also trigger when any agent is about to execute `edgeone makers deploy` or `edgeone makers deploy` commands — the skill contains critical rules for parsing deploy output and presenting access URLs. - Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy — - use edgeone-makers-dev for troubleshooting). + Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — + route those to the skill owning the runtime: edgeone-makers-edge-functions, + makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. metadata: author: edgeone version: "2.2.0" diff --git a/skills/makers-migration/SKILL.md b/skills/makers-migration/SKILL.md index b2bd53a..9ca349c 100644 --- a/skills/makers-migration/SKILL.md +++ b/skills/makers-migration/SKILL.md @@ -6,7 +6,7 @@ description: >- Use when the user wants to adapt a standard agent project to run on EdgeOne Makers, convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). - Do NOT trigger for new agent projects (use makers-agents instead). + Do NOT trigger for new agent projects (use edgeone-makers-agents instead). metadata: author: edgeone version: "1.0.0" From d58ecb25d5614fc0b288da29cfd3a9c94ec98c1b Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 05:01:48 +0800 Subject: [PATCH 14/29] =?UTF-8?q?=E6=8B=86=E5=88=86=20crewai.md=20?= =?UTF-8?q?=E8=87=B3=20500=20=E8=A1=8C=E4=BB=A5=E5=86=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 2 + .../python-frameworks/crewai-integration.md | 107 ++++++++++ .../references/python-frameworks/crewai.md | 197 +----------------- .../python-runtime-conventions.md | 94 +++++++++ 4 files changed, 207 insertions(+), 193 deletions(-) create mode 100644 skills/makers-agents/references/python-frameworks/crewai-integration.md create mode 100644 skills/makers-agents/references/python-frameworks/python-runtime-conventions.md diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 16f8e67..103421d 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -242,6 +242,8 @@ Need a sandbox to run code, process uploaded files, or use MCP tools? | LangGraph (Python) | [python-frameworks/langgraph.md](references/python-frameworks/langgraph.md) | | DeepAgents (Python) | [python-frameworks/deepagents.md](references/python-frameworks/deepagents.md) | | CrewAI (Python only) | [python-frameworks/crewai.md](references/python-frameworks/crewai.md) | +| CrewAI — tool integration, review checklist, pitfalls | [python-frameworks/crewai-integration.md](references/python-frameworks/crewai-integration.md) | +| Python runtime conventions (all Python routes) | [python-frameworks/python-runtime-conventions.md](references/python-frameworks/python-runtime-conventions.md) | | Review checklist | [review-checklist.md](references/review-checklist.md) | --- diff --git a/skills/makers-agents/references/python-frameworks/crewai-integration.md b/skills/makers-agents/references/python-frameworks/crewai-integration.md new file mode 100644 index 0000000..7591728 --- /dev/null +++ b/skills/makers-agents/references/python-frameworks/crewai-integration.md @@ -0,0 +1,107 @@ +# Route E: CrewAI — Integration & Review + +> Continues [crewai.md](crewai.md). Read that first for runtime conventions and the core pattern. + +## Tool integration (context.tools) + +Once `edgeone.json` sets `agents.framework: 'crewai'`, `context.tools` returns CrewAI `BaseTool` instances: + +```python +async def handler(context): + # ⭐ Must use to_crewai_tools to get real CrewAI BaseTool instances + from crewai import BaseTool + tools = context.tools.to_crewai_tools(BaseTool) + + crew = Crew( + agents=[Agent(role="...", tools=tools, llm=llm)], + tasks=[...], + ) +``` + +> Use `ctx.tools.to_crewai_tools(BaseTool)` to get real CrewAI `BaseTool` instances. This injects the CrewAI class at call time so the toolkit doesn't depend on CrewAI directly. + +--- + +## Route E review checklist + +- [ ] `edgeone.json` sets `agents.framework` (`crewai` or `langgraph` for hybrid) +- [ ] `requirements.txt` exists and versions align with the platform's bundled lib +- [ ] LLM construction uses `provider="openai"` (bypassing LiteLLM) +- [ ] `LLM` / Crew / OpenAI client use a module-level singleton + env fingerprint reset +- [ ] env is read solely from `context.env`; **never from `os.environ`** (frontend code is exempt) +- [ ] Crew has `memory=False` + `verbose=False` (events go through event_bus, nothing on stdout) +- [ ] `crew.kickoff()` is wrapped in `asyncio.to_thread` (does not block the event loop) +- [ ] event_bus bridges `LLMStreamChunkEvent` → SSE `ai_response` and `TaskCompletedEvent` → `tool_result` +- [ ] SSE frame format `data: \n\n` + 5-second `ping` heartbeat + closing `[DONE]` +- [ ] AbortSignal: Python uses `context.request.signal.is_set()` (not `.aborted`) +- [ ] `/stop` calls `context.utils.abort_active_run(conversation_id)` (snake_case) +- [ ] Memory API uses snake_case: `store.append_message(conversation_id=..., ...)` / `store.get_messages(conversation_id=...)` +- [ ] `/stop` reads body only — **no** `makers-conversation-id` header +- [ ] Templates that use the `web_search` tool have `WSA_API_KEY` configured +- [ ] ⭐ Frontend calls this endpoint with the `makers-conversation-id` header (the frontend is TypeScript, identical to the TS routes) + +--- + +## Frontend call example (frontend is TS, identical to other routes) + +```typescript +// Frontend code example +const conversationId = getOrCreateConversationId(); // UUID cached in localStorage + +const resp = await fetch('/email/run', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'makers-conversation-id': conversationId, // ⭐ required + }, + body: JSON.stringify({ task: 'daily_digest' }), +}); + +// /stop (NEVER include the header) +await fetch('/email/stop', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ conversation_id: conversationId }), +}); +``` + +--- + +## Quick comparison vs. A/B/C/D + +| Dimension | TS routes (A/B/C/D) | **Python route (E)** | +|------|------|------| +| Language | TypeScript | **Python** | +| Runtime config | `agents.framework` | `agents.framework` | +| Entry signature | `export async function onRequest(context)` | **`async def handler(context):`** | +| Naming style | camelCase | **snake_case** | +| Memory API | `store.appendMessage({ conversationId, role, content })` | **`await store.append_message(conversation_id=..., role=..., content=...)`** | +| Abort | `signal.aborted` | **`signal.is_set()`** | +| Stream orchestration | SDK-built-in or hand-written | event_bus bridge + asyncio.Queue + asyncio.to_thread | +| Multi-agent | C's Handoff / D's subAgents | **Crew + Process.sequential / hierarchical** | +| Built-in memory option | None | CrewAI's own `memory=True` (typically replaced by ctx.store) | +| Skill loading | None | `Crew(skills=[dir])` loads local SKILL.md | +| LiteLLM compatibility trap | None | ⭐ `provider="openai"` is mandatory (platform has no LiteLLM) | + +--- + +## Common pitfalls + +1. **`provider="openai"` not set** → CrewAI dispatches via LiteLLM, which is absent on the platform and will crash outright +2. **`crew.kickoff()` not wrapped in `asyncio.to_thread`** → blocks the event loop and stalls all SSE heartbeats +3. **`verbose=True` not flipped to False** → CrewAI logs to stdout and may corrupt the SSE stream +4. **`memory=True` enabled while also using `ctx.store`** → double-write, state desync +5. **Reading env via `os.environ.get("AI_GATEWAY_API_KEY")` directly** → must read from `context.env` (the platform-injected path) +6. **Python `.is_set()` written as `.aborted`** → AbortSignal never fires +7. **Calling `store.append_message` with camelCase** → wrong name, AttributeError +8. **`requirements.txt` not pinned, or grossly diverging from the bundled platform versions** → dependency conflicts and failed deployment + +See also: +- +- Route B (Claude Agent SDK): `../node-frameworks/claude-sdk.md` +- Route C (OpenAI Agents SDK): `../node-frameworks/openai-agents.md` +- Route D (LangGraph + DeepAgents): `../node-frameworks/langgraph.md` +- Platform conventions: `../platform/node-entry.md` +- Sandbox & tools: `../capabilities/sandbox.md` +- Memory store: `../capabilities/store.md` +- Review checklist: `review-checklist.md` diff --git a/skills/makers-agents/references/python-frameworks/crewai.md b/skills/makers-agents/references/python-frameworks/crewai.md index 554d033..7c5fe8d 100644 --- a/skills/makers-agents/references/python-frameworks/crewai.md +++ b/skills/makers-agents/references/python-frameworks/crewai.md @@ -39,98 +39,9 @@ --- -## Python Runtime Conventions (applies to all Python routes) +## Python Runtime Conventions -The Python agent runtime is an ASGI application (runs on uvicorn). It shares the same platform conventions as the Node runtime, but with Python-specific idioms. - -### Entry Signature - -```python -async def handler(ctx): - """Every Python agent endpoint exports a top-level `handler` function.""" - ... -``` - -- The parameter is an `AgentContext` dataclass (imported from `_platform.context` internally, but you never need to import it yourself). -- File-based routing: `agents//index.py` or `agents/.py` → `POST /` (same as TS). -- Internal modules use `_` prefix: `_llm.py`, `_tools.py`, `_state.py` etc. - -### Context Object (`ctx`) - -| Field | Type | Description | -|-------|------|-------------| -| `ctx.request.body` | `dict` | Parsed JSON request body | -| `ctx.request.headers` | `dict` | Request headers (lowercase keys) | -| `ctx.request.signal` | `asyncio.Event` | Cancellation signal — check with `ctx.request.signal.is_set()` | -| `ctx.request.query` | `dict` | URL query parameters | -| `ctx.env` | `dict` | Environment variables (⚠️ never use `os.environ` in agent code) | -| `ctx.conversation_id` | `str` | From `makers-conversation-id` header | -| `ctx.run_id` | `str` | Current run ID | -| `ctx.store` | `ConversationMemory` | Message history CRUD + LangGraph adapters | -| `ctx.tools` | Tools | Platform tools (lazy-loaded, shaped by `agents.framework`) | -| `ctx.sandbox` | Sandbox | Sandbox client (lazy-loaded) | -| `ctx.kv` | KV store | Per-route KV store | -| `ctx.utils` | `ContextUtils` | Platform utilities (SSE, abort, etc.) | - -### SSE Streaming (recommended pattern) - -```python -async def handler(ctx): - async def gen(): - yield ctx.utils.sse({"type": "ai_response", "content": "Hello"}) - yield ctx.utils.sse({"type": "ping", "ts": int(time.time() * 1000)}) - yield b"data: [DONE]\n\n" - return ctx.utils.stream_sse(gen()) -``` - -- `ctx.utils.sse(data, event=None)` → returns `bytes` (one SSE frame) -- `ctx.utils.stream_sse(gen())` → returns `StreamResponse` with correct headers (Content-Type, Cache-Control, X-Accel-Buffering, Connection) -- No need to manually set response headers — the platform handles them. - -### Memory / Store API (snake_case) - -```python -# Append a message -msg_id = await ctx.store.append_message(ctx.conversation_id, "user", "Hello!") - -# Get messages (ascending by time, for prompt construction) -messages = await ctx.store.get_messages(ctx.conversation_id, limit=50) - -# Convert to OpenAI format -openai_msgs = ctx.store.to_openai_input(messages) - -# LangGraph adapters (direct properties, snake_case) -checkpointer = ctx.store.langgraph_checkpointer -lg_store = ctx.store.langgraph_store -``` - -### /stop Endpoint - -```python -async def handler(ctx): - target = ctx.request.body.get("conversation_id") or "" - result = ctx.utils.abortActiveRun(target) # camelCase (aligned with Node) - # or: result = ctx.utils.abort_active_run(target) # snake_case alias - return { - "status": "aborted" if result.aborted else "idle", - "conversation_id": result.conversation_id, - "run_id": result.run_id, - } -``` - -### Key Differences from Node Runtime - -| Dimension | Node (TS) | Python | -|-----------|-----------|--------| -| Abort signal | `signal.aborted` (boolean) | `ctx.request.signal.is_set()` (asyncio.Event) | -| Abort utility | `ctx.utils.abortActiveRun(id)` | `ctx.utils.abortActiveRun(id)` or `ctx.utils.abort_active_run(id)` | -| SSE helper | `createSSEResponse(gen, signal)` | `ctx.utils.stream_sse(gen())` | -| Store methods | camelCase: `appendMessage`, `getMessages` | snake_case: `append_message`, `get_messages` | -| LangGraph adapters | `ctx.store.langgraphCheckpointer` | `ctx.store.langgraph_checkpointer` | -| Return type | `Response` object | `dict` / `StreamResponse` / async generator | -| Blocking work | N/A | Wrap in `asyncio.to_thread()` (e.g., `crew.kickoff()`) | - ---- +These apply to every Python route, not just CrewAI: [python-runtime-conventions.md](python-runtime-conventions.md). ## Core pattern breakdown @@ -496,106 +407,6 @@ async def handler(context): --- -## Tool integration (context.tools) - -Once `edgeone.json` sets `agents.framework: 'crewai'`, `context.tools` returns CrewAI `BaseTool` instances: - -```python -async def handler(context): - # ⭐ Must use to_crewai_tools to get real CrewAI BaseTool instances - from crewai import BaseTool - tools = context.tools.to_crewai_tools(BaseTool) - - crew = Crew( - agents=[Agent(role="...", tools=tools, llm=llm)], - tasks=[...], - ) -``` - -> Use `ctx.tools.to_crewai_tools(BaseTool)` to get real CrewAI `BaseTool` instances. This injects the CrewAI class at call time so the toolkit doesn't depend on CrewAI directly. - ---- - -## Route E review checklist - -- [ ] `edgeone.json` sets `agents.framework` (`crewai` or `langgraph` for hybrid) -- [ ] `requirements.txt` exists and versions align with the platform's bundled lib -- [ ] LLM construction uses `provider="openai"` (bypassing LiteLLM) -- [ ] `LLM` / Crew / OpenAI client use a module-level singleton + env fingerprint reset -- [ ] env is read solely from `context.env`; **never from `os.environ`** (frontend code is exempt) -- [ ] Crew has `memory=False` + `verbose=False` (events go through event_bus, nothing on stdout) -- [ ] `crew.kickoff()` is wrapped in `asyncio.to_thread` (does not block the event loop) -- [ ] event_bus bridges `LLMStreamChunkEvent` → SSE `ai_response` and `TaskCompletedEvent` → `tool_result` -- [ ] SSE frame format `data: \n\n` + 5-second `ping` heartbeat + closing `[DONE]` -- [ ] AbortSignal: Python uses `context.request.signal.is_set()` (not `.aborted`) -- [ ] `/stop` calls `context.utils.abort_active_run(conversation_id)` (snake_case) -- [ ] Memory API uses snake_case: `store.append_message(conversation_id=..., ...)` / `store.get_messages(conversation_id=...)` -- [ ] `/stop` reads body only — **no** `makers-conversation-id` header -- [ ] Templates that use the `web_search` tool have `WSA_API_KEY` configured -- [ ] ⭐ Frontend calls this endpoint with the `makers-conversation-id` header (the frontend is TypeScript, identical to the TS routes) - ---- - -## Frontend call example (frontend is TS, identical to other routes) - -```typescript -// Frontend code example -const conversationId = getOrCreateConversationId(); // UUID cached in localStorage - -const resp = await fetch('/email/run', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'makers-conversation-id': conversationId, // ⭐ required - }, - body: JSON.stringify({ task: 'daily_digest' }), -}); - -// /stop (NEVER include the header) -await fetch('/email/stop', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ conversation_id: conversationId }), -}); -``` - ---- - -## Quick comparison vs. A/B/C/D - -| Dimension | TS routes (A/B/C/D) | **Python route (E)** | -|------|------|------| -| Language | TypeScript | **Python** | -| Runtime config | `agents.framework` | `agents.framework` | -| Entry signature | `export async function onRequest(context)` | **`async def handler(context):`** | -| Naming style | camelCase | **snake_case** | -| Memory API | `store.appendMessage({ conversationId, role, content })` | **`await store.append_message(conversation_id=..., role=..., content=...)`** | -| Abort | `signal.aborted` | **`signal.is_set()`** | -| Stream orchestration | SDK-built-in or hand-written | event_bus bridge + asyncio.Queue + asyncio.to_thread | -| Multi-agent | C's Handoff / D's subAgents | **Crew + Process.sequential / hierarchical** | -| Built-in memory option | None | CrewAI's own `memory=True` (typically replaced by ctx.store) | -| Skill loading | None | `Crew(skills=[dir])` loads local SKILL.md | -| LiteLLM compatibility trap | None | ⭐ `provider="openai"` is mandatory (platform has no LiteLLM) | - ---- +## Next: integration & review -## Common pitfalls - -1. **`provider="openai"` not set** → CrewAI dispatches via LiteLLM, which is absent on the platform and will crash outright -2. **`crew.kickoff()` not wrapped in `asyncio.to_thread`** → blocks the event loop and stalls all SSE heartbeats -3. **`verbose=True` not flipped to False** → CrewAI logs to stdout and may corrupt the SSE stream -4. **`memory=True` enabled while also using `ctx.store`** → double-write, state desync -5. **Reading env via `os.environ.get("AI_GATEWAY_API_KEY")` directly** → must read from `context.env` (the platform-injected path) -6. **Python `.is_set()` written as `.aborted`** → AbortSignal never fires -7. **Calling `store.append_message` with camelCase** → wrong name, AttributeError -8. **`requirements.txt` not pinned, or grossly diverging from the bundled platform versions** → dependency conflicts and failed deployment - -See also: -- -- Route B (Claude Agent SDK): `../node-frameworks/claude-sdk.md` -- Route C (OpenAI Agents SDK): `../node-frameworks/openai-agents.md` -- Route D (LangGraph + DeepAgents): `../node-frameworks/langgraph.md` -- Platform conventions: `../platform/node-entry.md` -- Sandbox & tools: `../capabilities/sandbox.md` -- Memory store: `../capabilities/store.md` -- Review checklist: `review-checklist.md` +Tool integration, the review checklist, the frontend call example, the A/B/C/D comparison, and common pitfalls are in [crewai-integration.md](crewai-integration.md). diff --git a/skills/makers-agents/references/python-frameworks/python-runtime-conventions.md b/skills/makers-agents/references/python-frameworks/python-runtime-conventions.md new file mode 100644 index 0000000..45d1be9 --- /dev/null +++ b/skills/makers-agents/references/python-frameworks/python-runtime-conventions.md @@ -0,0 +1,94 @@ +# Python Runtime Conventions (all Python routes) + +> Shared by every Python route (CrewAI / LangGraph / DeepAgents / OpenAI Agents / Claude SDK). + + +The Python agent runtime is an ASGI application (runs on uvicorn). It shares the same platform conventions as the Node runtime, but with Python-specific idioms. + +### Entry Signature + +```python +async def handler(ctx): + """Every Python agent endpoint exports a top-level `handler` function.""" + ... +``` + +- The parameter is an `AgentContext` dataclass (imported from `_platform.context` internally, but you never need to import it yourself). +- File-based routing: `agents//index.py` or `agents/.py` → `POST /` (same as TS). +- Internal modules use `_` prefix: `_llm.py`, `_tools.py`, `_state.py` etc. + +### Context Object (`ctx`) + +| Field | Type | Description | +|-------|------|-------------| +| `ctx.request.body` | `dict` | Parsed JSON request body | +| `ctx.request.headers` | `dict` | Request headers (lowercase keys) | +| `ctx.request.signal` | `asyncio.Event` | Cancellation signal — check with `ctx.request.signal.is_set()` | +| `ctx.request.query` | `dict` | URL query parameters | +| `ctx.env` | `dict` | Environment variables (⚠️ never use `os.environ` in agent code) | +| `ctx.conversation_id` | `str` | From `makers-conversation-id` header | +| `ctx.run_id` | `str` | Current run ID | +| `ctx.store` | `ConversationMemory` | Message history CRUD + LangGraph adapters | +| `ctx.tools` | Tools | Platform tools (lazy-loaded, shaped by `agents.framework`) | +| `ctx.sandbox` | Sandbox | Sandbox client (lazy-loaded) | +| `ctx.kv` | KV store | Per-route KV store | +| `ctx.utils` | `ContextUtils` | Platform utilities (SSE, abort, etc.) | + +### SSE Streaming (recommended pattern) + +```python +async def handler(ctx): + async def gen(): + yield ctx.utils.sse({"type": "ai_response", "content": "Hello"}) + yield ctx.utils.sse({"type": "ping", "ts": int(time.time() * 1000)}) + yield b"data: [DONE]\n\n" + return ctx.utils.stream_sse(gen()) +``` + +- `ctx.utils.sse(data, event=None)` → returns `bytes` (one SSE frame) +- `ctx.utils.stream_sse(gen())` → returns `StreamResponse` with correct headers (Content-Type, Cache-Control, X-Accel-Buffering, Connection) +- No need to manually set response headers — the platform handles them. + +### Memory / Store API (snake_case) + +```python +# Append a message +msg_id = await ctx.store.append_message(ctx.conversation_id, "user", "Hello!") + +# Get messages (ascending by time, for prompt construction) +messages = await ctx.store.get_messages(ctx.conversation_id, limit=50) + +# Convert to OpenAI format +openai_msgs = ctx.store.to_openai_input(messages) + +# LangGraph adapters (direct properties, snake_case) +checkpointer = ctx.store.langgraph_checkpointer +lg_store = ctx.store.langgraph_store +``` + +### /stop Endpoint + +```python +async def handler(ctx): + target = ctx.request.body.get("conversation_id") or "" + result = ctx.utils.abortActiveRun(target) # camelCase (aligned with Node) + # or: result = ctx.utils.abort_active_run(target) # snake_case alias + return { + "status": "aborted" if result.aborted else "idle", + "conversation_id": result.conversation_id, + "run_id": result.run_id, + } +``` + +### Key Differences from Node Runtime + +| Dimension | Node (TS) | Python | +|-----------|-----------|--------| +| Abort signal | `signal.aborted` (boolean) | `ctx.request.signal.is_set()` (asyncio.Event) | +| Abort utility | `ctx.utils.abortActiveRun(id)` | `ctx.utils.abortActiveRun(id)` or `ctx.utils.abort_active_run(id)` | +| SSE helper | `createSSEResponse(gen, signal)` | `ctx.utils.stream_sse(gen())` | +| Store methods | camelCase: `appendMessage`, `getMessages` | snake_case: `append_message`, `get_messages` | +| LangGraph adapters | `ctx.store.langgraphCheckpointer` | `ctx.store.langgraph_checkpointer` | +| Return type | `Response` object | `dict` / `StreamResponse` / async generator | +| Blocking work | N/A | Wrap in `asyncio.to_thread()` (e.g., `crew.kickoff()`) | + From aa446cc6f1bfa46119af0731979aba2bd98a85c2 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 05:02:32 +0800 Subject: [PATCH 15/29] =?UTF-8?q?CLI=20=E6=9C=80=E4=BD=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=A6=81=E6=B1=82=E7=BB=9F=E4=B8=80=E4=B8=BA=201.6.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-deploy/SKILL.md | 30 +++++++++---------- .../references/command-reference.md | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/skills/makers-deploy/SKILL.md b/skills/makers-deploy/SKILL.md index 2693caf..5592f05 100644 --- a/skills/makers-deploy/SKILL.md +++ b/skills/makers-deploy/SKILL.md @@ -24,7 +24,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -39,7 +39,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -78,10 +78,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.0) +# Check 1: CLI installed and correct version? (must be >= 1.6.7) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -94,11 +94,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.0 | — | → Go to **Install CLI** | -| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.7 | — | → Go to **Install CLI** | +| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -108,7 +108,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -139,7 +139,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -291,7 +291,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.0) +### Preferred: `--json` (CLI ≥ 1.6.7) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -351,11 +351,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/skills/makers-deploy/references/command-reference.md b/skills/makers-deploy/references/command-reference.md index f1c77d2..ba067d8 100644 --- a/skills/makers-deploy/references/command-reference.md +++ b/skills/makers-deploy/references/command-reference.md @@ -45,7 +45,7 @@ edgeone makers link --name -t # Non-interactive | Action | Command | |--------|---------| | Install CLI | `npm install -g edgeone@latest` | -| Check version | `edgeone -v` (require ≥ 1.6.0) | +| Check version | `edgeone -v` (require ≥ 1.6.7) | | Login (China, browser) | `edgeone login --site china` | | Login (Global, browser) | `edgeone login --site global` | | Login (token, auto-site) | `edgeone login --token ` | From 18be56286b110925b56cb06df844c5f08b855152 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 05:04:15 +0800 Subject: [PATCH 16/29] =?UTF-8?q?=E9=A2=84=E8=A7=88=E5=89=8D=E5=BF=85?= =?UTF-8?q?=E9=A1=BB=E8=AF=A2=E9=97=AE=E7=94=A8=E6=88=B7=EF=BC=8C=E4=B8=8E?= =?UTF-8?q?=E4=B8=93=E5=AE=B6=E5=9B=A2=20prompt=20=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 2 +- skills/makers-env-adaption/SKILL.md | 16 +++++++++------- skills/makers-recipes/SKILL.md | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 103421d..60676c4 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -23,7 +23,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. diff --git a/skills/makers-env-adaption/SKILL.md b/skills/makers-env-adaption/SKILL.md index 4c6fcc3..6236ebf 100644 --- a/skills/makers-env-adaption/SKILL.md +++ b/skills/makers-env-adaption/SKILL.md @@ -25,12 +25,14 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` - ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ - │ -Enter ┤ -preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ Still iterating? ───┤ - └─ No ──→ start edgeone makers dev → present_files(...) +Dev done ──→ ASK the user how to verify (never assume) + │ + ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ + │ + ├─ "local preview" ────────────┐ + └─ "preview then deploy" ──────┤ + ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -176,7 +178,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: +After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. diff --git a/skills/makers-recipes/SKILL.md b/skills/makers-recipes/SKILL.md index 5fc18a0..a6d3a86 100644 --- a/skills/makers-recipes/SKILL.md +++ b/skills/makers-recipes/SKILL.md @@ -10,7 +10,7 @@ metadata: # Common Recipes -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. From 756a1d79c4afbe3f7b2008624873f04e856b4f3a Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 05:04:58 +0800 Subject: [PATCH 17/29] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20env-adaption=20?= =?UTF-8?q?=E7=AB=A0=E8=8A=82=E7=BC=96=E5=8F=B7=E6=96=AD=E8=A3=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-env-adaption/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/makers-env-adaption/SKILL.md b/skills/makers-env-adaption/SKILL.md index 6236ebf..b950551 100644 --- a/skills/makers-env-adaption/SKILL.md +++ b/skills/makers-env-adaption/SKILL.md @@ -226,7 +226,7 @@ present_files("https://my-app-w9t0lxe8.edgeone.cool") # --- -### 10. Next.js HMR cross-origin configuration +### 8. Next.js HMR cross-origin configuration The Next.js 15+ dev server trusts only `localhost` by default. Accessing it via `127.0.0.1` in the sandbox is treated as cross-origin, so the HMR WebSocket is blocked and the page becomes unresponsive. @@ -239,7 +239,7 @@ Note: the value is a **bare host**, without an `http://` prefix and without a po --- -### 11. Project linking (required for Blob/KV) +### 9. Project linking (required for Blob/KV) Projects that use Blob Storage or KV must ensure the project is linked (a `.edgeone/project.json` exists) before starting dev. When not linked, Blob/KV calls report `Missing: deployCredential`. @@ -264,7 +264,7 @@ If the project named by `--name` does not exist remotely, the `link` command cre --- -### 12. Framework version requirements +### 10. Framework version requirements | Framework/package | Minimum version | Reason | |---------|---------|------| From 687eebf0fc9a81d117e6909982833bec848489ab Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:01:44 +0800 Subject: [PATCH 18/29] =?UTF-8?q?=E4=B8=BA=E8=B6=85=20100=20=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=20reference=20=E8=A1=A5=E7=9B=AE=E5=BD=95=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=AE=A9=E6=82=AC=E7=A9=BA=E5=90=8D=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E9=94=9A=E7=82=B9=20slug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/add-toc.mjs | 93 +++++++++++++++++++ scripts/lib/skill-graph.mjs | 10 +- scripts/lib/skill-graph.test.mjs | 26 ++++++ .../references/capabilities/store.md | 12 +++ .../references/capabilities/tools.md | 12 +++ .../references/framework-native-patterns.md | 10 ++ .../references/node-frameworks/claude-sdk.md | 9 ++ .../references/node-frameworks/deepagents.md | 8 ++ .../references/node-frameworks/langgraph.md | 10 ++ .../node-frameworks/openai-agents.md | 9 ++ .../references/platform/node-entry.md | 6 ++ .../references/platform/python-entry.md | 14 +++ .../python-frameworks/claude-sdk.md | 9 ++ .../python-frameworks/crewai-integration.md | 8 ++ .../references/python-frameworks/crewai.md | 8 ++ .../python-frameworks/deepagents.md | 8 ++ .../references/python-frameworks/langgraph.md | 10 ++ .../python-frameworks/openai-agents.md | 9 ++ .../references/review-checklist.md | 15 +++ .../references/go-functions.md | 13 +++ .../references/node-functions.md | 15 +++ .../references/python-functions.md | 13 +++ .../references/troubleshooting.md | 10 ++ .../references/api-route-to-makers.md | 6 ++ .../references/claude-agent-sdk-to-makers.md | 6 ++ .../references/crewai-to-makers.md | 6 ++ .../references/deepagents-to-makers.md | 6 ++ .../references/langgraph-to-makers.md | 6 ++ .../references/openai-agents-to-makers.md | 6 ++ skills/makers-storage/references/blob.md | 10 ++ skills/makers-storage/references/kv.md | 12 +++ 31 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 scripts/add-toc.mjs diff --git a/scripts/add-toc.mjs b/scripts/add-toc.mjs new file mode 100644 index 0000000..886a13c --- /dev/null +++ b/scripts/add-toc.mjs @@ -0,0 +1,93 @@ +#!/usr/bin/env node +/** + * 一次性工具:为超 100 行、且开头没有锚点目录的 reference 插入目录。 + * 目录由文件自身的标题生成,插在 H1 之后、第一段正文之前。 + * + * 工作清单直接取自 findMissingTocs,保证生成器与验收闸门用同一套判定, + * 不会出现「脚本认为加过了、doctor 仍然报缺」的分歧。 + * + * 幂等:已有目录的文件不会出现在 findMissingTocs 结果里,重跑即零改动。 + */ +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { findMissingTocs } from './lib/skill-graph.mjs'; + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const SKILLS_DIR = join(REPO_ROOT, 'skills'); + +/** GitHub 风格锚点:小写、去标点、空格转连字符。保留 CJK。 */ +export function slugify(heading) { + return heading + .trim() + .toLowerCase() + .replace(/[`*_~]/g, '') + .replace(/[^\w一-龥\s-]/g, '') + .trim() + .replace(/\s+/g, '-'); +} + +/** + * 提取指定层级的标题,跳过代码块内的 #。 + * level 为 2 取 `## `,为 3 取 `### `。 + */ +export function extractHeadings(lines, level = 2) { + const pattern = new RegExp(`^#{${level}}\\s+(.+?)\\s*$`); + const headings = []; + let inFence = false; + lines.forEach((line, index) => { + if (/^```/.test(line)) { + inFence = !inFence; + return; + } + if (inFence) return; + const match = pattern.exec(line); + if (match) headings.push({ text: match[1], line: index }); + }); + return headings; +} + +/** + * 返回插入目录后的完整文本;两个层级都凑不出 2 条标题时原样返回。 + * + * `##` 不足 2 条时回退到 `###`:capabilities/tools.md 就是这种结构 + * (1 个 `##` + 9 个 `###`),不回退的话它永远留在 findMissingTocs 里, + * doctor 也就永远转不了绿。只回退、不混层,免得目录深浅不一。 + */ +export function withToc(content) { + const lines = content.split(/\r?\n/); + let headings = extractHeadings(lines, 2); + if (headings.length < 2) headings = extractHeadings(lines, 3); + if (headings.length < 2) return content; + + const toc = ['## Contents', '', ...headings.map((h) => `- [${h.text}](#${slugify(h.text)})`), '']; + + // 插入点:H1 之后的首个非空行之前;没有 H1 就插到文件开头。 + let insertAt = 0; + if (/^#\s+/.test(lines[0])) { + insertAt = 1; + while (insertAt < lines.length && lines[insertAt].trim() === '') insertAt += 1; + } + return [...lines.slice(0, insertAt), ...toc, ...lines.slice(insertAt)].join('\n'); +} + +function main() { + const targets = findMissingTocs(SKILLS_DIR); + let changed = 0; + for (const { file } of targets) { + const abs = join(SKILLS_DIR, file); + const before = readFileSync(abs, 'utf8'); + const after = withToc(before); + if (after !== before) { + writeFileSync(abs, after); + changed += 1; + console.log(` + ${file}`); + } else { + console.log(` ! 跳过(两个层级都不足 2 条标题)${file}`); + } + } + console.log(`\n${changed} / ${targets.length} 个文件已加目录`); +} + +main(); diff --git a/scripts/lib/skill-graph.mjs b/scripts/lib/skill-graph.mjs index 11850b8..9f32801 100644 --- a/scripts/lib/skill-graph.mjs +++ b/scripts/lib/skill-graph.mjs @@ -168,6 +168,9 @@ const SKILL_NAME_TOKEN = /\bedgeone-(?:makers|pages)-[a-z0-9-]+/g; /** marketplace / plugin 的产品 slug,不是 skill 名,不参与悬空判定。 */ const NON_SKILL_SLUGS = new Set(['edgeone-makers-tools']); +/** markdown 链接里的锚点目标 `](#…)`,扫描 skill 名前先剥掉。 */ +const ANCHOR_LINK_TARGET = /\]\(#[^)]*\)/g; + /** * 各 SKILL.md frontmatter 声明的 name 集合。 * @@ -192,12 +195,17 @@ export function listDeclaredSkillNames(root) { * * 读不到的文件不在这里单独记账:findBrokenLinks 走的是同一批文件, * 已经会把它们报出来,doctor 那层不需要同一个问题听三遍。 + * + * 锚点链接里的名字不算引用:`- [… EdgeOne Makers style](#…-edgeone-makers-style)` + * 这种目录条目,slug 会把散文标题压成看着像 skill 名的形状,但它指向的是 + * 本文件的小节,不是要加载的 skill。剥掉 `](#…)` 再扫,避免误报。 */ export function findDanglingSkillNames(root) { const declared = listDeclaredSkillNames(root); const dangling = []; forEachMarkdownLine(root, (file, line, lineNumber) => { - for (const match of line.matchAll(SKILL_NAME_TOKEN)) { + const scannable = line.replace(ANCHOR_LINK_TARGET, '](#)'); + for (const match of scannable.matchAll(SKILL_NAME_TOKEN)) { const name = match[0]; if (declared.has(name) || NON_SKILL_SLUGS.has(name)) continue; dangling.push({ file, line: lineNumber, name }); diff --git a/scripts/lib/skill-graph.test.mjs b/scripts/lib/skill-graph.test.mjs index b323754..726a32d 100644 --- a/scripts/lib/skill-graph.test.mjs +++ b/scripts/lib/skill-graph.test.mjs @@ -184,6 +184,32 @@ test('skill-graph.findDanglingSkillNames accepts declared names and the marketpl } }); +test('skill-graph.findDanglingSkillNames ignores a name that only appears inside an anchor slug', async () => { + const root = await makeSkills({ + 'makers-a/references/long.md': + '# Long\n\n- [Remediation: from Vercel style to EdgeOne Makers style](#remediation-from-vercel-style-to-edgeone-makers-style)\n\n## Remediation: from Vercel style to EdgeOne Makers style\n', + }); + try { + assert.deepEqual(findDanglingSkillNames(root), []); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('skill-graph.findDanglingSkillNames still flags a real name on a line that also has an anchor link', async () => { + const root = await makeSkills({ + 'makers-a/references/long.md': + '# Long\n\nUse edgeone-pages-dev, see [Setup](#setup-edgeone-makers-style).\n\n## Setup edgeone makers style\n', + }); + try { + const dangling = findDanglingSkillNames(root); + assert.equal(dangling.length, 1); + assert.equal(dangling[0].name, 'edgeone-pages-dev'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + test('skill-graph.findDeepReferenceLinks flags links that climb two levels', async () => { const root = await makeSkills({ 'makers-a/references/x.md': 'See [y](../../makers-b/references/y.md).\n', diff --git a/skills/makers-agents/references/capabilities/store.md b/skills/makers-agents/references/capabilities/store.md index 47dc7a5..0e164b9 100644 --- a/skills/makers-agents/references/capabilities/store.md +++ b/skills/makers-agents/references/capabilities/store.md @@ -1,5 +1,17 @@ # Memory / Store Cheat Sheet (Five Frameworks → context.store Adapters) +## Contents + +- [0. One-Sentence Mental Model](#0-one-sentence-mental-model) +- [1. Two Entry Points (First Decide Which Directory the Endpoint Lives In)](#1-two-entry-points-first-decide-which-directory-the-endpoint-lives-in) +- [2. Five-Framework Adapter Matrix (Core Cheat Sheet)](#2-five-framework-adapter-matrix-core-cheat-sheet) +- [3. API Signature Essentials (**single-object input — do not use two-arg form**)](#3-api-signature-essentials-single-object-input-do-not-use-two-arg-form) +- [4. Copy-Paste Snippets](#4-copy-paste-snippets) +- [5. Limits Cheat Sheet](#5-limits-cheat-sheet) +- [6. Choosing the Right Storage](#6-choosing-the-right-storage) +- [Python Store API (Route E and future Python routes)](#python-store-api-route-e-and-future-python-routes) +- [7. Review Red Lines (Spot Issues in 5 Seconds)](#7-review-red-lines-spot-issues-in-5-seconds) + > One-page reference: on EdgeOne Makers, which store entry point each Agent framework should use, how short-term/long-term memory is wired up, and how cloud-functions read it. --- diff --git a/skills/makers-agents/references/capabilities/tools.md b/skills/makers-agents/references/capabilities/tools.md index 334827e..1dbf615 100644 --- a/skills/makers-agents/references/capabilities/tools.md +++ b/skills/makers-agents/references/capabilities/tools.md @@ -1,5 +1,17 @@ # Tools Registry (context.tools) +## Contents + +- [2.1 ToolsContext Interface (@edgeone/pages-agent-toolkit)](#21-toolscontext-interface-edgeonepages-agent-toolkit) +- [2.2 Three Types of Tool Access](#22-three-types-of-tool-access) +- [2.3 Framework-Specific Tool Wiring](#23-framework-specific-tool-wiring) +- [2.4 Code Examples Per Framework](#24-code-examples-per-framework) +- [2.5 Getting a Single Tool / Group](#25-getting-a-single-tool-group) +- [⭐ web_search Configuration Requirements](#websearch-configuration-requirements) +- [web_search Return Value](#websearch-return-value) +- [web_search Input Parameters](#websearch-input-parameters) +- [web_search vs browser_* — when to use which](#websearch-vs-browser-when-to-use-which) + > Covers: ToolsContext interface, agents.framework-driven shape, 5-framework integration, built-in tools inventory. --- diff --git a/skills/makers-agents/references/framework-native-patterns.md b/skills/makers-agents/references/framework-native-patterns.md index c8fb78e..b7708ab 100644 --- a/skills/makers-agents/references/framework-native-patterns.md +++ b/skills/makers-agents/references/framework-native-patterns.md @@ -1,5 +1,15 @@ # Native Code Patterns Across Five Frameworks (Migration Reference) +## Contents + +- [0. Framework Positioning and Officially Recommended Path](#0-framework-positioning-and-officially-recommended-path) +- [1. LangGraph](#1-langgraph) +- [2. OpenAI Agents SDK](#2-openai-agents-sdk) +- [3. CrewAI](#3-crewai) +- [4. DeepAgents / LangChain createAgent](#4-deepagents-langchain-createagent) +- [5. Claude Agent SDK](#5-claude-agent-sdk) +- [6. Native → Makers Injection Cheat Sheet](#6-native-makers-injection-cheat-sheet) + > ⚠️ **Purpose**: This document shows the **official native patterns** for each Agent framework (i.e. what they look like *without* EdgeOne Makers injection). > **Do NOT copy these patterns into Makers templates** — on Makers, models go through the `context.env` gateway, tools come from `context.tools`, and storage comes from `context.store`. > Use this file to: ① understand the native shape of each framework, ② see by contrast what Makers injection saves you, ③ help teammates migrate from native usage to Makers. diff --git a/skills/makers-agents/references/node-frameworks/claude-sdk.md b/skills/makers-agents/references/node-frameworks/claude-sdk.md index 9b92bfb..b45db71 100644 --- a/skills/makers-agents/references/node-frameworks/claude-sdk.md +++ b/skills/makers-agents/references/node-frameworks/claude-sdk.md @@ -1,5 +1,14 @@ # Route B: Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`) +## Contents + +- [Dependencies](#dependencies) +- [When to Use Route B](#when-to-use-route-b) +- [Core Pattern Walkthrough](#core-pattern-walkthrough) +- [Route B Review Checklist](#route-b-review-checklist) +- [Frontend Call Example (chat + stop + file upload)](#frontend-call-example-chat-stop-file-upload) +- [See Also](#see-also) + > Use when: multi-step agentic flows, sandbox code execution, file processing, session memory. > Core pattern: `query()` + dual MCP servers (sandbox + custom tools) + session binding + SSE side channel. diff --git a/skills/makers-agents/references/node-frameworks/deepagents.md b/skills/makers-agents/references/node-frameworks/deepagents.md index 1a6ef55..6fdc425 100644 --- a/skills/makers-agents/references/node-frameworks/deepagents.md +++ b/skills/makers-agents/references/node-frameworks/deepagents.md @@ -1,5 +1,13 @@ # DeepAgents (Node) +## Contents + +- [Dependencies](#dependencies) +- [When to Pick DeepAgents](#when-to-pick-deepagents) +- [Core Pattern](#core-pattern) +- [Memory](#memory) +- [Review Checklist](#review-checklist) + > Use when: long-running tasks with automatic context compression, sub-agent orchestration, middleware (retry/call-limit). > Core pattern: `createDeepAgent({ model, systemPrompt, tools, middleware })` + `agent.stream({ messages }, { streamMode })`. diff --git a/skills/makers-agents/references/node-frameworks/langgraph.md b/skills/makers-agents/references/node-frameworks/langgraph.md index 7ed328e..e0220b0 100644 --- a/skills/makers-agents/references/node-frameworks/langgraph.md +++ b/skills/makers-agents/references/node-frameworks/langgraph.md @@ -1,5 +1,15 @@ # LangGraph (Node) +## Contents + +- [Dependencies](#dependencies) +- [When to Pick LangGraph](#when-to-pick-langgraph) +- [Core Pattern](#core-pattern) +- [Memory](#memory) +- [Stream Modes](#stream-modes) +- [Human-in-the-Loop](#human-in-the-loop) +- [Review Checklist](#review-checklist) + > Use when: fine-grained graph orchestration, custom node/edge control, human-in-the-loop (interrupt/resume), persistent thread state. > Core pattern: `StateGraph` + `compile({ checkpointer, store })` + `graph.stream()` → SSE. diff --git a/skills/makers-agents/references/node-frameworks/openai-agents.md b/skills/makers-agents/references/node-frameworks/openai-agents.md index 2aeb6e6..43da657 100644 --- a/skills/makers-agents/references/node-frameworks/openai-agents.md +++ b/skills/makers-agents/references/node-frameworks/openai-agents.md @@ -1,5 +1,14 @@ # Route C: OpenAI Agents SDK (`@openai/agents`) +## Contents + +- [Dependencies](#dependencies) +- [When to use Route C](#when-to-use-route-c) +- [Core pattern breakdown](#core-pattern-breakdown) +- [Route C review checklist](#route-c-review-checklist) +- [Frontend call examples](#frontend-call-examples) +- [Quick diff vs. other frameworks](#quick-diff-vs-other-frameworks) + > Use when: multi-agent collaboration (`handoff`), `guardrails`, or scenarios that need `Session` to auto-prepend history. > Core pattern: `Agent` + `run()` streaming + `context.store.openaiSession()` + event-to-SSE mapping. diff --git a/skills/makers-agents/references/platform/node-entry.md b/skills/makers-agents/references/platform/node-entry.md index 3d8353b..4f08886 100644 --- a/skills/makers-agents/references/platform/node-entry.md +++ b/skills/makers-agents/references/platform/node-entry.md @@ -1,5 +1,11 @@ # File Routing + onRequest Entry Convention +## Contents + +- [1. File Routing Convention](#1-file-routing-convention) +- [2. `onRequest` Entry Convention](#2-onrequest-entry-convention) +- [`externalNodeModules` (build config, usually not needed)](#externalnodemodules-build-config-usually-not-needed) + > Covers: file-based routing rules, `onRequest` signature, context fields, environment variable iron rule. --- diff --git a/skills/makers-agents/references/platform/python-entry.md b/skills/makers-agents/references/platform/python-entry.md index 0b05389..d55e195 100644 --- a/skills/makers-agents/references/platform/python-entry.md +++ b/skills/makers-agents/references/platform/python-entry.md @@ -1,5 +1,19 @@ # Python Agent Runtime Convention +## Contents + +- [Prerequisites](#prerequisites) +- [1. Entry Signature](#1-entry-signature) +- [2. Context Object (`ctx`)](#2-context-object-ctx) +- [3. SSE Streaming](#3-sse-streaming) +- [4. Return Values](#4-return-values) +- [5. Memory / Store API](#5-memory-store-api) +- [6. Abort / Stop Convention](#6-abort-stop-convention) +- [7. Node ↔ Python Naming Mapping](#7-node-python-naming-mapping) +- [8. Blocking Code (Critical for Python)](#8-blocking-code-critical-for-python) +- [9. File Routing (same as Node)](#9-file-routing-same-as-node) +- [See Also](#see-also) + > The Python agent runtime is an ASGI application (uvicorn). It shares the same platform conventions as the Node runtime (file-based routing, `makers-conversation-id` header contract, SSE protocol, etc.), but uses Python idioms. > Applies to: Route E (CrewAI) and any future Python-based routes (LangGraph Python, DeepAgents Python, etc.). diff --git a/skills/makers-agents/references/python-frameworks/claude-sdk.md b/skills/makers-agents/references/python-frameworks/claude-sdk.md index 27defce..e7c4b76 100644 --- a/skills/makers-agents/references/python-frameworks/claude-sdk.md +++ b/skills/makers-agents/references/python-frameworks/claude-sdk.md @@ -1,5 +1,14 @@ # Route B (Python): Claude Agent SDK +## Contents + +- [Dependencies](#dependencies) +- [When to Use This Route (Python)](#when-to-use-this-route-python) +- [Core Pattern Breakdown](#core-pattern-breakdown) +- [Key Differences from Node Route B](#key-differences-from-node-route-b) +- [Review Checklist (Python Claude SDK)](#review-checklist-python-claude-sdk) +- [See Also](#see-also) + > Use when: multi-step agentic flows, sandbox code execution, file processing, session memory. > Core pattern: `claude_agent_sdk.query()` + MCP servers + session binding + SSE streaming. > Python runtime — see [../platform/python-entry.md](./../platform/python-entry.md) for entry signature, ctx object, and SSE conventions. diff --git a/skills/makers-agents/references/python-frameworks/crewai-integration.md b/skills/makers-agents/references/python-frameworks/crewai-integration.md index 7591728..12660ce 100644 --- a/skills/makers-agents/references/python-frameworks/crewai-integration.md +++ b/skills/makers-agents/references/python-frameworks/crewai-integration.md @@ -1,5 +1,13 @@ # Route E: CrewAI — Integration & Review +## Contents + +- [Tool integration (context.tools)](#tool-integration-contexttools) +- [Route E review checklist](#route-e-review-checklist) +- [Frontend call example (frontend is TS, identical to other routes)](#frontend-call-example-frontend-is-ts-identical-to-other-routes) +- [Quick comparison vs. A/B/C/D](#quick-comparison-vs-abcd) +- [Common pitfalls](#common-pitfalls) + > Continues [crewai.md](crewai.md). Read that first for runtime conventions and the core pattern. ## Tool integration (context.tools) diff --git a/skills/makers-agents/references/python-frameworks/crewai.md b/skills/makers-agents/references/python-frameworks/crewai.md index 7c5fe8d..2d5a4ea 100644 --- a/skills/makers-agents/references/python-frameworks/crewai.md +++ b/skills/makers-agents/references/python-frameworks/crewai.md @@ -1,5 +1,13 @@ # Route E: CrewAI (Python-only) +## Contents + +- [When to use Route E](#when-to-use-route-e) +- [⚠️ Key differences between Python and TS routes (in one shot)](#key-differences-between-python-and-ts-routes-in-one-shot) +- [Python Runtime Conventions](#python-runtime-conventions) +- [Core pattern breakdown](#core-pattern-breakdown) +- [Next: integration & review](#next-integration-review) + > Use when: multi-agent collaboration (role split + Sequential/Hierarchical Process), YAML-configured Agent/Task is desired, or you want to leverage CrewAI's built-in skills / event_bus capabilities. > Core pattern: `Crew(agents, tasks, process)` + `crew.kickoff()` + bridging events to SSE. > ⚠️ **CrewAI has no official JS SDK** — this is the **only route among the five that requires the Python runtime**. diff --git a/skills/makers-agents/references/python-frameworks/deepagents.md b/skills/makers-agents/references/python-frameworks/deepagents.md index 2bc43cb..401a615 100644 --- a/skills/makers-agents/references/python-frameworks/deepagents.md +++ b/skills/makers-agents/references/python-frameworks/deepagents.md @@ -1,5 +1,13 @@ # DeepAgents (Python) +## Contents + +- [Dependencies](#dependencies) +- [When to Pick DeepAgents (Python)](#when-to-pick-deepagents-python) +- [Core Pattern](#core-pattern) +- [Memory](#memory) +- [Review Checklist](#review-checklist) + > Use when: long-running tasks with automatic context compression, sub-agent orchestration, middleware. > Core pattern: `create_deep_agent()` + `agent.astream()` → SSE. > Python runtime — see [../platform/python-entry.md](./../platform/python-entry.md) for entry signature, ctx object, and SSE conventions. diff --git a/skills/makers-agents/references/python-frameworks/langgraph.md b/skills/makers-agents/references/python-frameworks/langgraph.md index cea2975..6665e5b 100644 --- a/skills/makers-agents/references/python-frameworks/langgraph.md +++ b/skills/makers-agents/references/python-frameworks/langgraph.md @@ -1,5 +1,15 @@ # LangGraph (Python) +## Contents + +- [Dependencies](#dependencies) +- [When to Pick LangGraph (Python)](#when-to-pick-langgraph-python) +- [Core Pattern](#core-pattern) +- [Memory](#memory) +- [Stream Modes](#stream-modes) +- [Human-in-the-Loop](#human-in-the-loop) +- [Review Checklist](#review-checklist) + > Use when: fine-grained graph orchestration, custom node/edge control, human-in-the-loop (interrupt/resume), persistent thread state. > Core pattern: `StateGraph` + `compile(checkpointer=..., store=...)` + `graph.astream()` → SSE. > Python runtime — see [../platform/python-entry.md](./../platform/python-entry.md) for entry signature, ctx object, and SSE conventions. diff --git a/skills/makers-agents/references/python-frameworks/openai-agents.md b/skills/makers-agents/references/python-frameworks/openai-agents.md index 7679e76..b93bf80 100644 --- a/skills/makers-agents/references/python-frameworks/openai-agents.md +++ b/skills/makers-agents/references/python-frameworks/openai-agents.md @@ -1,5 +1,14 @@ # Route C (Python): OpenAI Agents SDK +## Contents + +- [Dependencies](#dependencies) +- [When to Use This Route (Python)](#when-to-use-this-route-python) +- [Core Pattern Breakdown](#core-pattern-breakdown) +- [Key Differences from Node Route C](#key-differences-from-node-route-c) +- [Review Checklist (Python OpenAI Agents)](#review-checklist-python-openai-agents) +- [See Also](#see-also) + > Use when: multi-agent collaboration (`handoff`), `guardrails`, or scenarios that need `Session` to auto-prepend history. > Core pattern: `Agent` + `Runner.run()` streaming + session + event-to-SSE mapping. > Python runtime — see [../platform/python-entry.md](./../platform/python-entry.md) for entry signature, ctx object, and SSE conventions. diff --git a/skills/makers-agents/references/review-checklist.md b/skills/makers-agents/references/review-checklist.md index fa34554..0da7a22 100644 --- a/skills/makers-agents/references/review-checklist.md +++ b/skills/makers-agents/references/review-checklist.md @@ -1,5 +1,20 @@ # EdgeOne Makers Agent Review Checklist +## Contents + +- [A. Directory Structure](#a-directory-structure) +- [B. Entry Point & Signature](#b-entry-point-signature) +- [C. Environment Variables & Models](#c-environment-variables-models) +- [D. SSE Protocol (where consistency tends to slip)](#d-sse-protocol-where-consistency-tends-to-slip) +- [E. Conversation ID & the /stop Dual Channel](#e-conversation-id-the-stop-dual-channel) +- [F. Robustness](#f-robustness) +- [G. Sandbox / Tools](#g-sandbox-tools) +- [H. Memory / Persistence (built on the official context.store API)](#h-memory-persistence-built-on-the-official-contextstore-api) +- [I. Frontend Integration (app/)](#i-frontend-integration-app) +- [Remediation Table: from "generic Vercel style" → "EdgeOne Makers style"](#remediation-table-from-generic-vercel-style-edgeone-makers-style) +- [J. Python Routes (Route E and future Python routes)](#j-python-routes-route-e-and-future-python-routes) +- [Bulk Refactor Workflow (SOP for teammates)](#bulk-refactor-workflow-sop-for-teammates) + > Purpose: when bulk-auditing or refactoring existing templates, walk this list top-to-bottom. Items marked ⚠️ are common foot-guns. --- diff --git a/skills/makers-cloud-functions/references/go-functions.md b/skills/makers-cloud-functions/references/go-functions.md index 1bf66ab..f2803af 100644 --- a/skills/makers-cloud-functions/references/go-functions.md +++ b/skills/makers-cloud-functions/references/go-functions.md @@ -1,5 +1,18 @@ # Go Functions +## Contents + +- [Development Modes](#development-modes) +- [Handler Mode](#handler-mode) +- [Framework Mode (Gin example)](#framework-mode-gin-example) +- [Framework Mode — Echo example](#framework-mode-echo-example) +- [File-system Routing (Handler mode)](#file-system-routing-handler-mode) +- [Dynamic Routes](#dynamic-routes) +- [Supported Frameworks](#supported-frameworks) +- [Local Development](#local-development) +- [Limits](#limits) +- [Template Projects](#template-projects) + Go runtime functions under `cloud-functions/`. High-performance compiled language with low memory footprint and fast cold start. Supports Handler mode (file-system routing) and Framework mode (Gin, Echo, Fiber, Chi). > **Runtime:** Go 1.26+ — cross-compiled automatically by the platform. No manual build configuration needed. diff --git a/skills/makers-cloud-functions/references/node-functions.md b/skills/makers-cloud-functions/references/node-functions.md index e0be788..b9f08da 100644 --- a/skills/makers-cloud-functions/references/node-functions.md +++ b/skills/makers-cloud-functions/references/node-functions.md @@ -1,5 +1,20 @@ # Node.js Functions +## Contents + +- [Basic function](#basic-function) +- [Handler methods](#handler-methods) +- [EventContext object](#eventcontext-object) +- [Using npm packages](#using-npm-packages) +- [Express integration](#express-integration) +- [Koa integration](#koa-integration) +- [File-system Routing](#file-system-routing) +- [Dynamic Routes](#dynamic-routes) +- [WebSocket](#websocket) +- [Local Development](#local-development) +- [Limits](#limits) +- [Template Projects](#template-projects) + Node.js v20.x runtime functions under `cloud-functions/`. Full npm ecosystem support. Ideal for complex backend logic, database access, Express/Koa frameworks, and WebSocket. > **Runtime:** Node.js v20.x — supports ES modules, full npm ecosystem, and WebSocket. diff --git a/skills/makers-cloud-functions/references/python-functions.md b/skills/makers-cloud-functions/references/python-functions.md index d09ccdc..0a4a115 100644 --- a/skills/makers-cloud-functions/references/python-functions.md +++ b/skills/makers-cloud-functions/references/python-functions.md @@ -1,5 +1,18 @@ # Python Functions +## Contents + +- [Development Modes](#development-modes) +- [Handler Mode](#handler-mode) +- [Flask Framework (WSGI)](#flask-framework-wsgi) +- [FastAPI Framework (ASGI)](#fastapi-framework-asgi) +- [File-system Routing](#file-system-routing) +- [Dynamic Route Parameters](#dynamic-route-parameters) +- [Dependency Management](#dependency-management) +- [Local Development](#local-development) +- [Limits](#limits) +- [Template Projects](#template-projects) + Python 3.10 runtime functions under `cloud-functions/`. Supports Handler class, WSGI (Flask/Django), and ASGI (FastAPI/Sanic) modes with automatic dependency detection. > **Runtime:** Python 3.10 — auto-detects framework, auto-installs dependencies, no manual configuration needed. diff --git a/skills/makers-cloud-functions/references/troubleshooting.md b/skills/makers-cloud-functions/references/troubleshooting.md index 139bb15..f61e82b 100644 --- a/skills/makers-cloud-functions/references/troubleshooting.md +++ b/skills/makers-cloud-functions/references/troubleshooting.md @@ -1,5 +1,15 @@ # Debugging & Troubleshooting +## Contents + +- [Local Preview / Dev Server Verification](#local-preview-dev-server-verification) +- [General Issues](#general-issues) +- [Edge Functions](#edge-functions) +- [KV Storage](#kv-storage) +- [Cloud Functions — Node.js](#cloud-functions-nodejs) +- [Cloud Functions — Go](#cloud-functions-go) +- [Cloud Functions — Python](#cloud-functions-python) + ## Local Preview / Dev Server Verification > **Scope: WorkBuddy only.** The symptoms and conclusions below were verified by hands-on testing in the **WorkBuddy** environment. diff --git a/skills/makers-migration/references/api-route-to-makers.md b/skills/makers-migration/references/api-route-to-makers.md index 5cd5b30..890add0 100644 --- a/skills/makers-migration/references/api-route-to-makers.md +++ b/skills/makers-migration/references/api-route-to-makers.md @@ -1,5 +1,11 @@ # Migration: Generic API Route → EdgeOne Makers (no-framework path) +## Contents + +- [Example 1: Next.js API Route (app router)](#example-1-nextjs-api-route-app-router) +- [Example 2: Express Router](#example-2-express-router) +- [Conversion Checklist](#conversion-checklist) + > This is the **framework-less fallback**. Use it when your project does **not** use any of the five > agent frameworks (LangGraph, DeepAgents, OpenAI Agents SDK, Claude Agent SDK, CrewAI) — e.g. a custom > agent loop wrapped in Express, a Next.js API route, or a plain HTTP endpoint. diff --git a/skills/makers-migration/references/claude-agent-sdk-to-makers.md b/skills/makers-migration/references/claude-agent-sdk-to-makers.md index 5e11b6b..d0607c6 100644 --- a/skills/makers-migration/references/claude-agent-sdk-to-makers.md +++ b/skills/makers-migration/references/claude-agent-sdk-to-makers.md @@ -1,5 +1,11 @@ # Migration: Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`) → EdgeOne Makers +## Contents + +- [Node](#node) +- [Python equivalent](#python-equivalent) +- [Conversion Checklist](#conversion-checklist) + Claude Agent SDK runs a Claude Code subprocess, so the biggest differences are: (1) route the model through AI Gateway by mapping `AI_GATEWAY_*` → `ANTHROPIC_*`, (2) provide a writable config/temp dir, (3) wire platform tools via `toClaudeMcpServer`, and (4) bind session memory through `claudeSessionStore`. --- diff --git a/skills/makers-migration/references/crewai-to-makers.md b/skills/makers-migration/references/crewai-to-makers.md index be84fbe..0126e19 100644 --- a/skills/makers-migration/references/crewai-to-makers.md +++ b/skills/makers-migration/references/crewai-to-makers.md @@ -1,5 +1,11 @@ # Migration: CrewAI (Python-only) → EdgeOne Makers +## Contents + +- [❌ Before — native CrewAI (Flask + direct OpenAI + LiteLLM)](#before-native-crewai-flask-direct-openai-litellm) +- [✅ After — Makers agent handler](#after-makers-agent-handler) +- [Conversion Checklist](#conversion-checklist) + CrewAI has no JS SDK, so this route is Python-only. The migration is about: routing the LLM through AI Gateway (`provider="openai"` to bypass LiteLLM), dropping `memory=True`/`verbose=True` for platform conventions, wrapping `crew.kickoff()` in a thread, and bridging the event_bus to Makers SSE. --- diff --git a/skills/makers-migration/references/deepagents-to-makers.md b/skills/makers-migration/references/deepagents-to-makers.md index 3455736..8730c44 100644 --- a/skills/makers-migration/references/deepagents-to-makers.md +++ b/skills/makers-migration/references/deepagents-to-makers.md @@ -1,5 +1,11 @@ # Migration: DeepAgents (Node + Python) → EdgeOne Makers +## Contents + +- [Node](#node) +- [Python equivalent](#python-equivalent) +- [Conversion Checklist](#conversion-checklist) + DeepAgents is a thin layer over LangGraph, so the migration is almost identical to LangGraph: swap the model endpoint, drop custom tools for `context.tools`, and replace the HTTP server with an `onRequest` handler. Memory reuses the LangGraph adapters. --- diff --git a/skills/makers-migration/references/langgraph-to-makers.md b/skills/makers-migration/references/langgraph-to-makers.md index db9e983..f1271dd 100644 --- a/skills/makers-migration/references/langgraph-to-makers.md +++ b/skills/makers-migration/references/langgraph-to-makers.md @@ -1,5 +1,11 @@ # Migration: LangGraph (Node + Python) → EdgeOne Makers +## Contents + +- [Node](#node) +- [Python equivalent](#python-equivalent) +- [Conversion Checklist](#conversion-checklist) + LangGraph's graph/state API is unchanged on Makers — you only swap three things: model endpoint, checkpointer/store backend, and the HTTP server. This file shows the native format and the exact changes. --- diff --git a/skills/makers-migration/references/openai-agents-to-makers.md b/skills/makers-migration/references/openai-agents-to-makers.md index 64671a3..c7b0146 100644 --- a/skills/makers-migration/references/openai-agents-to-makers.md +++ b/skills/makers-migration/references/openai-agents-to-makers.md @@ -1,5 +1,11 @@ # Migration: OpenAI Agents SDK (`@openai/agents`) → EdgeOne Makers +## Contents + +- [Node](#node) +- [Python equivalent](#python-equivalent) +- [Conversion Checklist](#conversion-checklist) + OpenAI Agents SDK has no special server format — it runs anywhere Node runs. The migration is about: routing the model through AI Gateway, replacing hand-built tools with `context.tools.all()`, swapping hand-managed history for `openaiSession`, and mapping SDK stream events to Makers SSE. --- diff --git a/skills/makers-storage/references/blob.md b/skills/makers-storage/references/blob.md index 155f180..fee7aa6 100644 --- a/skills/makers-storage/references/blob.md +++ b/skills/makers-storage/references/blob.md @@ -1,5 +1,15 @@ # Blob Storage +## Contents + +- [Quick Start](#quick-start) +- [Consistency Model](#consistency-model) +- [API Reference](#api-reference) +- [Examples](#examples) +- [Limits](#limits) +- [Common Errors](#common-errors) +- [Best Practices](#best-practices) + EdgeOne Makers Blob is a distributed **object storage** service for Makers Functions. Suitable for storing images, documents, user uploads, AI-generated content, and structured data sets. > ⚠️ Blob is for **Makers Functions (Cloud Functions)** — uses the `@edgeone/pages-blob` npm SDK (NOT a global variable like KV). diff --git a/skills/makers-storage/references/kv.md b/skills/makers-storage/references/kv.md index 65cf61d..e59447f 100644 --- a/skills/makers-storage/references/kv.md +++ b/skills/makers-storage/references/kv.md @@ -1,5 +1,17 @@ # KV Storage +## Contents + +- [Prerequisites (MUST complete before using KV)](#prerequisites-must-complete-before-using-kv) +- [Core Concept: KV is a Global Variable](#core-concept-kv-is-a-global-variable) +- [API Reference](#api-reference) +- [Examples](#examples) +- [Common Errors](#common-errors) +- [Limits](#limits) +- [Best Practices](#best-practices) +- [Local Development](#local-development) +- [Production Deployment](#production-deployment) + EdgeOne Makers KV is a globally distributed **key-value persistent storage** service deployed across multiple edge nodes. Data follows an eventual consistency model and synchronizes globally within **60 seconds**. > ⚠️ KV Storage is **only available in Edge Functions** — NOT supported in Node Functions. From cae0f37df12b958874d4262e428234381696c7d5 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:02:40 +0800 Subject: [PATCH 19/29] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20=5Fmeta.json=20?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=B8=85=E5=8D=95=E5=B9=B6=E5=B0=86=20doctor?= =?UTF-8?q?=20=E6=8E=A5=E5=85=A5=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 11 ++++++++ _meta.json | 11 +++++++- codex/makers-agents.md | 15 ++++++----- codex/makers-deploy.md | 35 +++++++++++++------------- codex/makers-edge-functions.md | 6 ++--- codex/makers-env-adaption.md | 22 ++++++++-------- codex/makers-migration.md | 35 +++++++++++++++++++------- codex/makers-recipes.md | 4 +-- cursor/rules/makers-agents.mdc | 15 ++++++----- cursor/rules/makers-deploy.mdc | 35 +++++++++++++------------- cursor/rules/makers-edge-functions.mdc | 6 ++--- cursor/rules/makers-env-adaption.mdc | 22 ++++++++-------- cursor/rules/makers-migration.mdc | 35 +++++++++++++++++++------- cursor/rules/makers-recipes.mdc | 4 +-- 14 files changed, 161 insertions(+), 95 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cce7eda..d3e482e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,17 @@ on: branches: [main] paths: - 'skills/**' + - 'scripts/**' + - 'hooks/**' + - '_meta.json' + - 'package.json' pull_request: paths: - 'skills/**' + - 'scripts/**' + - 'hooks/**' + - '_meta.json' + - 'package.json' jobs: build: @@ -22,6 +30,9 @@ jobs: - name: Run tests run: npm test + - name: Run doctor + run: npm run doctor + - name: Build multi-platform output run: node scripts/build.mjs diff --git a/_meta.json b/_meta.json index 2bd2717..5c1f66f 100644 --- a/_meta.json +++ b/_meta.json @@ -22,10 +22,12 @@ "skills/makers-agents/references/platform/python-entry.md", "skills/makers-agents/references/platform/sse-protocol.md", "skills/makers-agents/references/python-frameworks/claude-sdk.md", + "skills/makers-agents/references/python-frameworks/crewai-integration.md", "skills/makers-agents/references/python-frameworks/crewai.md", "skills/makers-agents/references/python-frameworks/deepagents.md", "skills/makers-agents/references/python-frameworks/langgraph.md", "skills/makers-agents/references/python-frameworks/openai-agents.md", + "skills/makers-agents/references/python-frameworks/python-runtime-conventions.md", "skills/makers-agents/references/review-checklist.md", "skills/makers-cli/SKILL.md", "skills/makers-cloud-functions/SKILL.md", @@ -38,9 +40,16 @@ "skills/makers-edge-functions/SKILL.md", "skills/makers-env-adaption/SKILL.md", "skills/makers-middleware/SKILL.md", + "skills/makers-migration/SKILL.md", + "skills/makers-migration/references/api-route-to-makers.md", + "skills/makers-migration/references/claude-agent-sdk-to-makers.md", + "skills/makers-migration/references/crewai-to-makers.md", + "skills/makers-migration/references/deepagents-to-makers.md", + "skills/makers-migration/references/langgraph-to-makers.md", + "skills/makers-migration/references/openai-agents-to-makers.md", "skills/makers-recipes/SKILL.md", "skills/makers-storage/SKILL.md", "skills/makers-storage/references/blob.md", "skills/makers-storage/references/kv.md" ] -} \ No newline at end of file +} diff --git a/codex/makers-agents.md b/codex/makers-agents.md index 0dc498c..60676c4 100644 --- a/codex/makers-agents.md +++ b/codex/makers-agents.md @@ -11,8 +11,9 @@ description: >- agent endpoint", "wire LangGraph into Makers", "stream LLM responses with SSE", "review my agent template", "use context.store / context.sandbox / context.tools". Do NOT trigger for plain Edge Functions, Cloud Functions, or middleware - (those don't run AI logic — use edgeone-pages-dev instead). - Do NOT trigger for deployment workflows (use edgeone-pages-deploy). + (those don't run AI logic — use edgeone-makers-edge-functions, + makers-cloud-functions, or edgeone-makers-middleware instead). + Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. metadata: @@ -22,7 +23,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. @@ -38,11 +39,11 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../makers-storage/SKILL.md). **Do NOT use for:** -- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-pages-dev` -- Deployment workflows → use `edgeone-pages-deploy` +- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-makers-edge-functions`, `makers-cloud-functions`, or `edgeone-makers-middleware` +- Deployment workflows → use `edgeone-makers-deploy` - Generic AI framework development outside an EdgeOne Makers project - Other platforms (Cloudflare Workers AI, Vercel AI SDK, AWS Bedrock) @@ -241,6 +242,8 @@ Need a sandbox to run code, process uploaded files, or use MCP tools? | LangGraph (Python) | [python-frameworks/langgraph.md](references/python-frameworks/langgraph.md) | | DeepAgents (Python) | [python-frameworks/deepagents.md](references/python-frameworks/deepagents.md) | | CrewAI (Python only) | [python-frameworks/crewai.md](references/python-frameworks/crewai.md) | +| CrewAI — tool integration, review checklist, pitfalls | [python-frameworks/crewai-integration.md](references/python-frameworks/crewai-integration.md) | +| Python runtime conventions (all Python routes) | [python-frameworks/python-runtime-conventions.md](references/python-frameworks/python-runtime-conventions.md) | | Review checklist | [review-checklist.md](references/review-checklist.md) | --- diff --git a/codex/makers-deploy.md b/codex/makers-deploy.md index 2d0f3e3..5592f05 100644 --- a/codex/makers-deploy.md +++ b/codex/makers-deploy.md @@ -10,8 +10,9 @@ description: >- "搭建并部署", "开发并上线", "build and deploy", "create and deploy". ⚠️ Also trigger when any agent is about to execute `edgeone makers deploy` or `edgeone makers deploy` commands — the skill contains critical rules for parsing deploy output and presenting access URLs. - Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy — - use edgeone-makers-dev for troubleshooting). + Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — + route those to the skill owning the runtime: edgeone-makers-edge-functions, + makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. metadata: author: edgeone version: "2.2.0" @@ -23,7 +24,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -38,7 +39,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -77,10 +78,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.0) +# Check 1: CLI installed and correct version? (must be >= 1.6.7) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -93,11 +94,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.0 | — | → Go to **Install CLI** | -| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.7 | — | → Go to **Install CLI** | +| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -107,7 +108,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -138,7 +139,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -290,7 +291,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.0) +### Preferred: `--json` (CLI ≥ 1.6.7) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -350,11 +351,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/codex/makers-edge-functions.md b/codex/makers-edge-functions.md index 7345171..72ff1ae 100644 --- a/codex/makers-edge-functions.md +++ b/codex/makers-edge-functions.md @@ -108,9 +108,9 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../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). +⚠️ **Prerequisites**: You must enable KV Storage in the EdgeOne Makers console, create a namespace, and bind it to your project before using KV. See [makers-storage/references/kv.md](../makers-storage/references/kv.md) for full setup instructions. The KV namespace is a **global variable** (name is set when binding in the console) — it is **NOT** on `context.env`. @@ -134,7 +134,7 @@ export async function onRequest(context) { } ``` -For full KV Storage API reference and usage guide, see: [kv-storage.md](kv-storage.md) (same directory). +For full KV Storage API reference and usage guide, see [makers-storage/references/kv.md](../makers-storage/references/kv.md). ## Supported Runtime APIs diff --git a/codex/makers-env-adaption.md b/codex/makers-env-adaption.md index 4c6fcc3..b950551 100644 --- a/codex/makers-env-adaption.md +++ b/codex/makers-env-adaption.md @@ -25,12 +25,14 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` - ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ - │ -Enter ┤ -preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ Still iterating? ───┤ - └─ No ──→ start edgeone makers dev → present_files(...) +Dev done ──→ ASK the user how to verify (never assume) + │ + ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ + │ + ├─ "local preview" ────────────┐ + └─ "preview then deploy" ──────┤ + ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -176,7 +178,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: +After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. @@ -224,7 +226,7 @@ present_files("https://my-app-w9t0lxe8.edgeone.cool") # --- -### 10. Next.js HMR cross-origin configuration +### 8. Next.js HMR cross-origin configuration The Next.js 15+ dev server trusts only `localhost` by default. Accessing it via `127.0.0.1` in the sandbox is treated as cross-origin, so the HMR WebSocket is blocked and the page becomes unresponsive. @@ -237,7 +239,7 @@ Note: the value is a **bare host**, without an `http://` prefix and without a po --- -### 11. Project linking (required for Blob/KV) +### 9. Project linking (required for Blob/KV) Projects that use Blob Storage or KV must ensure the project is linked (a `.edgeone/project.json` exists) before starting dev. When not linked, Blob/KV calls report `Missing: deployCredential`. @@ -262,7 +264,7 @@ If the project named by `--name` does not exist remotely, the `link` command cre --- -### 12. Framework version requirements +### 10. Framework version requirements | Framework/package | Minimum version | Reason | |---------|---------|------| diff --git a/codex/makers-migration.md b/codex/makers-migration.md index 449d501..9ca349c 100644 --- a/codex/makers-migration.md +++ b/codex/makers-migration.md @@ -6,7 +6,7 @@ description: >- Use when the user wants to adapt a standard agent project to run on EdgeOne Makers, convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). - Do NOT trigger for new agent projects (use makers-agents instead). + Do NOT trigger for new agent projects (use edgeone-makers-agents instead). metadata: author: edgeone version: "1.0.0" @@ -148,7 +148,7 @@ openai>=1.50.0 6. Replace custom tools with `ctx.tools.to_crewai_tools(BaseTool)` 7. Return SSE via `ctx.utils.stream_sse(gen())` -> See [makers-agents/skills/python-frameworks/crewai.md](../skills/makers-agents/references/python-frameworks/crewai.md) for the complete pattern. +> See [makers-agents/references/python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) for the complete pattern. > Detailed before/after: [references/crewai-to-makers.md](references/crewai-to-makers.md) --- @@ -197,9 +197,9 @@ openai>=1.50.0 6. Set `thread_id`: `{ configurable: { thread_id: context.conversation_id } }` 7. Replace response with SSE streaming pattern -> Node: [makers-agents/skills/node-frameworks/langgraph.md](../skills/makers-agents/references/node-frameworks/langgraph.md) -> Python: [makers-agents/skills/python-frameworks/langgraph.md](../skills/makers-agents/references/python-frameworks/langgraph.md) -> DeepAgents: [makers-agents/skills/node-frameworks/deepagents.md](../skills/makers-agents/references/node-frameworks/deepagents.md) +> Node: [makers-agents/references/node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) +> Python: [makers-agents/references/python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) +> DeepAgents: [makers-agents/references/node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) > Detailed before/after: [references/langgraph-to-makers.md](references/langgraph-to-makers.md), [references/deepagents-to-makers.md](references/deepagents-to-makers.md) --- @@ -246,8 +246,8 @@ openai>=1.50.0 4. Use `context.store.openaiSession(conversationId)` for session (Node) 5. Map stream events to SSE: `output_text_delta` → `ai_response`, `tool_called` → `tool_call` -> Node: [makers-agents/skills/node-frameworks/openai-agents.md](../skills/makers-agents/references/node-frameworks/openai-agents.md) -> Python: [makers-agents/skills/python-frameworks/openai-agents.md](../skills/makers-agents/references/python-frameworks/openai-agents.md) +> Node: [makers-agents/references/node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) +> Python: [makers-agents/references/python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) > Detailed before/after: [references/openai-agents-to-makers.md](references/openai-agents-to-makers.md) --- @@ -296,8 +296,8 @@ openai>=1.50.0 5. Node only: swallow `EPIPE` on `process.stdout` 6. Set writable config dirs: `CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk`, `CLAUDE_CODE_TMPDIR=/tmp` -> Node: [makers-agents/skills/node-frameworks/claude-sdk.md](../skills/makers-agents/references/node-frameworks/claude-sdk.md) -> Python: [makers-agents/skills/python-frameworks/claude-sdk.md](../skills/makers-agents/references/python-frameworks/claude-sdk.md) +> Node: [makers-agents/references/node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) +> Python: [makers-agents/references/python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) > Detailed before/after: [references/claude-agent-sdk-to-makers.md](references/claude-agent-sdk-to-makers.md) --- @@ -452,6 +452,23 @@ After migration, verify these items before deploying: ## See Also - Agent development guide: [makers-agents/SKILL.md](../makers-agents/SKILL.md) + +### Framework reference index + +Reference files in this skill link back here instead of climbing two directory +levels. Full framework patterns live in `makers-agents`: + +| Framework | Node | Python | +|---|---|---| +| LangGraph | [node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) | [python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) | +| DeepAgents | [node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) | [python-frameworks/deepagents.md](../makers-agents/references/python-frameworks/deepagents.md) | +| OpenAI Agents | [node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) | [python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) | +| Claude Agent SDK | [node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) | [python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) | +| CrewAI | — (Python only) | [python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) | + +Platform capabilities: [capabilities/sandbox.md](../makers-agents/references/capabilities/sandbox.md) · +[capabilities/store.md](../makers-agents/references/capabilities/store.md) · +[capabilities/tools.md](../makers-agents/references/capabilities/tools.md) - Platform conventions: [makers-agents/references/platform/](../makers-agents/references/platform/) - CLI commands: [makers-cli/SKILL.md](../makers-cli/SKILL.md) - Deploy guide: [makers-deploy/SKILL.md](../makers-deploy/SKILL.md) diff --git a/codex/makers-recipes.md b/codex/makers-recipes.md index 25da228..a6d3a86 100644 --- a/codex/makers-recipes.md +++ b/codex/makers-recipes.md @@ -10,7 +10,7 @@ metadata: # Common Recipes -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. @@ -141,7 +141,7 @@ my-app/ ## Edge API + KV counter -⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [kv-storage.md](kv-storage.md) (same directory) +⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [makers-storage/references/kv.md](../makers-storage/references/kv.md). ``` my-app/ diff --git a/cursor/rules/makers-agents.mdc b/cursor/rules/makers-agents.mdc index 0dc498c..60676c4 100644 --- a/cursor/rules/makers-agents.mdc +++ b/cursor/rules/makers-agents.mdc @@ -11,8 +11,9 @@ description: >- agent endpoint", "wire LangGraph into Makers", "stream LLM responses with SSE", "review my agent template", "use context.store / context.sandbox / context.tools". Do NOT trigger for plain Edge Functions, Cloud Functions, or middleware - (those don't run AI logic — use edgeone-pages-dev instead). - Do NOT trigger for deployment workflows (use edgeone-pages-deploy). + (those don't run AI logic — use edgeone-makers-edge-functions, + makers-cloud-functions, or edgeone-makers-middleware instead). + Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. metadata: @@ -22,7 +23,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. @@ -38,11 +39,11 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../makers-storage/SKILL.md). **Do NOT use for:** -- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-pages-dev` -- Deployment workflows → use `edgeone-pages-deploy` +- Plain Edge Functions / Cloud Functions / Middleware → use `edgeone-makers-edge-functions`, `makers-cloud-functions`, or `edgeone-makers-middleware` +- Deployment workflows → use `edgeone-makers-deploy` - Generic AI framework development outside an EdgeOne Makers project - Other platforms (Cloudflare Workers AI, Vercel AI SDK, AWS Bedrock) @@ -241,6 +242,8 @@ Need a sandbox to run code, process uploaded files, or use MCP tools? | LangGraph (Python) | [python-frameworks/langgraph.md](references/python-frameworks/langgraph.md) | | DeepAgents (Python) | [python-frameworks/deepagents.md](references/python-frameworks/deepagents.md) | | CrewAI (Python only) | [python-frameworks/crewai.md](references/python-frameworks/crewai.md) | +| CrewAI — tool integration, review checklist, pitfalls | [python-frameworks/crewai-integration.md](references/python-frameworks/crewai-integration.md) | +| Python runtime conventions (all Python routes) | [python-frameworks/python-runtime-conventions.md](references/python-frameworks/python-runtime-conventions.md) | | Review checklist | [review-checklist.md](references/review-checklist.md) | --- diff --git a/cursor/rules/makers-deploy.mdc b/cursor/rules/makers-deploy.mdc index 2d0f3e3..5592f05 100644 --- a/cursor/rules/makers-deploy.mdc +++ b/cursor/rules/makers-deploy.mdc @@ -10,8 +10,9 @@ description: >- "搭建并部署", "开发并上线", "build and deploy", "create and deploy". ⚠️ Also trigger when any agent is about to execute `edgeone makers deploy` or `edgeone makers deploy` commands — the skill contains critical rules for parsing deploy output and presenting access URLs. - Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy — - use edgeone-makers-dev for troubleshooting). + Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — + route those to the skill owning the runtime: edgeone-makers-edge-functions, + makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. metadata: author: edgeone version: "2.2.0" @@ -23,7 +24,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -38,7 +39,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -77,10 +78,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.0) +# Check 1: CLI installed and correct version? (must be >= 1.6.7) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -93,11 +94,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.0 | — | → Go to **Install CLI** | -| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.7 | — | → Go to **Install CLI** | +| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -107,7 +108,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -138,7 +139,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -290,7 +291,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.0) +### Preferred: `--json` (CLI ≥ 1.6.7) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -350,11 +351,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/cursor/rules/makers-edge-functions.mdc b/cursor/rules/makers-edge-functions.mdc index 7345171..72ff1ae 100644 --- a/cursor/rules/makers-edge-functions.mdc +++ b/cursor/rules/makers-edge-functions.mdc @@ -108,9 +108,9 @@ 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`. +> Cross-reference: if your code uses `context.store` or KV APIs, also read [makers-storage/SKILL.md](../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). +⚠️ **Prerequisites**: You must enable KV Storage in the EdgeOne Makers console, create a namespace, and bind it to your project before using KV. See [makers-storage/references/kv.md](../makers-storage/references/kv.md) for full setup instructions. The KV namespace is a **global variable** (name is set when binding in the console) — it is **NOT** on `context.env`. @@ -134,7 +134,7 @@ export async function onRequest(context) { } ``` -For full KV Storage API reference and usage guide, see: [kv-storage.md](kv-storage.md) (same directory). +For full KV Storage API reference and usage guide, see [makers-storage/references/kv.md](../makers-storage/references/kv.md). ## Supported Runtime APIs diff --git a/cursor/rules/makers-env-adaption.mdc b/cursor/rules/makers-env-adaption.mdc index 4c6fcc3..b950551 100644 --- a/cursor/rules/makers-env-adaption.mdc +++ b/cursor/rules/makers-env-adaption.mdc @@ -25,12 +25,14 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` - ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ - │ -Enter ┤ -preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ Still iterating? ───┤ - └─ No ──→ start edgeone makers dev → present_files(...) +Dev done ──→ ASK the user how to verify (never assume) + │ + ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ + │ + ├─ "local preview" ────────────┐ + └─ "preview then deploy" ──────┤ + ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -176,7 +178,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: +After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. @@ -224,7 +226,7 @@ present_files("https://my-app-w9t0lxe8.edgeone.cool") # --- -### 10. Next.js HMR cross-origin configuration +### 8. Next.js HMR cross-origin configuration The Next.js 15+ dev server trusts only `localhost` by default. Accessing it via `127.0.0.1` in the sandbox is treated as cross-origin, so the HMR WebSocket is blocked and the page becomes unresponsive. @@ -237,7 +239,7 @@ Note: the value is a **bare host**, without an `http://` prefix and without a po --- -### 11. Project linking (required for Blob/KV) +### 9. Project linking (required for Blob/KV) Projects that use Blob Storage or KV must ensure the project is linked (a `.edgeone/project.json` exists) before starting dev. When not linked, Blob/KV calls report `Missing: deployCredential`. @@ -262,7 +264,7 @@ If the project named by `--name` does not exist remotely, the `link` command cre --- -### 12. Framework version requirements +### 10. Framework version requirements | Framework/package | Minimum version | Reason | |---------|---------|------| diff --git a/cursor/rules/makers-migration.mdc b/cursor/rules/makers-migration.mdc index 449d501..9ca349c 100644 --- a/cursor/rules/makers-migration.mdc +++ b/cursor/rules/makers-migration.mdc @@ -6,7 +6,7 @@ description: >- Use when the user wants to adapt a standard agent project to run on EdgeOne Makers, convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). - Do NOT trigger for new agent projects (use makers-agents instead). + Do NOT trigger for new agent projects (use edgeone-makers-agents instead). metadata: author: edgeone version: "1.0.0" @@ -148,7 +148,7 @@ openai>=1.50.0 6. Replace custom tools with `ctx.tools.to_crewai_tools(BaseTool)` 7. Return SSE via `ctx.utils.stream_sse(gen())` -> See [makers-agents/skills/python-frameworks/crewai.md](../skills/makers-agents/references/python-frameworks/crewai.md) for the complete pattern. +> See [makers-agents/references/python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) for the complete pattern. > Detailed before/after: [references/crewai-to-makers.md](references/crewai-to-makers.md) --- @@ -197,9 +197,9 @@ openai>=1.50.0 6. Set `thread_id`: `{ configurable: { thread_id: context.conversation_id } }` 7. Replace response with SSE streaming pattern -> Node: [makers-agents/skills/node-frameworks/langgraph.md](../skills/makers-agents/references/node-frameworks/langgraph.md) -> Python: [makers-agents/skills/python-frameworks/langgraph.md](../skills/makers-agents/references/python-frameworks/langgraph.md) -> DeepAgents: [makers-agents/skills/node-frameworks/deepagents.md](../skills/makers-agents/references/node-frameworks/deepagents.md) +> Node: [makers-agents/references/node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) +> Python: [makers-agents/references/python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) +> DeepAgents: [makers-agents/references/node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) > Detailed before/after: [references/langgraph-to-makers.md](references/langgraph-to-makers.md), [references/deepagents-to-makers.md](references/deepagents-to-makers.md) --- @@ -246,8 +246,8 @@ openai>=1.50.0 4. Use `context.store.openaiSession(conversationId)` for session (Node) 5. Map stream events to SSE: `output_text_delta` → `ai_response`, `tool_called` → `tool_call` -> Node: [makers-agents/skills/node-frameworks/openai-agents.md](../skills/makers-agents/references/node-frameworks/openai-agents.md) -> Python: [makers-agents/skills/python-frameworks/openai-agents.md](../skills/makers-agents/references/python-frameworks/openai-agents.md) +> Node: [makers-agents/references/node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) +> Python: [makers-agents/references/python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) > Detailed before/after: [references/openai-agents-to-makers.md](references/openai-agents-to-makers.md) --- @@ -296,8 +296,8 @@ openai>=1.50.0 5. Node only: swallow `EPIPE` on `process.stdout` 6. Set writable config dirs: `CLAUDE_CONFIG_DIR=/tmp/claude-agent-sdk`, `CLAUDE_CODE_TMPDIR=/tmp` -> Node: [makers-agents/skills/node-frameworks/claude-sdk.md](../skills/makers-agents/references/node-frameworks/claude-sdk.md) -> Python: [makers-agents/skills/python-frameworks/claude-sdk.md](../skills/makers-agents/references/python-frameworks/claude-sdk.md) +> Node: [makers-agents/references/node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) +> Python: [makers-agents/references/python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) > Detailed before/after: [references/claude-agent-sdk-to-makers.md](references/claude-agent-sdk-to-makers.md) --- @@ -452,6 +452,23 @@ After migration, verify these items before deploying: ## See Also - Agent development guide: [makers-agents/SKILL.md](../makers-agents/SKILL.md) + +### Framework reference index + +Reference files in this skill link back here instead of climbing two directory +levels. Full framework patterns live in `makers-agents`: + +| Framework | Node | Python | +|---|---|---| +| LangGraph | [node-frameworks/langgraph.md](../makers-agents/references/node-frameworks/langgraph.md) | [python-frameworks/langgraph.md](../makers-agents/references/python-frameworks/langgraph.md) | +| DeepAgents | [node-frameworks/deepagents.md](../makers-agents/references/node-frameworks/deepagents.md) | [python-frameworks/deepagents.md](../makers-agents/references/python-frameworks/deepagents.md) | +| OpenAI Agents | [node-frameworks/openai-agents.md](../makers-agents/references/node-frameworks/openai-agents.md) | [python-frameworks/openai-agents.md](../makers-agents/references/python-frameworks/openai-agents.md) | +| Claude Agent SDK | [node-frameworks/claude-sdk.md](../makers-agents/references/node-frameworks/claude-sdk.md) | [python-frameworks/claude-sdk.md](../makers-agents/references/python-frameworks/claude-sdk.md) | +| CrewAI | — (Python only) | [python-frameworks/crewai.md](../makers-agents/references/python-frameworks/crewai.md) | + +Platform capabilities: [capabilities/sandbox.md](../makers-agents/references/capabilities/sandbox.md) · +[capabilities/store.md](../makers-agents/references/capabilities/store.md) · +[capabilities/tools.md](../makers-agents/references/capabilities/tools.md) - Platform conventions: [makers-agents/references/platform/](../makers-agents/references/platform/) - CLI commands: [makers-cli/SKILL.md](../makers-cli/SKILL.md) - Deploy guide: [makers-deploy/SKILL.md](../makers-deploy/SKILL.md) diff --git a/cursor/rules/makers-recipes.mdc b/cursor/rules/makers-recipes.mdc index 25da228..a6d3a86 100644 --- a/cursor/rules/makers-recipes.mdc +++ b/cursor/rules/makers-recipes.mdc @@ -10,7 +10,7 @@ metadata: # Common Recipes -> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. @@ -141,7 +141,7 @@ my-app/ ## Edge API + KV counter -⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [kv-storage.md](kv-storage.md) (same directory) +⚠️ **Prerequisites**: You must enable KV Storage in the console and bind a namespace first. See [makers-storage/references/kv.md](../makers-storage/references/kv.md). ``` my-app/ From 4f20dcf9a140ce419990da189a4ea4b57944a0fc Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:04:49 +0800 Subject: [PATCH 20/29] =?UTF-8?q?hook=20=E6=94=B9=E4=B8=BA=E8=81=9A?= =?UTF-8?q?=E5=90=88=E6=89=80=E6=9C=89=E5=91=BD=E4=B8=AD=E7=9A=84=20skill?= =?UTF-8?q?=20=E6=A0=A1=E9=AA=8C=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/validate-write.mjs | 40 +++++++++++------ hooks/validate-write.test.mjs | 84 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 14 deletions(-) diff --git a/hooks/validate-write.mjs b/hooks/validate-write.mjs index 4df7b1d..bf6422f 100644 --- a/hooks/validate-write.mjs +++ b/hooks/validate-write.mjs @@ -145,21 +145,33 @@ function getToolWriteContent(payload) { 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; +/** + * 返回所有 pathPatterns 命中该路径的规则。 + * + * 不能只取第一条:规则按 skills/ 的字母序加载,而 `agents/**` 与 + * `cloud-functions/**` 这类前缀天然会重叠。只取首条等于让「哪条铁律生效」 + * 由目录名的字母序偶然决定,多个 skill 共管同一路径时会静默丢提醒。 + */ +function findSkillsForPath(filePath, rules) { + if (!filePath) return []; + return rules.filter((rule) => + rule.pathPatterns.some((pattern) => globToRegExp(pattern).test(filePath)), + ); } -function selectValidationMatches(content, rule) { +/** + * 收集全部命中的校验项,message 去重并保留首次出现顺序。 + * 每项带上来源 skill,供 signal log 归因。 + */ +function selectValidationMatches(content, matchedRules) { const seen = new Set(); const matches = []; - for (const item of rule.validate) { - if (new RegExp(item.pattern).test(content) && !seen.has(item.message)) { + 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); + matches.push({ ...item, skill: rule.skill }); } } return matches; @@ -174,13 +186,13 @@ export function buildValidateWriteOutput(payload, options = {}) { const content = getToolWriteContent(payload); if (!content) return null; - const rule = findSkillForPath( + const matchedRules = findSkillsForPath( getToolPath(getToolInput(payload)), options.rules || loadSkillValidateRules(), ); - if (!rule) return null; + if (matchedRules.length === 0) return null; - const matches = selectValidationMatches(content, rule); + const matches = selectValidationMatches(content, matchedRules); if (matches.length === 0) return null; if (shouldWriteSignalLog(options)) { @@ -189,7 +201,7 @@ export function buildValidateWriteOutput(payload, options = {}) { { hook: 'PreToolUse', trigger: 'validate', - matchedSkill: rule.skill, + matchedSkill: match.skill, reason: match.message, toolName: getToolName(payload), }, diff --git a/hooks/validate-write.test.mjs b/hooks/validate-write.test.mjs index 3c6bfe0..1186e4a 100644 --- a/hooks/validate-write.test.mjs +++ b/hooks/validate-write.test.mjs @@ -200,3 +200,87 @@ test('plugin-skill-injection-optimization.SIGNAL_LOGGING.3 logs validate matches await rm(tmp, { recursive: true, force: true }); } }); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.7 aggregates matches from every skill whose pathPatterns match', () => { + const rules = [ + { + skill: 'skill-alpha', + pathPatterns: ['functions/**'], + validate: [{ pattern: 'process\\.env', message: 'Alpha says use context.env.' }], + }, + { + skill: 'skill-beta', + pathPatterns: ['functions/**'], + validate: [{ pattern: 'process\\.env', message: 'Beta says the same thing differently.' }], + }, + ]; + + const output = buildValidateWriteOutput( + { + tool_name: 'Write', + tool_input: { file_path: 'functions/index.ts', content: 'process.env.KEY' }, + }, + { rules }, + ); + + assert.deepEqual(output, { + hookSpecificOutput: { + hookEventName: 'PreToolUse', + additionalContext: + 'Validation reminder:\n- Alpha says use context.env.\n- Beta says the same thing differently.', + }, + }); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.7 deduplicates an identical message from two skills', () => { + const rules = [ + { + skill: 'skill-alpha', + pathPatterns: ['functions/**'], + validate: [{ pattern: 'process\\.env', message: 'Use context.env.' }], + }, + { + skill: 'skill-beta', + pathPatterns: ['functions/**'], + validate: [{ pattern: 'process\\.env', message: 'Use context.env.' }], + }, + ]; + + const output = buildValidateWriteOutput( + { + tool_name: 'Write', + tool_input: { file_path: 'functions/index.ts', content: 'process.env.KEY' }, + }, + { rules }, + ); + + assert.equal( + output.hookSpecificOutput.additionalContext, + 'Validation reminder:\n- Use context.env.', + ); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.7 ignores a skill whose pathPatterns do not match', () => { + const rules = [ + { + skill: 'skill-alpha', + pathPatterns: ['edge-functions/**'], + validate: [{ pattern: 'process\\.env', message: 'Alpha.' }], + }, + { + skill: 'skill-beta', + pathPatterns: ['agents/**'], + validate: [{ pattern: 'process\\.env', message: 'Beta.' }], + }, + ]; + + const output = buildValidateWriteOutput( + { + tool_name: 'Write', + tool_input: { file_path: 'agents/chat/index.ts', content: 'process.env.KEY' }, + }, + { rules }, + ); + + assert.equal(output.hookSpecificOutput.additionalContext, 'Validation reminder:\n- Beta.'); +}); From ad1cf2b478f75802030c394b519c5d0e50bef1b9 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:07:46 +0800 Subject: [PATCH 21/29] =?UTF-8?q?=E4=B8=BA=209=20=E4=B8=AA=20skill=20?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=20validate=20=E7=A1=AC=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 9 +++++++++ skills/makers-cloud-functions/SKILL.md | 5 +++++ skills/makers-deploy/SKILL.md | 6 ++++++ skills/makers-edge-functions/SKILL.md | 2 ++ skills/makers-env-adaption/SKILL.md | 8 ++++++++ skills/makers-middleware/SKILL.md | 6 ++++++ skills/makers-migration/SKILL.md | 6 ++++++ skills/makers-recipes/SKILL.md | 7 +++++++ skills/makers-storage/SKILL.md | 5 +++++ 9 files changed, 54 insertions(+) diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 60676c4..254ab1d 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -16,6 +16,15 @@ description: >- Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. +pathPatterns: + - agents/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside agents/ and cloud-functions/, never process.env or os.environ (Critical Rule 3)." + - pattern: "headers\\s*\\.\\s*get\\s*\\(" + message: "Headers are plain objects here: context.request.headers['x-name'], not .get('x-name') (Critical Rule 4)." + - pattern: "langgraphStore\\s*\\?\\?\\s*store" + message: "Never write `store?.langgraphStore ?? store` — in cloud-function context it falls back to a store with no .get and crashes (Critical Rule 12)." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-cloud-functions/SKILL.md b/skills/makers-cloud-functions/SKILL.md index 26fac3d..aef97dc 100644 --- a/skills/makers-cloud-functions/SKILL.md +++ b/skills/makers-cloud-functions/SKILL.md @@ -3,6 +3,11 @@ name: makers-cloud-functions description: >- EdgeOne Makers Cloud Functions — Node.js, Go, and Python runtimes. Use when building server-side APIs, Express/Koa patterns, or backend logic. +pathPatterns: + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside cloud-functions/, never process.env or os.environ." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-deploy/SKILL.md b/skills/makers-deploy/SKILL.md index 5592f05..f504602 100644 --- a/skills/makers-deploy/SKILL.md +++ b/skills/makers-deploy/SKILL.md @@ -13,6 +13,12 @@ description: >- Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — route those to the skill owning the runtime: edgeone-makers-edge-functions, makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. +pathPatterns: + - "*.sh" + - .github/workflows/** +validate: + - pattern: "whoami[^\\n]*\\s-t\\s" + message: "edgeone whoami does not accept -t. Check the exit code instead: 0 = logged in, 1 = not." metadata: author: edgeone version: "2.2.0" diff --git a/skills/makers-edge-functions/SKILL.md b/skills/makers-edge-functions/SKILL.md index 72ff1ae..bfe13c9 100644 --- a/skills/makers-edge-functions/SKILL.md +++ b/skills/makers-edge-functions/SKILL.md @@ -13,6 +13,8 @@ validate: message: "Use plain object headers for this runtime surface." - pattern: "fs\\.writeFile" message: "Edge Functions do not support filesystem writes." + - pattern: "Response\\.json\\s*\\(" + message: "Response.json() is not available in this V8 runtime — use new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-env-adaption/SKILL.md b/skills/makers-env-adaption/SKILL.md index b950551..c782c93 100644 --- a/skills/makers-env-adaption/SKILL.md +++ b/skills/makers-env-adaption/SKILL.md @@ -8,6 +8,14 @@ description: >- Covers: non-interactive CLI flags, network isolation workarounds, login in sandbox, proxy bypass, file preview constraints (MUST use http:// via dev server, NEVER file://, NEVER python -m http.server / npx serve), dev server requirements. +pathPatterns: + - "*.sh" + - package.json +validate: + - pattern: "python\\s+-m\\s+http\\.server|npx\\s+(serve|http-server)" + message: "Use `edgeone makers dev` — self-hosted static servers skip Blob credentials, Cloud Functions routing, Edge Functions and middleware." + - pattern: "localhost:80(88|89)" + message: "Use 127.0.0.1, not localhost — in the sandbox localhost resolves to ::1 and yields false 404s." metadata: author: edgeone version: "1.1.0" diff --git a/skills/makers-middleware/SKILL.md b/skills/makers-middleware/SKILL.md index 751cc3d..c1d580e 100644 --- a/skills/makers-middleware/SKILL.md +++ b/skills/makers-middleware/SKILL.md @@ -3,6 +3,12 @@ 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). +pathPatterns: + - middleware.js + - middleware.ts +validate: + - pattern: "NextRequest|NextResponse|next/server" + message: "Framework projects must use the framework's own middleware. This platform format takes a context object with next/redirect/rewrite — not NextRequest/NextResponse." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-migration/SKILL.md b/skills/makers-migration/SKILL.md index 9ca349c..1e1786e 100644 --- a/skills/makers-migration/SKILL.md +++ b/skills/makers-migration/SKILL.md @@ -7,6 +7,12 @@ description: >- convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use edgeone-makers-agents instead). +pathPatterns: + - agents/** + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Migration checklist: replace process.env / os.environ with context.env (TS) or ctx env access (Python)." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-recipes/SKILL.md b/skills/makers-recipes/SKILL.md index a6d3a86..1dbd722 100644 --- a/skills/makers-recipes/SKILL.md +++ b/skills/makers-recipes/SKILL.md @@ -3,6 +3,13 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. +pathPatterns: + - next.config.js + - next.config.mjs + - next.config.ts +validate: + - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" + message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-storage/SKILL.md b/skills/makers-storage/SKILL.md index ab8162c..6d869f1 100644 --- a/skills/makers-storage/SKILL.md +++ b/skills/makers-storage/SKILL.md @@ -3,6 +3,11 @@ name: edgeone-makers-storage description: >- KV and Blob storage services on EdgeOne Makers. KV for edge key-value pairs, Blob for file/object storage in Cloud Functions. Covers SDK usage, setup, and troubleshooting. +pathPatterns: + - edge-functions/** +validate: + - pattern: "context\\.env\\.[A-Za-z_]*[Kk][Vv]" + message: "KV is a console-bound global variable, not on context.env — call my_kv.get(...) directly. See makers-storage/references/kv.md." metadata: author: edgeone version: "1.0.0" From f8317f36986882930fdc0a92e315d8b15e309015 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:07:46 +0800 Subject: [PATCH 22/29] =?UTF-8?q?hook=20=E6=B5=8B=E8=AF=95=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=96=AD=E8=A8=80=E8=A7=84=E5=88=99=E4=B8=8D=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E8=80=8C=E9=9D=9E=E5=9B=BA=E5=AE=9A=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/validate-write.test.mjs | 49 ++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/hooks/validate-write.test.mjs b/hooks/validate-write.test.mjs index 1186e4a..1417324 100644 --- a/hooks/validate-write.test.mjs +++ b/hooks/validate-write.test.mjs @@ -10,21 +10,23 @@ test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.1 loads validate ru 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.', - }, - ]); + assert.ok(edgeFunctions, 'edge-functions skill 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', () => { @@ -284,3 +286,20 @@ test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.7 ignores a skill w assert.equal(output.hookSpecificOutput.additionalContext, 'Validation reminder:\n- Beta.'); }); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.8 every declared rule has usable patterns and paths', async () => { + const rules = await loadSkillValidateRules(); + assert.ok(rules.length >= 8, `expected at least 8 skills with validate, got ${rules.length}`); + + for (const rule of rules) { + assert.ok(rule.pathPatterns.length > 0, `${rule.skill} declares validate but no pathPatterns`); + assert.ok(rule.validate.length > 0, `${rule.skill} has an empty validate list`); + for (const item of rule.validate) { + assert.doesNotThrow( + () => new RegExp(item.pattern), + `${rule.skill}: invalid regex ${item.pattern}`, + ); + assert.ok(item.message.length > 10, `${rule.skill}: message too short to be actionable`); + } + } +}); From f3335a31b005e56736cfd496783903be81d1f036 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Sun, 9 Aug 2026 06:08:34 +0800 Subject: [PATCH 23/29] =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=A4=9A=E5=B9=B3=E5=8F=B0=E4=BA=A7=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex/makers-agents.md | 9 +++++++++ codex/makers-cloud-functions.md | 5 +++++ codex/makers-deploy.md | 6 ++++++ codex/makers-edge-functions.md | 2 ++ codex/makers-env-adaption.md | 8 ++++++++ codex/makers-middleware.md | 6 ++++++ codex/makers-migration.md | 6 ++++++ codex/makers-recipes.md | 7 +++++++ codex/makers-storage.md | 5 +++++ cursor/rules/makers-agents.mdc | 9 +++++++++ cursor/rules/makers-cloud-functions.mdc | 5 +++++ cursor/rules/makers-deploy.mdc | 6 ++++++ cursor/rules/makers-edge-functions.mdc | 2 ++ cursor/rules/makers-env-adaption.mdc | 8 ++++++++ cursor/rules/makers-middleware.mdc | 6 ++++++ cursor/rules/makers-migration.mdc | 6 ++++++ cursor/rules/makers-recipes.mdc | 7 +++++++ cursor/rules/makers-storage.mdc | 5 +++++ 18 files changed, 108 insertions(+) diff --git a/codex/makers-agents.md b/codex/makers-agents.md index 60676c4..254ab1d 100644 --- a/codex/makers-agents.md +++ b/codex/makers-agents.md @@ -16,6 +16,15 @@ description: >- Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. +pathPatterns: + - agents/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside agents/ and cloud-functions/, never process.env or os.environ (Critical Rule 3)." + - pattern: "headers\\s*\\.\\s*get\\s*\\(" + message: "Headers are plain objects here: context.request.headers['x-name'], not .get('x-name') (Critical Rule 4)." + - pattern: "langgraphStore\\s*\\?\\?\\s*store" + message: "Never write `store?.langgraphStore ?? store` — in cloud-function context it falls back to a store with no .get and crashes (Critical Rule 12)." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-cloud-functions.md b/codex/makers-cloud-functions.md index 26fac3d..aef97dc 100644 --- a/codex/makers-cloud-functions.md +++ b/codex/makers-cloud-functions.md @@ -3,6 +3,11 @@ name: makers-cloud-functions description: >- EdgeOne Makers Cloud Functions — Node.js, Go, and Python runtimes. Use when building server-side APIs, Express/Koa patterns, or backend logic. +pathPatterns: + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside cloud-functions/, never process.env or os.environ." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-deploy.md b/codex/makers-deploy.md index 5592f05..f504602 100644 --- a/codex/makers-deploy.md +++ b/codex/makers-deploy.md @@ -13,6 +13,12 @@ description: >- Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — route those to the skill owning the runtime: edgeone-makers-edge-functions, makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. +pathPatterns: + - "*.sh" + - .github/workflows/** +validate: + - pattern: "whoami[^\\n]*\\s-t\\s" + message: "edgeone whoami does not accept -t. Check the exit code instead: 0 = logged in, 1 = not." metadata: author: edgeone version: "2.2.0" diff --git a/codex/makers-edge-functions.md b/codex/makers-edge-functions.md index 72ff1ae..bfe13c9 100644 --- a/codex/makers-edge-functions.md +++ b/codex/makers-edge-functions.md @@ -13,6 +13,8 @@ validate: message: "Use plain object headers for this runtime surface." - pattern: "fs\\.writeFile" message: "Edge Functions do not support filesystem writes." + - pattern: "Response\\.json\\s*\\(" + message: "Response.json() is not available in this V8 runtime — use new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-env-adaption.md b/codex/makers-env-adaption.md index b950551..c782c93 100644 --- a/codex/makers-env-adaption.md +++ b/codex/makers-env-adaption.md @@ -8,6 +8,14 @@ description: >- Covers: non-interactive CLI flags, network isolation workarounds, login in sandbox, proxy bypass, file preview constraints (MUST use http:// via dev server, NEVER file://, NEVER python -m http.server / npx serve), dev server requirements. +pathPatterns: + - "*.sh" + - package.json +validate: + - pattern: "python\\s+-m\\s+http\\.server|npx\\s+(serve|http-server)" + message: "Use `edgeone makers dev` — self-hosted static servers skip Blob credentials, Cloud Functions routing, Edge Functions and middleware." + - pattern: "localhost:80(88|89)" + message: "Use 127.0.0.1, not localhost — in the sandbox localhost resolves to ::1 and yields false 404s." metadata: author: edgeone version: "1.1.0" diff --git a/codex/makers-middleware.md b/codex/makers-middleware.md index 751cc3d..c1d580e 100644 --- a/codex/makers-middleware.md +++ b/codex/makers-middleware.md @@ -3,6 +3,12 @@ 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). +pathPatterns: + - middleware.js + - middleware.ts +validate: + - pattern: "NextRequest|NextResponse|next/server" + message: "Framework projects must use the framework's own middleware. This platform format takes a context object with next/redirect/rewrite — not NextRequest/NextResponse." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-migration.md b/codex/makers-migration.md index 9ca349c..1e1786e 100644 --- a/codex/makers-migration.md +++ b/codex/makers-migration.md @@ -7,6 +7,12 @@ description: >- convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use edgeone-makers-agents instead). +pathPatterns: + - agents/** + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Migration checklist: replace process.env / os.environ with context.env (TS) or ctx env access (Python)." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-recipes.md b/codex/makers-recipes.md index a6d3a86..1dbd722 100644 --- a/codex/makers-recipes.md +++ b/codex/makers-recipes.md @@ -3,6 +3,13 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. +pathPatterns: + - next.config.js + - next.config.mjs + - next.config.ts +validate: + - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" + message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-storage.md b/codex/makers-storage.md index ab8162c..6d869f1 100644 --- a/codex/makers-storage.md +++ b/codex/makers-storage.md @@ -3,6 +3,11 @@ name: edgeone-makers-storage description: >- KV and Blob storage services on EdgeOne Makers. KV for edge key-value pairs, Blob for file/object storage in Cloud Functions. Covers SDK usage, setup, and troubleshooting. +pathPatterns: + - edge-functions/** +validate: + - pattern: "context\\.env\\.[A-Za-z_]*[Kk][Vv]" + message: "KV is a console-bound global variable, not on context.env — call my_kv.get(...) directly. See makers-storage/references/kv.md." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-agents.mdc b/cursor/rules/makers-agents.mdc index 60676c4..254ab1d 100644 --- a/cursor/rules/makers-agents.mdc +++ b/cursor/rules/makers-agents.mdc @@ -16,6 +16,15 @@ description: >- Do NOT trigger for deployment workflows (use edgeone-makers-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. +pathPatterns: + - agents/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside agents/ and cloud-functions/, never process.env or os.environ (Critical Rule 3)." + - pattern: "headers\\s*\\.\\s*get\\s*\\(" + message: "Headers are plain objects here: context.request.headers['x-name'], not .get('x-name') (Critical Rule 4)." + - pattern: "langgraphStore\\s*\\?\\?\\s*store" + message: "Never write `store?.langgraphStore ?? store` — in cloud-function context it falls back to a store with no .get and crashes (Critical Rule 12)." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-cloud-functions.mdc b/cursor/rules/makers-cloud-functions.mdc index 26fac3d..aef97dc 100644 --- a/cursor/rules/makers-cloud-functions.mdc +++ b/cursor/rules/makers-cloud-functions.mdc @@ -3,6 +3,11 @@ name: makers-cloud-functions description: >- EdgeOne Makers Cloud Functions — Node.js, Go, and Python runtimes. Use when building server-side APIs, Express/Koa patterns, or backend logic. +pathPatterns: + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside cloud-functions/, never process.env or os.environ." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-deploy.mdc b/cursor/rules/makers-deploy.mdc index 5592f05..f504602 100644 --- a/cursor/rules/makers-deploy.mdc +++ b/cursor/rules/makers-deploy.mdc @@ -13,6 +13,12 @@ description: >- Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy) — route those to the skill owning the runtime: edgeone-makers-edge-functions, makers-cloud-functions, edgeone-makers-middleware, or edgeone-makers-agents. +pathPatterns: + - "*.sh" + - .github/workflows/** +validate: + - pattern: "whoami[^\\n]*\\s-t\\s" + message: "edgeone whoami does not accept -t. Check the exit code instead: 0 = logged in, 1 = not." metadata: author: edgeone version: "2.2.0" diff --git a/cursor/rules/makers-edge-functions.mdc b/cursor/rules/makers-edge-functions.mdc index 72ff1ae..bfe13c9 100644 --- a/cursor/rules/makers-edge-functions.mdc +++ b/cursor/rules/makers-edge-functions.mdc @@ -13,6 +13,8 @@ validate: message: "Use plain object headers for this runtime surface." - pattern: "fs\\.writeFile" message: "Edge Functions do not support filesystem writes." + - pattern: "Response\\.json\\s*\\(" + message: "Response.json() is not available in this V8 runtime — use new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-env-adaption.mdc b/cursor/rules/makers-env-adaption.mdc index b950551..c782c93 100644 --- a/cursor/rules/makers-env-adaption.mdc +++ b/cursor/rules/makers-env-adaption.mdc @@ -8,6 +8,14 @@ description: >- Covers: non-interactive CLI flags, network isolation workarounds, login in sandbox, proxy bypass, file preview constraints (MUST use http:// via dev server, NEVER file://, NEVER python -m http.server / npx serve), dev server requirements. +pathPatterns: + - "*.sh" + - package.json +validate: + - pattern: "python\\s+-m\\s+http\\.server|npx\\s+(serve|http-server)" + message: "Use `edgeone makers dev` — self-hosted static servers skip Blob credentials, Cloud Functions routing, Edge Functions and middleware." + - pattern: "localhost:80(88|89)" + message: "Use 127.0.0.1, not localhost — in the sandbox localhost resolves to ::1 and yields false 404s." metadata: author: edgeone version: "1.1.0" diff --git a/cursor/rules/makers-middleware.mdc b/cursor/rules/makers-middleware.mdc index 751cc3d..c1d580e 100644 --- a/cursor/rules/makers-middleware.mdc +++ b/cursor/rules/makers-middleware.mdc @@ -3,6 +3,12 @@ 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). +pathPatterns: + - middleware.js + - middleware.ts +validate: + - pattern: "NextRequest|NextResponse|next/server" + message: "Framework projects must use the framework's own middleware. This platform format takes a context object with next/redirect/rewrite — not NextRequest/NextResponse." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-migration.mdc b/cursor/rules/makers-migration.mdc index 9ca349c..1e1786e 100644 --- a/cursor/rules/makers-migration.mdc +++ b/cursor/rules/makers-migration.mdc @@ -7,6 +7,12 @@ description: >- convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use edgeone-makers-agents instead). +pathPatterns: + - agents/** + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Migration checklist: replace process.env / os.environ with context.env (TS) or ctx env access (Python)." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-recipes.mdc b/cursor/rules/makers-recipes.mdc index a6d3a86..1dbd722 100644 --- a/cursor/rules/makers-recipes.mdc +++ b/cursor/rules/makers-recipes.mdc @@ -3,6 +3,13 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. +pathPatterns: + - next.config.js + - next.config.mjs + - next.config.ts +validate: + - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" + message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-storage.mdc b/cursor/rules/makers-storage.mdc index ab8162c..6d869f1 100644 --- a/cursor/rules/makers-storage.mdc +++ b/cursor/rules/makers-storage.mdc @@ -3,6 +3,11 @@ name: edgeone-makers-storage description: >- KV and Blob storage services on EdgeOne Makers. KV for edge key-value pairs, Blob for file/object storage in Cloud Functions. Covers SDK usage, setup, and troubleshooting. +pathPatterns: + - edge-functions/** +validate: + - pattern: "context\\.env\\.[A-Za-z_]*[Kk][Vv]" + message: "KV is a console-bound global variable, not on context.env — call my_kv.get(...) directly. See makers-storage/references/kv.md." metadata: author: edgeone version: "1.0.0" From 9dda898da5bc71d0814abd81f147480d58e6d83b Mon Sep 17 00:00:00 2001 From: jesperxu Date: Mon, 10 Aug 2026 17:03:05 +0800 Subject: [PATCH 24/29] del:ci --- .github/workflows/build.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3e482e..fa3bcee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,17 +5,17 @@ on: branches: [main] paths: - 'skills/**' - - 'scripts/**' - - 'hooks/**' - - '_meta.json' - - 'package.json' + # - 'scripts/**' + # - 'hooks/**' + # - '_meta.json' + # - 'package.json' pull_request: paths: - 'skills/**' - - 'scripts/**' - - 'hooks/**' - - '_meta.json' - - 'package.json' + # - 'scripts/**' + # - 'hooks/**' + # - '_meta.json' + # - 'package.json' jobs: build: @@ -30,8 +30,8 @@ jobs: - name: Run tests run: npm test - - name: Run doctor - run: npm run doctor + # - name: Run doctor + # run: npm run doctor - name: Build multi-platform output run: node scripts/build.mjs From 74bc439d26ec392e96c91eec5b296545f89bb105 Mon Sep 17 00:00:00 2001 From: jesperxu Date: Mon, 10 Aug 2026 19:11:37 +0800 Subject: [PATCH 25/29] =?UTF-8?q?=E5=9B=9E=E9=80=80=20CLI=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=A3=E5=BE=84=E4=B8=8E=E9=A2=84=E8=A7=88=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex/makers-agents.md | 2 +- codex/makers-deploy.md | 30 +++++++++---------- codex/makers-env-adaption.md | 16 +++++----- codex/makers-recipes.md | 2 +- cursor/rules/makers-agents.mdc | 2 +- cursor/rules/makers-deploy.mdc | 30 +++++++++---------- cursor/rules/makers-env-adaption.mdc | 16 +++++----- cursor/rules/makers-recipes.mdc | 2 +- skills/makers-agents/SKILL.md | 2 +- skills/makers-deploy/SKILL.md | 30 +++++++++---------- .../references/command-reference.md | 2 +- skills/makers-env-adaption/SKILL.md | 16 +++++----- skills/makers-recipes/SKILL.md | 2 +- 13 files changed, 73 insertions(+), 79 deletions(-) diff --git a/codex/makers-agents.md b/codex/makers-agents.md index 254ab1d..0fa27bd 100644 --- a/codex/makers-agents.md +++ b/codex/makers-agents.md @@ -32,7 +32,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. diff --git a/codex/makers-deploy.md b/codex/makers-deploy.md index f504602..35258ef 100644 --- a/codex/makers-deploy.md +++ b/codex/makers-deploy.md @@ -30,7 +30,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -45,7 +45,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -84,10 +84,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.7) +# Check 1: CLI installed and correct version? (must be >= 1.6.0) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -100,11 +100,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.7 | — | → Go to **Install CLI** | -| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.0 | — | → Go to **Install CLI** | +| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -114,7 +114,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -145,7 +145,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -297,7 +297,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.7) +### Preferred: `--json` (CLI ≥ 1.6.0) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -357,11 +357,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/codex/makers-env-adaption.md b/codex/makers-env-adaption.md index c782c93..d83a45b 100644 --- a/codex/makers-env-adaption.md +++ b/codex/makers-env-adaption.md @@ -33,14 +33,12 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` -Dev done ──→ ASK the user how to verify (never assume) - │ - ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ - │ - ├─ "local preview" ────────────┐ - └─ "preview then deploy" ──────┤ - ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ No ──→ start edgeone makers dev → present_files(...) + ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ + │ +Enter ┤ +preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ Still iterating? ───┤ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -186,7 +184,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: +After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. diff --git a/codex/makers-recipes.md b/codex/makers-recipes.md index 1dbd722..9e055f8 100644 --- a/codex/makers-recipes.md +++ b/codex/makers-recipes.md @@ -17,7 +17,7 @@ metadata: # Common Recipes -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. diff --git a/cursor/rules/makers-agents.mdc b/cursor/rules/makers-agents.mdc index 254ab1d..0fa27bd 100644 --- a/cursor/rules/makers-agents.mdc +++ b/cursor/rules/makers-agents.mdc @@ -32,7 +32,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. diff --git a/cursor/rules/makers-deploy.mdc b/cursor/rules/makers-deploy.mdc index f504602..35258ef 100644 --- a/cursor/rules/makers-deploy.mdc +++ b/cursor/rules/makers-deploy.mdc @@ -30,7 +30,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -45,7 +45,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -84,10 +84,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.7) +# Check 1: CLI installed and correct version? (must be >= 1.6.0) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -100,11 +100,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.7 | — | → Go to **Install CLI** | -| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.0 | — | → Go to **Install CLI** | +| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -114,7 +114,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -145,7 +145,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -297,7 +297,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.7) +### Preferred: `--json` (CLI ≥ 1.6.0) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -357,11 +357,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/cursor/rules/makers-env-adaption.mdc b/cursor/rules/makers-env-adaption.mdc index c782c93..d83a45b 100644 --- a/cursor/rules/makers-env-adaption.mdc +++ b/cursor/rules/makers-env-adaption.mdc @@ -33,14 +33,12 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` -Dev done ──→ ASK the user how to verify (never assume) - │ - ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ - │ - ├─ "local preview" ────────────┐ - └─ "preview then deploy" ──────┤ - ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ No ──→ start edgeone makers dev → present_files(...) + ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ + │ +Enter ┤ +preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ Still iterating? ───┤ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -186,7 +184,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: +After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. diff --git a/cursor/rules/makers-recipes.mdc b/cursor/rules/makers-recipes.mdc index 1dbd722..9e055f8 100644 --- a/cursor/rules/makers-recipes.mdc +++ b/cursor/rules/makers-recipes.mdc @@ -17,7 +17,7 @@ metadata: # Common Recipes -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 254ab1d..0fa27bd 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -32,7 +32,7 @@ metadata: # EdgeOne Makers Agent Development Guide -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. Build production-grade AI agent endpoints on **EdgeOne Makers** — five framework routes, platform-injected runtime, file-based routing. diff --git a/skills/makers-deploy/SKILL.md b/skills/makers-deploy/SKILL.md index f504602..35258ef 100644 --- a/skills/makers-deploy/SKILL.md +++ b/skills/makers-deploy/SKILL.md @@ -30,7 +30,7 @@ Deploy any project to **EdgeOne Makers**. ## ⛔ Critical Rules (never skip) -1. **CLI version ≥ `1.6.7`** — reinstall if lower. Versions below `1.6.7` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. +1. **CLI version ≥ `1.6.0`** — reinstall if lower. Versions below `1.6.0` lack the non-interactive fixes (whoami fail-fast, `--json` output) and will hang in Agent/CI environments. Never proceed with an outdated version. 2. **Never truncate the deploy URL — this applies to EVERY mention** — `EDGEONE_DEPLOY_URL` includes query parameters (`?eo_token=...&eo_time=...`) required for access. Without them the page returns 401. Always output the **complete** URL with full query string. This rule applies to: the primary display, summary tables, footnotes, comparisons, code blocks, `present_files` calls — **every single occurrence** of the URL in your reply. Truncation is any removal of the `?` and everything after it. ❌ WRONG (truncated — will 401): @@ -45,7 +45,7 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. -3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.7, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. +3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. 4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: ``` 🌐 Live URL: https://my-project-abc123.edgeone.cool? @@ -84,10 +84,10 @@ Run these checks first, then follow the decision table: # Check 0: Set environment variable (required before any edgeone command) export PAGES_SOURCE=skills -# Check 1: CLI installed and correct version? (must be >= 1.6.7) +# Check 1: CLI installed and correct version? (must be >= 1.6.0) edgeone -v -# Check 2: Already logged in? (CLI >= 1.6.7 whoami fails fast, won't hang) +# Check 2: Already logged in? (CLI >= 1.6.0 whoami fails fast, won't hang) edgeone whoami # If exit 0 → logged in, no -t needed # If exit 1 → not logged in, need token or browser login @@ -100,11 +100,11 @@ cat edgeone.json 2>/dev/null | CLI version | Login status | Action | |-------------|-------------|--------| -| Not installed or < 1.6.7 | — | → Go to **Install CLI** | -| `≥ 1.6.7` ✓ | Logged in (or token present) | → Go to **Deploy** | -| `≥ 1.6.7` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | -| `≥ 1.6.7` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | +| Not installed or < 1.6.0 | — | → Go to **Install CLI** | +| `≥ 1.6.0` ✓ | Logged in (or token present) | → Go to **Deploy** | +| `≥ 1.6.0` ✓ | Not logged in, has saved token | → Go to **Deploy with Token** (use saved token) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **interactive desktop** | → Go to **Login** (browser) | +| `≥ 1.6.0` ✓ | Not logged in, no saved token, **non-interactive (Agent/CI/headless)** | → Ask user for a **token**; browser login is unavailable and `deploy` will fail fast with a token hint | --- @@ -114,7 +114,7 @@ cat edgeone.json 2>/dev/null npm install -g edgeone@latest ``` -Verify: `edgeone -v` — confirm output is `1.6.7` or higher. Retry installation if not. (Versions < 1.6.7 hang on `whoami`/login in non-interactive environments and lack `--json`.) +Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) --- @@ -145,7 +145,7 @@ Use the IDE's selection control (`ask_followup_question`) before running any log ⚠️ **CRITICAL**: After the user chooses, you MUST invoke login with an explicit `--site ` flag (e.g. `edgeone login --site china`). **NEVER run a bare `edgeone login` (without `--site`) when driven by an Agent / skill.** -On CLI ≥ 1.6.7, a bare `login` in a non-interactive context fails fast asking for +On CLI ≥ 1.6.0, a bare `login` in a non-interactive context fails fast asking for `--site` (it no longer pops an interactive site-picker that would hang). The site choice is meant to happen here in the conversation, not inside the CLI. @@ -297,7 +297,7 @@ The CLI auto-detects the framework, runs the build, and uploads the output direc ## ⚠️ Parse Deploy Output (Critical) -### Preferred: `--json` (CLI ≥ 1.6.7) +### Preferred: `--json` (CLI ≥ 1.6.0) When deploy is run with `--json`, the **last line** of stdout is a single JSON object — parse that directly, no regex / ANSI cleanup needed: @@ -357,11 +357,11 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| | `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.7 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | -| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.7) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | +| "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | -| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.7 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | +| Deploy seems to hang at `[DeployStatus] Deploying...` | On CLI ≥ 1.6.0 non-TTY emits heartbeat lines; it is NOT stuck. If a wrapper still mis-detects, use `--json` or run in background and poll. Do not kill it | | Auth error with token | Token may be expired — regenerate at the console | | Login appears successful but `deploy` reports auth error | Browser reused a session from the wrong site, binding the wrong account. Click "Sign in with a different account" on the login page, or log out from all Tencent Cloud consoles first | | `edgeone whoami` shows an unexpected account | Browser session reuse. Click "Sign in with a different account" or log out from all consoles and re-login | diff --git a/skills/makers-deploy/references/command-reference.md b/skills/makers-deploy/references/command-reference.md index ba067d8..f1c77d2 100644 --- a/skills/makers-deploy/references/command-reference.md +++ b/skills/makers-deploy/references/command-reference.md @@ -45,7 +45,7 @@ edgeone makers link --name -t # Non-interactive | Action | Command | |--------|---------| | Install CLI | `npm install -g edgeone@latest` | -| Check version | `edgeone -v` (require ≥ 1.6.7) | +| Check version | `edgeone -v` (require ≥ 1.6.0) | | Login (China, browser) | `edgeone login --site china` | | Login (Global, browser) | `edgeone login --site global` | | Login (token, auto-site) | `edgeone login --token ` | diff --git a/skills/makers-env-adaption/SKILL.md b/skills/makers-env-adaption/SKILL.md index c782c93..d83a45b 100644 --- a/skills/makers-env-adaption/SKILL.md +++ b/skills/makers-env-adaption/SKILL.md @@ -33,14 +33,12 @@ metadata: When you reach the "display / preview" step, **read this first before deciding how to call `present_files`**: ``` -Dev done ──→ ASK the user how to verify (never assume) - │ - ├─ "deploy directly" ──→ deploy → present_files(deployed EdgeOne URL) ✅ - │ - ├─ "local preview" ────────────┐ - └─ "preview then deploy" ──────┤ - ├─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ - └─ No ──→ start edgeone makers dev → present_files(...) + ┌─ Delivering finished work? ── Yes ──→ present_files(deployed EdgeOne URL) ✅ + │ +Enter ┤ +preview │ ┌─ dev server running? ─ Yes ──→ present_files(http://127.0.0.1:8088/) ✅ + └─ Still iterating? ───┤ + └─ No ──→ start edgeone makers dev → present_files(...) ``` | What you want to do | Correct approach | Wrong approach (breaks) | @@ -186,7 +184,7 @@ A `setLocalData EPERM` does not affect the running service; it only affects the ### 7.1 Preview & Dev Server full flow (MUST use HTTP, file:// forbidden) -After finishing development, **ask the user how they want to verify** — do not assume local preview. Offer three options: local preview / deploy directly / preview then deploy. Once the user picks local preview, follow this flow: +After finishing development, **start the dev server and preview directly** — do not ask "do you want to preview?". Full flow: > ⚠️ **WorkBuddy default behavior**: when you create an HTML file the platform may auto-open a preview via file:// — **ignore it**, that is not a valid preview. You must wait until `edgeone makers dev` is up, then re-open via the HTTP URL to override it. diff --git a/skills/makers-recipes/SKILL.md b/skills/makers-recipes/SKILL.md index 1dbd722..9e055f8 100644 --- a/skills/makers-recipes/SKILL.md +++ b/skills/makers-recipes/SKILL.md @@ -17,7 +17,7 @@ metadata: # Common Recipes -> ⛔ **Preview rules**: after finishing development, ask the user how they want to verify — never assume local preview. When they do choose local preview, you MUST serve it via `edgeone makers dev` and open `http://127.0.0.1:8088/` with `present_files`. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. +> ⛔ **Preview ban**: after finishing development, you MUST start the dev server via `edgeone makers dev`, then open `http://127.0.0.1:8088/` with `present_files` to preview. Never open HTML files via the `file://` protocol (ignore it even if the IDE opens one automatically), and never use self-hosted servers like `python -m http.server` or `npx serve`. Next.js projects must also set `allowedDevOrigins: ["127.0.0.1"]` in `next.config`. > ⚠️ **`.env.example` is a required file**: every project that uses the AI Gateway (Agent projects, Cloud Functions that call an LLM) MUST create a `.env.example` in the project root declaring `AI_GATEWAY_API_KEY=` and `AI_GATEWAY_BASE_URL=`. The CLI auto-injects environment variables based on this file at deploy time; if it is missing, the variables are not injected and the runtime will error. From abf252f786ed07d149c903d63962df32416210fa Mon Sep 17 00:00:00 2001 From: jesperxu Date: Mon, 10 Aug 2026 19:51:17 +0800 Subject: [PATCH 26/29] =?UTF-8?q?=E5=88=A0=E9=99=A4=20next.config=20?= =?UTF-8?q?=E7=9A=84=E5=90=A6=E5=AE=9A=E5=BC=8F=E6=A0=A1=E9=AA=8C=E8=A7=84?= =?UTF-8?q?=E5=88=99=EF=BC=8C=E9=81=BF=E5=85=8D=E8=AF=AF=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex/makers-recipes.md | 7 ------- cursor/rules/makers-recipes.mdc | 7 ------- skills/makers-recipes/SKILL.md | 7 ------- 3 files changed, 21 deletions(-) diff --git a/codex/makers-recipes.md b/codex/makers-recipes.md index 9e055f8..5fc18a0 100644 --- a/codex/makers-recipes.md +++ b/codex/makers-recipes.md @@ -3,13 +3,6 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. -pathPatterns: - - next.config.js - - next.config.mjs - - next.config.ts -validate: - - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" - message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-recipes.mdc b/cursor/rules/makers-recipes.mdc index 9e055f8..5fc18a0 100644 --- a/cursor/rules/makers-recipes.mdc +++ b/cursor/rules/makers-recipes.mdc @@ -3,13 +3,6 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. -pathPatterns: - - next.config.js - - next.config.mjs - - next.config.ts -validate: - - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" - message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" diff --git a/skills/makers-recipes/SKILL.md b/skills/makers-recipes/SKILL.md index 9e055f8..5fc18a0 100644 --- a/skills/makers-recipes/SKILL.md +++ b/skills/makers-recipes/SKILL.md @@ -3,13 +3,6 @@ name: edgeone-makers-recipes description: >- Project structure templates and scaffolding recipes for typical EdgeOne Makers applications — full-stack apps, static sites, API services, and AI agent projects. -pathPatterns: - - next.config.js - - next.config.mjs - - next.config.ts -validate: - - pattern: "^(?![\\s\\S]*allowedDevOrigins)[\\s\\S]*$" - message: "Next.js dev server must set allowedDevOrigins: [\"127.0.0.1\"] — otherwise the HMR WebSocket is blocked in the sandbox and the page stops responding." metadata: author: edgeone version: "1.0.0" From 362fe0b2a21511f46f9a871492073c8d41b0f88d Mon Sep 17 00:00:00 2001 From: jesperxu Date: Mon, 10 Aug 2026 20:06:27 +0800 Subject: [PATCH 27/29] =?UTF-8?q?hook=20=E5=87=BA=E9=94=99=E6=97=B6?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E9=80=80=E5=87=BA=EF=BC=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=9B=A0=E7=BC=BA=20skills=20=E7=9B=AE=E5=BD=95=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/validate-write.mjs | 54 +++++++++++++++++++++++++++++------ hooks/validate-write.test.mjs | 22 ++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/hooks/validate-write.mjs b/hooks/validate-write.mjs index bf6422f..c916604 100644 --- a/hooks/validate-write.mjs +++ b/hooks/validate-write.mjs @@ -112,12 +112,32 @@ function parseSkillValidateRule(skillPath) { }; } +/** + * 读取各 skill 声明的 validate 规则。 + * + * 读不到就返回空数组,绝不抛错:本函数跑在 PreToolUse 钩子里, + * 模型每写一个文件都会经过它。skills/ 不存在(部分安装、CLAUDE_PLUGIN_ROOT + * 解析错位)时若抛 ENOENT,用户每次写文件都会看到一次报错。 + * 校验器失效的正确表现是「不提醒」,而不是「报错」。 + */ export function loadSkillValidateRules(skillsDir = DEFAULT_SKILLS_DIR) { if (skillsDir === DEFAULT_SKILLS_DIR && cachedRules) return cachedRules; - const rules = readdirSync(skillsDir, { withFileTypes: true }) + 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) => parseSkillValidateRule(skillPath)) + .map((skillPath) => { + try { + return parseSkillValidateRule(skillPath); + } catch { + return null; + } + }) .filter(Boolean); if (skillsDir === DEFAULT_SKILLS_DIR) cachedRules = rules; return rules; @@ -226,17 +246,33 @@ async function readStdin() { return input; } +/** + * 钩子入口。任何异常都吞掉并静默返回: + * 这段代码挡在模型每一次 Edit/Write 前面,宁可漏一次提醒, + * 也不能因为自身出错(stdin 不是合法 JSON、规则读不到等) + * 让用户每写一个文件都看到一次报错。 + */ export async function main() { - const rawInput = await readStdin(); - const payload = rawInput.trim() ? JSON.parse(rawInput) : {}; - const output = buildValidateWriteOutput(payload, { enableSignalLog: true }); + let payload; + try { + const rawInput = await readStdin(); + payload = rawInput.trim() ? JSON.parse(rawInput) : {}; + } catch { + return; + } + + let output; + try { + output = buildValidateWriteOutput(payload, { enableSignalLog: true }); + } catch { + return; + } + 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); - }); + // main() 内部已兜住所有异常;这里再兜一层,保证退出码始终是 0。 + main().catch(() => {}); } diff --git a/hooks/validate-write.test.mjs b/hooks/validate-write.test.mjs index 1417324..46ae59b 100644 --- a/hooks/validate-write.test.mjs +++ b/hooks/validate-write.test.mjs @@ -303,3 +303,25 @@ test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.8 every declared ru } } }); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.9 returns no rules when the skills directory is missing', () => { + // 部分安装 / CLAUDE_PLUGIN_ROOT 解析错位时,skills/ 可能不存在。 + // 校验器失效应当是「不提醒」,不能变成每次写文件都抛 ENOENT。 + assert.deepEqual(loadSkillValidateRules('/nonexistent-skills-dir-for-test'), []); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.9 returns no rules when the skills path is a file', () => { + assert.deepEqual(loadSkillValidateRules('hooks/validate-write.mjs'), []); +}); + +test('plugin-skill-injection-optimization.VALIDATE_RED_LINES.9 yields no output instead of throwing when rules cannot be loaded', () => { + const output = buildValidateWriteOutput( + { + tool_name: 'Write', + tool_input: { file_path: 'agents/chat/index.ts', content: 'process.env.KEY' }, + }, + { rules: loadSkillValidateRules('/nonexistent-skills-dir-for-test') }, + ); + + assert.equal(output, null); +}); From 72ad5a4e1dbb7d8187ac927340b870b3b268607e Mon Sep 17 00:00:00 2001 From: jesperxu Date: Tue, 11 Aug 2026 16:47:08 +0800 Subject: [PATCH 28/29] =?UTF-8?q?feat:npm=20=E9=BB=98=E8=AE=A4=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=B7=98=E5=AE=9D=E6=BA=90=EF=BC=8C=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=90=8E=E7=9A=84=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/makers-agents/SKILL.md | 2 +- skills/makers-cli/SKILL.md | 29 ++++++- .../references/go-functions.md | 2 +- .../references/node-functions.md | 2 +- .../references/python-functions.md | 2 +- skills/makers-deploy/SKILL.md | 75 +++++++++++++++---- .../references/command-reference.md | 2 +- 7 files changed, 90 insertions(+), 24 deletions(-) diff --git a/skills/makers-agents/SKILL.md b/skills/makers-agents/SKILL.md index 0fa27bd..edd38df 100644 --- a/skills/makers-agents/SKILL.md +++ b/skills/makers-agents/SKILL.md @@ -262,7 +262,7 @@ Need a sandbox to run code, process uploaded files, or use MCP tools? ### Install the EdgeOne CLI ```bash -npm install -g edgeone +npm install -g edgeone@latest --registry=https://registry.npmmirror.com ``` Verify: `edgeone -v`. diff --git a/skills/makers-cli/SKILL.md b/skills/makers-cli/SKILL.md index ea6e420..b0328b8 100644 --- a/skills/makers-cli/SKILL.md +++ b/skills/makers-cli/SKILL.md @@ -12,11 +12,34 @@ metadata: ## Install +**Default — install from the npmmirror registry** (significantly faster for users in mainland China; the CLI package itself is identical on both registries): + +```bash +npm install -g edgeone@latest --registry=https://registry.npmmirror.com +``` + +Verify: `edgeone -v` — output must be `1.6.7` or higher. + +### If the mirror install fails → retry once against the official registry + +npmmirror is a lazy-sync mirror, so a freshly published CLI version can lag behind by minutes. If the mirror install fails, or `edgeone -v` reports a version below `1.6.7`, fall back to the official registry: + ```bash -npm install -g edgeone +npm install -g edgeone@latest --registry=https://registry.npmjs.org ``` -Verify: `edgeone -v` +Tell the user which registry you used and why before running the fallback. Do not silently retry more than once per registry. + +### Install error reference + +| Error | Cause | Action | +|-------|-------|--------| +| `ETIMEDOUT` / `ENOTFOUND` / `EAI_AGAIN` / `network` | Registry unreachable | Retry once on the other registry (see above) | +| `edgeone -v` < `1.6.7` after install | Mirror lag, or a stale global install | Retry on the official registry | +| `EACCES` / `permission denied` | No write access to the global prefix | Do NOT use `sudo` unprompted. Tell the user and suggest `npm config set prefix ~/.npm-global` (plus adding `~/.npm-global/bin` to PATH), or ask them to run the install themselves | +| `command not found: edgeone` right after a successful install | Global bin dir not on PATH | Report the npm global prefix (`npm prefix -g`) and ask the user to add its `bin/` to PATH | + +> ⚠️ Version `>= 1.6.7` is required. Older versions hang on interactive prompts in Agent/CI/sandbox environments and lack `--json` output. Never proceed with an outdated version. ## Commands @@ -52,7 +75,7 @@ Or inline: `PAGES_SOURCE=skills edgeone makers dev` ### First-time setup ```bash -npm install -g edgeone +npm install -g edgeone@latest --registry=https://registry.npmmirror.com edgeone login PAGES_SOURCE=skills edgeone makers link PAGES_SOURCE=skills edgeone makers env pull diff --git a/skills/makers-cloud-functions/references/go-functions.md b/skills/makers-cloud-functions/references/go-functions.md index f2803af..ac02097 100644 --- a/skills/makers-cloud-functions/references/go-functions.md +++ b/skills/makers-cloud-functions/references/go-functions.md @@ -176,7 +176,7 @@ cloud-functions/ Prerequisites: Go installed locally. ```bash -npm install -g edgeone # Install CLI +npm install -g edgeone@latest --registry=https://registry.npmmirror.com # Install CLI (mirror; fallback: registry.npmjs.org) edgeone makers dev # Start local dev server ``` diff --git a/skills/makers-cloud-functions/references/node-functions.md b/skills/makers-cloud-functions/references/node-functions.md index b9f08da..8b2f5e8 100644 --- a/skills/makers-cloud-functions/references/node-functions.md +++ b/skills/makers-cloud-functions/references/node-functions.md @@ -254,7 +254,7 @@ export function onRequestGet(context) { ## Local Development ```bash -npm install -g edgeone # Install CLI +npm install -g edgeone@latest --registry=https://registry.npmmirror.com # Install CLI (mirror; fallback: registry.npmjs.org) edgeone makers dev # Start local dev server on port 8088 ``` diff --git a/skills/makers-cloud-functions/references/python-functions.md b/skills/makers-cloud-functions/references/python-functions.md index 0a4a115..7499e6f 100644 --- a/skills/makers-cloud-functions/references/python-functions.md +++ b/skills/makers-cloud-functions/references/python-functions.md @@ -240,7 +240,7 @@ These are not scanned or copied to build output: ## Local Development ```bash -npm install -g edgeone # Install CLI +npm install -g edgeone@latest --registry=https://registry.npmmirror.com # Install CLI (mirror; fallback: registry.npmjs.org) edgeone makers dev # Start local dev server ``` diff --git a/skills/makers-deploy/SKILL.md b/skills/makers-deploy/SKILL.md index 35258ef..a6a5cf4 100644 --- a/skills/makers-deploy/SKILL.md +++ b/skills/makers-deploy/SKILL.md @@ -46,11 +46,14 @@ Deploy any project to **EdgeOne Makers**. **Self-check after writing your reply**: scan for every instance of the `.edgeone.cool` domain. Does each one include `?eo_token=`? If any doesn't, fix it NOW — the user will get a 401. 3a. **Prefer `--json` when running non-interactively** — in Agent/CI/headless contexts, always pass `--json` to `deploy` so the result is a single machine-readable line; no need to scrape colored/`\r`-animated stdout. See **Parse Deploy Output**. 3b. **Use `edgeone whoami` to check login status** — on CLI ≥ 1.6.0, `whoami` fails fast (exit 1) when not logged in instead of hanging. If it exits 0, the user is already logged in and `-t` is not needed. **Do NOT** check `cat .edgeone/.token` — CLI stores credentials in `~/.edgeone/` files, not a fixed `.token` path. -4. **⚠️ The deploy URL MUST be placed prominently at the very top of your reply** — once deployment finishes, the complete access URL is the core deliverable the user cares about most. You MUST: ① place it on the first line or in the first standalone block of your reply body; ② use a prominent format (e.g. a large heading + code block); ③ never bury the URL in the middle of a long paragraph where the user has to hunt for it. Example format: +4. **⚠️ The deploy result block is a fixed format — open your reply with it and add nothing extra.** Your reply MUST begin with the line `🎉 部署成功,页面已上线至 EdgeOne Makers`, followed by the complete access URL, followed by the console URL — and nothing else about the deployment. **Do NOT append caveats, expiry claims, access-policy claims, or console menu paths.** See **Present the Deploy Result** for the exact template and the list of banned fabrications. ``` - 🌐 Live URL: https://my-project-abc123.edgeone.cool? + 🎉 部署成功,页面已上线至 EdgeOne Makers + + 🌐 https://my-project-abc123.edgeone.cool? + + 控制台: ``` - Then append any other notes (console URL, caveats, etc.). 5. **Ask the user to choose China or Global site** before browser login. Never assume. (Token login via `edgeone login --token` auto-detects site, no need to ask.) 6. **Auto-detect the login method** — browser login in desktop environments, token login in headless/remote/CI environments. Follow the decision table below. 7. **After token login, ask if the user wants to save the token locally** for future use. @@ -110,11 +113,23 @@ cat edgeone.json 2>/dev/null ## Install CLI +**Default — install from the npmmirror registry** (much faster in mainland China; identical package): + +```bash +npm install -g edgeone@latest --registry=https://registry.npmmirror.com +``` + +Verify: `edgeone -v` — confirm output is `1.6.0` or higher. + +If the mirror install fails, or the version is still below `1.6.0`, retry **once** against the official registry (npmmirror is a lazy-sync mirror and can lag behind a fresh publish): + ```bash -npm install -g edgeone@latest +npm install -g edgeone@latest --registry=https://registry.npmjs.org ``` -Verify: `edgeone -v` — confirm output is `1.6.0` or higher. Retry installation if not. (Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) +(Versions < 1.6.0 hang on `whoami`/login in non-interactive environments and lack `--json`.) + +> For the full install error reference (`ETIMEDOUT`, `EACCES`, PATH issues), see the `makers-cli` skill's **Install** section. --- @@ -331,9 +346,43 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | **Project ID** | Value after `EDGEONE_PROJECT_ID=` | — | | **Console URL** | Line after "You can view your deployment..." | — | -**Show the user — the deploy URL MUST be placed at the very top of your reply, in the most prominent position:** +## ⛔ Present the Deploy Result (exact format — no improvisation) + +Your reply MUST open with this line, verbatim: + +``` +🎉 部署成功,页面已上线至 EdgeOne Makers +``` + +Then the full access URL, then the console URL. Nothing else. -⚠️ **URL Integrity Rules (read before composing your reply):** +**Template:** + +> 🎉 部署成功,页面已上线至 EdgeOne Makers +> +> 🌐 `https://my-project-abc123.edgeone.cool?eo_token=abc123&eo_time=1234567890` +> +> 控制台:`` + +### ⛔ No extra description — this is the rule most often broken + +**Do NOT add any description, caveat, or guidance about the URL, the project, or the console.** Output only the three lines above. Specifically, NEVER write any of these — every one of them has been fabricated by a model at some point and is either wrong or unverifiable: + +| ❌ Never write | Why | +|---------------|-----| +| Any console menu path (e.g. "设置 → 数据管理 → 我发布的应用", "Settings → My Apps") | **These menus do not exist.** You cannot know the console's navigation structure. Paste the `consoleUrl` and stop. | +| "永久有效" / "permanent" / "公开访问" / "no auth needed" / "anyone can access" | You cannot verify the URL's access policy or lifetime | +| ICP filing / 备案 explanations, CDN policy explanations | Unverifiable from deploy output | +| Invented expiry times ("链接 3 小时后失效", "valid for 24 hours") | Only state an expiry if the CLI output contains one | +| Custom-domain binding instructions, DNS steps | Not part of the deploy result | +| Invented next steps ("你可以在控制台开启 xxx") | You do not know which features exist | + +If the CLI's JSON output contains an `instruction` field, follow it exactly. If it contains `expiredTime`, you may state that specific expiry — otherwise say nothing about expiry. + +> Rule of thumb: **if a fact is not literally present in the CLI's output, it does not go into your reply.** + + +### URL Integrity Rules (read before composing your reply) | Rule | Detail | |------|--------| @@ -342,13 +391,7 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | **Concrete, not abstract** | Use the actual URL from deploy output. Do not replace query params with `...` or `(params omitted)` or any placeholder in user-facing text. | | **Self-check before sending** | Search your draft for `.edgeone.cool` — every hit must have `?eo_token=`. | -> 🌐 **Live URL**: `https://my-project-abc123.edgeone.cool?eo_token=abc123&eo_time=1234567890` -> -> --- -> -> - **Console URL**: `https://console.cloud.tencent.com/edgeone/pages/project/...` -> -> ℹ️ Note: This preview URL is for quick deployment verification. When accessed from mainland China, the link may become restricted (e.g., 401) after some time or when shared, due to domain ICP filing status or CDN acceleration policies. For long-term stable public access, bind a custom domain with proper ICP filing. +> **Scope**: these presentation rules govern the deploy-result block. A later summary of the work (architecture, code walkthrough, follow-up suggestions) is fine — but any URL quoted there must still be complete, and the fabrication bans above still apply. --- @@ -356,8 +399,8 @@ https://console.cloud.tencent.com/edgeone/pages/project/pages-xxxxxxxx/deploymen | Error | Solution | |-------|----------| -| `command not found: edgeone` | Run `npm install -g edgeone@latest` | -| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest`. Older versions hang on whoami/login in non-interactive contexts | +| `command not found: edgeone` | Run `npm install -g edgeone@latest --registry=https://registry.npmmirror.com` (see **Install CLI** for the official-registry fallback) | +| CLI version < 1.6.0 | Reinstall: `npm install -g edgeone@latest --registry=https://registry.npmmirror.com`. If the version is still low, retry on `--registry=https://registry.npmjs.org` (mirror lag). Older versions hang on whoami/login in non-interactive contexts | | Browser does not open during login | Switch to token login | | "not authenticated" / exit 1 from `whoami` (CLI ≥ 1.6.0) | Expected when not logged in — whoami now fails fast instead of hanging. Run `edgeone login` (desktop) or provide a token | | Non-interactive deploy says "browser login is unavailable" + exits 1 | Expected fail-fast in Agent/CI/headless with no token. Provide a token via `-t ` or set `EDGEONE_PAGES_API_TOKEN` | diff --git a/skills/makers-deploy/references/command-reference.md b/skills/makers-deploy/references/command-reference.md index f1c77d2..2c2de7f 100644 --- a/skills/makers-deploy/references/command-reference.md +++ b/skills/makers-deploy/references/command-reference.md @@ -44,7 +44,7 @@ edgeone makers link --name -t # Non-interactive | Action | Command | |--------|---------| -| Install CLI | `npm install -g edgeone@latest` | +| Install CLI | `npm install -g edgeone@latest --registry=https://registry.npmmirror.com` (fallback: `--registry=https://registry.npmjs.org`) | | Check version | `edgeone -v` (require ≥ 1.6.0) | | Login (China, browser) | `edgeone login --site china` | | Login (Global, browser) | `edgeone login --site global` | From 6d02ee31461147c1c92937d620c0ac90e281c84d Mon Sep 17 00:00:00 2001 From: jesperxu Date: Thu, 13 Aug 2026 17:12:30 +0800 Subject: [PATCH 29/29] =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=A4=9A=E5=B9=B3=E5=8F=B0=E4=BA=A7=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codex/makers-agents.md | 9 +++++++++ codex/makers-cloud-functions.md | 5 +++++ codex/makers-deploy.md | 6 ++++++ codex/makers-edge-functions.md | 2 ++ codex/makers-env-adaption.md | 8 ++++++++ codex/makers-middleware.md | 6 ++++++ codex/makers-migration.md | 6 ++++++ codex/makers-storage.md | 5 +++++ cursor/rules/makers-agents.mdc | 9 +++++++++ cursor/rules/makers-cloud-functions.mdc | 5 +++++ cursor/rules/makers-deploy.mdc | 6 ++++++ cursor/rules/makers-edge-functions.mdc | 2 ++ cursor/rules/makers-env-adaption.mdc | 8 ++++++++ cursor/rules/makers-middleware.mdc | 6 ++++++ cursor/rules/makers-migration.mdc | 6 ++++++ cursor/rules/makers-storage.mdc | 5 +++++ 16 files changed, 94 insertions(+) diff --git a/codex/makers-agents.md b/codex/makers-agents.md index 474e539..98648ab 100644 --- a/codex/makers-agents.md +++ b/codex/makers-agents.md @@ -15,6 +15,15 @@ description: >- Do NOT trigger for deployment workflows (use edgeone-pages-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. +pathPatterns: + - agents/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside agents/ and cloud-functions/, never process.env or os.environ (Critical Rule 3)." + - pattern: "headers\\s*\\.\\s*get\\s*\\(" + message: "Headers are plain objects here: context.request.headers['x-name'], not .get('x-name') (Critical Rule 4)." + - pattern: "langgraphStore\\s*\\?\\?\\s*store" + message: "Never write `store?.langgraphStore ?? store` — in cloud-function context it falls back to a store with no .get and crashes (Critical Rule 12)." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-cloud-functions.md b/codex/makers-cloud-functions.md index 7b8ce09..86832a1 100644 --- a/codex/makers-cloud-functions.md +++ b/codex/makers-cloud-functions.md @@ -3,6 +3,11 @@ name: edgeone-makers-cloud-functions description: >- EdgeOne Makers Cloud Functions — Node.js, Go, and Python runtimes. Use when building server-side APIs, Express/Koa patterns, or backend logic. +pathPatterns: + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside cloud-functions/, never process.env or os.environ." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-deploy.md b/codex/makers-deploy.md index dc2dff9..c83ec64 100644 --- a/codex/makers-deploy.md +++ b/codex/makers-deploy.md @@ -12,6 +12,12 @@ description: >- commands — the skill contains critical rules for parsing deploy output and presenting access URLs. Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy — use edgeone-makers-dev for troubleshooting). +pathPatterns: + - "*.sh" + - .github/workflows/** +validate: + - pattern: "whoami[^\\n]*\\s-t\\s" + message: "edgeone whoami does not accept -t. Check the exit code instead: 0 = logged in, 1 = not." metadata: author: edgeone version: "2.2.0" diff --git a/codex/makers-edge-functions.md b/codex/makers-edge-functions.md index b7d0502..98bcd31 100644 --- a/codex/makers-edge-functions.md +++ b/codex/makers-edge-functions.md @@ -13,6 +13,8 @@ validate: message: "Use plain object headers for this runtime surface." - pattern: "fs\\.writeFile" message: "Edge Functions do not support filesystem writes." + - pattern: "Response\\.json\\s*\\(" + message: "Response.json() is not available in this V8 runtime — use new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-env-adaption.md b/codex/makers-env-adaption.md index 495ae8a..6921ae9 100644 --- a/codex/makers-env-adaption.md +++ b/codex/makers-env-adaption.md @@ -7,6 +7,14 @@ description: >- Covers: non-interactive CLI flags, network isolation workarounds, login in sandbox, proxy bypass, file preview constraints (MUST use http:// via dev server, NEVER file://, NEVER python -m http.server / npx serve), dev server requirements. +pathPatterns: + - "*.sh" + - package.json +validate: + - pattern: "python\\s+-m\\s+http\\.server|npx\\s+(serve|http-server)" + message: "Use `edgeone makers dev` — self-hosted static servers skip Blob credentials, Cloud Functions routing, Edge Functions and middleware." + - pattern: "localhost:80(88|89)" + message: "Use 127.0.0.1, not localhost — in the sandbox localhost resolves to ::1 and yields false 404s." metadata: author: edgeone version: "1.1.1" diff --git a/codex/makers-middleware.md b/codex/makers-middleware.md index 751cc3d..c1d580e 100644 --- a/codex/makers-middleware.md +++ b/codex/makers-middleware.md @@ -3,6 +3,12 @@ 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). +pathPatterns: + - middleware.js + - middleware.ts +validate: + - pattern: "NextRequest|NextResponse|next/server" + message: "Framework projects must use the framework's own middleware. This platform format takes a context object with next/redirect/rewrite — not NextRequest/NextResponse." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-migration.md b/codex/makers-migration.md index afe05be..94826dc 100644 --- a/codex/makers-migration.md +++ b/codex/makers-migration.md @@ -7,6 +7,12 @@ description: >- convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use makers-agents instead). +pathPatterns: + - agents/** + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Migration checklist: replace process.env / os.environ with context.env (TS) or ctx env access (Python)." metadata: author: edgeone version: "1.0.0" diff --git a/codex/makers-storage.md b/codex/makers-storage.md index 304f586..514a977 100644 --- a/codex/makers-storage.md +++ b/codex/makers-storage.md @@ -3,6 +3,11 @@ name: edgeone-makers-storage description: >- KV and Blob storage services on EdgeOne Makers. KV for edge key-value pairs, Blob for file/object storage in Cloud Functions. Covers SDK usage, setup, and troubleshooting. +pathPatterns: + - edge-functions/** +validate: + - pattern: "context\\.env\\.[A-Za-z_]*[Kk][Vv]" + message: "KV is a console-bound global variable, not on context.env — call my_kv.get(...) directly. See makers-storage/references/kv.md." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-agents.mdc b/cursor/rules/makers-agents.mdc index 474e539..98648ab 100644 --- a/cursor/rules/makers-agents.mdc +++ b/cursor/rules/makers-agents.mdc @@ -15,6 +15,15 @@ description: >- Do NOT trigger for deployment workflows (use edgeone-pages-deploy). Do NOT trigger for generic AI framework development outside an EdgeOne Makers project. +pathPatterns: + - agents/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside agents/ and cloud-functions/, never process.env or os.environ (Critical Rule 3)." + - pattern: "headers\\s*\\.\\s*get\\s*\\(" + message: "Headers are plain objects here: context.request.headers['x-name'], not .get('x-name') (Critical Rule 4)." + - pattern: "langgraphStore\\s*\\?\\?\\s*store" + message: "Never write `store?.langgraphStore ?? store` — in cloud-function context it falls back to a store with no .get and crashes (Critical Rule 12)." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-cloud-functions.mdc b/cursor/rules/makers-cloud-functions.mdc index 7b8ce09..86832a1 100644 --- a/cursor/rules/makers-cloud-functions.mdc +++ b/cursor/rules/makers-cloud-functions.mdc @@ -3,6 +3,11 @@ name: edgeone-makers-cloud-functions description: >- EdgeOne Makers Cloud Functions — Node.js, Go, and Python runtimes. Use when building server-side APIs, Express/Koa patterns, or backend logic. +pathPatterns: + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Read env via context.env inside cloud-functions/, never process.env or os.environ." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-deploy.mdc b/cursor/rules/makers-deploy.mdc index dc2dff9..c83ec64 100644 --- a/cursor/rules/makers-deploy.mdc +++ b/cursor/rules/makers-deploy.mdc @@ -12,6 +12,12 @@ description: >- commands — the skill contains critical rules for parsing deploy output and presenting access URLs. Do NOT trigger for post-deployment runtime errors (e.g. CORS issues, 500 errors after deploy — use edgeone-makers-dev for troubleshooting). +pathPatterns: + - "*.sh" + - .github/workflows/** +validate: + - pattern: "whoami[^\\n]*\\s-t\\s" + message: "edgeone whoami does not accept -t. Check the exit code instead: 0 = logged in, 1 = not." metadata: author: edgeone version: "2.2.0" diff --git a/cursor/rules/makers-edge-functions.mdc b/cursor/rules/makers-edge-functions.mdc index b7d0502..98bcd31 100644 --- a/cursor/rules/makers-edge-functions.mdc +++ b/cursor/rules/makers-edge-functions.mdc @@ -13,6 +13,8 @@ validate: message: "Use plain object headers for this runtime surface." - pattern: "fs\\.writeFile" message: "Edge Functions do not support filesystem writes." + - pattern: "Response\\.json\\s*\\(" + message: "Response.json() is not available in this V8 runtime — use new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-env-adaption.mdc b/cursor/rules/makers-env-adaption.mdc index 495ae8a..6921ae9 100644 --- a/cursor/rules/makers-env-adaption.mdc +++ b/cursor/rules/makers-env-adaption.mdc @@ -7,6 +7,14 @@ description: >- Covers: non-interactive CLI flags, network isolation workarounds, login in sandbox, proxy bypass, file preview constraints (MUST use http:// via dev server, NEVER file://, NEVER python -m http.server / npx serve), dev server requirements. +pathPatterns: + - "*.sh" + - package.json +validate: + - pattern: "python\\s+-m\\s+http\\.server|npx\\s+(serve|http-server)" + message: "Use `edgeone makers dev` — self-hosted static servers skip Blob credentials, Cloud Functions routing, Edge Functions and middleware." + - pattern: "localhost:80(88|89)" + message: "Use 127.0.0.1, not localhost — in the sandbox localhost resolves to ::1 and yields false 404s." metadata: author: edgeone version: "1.1.1" diff --git a/cursor/rules/makers-middleware.mdc b/cursor/rules/makers-middleware.mdc index 751cc3d..c1d580e 100644 --- a/cursor/rules/makers-middleware.mdc +++ b/cursor/rules/makers-middleware.mdc @@ -3,6 +3,12 @@ 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). +pathPatterns: + - middleware.js + - middleware.ts +validate: + - pattern: "NextRequest|NextResponse|next/server" + message: "Framework projects must use the framework's own middleware. This platform format takes a context object with next/redirect/rewrite — not NextRequest/NextResponse." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-migration.mdc b/cursor/rules/makers-migration.mdc index afe05be..94826dc 100644 --- a/cursor/rules/makers-migration.mdc +++ b/cursor/rules/makers-migration.mdc @@ -7,6 +7,12 @@ description: >- convert Express/Next.js API routes to Makers handlers, or add platform capabilities (context.tools, context.sandbox, context.store). Do NOT trigger for new agent projects (use makers-agents instead). +pathPatterns: + - agents/** + - cloud-functions/** +validate: + - pattern: "process\\.env|os\\.environ" + message: "Migration checklist: replace process.env / os.environ with context.env (TS) or ctx env access (Python)." metadata: author: edgeone version: "1.0.0" diff --git a/cursor/rules/makers-storage.mdc b/cursor/rules/makers-storage.mdc index 304f586..514a977 100644 --- a/cursor/rules/makers-storage.mdc +++ b/cursor/rules/makers-storage.mdc @@ -3,6 +3,11 @@ name: edgeone-makers-storage description: >- KV and Blob storage services on EdgeOne Makers. KV for edge key-value pairs, Blob for file/object storage in Cloud Functions. Covers SDK usage, setup, and troubleshooting. +pathPatterns: + - edge-functions/** +validate: + - pattern: "context\\.env\\.[A-Za-z_]*[Kk][Vv]" + message: "KV is a console-bound global variable, not on context.env — call my_kv.get(...) directly. See makers-storage/references/kv.md." metadata: author: edgeone version: "1.0.0"