android2harmony 0.1.4 → 0.1.6

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 (116) hide show
  1. package/README.md +1 -407
  2. package/agents/self-tester.md +55 -376
  3. package/dist/index.js +238 -124
  4. package/dist/index.js.map +4 -4
  5. package/package.json +36 -32
  6. package/skills/a2h-resource-convert/SKILL.md +931 -0
  7. package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
  8. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
  9. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
  10. package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
  11. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
  12. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
  13. package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2186 -0
  14. package/skills/a2h-resource-convert/scripts/app_identity.js +741 -0
  15. package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
  16. package/skills/a2h-resource-convert/scripts/package.json +3 -0
  17. package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
  18. package/skills/a2h-ui-transfer/SKILL.md +431 -0
  19. package/skills/a2h-ui-transfer/references/conversion-procedure.md +547 -0
  20. package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
  21. package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
  22. package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +532 -301
  23. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
  24. package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
  25. package/skills/a2h-ui-transfer/scripts/package.json +3 -0
  26. package/skills/hmos-fix-build-errors/SKILL.md +1 -1
  27. package/skills/hmos-incremental-ui-align/README.md +15 -15
  28. package/skills/hmos-incremental-ui-align/SKILL.md +15 -15
  29. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
  30. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
  31. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
  32. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
  33. package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
  34. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
  35. package/skills/hmos-integration-test/README.md +341 -0
  36. package/skills/hmos-integration-test/SKILL.md +446 -0
  37. package/skills/hmos-integration-test/scripts/report-tool.mjs +646 -0
  38. package/skills/hmos-integration-test/scripts/resolve-metadata-tool.mjs +147 -0
  39. package/skills/hmos-integration-test/scripts/self-test-runner.mjs +1006 -0
  40. package/skills/hmos-integration-test/scripts/testcases-tool.mjs +189 -0
  41. package/skills/hmos-spec-generate/SKILL.md +26 -24
  42. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +515 -0
  43. package/skills/hmos-spec-generate/template/REQ.txt +22 -0
  44. package/skills/hmos-spec-generate/template/REQ.xlsx +0 -0
  45. package/skills/hmos-batch-ui-align/SKILL.md +0 -141
  46. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
  47. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
  48. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
  49. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
  50. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
  51. package/skills/hmos-resources-convert/SKILL.md +0 -654
  52. package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
  53. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
  54. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  55. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  56. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
  57. package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
  58. package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
  59. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
  60. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
  61. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
  62. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
  63. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
  64. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
  65. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
  66. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
  67. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
  68. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
  69. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
  70. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  71. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  72. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
  73. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  74. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
  75. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
  76. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
  77. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
  78. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
  79. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
  80. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
  81. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
  82. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
  83. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
  84. package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
  85. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
  86. package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
  87. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
  88. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  89. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  90. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  91. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
  92. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
  93. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  94. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
  95. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
  96. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
  97. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  98. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
  99. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  100. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
  101. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  102. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
  103. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
  104. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
  105. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
  106. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
  107. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
  108. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
  109. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
  110. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
  111. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  112. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
  113. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  114. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
  115. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  116. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate or validate testcases.json for the Self-Testing Agent.
