openyida 2026.7.10 → 2026.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ const zlib = require('zlib');
9
9
  const ROOT = path.resolve(__dirname, '..');
10
10
  const SOURCE_ROOT = path.join(ROOT, 'yida-skills');
11
11
  const SOURCE_SUBSKILLS_ROOT = path.join(SOURCE_ROOT, 'skills');
12
+ const SOURCE_SKILLS_INDEX_FILE = path.join(SOURCE_ROOT, 'skills-index.json');
12
13
  const DEFAULT_OUTPUT_ROOT = path.join(ROOT, 'dist', 'skills', 'openyida');
13
14
  const DEFAULT_ZIP_OUT = path.join(ROOT, 'openyida-skills.zip');
14
15
 
@@ -123,6 +124,19 @@ function writeRootSkill(src, dest) {
123
124
  return 1;
124
125
  }
125
126
 
127
+ function copySkillsIndex(outputRoot) {
128
+ const dest = path.join(outputRoot, 'skills-index.json');
129
+ fs.copyFileSync(readRequiredFilePath(SOURCE_SKILLS_INDEX_FILE), dest);
130
+ return 1;
131
+ }
132
+
133
+ function readRequiredFilePath(src) {
134
+ if (!fs.existsSync(src)) {
135
+ throw new Error('Missing required file: ' + path.relative(ROOT, src));
136
+ }
137
+ return src;
138
+ }
139
+
126
140
  function transformSubskillReference(content) {
127
141
  return content
128
142
  .replace(/^---\n[\s\S]*?\n---\n?/, '')
@@ -253,6 +267,15 @@ function assertNoSourceSkillLinks(outputRoot) {
253
267
  }
254
268
  }
255
269
 
