@zeyue0329/xiaoma-cli 1.20.0 → 1.22.0

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.
Files changed (130) hide show
  1. package/package.json +1 -1
  2. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/README.md +74 -0
  3. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/SKILL.md +58 -0
  4. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/examples/example-clarification.md +61 -0
  5. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/references/checklist.md +99 -0
  6. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/references/output-template.md +73 -0
  7. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/references/scoring.md +73 -0
  8. package/src/xmc-skills/2-plan-workflows/xiaoma-prd-clarify/scripts/export_to_excel.py +156 -0
  9. package/src/xmc-skills/module-help.csv +1 -0
  10. package/src/zqyl-skills/backend/components/xiaoma-zqyl-bar-chart-backend/SKILL.md +142 -0
  11. package/src/zqyl-skills/backend/components/xiaoma-zqyl-button-backend/SKILL.md +108 -0
  12. package/src/zqyl-skills/backend/components/xiaoma-zqyl-collapse-backend/SKILL.md +142 -0
  13. package/src/zqyl-skills/backend/components/xiaoma-zqyl-container-foot-backend/SKILL.md +192 -0
  14. package/src/zqyl-skills/backend/components/xiaoma-zqyl-data-census-backend/SKILL.md +104 -0
  15. package/src/zqyl-skills/backend/components/xiaoma-zqyl-dialog-backend/SKILL.md +205 -0
  16. package/src/zqyl-skills/backend/components/xiaoma-zqyl-drawer-backend/SKILL.md +100 -0
  17. package/src/zqyl-skills/backend/components/xiaoma-zqyl-empty-page-backend/SKILL.md +131 -0
  18. package/src/zqyl-skills/backend/components/xiaoma-zqyl-file-upload-backend/SKILL.md +291 -0
  19. package/src/zqyl-skills/backend/components/xiaoma-zqyl-flow-chart-backend/SKILL.md +118 -0
  20. package/src/zqyl-skills/backend/components/xiaoma-zqyl-form-editor-backend/SKILL.md +315 -0
  21. package/src/zqyl-skills/backend/components/xiaoma-zqyl-form-view-backend/SKILL.md +165 -0
  22. package/src/zqyl-skills/backend/components/xiaoma-zqyl-funnel-chart-backend/SKILL.md +102 -0
  23. package/src/zqyl-skills/backend/components/xiaoma-zqyl-gauge-chart-backend/SKILL.md +103 -0
  24. package/src/zqyl-skills/backend/components/xiaoma-zqyl-heatmap-chart-backend/SKILL.md +125 -0
  25. package/src/zqyl-skills/backend/components/xiaoma-zqyl-line-chart-backend/SKILL.md +136 -0
  26. package/src/zqyl-skills/backend/components/xiaoma-zqyl-map-chart-backend/SKILL.md +130 -0
  27. package/src/zqyl-skills/backend/components/xiaoma-zqyl-message-feedback-backend/SKILL.md +63 -0
  28. package/src/zqyl-skills/backend/components/xiaoma-zqyl-module-backend/SKILL.md +180 -0
  29. package/src/zqyl-skills/backend/components/xiaoma-zqyl-pie-chart-backend/SKILL.md +132 -0
  30. package/src/zqyl-skills/backend/components/xiaoma-zqyl-popover-backend/SKILL.md +61 -0
  31. package/src/zqyl-skills/backend/components/xiaoma-zqyl-preview-backend/SKILL.md +117 -0
  32. package/src/zqyl-skills/backend/components/xiaoma-zqyl-search-form-backend/SKILL.md +239 -0
  33. package/src/zqyl-skills/backend/components/xiaoma-zqyl-select-tree-backend/SKILL.md +161 -0
  34. package/src/zqyl-skills/backend/components/xiaoma-zqyl-table-simple-backend/SKILL.md +341 -0
  35. package/src/zqyl-skills/backend/components/xiaoma-zqyl-tips-backend/SKILL.md +257 -0
  36. package/src/zqyl-skills/backend/components/xiaoma-zqyl-title-backend/SKILL.md +145 -0
  37. package/src/zqyl-skills/backend/xiaoma-zqyl-feedback-page-backend/SKILL.md +30 -0
  38. package/src/zqyl-skills/backend/xiaoma-zqyl-feedback-page-backend/pages/feedbackPage.md +124 -0
  39. package/src/zqyl-skills/backend/xiaoma-zqyl-form-detail-page-backend/SKILL.md +39 -0
  40. package/src/zqyl-skills/backend/xiaoma-zqyl-form-detail-page-backend/pages/formDetailPage.md +354 -0
  41. package/src/zqyl-skills/backend/xiaoma-zqyl-import-dialog-page-backend/SKILL.md +27 -0
  42. package/src/zqyl-skills/backend/xiaoma-zqyl-import-dialog-page-backend/pages/importDialogPage.md +101 -0
  43. package/src/zqyl-skills/backend/xiaoma-zqyl-list-page-backend/SKILL.md +31 -0
  44. package/src/zqyl-skills/backend/xiaoma-zqyl-list-page-backend/pages/listPage.md +306 -0
  45. package/src/zqyl-skills/front/components/xiaoma-zqyl-bar-chart-front/SKILL.md +142 -0
  46. package/src/zqyl-skills/front/components/xiaoma-zqyl-button-front/SKILL.md +108 -0
  47. package/src/zqyl-skills/front/components/xiaoma-zqyl-collapse-front/SKILL.md +142 -0
  48. package/src/zqyl-skills/front/components/xiaoma-zqyl-container-foot-front/SKILL.md +192 -0
  49. package/src/zqyl-skills/front/components/xiaoma-zqyl-data-census-front/SKILL.md +104 -0
  50. package/src/zqyl-skills/front/components/xiaoma-zqyl-dialog-front/SKILL.md +205 -0
  51. package/src/zqyl-skills/front/components/xiaoma-zqyl-drawer-front/SKILL.md +100 -0
  52. package/src/zqyl-skills/front/components/xiaoma-zqyl-empty-page-front/SKILL.md +131 -0
  53. package/src/zqyl-skills/front/components/xiaoma-zqyl-file-upload-front/SKILL.md +291 -0
  54. package/src/zqyl-skills/front/components/xiaoma-zqyl-flow-chart-front/SKILL.md +118 -0
  55. package/src/zqyl-skills/front/components/xiaoma-zqyl-form-editor-front/SKILL.md +315 -0
  56. package/src/zqyl-skills/front/components/xiaoma-zqyl-form-view-front/SKILL.md +165 -0
  57. package/src/zqyl-skills/front/components/xiaoma-zqyl-funnel-chart-front/SKILL.md +102 -0
  58. package/src/zqyl-skills/front/components/xiaoma-zqyl-gauge-chart-front/SKILL.md +103 -0
  59. package/src/zqyl-skills/front/components/xiaoma-zqyl-heatmap-chart-front/SKILL.md +125 -0
  60. package/src/zqyl-skills/front/components/xiaoma-zqyl-line-chart-front/SKILL.md +136 -0
  61. package/src/zqyl-skills/front/components/xiaoma-zqyl-map-chart-front/SKILL.md +130 -0
  62. package/src/zqyl-skills/front/components/xiaoma-zqyl-message-feedback-front/SKILL.md +63 -0
  63. package/src/zqyl-skills/front/components/xiaoma-zqyl-module-front/SKILL.md +180 -0
  64. package/src/zqyl-skills/front/components/xiaoma-zqyl-pie-chart-front/SKILL.md +132 -0
  65. package/src/zqyl-skills/front/components/xiaoma-zqyl-popover-front/SKILL.md +61 -0
  66. package/src/zqyl-skills/front/components/xiaoma-zqyl-preview-front/SKILL.md +117 -0
  67. package/src/zqyl-skills/front/components/xiaoma-zqyl-search-form-front/SKILL.md +239 -0
  68. package/src/zqyl-skills/front/components/xiaoma-zqyl-select-tree-front/SKILL.md +161 -0
  69. package/src/zqyl-skills/front/components/xiaoma-zqyl-table-simple-front/SKILL.md +341 -0
  70. package/src/zqyl-skills/front/components/xiaoma-zqyl-tips-front/SKILL.md +257 -0
  71. package/src/zqyl-skills/front/components/xiaoma-zqyl-title-front/SKILL.md +145 -0
  72. package/src/zqyl-skills/front/xiaoma-zqyl-feedback-page-front/SKILL.md +30 -0
  73. package/src/zqyl-skills/front/xiaoma-zqyl-feedback-page-front/pages/feedbackPage.md +124 -0
  74. package/src/zqyl-skills/front/xiaoma-zqyl-form-detail-page-front/SKILL.md +39 -0
  75. package/src/zqyl-skills/front/xiaoma-zqyl-form-detail-page-front/pages/formDetailPage.md +351 -0
  76. package/src/zqyl-skills/front/xiaoma-zqyl-import-dialog-page-front/SKILL.md +27 -0
  77. package/src/zqyl-skills/front/xiaoma-zqyl-import-dialog-page-front/pages/importDialogPage.md +101 -0
  78. package/src/zqyl-skills/front/xiaoma-zqyl-list-page-front/SKILL.md +31 -0
  79. package/src/zqyl-skills/front/xiaoma-zqyl-list-page-front/pages/listPage.md +380 -0
  80. package/src/zqyl-skills/mid/components/xiaoma-zqyl-bar-chart-mid/SKILL.md +142 -0
  81. package/src/zqyl-skills/mid/components/xiaoma-zqyl-button-mid/SKILL.md +108 -0
  82. package/src/zqyl-skills/mid/components/xiaoma-zqyl-collapse-mid/SKILL.md +142 -0
  83. package/src/zqyl-skills/mid/components/xiaoma-zqyl-container-foot-mid/SKILL.md +192 -0
  84. package/src/zqyl-skills/mid/components/xiaoma-zqyl-data-census-mid/SKILL.md +104 -0
  85. package/src/zqyl-skills/mid/components/xiaoma-zqyl-dialog-mid/SKILL.md +205 -0
  86. package/src/zqyl-skills/mid/components/xiaoma-zqyl-drawer-mid/SKILL.md +100 -0
  87. package/src/zqyl-skills/mid/components/xiaoma-zqyl-empty-page-mid/SKILL.md +131 -0
  88. package/src/zqyl-skills/mid/components/xiaoma-zqyl-file-upload-mid/SKILL.md +291 -0
  89. package/src/zqyl-skills/mid/components/xiaoma-zqyl-flow-chart-mid/SKILL.md +118 -0
  90. package/src/zqyl-skills/mid/components/xiaoma-zqyl-form-editor-mid/SKILL.md +315 -0
  91. package/src/zqyl-skills/mid/components/xiaoma-zqyl-form-view-mid/SKILL.md +165 -0
  92. package/src/zqyl-skills/mid/components/xiaoma-zqyl-funnel-chart-mid/SKILL.md +102 -0
  93. package/src/zqyl-skills/mid/components/xiaoma-zqyl-gauge-chart-mid/SKILL.md +103 -0
  94. package/src/zqyl-skills/mid/components/xiaoma-zqyl-heatmap-chart-mid/SKILL.md +125 -0
  95. package/src/zqyl-skills/mid/components/xiaoma-zqyl-line-chart-mid/SKILL.md +136 -0
  96. package/src/zqyl-skills/mid/components/xiaoma-zqyl-manager-title-mid/SKILL.md +132 -0
  97. package/src/zqyl-skills/mid/components/xiaoma-zqyl-map-chart-mid/SKILL.md +130 -0
  98. package/src/zqyl-skills/mid/components/xiaoma-zqyl-message-feedback-mid/SKILL.md +63 -0
  99. package/src/zqyl-skills/mid/components/xiaoma-zqyl-module-mid/SKILL.md +180 -0
  100. package/src/zqyl-skills/mid/components/xiaoma-zqyl-mps-table-mid/SKILL.md +179 -0
  101. package/src/zqyl-skills/mid/components/xiaoma-zqyl-pie-chart-mid/SKILL.md +132 -0
  102. package/src/zqyl-skills/mid/components/xiaoma-zqyl-popover-mid/SKILL.md +61 -0
  103. package/src/zqyl-skills/mid/components/xiaoma-zqyl-preview-mid/SKILL.md +117 -0
  104. package/src/zqyl-skills/mid/components/xiaoma-zqyl-search-form-mid/SKILL.md +239 -0
  105. package/src/zqyl-skills/mid/components/xiaoma-zqyl-select-tree-mid/SKILL.md +161 -0
  106. package/src/zqyl-skills/mid/components/xiaoma-zqyl-table-simple-mid/SKILL.md +341 -0
  107. package/src/zqyl-skills/mid/components/xiaoma-zqyl-tips-mid/SKILL.md +257 -0
  108. package/src/zqyl-skills/mid/components/xiaoma-zqyl-title-mid/SKILL.md +145 -0
  109. package/src/zqyl-skills/mid/xiaoma-zqyl-feedback-page-mid/SKILL.md +30 -0
  110. package/src/zqyl-skills/mid/xiaoma-zqyl-feedback-page-mid/pages/feedbackPage.md +125 -0
  111. package/src/zqyl-skills/mid/xiaoma-zqyl-form-detail-page-mid/SKILL.md +39 -0
  112. package/src/zqyl-skills/mid/xiaoma-zqyl-form-detail-page-mid/pages/formDetailPage.md +357 -0
  113. package/src/zqyl-skills/mid/xiaoma-zqyl-import-dialog-page-mid/SKILL.md +27 -0
  114. package/src/zqyl-skills/mid/xiaoma-zqyl-import-dialog-page-mid/pages/importDialogPage.md +102 -0
  115. package/src/zqyl-skills/mid/xiaoma-zqyl-list-page-mid/SKILL.md +30 -0
  116. package/src/zqyl-skills/mid/xiaoma-zqyl-list-page-mid/pages/listPage.md +389 -0
  117. package/src/zqyl-skills/module-help.csv +15 -0
  118. package/src/zqyl-skills/module.yaml +38 -0
  119. package/src/zqyl-skills/xiaoma-zqyl-ui-spec/SKILL.md +478 -0
  120. package/src/zqyl-skills/xiaoma-zqyl-ui-spec/VERSION.md +30 -0
  121. package/src/zqyl-skills/xiaoma-zqyl-ui-spec/components-index.md +55 -0
  122. package/src/zqyl-skills/xiaoma-zqyl-ui-spec/design-tokens.md +167 -0
  123. package/src/zqyl-skills/xiaoma-zqyl-ui-spec/reference.md +385 -0
  124. package/tools/installer/core/manifest.js +4 -3
  125. package/tools/installer/list-options.js +2 -2
  126. package/tools/installer/modules/official-modules.js +15 -47
  127. package/tools/installer/modules/version-resolver.js +2 -2
  128. package/tools/installer/project-root.js +12 -9
  129. package/tools/installer/ui.js +2 -1
  130. package/tools/validate-zqyl-pack.js +171 -0