4
+ *
5
+ * CLI (no subcommands — positional args + flags):
6
+ * testcases-tool.mjs <input-json> <output-json> [--validate]
7
+ * Read `_extracted.json` ({bundle_name, app_name, cases[]}) and write
8
+ * `testcases.json` (array of {case_name, bundle_name, app_name, test_steps}),
9
+ * composing test_steps strictly as "动作:..." / "预期结果:..." and
10
+ * rewriting app_name → bundle_name. With --validate, re-check the output.
11
+ *
12
+ * testcases-tool.mjs --check-only <testcases-json>
13
+ * Validate an existing testcases.json (array of cases) against the
14
+ * in-file rules (FORBIDDEN_STRINGS / REQUIRED_LINES / TEST_STEPS_TEMPLATE).
15
+ * Exit 0 = PASSED, exit 1 = FAILED.
16
+ *
17
+ * The forbidden-strings and section-header rules live in this file as the
18
+ * single source of truth — generation enforces them at write time, validation
19
+ * re-checks them on existing files.
20
+ */
21
+ import fs from 'node:fs';
22
+ const FORBIDDEN_STRINGS = [
23
+ '前置条件',
24
+ 'Spec:',
25
+ 'Scenario:',
26
+ 'ability_name:',
27
+ 'module_name:',
28
+ ];
29
+ const SECTION_HEADER_RE = /【(.+?)】/;
30
+ const REQUIRED_LINES = ['动作:', '预期结果:'];
31
+ const TEST_STEPS_TEMPLATE = '动作:{actions}\n预期结果:{expected_results}';
32
+ /** Safety net: replace remaining app_name references with bundle_name. */
33
+ function rewriteAppName(text, appName, bundleName) {
34
+ if (appName && text.includes(appName)) {
35
+ return text.replaceAll(appName, bundleName);
36
+ }
37
+ return text;
38
+ }
39
+ /** Compose a compliant test_steps string with only 动作 and 预期结果. */
40
+ function composeTestSteps(appName, bundleName, actions, expectedResults) {
41
+ const rewrittenActions = rewriteAppName(actions, appName, bundleName);
42
+ const rewrittenExpected = rewriteAppName(expectedResults, appName, bundleName);
43
+ // Use function replacements so '$' sequences ($&, $$, $1, ...) in the action /
44
+ // expected text are inserted literally — String.replace with a STRING argument
45
+ // would interpret them.
46
+ let testSteps = TEST_STEPS_TEMPLATE.replace('{actions}', () => rewrittenActions).replace('{expected_results}', () => rewrittenExpected);
47
+ // Final safety: rewrite once more in case app_name only became visible after composition.
48
+ testSteps = rewriteAppName(testSteps, appName, bundleName);
49
+ for (const forbidden of FORBIDDEN_STRINGS) {
50
+ if (testSteps.includes(forbidden)) {
51
+ throw new Error(`FATAL: forbidden string '${forbidden}' found in generated test_steps. ` +
52
+ `This is a bug in testcases-tool.`);
53
+ }
54
+ }
55
+ const sectionMatch = SECTION_HEADER_RE.exec(testSteps);
56
+ if (sectionMatch) {
57
+ throw new Error(`FATAL: unexpected section header 【${sectionMatch[1]}】 in generated test_steps. ` +
58
+ `test_steps should only contain 动作 and 预期结果.`);
59
+ }
60
+ return testSteps;
61
+ }
62
+ /** Generate compliant testcases array from input data. */
63
+ export function generate(inputData) {
64
+ const { bundle_name: bundleName, app_name: appName, cases } = inputData;
65
+ // Fail loudly on malformed extractor input instead of silently writing
66
+ // `undefined`/dropped keys.
67
+ const missingTop = ['bundle_name', 'app_name', 'cases'].filter((k) => inputData[k] === undefined);
68
+ if (missingTop.length > 0) {
69
+ throw new Error(`FATAL: input is missing required key(s): ${missingTop.join(', ')}.`);
70
+ }
71
+ const result = [];
72
+ for (const [i, c] of cases.entries()) {
73
+ const missingCase = ['case_name', 'actions', 'expected_results'].filter((k) => c[k] === undefined);
74
+ if (missingCase.length > 0) {
75
+ throw new Error(`FATAL: case ${i} (${c.case_name ?? '<no case_name>'}) is missing required key(s): ` +
76
+ `${missingCase.join(', ')}.`);
77
+ }
78
+ const testSteps = composeTestSteps(appName, bundleName, c.actions, c.expected_results);
79
+ result.push({
80
+ case_name: c.case_name,
81
+ bundle_name: bundleName,
82
+ app_name: appName,
83
+ test_steps: testSteps,
84
+ });
85
+ }
86
+ return result;
87
+ }
88
+ /** Return a list of validation error messages. Empty list means PASSED. */
89
+ export function validateTestcases(filepath) {
90
+ let raw = fs.readFileSync(filepath, 'utf-8');
91
+ if (raw.charCodeAt(0) === 0xfeff)
92
+ raw = raw.slice(1);
93
+ const cases = JSON.parse(raw);
94
+ if (!Array.isArray(cases))
95
+ return ['Root element is not a JSON array'];
96
+ const errors = [];
97
+ const globalSectionRe = new RegExp(SECTION_HEADER_RE.source, 'g');
98
+ cases.forEach((c, i) => {
99
+ const ts = c?.test_steps ?? '';
100
+ const name = c?.case_name ?? `case_${i}`;
101
+ const appName = c?.app_name ?? '';
102
+ const bundleName = c?.bundle_name ?? '';
103
+ const prefix = `Case ${i + 1} [${name}]`;
104
+ for (const required of REQUIRED_LINES) {
105
+ if (!ts.includes(required))
106
+ errors.push(`${prefix}: MISSING ${required} line`);
107
+ }
108
+ for (const forbidden of FORBIDDEN_STRINGS) {
109
+ if (ts.includes(forbidden)) {
110
+ errors.push(`${prefix}: CONTAINS ${forbidden} (must be excluded)`);
111
+ }
112
+ }
113
+ let m;
114
+ while ((m = globalSectionRe.exec(ts)) !== null) {
115
+ errors.push(`${prefix}: CONTAINS unexpected section header 【${m[1]}】 ` +
116
+ `(test_steps should only contain 动作 and 预期结果)`);
117
+ }
118
+ if (appName && bundleName && appName !== bundleName && ts.includes(appName)) {
119
+ errors.push(`${prefix}: test_steps contains app_name '${appName}' instead of bundle_name ` +
120
+ `'${bundleName}'. All app name references in test_steps must use bundle_name.`);
121
+ }
122
+ });
123
+ return errors;
124
+ }
125
+ function cmdGenerate(input, output, doValidate) {
126
+ let raw = fs.readFileSync(input, 'utf-8');
127
+ if (raw.charCodeAt(0) === 0xfeff)
128
+ raw = raw.slice(1);
129
+ const inputData = JSON.parse(raw);
130
+ const testcases = generate(inputData);
131
+ fs.writeFileSync(output, JSON.stringify(testcases, null, 2), 'utf-8');
132
+ console.log(`Generated ${testcases.length} test cases -> ${output}`);
133
+ if (doValidate) {
134
+ const errors = validateTestcases(output);
135
+ if (errors.length > 0) {
136
+ console.log('VALIDATION FAILED:');
137
+ for (const e of errors)
138
+ console.log(` [FAIL] ${e}`);
139
+ process.exit(1);
140
+ }
141
+ console.log(`VALIDATION PASSED: all ${testcases.length} cases conform to the schema`);
142
+ }
143
+ }
144
+ function cmdValidate(filepath) {
145
+ const errors = validateTestcases(filepath);
146
+ if (errors.length > 0) {
147
+ console.log('VALIDATION FAILED:');
148
+ for (const e of errors)
149
+ console.log(` [FAIL] ${e}`);
150
+ process.exit(1);
151
+ }
152
+ const cases = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
153
+ console.log(`VALIDATION PASSED: all ${cases.length} cases have correct test_steps format`);
154
+ }
155
+ // ---- CLI ----
156
+ const args = process.argv.slice(2);
157
+ if (args.includes('-h') || args.includes('--help')) {
158
+ console.log(`Usage: testcases-tool.mjs <input> <output> [--validate]
159
+
160
+ Arguments:
161
+ <input> Path to the extracted input JSON (_extracted.json)
162
+ <output> Path to write testcases.json
163
+
164
+ Options:
165
+ --validate After writing, re-validate the output
166
+ --check-only <path> Validate an existing testcases.json without generating
167
+ `);
168
+ process.exit(0);
169
+ }
170
+ if (args.includes('--check-only')) {
171
+ // Validate mode
172
+ const idx = args.indexOf('--check-only');
173
+ const filepath = args[idx + 1];
174
+ if (!filepath) {
175
+ console.error('Usage: testcases-tool.mjs --check-only <path>');
176
+ process.exit(1);
177
+ }
178
+ cmdValidate(filepath);
179
+ }
180
+ else {
181
+ // Generate mode (default)
182
+ const input = args.find((a) => !a.startsWith('-'));
183
+ const output = args.filter((a) => !a.startsWith('-')).find((a) => a !== input);
184
+ if (!input || !output) {
185
+ console.error('Usage: testcases-tool.mjs <input> <output> [--validate]');
186
+ process.exit(1);
187
+ }
188
+ cmdGenerate(input, output, args.includes('--validate'));
189
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hmos-spec-generate
3
- description: "Generate requirement specification (spec) documents for Android-to-HarmonyOS migration. For each .txt requirement file in a folder, explore the Android codebase via homegraph and decompose the requirement into atomic user scenarios, emitting one markdown spec per file. Use when generating spec docs from a folder of raw requirement descriptions, decomposing requirements into atomic scenarios, or preparing scenario-based specs for HarmonyOS migration. Triggers: spec generation, 生成spec, requirement to spec, atomic scenarios, 拆分场景, scenario decomposition, 需求转spec."
3
+ description: "Generate requirement specification (spec) documents for Android-to-HarmonyOS migration. Read a requirement description file containing multiple requirements, split it into individual requirements, then for each one explore the Android codebase via homegraph and decompose it into atomic user scenarios, emitting one markdown spec per requirement. Use when generating spec docs from raw requirement descriptions, decomposing requirements into atomic scenarios, or preparing scenario-based specs for HarmonyOS migration. Triggers: hmos-spec-generate, spec generation, 生成spec, requirement to spec, atomic scenarios, 拆分场景, scenario decomposition, 需求转spec."
4
4
  license: MIT
5
5
  allowed-tools:
6
6
  - Read
@@ -17,26 +17,26 @@ allowed-tools:
17
17
 
18
18
  # Spec Generator Skill
19
19
 
20
- You are a **Spec Generator** that produces high-quality requirement specification (spec) documents for Android-to-HarmonyOS migration projects. Given a folder of raw requirement `.txt` files, explore the corresponding Android codebase via homegraph and emit a clean, atomic-scenario-based spec markdown for every requirement.
20
+ You are a **Spec Generator** that produces high-quality requirement specification (spec) documents for Android-to-HarmonyOS migration projects. Given a requirement description file containing multiple requirements, split it into individual requirements, explore the corresponding Android codebase via homegraph and emit a clean, atomic-scenario-based spec markdown for every requirement.
21
21
 
22
22
  ## Quick Reference
23
23
 
24
24
  | Item | Value |
25
25
  |------|-------|
26
- | **Input** | `requirement-description-dir` + `android-project-path` + `spec-output-dir` |
27
- | **Output** | One `<source_filename_without_ext>.md` per input `.txt` under `spec-output-dir` |
26
+ | **Input** | `requirement_description_file` + `android_project_dir` + `spec_output_dir` |
27
+ | **Output** | One `<feature>-SPEC.md` per requirement under `spec_output_dir` |
28
28
  | **homegraph** | `npx --yes homegraph <command>` CLI via `Bash` (index management + code queries) |
29
- | **Processing Mode** | Per-file end-to-end loop (no batch pre-read) |
29
+ | **Processing Mode** | Per-requirement end-to-end loop (split first, then one requirement at a time) |
30
30
 
31
31
  ## Expected Input
32
32
 
33
- - `requirement-description-dir`: Absolute path to a directory containing one or more `.txt` files. Each `.txt` file's content is the raw requirement description of a single requirement (required).
34
- - `android-project-path`: Absolute path to the Android project root (required).
35
- - `spec-output-dir`: Absolute path to the directory where the generated spec files (`xxx.md`) will be written (required).
33
+ - `requirement_description_file`: Absolute path to a requirement description file whose content contains multiple requirements (required). Supported types: Excel (`.xlsx` / `.xlsm` / `.csv`, one row = one requirement) and text (`.txt` / `.md`, one blank-line-separated paragraph = one requirement). See `template/REQ.xlsx` and `template/REQ.txt` for equivalent examples.
34
+ - `android_project_dir`: Absolute path to the Android project root (required).
35
+ - `spec_output_dir`: Absolute path to the directory where the generated spec files (`<feature>-SPEC.md`) will be written (required).
36
36
 
37
37
  ## Expected Output
38
38
 
39
- - One markdown spec file per input `.txt` requirement file, written under `spec-output-dir`. The spec filename mirrors the source filename with `.md` extension (e.g. `create_playlist.txt` `create_playlist.md`).
39
+ - One markdown spec file per requirement split out of `requirement_description_file`, written under `spec_output_dir` as **`<feature>-SPEC.md`**, where `<feature>` is the requirement title. The `-SPEC.md` suffix is required by the downstream `hmos-test-case-generation` skill, which only accepts a `spec-path` ending in it.
40
40
 
41
41
  ---
42
42
 
@@ -362,41 +362,43 @@ These are the authoring rules the final spec MUST follow:
362
362
 
363
363
  ## Workflow
364
364
 
365
- ### Step 0 — Validate inputs
365
+ ### Step 0 — Validate inputs and split requirements
366
366
 
367
- 1. Verify `requirement-description-dir` exists and is a directory. List all `.txt` files directly under it (collect the list of filenames only do NOT read their contents yet). If there are no `.txt` files, stop and report the error.
368
- 2. Verify `android-project-path` exists and is a directory.
369
- 3. Ensure `spec-output-dir` exists (create it if it does not).
367
+ 1. Verify `requirement_description_file` exists and is a readable file, then split its content into individual requirements by input type. Keep each requirement's text verbatim and record them in order; do NOT analyze them yet. If no requirement can be extracted, stop and report the error.
368
+ - **Excel type (`.xlsx` / `.xlsm` / `.csv`)**: run `node <skill-dir>/scripts/parse_requirements.ts --input <requirement_description_file> --json` via `Bash` to read the file content. Each item of the returned JSON array is one raw requirement: `uiPath` is the title, `description` is the requirement body. If the script errors, surface it verbatim and stop.
369
+ - **Text type (`.txt` / `.md`)**: read the content directly and split it into individual requirements (each requirement starts with a title line followed by its description; requirements are separated by blank lines).
370
+ 2. Verify `android_project_dir` exists and is a directory.
371
+ 3. Ensure `spec_output_dir` exists (create it if it does not).
370
372
 
371
373
  ### Step 1 — Ensure homegraph has a fresh index for the project
372
374
 
373
- homegraph is invoked exclusively through its CLI via `Bash`. Always prefix commands with `npx` (e.g. `npx --yes homegraph status <android-project-path>`); `npx` resolves the binary without requiring a global install. All index management and code query commands share the same CLI — there is no MCP surface in this skill.
375
+ homegraph is invoked exclusively through its CLI via `Bash`. Always prefix commands with `npx` (e.g. `npx --yes homegraph status <android_project_dir>`); `npx` resolves the binary without requiring a global install. All index management and code query commands share the same CLI — there is no MCP surface in this skill.
374
376
 
375
377
  homegraph indexes per-project into a local `.homegraph/` directory; there is no central repo registry. Always pass the Android project path as the positional `[path]` argument to scope the command to the Android project (this skill's working directory is the spec project, NOT the Android project, so omitting the path queries the wrong project).
376
378
 
377
379
  Procedure:
378
380
 
379
- 1. Run `npx --yes homegraph status <android-project-path>` to inspect the index state.
381
+ 1. Run `npx --yes homegraph status <android_project_dir>` to inspect the index state.
380
382
  2. Decide based on the output:
381
- - **Not indexed** (no `.homegraph/`) → run `npx --yes homegraph init -i <android-project-path>`, wait for completion.
382
- - **Stale** (`npx --yes homegraph status` output does NOT contain `[OK] Index is up to date`) → run `npx --yes homegraph sync <android-project-path>` (incremental; use `npx --yes homegraph index <android-project-path> --force` for a full rebuild), wait for completion.
383
+ - **Not indexed** (no `.homegraph/`) → run `npx --yes homegraph init -i <android_project_dir>`, wait for completion.
384
+ - **Stale** (`npx --yes homegraph status` output does NOT contain `[OK] Index is up to date`) → run `npx --yes homegraph sync <android_project_dir>` (incremental; use `npx --yes homegraph index <android_project_dir> --force` for a full rebuild), wait for completion.
383
385
  - **Fresh** → proceed to Step 2.
384
386
 
385
387
  **Hard fail policy** — any of the following stops the skill and surfaces the error to the user; do NOT degrade to `Read` + `Grep`:
386
388
 
387
389
  - `homegraph` CLI is not reachable via `npx` → ask the user to install it (`npm install -g homegraph` or `npx --yes homegraph install`).
388
390
  - `npx --yes homegraph status`, `init`, `sync`, or `index` returns an error → surface the error verbatim and stop. Do not attempt spec generation without an index — the resulting spec would miss code-grounded scenarios.
389
- - Any `npx --yes homegraph` query reports the project is not indexed → verify `init -i` ran against `android-project-path`.
391
+ - Any `npx --yes homegraph` query reports the project is not indexed → verify `init -i` ran against `android_project_dir`.
390
392
 
391
- ### Step 2 — Process each requirement file independently, one at a time
393
+ ### Step 2 — Process each requirement independently, one at a time
392
394
 
393
- For each `.txt` filename collected in Step 0, in order, run 2.1 → 2.4 end-to-end before moving to the next file. Do NOT pre-read all `.txt` files.
395
+ For each requirement split out in Step 0, in order, run 2.1 → 2.4 end-to-end before moving to the next requirement.
394
396
 
395
- - **2.1 Read**: Read this file's content verbatim. If empty, warn and skip to the next file.
396
- - **2.2 Analyze**: Explore the Android codebase for this requirement using the `npx --yes homegraph` CLI (via `Bash`) to retrieve the complete code implementation (entry points, end-to-end call chains, state writes / persistence keys, manifest declarations). Query commands (`query` / `explore` / `node` / `callers` / `callees` / `impact` / `files`) resolve their target project from the current working directory's nearest `.homegraph/` index, so run them from within the Android project root using a subshell — e.g. `cd <android-project-path> && npx --yes homegraph explore "<query>"` (the index commands `status` / `init` / `sync` / `index` accept an explicit path argument, see Step 1). Use `npx --yes homegraph query "<keyword>"` / `npx --yes homegraph explore "<query>"` to locate symbols and read their line-numbered source; `npx --yes homegraph node "<symbol|file>"` to read a single symbol or whole file; `npx --yes homegraph callers "<symbol>"` / `npx --yes homegraph callees "<symbol>"` to walk call chains up and down; `npx --yes homegraph impact "<symbol>"` for change blast radius; `npx --yes homegraph files` to enumerate indexed files; and `Grep` for exact literals (settings keys, DAO columns, `android:key`) that live outside the code graph. Always run query commands from `<android-project-path>` — this skill's working directory is the spec project, NOT the Android project, so running them from elsewhere queries the wrong project (or none). Use the CLI directly — do NOT invoke any MCP tool or nested exploration skill. If any `npx --yes homegraph` call fails unexpectedly, surface the error and stop.
397
+ - **2.1 Scope**: Take this requirement's title + description verbatim as the raw requirement under analysis. If it is empty, warn and skip to the next requirement.
398
+ - **2.2 Analyze**: Explore the Android codebase for this requirement using the `npx --yes homegraph` CLI (via `Bash`) to retrieve the complete code implementation (entry points, end-to-end call chains, state writes / persistence keys, manifest declarations). Query commands (`query` / `explore` / `node` / `callers` / `callees` / `impact` / `files`) resolve their target project from the current working directory's nearest `.homegraph/` index, so run them from within the Android project root using a subshell — e.g. `cd <android_project_dir> && npx --yes homegraph explore "<query>"` (the index commands `status` / `init` / `sync` / `index` accept an explicit path argument, see Step 1). Use `npx --yes homegraph query "<keyword>"` / `npx --yes homegraph explore "<query>"` to locate symbols and read their line-numbered source; `npx --yes homegraph node "<symbol|file>"` to read a single symbol or whole file; `npx --yes homegraph callers "<symbol>"` / `npx --yes homegraph callees "<symbol>"` to walk call chains up and down; `npx --yes homegraph impact "<symbol>"` for change blast radius; `npx --yes homegraph files` to enumerate indexed files; and `Grep` for exact literals (settings keys, DAO columns, `android:key`) that live outside the code graph. Always run query commands from `<android_project_dir>` — this skill's working directory is the spec project, NOT the Android project, so running them from elsewhere queries the wrong project (or none). Use the CLI directly — do NOT invoke any MCP tool or nested exploration skill. If any `npx --yes homegraph` call fails unexpectedly, surface the error and stop.
397
399
  - **2.3 Decompose**: Using the raw requirement + code from 2.2, generate the spec per `## Principles` and `## Spec Few-shot Samples`.
398
- - **2.4 Write**: Write the spec to `<spec-output-dir>/<source_filename_without_ext>.md` (overwrite if exists), then move on to the next file.
400
+ - **2.4 Write**: Write the spec to `<spec_output_dir>/<feature>-SPEC.md` (overwrite if exists), then move on to the next requirement.
399
401
 
400
402
  ### Step 3 — Report
401
403
 
402
- After all requirement files have been processed (or skipped with a warning), report a brief summary listing each generated spec file path and any files that were skipped.
404
+ After all requirements have been processed (or skipped with a warning), report a brief summary listing each generated spec file path and any requirements that were skipped.