@harmonyos-arkts/opencode-plugin 0.0.7 → 0.0.9
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
|
@@ -14814,16 +14814,67 @@ await Promise.all([
|
|
|
14814
14814
|
|
|
14815
14815
|
// src/shared/log.ts
|
|
14816
14816
|
var logFile = path3.join(HMGlobal.Path.log, "plugin.log");
|
|
14817
|
+
var logDir = path3.dirname(logFile);
|
|
14818
|
+
var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
|
|
14819
|
+
var MAX_LOG_AGE_DAYS = 7;
|
|
14820
|
+
var MAX_ROTATED_FILES = 5;
|
|
14817
14821
|
var buffer = [];
|
|
14818
14822
|
var flushTimer = null;
|
|
14819
14823
|
var FLUSH_INTERVAL_MS = 500;
|
|
14820
14824
|
var BUFFER_SIZE_LIMIT = 50;
|
|
14825
|
+
function cleanupRotatedLogs() {
|
|
14826
|
+
try {
|
|
14827
|
+
const entries = fs2.readdirSync(logDir);
|
|
14828
|
+
const rotated = entries.filter((f) => f.startsWith("plugin.") && f.endsWith(".log") && f !== "plugin.log").map((f) => {
|
|
14829
|
+
const full = path3.join(logDir, f);
|
|
14830
|
+
try {
|
|
14831
|
+
return { file: full, mtimeMs: fs2.statSync(full).mtimeMs };
|
|
14832
|
+
} catch {
|
|
14833
|
+
return null;
|
|
14834
|
+
}
|
|
14835
|
+
}).filter((e) => e !== null);
|
|
14836
|
+
const now = Date.now();
|
|
14837
|
+
const maxAgeMs = MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1e3;
|
|
14838
|
+
for (const entry of rotated) {
|
|
14839
|
+
if (now - entry.mtimeMs > maxAgeMs) {
|
|
14840
|
+
try {
|
|
14841
|
+
fs2.unlinkSync(entry.file);
|
|
14842
|
+
} catch {
|
|
14843
|
+
}
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
const surviving = rotated.filter((e) => now - e.mtimeMs <= maxAgeMs).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
14847
|
+
if (surviving.length > MAX_ROTATED_FILES) {
|
|
14848
|
+
const doomed = surviving.slice(0, surviving.length - MAX_ROTATED_FILES);
|
|
14849
|
+
for (const entry of doomed) {
|
|
14850
|
+
try {
|
|
14851
|
+
fs2.unlinkSync(entry.file);
|
|
14852
|
+
} catch {
|
|
14853
|
+
}
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
} catch {
|
|
14857
|
+
}
|
|
14858
|
+
}
|
|
14859
|
+
function rotateIfNeeded() {
|
|
14860
|
+
try {
|
|
14861
|
+
const stat3 = fs2.statSync(logFile);
|
|
14862
|
+
if (stat3.size >= MAX_LOG_SIZE_BYTES) {
|
|
14863
|
+
const now = /* @__PURE__ */ new Date();
|
|
14864
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
14865
|
+
const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
14866
|
+
fs2.renameSync(logFile, path3.join(logDir, `plugin.${ts}.log`));
|
|
14867
|
+
}
|
|
14868
|
+
} catch {
|
|
14869
|
+
}
|
|
14870
|
+
}
|
|
14821
14871
|
function flush() {
|
|
14822
14872
|
if (buffer.length === 0) return;
|
|
14823
14873
|
const data = buffer.join("");
|
|
14824
14874
|
buffer = [];
|
|
14825
14875
|
try {
|
|
14826
14876
|
fs2.appendFileSync(logFile, data);
|
|
14877
|
+
rotateIfNeeded();
|
|
14827
14878
|
} catch {
|
|
14828
14879
|
}
|
|
14829
14880
|
}
|
|
@@ -14836,7 +14887,9 @@ function scheduleFlush() {
|
|
|
14836
14887
|
}
|
|
14837
14888
|
function log(message, data) {
|
|
14838
14889
|
try {
|
|
14839
|
-
const
|
|
14890
|
+
const now = /* @__PURE__ */ new Date();
|
|
14891
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
14892
|
+
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
14840
14893
|
const logEntry = `[${timestamp}] ${message} ${data ? JSON.stringify(data) : ""}
|
|
14841
14894
|
`;
|
|
14842
14895
|
buffer.push(logEntry);
|
|
@@ -14848,6 +14901,7 @@ function log(message, data) {
|
|
|
14848
14901
|
} catch {
|
|
14849
14902
|
}
|
|
14850
14903
|
}
|
|
14904
|
+
cleanupRotatedLogs();
|
|
14851
14905
|
|
|
14852
14906
|
// src/agents/prompt.ts
|
|
14853
14907
|
var HM_DESIGN = `
|
|
@@ -14860,7 +14914,7 @@ var HM_DEVELOP = `
|
|
|
14860
14914
|
### Development Phase
|
|
14861
14915
|
- Use the \`harmonyos-atomic-dev\` skill to implement features according to the design document for harmonyos atomic service
|
|
14862
14916
|
- 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.
|
|
14917
|
+
- Use the createHmTemplate tool to create empty applicaiton\u3001module\u3001atomic.
|
|
14864
14918
|
- Use the \`.harmonyos/\` file (*.md\u3001*.html) as references to generate
|
|
14865
14919
|
- After completing development, you MUST proceed to the Build phase
|
|
14866
14920
|
- **NO EXCEPTIONS**: Code must be built successfully before marking development as complete
|
|
@@ -15023,8 +15077,8 @@ ${HM_BUILD}
|
|
|
15023
15077
|
|
|
15024
15078
|
Follow these workflows:
|
|
15025
15079
|
- **New project (greenfield)**: PRD Design \u2192 Development \u2192 Build.
|
|
15026
|
-
- **
|
|
15027
|
-
- **
|
|
15080
|
+
- **Complex tasks**: List a plan \u2192 Discuss plan details with the user if needed \u2192 Development \u2192 Build.
|
|
15081
|
+
- **Simple tasks**: Development \u2192 Build directly.
|
|
15028
15082
|
`;
|
|
15029
15083
|
}
|
|
15030
15084
|
function buildHmAgentPrompt(type) {
|
|
@@ -15169,7 +15223,11 @@ function createHmAgent() {
|
|
|
15169
15223
|
prompt: buildHmAgentPrompt("all"),
|
|
15170
15224
|
temperature: 0.3,
|
|
15171
15225
|
permission: {
|
|
15172
|
-
"
|
|
15226
|
+
"*": "allow",
|
|
15227
|
+
doom_loop: "ask",
|
|
15228
|
+
external_directory: {
|
|
15229
|
+
"*": "ask"
|
|
15230
|
+
}
|
|
15173
15231
|
},
|
|
15174
15232
|
metadata: void 0
|
|
15175
15233
|
};
|
|
@@ -15425,7 +15483,7 @@ function createConfigHandler(_pluginConfig, agent, _projectDir) {
|
|
|
15425
15483
|
template: "/harmony-development-env-check",
|
|
15426
15484
|
description: "\u68C0\u67E5 HarmonyOS \u5F00\u53D1\u73AF\u5883\u914D\u7F6E\uFF08Node.js\u3001JDK\u3001ohpm\u3001hvigor\u3001DevEco SDK\uFF09"
|
|
15427
15485
|
};
|
|
15428
|
-
log("Config merged"
|
|
15486
|
+
log("Config merged");
|
|
15429
15487
|
};
|
|
15430
15488
|
}
|
|
15431
15489
|
|
|
@@ -28138,23 +28196,20 @@ function createHmTemplateTool(managers) {
|
|
|
28138
28196
|
});
|
|
28139
28197
|
}
|
|
28140
28198
|
|
|
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
28199
|
// src/tools/skill-search/search-skill.ts
|
|
28146
|
-
import { readFileSync } from "node:fs";
|
|
28147
|
-
import { join as
|
|
28200
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
28201
|
+
import { join as join4 } from "node:path";
|
|
28148
28202
|
|
|
28149
28203
|
// src/tools/skill-search/tokenizer.ts
|
|
28204
|
+
var CJK_RE = /[一-鿿㐀-䶿]/;
|
|
28205
|
+
var SEG_RE = /[一-鿿㐀-䶿]+|[\w@.]+/g;
|
|
28206
|
+
var CAMEL_RE = /[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\b)|[0-9]+/g;
|
|
28150
28207
|
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
28208
|
function tokenize(text) {
|
|
28154
28209
|
const segments = text.match(SEG_RE) ?? [];
|
|
28155
28210
|
const tokens = [];
|
|
28156
28211
|
for (const seg of segments) {
|
|
28157
|
-
if (
|
|
28212
|
+
if (CJK_RE.test(seg)) {
|
|
28158
28213
|
for (const s of segmenter.segment(seg)) {
|
|
28159
28214
|
if (s.isWordLike) tokens.push(s.segment.toLowerCase().trim());
|
|
28160
28215
|
}
|
|
@@ -28170,7 +28225,7 @@ function tokenize(text) {
|
|
|
28170
28225
|
}
|
|
28171
28226
|
}
|
|
28172
28227
|
}
|
|
28173
|
-
return tokens.filter((w) => w.length > 0);
|
|
28228
|
+
return [...new Set(tokens.filter((w) => w.length > 0))];
|
|
28174
28229
|
}
|
|
28175
28230
|
|
|
28176
28231
|
// src/tools/skill-search/bm25.ts
|
|
@@ -28325,13 +28380,198 @@ var filter_default = [
|
|
|
28325
28380
|
{ id: "25", summary: "WaterFlow \u7011\u5E03\u6D41\u5E03\u5C40\u5F00\u53D1\u5B9E\u8DF5", path: "experience/experience_water_flow.md", category: "water_flow" }
|
|
28326
28381
|
];
|
|
28327
28382
|
|
|
28383
|
+
// src/tools/skill-search/build-sdk-index.ts
|
|
28384
|
+
import { readdir as readdir2, readFile as readFile3, writeFile } from "node:fs/promises";
|
|
28385
|
+
import { join as join3, relative, basename, dirname as dirname2 } from "node:path";
|
|
28386
|
+
var SCAN_DIRS = [
|
|
28387
|
+
"openharmony/js/api",
|
|
28388
|
+
"openharmony/ets/api",
|
|
28389
|
+
"openharmony/ets/component",
|
|
28390
|
+
"openharmony/ets/kits",
|
|
28391
|
+
"hms/ets/api",
|
|
28392
|
+
"hms/ets/kits"
|
|
28393
|
+
];
|
|
28394
|
+
var TS_EXT_RE = /\.(?:d\.ts|d\.ets|ts|ets)$/;
|
|
28395
|
+
async function scanFiles(dir) {
|
|
28396
|
+
const results = [];
|
|
28397
|
+
async function walk(current) {
|
|
28398
|
+
const entries = await readdir2(current, { withFileTypes: true });
|
|
28399
|
+
for (const entry of entries) {
|
|
28400
|
+
const full = join3(current, entry.name);
|
|
28401
|
+
if (entry.isDirectory()) {
|
|
28402
|
+
await walk(full);
|
|
28403
|
+
} else if (TS_EXT_RE.test(entry.name)) {
|
|
28404
|
+
results.push(full);
|
|
28405
|
+
}
|
|
28406
|
+
}
|
|
28407
|
+
}
|
|
28408
|
+
await walk(dir);
|
|
28409
|
+
return results;
|
|
28410
|
+
}
|
|
28411
|
+
function extractSummary(content, filename, relPath) {
|
|
28412
|
+
const rawName = filename.replace(/\.(?:d\.ts|d\.ets|ts|ets)$/, "");
|
|
28413
|
+
let moduleName;
|
|
28414
|
+
if (rawName.startsWith("@")) {
|
|
28415
|
+
moduleName = rawName.replace(/^@/, "");
|
|
28416
|
+
} else {
|
|
28417
|
+
const dirParts = dirname2(relPath).split("/").filter(Boolean);
|
|
28418
|
+
moduleName = [...dirParts, rawName].join(".");
|
|
28419
|
+
}
|
|
28420
|
+
const symbols = [];
|
|
28421
|
+
const stripped = content.replace(/\/\*\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
28422
|
+
const patterns = [
|
|
28423
|
+
// Prefixed at file/module level
|
|
28424
|
+
/declare\s+namespace\s+(\w+)/g,
|
|
28425
|
+
/export\s+(?:default\s+)?interface\s+(\w+)/g,
|
|
28426
|
+
/export\s+(?:default\s+)?class\s+(\w+)/g,
|
|
28427
|
+
/export\s+(?:default\s+)?function\s+(\w+)/g,
|
|
28428
|
+
/export\s+(?:default\s+)?const\s+(\w+)/g,
|
|
28429
|
+
/export\s+(?:default\s+)?type\s+(\w+)/g,
|
|
28430
|
+
/export\s+(?:default\s+)?enum\s+(\w+)/g,
|
|
28431
|
+
/export\s+module\s+(\w+)/g,
|
|
28432
|
+
/declare\s+function\s+(\w+)/g,
|
|
28433
|
+
/declare\s+class\s+(\w+)/g,
|
|
28434
|
+
/declare\s+interface\s+(\w+)/g,
|
|
28435
|
+
/declare\s+const\s+(\w+)/g,
|
|
28436
|
+
/declare\s+type\s+(\w+)/g,
|
|
28437
|
+
/declare\s+enum\s+(\w+)/g,
|
|
28438
|
+
// Bare declarations inside declare namespace / module blocks
|
|
28439
|
+
/\bfunction\s+(\w+)\s*\(/g,
|
|
28440
|
+
/\binterface\s+(\w+)\s*(?:\{|extends)/g,
|
|
28441
|
+
/\benum\s+(\w+)\s*\{/g,
|
|
28442
|
+
/\bclass\s+(\w+)\s*(?:\{|extends|implements)/g,
|
|
28443
|
+
/\btype\s+(\w+)\s*=/g
|
|
28444
|
+
];
|
|
28445
|
+
for (const pat of patterns) {
|
|
28446
|
+
let m;
|
|
28447
|
+
while ((m = pat.exec(stripped)) !== null) {
|
|
28448
|
+
if (!symbols.includes(m[1])) {
|
|
28449
|
+
symbols.push(m[1]);
|
|
28450
|
+
}
|
|
28451
|
+
}
|
|
28452
|
+
}
|
|
28453
|
+
const importNamedRe = /import\s+\{([^}]+)\}\s+from\s+/g;
|
|
28454
|
+
let mImport;
|
|
28455
|
+
while ((mImport = importNamedRe.exec(stripped)) !== null) {
|
|
28456
|
+
for (const name of mImport[1].split(",")) {
|
|
28457
|
+
const clean = name.trim().split(/\s+as\s+/)[0].trim();
|
|
28458
|
+
if (clean && !symbols.includes(clean)) {
|
|
28459
|
+
symbols.push(clean);
|
|
28460
|
+
}
|
|
28461
|
+
}
|
|
28462
|
+
}
|
|
28463
|
+
const importDefaultRe = /import\s+(\w+)\s+from\s+/g;
|
|
28464
|
+
let mDef;
|
|
28465
|
+
while ((mDef = importDefaultRe.exec(stripped)) !== null) {
|
|
28466
|
+
const name = mDef[1];
|
|
28467
|
+
if (!symbols.includes(name)) {
|
|
28468
|
+
symbols.push(name);
|
|
28469
|
+
}
|
|
28470
|
+
}
|
|
28471
|
+
const descMatch = content.match(
|
|
28472
|
+
/\*\s*(?:@description\s+)?(Provides[^.]*\.)/s
|
|
28473
|
+
);
|
|
28474
|
+
let providesDesc = "";
|
|
28475
|
+
if (descMatch) {
|
|
28476
|
+
providesDesc = descMatch[1].replace(/\*\s*/g, "").replace(/\n/g, " ").trim().slice(0, 200);
|
|
28477
|
+
}
|
|
28478
|
+
if (!providesDesc) {
|
|
28479
|
+
const gDesc = content.match(
|
|
28480
|
+
/\*\s*@description\s+([\s\S]*?)\s*\*\s*\//s
|
|
28481
|
+
);
|
|
28482
|
+
if (gDesc) {
|
|
28483
|
+
providesDesc = gDesc[1].replace(/\*\s*/g, "").replace(/\n/g, " ").trim().slice(0, 200);
|
|
28484
|
+
}
|
|
28485
|
+
}
|
|
28486
|
+
const kitMatch = content.match(/\*\s*@kit\s+(\w+)/);
|
|
28487
|
+
const kit = kitMatch ? kitMatch[1] : "";
|
|
28488
|
+
const syscapMatch = content.match(
|
|
28489
|
+
/\*\s*@syscap\s+SystemCapability\.([\w.]+)/
|
|
28490
|
+
);
|
|
28491
|
+
const syscap = syscapMatch ? syscapMatch[1] : "";
|
|
28492
|
+
const parts = [moduleName];
|
|
28493
|
+
if (kit && !moduleName.includes(kit)) parts.push(`Kit:${kit}`);
|
|
28494
|
+
if (syscap) parts.push(syscap);
|
|
28495
|
+
if (providesDesc) parts.push(providesDesc);
|
|
28496
|
+
if (symbols.length > 0) {
|
|
28497
|
+
parts.push(symbols.slice(0, 20).join(", "));
|
|
28498
|
+
}
|
|
28499
|
+
return {
|
|
28500
|
+
summary: parts.join(" | "),
|
|
28501
|
+
moduleName,
|
|
28502
|
+
symbols: symbols.slice(0, 50)
|
|
28503
|
+
};
|
|
28504
|
+
}
|
|
28505
|
+
async function buildSDKIndex(sdk_path) {
|
|
28506
|
+
let allFiles = [];
|
|
28507
|
+
for (const dir of SCAN_DIRS) {
|
|
28508
|
+
const fullDir = join3(sdk_path, "default", dir);
|
|
28509
|
+
log(`Scanning ${dir} ...`);
|
|
28510
|
+
const files = await scanFiles(fullDir);
|
|
28511
|
+
log(` Found ${files.length} files`);
|
|
28512
|
+
for (const f of files) {
|
|
28513
|
+
allFiles.push({
|
|
28514
|
+
absPath: f,
|
|
28515
|
+
relPath: relative(fullDir, f),
|
|
28516
|
+
sourceDir: dir
|
|
28517
|
+
});
|
|
28518
|
+
}
|
|
28519
|
+
}
|
|
28520
|
+
log(`Total files: ${allFiles.length}`);
|
|
28521
|
+
const entries = [];
|
|
28522
|
+
for (let i = 0; i < allFiles.length; i++) {
|
|
28523
|
+
const { absPath, relPath, sourceDir } = allFiles[i];
|
|
28524
|
+
const filename = basename(absPath);
|
|
28525
|
+
try {
|
|
28526
|
+
const content = await readFile3(absPath, "utf-8");
|
|
28527
|
+
const { summary, moduleName, symbols } = extractSummary(
|
|
28528
|
+
content,
|
|
28529
|
+
filename,
|
|
28530
|
+
relPath
|
|
28531
|
+
);
|
|
28532
|
+
entries.push({
|
|
28533
|
+
id: String(i + 1),
|
|
28534
|
+
summary,
|
|
28535
|
+
path: absPath,
|
|
28536
|
+
moduleName,
|
|
28537
|
+
symbols
|
|
28538
|
+
});
|
|
28539
|
+
} catch {
|
|
28540
|
+
}
|
|
28541
|
+
if ((i + 1) % 500 === 0) {
|
|
28542
|
+
log(`Processed ${i + 1}/${allFiles.length}`);
|
|
28543
|
+
}
|
|
28544
|
+
}
|
|
28545
|
+
const outputPath = join3(import.meta.dirname, "sdk-index.json");
|
|
28546
|
+
await writeFile(outputPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
28547
|
+
log(`Done! Wrote ${entries.length} entries to sdk-index.json`);
|
|
28548
|
+
log(
|
|
28549
|
+
`Sample entries:`,
|
|
28550
|
+
entries.slice(0, 5).map((e) => e.moduleName)
|
|
28551
|
+
);
|
|
28552
|
+
}
|
|
28553
|
+
|
|
28328
28554
|
// src/tools/skill-search/search-skill.ts
|
|
28329
28555
|
var cachedEntries = null;
|
|
28330
28556
|
var cachedIndexableEntries = null;
|
|
28331
28557
|
var cachedFilters = null;
|
|
28332
28558
|
var cachedRetriever = null;
|
|
28333
|
-
|
|
28334
|
-
|
|
28559
|
+
var cachedSdkEntries = null;
|
|
28560
|
+
var cachedSdkRetriever = null;
|
|
28561
|
+
async function loadAndCache(resolvedPath) {
|
|
28562
|
+
const sdk_path = findSdkDir();
|
|
28563
|
+
log("start load sdk cache", sdk_path);
|
|
28564
|
+
if (sdk_path !== void 0) {
|
|
28565
|
+
const sdkIndexPath = join4(import.meta.dirname, "sdk-index.json");
|
|
28566
|
+
if (!existsSync(sdkIndexPath)) {
|
|
28567
|
+
log("sdk path is not exist, need to build");
|
|
28568
|
+
await buildSDKIndex(sdk_path);
|
|
28569
|
+
}
|
|
28570
|
+
loadSdkIndex();
|
|
28571
|
+
log("success to load sdk cache");
|
|
28572
|
+
}
|
|
28573
|
+
log("start load skill cache");
|
|
28574
|
+
const raw = readFileSync(join4(resolvedPath, "index.json"), "utf-8");
|
|
28335
28575
|
const entries = JSON.parse(raw);
|
|
28336
28576
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
28337
28577
|
return false;
|
|
@@ -28344,23 +28584,45 @@ function loadAndCache(resolvedPath) {
|
|
|
28344
28584
|
const retriever = new HybridRetriever();
|
|
28345
28585
|
retriever.addDocuments(documents, tokenizedDocs);
|
|
28346
28586
|
cachedRetriever = retriever;
|
|
28587
|
+
log("success to load sdk cache");
|
|
28347
28588
|
return true;
|
|
28348
28589
|
}
|
|
28349
|
-
function
|
|
28590
|
+
function loadSdkIndex() {
|
|
28591
|
+
if (cachedSdkRetriever) return true;
|
|
28592
|
+
const sdkIndexPath = join4(import.meta.dirname, "sdk-index.json");
|
|
28593
|
+
if (!existsSync(sdkIndexPath)) return false;
|
|
28594
|
+
try {
|
|
28595
|
+
const raw = readFileSync(sdkIndexPath, "utf-8");
|
|
28596
|
+
const entries = JSON.parse(raw);
|
|
28597
|
+
if (!Array.isArray(entries) || entries.length === 0) return false;
|
|
28598
|
+
cachedSdkEntries = entries;
|
|
28599
|
+
const documents = entries.map((e) => e.summary);
|
|
28600
|
+
const tokenizedDocs = documents.map((d) => tokenize(d));
|
|
28601
|
+
const retriever = new BM25Retriever();
|
|
28602
|
+
retriever.addDocuments(documents, tokenizedDocs);
|
|
28603
|
+
cachedSdkRetriever = retriever;
|
|
28604
|
+
return true;
|
|
28605
|
+
} catch {
|
|
28606
|
+
return false;
|
|
28607
|
+
}
|
|
28608
|
+
}
|
|
28609
|
+
async function searchSkill(skill_path, query, topK = 3) {
|
|
28350
28610
|
if (!cachedEntries) {
|
|
28351
28611
|
try {
|
|
28352
|
-
if (!loadAndCache(skill_path?.trim())) return [];
|
|
28612
|
+
if (!await loadAndCache(skill_path?.trim())) return [];
|
|
28353
28613
|
} catch {
|
|
28354
28614
|
return [];
|
|
28355
28615
|
}
|
|
28356
28616
|
}
|
|
28617
|
+
const prefix = skill_path.replace(/[\/\\]+$/, "");
|
|
28357
28618
|
const preFiltered = [];
|
|
28358
28619
|
for (const [category, entry] of cachedFilters) {
|
|
28359
28620
|
const idx = query.toLowerCase().indexOf(category);
|
|
28360
28621
|
if (idx !== -1) {
|
|
28361
28622
|
preFiltered.push({
|
|
28362
28623
|
ets_file_path: "",
|
|
28363
|
-
experience_file_path: entry.path
|
|
28624
|
+
experience_file_path: join4(prefix, entry.path),
|
|
28625
|
+
sdk_file_path: ""
|
|
28364
28626
|
});
|
|
28365
28627
|
query = (query.slice(0, idx) + query.slice(idx + category.length)).trim();
|
|
28366
28628
|
}
|
|
@@ -28368,21 +28630,42 @@ function searchSkill(skill_path, query, topK = 5) {
|
|
|
28368
28630
|
if (!query) {
|
|
28369
28631
|
return preFiltered;
|
|
28370
28632
|
}
|
|
28633
|
+
const MIN_SCORE = 0.01;
|
|
28634
|
+
const sdkResults = loadSdkIndex() ? cachedSdkRetriever.search(query, topK).filter((r) => r.score >= MIN_SCORE).map((r) => ({
|
|
28635
|
+
ets_file_path: "",
|
|
28636
|
+
experience_file_path: "",
|
|
28637
|
+
sdk_file_path: cachedSdkEntries[r.index].path
|
|
28638
|
+
})) : [];
|
|
28371
28639
|
const searchResults = cachedRetriever.search(query, topK);
|
|
28372
|
-
const MIN_SCORE = 2e-3;
|
|
28373
28640
|
const filtered = searchResults.filter((r) => r.score >= MIN_SCORE);
|
|
28374
|
-
|
|
28375
|
-
return [...preFiltered, ...
|
|
28641
|
+
const skillResults = filtered.length > 0 ? aggregateByExperience(convert_search_result(filtered, prefix)) : [];
|
|
28642
|
+
return [...preFiltered, ...skillResults, ...sdkResults];
|
|
28643
|
+
}
|
|
28644
|
+
function aggregateByExperience(results) {
|
|
28645
|
+
const map3 = /* @__PURE__ */ new Map();
|
|
28646
|
+
for (const r of results) {
|
|
28647
|
+
const key = r.experience_file_path || r.ets_file_path;
|
|
28648
|
+
const existing = map3.get(key);
|
|
28649
|
+
if (existing) {
|
|
28650
|
+
if (r.ets_file_path) {
|
|
28651
|
+
existing.ets_file_path = existing.ets_file_path ? existing.ets_file_path + "\n" + r.ets_file_path : r.ets_file_path;
|
|
28652
|
+
}
|
|
28653
|
+
} else {
|
|
28654
|
+
map3.set(key, { ...r });
|
|
28655
|
+
}
|
|
28656
|
+
}
|
|
28657
|
+
return [...map3.values()];
|
|
28376
28658
|
}
|
|
28377
|
-
function convert_search_result(searchResults) {
|
|
28659
|
+
function convert_search_result(searchResults, prefix) {
|
|
28378
28660
|
return searchResults.map((result, rank) => {
|
|
28379
28661
|
const entry = cachedIndexableEntries[result.index];
|
|
28380
28662
|
const experience = cachedEntries.find(
|
|
28381
28663
|
(e) => e.type === "experience" && e.category === entry.category
|
|
28382
28664
|
);
|
|
28383
28665
|
return {
|
|
28384
|
-
ets_file_path: entry.path,
|
|
28385
|
-
experience_file_path: experience?.path
|
|
28666
|
+
ets_file_path: join4(prefix, entry.path),
|
|
28667
|
+
experience_file_path: experience?.path ? join4(prefix, experience.path) : "",
|
|
28668
|
+
sdk_file_path: ""
|
|
28386
28669
|
};
|
|
28387
28670
|
});
|
|
28388
28671
|
}
|
|
@@ -28390,25 +28673,27 @@ function convert_search_result(searchResults) {
|
|
|
28390
28673
|
// src/tools/skill-search/skill-search-tool.ts
|
|
28391
28674
|
function skillSearchTool(managers) {
|
|
28392
28675
|
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.
|
|
28676
|
+
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
28677
|
args: {
|
|
28395
|
-
skill_path: tool.schema.string("Absolute path to the skill directory
|
|
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(
|
|
28678
|
+
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'."),
|
|
28679
|
+
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'. "),
|
|
28680
|
+
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
28681
|
},
|
|
28399
28682
|
execute: async (args, context) => {
|
|
28400
28683
|
try {
|
|
28401
|
-
const results = searchSkill(args.skill_path, args.query, args.topK);
|
|
28684
|
+
const results = await searchSkill(args.skill_path, args.query, args.topK);
|
|
28402
28685
|
if (results.length === 0) {
|
|
28403
28686
|
return "No relative items found in the skill directory.";
|
|
28404
28687
|
}
|
|
28405
|
-
|
|
28406
|
-
|
|
28407
|
-
|
|
28408
|
-
|
|
28409
|
-
}
|
|
28410
|
-
return
|
|
28688
|
+
const sdkInfo = results.map((r) => {
|
|
28689
|
+
const parts = [];
|
|
28690
|
+
if (r.experience_file_path) parts.push(`experience path ${r.experience_file_path}`);
|
|
28691
|
+
if (r.ets_file_path) parts.push(`sample code path ${r.ets_file_path}`);
|
|
28692
|
+
if (r.sdk_file_path) parts.push(`sdk info path ${r.sdk_file_path}`);
|
|
28693
|
+
return parts.join("\n");
|
|
28411
28694
|
}).join("\n\n");
|
|
28695
|
+
return `You can read the following files as needed to obtain information.
|
|
28696
|
+
${sdkInfo}`;
|
|
28412
28697
|
} catch (e) {
|
|
28413
28698
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
28414
28699
|
}
|
|
@@ -28440,7 +28725,7 @@ function createTools(args) {
|
|
|
28440
28725
|
}
|
|
28441
28726
|
|
|
28442
28727
|
// src/hooks/tool-hooks.ts
|
|
28443
|
-
import { readFile as
|
|
28728
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
28444
28729
|
|
|
28445
28730
|
// node_modules/diff/lib/index.mjs
|
|
28446
28731
|
function Diff() {
|
|
@@ -29025,7 +29310,7 @@ function createToolHooks(sessionManager, projectDir) {
|
|
|
29025
29310
|
if (input.tool === "write") {
|
|
29026
29311
|
const filePath = output.args?.filePath;
|
|
29027
29312
|
if (filePath) {
|
|
29028
|
-
const oldContent = await
|
|
29313
|
+
const oldContent = await readFile4(filePath, "utf-8").catch(() => "");
|
|
29029
29314
|
writeContentCache.set(input.callID, oldContent);
|
|
29030
29315
|
}
|
|
29031
29316
|
}
|
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.
|
|
4
|
+
"version": "0.0.9",
|
|
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` 中注册即可。
|