@@ -2,7 +2,7 @@ const path = require('node:path');
2
2
  const semver = require('semver');
3
3
  const yaml = require('yaml');
4
4
  const fs = require('../fs-native');
5
- const { getExternalModuleCachePath, getModulePath, resolveInstalledModuleYaml } = require('../project-root');
5
+ const { BUILTIN_MODULE_CODES, getExternalModuleCachePath, getModulePath, resolveInstalledModuleYaml } = require('../project-root');
6
6
 
7
7
  const DEFAULT_PARENT_DEPTH = 8;
8
8
 
@@ -141,7 +141,7 @@ async function buildSearchRoots(moduleName, moduleSourcePath) {
141
141
 
142
142
  await addRoot(moduleSourcePath);
143
143
 
144
- if (moduleName === 'core' || moduleName === 'xmc' || moduleName === 'xpm') {
144
+ if (BUILTIN_MODULE_CODES.includes(moduleName)) {
145
145
  await addRoot(getModulePath(moduleName));
146
146
  } else {
147
147
  await addRoot(getExternalModuleCachePath(moduleName));
@@ -59,20 +59,22 @@ function getSourcePath(...segments) {
59
59
  return path.join(getProjectRoot(), 'src', ...segments);
60
60
  }
61
61
 
62
+ /**
63
+ * Built-in module codes. Each ships inside this repo as `src/<code>-skills/`
64
+ * and is versioned with the CLI itself; every other code resolves to an
65
+ * external/community/custom source. Single source of truth — the installer,
66
+ * manifest writer, version resolver, and `--list-options` all key off this.
67
+ */
68
+ const BUILTIN_MODULE_CODES = ['core', 'xmc', 'xpm', 'zqyl'];
69
+
62
70
  /**
63
71
  * Get path to a module's directory
64
- * core, xmc, and xpm are built-in modules directly under src/ (as *-skills dirs)
72
+ * Built-in modules live directly under src/ (as *-skills dirs)
65
73
  * All other modules are stored remote
66
74
  */
67
75
  function getModulePath(moduleName, ...segments) {
68
- if (moduleName === 'core') {
69
- return getSourcePath('core-skills', ...segments);
70
- }
71
- if (moduleName === 'xmc') {
72
- return getSourcePath('xmc-skills', ...segments);
73
- }
74
- if (moduleName === 'xpm') {
75
- return getSourcePath('xpm-skills', ...segments);
76
+ if (BUILTIN_MODULE_CODES.includes(moduleName)) {
77
+ return getSourcePath(`${moduleName}-skills`, ...segments);
76
78
  }
77
79
  return getSourcePath('modules', moduleName, ...segments);
78
80
  }
@@ -221,6 +223,7 @@ async function resolveInstalledModuleYaml(moduleName) {
221
223
  }
222
224
 
223
225
  module.exports = {
226
+ BUILTIN_MODULE_CODES,
224
227
  getProjectRoot,
225
228
  getSourcePath,
226
229
  getModulePath,
@@ -7,6 +7,7 @@ const { CLIUtils } = require('./cli-utils');
7
7
  const { ExternalModuleManager } = require('./modules/external-manager');
8
8
  const { resolveModuleVersion } = require('./modules/version-resolver');
9
9
  const { Manifest } = require('./core/manifest');
10
+ const { BUILTIN_MODULE_CODES } = require('./project-root');
10
11
  const {
11
12
  parseChannelOptions,
12
13
  buildPlan,
@@ -884,7 +885,7 @@ class UI {
884
885
  // they can be managed via --custom-source, uninstall, or a dedicated installer.
885
886
  const externalManager = new ExternalModuleManager();
886
887
  const registryModules = await externalManager.listAvailable();
887
- const officialRegistryCodes = new Set(['core', 'xmc', 'xpm', ...registryModules.map((m) => m.code)]);
888
+ const officialRegistryCodes = new Set([...BUILTIN_MODULE_CODES, ...registryModules.map((m) => m.code)]);
888
889
  const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
889
890
 
890
891
  // Phase 2: Custom URL modules
@@ -0,0 +1,171 @@
1
+ /**
2
+ * ZQYL Pack Consistency Validator
3
+ *
4
+ * The zqyl module is the only pack whose skills ship in three near-identical
5
+ * platform variants (backend / mid / front), and whose descriptions carry
6
+ * import-authored text that asserts facts about OTHER files. A multi-agent UAT
7
+ * showed both failure modes are silent: a wrong `platformType` produces the wrong
8
+ * theme with no console error, and a skill reference that does not resolve just
9
+ * makes the agent guess. Neither is caught by validate-skills.js (frontmatter
10
+ * shape) or validate-file-refs.js (path resolution) — skill NAMES are not paths.
11
+ *
12
+ * What it checks:
13
+ * - ZQYL-01: every `xiaoma-zqyl-*` skill name asserted anywhere in the pack resolves
14
+ * to an installed skill directory (placeholder forms are exempt)
15
+ * - ZQYL-02: the `platformType` quoted in a skill's description equals the value in
16
+ * that same skill's body (`- platformType:`x``) — catches invented values
17
+ * - ZQYL-03: frontmatter `name` matches the directory (the manifest generator skips
18
+ * mismatches silently, so the skill would vanish at install time)
19
+ * - ZQYL-04: no unresolved `{平台}` placeholder inside a platform-pinned file
20
+ * - ZQYL-05: components-index.md's availability matrix matches the directories on disk
21
+ *
22
+ * Usage:
23
+ * node tools/validate-zqyl-pack.js # report, exit 0
24
+ * node tools/validate-zqyl-pack.js --strict # exit 1 on any finding
25
+ */
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const yaml = require('yaml');
30
+
31
+ const PROJECT_ROOT = path.resolve(__dirname, '..');
32
+ const PACK_DIR = path.join(PROJECT_ROOT, 'src', 'zqyl-skills');
33
+ const INDEX_FILE = path.join(PACK_DIR, 'xiaoma-zqyl-ui-spec', 'components-index.md');
34
+ const STRICT = process.argv.includes('--strict');
35
+
36
+ const PLATFORMS = ['backend', 'mid', 'front'];
37
+ const findings = [];
38
+
39
+ function report(rule, file, detail) {
40
+ findings.push({ rule, file: path.relative(PROJECT_ROOT, file), detail });
41
+ }
42
+
43
+ function walk(dir, onFile) {
44
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
45
+ const full = path.join(dir, entry.name);
46
+ if (entry.isDirectory()) walk(full, onFile);
47
+ else onFile(full);
48
+ }
49
+ }
50
+
51
+ if (!fs.existsSync(PACK_DIR)) {
52
+ console.log('src/zqyl-skills not present — nothing to validate.');
53
+ process.exit(0);
54
+ }
55
+
56
+ // --- collect skills -------------------------------------------------------
57
+
58
+ const skills = new Map(); // name -> { dir, fm, body }
59
+ walk(PACK_DIR, (file) => {
60
+ if (path.basename(file) !== 'SKILL.md') return;
61
+ const raw = fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n');
62
+ const match = raw.match(/^---\n([\S\s]*?)\n---\n/);
63
+ if (!match) {
64
+ report('ZQYL-03', file, 'SKILL.md has no frontmatter');
65
+ return;
66
+ }
67
+ let fm;
68
+ try {
69
+ fm = yaml.parse(match[1]);
70
+ } catch (error) {
71
+ report('ZQYL-03', file, `unparseable frontmatter: ${error.message}`);
72
+ return;
73
+ }
74
+ const dirName = path.basename(path.dirname(file));
75
+ if (!fm || fm.name !== dirName) {
76
+ report('ZQYL-03', file, `frontmatter name "${fm && fm.name}" != directory "${dirName}" — the installer would skip this skill silently`);
77
+ return;
78
+ }
79
+ skills.set(fm.name, { dir: path.dirname(file), fm, body: raw.slice(match[0].length) });
80
+ });
81
+
82
+ // --- ZQYL-01: asserted skill names resolve --------------------------------
83
+
84
+ walk(PACK_DIR, (file) => {
85
+ if (!file.endsWith('.md')) return;
86
+ const text = fs.readFileSync(file, 'utf8');
87
+ for (const [lineNo, line] of text.split('\n').entries()) {
88
+ for (const m of line.matchAll(/xiaoma-zqyl-[a-z0-9-]+/g)) {
89
+ const name = m[0];
90
+ const after = line.slice(m.index + name.length);
91
+ // `xiaoma-zqyl-{组件 key}-{平台}` and `xiaoma-zqyl-{line,bar}-chart-…` are notation, not claims
92
+ if (after.startsWith('{') || after.startsWith(',')) continue;
93
+ if (skills.has(name)) continue;
94
+ report('ZQYL-01', file, `line ${lineNo + 1}: references "${name}", which is not a skill in this pack`);
95
+ }
96
+ }
97
+ });
98
+
99
+ // --- ZQYL-02: description platformType matches the skill's own body -------
100
+
101
+ for (const [name, { dir, fm, body }] of skills) {
102
+ const claimed = fm.description && fm.description.match(/platformType\s*`([A-Za-z]+)`/);
103
+ if (!claimed) continue;
104
+ const own = body.match(/^-\s*platformType[::]\s*`([A-Za-z]+)`/m);
105
+ if (own && own[1] !== claimed[1]) {
106
+ report(
107
+ 'ZQYL-02',
108
+ path.join(dir, 'SKILL.md'),
109
+ `description claims platformType \`${claimed[1]}\` but the body declares \`${own[1]}\` (${name})`,
110
+ );
111
+ }
112
+ }
113
+
114
+ // --- ZQYL-04: no {平台} left in a platform-pinned file ---------------------
115
+
116
+ for (const [name, { dir }] of skills) {
117
+ if (!PLATFORMS.some((p) => name.endsWith(`-${p}`))) continue;
118
+ walk(dir, (file) => {
119
+ if (!file.endsWith('.md')) return;
120
+ const text = fs.readFileSync(file, 'utf8');
121
+ for (const [lineNo, line] of text.split('\n').entries()) {
122
+ // only flag placeholders attached to a skill reference; prose notation elsewhere is fine
123
+ if (/xiaoma-zqyl-[a-z0-9-]*-\{平台\}/.test(line)) {
124
+ report('ZQYL-04', file, `line ${lineNo + 1}: unresolved {平台} in "${name}", a skill already pinned to one platform`);
125
+ }
126
+ }
127
+ });
128
+ }
129
+
130
+ // --- ZQYL-05: components-index matrix matches disk ------------------------
131
+
132
+ if (fs.existsSync(INDEX_FILE)) {
133
+ const rows = fs
134
+ .readFileSync(INDEX_FILE, 'utf8')
135
+ .split('\n')
136
+ .filter((l) => /^\|\s*`[a-z-]+`\s*\|/.test(l));
137
+ for (const row of rows) {
138
+ const cells = row.split('|').map((c) => c.trim());
139
+ const key = cells[1].replaceAll('`', '');
140
+ const [b, m, f] = [cells[3], cells[4], cells[5]];
141
+ for (const [platform, cell] of [
142
+ ['backend', b],
143
+ ['mid', m],
144
+ ['front', f],
145
+ ]) {
146
+ const claimsAvailable = cell === '✅';
147
+ const exists = skills.has(`xiaoma-zqyl-${key}-${platform}`);
148
+ if (claimsAvailable !== exists) {
149
+ report(
150
+ 'ZQYL-05',
151
+ INDEX_FILE,
152
+ `"${key}" is marked ${claimsAvailable ? 'available' : 'unavailable'} on ${platform}, but xiaoma-zqyl-${key}-${platform} ${exists ? 'exists' : 'does not exist'}`,
153
+ );
154
+ }
155
+ }
156
+ }
157
+ } else {
158
+ report('ZQYL-05', INDEX_FILE, 'components-index.md is missing — the 24 chart skills lose their only entry point');
159
+ }
160
+
161
+ // --- output ---------------------------------------------------------------
162
+
163
+ console.log(`\nZQYL pack validation: ${skills.size} skills in ${path.relative(PROJECT_ROOT, PACK_DIR)}\n`);
164
+ if (findings.length === 0) {
165
+ console.log(' All checks passed.\n');
166
+ } else {
167
+ for (const f of findings) console.log(` [${f.rule}] ${f.file}\n ${f.detail}`);
168
+ console.log(`\n ${findings.length} finding(s).\n`);
169
+ }
170
+
171
+ if (STRICT && findings.length > 0) process.exit(1);