@harmonyos-arkts/opencode-plugin 0.0.6 → 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 +465 -219
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14836,7 +14836,9 @@ function scheduleFlush() {
|
|
|
14836
14836
|
}
|
|
14837
14837
|
function log(message, data) {
|
|
14838
14838
|
try {
|
|
14839
|
-
const
|
|
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
|
|
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
|
|
@@ -14885,22 +14887,118 @@ var HM_BUILD = `
|
|
|
14885
14887
|
5. If still failing after 3 attempts, report the remaining errors to the user and ask for guidance
|
|
14886
14888
|
- Only after a successful build should the development cycle be considered complete
|
|
14887
14889
|
`;
|
|
14888
|
-
var HM_ASCF = `
|
|
14889
|
-
### ASCF Phase
|
|
14890
|
-
- HM_ASCF is the default active environment. Treat all requests as ASCF-context tasks unless the user explicitly asks to switch domain.
|
|
14891
|
-
- Skill routing is mandatory and must be explicit before execution. Never handle ASCF tasks without selecting one of the ASCF skills first.
|
|
14892
|
-
- Use \`harmonyos-atomic-ascf-convert\` for mini-program to HarmonyOS Atomic Service conversion (migration, API mapping, directory and component adaptation, conversion implementation).
|
|
14893
|
-
- Use \`harmonyos-ascf-knowledge\` for ASCF development guidance, technical Q&A, issue localization, and troubleshooting in existing ASCF projects.
|
|
14894
|
-
- For mixed requests, execute in two stages: (1) conversion via \`harmonyos-atomic-ascf-convert\`, then (2) engineering verification and issue diagnosis via \`harmonyos-ascf-knowledge\`.
|
|
14895
|
-
- If the request is "how to", "why it fails", "where the issue is", or "how to fix", always prioritize \`harmonyos-ascf-knowledge\`.
|
|
14896
|
-
- If a selected skill returns insufficient context, immediately continue with the other ASCF skill as fallback, then merge results into a single ASCF-oriented answer.
|
|
14897
|
-
`;
|
|
14898
14890
|
var HM_ONE2MANY = `
|
|
14899
14891
|
### One-to-Many Adaptation Phase
|
|
14900
14892
|
- Use the \`harmonyos-one2many-adapt\` skill to adapt the HarmonyOS application for multiple device types (phone, tablet, foldable, 2-in-1, wearable, TV)
|
|
14901
14893
|
- After completing adaptation, you MUST proceed to the Build phase
|
|
14902
14894
|
- **NO EXCEPTIONS**: Code must be built successfully before marking adaptation as complete
|
|
14903
14895
|
`;
|
|
14896
|
+
var HM_PLAN = `
|
|
14897
|
+
### Plan Phase
|
|
14898
|
+
You are a **planning specialist** for HarmonyOS projects. Your sole responsibility is to ANALYZE, PLAN, and DOCUMENT.
|
|
14899
|
+
|
|
14900
|
+
**CRITICAL RULES (you MUST follow these):**
|
|
14901
|
+
1. You MUST follow ALL steps below in strict order. Do not skip any step.
|
|
14902
|
+
2. You MUST NOT write any implementation code or create/edit source files (except the plan document).
|
|
14903
|
+
3. You MUST write the final plan to \`.harmonyos/plan.md\`.
|
|
14904
|
+
4. After outputting the plan, STOP and wait for user/approver feedback. Do NOT proceed to development yourself.
|
|
14905
|
+
|
|
14906
|
+
---
|
|
14907
|
+
|
|
14908
|
+
**Step 1: Explore the Codebase**
|
|
14909
|
+
|
|
14910
|
+
Thoroughly explore the existing project to understand:
|
|
14911
|
+
- Directory structure and file organization (use Glob)
|
|
14912
|
+
- Architecture patterns \u2014 look for @ComponentV2/@ObservedV2, MVVM, state management singletons
|
|
14913
|
+
- Page routing setup (main_pages.json, route_map.json)
|
|
14914
|
+
- Existing models, stores, and data flow
|
|
14915
|
+
- Any existing features similar to what's being requested
|
|
14916
|
+
- module.json5 for permissions and ability configuration
|
|
14917
|
+
- Entry point (EntryAbility) and initial page loading
|
|
14918
|
+
|
|
14919
|
+
If \`.harmonyos/codebase.md\` exists, read it first for cached analysis.
|
|
14920
|
+
|
|
14921
|
+
**Step 2: Analyze Requirements**
|
|
14922
|
+
|
|
14923
|
+
Break down the user's request into:
|
|
14924
|
+
- Core features and sub-features
|
|
14925
|
+
- Dependencies between tasks (what must be done before what \u2014 use explicit "depends on Step N" markers)
|
|
14926
|
+
- Technical constraints (HarmonyOS NEXT API availability, Kit compatibility, permission requirements)
|
|
14927
|
+
- Impact analysis: which existing files need modification vs which are new files
|
|
14928
|
+
|
|
14929
|
+
Output a dependency-ordered task list.
|
|
14930
|
+
|
|
14931
|
+
**Step 3: Create the Development Plan**
|
|
14932
|
+
|
|
14933
|
+
Write a structured plan to \`.harmonyos/plan.md\` using this template:
|
|
14934
|
+
|
|
14935
|
+
\`\`\`markdown
|
|
14936
|
+
# Development Plan: [Feature Name]
|
|
14937
|
+
|
|
14938
|
+
## Overview
|
|
14939
|
+
[1-2 sentence summary of what will be implemented]
|
|
14940
|
+
|
|
14941
|
+
## Current Architecture Summary
|
|
14942
|
+
[Brief description of the relevant existing code that will be affected]
|
|
14943
|
+
|
|
14944
|
+
## Implementation Steps
|
|
14945
|
+
|
|
14946
|
+
### Step 1: [Clear Action Title]
|
|
14947
|
+
- **Objective**: [What this step accomplishes]
|
|
14948
|
+
- **Files to create**: [Full paths of new files, or "None"]
|
|
14949
|
+
- **Files to modify**: [Full paths of existing files to change, or "None"]
|
|
14950
|
+
- **Key changes**:
|
|
14951
|
+
- [Specific code changes: new classes, methods, UI components]
|
|
14952
|
+
- [APIs or Kits to use]
|
|
14953
|
+
- [State management approach]
|
|
14954
|
+
- [Configuration or permission updates]
|
|
14955
|
+
- **Acceptance criteria**: [How to verify this step is correctly completed]
|
|
14956
|
+
|
|
14957
|
+
### Step 2: [Clear Action Title]
|
|
14958
|
+
[Same structure, with "depends on Step 1" if applicable]
|
|
14959
|
+
...
|
|
14960
|
+
|
|
14961
|
+
## Risk & Considerations
|
|
14962
|
+
- [Potential issues, edge cases, or breaking changes to existing functionality]
|
|
14963
|
+
\`\`\`
|
|
14964
|
+
|
|
14965
|
+
**Step 4: Present and Review**
|
|
14966
|
+
|
|
14967
|
+
Output the plan content to the user in your response. Then conclude with:
|
|
14968
|
+
|
|
14969
|
+
> Plan saved to \`.harmonyos/plan.md\`. Please review and confirm, or suggest adjustments before implementation begins.
|
|
14970
|
+
|
|
14971
|
+
**STOP HERE. Do NOT proceed to implementation. The main agent or user will handle the next phase.**
|
|
14972
|
+
|
|
14973
|
+
---
|
|
14974
|
+
|
|
14975
|
+
**Granularity Guidelines:**
|
|
14976
|
+
- Simple tasks (1-2 files changed): 2-3 high-level steps
|
|
14977
|
+
- Medium tasks (3-5 files): 4-6 steps, one major file change per step
|
|
14978
|
+
- Complex tasks (6+ files or architectural changes): Fine-grained steps with explicit dependency chains
|
|
14979
|
+
- Each step MUST be independently verifiable
|
|
14980
|
+
- Group related changes that must be deployed together into the same step
|
|
14981
|
+
|
|
14982
|
+
**Plan Output Example (for reference):**
|
|
14983
|
+
|
|
14984
|
+
### Step 1: Add UserStore Model
|
|
14985
|
+
- **Objective**: Create shared user authentication state management
|
|
14986
|
+
- **Files to create**: entry/src/main/ets/model/UserStore.ets
|
|
14987
|
+
- **Files to modify**: None
|
|
14988
|
+
- **Key changes**:
|
|
14989
|
+
- Create UserInfo interface and UserStore singleton with @ObservedV2/@Trace
|
|
14990
|
+
- Add loginWithHuaweiID() method using @kit.AccountKit authentication
|
|
14991
|
+
- Add logout() method
|
|
14992
|
+
- **Acceptance criteria**: UserStore exports getInstance(), isLoggedIn is reactive
|
|
14993
|
+
|
|
14994
|
+
### Step 2: Create Splash Page with Countdown
|
|
14995
|
+
- **Objective**: Add splash screen that navigates based on login state
|
|
14996
|
+
- **Files to create**: entry/src/main/ets/pages/SplashPage.ets
|
|
14997
|
+
- **Files to modify**: entry/src/main/resources/base/profile/main_pages.json, entry/src/main/ets/entryability/EntryAbility.ets
|
|
14998
|
+
- **Key changes**:
|
|
14999
|
+
- ... (depends on Step 1)
|
|
15000
|
+
- **Acceptance criteria**: App starts on SplashPage, countdown works, routes correctly
|
|
15001
|
+
`;
|
|
14904
15002
|
function buildPlanPrompt(type) {
|
|
14905
15003
|
switch (type) {
|
|
14906
15004
|
case "dev":
|
|
@@ -14909,10 +15007,10 @@ function buildPlanPrompt(type) {
|
|
|
14909
15007
|
return HM_DESIGN;
|
|
14910
15008
|
case "build":
|
|
14911
15009
|
return HM_BUILD;
|
|
14912
|
-
case "ascf":
|
|
14913
|
-
return HM_ASCF;
|
|
14914
15010
|
case "one2many":
|
|
14915
15011
|
return HM_ONE2MANY;
|
|
15012
|
+
case "plan":
|
|
15013
|
+
return HM_PLAN;
|
|
14916
15014
|
case "all":
|
|
14917
15015
|
return buildAllAgentPrompt();
|
|
14918
15016
|
default:
|
|
@@ -14920,40 +15018,16 @@ function buildPlanPrompt(type) {
|
|
|
14920
15018
|
}
|
|
14921
15019
|
}
|
|
14922
15020
|
function buildAllAgentPrompt() {
|
|
14923
|
-
return `All HarmonyOS development tasks can be broadly categorized into three main phases: Design, Development, Build
|
|
15021
|
+
return `All HarmonyOS development tasks can be broadly categorized into three main phases: Design, Development, Build. Each phase has its own set of best practices and workflows to ensure efficient and successful project completion.
|
|
14924
15022
|
${HM_DESIGN}
|
|
14925
15023
|
${HM_DEVELOP}
|
|
14926
15024
|
${HM_BUILD}
|
|
14927
|
-
${HM_ASCF}
|
|
14928
15025
|
|
|
14929
15026
|
Follow these workflows:
|
|
14930
15027
|
- **New project (greenfield)**: PRD Design \u2192 Development \u2192 Build.
|
|
14931
|
-
- **
|
|
14932
|
-
- **
|
|
14933
|
-
|
|
14934
|
-
}
|
|
14935
|
-
function buildHmExploreSubAgentPrompt() {
|
|
14936
|
-
return `
|
|
14937
|
-
You are a HarmonyOS project file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
14938
|
-
|
|
14939
|
-
Your strengths:
|
|
14940
|
-
- Rapidly finding files using glob patterns
|
|
14941
|
-
- Searching code and text with powerful regex patterns
|
|
14942
|
-
- Reading and analyzing file contents
|
|
14943
|
-
|
|
14944
|
-
Guidelines:
|
|
14945
|
-
- Use Glob for broad file pattern matching
|
|
14946
|
-
- Use Grep for searching file contents with regex
|
|
14947
|
-
- Use Read when you know the specific file path you need to read
|
|
14948
|
-
- Use Bash for file operations like copying, moving, or listing directory contents
|
|
14949
|
-
- Adapt your search approach based on the thoroughness level specified by the caller
|
|
14950
|
-
- Return file paths as absolute paths in your final response
|
|
14951
|
-
- For clear communication, avoid using emojis
|
|
14952
|
-
- Create '\`.harmonyos/codebase.md\`' to write your findings, and you can also read '\`.harmonyos/codebase.md\`' if this existed
|
|
14953
|
-
- Do not create any files except '\`.harmonyos/codebase.md\`', or run bash commands that modify the user's system state in any way
|
|
14954
|
-
|
|
14955
|
-
Complete the user's search request efficiently and report your findings clearly.
|
|
14956
|
-
`;
|
|
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.
|
|
15030
|
+
`;
|
|
14957
15031
|
}
|
|
14958
15032
|
function buildHmAgentPrompt(type) {
|
|
14959
15033
|
return `You are HarmonyOS Dev Assistant(HDACode), an expert coding agent specialized in HarmonyOS development.
|
|
@@ -15097,10 +15171,11 @@ function createHmAgent() {
|
|
|
15097
15171
|
prompt: buildHmAgentPrompt("all"),
|
|
15098
15172
|
temperature: 0.3,
|
|
15099
15173
|
permission: {
|
|
15100
|
-
"
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15174
|
+
"*": "allow",
|
|
15175
|
+
doom_loop: "ask",
|
|
15176
|
+
external_directory: {
|
|
15177
|
+
"*": "ask"
|
|
15178
|
+
}
|
|
15104
15179
|
},
|
|
15105
15180
|
metadata: void 0
|
|
15106
15181
|
};
|
|
@@ -15134,13 +15209,6 @@ function createDesignAgent() {
|
|
|
15134
15209
|
}
|
|
15135
15210
|
|
|
15136
15211
|
// src/agents/dev-agent.ts
|
|
15137
|
-
function getHmExploreSubAgentDescription() {
|
|
15138
|
-
return `
|
|
15139
|
-
Fast agent specialized for exploring codebases.
|
|
15140
|
-
Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?").
|
|
15141
|
-
When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.
|
|
15142
|
-
`;
|
|
15143
|
-
}
|
|
15144
15212
|
function getHmDevDescription() {
|
|
15145
15213
|
return `
|
|
15146
15214
|
HarmonyOS Development
|
|
@@ -15155,72 +15223,12 @@ function createHmDevelopmentAgent() {
|
|
|
15155
15223
|
prompt: buildHmAgentPrompt("dev"),
|
|
15156
15224
|
temperature: 0.3,
|
|
15157
15225
|
permission: {
|
|
15158
|
-
"task": {
|
|
15159
|
-
"explore": "deny"
|
|
15160
|
-
},
|
|
15161
15226
|
"skillSearch": "allow"
|
|
15162
15227
|
},
|
|
15163
15228
|
metadata: void 0
|
|
15164
15229
|
};
|
|
15165
15230
|
return agent;
|
|
15166
15231
|
}
|
|
15167
|
-
function createHmExploreSubAgent() {
|
|
15168
|
-
const agent = {
|
|
15169
|
-
id: "harmonyos-explore",
|
|
15170
|
-
name: "harmonyos-explore",
|
|
15171
|
-
description: getHmExploreSubAgentDescription(),
|
|
15172
|
-
mode: "subagent",
|
|
15173
|
-
prompt: buildHmExploreSubAgentPrompt(),
|
|
15174
|
-
temperature: 0.1,
|
|
15175
|
-
permission: {
|
|
15176
|
-
"*": "deny",
|
|
15177
|
-
"grep": "allow",
|
|
15178
|
-
"glob": "allow",
|
|
15179
|
-
"list": "allow",
|
|
15180
|
-
"bash": "allow",
|
|
15181
|
-
"read": "allow",
|
|
15182
|
-
"edit": {
|
|
15183
|
-
"*.harmonyos/codebase.md": "allow"
|
|
15184
|
-
},
|
|
15185
|
-
"write": {
|
|
15186
|
-
"*.harmonyos/codebase.md": "allow"
|
|
15187
|
-
}
|
|
15188
|
-
},
|
|
15189
|
-
metadata: void 0
|
|
15190
|
-
};
|
|
15191
|
-
return agent;
|
|
15192
|
-
}
|
|
15193
|
-
|
|
15194
|
-
// src/agents/ascf-agent.ts
|
|
15195
|
-
function getHmAscfDescription() {
|
|
15196
|
-
return `
|
|
15197
|
-
HarmonyOS ASCF assistant for mini-program conversion, ASCF development Q&A, and issue troubleshooting
|
|
15198
|
-
`;
|
|
15199
|
-
}
|
|
15200
|
-
function createAscfAgent() {
|
|
15201
|
-
const agent = {
|
|
15202
|
-
id: "harmonyos-ascf",
|
|
15203
|
-
name: "harmonyos-ascf",
|
|
15204
|
-
description: getHmAscfDescription(),
|
|
15205
|
-
mode: "primary",
|
|
15206
|
-
prompt: buildHmAgentPrompt("ascf"),
|
|
15207
|
-
temperature: 0.6,
|
|
15208
|
-
permission: {
|
|
15209
|
-
"skill": {
|
|
15210
|
-
"*": "deny",
|
|
15211
|
-
"harmonyos-atomic-ascf-convert": "allow",
|
|
15212
|
-
"harmonyos-ascf-knowledge": "allow",
|
|
15213
|
-
"huawei-payment-integration": "allow",
|
|
15214
|
-
"harmonyos-atomic-service-filing": "allow",
|
|
15215
|
-
"harmony-emulator-debugging": "allow",
|
|
15216
|
-
"harmonyos-atomic-ascf-release": "allow"
|
|
15217
|
-
},
|
|
15218
|
-
"skillSearch": "deny"
|
|
15219
|
-
},
|
|
15220
|
-
metadata: void 0
|
|
15221
|
-
};
|
|
15222
|
-
return agent;
|
|
15223
|
-
}
|
|
15224
15232
|
|
|
15225
15233
|
// src/agents/one2many-agent.ts
|
|
15226
15234
|
function getHmOne2ManyDescription() {
|
|
@@ -15251,9 +15259,7 @@ var AgentRegistry = class {
|
|
|
15251
15259
|
this.register(createHmAgent());
|
|
15252
15260
|
this.register(createDesignAgent());
|
|
15253
15261
|
this.register(createHmDevelopmentAgent());
|
|
15254
|
-
this.register(createAscfAgent());
|
|
15255
15262
|
this.register(createOne2ManyAgent());
|
|
15256
|
-
this.register(createHmExploreSubAgent());
|
|
15257
15263
|
}
|
|
15258
15264
|
register(agent) {
|
|
15259
15265
|
this.agents.set(agent.id, agent);
|
|
@@ -15385,57 +15391,7 @@ var AgentManager = class {
|
|
|
15385
15391
|
}
|
|
15386
15392
|
};
|
|
15387
15393
|
|
|
15388
|
-
// src/shared/ets-counter.ts
|
|
15389
|
-
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
15390
|
-
import { join as join2, extname } from "path";
|
|
15391
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15392
|
-
"node_modules",
|
|
15393
|
-
".preview",
|
|
15394
|
-
"build",
|
|
15395
|
-
".cxx",
|
|
15396
|
-
".gradle",
|
|
15397
|
-
"oh_modules",
|
|
15398
|
-
".hvigor",
|
|
15399
|
-
"entry/build"
|
|
15400
|
-
]);
|
|
15401
|
-
async function collectEtsFiles(dir, results) {
|
|
15402
|
-
let entries;
|
|
15403
|
-
try {
|
|
15404
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
15405
|
-
} catch {
|
|
15406
|
-
return;
|
|
15407
|
-
}
|
|
15408
|
-
for (const entry of entries) {
|
|
15409
|
-
if (entry.isDirectory()) {
|
|
15410
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
15411
|
-
if (entry.name.startsWith(".") && entry.name !== ".ets") continue;
|
|
15412
|
-
await collectEtsFiles(join2(dir, entry.name), results);
|
|
15413
|
-
} else if (extname(entry.name) === ".ets") {
|
|
15414
|
-
const filePath = join2(dir, entry.name);
|
|
15415
|
-
try {
|
|
15416
|
-
const content = await readFile2(filePath, "utf-8");
|
|
15417
|
-
const lines = content.split("\n").length;
|
|
15418
|
-
results.push({ path: filePath, lines });
|
|
15419
|
-
} catch {
|
|
15420
|
-
log("Failed to read .ets file", { path: filePath });
|
|
15421
|
-
}
|
|
15422
|
-
}
|
|
15423
|
-
}
|
|
15424
|
-
}
|
|
15425
|
-
async function countEtsLines(projectDir) {
|
|
15426
|
-
const files = [];
|
|
15427
|
-
await collectEtsFiles(projectDir, files);
|
|
15428
|
-
const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
|
|
15429
|
-
return {
|
|
15430
|
-
fileCount: files.length,
|
|
15431
|
-
totalLines,
|
|
15432
|
-
files,
|
|
15433
|
-
timestamp: Date.now()
|
|
15434
|
-
};
|
|
15435
|
-
}
|
|
15436
|
-
|
|
15437
15394
|
// src/managers/config-handler.ts
|
|
15438
|
-
var EXPLORE_ETS_THRESHOLD = 2e3;
|
|
15439
15395
|
function mergePermission(target, source) {
|
|
15440
15396
|
if (!target) return source;
|
|
15441
15397
|
const result = { ...target };
|
|
@@ -15450,27 +15406,11 @@ function mergePermission(target, source) {
|
|
|
15450
15406
|
}
|
|
15451
15407
|
return result;
|
|
15452
15408
|
}
|
|
15453
|
-
function createConfigHandler(_pluginConfig, agent,
|
|
15409
|
+
function createConfigHandler(_pluginConfig, agent, _projectDir) {
|
|
15454
15410
|
return async (config3) => {
|
|
15455
|
-
let exploreEnabled = true;
|
|
15456
|
-
try {
|
|
15457
|
-
const stats = await countEtsLines(projectDir);
|
|
15458
|
-
exploreEnabled = stats.totalLines >= EXPLORE_ETS_THRESHOLD;
|
|
15459
|
-
log("ETS count for explore agent", {
|
|
15460
|
-
totalLines: stats.totalLines,
|
|
15461
|
-
fileCount: stats.fileCount,
|
|
15462
|
-
enabled: exploreEnabled
|
|
15463
|
-
});
|
|
15464
|
-
} catch (err) {
|
|
15465
|
-
log("Failed to count ETS lines, enabling explore by default", { error: String(err) });
|
|
15466
|
-
}
|
|
15467
15411
|
const pluginAgents = agent.record();
|
|
15468
15412
|
if (config3.agent) {
|
|
15469
15413
|
for (const [id, pluginAgentConfig] of Object.entries(pluginAgents)) {
|
|
15470
|
-
if (id === "harmonyos-explore" && !exploreEnabled) {
|
|
15471
|
-
log("Skipping harmonyos-explore agent (insufficient .ets files)");
|
|
15472
|
-
continue;
|
|
15473
|
-
}
|
|
15474
15414
|
if (config3.agent[id]) {
|
|
15475
15415
|
const merged = { ...config3.agent[id], ...pluginAgentConfig };
|
|
15476
15416
|
if (config3.agent[id].permission && pluginAgentConfig.permission) {
|
|
@@ -15491,7 +15431,7 @@ function createConfigHandler(_pluginConfig, agent, projectDir) {
|
|
|
15491
15431
|
template: "/harmony-development-env-check",
|
|
15492
15432
|
description: "\u68C0\u67E5 HarmonyOS \u5F00\u53D1\u73AF\u5883\u914D\u7F6E\uFF08Node.js\u3001JDK\u3001ohpm\u3001hvigor\u3001DevEco SDK\uFF09"
|
|
15493
15433
|
};
|
|
15494
|
-
log("Config merged"
|
|
15434
|
+
log("Config merged");
|
|
15495
15435
|
};
|
|
15496
15436
|
}
|
|
15497
15437
|
|
|
@@ -28073,6 +28013,86 @@ async function downloadTemplate(mode, filePath) {
|
|
|
28073
28013
|
}
|
|
28074
28014
|
}
|
|
28075
28015
|
|
|
28016
|
+
// src/shared/ets-counter.ts
|
|
28017
|
+
import { readdir, readFile as readFile2, stat } from "fs/promises";
|
|
28018
|
+
import { join as join2, extname } from "path";
|
|
28019
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
28020
|
+
"node_modules",
|
|
28021
|
+
".preview",
|
|
28022
|
+
"build",
|
|
28023
|
+
".cxx",
|
|
28024
|
+
".gradle",
|
|
28025
|
+
"oh_modules",
|
|
28026
|
+
".hvigor",
|
|
28027
|
+
"entry/build"
|
|
28028
|
+
]);
|
|
28029
|
+
var HM_PROJECT_INDICATORS = ["build-profile.json5", "oh-package.json5"];
|
|
28030
|
+
var MAX_CONTENT_READ_FILES = 500;
|
|
28031
|
+
var ESTIMATED_LINES_PER_FILE = 100;
|
|
28032
|
+
async function isHarmonyOSProject(dir) {
|
|
28033
|
+
for (const indicator of HM_PROJECT_INDICATORS) {
|
|
28034
|
+
try {
|
|
28035
|
+
await stat(join2(dir, indicator));
|
|
28036
|
+
return true;
|
|
28037
|
+
} catch {
|
|
28038
|
+
}
|
|
28039
|
+
}
|
|
28040
|
+
return false;
|
|
28041
|
+
}
|
|
28042
|
+
async function collectEtsFilePaths(dir, results) {
|
|
28043
|
+
let entries;
|
|
28044
|
+
try {
|
|
28045
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
28046
|
+
} catch {
|
|
28047
|
+
return;
|
|
28048
|
+
}
|
|
28049
|
+
for (const entry of entries) {
|
|
28050
|
+
if (entry.isDirectory()) {
|
|
28051
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
28052
|
+
if (entry.name.startsWith(".") && entry.name !== ".ets") continue;
|
|
28053
|
+
await collectEtsFilePaths(join2(dir, entry.name), results);
|
|
28054
|
+
} else if (extname(entry.name) === ".ets") {
|
|
28055
|
+
results.push(join2(dir, entry.name));
|
|
28056
|
+
}
|
|
28057
|
+
}
|
|
28058
|
+
}
|
|
28059
|
+
async function countEtsLines(projectDir) {
|
|
28060
|
+
const startTime = Date.now();
|
|
28061
|
+
if (!await isHarmonyOSProject(projectDir)) {
|
|
28062
|
+
log("ets.count.skipped", { projectDir, elapsed: Date.now() - startTime });
|
|
28063
|
+
return { fileCount: 0, totalLines: 0, files: [], timestamp: Date.now() };
|
|
28064
|
+
}
|
|
28065
|
+
const filePaths = [];
|
|
28066
|
+
await collectEtsFilePaths(projectDir, filePaths);
|
|
28067
|
+
const fileCount = filePaths.length;
|
|
28068
|
+
if (fileCount > MAX_CONTENT_READ_FILES) {
|
|
28069
|
+
const elapsed2 = Date.now() - startTime;
|
|
28070
|
+
log("ets.count.estimated", { fileCount, estimatedLines: fileCount * ESTIMATED_LINES_PER_FILE, elapsed: elapsed2 });
|
|
28071
|
+
return {
|
|
28072
|
+
fileCount,
|
|
28073
|
+
totalLines: fileCount * ESTIMATED_LINES_PER_FILE,
|
|
28074
|
+
files: [],
|
|
28075
|
+
timestamp: Date.now()
|
|
28076
|
+
};
|
|
28077
|
+
}
|
|
28078
|
+
const files = [];
|
|
28079
|
+
for (const filePath of filePaths) {
|
|
28080
|
+
try {
|
|
28081
|
+
const content = await readFile2(filePath, "utf-8");
|
|
28082
|
+
files.push({ path: filePath, lines: content.split("\n").length });
|
|
28083
|
+
} catch {
|
|
28084
|
+
}
|
|
28085
|
+
}
|
|
28086
|
+
const elapsed = Date.now() - startTime;
|
|
28087
|
+
log("ets.count.precise", { fileCount, totalLines: files.reduce((sum, f) => sum + f.lines, 0), elapsed });
|
|
28088
|
+
return {
|
|
28089
|
+
fileCount,
|
|
28090
|
+
totalLines: files.reduce((sum, f) => sum + f.lines, 0),
|
|
28091
|
+
files,
|
|
28092
|
+
timestamp: Date.now()
|
|
28093
|
+
};
|
|
28094
|
+
}
|
|
28095
|
+
|
|
28076
28096
|
// src/tools/create-template/ets-check.ts
|
|
28077
28097
|
async function checkEtsExists(directory) {
|
|
28078
28098
|
try {
|
|
@@ -28124,24 +28144,20 @@ function createHmTemplateTool(managers) {
|
|
|
28124
28144
|
});
|
|
28125
28145
|
}
|
|
28126
28146
|
|
|
28127
|
-
// src/tools/skill-search/skill-search-tool.ts
|
|
28128
|
-
import { homedir as homedir3 } from "node:os";
|
|
28129
|
-
import { join as join4 } from "node:path";
|
|
28130
|
-
|
|
28131
28147
|
// src/tools/skill-search/search-skill.ts
|
|
28132
|
-
import { readFileSync } from "node:fs";
|
|
28133
|
-
import {
|
|
28134
|
-
import { join as join3 } from "node:path";
|
|
28148
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
28149
|
+
import { join as join4 } from "node:path";
|
|
28135
28150
|
|
|
28136
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;
|
|
28137
28155
|
var segmenter = new Intl.Segmenter("zh", { granularity: "word" });
|
|
28138
|
-
var SEG_RE = /[一-鿿㐀-䶿]+|[^\s\p{P}一-鿿㐀-䶿]+/gu;
|
|
28139
|
-
var CAMEL_RE = /[A-Z]?[a-z]+|[A-Z]+(?![a-z])/g;
|
|
28140
28156
|
function tokenize(text) {
|
|
28141
28157
|
const segments = text.match(SEG_RE) ?? [];
|
|
28142
28158
|
const tokens = [];
|
|
28143
28159
|
for (const seg of segments) {
|
|
28144
|
-
if (
|
|
28160
|
+
if (CJK_RE.test(seg)) {
|
|
28145
28161
|
for (const s of segmenter.segment(seg)) {
|
|
28146
28162
|
if (s.isWordLike) tokens.push(s.segment.toLowerCase().trim());
|
|
28147
28163
|
}
|
|
@@ -28157,7 +28173,7 @@ function tokenize(text) {
|
|
|
28157
28173
|
}
|
|
28158
28174
|
}
|
|
28159
28175
|
}
|
|
28160
|
-
return tokens.filter((w) => w.length > 0);
|
|
28176
|
+
return [...new Set(tokens.filter((w) => w.length > 0))];
|
|
28161
28177
|
}
|
|
28162
28178
|
|
|
28163
28179
|
// src/tools/skill-search/bm25.ts
|
|
@@ -28312,14 +28328,198 @@ var filter_default = [
|
|
|
28312
28328
|
{ id: "25", summary: "WaterFlow \u7011\u5E03\u6D41\u5E03\u5C40\u5F00\u53D1\u5B9E\u8DF5", path: "experience/experience_water_flow.md", category: "water_flow" }
|
|
28313
28329
|
];
|
|
28314
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
|
+
|
|
28315
28502
|
// src/tools/skill-search/search-skill.ts
|
|
28316
|
-
var DEFAULT_SKILL_PATH = join3(homedir2(), ".config", "opencode", "skills", "harmonyos-atomic-dev");
|
|
28317
28503
|
var cachedEntries = null;
|
|
28318
28504
|
var cachedIndexableEntries = null;
|
|
28319
28505
|
var cachedFilters = null;
|
|
28320
28506
|
var cachedRetriever = null;
|
|
28321
|
-
|
|
28322
|
-
|
|
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");
|
|
28323
28523
|
const entries = JSON.parse(raw);
|
|
28324
28524
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
28325
28525
|
return false;
|
|
@@ -28332,26 +28532,45 @@ function loadAndCache(resolvedPath) {
|
|
|
28332
28532
|
const retriever = new HybridRetriever();
|
|
28333
28533
|
retriever.addDocuments(documents, tokenizedDocs);
|
|
28334
28534
|
cachedRetriever = retriever;
|
|
28535
|
+
log("success to load sdk cache");
|
|
28335
28536
|
return true;
|
|
28336
28537
|
}
|
|
28337
|
-
function
|
|
28338
|
-
|
|
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) {
|
|
28339
28558
|
if (!cachedEntries) {
|
|
28340
28559
|
try {
|
|
28341
|
-
if (!loadAndCache(
|
|
28560
|
+
if (!await loadAndCache(skill_path?.trim())) return [];
|
|
28342
28561
|
} catch {
|
|
28343
28562
|
return [];
|
|
28344
28563
|
}
|
|
28345
28564
|
}
|
|
28565
|
+
const prefix = skill_path.replace(/[\/\\]+$/, "");
|
|
28346
28566
|
const preFiltered = [];
|
|
28347
28567
|
for (const [category, entry] of cachedFilters) {
|
|
28348
28568
|
const idx = query.toLowerCase().indexOf(category);
|
|
28349
28569
|
if (idx !== -1) {
|
|
28350
28570
|
preFiltered.push({
|
|
28351
28571
|
ets_file_path: "",
|
|
28352
|
-
experience_file_path: entry.path,
|
|
28353
|
-
|
|
28354
|
-
range: 0
|
|
28572
|
+
experience_file_path: join4(prefix, entry.path),
|
|
28573
|
+
sdk_file_path: ""
|
|
28355
28574
|
});
|
|
28356
28575
|
query = (query.slice(0, idx) + query.slice(idx + category.length)).trim();
|
|
28357
28576
|
}
|
|
@@ -28359,23 +28578,42 @@ function searchSkill(skill_path, query, topK = 5) {
|
|
|
28359
28578
|
if (!query) {
|
|
28360
28579
|
return preFiltered;
|
|
28361
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
|
+
})) : [];
|
|
28362
28587
|
const searchResults = cachedRetriever.search(query, topK);
|
|
28363
|
-
const MIN_SCORE = 2e-3;
|
|
28364
28588
|
const filtered = searchResults.filter((r) => r.score >= MIN_SCORE);
|
|
28365
|
-
|
|
28366
|
-
return [...preFiltered, ...
|
|
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()];
|
|
28367
28606
|
}
|
|
28368
|
-
function convert_search_result(searchResults) {
|
|
28607
|
+
function convert_search_result(searchResults, prefix) {
|
|
28369
28608
|
return searchResults.map((result, rank) => {
|
|
28370
28609
|
const entry = cachedIndexableEntries[result.index];
|
|
28371
28610
|
const experience = cachedEntries.find(
|
|
28372
28611
|
(e) => e.type === "experience" && e.category === entry.category
|
|
28373
28612
|
);
|
|
28374
28613
|
return {
|
|
28375
|
-
ets_file_path: entry.path,
|
|
28376
|
-
experience_file_path: experience?.path
|
|
28377
|
-
|
|
28378
|
-
range: rank + 1
|
|
28614
|
+
ets_file_path: join4(prefix, entry.path),
|
|
28615
|
+
experience_file_path: experience?.path ? join4(prefix, experience.path) : "",
|
|
28616
|
+
sdk_file_path: ""
|
|
28379
28617
|
};
|
|
28380
28618
|
});
|
|
28381
28619
|
}
|
|
@@ -28383,19 +28621,27 @@ function convert_search_result(searchResults) {
|
|
|
28383
28621
|
// src/tools/skill-search/skill-search-tool.ts
|
|
28384
28622
|
function skillSearchTool(managers) {
|
|
28385
28623
|
return tool({
|
|
28386
|
-
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.
|
|
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.",
|
|
28387
28625
|
args: {
|
|
28388
|
-
skill_path: tool.schema.string("Absolute path to the skill directory
|
|
28389
|
-
query: tool.schema.string("A decomposed requirement or intent describing what you want to find, broken down into searchable keywords separated by spaces."),
|
|
28390
|
-
topK: tool.schema.number("Maximum number of top-ranked documents to return. Actual results may be fewer depending on query relevance.").min(1).max(
|
|
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)
|
|
28391
28629
|
},
|
|
28392
28630
|
execute: async (args, context) => {
|
|
28393
28631
|
try {
|
|
28394
|
-
const results = searchSkill(args.skill_path, args.query, args.topK);
|
|
28632
|
+
const results = await searchSkill(args.skill_path, args.query, args.topK);
|
|
28395
28633
|
if (results.length === 0) {
|
|
28396
28634
|
return "No relative items found in the skill directory.";
|
|
28397
28635
|
}
|
|
28398
|
-
|
|
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");
|
|
28642
|
+
}).join("\n\n");
|
|
28643
|
+
return `You can read the following files as needed to obtain information.
|
|
28644
|
+
${sdkInfo}`;
|
|
28399
28645
|
} catch (e) {
|
|
28400
28646
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
28401
28647
|
}
|
|
@@ -28427,7 +28673,7 @@ function createTools(args) {
|
|
|
28427
28673
|
}
|
|
28428
28674
|
|
|
28429
28675
|
// src/hooks/tool-hooks.ts
|
|
28430
|
-
import { readFile as
|
|
28676
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
28431
28677
|
|
|
28432
28678
|
// node_modules/diff/lib/index.mjs
|
|
28433
28679
|
function Diff() {
|
|
@@ -29012,7 +29258,7 @@ function createToolHooks(sessionManager, projectDir) {
|
|
|
29012
29258
|
if (input.tool === "write") {
|
|
29013
29259
|
const filePath = output.args?.filePath;
|
|
29014
29260
|
if (filePath) {
|
|
29015
|
-
const oldContent = await
|
|
29261
|
+
const oldContent = await readFile4(filePath, "utf-8").catch(() => "");
|
|
29016
29262
|
writeContentCache.set(input.callID, oldContent);
|
|
29017
29263
|
}
|
|
29018
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.
|
|
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",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18.0.0"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|