@buaa_smat/hometrans 0.1.10 → 0.1.11
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.
- package/README.md +229 -214
- package/agents/build-fixer.md +17 -10
- package/agents/code-reviewer.md +4 -2
- package/dist/cli/config-store.js +57 -0
- package/dist/cli/config.js +8 -9
- package/dist/cli/init.js +311 -19
- package/dist/cli/mcp-setup.js +2 -6
- package/dist/context/index.js +46 -0
- package/package.json +1 -1
- package/resource/choose_editor.png +0 -0
- package/resource/hometrans_config.png +0 -0
- package/skills/hmos-convert-pipeline/SKILL.md +19 -11
- package/skills/hmos-fix-build-errors/SKILL.md +13 -5
- package/skills/hmos-incremental-ui-align/SKILL.md +15 -1
- package/resource/finish_init.png +0 -0
package/dist/cli/config.js
CHANGED
|
@@ -17,16 +17,15 @@ export async function configCommand() {
|
|
|
17
17
|
console.log(` Path: ${configPath}`);
|
|
18
18
|
console.log('');
|
|
19
19
|
// User parameters
|
|
20
|
-
const
|
|
21
|
-
? config.env.TEST_API_KEY.slice(0, 4) +
|
|
22
|
-
'***' +
|
|
23
|
-
config.env.TEST_API_KEY.slice(-4)
|
|
24
|
-
: '(not set)';
|
|
20
|
+
const mask = (key) => key ? key.slice(0, 4) + '***' + key.slice(-4) : '(not set)';
|
|
25
21
|
console.log(' Parameters:');
|
|
26
|
-
console.log(`
|
|
27
|
-
console.log(`
|
|
28
|
-
console.log(`
|
|
29
|
-
console.log(`
|
|
22
|
+
console.log(` DEVECO_SDK_HOME : ${config.env.DEVECO_SDK_HOME || '(not set)'}`);
|
|
23
|
+
console.log(` DEVECO_PATH : ${config.env.DEVECO_PATH || '(not set)'} (derived)`);
|
|
24
|
+
console.log(` OHOS_SDK_PATH : ${config.env.OHOS_SDK_PATH || '(not set)'} (derived)`);
|
|
25
|
+
console.log(` HMS_SDK_PATH : ${config.env.HMS_SDK_PATH || '(not set)'} (derived)`);
|
|
26
|
+
console.log(` TEST_API_KEY : ${mask(config.env.TEST_API_KEY)}`);
|
|
27
|
+
console.log(` GLM_API_KEY : ${mask(config.env.GLM_API_KEY)}`);
|
|
28
|
+
console.log(` TOOL_PATH : ${config.env.TOOL_PATH || '(not set)'}`);
|
|
30
29
|
console.log('');
|
|
31
30
|
// Full config content
|
|
32
31
|
console.log(' Full Content:');
|
package/dist/cli/init.js
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'node:fs/promises';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import figlet from 'figlet';
|
|
14
14
|
import inquirer from 'inquirer';
|
|
15
|
+
import { parseTree, modify, applyEdits } from 'jsonc-parser';
|
|
15
16
|
import { setupMcpForAllEditors } from './mcp-setup.js';
|
|
16
|
-
import { expandHome, getConfigPath, getToolsDir, loadHomeTransConfig, saveHomeTransConfig, } from './config-store.js';
|
|
17
|
+
import { deriveSdkPaths, expandHome, getConfigPath, getToolsDir, loadHomeTransConfig, saveHomeTransConfig, } from './config-store.js';
|
|
17
18
|
function ensureChalkColor() {
|
|
18
19
|
if (process.stdout.isTTY && chalk.level === 0) {
|
|
19
20
|
chalk.level = 1;
|
|
@@ -130,6 +131,70 @@ async function refreshAutotestConfig(autotestDir, apiKey) {
|
|
|
130
131
|
? `autotest config.yaml seeded + api_key filled`
|
|
131
132
|
: `autotest config.yaml api_key refreshed`;
|
|
132
133
|
}
|
|
134
|
+
const UI_ALIGN_SKILL = 'hmos-incremental-ui-align';
|
|
135
|
+
/**
|
|
136
|
+
* Initialize / refresh `<skillsDir>/hmos-incremental-ui-align/config.json`
|
|
137
|
+
* in an editor's installed skills dir — same semantics as the autotest
|
|
138
|
+
* config.yaml handling:
|
|
139
|
+
* - If config.json does not exist, seed it from config-example.json.
|
|
140
|
+
* - If it exists, surgically overwrite ONLY the `glm_api_key` and
|
|
141
|
+
* `hmos_sdk_dir` values (via jsonc edits), preserving every other
|
|
142
|
+
* field the user has set. `hmos_sdk_dir` = `<DEVECO_SDK_HOME>/default`.
|
|
143
|
+
*
|
|
144
|
+
* Returns a status string for the result summary, or null if nothing was
|
|
145
|
+
* done (skill not installed there, or no values to write into an existing file).
|
|
146
|
+
*/
|
|
147
|
+
export async function refreshUiAlignConfig(skillsDir, glmApiKey, hmosSdkDir) {
|
|
148
|
+
const skillDir = path.join(skillsDir, UI_ALIGN_SKILL);
|
|
149
|
+
const examplePath = path.join(skillDir, 'config-example.json');
|
|
150
|
+
const configPath = path.join(skillDir, 'config.json');
|
|
151
|
+
const hasExample = await fs
|
|
152
|
+
.access(examplePath)
|
|
153
|
+
.then(() => true)
|
|
154
|
+
.catch(() => false);
|
|
155
|
+
if (!hasExample)
|
|
156
|
+
return null;
|
|
157
|
+
let seeded = false;
|
|
158
|
+
const hasConfig = await fs
|
|
159
|
+
.access(configPath)
|
|
160
|
+
.then(() => true)
|
|
161
|
+
.catch(() => false);
|
|
162
|
+
if (!hasConfig) {
|
|
163
|
+
await fs.copyFile(examplePath, configPath);
|
|
164
|
+
seeded = true;
|
|
165
|
+
}
|
|
166
|
+
const updates = [];
|
|
167
|
+
if (glmApiKey)
|
|
168
|
+
updates.push(['glm_api_key', glmApiKey]);
|
|
169
|
+
if (hmosSdkDir)
|
|
170
|
+
updates.push(['hmos_sdk_dir', hmosSdkDir]);
|
|
171
|
+
if (updates.length === 0) {
|
|
172
|
+
return seeded
|
|
173
|
+
? `${UI_ALIGN_SKILL}/config.json seeded (glm_api_key / hmos_sdk_dir left empty)`
|
|
174
|
+
: null;
|
|
175
|
+
}
|
|
176
|
+
let raw = await fs.readFile(configPath, 'utf-8');
|
|
177
|
+
const parseErrors = [];
|
|
178
|
+
const tree = parseTree(raw, parseErrors);
|
|
179
|
+
if (!tree || tree.type !== 'object' || parseErrors.length > 0) {
|
|
180
|
+
// Corrupt config — never regenerate over user content; just report.
|
|
181
|
+
return `${UI_ALIGN_SKILL}/config.json NOT updated (file is not valid JSON — fix it manually)`;
|
|
182
|
+
}
|
|
183
|
+
const original = raw;
|
|
184
|
+
for (const [key, value] of updates) {
|
|
185
|
+
const edits = modify(raw, [key], value, {
|
|
186
|
+
formattingOptions: { tabSize: 2, insertSpaces: true },
|
|
187
|
+
});
|
|
188
|
+
raw = applyEdits(raw, edits);
|
|
189
|
+
}
|
|
190
|
+
if (raw !== original) {
|
|
191
|
+
await fs.writeFile(configPath, raw, 'utf-8');
|
|
192
|
+
}
|
|
193
|
+
const what = updates.map(([k]) => k).join(' + ');
|
|
194
|
+
return seeded
|
|
195
|
+
? `${UI_ALIGN_SKILL}/config.json seeded + ${what} filled`
|
|
196
|
+
: `${UI_ALIGN_SKILL}/config.json ${what} refreshed`;
|
|
197
|
+
}
|
|
133
198
|
async function installSkillsTo(skillsRoot, targetDir) {
|
|
134
199
|
let entries;
|
|
135
200
|
try {
|
|
@@ -180,7 +245,7 @@ async function installAgentsTo(agentsRoot, targetDir) {
|
|
|
180
245
|
}
|
|
181
246
|
return installed;
|
|
182
247
|
}
|
|
183
|
-
async function installForEditor(editor, skillsRoot, agentsRoot, result) {
|
|
248
|
+
async function installForEditor(editor, skillsRoot, agentsRoot, glmApiKey, hmosSdkDir, result) {
|
|
184
249
|
const marker = expandHome(editor.markerDir);
|
|
185
250
|
if (marker && !(await dirExists(marker))) {
|
|
186
251
|
result.skipped.push(`${editor.name} (not installed)`);
|
|
@@ -197,6 +262,17 @@ async function installForEditor(editor, skillsRoot, agentsRoot, result) {
|
|
|
197
262
|
catch (err) {
|
|
198
263
|
result.errors.push(`${editor.name} skills: ${err.message}`);
|
|
199
264
|
}
|
|
265
|
+
// Seed / refresh the incremental-ui-align per-skill config.json with the
|
|
266
|
+
// GLM key + SDK dir from ~/.hometrans/config.json (existing files: key-only update).
|
|
267
|
+
try {
|
|
268
|
+
const status = await refreshUiAlignConfig(skillsDir, glmApiKey, hmosSdkDir);
|
|
269
|
+
if (status) {
|
|
270
|
+
result.configured.push(`${editor.name} ${status}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
result.errors.push(`${editor.name} ${UI_ALIGN_SKILL}/config.json: ${err.message}`);
|
|
275
|
+
}
|
|
200
276
|
try {
|
|
201
277
|
const agents = await installAgentsTo(agentsRoot, agentsDir);
|
|
202
278
|
if (agents.length > 0) {
|
|
@@ -207,12 +283,185 @@ async function installForEditor(editor, skillsRoot, agentsRoot, result) {
|
|
|
207
283
|
result.errors.push(`${editor.name} agents: ${err.message}`);
|
|
208
284
|
}
|
|
209
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Display paths as full absolute OS-native paths (e.g. on Windows:
|
|
288
|
+
* `C:\Users\<you>\.claude\skills`) — no `~` abbreviation, no separator
|
|
289
|
+
* rewriting, so output matches what the user can copy into their shell.
|
|
290
|
+
*/
|
|
210
291
|
export function prettyHome(p) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
292
|
+
return p;
|
|
293
|
+
}
|
|
294
|
+
/** Locate a command on PATH (`where` on Windows, `which` elsewhere). */
|
|
295
|
+
function whichSync(cmd) {
|
|
296
|
+
const isWin = process.platform === 'win32';
|
|
297
|
+
try {
|
|
298
|
+
const out = execFileSync(isWin ? 'where' : 'which', [cmd], {
|
|
299
|
+
encoding: 'utf-8',
|
|
300
|
+
timeout: 5000,
|
|
301
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
302
|
+
});
|
|
303
|
+
const lines = out.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
304
|
+
return lines[0] || null;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function captureVersion(cmd, args) {
|
|
311
|
+
try {
|
|
312
|
+
const out = execFileSync(cmd, args, {
|
|
313
|
+
encoding: 'utf-8',
|
|
314
|
+
timeout: 5000,
|
|
315
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
316
|
+
});
|
|
317
|
+
return out.split('\n')[0].trim() || null;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async function fileExists(p) {
|
|
324
|
+
try {
|
|
325
|
+
await fs.access(p);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/** Install hints shown when a tool is missing. */
|
|
333
|
+
const TOOL_HINTS = {
|
|
334
|
+
deveco: 'set DEVECO_SDK_HOME via `ht init` (DevEco Studio install)',
|
|
335
|
+
adb: 'Android SDK platform-tools (needed for Android-device capture)',
|
|
336
|
+
hdc: 'ships with DevEco: <sdk>/default/openharmony/toolchains',
|
|
337
|
+
python: 'install Python >= 3.10 and add to PATH (UI-align capture/parse scripts)',
|
|
338
|
+
uv: 'https://docs.astral.sh/uv (AutoTest runs under uv)',
|
|
339
|
+
java: 'any JDK 17+, or reuse DevEco jbr: <deveco>/jbr/bin',
|
|
340
|
+
gitnexus: 'npm i -g gitnexus && gitnexus setup',
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Detect external tools. `hdc` falls back to the DevEco toolchains dir and
|
|
344
|
+
* `java` to DevEco's bundled jbr when not on PATH, mirroring how the
|
|
345
|
+
* skills/agents themselves resolve them.
|
|
346
|
+
*/
|
|
347
|
+
async function detectEnvironment(env) {
|
|
348
|
+
const status = new Map();
|
|
349
|
+
const isWin = process.platform === 'win32';
|
|
350
|
+
const sdkOk = env.DEVECO_SDK_HOME ? await dirExists(env.DEVECO_SDK_HOME) : false;
|
|
351
|
+
status.set('deveco', {
|
|
352
|
+
found: sdkOk,
|
|
353
|
+
path: sdkOk ? env.DEVECO_SDK_HOME : undefined,
|
|
354
|
+
});
|
|
355
|
+
for (const cmd of ['adb', 'python', 'uv', 'gitnexus']) {
|
|
356
|
+
const p = whichSync(cmd);
|
|
357
|
+
const st = { found: !!p, path: p ?? undefined };
|
|
358
|
+
if (cmd === 'python' && p) {
|
|
359
|
+
const v = captureVersion('python', ['--version']);
|
|
360
|
+
if (v) {
|
|
361
|
+
st.note = v;
|
|
362
|
+
const m = v.match(/(\d+)\.(\d+)/);
|
|
363
|
+
if (m && (Number(m[1]) < 3 || (Number(m[1]) === 3 && Number(m[2]) < 10))) {
|
|
364
|
+
st.note += ' (UI-align scripts require >= 3.10)';
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
status.set(cmd, st);
|
|
369
|
+
}
|
|
370
|
+
// hdc: PATH first, then DevEco toolchains.
|
|
371
|
+
let hdcPath = whichSync('hdc');
|
|
372
|
+
if (!hdcPath && sdkOk) {
|
|
373
|
+
const cand = path.join(env.DEVECO_SDK_HOME, 'default', 'openharmony', 'toolchains', isWin ? 'hdc.exe' : 'hdc');
|
|
374
|
+
if (await fileExists(cand))
|
|
375
|
+
hdcPath = cand;
|
|
376
|
+
}
|
|
377
|
+
status.set('hdc', { found: !!hdcPath, path: hdcPath ?? undefined });
|
|
378
|
+
// java: PATH first, then DevEco jbr.
|
|
379
|
+
let javaPath = whichSync('java');
|
|
380
|
+
let javaNote;
|
|
381
|
+
if (!javaPath && env.DEVECO_PATH) {
|
|
382
|
+
const cand = path.join(env.DEVECO_PATH, 'jbr', 'bin', isWin ? 'java.exe' : 'java');
|
|
383
|
+
if (await fileExists(cand)) {
|
|
384
|
+
javaPath = cand;
|
|
385
|
+
javaNote = 'via DevEco jbr';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
status.set('java', { found: !!javaPath, path: javaPath ?? undefined, note: javaNote });
|
|
389
|
+
return status;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Which tools each skill/agent needs. `required` missing → BLOCKED;
|
|
393
|
+
* only `optional` missing → LIMITED. Kept in sync with the README
|
|
394
|
+
* "Per-skill environment requirements" matrix.
|
|
395
|
+
*/
|
|
396
|
+
const IMPACT_MATRIX = [
|
|
397
|
+
{ name: 'hmos-spec-generate', kind: 'skill', required: ['gitnexus'], optional: [] },
|
|
398
|
+
{ name: 'hmos-resources-convert', kind: 'skill', required: [], optional: ['java'], note: 'no java -> falls back to source res/' },
|
|
399
|
+
{ name: 'hmos-batch-ui-align', kind: 'skill', required: ['python'], optional: ['adb'], note: 'adb only for page exploration' },
|
|
400
|
+
{ name: 'hmos-incremental-ui-align', kind: 'skill', required: ['deveco', 'python', 'hdc', 'adb'], optional: [] },
|
|
401
|
+
{ name: 'hmos-integration-test', kind: 'skill', required: ['deveco', 'uv', 'hdc'], optional: [] },
|
|
402
|
+
{ name: 'hmos-fix-build-errors', kind: 'skill', required: ['deveco'], optional: [] },
|
|
403
|
+
{ name: 'hmos-convert-pipeline', kind: 'skill', required: ['deveco'], optional: ['uv', 'hdc'], note: 'test stage skippable via skip-test' },
|
|
404
|
+
{ name: 'logic-context-builder', kind: 'agent', required: [], optional: ['python'] },
|
|
405
|
+
{ name: 'logic-coder', kind: 'agent', required: [], optional: ['python'] },
|
|
406
|
+
{ name: 'spec-generator', kind: 'agent', required: ['gitnexus'], optional: [] },
|
|
407
|
+
{ name: 'build-fixer', kind: 'agent', required: ['deveco'], optional: [] },
|
|
408
|
+
{ name: 'code-reviewer', kind: 'agent', required: [], optional: ['deveco'] },
|
|
409
|
+
{ name: 'review-fixer', kind: 'agent', required: [], optional: [] },
|
|
410
|
+
{ name: 'self-tester', kind: 'agent', required: ['uv', 'hdc'], optional: [] },
|
|
411
|
+
{ name: 'self-test-fixer', kind: 'agent', required: [], optional: [] },
|
|
412
|
+
];
|
|
413
|
+
/** Print detected tools + the per-skill/agent impact table. */
|
|
414
|
+
export async function runEnvironmentCheck(env) {
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log(chalk.blue(' Environment Check'));
|
|
417
|
+
const tools = await detectEnvironment(env);
|
|
418
|
+
const order = ['deveco', 'adb', 'hdc', 'python', 'uv', 'java', 'gitnexus'];
|
|
419
|
+
for (const name of order) {
|
|
420
|
+
const st = tools.get(name);
|
|
421
|
+
if (st.found) {
|
|
422
|
+
const note = st.note ? chalk.gray(` (${st.note})`) : '';
|
|
423
|
+
console.log(` ${chalk.green('+')} ${name.padEnd(9)} ${st.path ?? ''}${note}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(` ${chalk.yellow('!')} ${name.padEnd(9)} ${chalk.yellow('not found')} ${chalk.gray(TOOL_HINTS[name] ?? '')}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const missing = order.filter((n) => !tools.get(n).found);
|
|
430
|
+
console.log('');
|
|
431
|
+
console.log(chalk.blue(' Impact on skills/agents:'));
|
|
432
|
+
const nameW = 28;
|
|
433
|
+
console.log(chalk.gray(` ${'Name'.padEnd(nameW)} ${'Kind'.padEnd(6)} ${'Status'.padEnd(8)} Missing`));
|
|
434
|
+
for (const row of IMPACT_MATRIX) {
|
|
435
|
+
const missReq = row.required.filter((t) => missing.includes(t));
|
|
436
|
+
const missOpt = row.optional.filter((t) => missing.includes(t));
|
|
437
|
+
let plain;
|
|
438
|
+
let color;
|
|
439
|
+
let parts;
|
|
440
|
+
if (missReq.length > 0) {
|
|
441
|
+
plain = 'BLOCKED';
|
|
442
|
+
color = chalk.red;
|
|
443
|
+
parts = [...missReq, ...missOpt.map((t) => `${t}(optional)`)];
|
|
444
|
+
}
|
|
445
|
+
else if (missOpt.length > 0) {
|
|
446
|
+
plain = 'LIMITED';
|
|
447
|
+
color = chalk.yellow;
|
|
448
|
+
parts = missOpt.map((t) => `${t}(optional)`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
plain = 'OK';
|
|
452
|
+
color = chalk.green;
|
|
453
|
+
parts = [];
|
|
454
|
+
}
|
|
455
|
+
const noteStr = parts.length > 0 && row.note ? chalk.gray(` — ${row.note}`) : '';
|
|
456
|
+
console.log(` ${row.name.padEnd(nameW)} ${row.kind.padEnd(6)} ${color(plain.padEnd(8))} ${parts.join(', ')}${noteStr}`);
|
|
457
|
+
}
|
|
458
|
+
console.log('');
|
|
459
|
+
if (missing.length === 0) {
|
|
460
|
+
console.log(chalk.green(' All environment dependencies found — every skill/agent is fully usable.'));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(chalk.gray(' Install the missing tools above and re-run `ht init` to clear the impact list.'));
|
|
214
464
|
}
|
|
215
|
-
return p.replace(/\\/g, '/');
|
|
216
465
|
}
|
|
217
466
|
async function detectInstalledEditors(editors) {
|
|
218
467
|
const status = new Map();
|
|
@@ -309,33 +558,72 @@ export async function initCommand(options = {}) {
|
|
|
309
558
|
const answers = await inquirer.prompt([
|
|
310
559
|
{
|
|
311
560
|
type: 'input',
|
|
312
|
-
name: '
|
|
313
|
-
message: '
|
|
314
|
-
default: config.env.
|
|
561
|
+
name: 'DEVECO_SDK_HOME',
|
|
562
|
+
message: 'DEVECO_SDK_HOME (DevEco Studio SDK dir):',
|
|
563
|
+
default: config.env.DEVECO_SDK_HOME || undefined,
|
|
315
564
|
},
|
|
316
565
|
{
|
|
317
566
|
type: 'input',
|
|
318
|
-
name: '
|
|
319
|
-
message: '
|
|
320
|
-
default: config.env.
|
|
567
|
+
name: 'TEST_API_KEY',
|
|
568
|
+
message: 'TEST_API_KEY (LLM API key used by the integration-test agent to run test cases):',
|
|
569
|
+
default: config.env.TEST_API_KEY || undefined,
|
|
321
570
|
},
|
|
322
571
|
{
|
|
323
572
|
type: 'input',
|
|
324
|
-
name: '
|
|
325
|
-
message: '
|
|
326
|
-
default: config.env.
|
|
573
|
+
name: 'GLM_API_KEY',
|
|
574
|
+
message: 'GLM_API_KEY (LLM API key for the GLM phone-agent used in UI alignment):',
|
|
575
|
+
default: config.env.GLM_API_KEY || undefined,
|
|
327
576
|
},
|
|
328
577
|
]);
|
|
329
|
-
|
|
330
|
-
config.env.
|
|
578
|
+
const sdk = deriveSdkPaths(answers.DEVECO_SDK_HOME);
|
|
579
|
+
config.env.DEVECO_SDK_HOME = sdk.DEVECO_SDK_HOME;
|
|
580
|
+
config.env.DEVECO_PATH = sdk.DEVECO_PATH;
|
|
581
|
+
config.env.OHOS_SDK_PATH = sdk.OHOS_SDK_PATH;
|
|
582
|
+
config.env.HMS_SDK_PATH = sdk.HMS_SDK_PATH;
|
|
331
583
|
config.env.TEST_API_KEY = answers.TEST_API_KEY.trim();
|
|
584
|
+
config.env.GLM_API_KEY = answers.GLM_API_KEY.trim();
|
|
332
585
|
await saveHomeTransConfig(config);
|
|
586
|
+
// Echo the derived paths and validate each exists on disk; missing ones are
|
|
587
|
+
// a strong signal of a typo'd DEVECO_SDK_HOME or a broken DevEco install.
|
|
588
|
+
if (sdk.DEVECO_SDK_HOME) {
|
|
589
|
+
const checks = [
|
|
590
|
+
['DEVECO_SDK_HOME', sdk.DEVECO_SDK_HOME],
|
|
591
|
+
['DEVECO_PATH', sdk.DEVECO_PATH],
|
|
592
|
+
['OHOS_SDK_PATH', sdk.OHOS_SDK_PATH],
|
|
593
|
+
['HMS_SDK_PATH', sdk.HMS_SDK_PATH],
|
|
594
|
+
];
|
|
595
|
+
console.log('');
|
|
596
|
+
console.log(chalk.blue(' Derived from DEVECO_SDK_HOME:'));
|
|
597
|
+
let missingCount = 0;
|
|
598
|
+
for (const [name, p] of checks) {
|
|
599
|
+
const exists = await dirExists(p);
|
|
600
|
+
if (!exists)
|
|
601
|
+
missingCount++;
|
|
602
|
+
const mark = exists ? chalk.green('+') : chalk.yellow('!');
|
|
603
|
+
const note = exists ? '' : chalk.yellow(' (not found on disk)');
|
|
604
|
+
console.log(` ${mark} ${name.padEnd(15)} : ${p}${note}`);
|
|
605
|
+
}
|
|
606
|
+
if (missingCount > 0) {
|
|
607
|
+
console.log('');
|
|
608
|
+
console.log(chalk.yellow(' ! Check DEVECO_SDK_HOME (it should be the "sdk" folder inside your DevEco Studio install)'));
|
|
609
|
+
console.log(chalk.yellow(' and re-run `ht init` after fixing it. Skills that build or'));
|
|
610
|
+
console.log(chalk.yellow(' review code will not work until these paths resolve.'));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
333
613
|
}
|
|
334
614
|
catch (err) {
|
|
335
615
|
if (isPromptAbort(err))
|
|
336
616
|
abortInit();
|
|
337
617
|
console.log(chalk.yellow(' Parameter prompts skipped (non-interactive mode).'));
|
|
338
618
|
}
|
|
619
|
+
// Detect external tools (adb / hdc / python / uv / java / gitnexus + DevEco)
|
|
620
|
+
// and report which skills/agents are impacted by anything missing.
|
|
621
|
+
try {
|
|
622
|
+
await runEnvironmentCheck(config.env);
|
|
623
|
+
}
|
|
624
|
+
catch (err) {
|
|
625
|
+
console.log(chalk.yellow(` ! environment check skipped: ${err.message}`));
|
|
626
|
+
}
|
|
339
627
|
// Copy bundled tools/ into ~/.hometrans/tools and record env.TOOL_PATH.
|
|
340
628
|
// Must run before the autotest config step below, which seeds config.yaml
|
|
341
629
|
// into the installed tools dir (the location agents read via env.TOOL_PATH).
|
|
@@ -369,9 +657,13 @@ export async function initCommand(options = {}) {
|
|
|
369
657
|
console.log('');
|
|
370
658
|
const editorsToSetup = editors.filter((e) => selectedEditors.includes(e.name));
|
|
371
659
|
const result = { configured: [], skipped: [], errors: [] };
|
|
660
|
+
// hmos_sdk_dir consumed by incremental-ui-align = <DEVECO_SDK_HOME>/default.
|
|
661
|
+
const hmosSdkDir = config.env.DEVECO_SDK_HOME
|
|
662
|
+
? path.join(config.env.DEVECO_SDK_HOME, 'default')
|
|
663
|
+
: '';
|
|
372
664
|
for (const editor of editorsToSetup) {
|
|
373
665
|
console.log(chalk.blue(` Configuring ${editor.name}...`));
|
|
374
|
-
await installForEditor(editor, skillsRoot, agentsRoot, result);
|
|
666
|
+
await installForEditor(editor, skillsRoot, agentsRoot, config.env.GLM_API_KEY, hmosSdkDir, result);
|
|
375
667
|
}
|
|
376
668
|
await setupMcpForAllEditors(editorsToSetup, result);
|
|
377
669
|
console.log('');
|
package/dist/cli/mcp-setup.js
CHANGED
|
@@ -227,12 +227,8 @@ async function writeTomlSection(editor, result) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
function prettyConfigPath(p) {
|
|
230
|
-
// 与 init.ts 中 prettyHome
|
|
231
|
-
|
|
232
|
-
if (home && p.startsWith(home)) {
|
|
233
|
-
return '~' + p.slice(home.length).replace(/\\/g, '/');
|
|
234
|
-
}
|
|
235
|
-
return p.replace(/\\/g, '/');
|
|
230
|
+
// 与 init.ts 中 prettyHome 行为一致:完整绝对路径、OS 原生分隔符,不做 ~ 缩写。
|
|
231
|
+
return p;
|
|
236
232
|
}
|
|
237
233
|
async function setupOneEditor(editor, result) {
|
|
238
234
|
const marker = expandHome(editor.markerDir);
|
package/dist/context/index.js
CHANGED
|
@@ -13,6 +13,29 @@ import {
|
|
|
13
13
|
import fs from "node:fs/promises";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
16
|
+
function deriveSdkPaths(devecoSdkHome) {
|
|
17
|
+
const home = devecoSdkHome.trim().replace(/[\\/]+$/, "");
|
|
18
|
+
if (!home) {
|
|
19
|
+
return { DEVECO_SDK_HOME: "", DEVECO_PATH: "", OHOS_SDK_PATH: "", HMS_SDK_PATH: "" };
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
DEVECO_SDK_HOME: home,
|
|
23
|
+
DEVECO_PATH: path.dirname(home),
|
|
24
|
+
OHOS_SDK_PATH: path.join(home, "default", "openharmony", "ets"),
|
|
25
|
+
HMS_SDK_PATH: path.join(home, "default", "hms", "ets")
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function sdkHomeFromLegacyPaths(env) {
|
|
29
|
+
const tryStrip = (p, suffix) => {
|
|
30
|
+
if (!p) return "";
|
|
31
|
+
const norm = p.replace(/[\\/]+$/, "").split(/[\\/]/);
|
|
32
|
+
if (norm.length <= suffix.length) return "";
|
|
33
|
+
const tail = norm.slice(-suffix.length).map((s) => s.toLowerCase());
|
|
34
|
+
if (tail.join("/") !== suffix.join("/")) return "";
|
|
35
|
+
return norm.slice(0, -suffix.length).join(path.sep);
|
|
36
|
+
};
|
|
37
|
+
return tryStrip(env.OHOS_SDK_PATH, ["default", "openharmony", "ets"]) || tryStrip(env.HMS_SDK_PATH, ["default", "hms", "ets"]);
|
|
38
|
+
}
|
|
16
39
|
function getConfigDir() {
|
|
17
40
|
return path.join(os.homedir(), ".hometrans");
|
|
18
41
|
}
|
|
@@ -99,9 +122,12 @@ async function fileExists(p) {
|
|
|
99
122
|
}
|
|
100
123
|
function defaultEnv() {
|
|
101
124
|
return {
|
|
125
|
+
DEVECO_SDK_HOME: "",
|
|
126
|
+
DEVECO_PATH: "",
|
|
102
127
|
OHOS_SDK_PATH: "",
|
|
103
128
|
HMS_SDK_PATH: "",
|
|
104
129
|
TEST_API_KEY: "",
|
|
130
|
+
GLM_API_KEY: "",
|
|
105
131
|
TOOL_PATH: ""
|
|
106
132
|
};
|
|
107
133
|
}
|
|
@@ -145,6 +171,26 @@ async function loadHomeTransConfig() {
|
|
|
145
171
|
delete anyParsed.sdkPaths;
|
|
146
172
|
delete anyParsed.params;
|
|
147
173
|
delete anyParsed.tool_path;
|
|
174
|
+
let envDirty = false;
|
|
175
|
+
if (!config.env.DEVECO_SDK_HOME) {
|
|
176
|
+
const migrated = sdkHomeFromLegacyPaths(config.env);
|
|
177
|
+
if (migrated) {
|
|
178
|
+
config.env.DEVECO_SDK_HOME = migrated;
|
|
179
|
+
envDirty = true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (config.env.DEVECO_SDK_HOME) {
|
|
183
|
+
const derived = deriveSdkPaths(config.env.DEVECO_SDK_HOME);
|
|
184
|
+
for (const key of ["DEVECO_PATH", "OHOS_SDK_PATH", "HMS_SDK_PATH"]) {
|
|
185
|
+
if (!config.env[key]) {
|
|
186
|
+
config.env[key] = derived[key];
|
|
187
|
+
envDirty = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (envDirty) {
|
|
192
|
+
await saveHomeTransConfig(config);
|
|
193
|
+
}
|
|
148
194
|
const existingNames = new Set(config.editors.map((e) => e.name));
|
|
149
195
|
const missingEditors = defaultEditors().filter(
|
|
150
196
|
(e) => !existingNames.has(e.name)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buaa_smat/hometrans",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "HomeTrans (Android-to-HarmonyOS) skill + agent installer. Run `ht init` to distribute conversion skills and subagents into AI editors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
Binary file
|
|
Binary file
|
|
@@ -17,12 +17,17 @@ Parse `$ARGUMENTS` as positional tokens:
|
|
|
17
17
|
|
|
18
18
|
- **Arg 1** (`android-project-path`): Path to the Android source project (required)
|
|
19
19
|
- **Arg 2** (`harmonyos-project-path`): Path to the target HarmonyOS project directory (required)
|
|
20
|
-
- **Arg 3** (`
|
|
21
|
-
- **Arg 4** (`
|
|
22
|
-
- **Arg 5** (`
|
|
23
|
-
- **Arg 6** (`
|
|
20
|
+
- **Arg 3** (`spec-file-path`): Path to the requirement spec document (required). This is the plan/spec that drives Stage 1 logic development and Stage 3 code review — both stages read it directly from this path.
|
|
21
|
+
- **Arg 4** (`assets-output-path`): Directory to store all output/report files (optional). When omitted, default to `<harmonyos-project-path>/.hometrans_output` — create the directory if it does not exist.
|
|
22
|
+
- **Arg 5** (`test-case-path`): Path to the self-test case file driving the Stage 4 self-testing loop (optional). When omitted, default to `OUTPUT/test_case.md`. If the resolved file does not exist, the Stage 4 loop is skipped (see Loop Setup).
|
|
23
|
+
- **Arg 6** (`pre-test-case-path`): Path to the pre-test case file (optional). When omitted, default to `OUTPUT/pre_test_case.md`. Only passed to the self-tester when the resolved file exists.
|
|
24
|
+
- **Arg 7** (`max-rounds-review`): Maximum number of Stage 3→3a→3b code-review-fix rounds to run (optional, default `2`). Must be a positive integer `>= 1`.
|
|
25
|
+
- **Arg 8** (`max-rounds-test`): Maximum number of Stage 4→4a→4b self-test rounds to run (optional, default `2`). Must be a positive integer `>= 1`.
|
|
26
|
+
- **Arg 9** (`skip-test`): `true` or `false` (optional, default `false`). When `true`, skip Stage 4 / 4a / 4b (Self-Testing Loop) entirely. Use this when no real HarmonyOS device is available for on-device testing.
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Args are positional: to pass any optional arg, provide explicit values for all optional args before it (e.g., passing `test-case-path` requires an explicit `assets-output-path`; passing `skip-test` requires explicit Args 4–8).
|
|
29
|
+
|
|
30
|
+
If any required argument is missing, ask the user before proceeding. If `spec-file-path` does not exist or is not a readable file, ask the user before proceeding. If `max-rounds-review` or `max-rounds-test` is provided but is not a positive integer, ask the user before proceeding. If `skip-test` is provided but is not `true` or `false`, ask the user before proceeding.
|
|
26
31
|
|
|
27
32
|
Define shorthand variables for the instructions below:
|
|
28
33
|
|
|
@@ -30,7 +35,10 @@ Define shorthand variables for the instructions below:
|
|
|
30
35
|
|----------|---------|
|
|
31
36
|
| `ANDROID` | android-project-path |
|
|
32
37
|
| `HMOS` | harmonyos-project-path |
|
|
33
|
-
| `
|
|
38
|
+
| `SPEC` | spec-file-path — requirement spec document, consumed directly by Stage 1 (`spec-file`) and Stage 3 (`test-case-path`) |
|
|
39
|
+
| `OUTPUT` | assets-output-path; defaults to `HMOS/.hometrans_output` when not provided (created on demand) |
|
|
40
|
+
| `TEST_CASE` | test-case-path; defaults to `OUTPUT/test_case.md` when not provided |
|
|
41
|
+
| `PRE_TEST_CASE` | pre-test-case-path; defaults to `OUTPUT/pre_test_case.md` when not provided |
|
|
34
42
|
| `MAX_ROUNDS_REVIEW` | max-rounds-review — positive integer (default `2`). Controls Stage 3→3a→3b code-review-fix loop. |
|
|
35
43
|
| `MAX_ROUNDS_TEST` | max-rounds-test — positive integer (default `2`). Controls Stage 4→4a→4b self-test loop. |
|
|
36
44
|
| `SKIP_TEST` | skip-test — `true` or `false` (default `false`). When `true`, skip Stage 4 / 4a / 4b entirely (no real device available). |
|
|
@@ -147,7 +155,7 @@ Prompt format (applies to both Stage 1 and Stage 1a): ONLY the key-value lines b
|
|
|
147
155
|
```
|
|
148
156
|
Agent(
|
|
149
157
|
subagent_type="logic-context-builder",
|
|
150
|
-
prompt="harmonyos-project-path: HMOS\nspec-file:
|
|
158
|
+
prompt="harmonyos-project-path: HMOS\nspec-file: SPEC\noutput-path: OUTPUT/logic"
|
|
151
159
|
)
|
|
152
160
|
```
|
|
153
161
|
2. Verify `OUTPUT/logic/plan.md` exists.
|
|
@@ -200,7 +208,7 @@ For each `review_round` from `1..MAX_ROUNDS_REVIEW`, set `REVIEW_ROUND_DIR = OUT
|
|
|
200
208
|
- `harmonyos-project-path`: `HMOS`
|
|
201
209
|
- `commit-id`: For Round 1, use `REVIEW_COMMIT_ID`. For Round 2+, review the project holistically (omit `commit-id` or pass `none`) since fixes have modified the codebase beyond the original commit scope.
|
|
202
210
|
- `output-path`: `REVIEW_ROUND_DIR`
|
|
203
|
-
- `test-case-path`: `
|
|
211
|
+
- `test-case-path`: `SPEC`
|
|
204
212
|
3. If a valid `commit-id` is available, the agent will automatically call the `extract_commit_context` MCP tool to extract commit-scoped code context before reviewing. If the MCP call fails, the agent falls back to direct git-diff analysis. If no `commit-id` is available, the agent reviews the project holistically without commit-scoped extraction.
|
|
205
213
|
4. The agent writes `REVIEW_ROUND_DIR/code-review-report.md` with per-scenario verdicts.
|
|
206
214
|
5. **Extract defect stats**: Read `REVIEW_ROUND_DIR/code-review-report.md`, extract the verdict breakdown (PASS/PARTIAL/FAIL/UNABLE TO VERIFY counts) and overall verdict.
|
|
@@ -277,7 +285,7 @@ Treat Stage 4 → 4a → 4b as a loop that runs up to `MAX_ROUNDS_TEST` times.
|
|
|
277
285
|
|
|
278
286
|
1. **Locate the initial HAP file**: Use the `.hap` mirrored to `OUTPUT/` by the Stage 3 review loop finalization (or from Stage 2 if the review loop did not produce one). The default location is `OUTPUT/entry-default-signed.hap`. If that file does not exist, search for `*-signed.hap` files in `OUTPUT/` and use the first match. If no `.hap` is found in `OUTPUT/`, fall back to searching `HMOS/entry/build/default/outputs/default/`.
|
|
279
287
|
2. Store the resolved path as `CURRENT_HAP`.
|
|
280
|
-
3. **`
|
|
288
|
+
3. **`TEST_CASE` existence guard**: If `TEST_CASE` does not exist, mark Stage 4 / 4a / 4b as failed with note "No test case file available" and skip the loop entirely.
|
|
281
289
|
4. Initialize loop state:
|
|
282
290
|
- `round = 1`
|
|
283
291
|
- `rounds_executed = 0`
|
|
@@ -297,8 +305,8 @@ For each `round` from `1..MAX_ROUNDS_TEST`, set `ROUND_DIR = OUTPUT/round-{round
|
|
|
297
305
|
3. Input (round 1):
|
|
298
306
|
- `hap-path`: `CURRENT_HAP`
|
|
299
307
|
- `output-path`: `OUTPUT` (the ROOT — the agent always writes here)
|
|
300
|
-
- `test-case-path`: `
|
|
301
|
-
- `pre-test-case-path`: `
|
|
308
|
+
- `test-case-path`: `TEST_CASE`
|
|
309
|
+
- `pre-test-case-path`: `PRE_TEST_CASE` (only include this line when the file exists)
|
|
302
310
|
- `setup`: `true`
|
|
303
311
|
4. Input (round 2+):
|
|
304
312
|
- `hap-path`: `CURRENT_HAP`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hmos-fix-build-errors
|
|
3
3
|
description: Build a HarmonyOS project via CLI and automatically fix compile errors in a loop until the build succeeds. Default unsigned HAP; pass --signed to build a signed HAP (signing config must already exist in the project's build-profile.json5).
|
|
4
|
-
argument-hint: <harmonyos-project-path>
|
|
4
|
+
argument-hint: <harmonyos-project-path> [deveco-studio-path] [--signed]
|
|
5
5
|
allowed-tools: Agent, Read, Write, Edit, Glob, Grep, Bash
|
|
6
6
|
type: tool
|
|
7
7
|
domain: engineering
|
|
@@ -12,8 +12,8 @@ domain: engineering
|
|
|
12
12
|
Automatically build a HarmonyOS NEXT project from the command line, parse compile errors, fix them, and retry — repeating until the build succeeds.
|
|
13
13
|
|
|
14
14
|
- **HarmonyOS Project**: `$ARGUMENTS[0]` (the HarmonyOS project root, e.g. `D:/MyHmosApp`)
|
|
15
|
-
- **DevEco Studio Path
|
|
16
|
-
- **--signed** (optional):
|
|
15
|
+
- **DevEco Studio Path** (optional): `$ARGUMENTS[1]` (DevEco Studio installation root, e.g. `D:/DevEco Studio`) — when omitted, resolved automatically (see Step 0.2)
|
|
16
|
+
- **--signed** (optional): if any argument equals `--signed`, the build produces a **signed HAP**. If omitted, the build produces an **unsigned HAP** (default).
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -21,12 +21,20 @@ Automatically build a HarmonyOS NEXT project from the command line, parse compil
|
|
|
21
21
|
|
|
22
22
|
1. **Verify project exists** — Check that `$ARGUMENTS[0]` contains a valid HarmonyOS project (look for `build-profile.json5`, `entry/src` directory, `oh-package.json5`).
|
|
23
23
|
|
|
24
|
-
2. **
|
|
24
|
+
2. **Resolve `<deveco-path>`** — stop at the first source that yields a valid path:
|
|
25
|
+
1. `$ARGUMENTS[1]` if provided (and not `--signed`).
|
|
26
|
+
2. `~/.hometrans/config.json` (on Windows `%USERPROFILE%\.hometrans\config.json`): `env.DEVECO_PATH`; else parent dir of `env.DEVECO_SDK_HOME`; else strip the trailing `/sdk/default/openharmony/ets` from `env.OHOS_SDK_PATH` (legacy).
|
|
27
|
+
3. Environment variables, same precedence: `DEVECO_PATH` → parent of `DEVECO_SDK_HOME` → strip suffix from `OHOS_SDK_PATH`.
|
|
28
|
+
4. **Ask the user** for the DevEco Studio install path (suggest running `ht init` to persist it).
|
|
29
|
+
|
|
30
|
+
**Verify the resolved `<deveco-path>`** contains:
|
|
25
31
|
- `tools/node/node.exe`
|
|
26
32
|
- `tools/hvigor/bin/hvigorw.js`
|
|
27
33
|
- `tools/ohpm/bin/ohpm`
|
|
28
34
|
- `sdk/` directory
|
|
29
35
|
|
|
36
|
+
If verification fails for a config/env-sourced path, treat that source as invalid and continue down the list; if it fails for a user-supplied path, report what is missing and ask again.
|
|
37
|
+
|
|
30
38
|
3. **Set up `local.properties`** — Ensure the project root has `local.properties` with:
|
|
31
39
|
```properties
|
|
32
40
|
hwsdk.dir=<deveco-path>/sdk
|
|
@@ -40,7 +48,7 @@ Automatically build a HarmonyOS NEXT project from the command line, parse compil
|
|
|
40
48
|
"<deveco-path>/tools/ohpm/bin/ohpm" install
|
|
41
49
|
```
|
|
42
50
|
|
|
43
|
-
5. **Determine Build Mode** — Check if `$ARGUMENTS[2]`
|
|
51
|
+
5. **Determine Build Mode** — Check if any argument equals `--signed` (it may be `$ARGUMENTS[1]` when the DevEco path is omitted, or `$ARGUMENTS[2]` when it is provided):
|
|
44
52
|
- **If NOT `--signed`** → **Unsigned build mode**. Ensure `build-profile.json5` does NOT have `signingConfigs` or `signingConfig` references in products (remove them if present). Go to Step 1.
|
|
45
53
|
- **If `--signed`** → **Signed build mode**. Proceed to Step 0.5 to validate signing config.
|
|
46
54
|
|