@harmonyos-arkts/opencode-plugin 0.0.7 → 0.0.8

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/dist/index.js CHANGED
@@ -14836,7 +14836,9 @@ function scheduleFlush() {
14836
14836
  }
14837
14837
  function log(message, data) {
14838
14838
  try {
14839
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
14839
+ const now = /* @__PURE__ */ new Date();
14840
+ const pad = (n) => String(n).padStart(2, "0");
14841
+ const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
14840
14842
  const logEntry = `[${timestamp}] ${message} ${data ? JSON.stringify(data) : ""}
14841
14843
  `;
14842
14844
  buffer.push(logEntry);
@@ -14860,7 +14862,7 @@ var HM_DEVELOP = `
14860
14862
  ### Development Phase
14861
14863
  - Use the \`harmonyos-atomic-dev\` skill to implement features according to the design document for harmonyos atomic service
14862
14864
  - Use the \`harmonyos-atomic-dev\` skill to fix issues and modify function in current projects
14863
- - Use the createHmTemplate tool to create empty applicaiton\u3001module\u3001atomic. After creation, use parallel Read tool calls to read the template's README.md and key files of the current project simultaneously \u2014 do NOT use glob/grep to explore the template.
14865
+ - Use the createHmTemplate tool to create empty applicaiton\u3001module\u3001atomic.
14864
14866
  - Use the \`.harmonyos/\` file (*.md\u3001*.html) as references to generate
14865
14867
  - After completing development, you MUST proceed to the Build phase
14866
14868
  - **NO EXCEPTIONS**: Code must be built successfully before marking development as complete
@@ -15023,8 +15025,8 @@ ${HM_BUILD}
15023
15025
 
15024
15026
  Follow these workflows:
15025
15027
  - **New project (greenfield)**: PRD Design \u2192 Development \u2192 Build.
15026
- - **Incremental development (complex)**: Use Plan Agent to list a plan \u2192 Discuss plan details with the user \u2192 Development \u2192 Build.
15027
- - **Incremental development (simple)**: Development \u2192 Build directly.
15028
+ - **Complex tasks**: List a plan \u2192 Discuss plan details with the user if needed \u2192 Development \u2192 Build.
15029
+ - **Simple tasks**: Development \u2192 Build directly.
15028
15030
  `;
15029
15031
  }
15030
15032
  function buildHmAgentPrompt(type) {
@@ -15169,7 +15171,11 @@ function createHmAgent() {
15169
15171
  prompt: buildHmAgentPrompt("all"),
15170
15172
  temperature: 0.3,
15171
15173
  permission: {
15172
- "skillSearch": "deny"
15174
+ "*": "allow",
15175
+ doom_loop: "ask",
15176
+ external_directory: {
15177
+ "*": "ask"
15178
+ }
15173
15179
  },
15174
15180
  metadata: void 0
15175
15181
  };
@@ -15425,7 +15431,7 @@ function createConfigHandler(_pluginConfig, agent, _projectDir) {
15425
15431
  template: "/harmony-development-env-check",
15426
15432
  description: "\u68C0\u67E5 HarmonyOS \u5F00\u53D1\u73AF\u5883\u914D\u7F6E\uFF08Node.js\u3001JDK\u3001ohpm\u3001hvigor\u3001DevEco SDK\uFF09"
15427
15433
  };
15428
- log("Config merged", config3.agent);
15434
+ log("Config merged");
15429
15435
  };
15430
15436
  }
15431
15437
 
@@ -28138,23 +28144,20 @@ function createHmTemplateTool(managers) {
28138
28144
  });
28139
28145
  }
28140
28146
 
28141
- // src/tools/skill-search/skill-search-tool.ts
28142
- import { homedir as homedir2 } from "node:os";
28143
- import { join as join4 } from "node:path";
28144
-
28145
28147
  // src/tools/skill-search/search-skill.ts
28146
- import { readFileSync } from "node:fs";
28147
- import { join as join3 } from "node:path";
28148
+ import { readFileSync, existsSync } from "node:fs";
28149
+ import { join as join4 } from "node:path";
28148
28150
 
28149
28151
  // src/tools/skill-search/tokenizer.ts