270
+ function assertGeneratedRootFiles(outputRoot) {
271
+ for (const fileName of ['SKILL.md', 'skills-index.json']) {
272
+ const filePath = path.join(outputRoot, fileName);
273
+ if (!fs.existsSync(filePath)) {
274
+ throw new Error('Generated OpenYida skill root missing required file: ' + path.relative(ROOT, filePath));
275
+ }
276
+ }
277
+ }
278
+
256
279
  function buildSkillsPackage(outputRoot) {
257
280
  if (!fs.existsSync(SOURCE_ROOT)) {
258
281
  throw new Error('Missing source skills directory: yida-skills');
@@ -266,6 +289,7 @@ function buildSkillsPackage(outputRoot) {
266
289
  path.join(SOURCE_ROOT, 'SKILL.md'),
267
290
  path.join(outputRoot, 'SKILL.md'),
268
291
  );
292
+ count += copySkillsIndex(outputRoot);
269
293
  count += copyReferencesRecursive(
270
294
  path.join(SOURCE_ROOT, 'references'),
271
295
  path.join(outputRoot, 'references'),
@@ -275,6 +299,7 @@ function buildSkillsPackage(outputRoot) {
275
299
  assertSingleWukongSkill(outputRoot);
276
300
  assertWukongFrontmatter(outputRoot);
277
301
  assertNoSourceSkillLinks(outputRoot);
302
+ assertGeneratedRootFiles(outputRoot);
278
303
 
279
304
  return count;
280
305
  }
@@ -287,7 +312,8 @@ function buildZipPackage(outputRoot, zipOut) {
287
312
  fs.mkdirSync(path.dirname(zipOut), { recursive: true });
288
313
  fs.rmSync(zipOut, { force: true });
289
314
 
290
- const zipBuffer = createZipBuffer(outputRoot);
315
+ const zipBuffer = createZipBuffer(outputRoot, { excludeRootSkillsIndex: true });
316
+ assertZipExcludesRootSkillsIndex(zipBuffer, outputRoot);
291
317
  fs.writeFileSync(zipOut, zipBuffer);
292
318
 
293
319
  const stat = fs.statSync(zipOut);
@@ -389,15 +415,26 @@ function writeCentralHeader(entry, nameBuffer, compressed, uncompressed, crc, me
389
415
  return header;
390
416
  }
391
417
 
392
- function createZipBuffer(outputRoot) {
418
+ function isExcludedZipEntry(entry, outputRoot, options) {
419
+ if (!options.excludeRootSkillsIndex || entry.isDirectory) {
420
+ return false;
421
+ }
422
+ const relativePath = path.relative(outputRoot, entry.absPath).split(path.sep).join('/');
423
+ return relativePath === 'skills-index.json';
424
+ }
425
+
426
+ function createZipBuffer(outputRoot, options = {}) {
393
427
  const entries = [];
394
428
  collectZipEntries(outputRoot, path.basename(outputRoot), entries);
429
+ const zipEntries = entries.filter(function(entry) {
430
+ return !isExcludedZipEntry(entry, outputRoot, options);
431
+ });
395
432
 
396
433
  const chunks = [];
397
434
  const centralChunks = [];
398
435
  let offset = 0;
399
436
 
400
- for (const entry of entries) {
437
+ for (const entry of zipEntries) {
401
438
  const nameBuffer = Buffer.from(entry.entryName.replace(/\\/g, '/'), 'utf8');
402
439
  const uncompressed = entry.isDirectory ? Buffer.alloc(0) : fs.readFileSync(entry.absPath);
403
440
  const method = entry.isDirectory ? 0 : 8;
@@ -421,8 +458,8 @@ function createZipBuffer(outputRoot) {
421
458
  end.writeUInt32LE(0x06054b50, 0);
422
459
  end.writeUInt16LE(0, 4);
423
460
  end.writeUInt16LE(0, 6);
424
- end.writeUInt16LE(entries.length, 8);
425
- end.writeUInt16LE(entries.length, 10);
461
+ end.writeUInt16LE(zipEntries.length, 8);
462
+ end.writeUInt16LE(zipEntries.length, 10);
426
463
  end.writeUInt32LE(centralSize, 12);
427
464
  end.writeUInt32LE(centralOffset, 16);
428
465
  end.writeUInt16LE(0, 20);
@@ -430,6 +467,46 @@ function createZipBuffer(outputRoot) {
430
467
  return Buffer.concat(chunks.concat(centralChunks, end));
431
468
  }
432
469
 
470
+ function findEndOfCentralDirectory(zipBuffer) {
471
+ for (let offset = zipBuffer.length - 22; offset >= 0; offset--) {
472
+ if (zipBuffer.readUInt32LE(offset) === 0x06054b50) {
473
+ return offset;
474
+ }
475
+ }
476
+ throw new Error('Invalid zip package: missing end of central directory');
477
+ }
478
+
479
+ function listZipEntryNames(zipBuffer) {
480
+ const endOffset = findEndOfCentralDirectory(zipBuffer);
481
+ const entryCount = zipBuffer.readUInt16LE(endOffset + 10);
482
+ let offset = zipBuffer.readUInt32LE(endOffset + 16);
483
+ const names = [];
484
+
485
+ for (let index = 0; index < entryCount; index++) {
486
+ if (zipBuffer.readUInt32LE(offset) !== 0x02014b50) {
487
+ throw new Error('Invalid zip package: malformed central directory');
488
+ }
489
+
490
+ const nameLength = zipBuffer.readUInt16LE(offset + 28);
491
+ const extraLength = zipBuffer.readUInt16LE(offset + 30);
492
+ const commentLength = zipBuffer.readUInt16LE(offset + 32);
493
+ const nameStart = offset + 46;
494
+ const nameEnd = nameStart + nameLength;
495
+ names.push(zipBuffer.slice(nameStart, nameEnd).toString('utf8'));
496
+ offset = nameEnd + extraLength + commentLength;
497
+ }
498
+
499
+ return names;
500
+ }
501
+
502
+ function assertZipExcludesRootSkillsIndex(zipBuffer, outputRoot) {
503
+ const rootIndexEntryName = path.basename(outputRoot).replace(/\\/g, '/') + '/skills-index.json';
504
+ const names = listZipEntryNames(zipBuffer);
505
+ if (names.includes(rootIndexEntryName)) {
506
+ throw new Error('Wukong upload zip must not include root skills-index.json: ' + rootIndexEntryName);
507
+ }
508
+ }
509
+
433
510
  function formatBytes(size) {
434
511
  if (size < 1024) {return size + ' B';}
435
512
  if (size < 1024 * 1024) {return (size / 1024).toFixed(1) + ' KB';}
@@ -29,6 +29,7 @@ const os = require('os');
29
29
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
30
30
  const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, 'package.json'));
31
31
  const SKILLS_DIR = path.join(PACKAGE_ROOT, 'yida-skills');
32
+ const SKILLS_INDEX_FILE = path.join(SKILLS_DIR, 'skills-index.json');
32
33
  const HOME_DIR = os.homedir();
33
34
  const CODEX_MARKETPLACE_NAME = 'openyida';
34
35
  const CODEX_PLUGIN_NAME = 'openyida';
@@ -205,10 +206,11 @@ description: >
205
206
  在执行任何会创建、修改或发布真实宜搭资源的操作前,先运行只读检查:
206
207
 
207
208
  \`\`\`bash
208
- openyida env --json
209
- openyida login --check-only --json
209
+ openyida agent-capabilities --json
210
210
  \`\`\`
211
211
 
212
+ 该命令一次返回版本、当前工作目录、AI 工具环境、登录态摘要和命令清单,避免反复探测 \`which\`、\`--version\`、\`--help\`、\`env\` 和 \`login --check-only\`。
213
+
212
214
  如果 \`openyida\` 不存在,先提醒用户需要安装,或在用户同意后执行:
213
215
 
214
216
  \`\`\`bash
@@ -251,7 +253,7 @@ openyida copy
251
253
 
252
254
  ## 子技能索引
253
255
 
254
- 根据用户意图选择最匹配的子技能。支持 \`use_skill\` / \`search_skills\` 的宿主中,必须调用 \`use_skill("<技能名>", "<本次目的>")\` 加载子技能;不要用 Read / read_file / cat 读取 SKILL.md 路径。\`skills-index.json\` 仅供 yida-agent 或同构宿主机器发现,不支持该索引的宿主忽略它。完全没有 \`use_skill\` 的本地工具,才允许按根技能路由表和技能包相对路径逐个读取当前阶段唯一必要的 SKILL.md,禁止并发批量读取多个 SKILL.md。
256
+ 根据用户意图选择最匹配的子技能。支持 \`use_skill\` / \`search_skills\` 的宿主中,必须调用 \`use_skill("<技能名>", "<本次目的>")\` 加载子技能;不要用 Read / read_file / cat 读取 SKILL.md 路径。\`skills-index.json\` 仅供 yida-agent 或同构宿主机器发现,不支持该索引的宿主忽略它。完全没有 \`use_skill\` 的本地工具,才允许按根技能路由表选定技能,并按 \`skills/<技能名>/SKILL.md\` 定位当前阶段唯一必要的 SKILL.md,禁止并发批量读取多个 SKILL.md。
255
257
 
256
258
  | 意图 | 子技能 |
257
259
  | --- | --- |
@@ -282,7 +284,7 @@ openyida copy
282
284
 
283
285
  - 不要编造 \`appType\`、\`formUuid\`、\`fieldId\`、\`reportId\`;必须从命令输出、缓存或 schema 中读取。
284
286
  - 同一命令失败后,根据错误信息检查登录态、组织、参数和字段 ID;不要无修改地连续重试。
285
- - 自定义页面发布前先运行 \`openyida check-page\` 和 \`openyida compile\`。
287
+ - native 自定义页面发布前先运行 \`openyida check-page\` 和 \`openyida compile\`;Code Canvas \`.canvas.jsx\` 页面不跑这两个 native 检查,使用 \`openyida publish\` 的 Canvas 编译阶段或 \`compileCanvasLocal\` 快检。
286
288
  - JSON 配置写入文件后先解析校验,再调用会修改平台资源的命令。
287
289
  - 新增用户可见文案或 CLI 行为时,遵循当前 OpenYida 仓库的 \`AGENTS.md\` 开发规范。
288
290
  `;
@@ -381,6 +383,10 @@ function installCodexPlugin() {
381
383
  createCodexPluginSkill(),
382
384
  'utf8',
383
385
  );
386
+ fs.copyFileSync(
387
+ SKILLS_INDEX_FILE,
388
+ path.join(pluginRoot, 'skills', CODEX_PLUGIN_NAME, 'skills-index.json'),
389
+ );
384
390
 
385
391
  writeCodexMarketplace(marketplaceRoot);
386
392
  ensureCodexConfig(codexDir, marketplaceRoot);
@@ -4,7 +4,11 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { COMMAND_GROUPS, flattenCommandManifest } = require('../lib/core/command-manifest');
7
+ const {
8
+ COMMAND_GROUPS,
9
+ flattenCommandManifest,
10
+ listCommandSideEffectIds,
11
+ } = require('../lib/core/command-manifest');
8
12
 
9
13
  const ROOT = path.resolve(__dirname, '..');
10
14
  const ROUTER_FILE = path.join(ROOT, 'bin/yida.js');
@@ -97,6 +101,45 @@ function validateGroupReferences() {
97
101
  }
98
102
  }
99
103
 
104
+ function validateSideEffects(commands) {
105
+ const allowedKinds = new Set(['local_read', 'local_write', 'remote_read', 'remote_write', 'mixed']);
106
+ const commandIds = new Set(commands.map(entry => entry.id));
107
+
108
+ for (const sideEffectId of listCommandSideEffectIds()) {
109
+ if (!commandIds.has(sideEffectId)) {
110
+ errors.push(`Side effect metadata references unknown command id: ${sideEffectId}`);
111
+ }
112
+ }
113
+
114
+ for (const entry of commands) {
115
+ const sideEffect = entry.sideEffect;
116
+ if (!sideEffect || typeof sideEffect !== 'object') {
117
+ errors.push(`Command manifest id "${entry.id}" is missing sideEffect metadata`);
118
+ continue;
119
+ }
120
+ if (!allowedKinds.has(sideEffect.kind)) {
121
+ errors.push(`Command manifest id "${entry.id}" has invalid sideEffect.kind "${sideEffect.kind}"`);
122
+ }
123
+ if (typeof sideEffect.mutates_yida !== 'boolean') {
124
+ errors.push(`Command manifest id "${entry.id}" sideEffect.mutates_yida must be boolean`);
125
+ }
126
+ if (typeof sideEffect.mutates_local !== 'boolean') {
127
+ errors.push(`Command manifest id "${entry.id}" sideEffect.mutates_local must be boolean`);
128
+ }
129
+ if (sideEffect.kind === 'mixed') {
130
+ if (sideEffect.action_dependent !== true) {
131
+ errors.push(`Command manifest id "${entry.id}" mixed sideEffect.action_dependent must be true`);
132
+ }
133
+ if (!Array.isArray(sideEffect.read_actions)) {
134
+ errors.push(`Command manifest id "${entry.id}" mixed sideEffect.read_actions must be an array`);
135
+ }
136
+ if (!Array.isArray(sideEffect.mutating_actions)) {
137
+ errors.push(`Command manifest id "${entry.id}" mixed sideEffect.mutating_actions must be an array`);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
100
143
  function run() {
101
144
  const commands = flattenCommandManifest();
102
145
 
@@ -104,6 +147,7 @@ function run() {
104
147
  validateGroupReferences();
105
148
  validateRouterCoverage(commands);
106
149
  validateReadmeCoverage(commands);
150
+ validateSideEffects(commands);
107
151
 
108
152
  if (errors.length > 0) {
109
153
  console.error('Command manifest validation failed:');
@@ -10,7 +10,31 @@ const SKILLS_ROOT = path.join(ROOT, 'yida-skills');
10
10
  const SKILLS_DIR = path.join(SKILLS_ROOT, 'skills');
11
11
  const INDEX_FILE = path.join(SKILLS_ROOT, 'SKILL.md');
12
12
  const SKILLS_INDEX_FILE = path.join(SKILLS_ROOT, 'skills-index.json');
13
+ const GENERATED_SKILL_ROOT = path.join(ROOT, 'dist', 'skills', 'openyida');
13
14
  const MAX_RECOMMENDED_LINES = 500;
15
+ const SKILLS_INDEX_ENTRY_ALLOWED_FIELDS = new Set([
16
+ 'name',
17
+ 'path',
18
+ 'display_name',
19
+ 'description',
20
+ 'category',
21
+ 'tags',
22
+ 'aliases',
23
+ 'priority',
24
+ 'requires',
25
+ 'capabilities',
26
+ 'modes',
27
+ 'requires_login',
28
+ ]);
29
+ const SKILLS_INDEX_PROMPT_FIELDS = new Set([
30
+ 'prompt',
31
+ 'instructions',
32
+ 'rules',
33
+ 'workflow',
34
+ 'steps',
35
+ 'doneWhen',
36
+ 'optionalAfterDone',
37
+ ]);
14
38
 
15
39
  const errors = [];
16
40
  const warnings = [];
@@ -195,6 +219,7 @@ function validateSkillsIndex(skillDirNames) {
195
219
  const expectedPaths = new Set(skillDirNames.map(function(skillDirName) {
196
220
  return 'skills/' + skillDirName + '/SKILL.md';
197
221
  }));
222
+ const expectedNames = new Set(skillDirNames);
198
223
  const seenNames = new Set();
199
224
  const seenPaths = new Set();
200
225
 
@@ -207,12 +232,24 @@ function validateSkillsIndex(skillDirNames) {
207
232
  continue;
208
233
  }
209
234
 
235
+ for (const key of Object.keys(entry)) {
236
+ if (!SKILLS_INDEX_ENTRY_ALLOWED_FIELDS.has(key)) {
237
+ errors.push(entryLabel + ': unsupported field "' + key + '"; skills-index.json must stay a machine registry');
238
+ }
239
+ if (SKILLS_INDEX_PROMPT_FIELDS.has(key)) {
240
+ errors.push(entryLabel + ': prompt/workflow field "' + key + '" belongs in SKILL.md, not skills-index.json');
241
+ }
242
+ }
243
+
210
244
  if (!entry.name || typeof entry.name !== 'string') {
211
245
  errors.push(entryLabel + ': missing string field "name"');
212
246
  } else if (seenNames.has(entry.name)) {
213
247
  errors.push(entryLabel + ': duplicate name "' + entry.name + '"');
214
248
  } else {
215
249
  seenNames.add(entry.name);
250
+ if (!expectedNames.has(entry.name)) {
251
+ errors.push(entryLabel + ': orphan skill name "' + entry.name + '" has no matching skills/<name>/ directory');
252
+ }
216
253
  }
217
254
 
218
255
  if (!entry.path || typeof entry.path !== 'string') {
@@ -223,6 +260,9 @@ function validateSkillsIndex(skillDirNames) {
223
260
  errors.push(entryLabel + ': duplicate path "' + entry.path + '"');
224
261
  } else {
225
262
  seenPaths.add(entry.path);
263
+ if (!expectedPaths.has(entry.path)) {
264
+ errors.push(entryLabel + ': orphan path "' + entry.path + '" has no matching skills directory');
265
+ }
226
266
  }
227
267
 
228
268
  if (!/^skills\/[a-z0-9-]+\/SKILL\.md$/.test(entry.path)) {
@@ -253,6 +293,8 @@ function validateSkillsIndex(skillDirNames) {
253
293
  }
254
294
  if (!entry.description || typeof entry.description !== 'string') {
255
295
  errors.push(entryLabel + ': missing string field "description"');
296
+ } else if (entry.description.length > 280) {
297
+ errors.push(entryLabel + ': description must stay concise for machine search (<= 280 chars)');
256
298
  }
257
299
  if (!entry.category || typeof entry.category !== 'string') {
258
300
  errors.push(entryLabel + ': missing string field "category"');
@@ -267,6 +309,38 @@ function validateSkillsIndex(skillDirNames) {
267
309
  errors.push(toRelative(SKILLS_INDEX_FILE) + ': missing skill path "' + expectedPath + '"');
268
310
  }
269
311
  }
312
+ for (const expectedName of expectedNames) {
313
+ if (!seenNames.has(expectedName)) {
314
+ errors.push(toRelative(SKILLS_INDEX_FILE) + ': missing skill name "' + expectedName + '"');
315
+ }
316
+ }
317
+ }
318
+
319
+ function validateGeneratedSkillRoot() {
320
+ if (!fs.existsSync(GENERATED_SKILL_ROOT)) {
321
+ warnings.push(toRelative(GENERATED_SKILL_ROOT) + ': generated skill root not found; run npm run build:skills to validate package output');
322
+ return;
323
+ }
324
+
325
+ for (const fileName of ['SKILL.md', 'skills-index.json']) {
326
+ const filePath = path.join(GENERATED_SKILL_ROOT, fileName);
327
+ if (!fs.existsSync(filePath)) {
328
+ errors.push(toRelative(GENERATED_SKILL_ROOT) + ': generated skill root must contain ' + fileName);
329
+ }
330
+ }
331
+
332
+ const generatedIndexFile = path.join(GENERATED_SKILL_ROOT, 'skills-index.json');
333
+ if (fs.existsSync(generatedIndexFile)) {
334
+ try {
335
+ const sourceIndex = readJson(SKILLS_INDEX_FILE);
336
+ const generatedIndex = readJson(generatedIndexFile);
337
+ if (JSON.stringify(generatedIndex) !== JSON.stringify(sourceIndex)) {
338
+ errors.push(toRelative(generatedIndexFile) + ': must match source yida-skills/skills-index.json');
339
+ }
340
+ } catch (error) {
341
+ errors.push(toRelative(generatedIndexFile) + ': invalid JSON: ' + error.message);
342
+ }
343
+ }
270
344
  }
271
345
 
272
346
  function validateSkillLoadingInstructions(instructionFiles) {
@@ -390,7 +464,7 @@ function run() {
390
464
  validateIndexEntry(skillDirName, skillFile);
391
465
  }
392
466
 
393
- validateSkillsIndex(skillDirNamesWithSkillFile);
467
+ validateSkillsIndex(skillDirNames);
394
468
  }
395
469
 
396
470
  const markdownFiles = [];
@@ -412,6 +486,7 @@ function run() {
412
486
  }
413
487
  }
414
488
  validateSkillLoadingInstructions(instructionFiles);
489
+ validateGeneratedSkillRoot();
415
490
 
416
491
  if (warnings.length > 0) {
417
492
  console.warn('Skill validation warnings:');
@@ -16,22 +16,24 @@ description: >
16
16
 
17
17
  - 如果当前宿主提供 `use_skill` / `search_skills`:必须通过 `use_skill("<技能名>", "<本阶段目的>")` 加载主技能和子技能,禁止用 `Read` / `read_file` / `cat` 读取 `SKILL.md` 路径;`use_skill` 会稳定返回技能内容和可读取的辅助文件列表。
18
18
  - `skills-index.json` 仅供 yida-agent 或同构宿主做机器可读发现;不支持该索引的宿主忽略它,不要把它当作运行前置条件。
19
- - 如果当前宿主没有 `use_skill` / `search_skills`:按本文的技能路由表选定技能名,再按技能包相对路径逐个读取当前阶段唯一必要的 `SKILL.md`;禁止并发批量读取多个 `SKILL.md`;禁止预读未来阶段技能。
19
+ - 如果当前宿主没有 `use_skill` / `search_skills`:按本文的技能路由表选定技能名,按 `skills/<技能名>/SKILL.md` 定位当前阶段唯一必要的子技能文档;禁止并发批量读取多个 `SKILL.md`;禁止预读未来阶段技能。
20
20
  - `references/`、`scripts/`、`assets/` 等辅助文件只能在已加载对应技能后按需读取。
21
21
 
22
22
  ---
23
23
 
24
- ## 第一步:环境与登录态检测(必做必读,先于一切操作)
24
+ ## 第一步:只读预检(先于真实资源操作)
25
25
 
26
26
  > ⚡ **前置门槛**:确认 openyida 已安装、Node/npm 依赖达标、登录态就绪。**未通过只读验证前,禁止创建应用/页面/表单或发布等任何真实资源操作。**
27
27
 
28
- **怎么做**:跑 `openyida env --json`(能跑 = 已安装,输出含 AI 工具 / project / 登录态)和 `openyida login --check-only --json`(只读登录态),再据结果对症处理:
28
+ **怎么做**:优先跑一次 `openyida agent-capabilities --json`。该命令一次返回 version、cwd、AI 工具环境、推荐工作目录、登录态摘要、commands sideEffects,避免反复 `which openyida`、`openyida --version`、`openyida --help`、`openyida env`、`login --check-only`。
29
+
30
+ 若当前 OpenYida 版本还没有 `agent-capabilities`,退回跑 `openyida env --json` 和 `openyida login --check-only --json`。旧版本地 agent 不需要认识 `skills-index.json`,也不需要支持 `agent-capabilities` 才能继续执行。
29
31
 
30
32
  | 检测结果 | 处理 |
31
33
  |---------|------|
32
34
  | 命令跑不了(`command not found`) | openyida 未安装 → `npm install -g openyida` |
33
35
  | Node/npm 版本不达标 | 先升级 Node(≥16)再装/升级 openyida |
34
- | `login.loggedIn` false | 未登录 → `openyida login`(指定入口带 URL 或 flag) |
36
+ | `login.status` 不是 `ok` 且 `login.can_auto_use` 不是 true | 未登录 → `openyida login`(指定入口带 URL 或 flag) |
35
37
  | `active.projectRootExists` 为 false | 无工作目录 → `openyida copy` 初始化 |
36
38
 
37
39
  **👉 完整命令解读、悟空降级、Codex handoff 等特殊分支 → [references/setup-and-env.md](references/setup-and-env.md)(必读)。**
@@ -44,7 +46,7 @@ description: >
44
46
 
45
47
  | 用户诉求信号 | 判定 | 走哪条路线 |
46
48
  |------------|------|-----------|
47
- | 创建/搭建/做一个 + 应用/系统/管理系统;或明确表达从零开始 | **全量搭建** | [完整开发流程](#完整开发流程全量搭建),从 Step 1 顺序执行 |
49
+ | 创建/搭建/做一个 + 应用/系统/管理系统;或明确表达从零开始 | **全量搭建** | 加载子技能 `yida-app`,由它执行完整应用 workflow |
48
50
  | 对已有应用/表单/页面的单点操作(加字段、查改数据、配公式、建报表、改权限、发布、美化…) | **单一 / 增量任务** | 到 [技能路由](#技能路由单一--增量任务) 选定 **1 个**,加载对应子技能执行,不回退流程 |
49
51
 
50
52
  ---
@@ -52,39 +54,12 @@ description: >
52
54
  ## 完整开发流程(全量搭建)
53
55
 
54
56
  > 📌 仅当第二步判定为「全量搭建」时进入;单一/增量任务请跳「技能路由」。
55
- > 在支持 `use_skill` 的宿主中,先加载 `yida-app` 流程编排技能;进入每个阶段前,再加载该阶段唯一需要的子技能,不要预读未来阶段技能。
56
-
57
- ```
58
- [Step 1] 创建应用 → openyida create-app 获得 appType
59
-
60
- [Step 2] 需求分析 写入 prd/<项目名>.md
61
- ↓ (必须含 MVP 边界、角色权限、核心旅程、状态机、数据约束、验收标准)
62
-
63
- [Step 3](按需)创建/更新表单 → openyida create-form → 获得 formUuid + fieldId(表单)
64
-
65
- [Step 4] 创建自定义页面 → openyida create-page → 获得 formUuid(看板用 --mode dashboard)
66
-
67
- [Step 5] 编写自定义页面代码 → 先按「自定义页面选路」定链路(**默认 Code Canvas yida-canvas-custom-page**,含开放 API 读数据页;仅强依赖原生实例数据桥的页回退 native yida-custom-page)
68
- ↓ (首次生成面向用户的页必做:先用 yida-page-uiux 产出「视觉方向决策块」,避免统一灰白圆角的 AI 味模板脸,再交所选链路落地)
69
-
70
- [Step 6] 发布页面 → openyida publish
71
-
72
- [Step 7](含看板/多页面时必做)整理导航顺序 → openyida nav-group order
73
- ↓ (总览/驾驶舱看板作为门面靠前,数据录入/明细表单在后)
74
-
75
- [Step 8](有表单时默认执行)灌入示例数据 → openyida data create form
76
- ↓ (2-3 条覆盖关键维度的记录,让看板首屏有真实数据;DateField 用 13 位毫秒时间戳,灌后 query 抽查非空)
77
-
78
- [Step 9](按需)配置公开访问 → openyida verify-short-url / save-share-config
79
-
80
- [Step 10] 输出访问链接,用系统浏览器打开
81
- ```
82
-
83
- > **Step 5 先定视觉方向(首次生成面向用户页必做)**:写 JSX 前调用 `use_skill("yida-page-uiux", "确定自定义页面视觉方向并产出决策块")` 锁定视觉方向(页面类型判定 → 意图解码 → 差异化决策 → 去 AI 味自检),产出「视觉方向决策块」,再交所选链路(默认 `yida-canvas-custom-page`,回退 `yida-custom-page`)按 `design-system.md` token 落地。跳过此步会直接套用统一灰白圆角模板,生成有 AI 味的平庸页面。
84
- >
85
- > **Step 7 导航整理(含看板/多页面时必做)**:首次生成完整应用后,必须基于业务信息架构重排导航,不能保留创建时的默认顺序。默认原则:面向决策者的**总览/驾驶舱看板作为应用门面靠前**,数据录入/明细表单在其后;同级多个专题看板按业务优先级排,不要把所有页面无脑堆最前。进入本阶段前调用 `use_skill("yida-nav-group", "整理应用导航顺序")`。
86
- >
87
- > **Step 8 灌入示例数据(有表单时默认执行)**:新建应用的表单默认无数据,看板会空。导航整理完成后,默认向核心表单灌入 **2-3 条**覆盖关键维度(如不同活动/渠道/日期)的示例记录,让看板首屏可展示真实聚合效果。`DateField`/`CascadeDateField` 必须用 13 位毫秒时间戳;灌后执行 `openyida data query` 抽查至少 1 条,确认字段值非空。进入本阶段前调用 `use_skill("yida-data-management", "写入和抽查示例数据")`。
57
+ > 加载子技能 `yida-app`,由它负责完整应用 workflow、阶段子技能加载、关键 ID 流转、PRD 与 schema cache 约束。
58
+ > 用户说“按默认方案 / 不要追问 / 直接创建 / 尽快搭建”时,`yida-app` 选择 `fast_build`:创建应用、必要表单、主页面、发布并输出链接。
59
+
60
+ **doneWhen**:`yida-app` 发布主页面成功并输出可访问 URL。到这里默认完成;不要发布后继续 TaskCreate、重复读技能或继续规划。
61
+
62
+ **optionalAfterDone**:导航整理、示例数据、公开访问、截图验证、深度视觉方向、报表/大屏,只在用户明确要求或 `yida-app` 模式为 `full_demo` / `deep_design` 时执行。
88
63
 
89
64
  ---
90
65
 
@@ -107,7 +82,7 @@ description: >
107
82
 
108
83
  | 分组 | 加载目标 | 何时选择(关键区别已内联) |
109
84
  |------|------|--------------------------|
110
- | **应用与登录** | 加载子技能 `yida-app` | 从零搭建整个应用(多步骤全流程编排) |
85
+ | **应用与登录** | 加载子技能 `yida-app` | 从零搭建整个应用;默认 `fast_build`,发布主页面拿到 URL 即完成 |
111
86
  | | 加载子技能 `yida-create-app` | 只需创建应用、拿 appType |
112
87
  | | 加载子技能 `yida-login` | 手动触发登录(通常自动触发) |
113
88
  | | 加载子技能 `yida-logout` | 切换账号或组织 |
@@ -166,7 +141,7 @@ description: >
166
141
 
167
142
  1. **技能加载唯一入口**:执行任何子技能前,支持 `use_skill` 的宿主必须调用 `use_skill("<技能名>", "<本阶段目的>")` 加载对应技能;不要用 `Read` / `read_file` / `cat` 读取 SKILL.md 路径,不凭记忆猜参数格式。
168
143
  2. **corpId 一致性检查**:创建页面前对比 prd 与 `.cache/cookies.json` 的 corpId,不一致必须询问用户(重新登录 or 当前组织新建)。
169
- 3. **发布前本地校验**:自定义页面发布前跑 `openyida check-page` + `openyida compile`;JSON 配置写盘后先解析校验,再调用平台命令。
144
+ 3. **发布前本地校验**:native `.oyd.jsx` / `.jsx` 页面发布前跑 `openyida check-page` + `openyida compile`;Code Canvas `.canvas.jsx` 不跑这两个 native 检查,改由 `openyida publish` 的 Canvas 编译阶段或 `compileCanvasLocal` 快检校验;JSON 配置写盘后先解析校验,再调用平台命令。
170
145
  4. **命令输入文件禁止 shell 写入**:当 OpenYida 命令需要 JSON/YAML/CSV/config/script 文件参数时,先使用当前 agent 运行时提供的结构化文件写入工具(如 create_file / Write / file edit tool)创建文件,再把路径传给命令;禁止用 shell heredoc、`cat`/`echo`/`printf`/`tee` 加输出重定向,或把命令 stdout 重定向成业务文件。
171
146
 
172
147
  ### 重要规则(IMPORTANT,影响质量/性能/可维护性)
@@ -182,6 +157,7 @@ description: >
182
157
  9. **报表美化先问方案**:用户说"优化/美化报表"时先问选原生报表(`yida-report`)还是 ECharts(`yida-chart`)。
183
158
  10. **按 schema 证据选技能**:先看 `formType`、组件树、`dataSource.online`;`receipt/process/report` 分别落到表单/流程/报表技能。
184
159
  11. **官方示例范式优先**:蒸馏官方示例时先理解脱敏 schema 承载方式,不凭截图/标题/视觉判断。
160
+ 12. **默认完成即停止**:完整应用默认以发布成功并输出 URL 为 doneWhen;示例数据、导航、截图、TaskCreate 和深度设计都是 optionalAfterDone。
185
161
 
186
162
  > 📖 每条规则的完整说明、PRD 质量门槛、临时文件路径规范、报表美化话术 → [references/development-rules.md](references/development-rules.md)
187
163