28152
+ var CJK_RE = /[一-鿿㐀-䶿]/;
28153
+ var SEG_RE = /[一-鿿㐀-䶿]+|[\w@.]+/g;
28154
+ var CAMEL_RE = /[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\b)|[0-9]+/g;
28150
28155
  var segmenter = new Intl.Segmenter("zh", { granularity: "word" });
28151
- var SEG_RE = /[一-鿿㐀-䶿]+|[^\s\p{P}一-鿿㐀-䶿]+/gu;
28152
- var CAMEL_RE = /[A-Z]?[a-z]+|[A-Z]+(?![a-z])/g;
28153
28156
  function tokenize(text) {
28154
28157
  const segments = text.match(SEG_RE) ?? [];
28155
28158
  const tokens = [];
28156
28159
  for (const seg of segments) {
28157
- if (/[一-鿿㐀-䶿]/.test(seg)) {
28160
+ if (CJK_RE.test(seg)) {
28158
28161
  for (const s of segmenter.segment(seg)) {
28159
28162
  if (s.isWordLike) tokens.push(s.segment.toLowerCase().trim());
28160
28163
  }
@@ -28170,7 +28173,7 @@ function tokenize(text) {
28170
28173
  }
28171
28174
  }
28172
28175
  }
28173
- return tokens.filter((w) => w.length > 0);
28176
+ return [...new Set(tokens.filter((w) => w.length > 0))];
28174
28177
  }
28175
28178
 
28176
28179
  // src/tools/skill-search/bm25.ts
@@ -28325,13 +28328,198 @@ var filter_default = [
28325
28328
  { id: "25", summary: "WaterFlow \u7011\u5E03\u6D41\u5E03\u5C40\u5F00\u53D1\u5B9E\u8DF5", path: "experience/experience_water_flow.md", category: "water_flow" }
28326
28329
  ];
28327
28330
 
28331
+ // src/tools/skill-search/build-sdk-index.ts
28332
+ import { readdir as readdir2, readFile as readFile3, writeFile } from "node:fs/promises";
28333
+ import { join as join3, relative, basename, dirname } from "node:path";
28334
+ var SCAN_DIRS = [
28335
+ "openharmony/js/api",
28336
+ "openharmony/ets/api",
28337
+ "openharmony/ets/component",
28338
+ "openharmony/ets/kits",
28339
+ "hms/ets/api",
28340
+ "hms/ets/kits"
28341
+ ];
28342
+ var TS_EXT_RE = /\.(?:d\.ts|d\.ets|ts|ets)$/;
28343
+ async function scanFiles(dir) {
28344
+ const results = [];
28345
+ async function walk(current) {
28346
+ const entries = await readdir2(current, { withFileTypes: true });
28347
+ for (const entry of entries) {
28348
+ const full = join3(current, entry.name);
28349
+ if (entry.isDirectory()) {
28350
+ await walk(full);
28351
+ } else if (TS_EXT_RE.test(entry.name)) {
28352
+ results.push(full);
28353
+ }
28354
+ }
28355
+ }
28356
+ await walk(dir);
28357
+ return results;
28358
+ }
28359
+ function extractSummary(content, filename, relPath) {
28360
+ const rawName = filename.replace(/\.(?:d\.ts|d\.ets|ts|ets)$/, "");
28361
+ let moduleName;
28362
+ if (rawName.startsWith("@")) {
28363
+ moduleName = rawName.replace(/^@/, "");
28364
+ } else {
28365
+ const dirParts = dirname(relPath).split("/").filter(Boolean);
28366
+ moduleName = [...dirParts, rawName].join(".");
28367
+ }
28368
+ const symbols = [];
28369
+ const stripped = content.replace(/\/\*\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
28370
+ const patterns = [
28371
+ // Prefixed at file/module level
28372
+ /declare\s+namespace\s+(\w+)/g,
28373
+ /export\s+(?:default\s+)?interface\s+(\w+)/g,
28374
+ /export\s+(?:default\s+)?class\s+(\w+)/g,
28375
+ /export\s+(?:default\s+)?function\s+(\w+)/g,
28376
+ /export\s+(?:default\s+)?const\s+(\w+)/g,
28377
+ /export\s+(?:default\s+)?type\s+(\w+)/g,
28378
+ /export\s+(?:default\s+)?enum\s+(\w+)/g,
28379
+ /export\s+module\s+(\w+)/g,
28380
+ /declare\s+function\s+(\w+)/g,
28381
+ /declare\s+class\s+(\w+)/g,
28382
+ /declare\s+interface\s+(\w+)/g,
28383
+ /declare\s+const\s+(\w+)/g,
28384
+ /declare\s+type\s+(\w+)/g,
28385
+ /declare\s+enum\s+(\w+)/g,
28386
+ // Bare declarations inside declare namespace / module blocks
28387
+ /\bfunction\s+(\w+)\s*\(/g,
28388
+ /\binterface\s+(\w+)\s*(?:\{|extends)/g,
28389
+ /\benum\s+(\w+)\s*\{/g,
28390
+ /\bclass\s+(\w+)\s*(?:\{|extends|implements)/g,
28391
+ /\btype\s+(\w+)\s*=/g
28392
+ ];
28393
+ for (const pat of patterns) {
28394
+ let m;
28395
+ while ((m = pat.exec(stripped)) !== null) {
28396
+ if (!symbols.includes(m[1])) {
28397
+ symbols.push(m[1]);
28398
+ }
28399
+ }
28400
+ }
28401
+ const importNamedRe = /import\s+\{([^}]+)\}\s+from\s+/g;
28402
+ let mImport;
28403
+ while ((mImport = importNamedRe.exec(stripped)) !== null) {
28404
+ for (const name of mImport[1].split(",")) {
28405
+ const clean = name.trim().split(/\s+as\s+/)[0].trim();
28406
+ if (clean && !symbols.includes(clean)) {
28407
+ symbols.push(clean);
28408
+ }
28409
+ }
28410
+ }
28411
+ const importDefaultRe = /import\s+(\w+)\s+from\s+/g;
28412
+ let mDef;
28413
+ while ((mDef = importDefaultRe.exec(stripped)) !== null) {
28414
+ const name = mDef[1];
28415
+ if (!symbols.includes(name)) {
28416
+ symbols.push(name);
28417
+ }
28418
+ }
28419
+ const descMatch = content.match(
28420
+ /\*\s*(?:@description\s+)?(Provides[^.]*\.)/s
28421
+ );
28422
+ let providesDesc = "";
28423
+ if (descMatch) {
28424
+ providesDesc = descMatch[1].replace(/\*\s*/g, "").replace(/\n/g, " ").trim().slice(0, 200);
28425
+ }
28426
+ if (!providesDesc) {
28427
+ const gDesc = content.match(
28428
+ /\*\s*@description\s+([\s\S]*?)\s*\*\s*\//s
28429
+ );
28430
+ if (gDesc) {
28431
+ providesDesc = gDesc[1].replace(/\*\s*/g, "").replace(/\n/g, " ").trim().slice(0, 200);
28432
+ }
28433
+ }
28434
+ const kitMatch = content.match(/\*\s*@kit\s+(\w+)/);
28435
+ const kit = kitMatch ? kitMatch[1] : "";
28436
+ const syscapMatch = content.match(
28437
+ /\*\s*@syscap\s+SystemCapability\.([\w.]+)/
28438
+ );
28439
+ const syscap = syscapMatch ? syscapMatch[1] : "";
28440
+ const parts = [moduleName];
28441
+ if (kit && !moduleName.includes(kit)) parts.push(`Kit:${kit}`);
28442
+ if (syscap) parts.push(syscap);
28443
+ if (providesDesc) parts.push(providesDesc);
28444
+ if (symbols.length > 0) {
28445
+ parts.push(symbols.slice(0, 20).join(", "));
28446
+ }
28447
+ return {
28448
+ summary: parts.join(" | "),
28449
+ moduleName,
28450
+ symbols: symbols.slice(0, 50)
28451
+ };
28452
+ }
28453
+ async function buildSDKIndex(sdk_path) {
28454
+ let allFiles = [];
28455
+ for (const dir of SCAN_DIRS) {
28456
+ const fullDir = join3(sdk_path, "default", dir);
28457
+ log(`Scanning ${dir} ...`);
28458
+ const files = await scanFiles(fullDir);
28459
+ log(` Found ${files.length} files`);
28460
+ for (const f of files) {
28461
+ allFiles.push({
28462
+ absPath: f,
28463
+ relPath: relative(fullDir, f),
28464
+ sourceDir: dir
28465
+ });
28466
+ }
28467
+ }
28468
+ log(`Total files: ${allFiles.length}`);
28469
+ const entries = [];
28470
+ for (let i = 0; i < allFiles.length; i++) {
28471
+ const { absPath, relPath, sourceDir } = allFiles[i];
28472
+ const filename = basename(absPath);
28473
+ try {
28474
+ const content = await readFile3(absPath, "utf-8");
28475
+ const { summary, moduleName, symbols } = extractSummary(
28476
+ content,
28477
+ filename,
28478
+ relPath
28479
+ );
28480
+ entries.push({
28481
+ id: String(i + 1),
28482
+ summary,
28483
+ path: absPath,
28484
+ moduleName,
28485
+ symbols
28486
+ });
28487
+ } catch {
28488
+ }
28489
+ if ((i + 1) % 500 === 0) {
28490
+ log(`Processed ${i + 1}/${allFiles.length}`);
28491
+ }
28492
+ }
28493
+ const outputPath = join3(import.meta.dirname, "sdk-index.json");
28494
+ await writeFile(outputPath, JSON.stringify(entries, null, 2), "utf-8");
28495
+ log(`Done! Wrote ${entries.length} entries to sdk-index.json`);
28496
+ log(
28497
+ `Sample entries:`,
28498
+ entries.slice(0, 5).map((e) => e.moduleName)
28499
+ );
28500
+ }
28501
+
28328
28502
  // src/tools/skill-search/search-skill.ts
28329
28503
  var cachedEntries = null;
28330
28504
  var cachedIndexableEntries = null;
28331
28505
  var cachedFilters = null;
28332
28506
  var cachedRetriever = null;
28333
- function loadAndCache(resolvedPath) {
28334
- const raw = readFileSync(join3(resolvedPath, "index.json"), "utf-8");
28507
+ var cachedSdkEntries = null;
28508
+ var cachedSdkRetriever = null;
28509
+ async function loadAndCache(resolvedPath) {
28510
+ const sdk_path = findSdkDir();
28511
+ log("start load sdk cache", sdk_path);
28512
+ if (sdk_path !== void 0) {
28513
+ const sdkIndexPath = join4(import.meta.dirname, "sdk-index.json");
28514
+ if (!existsSync(sdkIndexPath)) {
28515
+ log("sdk path is not exist, need to build");
28516
+ await buildSDKIndex(sdk_path);
28517
+ }
28518
+ loadSdkIndex();
28519
+ log("success to load sdk cache");
28520
+ }
28521
+ log("start load skill cache");
28522
+ const raw = readFileSync(join4(resolvedPath, "index.json"), "utf-8");
28335
28523
  const entries = JSON.parse(raw);
28336
28524
  if (!Array.isArray(entries) || entries.length === 0) {
28337
28525
  return false;
@@ -28344,23 +28532,45 @@ function loadAndCache(resolvedPath) {
28344
28532
  const retriever = new HybridRetriever();
28345
28533
  retriever.addDocuments(documents, tokenizedDocs);
28346
28534
  cachedRetriever = retriever;
28535
+ log("success to load sdk cache");
28347
28536
  return true;
28348
28537
  }
28349
- function searchSkill(skill_path, query, topK = 5) {
28538
+ function loadSdkIndex() {
28539
+ if (cachedSdkRetriever) return true;
28540
+ const sdkIndexPath = join4(import.meta.dirname, "sdk-index.json");
28541
+ if (!existsSync(sdkIndexPath)) return false;
28542
+ try {
28543
+ const raw = readFileSync(sdkIndexPath, "utf-8");
28544
+ const entries = JSON.parse(raw);
28545
+ if (!Array.isArray(entries) || entries.length === 0) return false;
28546
+ cachedSdkEntries = entries;
28547
+ const documents = entries.map((e) => e.summary);
28548
+ const tokenizedDocs = documents.map((d) => tokenize(d));
28549
+ const retriever = new BM25Retriever();
28550
+ retriever.addDocuments(documents, tokenizedDocs);
28551
+ cachedSdkRetriever = retriever;
28552
+ return true;
28553
+ } catch {
28554
+ return false;
28555
+ }
28556
+ }
28557
+ async function searchSkill(skill_path, query, topK = 3) {
28350
28558
  if (!cachedEntries) {
28351
28559
  try {
28352
- if (!loadAndCache(skill_path?.trim())) return [];
28560
+ if (!await loadAndCache(skill_path?.trim())) return [];
28353
28561
  } catch {
28354
28562
  return [];
28355
28563
  }
28356
28564
  }
28565
+ const prefix = skill_path.replace(/[\/\\]+$/, "");
28357
28566
  const preFiltered = [];
28358
28567
  for (const [category, entry] of cachedFilters) {
28359
28568
  const idx = query.toLowerCase().indexOf(category);
28360
28569
  if (idx !== -1) {
28361
28570
  preFiltered.push({
28362
28571
  ets_file_path: "",
28363
- experience_file_path: entry.path
28572
+ experience_file_path: join4(prefix, entry.path),
28573
+ sdk_file_path: ""
28364
28574
  });
28365
28575
  query = (query.slice(0, idx) + query.slice(idx + category.length)).trim();
28366
28576
  }
@@ -28368,21 +28578,42 @@ function searchSkill(skill_path, query, topK = 5) {
28368
28578
  if (!query) {
28369
28579
  return preFiltered;
28370
28580
  }
28581
+ const MIN_SCORE = 0.01;
28582
+ const sdkResults = loadSdkIndex() ? cachedSdkRetriever.search(query, topK).filter((r) => r.score >= MIN_SCORE).map((r) => ({
28583
+ ets_file_path: "",
28584
+ experience_file_path: "",
28585
+ sdk_file_path: cachedSdkEntries[r.index].path
28586
+ })) : [];
28371
28587
  const searchResults = cachedRetriever.search(query, topK);
28372
- const MIN_SCORE = 2e-3;
28373
28588
  const filtered = searchResults.filter((r) => r.score >= MIN_SCORE);
28374
- if (filtered.length === 0) return preFiltered;
28375
- return [...preFiltered, ...convert_search_result(filtered)];
28589
+ const skillResults = filtered.length > 0 ? aggregateByExperience(convert_search_result(filtered, prefix)) : [];
28590
+ return [...preFiltered, ...skillResults, ...sdkResults];
28591
+ }
28592
+ function aggregateByExperience(results) {
28593
+ const map3 = /* @__PURE__ */ new Map();
28594
+ for (const r of results) {
28595
+ const key = r.experience_file_path || r.ets_file_path;
28596
+ const existing = map3.get(key);
28597
+ if (existing) {
28598
+ if (r.ets_file_path) {
28599
+ existing.ets_file_path = existing.ets_file_path ? existing.ets_file_path + "\n" + r.ets_file_path : r.ets_file_path;
28600
+ }
28601
+ } else {
28602
+ map3.set(key, { ...r });
28603
+ }
28604
+ }
28605
+ return [...map3.values()];
28376
28606
  }
28377
- function convert_search_result(searchResults) {
28607
+ function convert_search_result(searchResults, prefix) {
28378
28608
  return searchResults.map((result, rank) => {
28379
28609
  const entry = cachedIndexableEntries[result.index];
28380
28610
  const experience = cachedEntries.find(
28381
28611
  (e) => e.type === "experience" && e.category === entry.category
28382
28612
  );
28383
28613
  return {
28384
- ets_file_path: entry.path,
28385
- experience_file_path: experience?.path ?? ""
28614
+ ets_file_path: join4(prefix, entry.path),
28615
+ experience_file_path: experience?.path ? join4(prefix, experience.path) : "",
28616
+ sdk_file_path: ""
28386
28617
  };
28387
28618
  });
28388
28619
  }
@@ -28390,25 +28621,27 @@ function convert_search_result(searchResults) {
28390
28621
  // src/tools/skill-search/skill-search-tool.ts
28391
28622
  function skillSearchTool(managers) {
28392
28623
  return tool({
28393
- description: "Search for relevant documents within the harmonyos-atomic-dev skill directory by keywords. Returns the top K most relevant document snippets from the skill directory, ranked by keyword match frequency. Use this tool instead of Glob/Grep when you need to find specific knowledge or documentation within harmonyos-atomic-dev skill. It scans all files under the given skill_path and returns the most relevant matches based on your query keywords. The results include ets_file_path (path to the ETS code examples), experience_file_path (path to the matching experience document best practices), summary (document summary), and range (relevance rank 1 = most relevant). QUERY TIPS: For best results, decompose your intent into short space-separated keywords instead of long sentences. Include: 1) Kit or component name (e.g. ScanKit, AdsKit, ShareKit), 2) specific API or method names (e.g. scanBarcode, loadAd, ShareController), 3) feature keywords (e.g. \u626B\u7801, \u5E7F\u544A\u52A0\u8F7D, \u5206\u4EAB). Example: 'ScanKit \u626B\u7801 scanBarcode startScanForResult' instead of '\u5E2E\u6211\u5B9E\u73B0\u4E00\u4E2A\u626B\u7801\u529F\u80FD'.",
28624
+ description: "Search for relevant documents within the harmonyos-atomic-dev skill directory by keywords. Returns the top K most relevant document snippets from the skill directory, ranked by keyword match frequency. Use this tool instead of Glob/Grep when you need to find specific knowledge or documentation within harmonyos-atomic-dev skill. The results include experience_file_path (path to the matching experience document best practices) and ets_file_path (path to the ETS code examples and SDK API file). IMPORTANT: Each scenario only supports ONE tool call. Do NOT call this tool multiple times.",
28394
28625
  args: {
28395
- skill_path: tool.schema.string("Absolute path to the skill directory to search within. Default path may not contains harmonyos-atomic-dev skill").default(join4(homedir2(), ".config", "opencode", "skills", "harmonyos-atomic-dev")),
28396
- query: tool.schema.string("A decomposed requirement or intent describing what you want to find, broken down into searchable keywords separated by spaces."),
28397
- topK: tool.schema.number("Maximum number of top-ranked documents to return. Actual results may be fewer depending on query relevance.").min(1).max(5).default(5)
28626
+ skill_path: tool.schema.string("Absolute path to the harmonyos-atomic-dev skill directory. IMPORTANT: this path should not contain any prefix or suffix like 'file://' or 'SKILL.md'."),
28627
+ query: tool.schema.string("A decomposed requirement or intent describing what you want to find, broken down into searchable keywords separated by spaces. QUERY TIPS: For best results, decompose your intent into short space-separated keywords instead of long sentences. Include: 1) Kit or component name (e.g. ScanKit, AdsKit, ShareKit), 2) specific API or method names (e.g. scanBarcode, loadAd, ShareController), 3) feature keywords (e.g. \u626B\u7801, \u5E7F\u544A\u52A0\u8F7D, \u5206\u4EAB). Example: 'ScanKit \u626B\u7801 scanBarcode startScanForResult' instead of '\u5E2E\u6211\u5B9E\u73B0\u4E00\u4E2A\u626B\u7801\u529F\u80FD'. "),
28628
+ topK: tool.schema.number("Maximum number of top-ranked documents to return. Actual results may be fewer depending on query relevance.").min(1).max(3).default(3)
28398
28629
  },
28399
28630
  execute: async (args, context) => {
28400
28631
  try {
28401
- const results = searchSkill(args.skill_path, args.query, args.topK);
28632
+ const results = await searchSkill(args.skill_path, args.query, args.topK);
28402
28633
  if (results.length === 0) {
28403
28634
  return "No relative items found in the skill directory.";
28404
28635
  }
28405
- return results.map((r) => {
28406
- if (r.ets_file_path) {
28407
- return `${r.experience_file_path}
28408
- \u793A\u4F8B\u6587\u4EF6\uFF1A${r.ets_file_path}`;
28409
- }
28410
- return r.experience_file_path;
28636
+ const sdkInfo = results.map((r) => {
28637
+ const parts = [];
28638
+ if (r.experience_file_path) parts.push(`experience path ${r.experience_file_path}`);
28639
+ if (r.ets_file_path) parts.push(`sample code path ${r.ets_file_path}`);
28640
+ if (r.sdk_file_path) parts.push(`sdk info path ${r.sdk_file_path}`);
28641
+ return parts.join("\n");
28411
28642
  }).join("\n\n");
28643
+ return `You can read the following files as needed to obtain information.
28644
+ ${sdkInfo}`;
28412
28645
  } catch (e) {
28413
28646
  return `Error: ${e instanceof Error ? e.message : String(e)}`;
28414
28647
  }
@@ -28440,7 +28673,7 @@ function createTools(args) {
28440
28673
  }
28441
28674
 
28442
28675
  // src/hooks/tool-hooks.ts
28443
- import { readFile as readFile3 } from "fs/promises";
28676
+ import { readFile as readFile4 } from "fs/promises";
28444
28677
 
28445
28678
  // node_modules/diff/lib/index.mjs
28446
28679
  function Diff() {
@@ -29025,7 +29258,7 @@ function createToolHooks(sessionManager, projectDir) {
29025
29258
  if (input.tool === "write") {
29026
29259
  const filePath = output.args?.filePath;
29027
29260
  if (filePath) {
29028
- const oldContent = await readFile3(filePath, "utf-8").catch(() => "");
29261
+ const oldContent = await readFile4(filePath, "utf-8").catch(() => "");
29029
29262
  writeContentCache.set(input.callID, oldContent);
29030
29263
  }
29031
29264
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@harmonyos-arkts/opencode-plugin",
4
- "version": "0.0.7",
4
+ "version": "0.0.8",
5
5
  "description": "HarmonyOS Full-Lifecycle Development Assistant. Specialized in the complete development lifecycle of HarmonyOS applications, including project creation, UI development, state management, network requests, data storage, permission requests, performance optimization, testing, and release.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -1,52 +0,0 @@
1
- # HarmonyOS Application Template
2
-
3
- HarmonyOS 标准应用工程模板,基于 Stage 模型,适用于开发完整的 HarmonyOS 应用程序。
4
-
5
- ## 目录结构
6
-
7
- ```
8
- application/
9
- ├── AppScope/ # 应用全局配置
10
- │ ├── app.json5 # 应用包名、版本号、图标等全局信息
11
- │ └── resources/ # 应用级资源(图标、字符串)
12
- ├── entry/ # 主模块(Entry Module)
13
- │ ├── src/
14
- │ │ ├── main/
15
- │ │ │ ├── ets/
16
- │ │ │ │ ├── entryability/ # UIAbility 入口
17
- │ │ │ │ ├── entrybackupability/# 备份恢复 ExtensionAbility
18
- │ │ │ │ └── pages/ # 页面(Index.ets)
19
- │ │ │ ├── resources/ # 模块资源(字符串、颜色、图片、配置)
20
- │ │ │ └── module.json5 # 模块配置(Ability、页面路由)
21
- │ │ ├── ohosTest/ # 仪器化测试
22
- │ │ └── test/ # 本地单元测试
23
- │ ├── build-profile.json5 # 模块级构建配置
24
- │ ├── oh-package.json5 # 模块依赖声明
25
- │ └── hvigorfile.ts # Hvigor 构建脚本
26
- ├── hvigor/ # Hvigor 构建引擎配置
27
- ├── build-profile.json5 # 工程级构建配置(签名、SDK 版本、模块列表)
28
- ├── oh-package.json5 # 工程级依赖声明
29
- ├── hvigorfile.ts # 工程级 Hvigor 脚本
30
- └── code-linter.json5 # 代码检查配置
31
- ```
32
-
33
- ## 关键配置
34
-
35
- | 配置项 | 值 |
36
- |--------|-----|
37
- | bundleName | `com.example.app` |
38
- | module type | `entry` |
39
- | targetSdkVersion | `6.0.1(21)` |
40
- | runtimeOS | `HarmonyOS` |
41
- | installationFree | `false` |
42
-
43
- ## 使用场景
44
-
45
- - 创建一个新的 HarmonyOS 标准应用工程
46
- - 包含完整的 UIAbility 生命周期管理
47
- - 内置备份恢复能力(EntryBackupAbility)
48
- - 适合需要独立安装和分发的应用
49
-
50
- ## 使用方式
51
-
52
- 此模板由 `createHmTemplate` 工具自动下载和解压,作为 HarmonyOS 应用开发的初始工程骨架。
@@ -1,62 +0,0 @@
1
- # HarmonyOS Atomic Service Template
2
-
3
- HarmonyOS 元服务(Atomic Service)工程模板,基于 Stage 模型,适用于开发免安装的轻量化服务。
4
-
5
- ## 目录结构
6
-
7
- ```
8
- atomic/
9
- ├── AppScope/ # 应用全局配置
10
- │ ├── app.json5 # 应用包名、版本号(bundleType: "atomicService")
11
- │ └── resources/ # 应用级资源(图标、字符串)
12
- ├── entry/ # 主模块(Entry Module)
13
- │ ├── src/
14
- │ │ ├── main/
15
- │ │ │ ├── ets/
16
- │ │ │ │ ├── entryability/ # UIAbility 入口
17
- │ │ │ │ └── pages/ # 页面(Index.ets)
18
- │ │ │ ├── resources/ # 模块资源(字符串、颜色、图片、配置)
19
- │ │ │ └── module.json5 # 模块配置(Ability、页面路由)
20
- │ │ ├── ohosTest/ # 仪器化测试
21
- │ │ ├── test/ # 本地单元测试
22
- │ │ └── mock/ # Mock 配置
23
- │ ├── build-profile.json5 # 模块级构建配置
24
- │ ├── oh-package.json5 # 模块依赖声明
25
- │ └── hvigorfile.ts # Hvigor 构建脚本
26
- ├── hvigor/ # Hvigor 构建引擎配置
27
- ├── build-profile.json5 # 工程级构建配置(签名、SDK 版本、模块列表)
28
- ├── oh-package.json5 # 工程级依赖声明
29
- ├── hvigorfile.ts # 工程级 Hvigor 脚本
30
- └── code-linter.json5 # 代码检查配置
31
- ```
32
-
33
- ## 关键配置
34
-
35
- | 配置项 | 值 |
36
- |--------|-----|
37
- | bundleName | `com.atomicservice.example` |
38
- | bundleType | `atomicService` |
39
- | module type | `entry` |
40
- | targetSdkVersion | `6.0.1(21)` |
41
- | runtimeOS | `HarmonyOS` |
42
- | installationFree | `true` |
43
-
44
- ## 与 Application 模板的区别
45
-
46
- | 特性 | Application | Atomic Service |
47
- |------|-------------|----------------|
48
- | bundleType | 默认(app) | `atomicService` |
49
- | installationFree | `false` | `true` |
50
- | 备份能力 | 包含 EntryBackupAbility | 不包含 |
51
- | 图标资源 | layered_image | app_icon |
52
- | 免安装 | 不支持 | 支持 |
53
-
54
- ## 使用场景
55
-
56
- - 开发免安装的元服务(Atomic Service)
57
- - 适用于服务卡片、快捷服务、轻量级功能入口
58
- - 通过 HarmonyOS 服务分发平台进行分发
59
-
60
- ## 使用方式
61
-
62
- 此模板由 `createHmTemplate` 工具自动下载和解压,作为 HarmonyOS 元服务开发的初始工程骨架。
@@ -1,42 +0,0 @@
1
- # HarmonyOS Module Template
2
-
3
- HarmonyOS HAR(HarmonyOS Archive)库模块模板,用于开发可复用的共享库模块。
4
-
5
- ## 目录结构
6
-
7
- ```
8
- module/
9
- ├── src/
10
- │ ├── main/
11
- │ │ ├── ets/
12
- │ │ │ └── components/ # 导出的组件(MainPage.ets)
13
- │ │ ├── resources/ # 模块资源(字符串、浮点数)
14
- │ │ └── module.json5 # 模块配置(type: "har")
15
- │ ├── ohosTest/ # 仪器化测试
16
- │ └── test/ # 本地单元测试
17
- ├── Index.ets # 库模块导出入口
18
- ├── build-profile.json5 # 模块级构建配置
19
- ├── oh-package.json5 # 模块依赖声明
20
- ├── hvigorfile.ts # Hvigor 构建脚本
21
- ├── consumer-rules.txt # 混淆消费者规则
22
- └── obfuscation-rules.txt # 混淆规则
23
- ```
24
-
25
- ## 关键配置
26
-
27
- | 配置项 | 值 |
28
- |--------|-----|
29
- | module name | `library` |
30
- | module type | `har` |
31
- | deviceTypes | `["phone"]` |
32
- | apiType | `stageMode` |
33
-
34
- ## 使用场景
35
-
36
- - 创建一个可复用的 HAR 共享库
37
- - 封装通用组件、工具类或业务逻辑供多个模块引用
38
- - 通过 `oh-package.json5` 管理依赖,支持混淆配置
39
-
40
- ## 使用方式
41
-
42
- 此模板由 `createHmTemplate` 工具自动下载和解压,用于在现有 HarmonyOS 工程中添加新的 HAR 库模块。将模板内容复制到工程目录下并在 `build-profile.json5` 的 `modules` 中注册即可。