@arrislink/axon 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/README.zh-CN.md +9 -8
- package/dist/index.js +208 -189
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
Axon is a unified AI-assisted development environment that solves context loss, wheel reinvention, and planning chaos in AI-powered programming. **Powered by [OpenCode](https://github.com/anomalyco/opencode) and [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)**, Axon orchestrates these powerful tools through specification-driven development and task management.
|
|
12
12
|
|
|
13
|
-
## ✨
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- **🤖
|
|
20
|
-
-
|
|
13
|
+
## ✨ Why Axon?
|
|
14
|
+
|
|
15
|
+
**Axon transforms AI from a "Code Autocompleter" into a "Development Partner".**
|
|
16
|
+
|
|
17
|
+
- **🧠 Spec-First**: Don't just chat. Define requirements in `spec.md` to keep the AI focused.
|
|
18
|
+
- **🗺️ Bead Planning**: Complex features are broken into atomic, dependency-sorted tasks (Beads).
|
|
19
|
+
- **🤖 Agentic Execution**: **OpenCode** agents execute tasks one-by-one, ensuring context and quality.
|
|
20
|
+
- **♻️ Skill Reuse**: Automatically apply proven patterns (e.g., "Secure Auth") from your team's library.
|
|
21
|
+
- **🛡️ Enterprise Safe**: Token budgeting, Git safety checks, and multi-provider failover via **OMO**.
|
|
21
22
|
|
|
22
23
|
## 🎯 Applicable Scenarios
|
|
23
24
|
|
package/README.zh-CN.md
CHANGED
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
Axon 是一个统一的 AI 辅助开发环境,解决 AI 编程中的上下文丢失、重复造轮子和规划失控问题。**由 [OpenCode](https://github.com/anomalyco/opencode) 和 [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) 驱动**,Axon 通过规格驱动开发和任务管理来编排这些强大的工具。
|
|
12
12
|
|
|
13
|
-
## ✨
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- **🤖
|
|
20
|
-
-
|
|
13
|
+
## ✨ 为什么选择 Axon?
|
|
14
|
+
|
|
15
|
+
**Axon 将 AI 从“代码补全工具”转变为真正的“开发合作伙伴”。**
|
|
16
|
+
|
|
17
|
+
- **🧠 规格优先**: 拒绝随意对话。在 `spec.md` 中定义需求,让 AI 保持专注。
|
|
18
|
+
- **🗺️ 珠子规划**: 将复杂功能拆解为原子的、按依赖排序的任务 (Beads)。
|
|
19
|
+
- **🤖 代理执行**: **OpenCode** 智能体逐个执行任务,确保上下文完整和代码质量。
|
|
20
|
+
- **♻️ 技能复用**: 自动应用团队库中经过验证的模式 (如“安全认证”)。
|
|
21
|
+
- **🛡️ 企业级安全**: Token 预算控制、Git 安全检查以及通过 **OMO** 实现的多模型故障转移。
|
|
21
22
|
|
|
22
23
|
## 🎯 适用场景
|
|
23
24
|
|
package/dist/index.js
CHANGED
|
@@ -17439,9 +17439,185 @@ var init_orchestrator = __esm(() => {
|
|
|
17439
17439
|
init_errors();
|
|
17440
17440
|
});
|
|
17441
17441
|
|
|
17442
|
+
// src/core/skills/library.ts
|
|
17443
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
17444
|
+
import { readdir, readFile, writeFile } from "fs/promises";
|
|
17445
|
+
import { join as join6, basename as basename2, dirname as dirname3 } from "path";
|
|
17446
|
+
function parseFrontmatter(content) {
|
|
17447
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
17448
|
+
if (!match) {
|
|
17449
|
+
return { metadata: {}, body: content };
|
|
17450
|
+
}
|
|
17451
|
+
const [, frontmatter, body] = match;
|
|
17452
|
+
const metadata = {};
|
|
17453
|
+
for (const line of frontmatter.split(`
|
|
17454
|
+
`)) {
|
|
17455
|
+
const colonIndex = line.indexOf(":");
|
|
17456
|
+
if (colonIndex > 0) {
|
|
17457
|
+
const key = line.slice(0, colonIndex).trim();
|
|
17458
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
17459
|
+
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
|
|
17460
|
+
try {
|
|
17461
|
+
value = JSON.parse(value);
|
|
17462
|
+
} catch {
|
|
17463
|
+
const strValue = value;
|
|
17464
|
+
const inner = strValue.slice(1, -1).trim();
|
|
17465
|
+
if (inner) {
|
|
17466
|
+
value = inner.split(",").map((s) => s.trim());
|
|
17467
|
+
} else {
|
|
17468
|
+
value = [];
|
|
17469
|
+
}
|
|
17470
|
+
}
|
|
17471
|
+
}
|
|
17472
|
+
metadata[key] = value;
|
|
17473
|
+
}
|
|
17474
|
+
}
|
|
17475
|
+
return { metadata, body: body.trim() };
|
|
17476
|
+
}
|
|
17477
|
+
|
|
17478
|
+
class SkillsLibrary {
|
|
17479
|
+
skills = [];
|
|
17480
|
+
indexed = false;
|
|
17481
|
+
paths;
|
|
17482
|
+
constructor(localPath, globalPath) {
|
|
17483
|
+
this.paths = [localPath, globalPath].filter((p) => existsSync7(p));
|
|
17484
|
+
}
|
|
17485
|
+
async index() {
|
|
17486
|
+
this.skills = [];
|
|
17487
|
+
for (const basePath of this.paths) {
|
|
17488
|
+
await this.indexDirectory(basePath);
|
|
17489
|
+
}
|
|
17490
|
+
this.indexed = true;
|
|
17491
|
+
}
|
|
17492
|
+
async indexDirectory(dir) {
|
|
17493
|
+
if (!existsSync7(dir))
|
|
17494
|
+
return;
|
|
17495
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
17496
|
+
for (const entry of entries) {
|
|
17497
|
+
const fullPath = join6(dir, entry.name);
|
|
17498
|
+
if (entry.isDirectory()) {
|
|
17499
|
+
await this.indexDirectory(fullPath);
|
|
17500
|
+
} else if (entry.name.endsWith(".md")) {
|
|
17501
|
+
try {
|
|
17502
|
+
const content = await readFile(fullPath, "utf-8");
|
|
17503
|
+
const { metadata, body } = parseFrontmatter(content);
|
|
17504
|
+
this.skills.push({
|
|
17505
|
+
metadata: {
|
|
17506
|
+
name: metadata["name"] || basename2(fullPath, ".md"),
|
|
17507
|
+
description: metadata["description"] || "",
|
|
17508
|
+
tags: metadata["tags"] || [],
|
|
17509
|
+
models: metadata["models"] || [],
|
|
17510
|
+
tokens_avg: Number(metadata["tokens_avg"]) || 2000,
|
|
17511
|
+
difficulty: metadata["difficulty"] || "medium",
|
|
17512
|
+
last_updated: metadata["last_updated"] || new Date().toISOString()
|
|
17513
|
+
},
|
|
17514
|
+
content: body,
|
|
17515
|
+
path: fullPath
|
|
17516
|
+
});
|
|
17517
|
+
} catch {}
|
|
17518
|
+
}
|
|
17519
|
+
}
|
|
17520
|
+
}
|
|
17521
|
+
async search(query, limit = 5) {
|
|
17522
|
+
if (!this.indexed) {
|
|
17523
|
+
await this.index();
|
|
17524
|
+
}
|
|
17525
|
+
const queryLower = query.toLowerCase();
|
|
17526
|
+
const results = [];
|
|
17527
|
+
for (const skill of this.skills) {
|
|
17528
|
+
let score = 0;
|
|
17529
|
+
const matchedOn = [];
|
|
17530
|
+
if (skill.metadata.name.toLowerCase().includes(queryLower)) {
|
|
17531
|
+
score += 100;
|
|
17532
|
+
matchedOn.push("name");
|
|
17533
|
+
}
|
|
17534
|
+
for (const tag of skill.metadata.tags) {
|
|
17535
|
+
if (tag.toLowerCase().includes(queryLower)) {
|
|
17536
|
+
score += 50;
|
|
17537
|
+
if (!matchedOn.includes("tags"))
|
|
17538
|
+
matchedOn.push("tags");
|
|
17539
|
+
}
|
|
17540
|
+
}
|
|
17541
|
+
if (skill.metadata.description.toLowerCase().includes(queryLower)) {
|
|
17542
|
+
score += 30;
|
|
17543
|
+
matchedOn.push("description");
|
|
17544
|
+
}
|
|
17545
|
+
const contentMatches = (skill.content.toLowerCase().match(new RegExp(queryLower, "g")) || []).length;
|
|
17546
|
+
if (contentMatches > 0) {
|
|
17547
|
+
score += contentMatches * 10;
|
|
17548
|
+
matchedOn.push("content");
|
|
17549
|
+
}
|
|
17550
|
+
if (score > 0) {
|
|
17551
|
+
results.push({ skill, score, matchedOn });
|
|
17552
|
+
}
|
|
17553
|
+
}
|
|
17554
|
+
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
17555
|
+
}
|
|
17556
|
+
async getByPath(path) {
|
|
17557
|
+
if (!this.indexed) {
|
|
17558
|
+
await this.index();
|
|
17559
|
+
}
|
|
17560
|
+
return this.skills.find((s) => s.path === path) || null;
|
|
17561
|
+
}
|
|
17562
|
+
async save(skill, targetPath) {
|
|
17563
|
+
const dir = dirname3(targetPath);
|
|
17564
|
+
if (!existsSync7(dir)) {
|
|
17565
|
+
mkdirSync4(dir, { recursive: true });
|
|
17566
|
+
}
|
|
17567
|
+
const frontmatter = [
|
|
17568
|
+
"---",
|
|
17569
|
+
`name: ${skill.metadata.name}`,
|
|
17570
|
+
`description: ${skill.metadata.description}`,
|
|
17571
|
+
`tags: ${JSON.stringify(skill.metadata.tags)}`,
|
|
17572
|
+
`models: ${JSON.stringify(skill.metadata.models)}`,
|
|
17573
|
+
`tokens_avg: ${skill.metadata.tokens_avg}`,
|
|
17574
|
+
`difficulty: ${skill.metadata.difficulty}`,
|
|
17575
|
+
`last_updated: ${new Date().toISOString().split("T")[0]}`,
|
|
17576
|
+
"---",
|
|
17577
|
+
""
|
|
17578
|
+
].join(`
|
|
17579
|
+
`);
|
|
17580
|
+
const content = frontmatter + skill.content;
|
|
17581
|
+
await writeFile(targetPath, content, "utf-8");
|
|
17582
|
+
this.indexed = false;
|
|
17583
|
+
}
|
|
17584
|
+
async list(filter) {
|
|
17585
|
+
if (!this.indexed) {
|
|
17586
|
+
await this.index();
|
|
17587
|
+
}
|
|
17588
|
+
let result = [...this.skills];
|
|
17589
|
+
if (filter?.tags?.length) {
|
|
17590
|
+
result = result.filter((s) => s.metadata.tags.some((t) => filter.tags?.includes(t)));
|
|
17591
|
+
}
|
|
17592
|
+
if (filter?.difficulty) {
|
|
17593
|
+
result = result.filter((s) => s.metadata.difficulty === filter.difficulty);
|
|
17594
|
+
}
|
|
17595
|
+
return result;
|
|
17596
|
+
}
|
|
17597
|
+
async getStats() {
|
|
17598
|
+
if (!this.indexed) {
|
|
17599
|
+
await this.index();
|
|
17600
|
+
}
|
|
17601
|
+
const byTag = {};
|
|
17602
|
+
const byDifficulty = {};
|
|
17603
|
+
for (const skill of this.skills) {
|
|
17604
|
+
for (const tag of skill.metadata.tags) {
|
|
17605
|
+
byTag[tag] = (byTag[tag] || 0) + 1;
|
|
17606
|
+
}
|
|
17607
|
+
byDifficulty[skill.metadata.difficulty] = (byDifficulty[skill.metadata.difficulty] || 0) + 1;
|
|
17608
|
+
}
|
|
17609
|
+
return {
|
|
17610
|
+
total: this.skills.length,
|
|
17611
|
+
byTag,
|
|
17612
|
+
byDifficulty
|
|
17613
|
+
};
|
|
17614
|
+
}
|
|
17615
|
+
}
|
|
17616
|
+
var init_library = () => {};
|
|
17617
|
+
|
|
17442
17618
|
// src/core/beads/executor.ts
|
|
17443
|
-
import { existsSync as
|
|
17444
|
-
import { join as
|
|
17619
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
17620
|
+
import { join as join7 } from "path";
|
|
17445
17621
|
|
|
17446
17622
|
class BeadsExecutor {
|
|
17447
17623
|
graph;
|
|
@@ -17449,15 +17625,18 @@ class BeadsExecutor {
|
|
|
17449
17625
|
config;
|
|
17450
17626
|
orchestrator;
|
|
17451
17627
|
git;
|
|
17628
|
+
skillsLibrary;
|
|
17452
17629
|
constructor(config, projectRoot, apiKey) {
|
|
17453
17630
|
this.config = config;
|
|
17454
|
-
this.graphPath =
|
|
17631
|
+
this.graphPath = join7(projectRoot, config.tools.beads.path, "graph.json");
|
|
17455
17632
|
this.graph = this.loadGraph();
|
|
17456
17633
|
this.orchestrator = new AgentOrchestrator(config, apiKey);
|
|
17457
17634
|
this.git = new GitOperations(projectRoot);
|
|
17635
|
+
const localSkillsPath = join7(projectRoot, config.tools.skills.local_path);
|
|
17636
|
+
this.skillsLibrary = new SkillsLibrary(localSkillsPath, config.tools.skills.global_path);
|
|
17458
17637
|
}
|
|
17459
17638
|
loadGraph() {
|
|
17460
|
-
if (!
|
|
17639
|
+
if (!existsSync8(this.graphPath)) {
|
|
17461
17640
|
throw new BeadsError("\u4EFB\u52A1\u56FE\u6587\u4EF6\u4E0D\u5B58\u5728", ["\u8FD0\u884C `ax plan` \u751F\u6210\u4EFB\u52A1\u56FE"]);
|
|
17462
17641
|
}
|
|
17463
17642
|
try {
|
|
@@ -17505,12 +17684,22 @@ class BeadsExecutor {
|
|
|
17505
17684
|
logger.info(`\uD83D\uDCCB \u63CF\u8FF0: ${bead.description}`);
|
|
17506
17685
|
this.updateBeadStatus(bead.id, "running");
|
|
17507
17686
|
try {
|
|
17508
|
-
const specPath =
|
|
17509
|
-
const spec =
|
|
17687
|
+
const specPath = join7(this.config.tools.openspec.path, "spec.md");
|
|
17688
|
+
const spec = existsSync8(specPath) ? readFileSync4(specPath, "utf-8") : "";
|
|
17689
|
+
let skills = [];
|
|
17690
|
+
if (this.config.tools.skills.enabled) {
|
|
17691
|
+
const query = `${bead.title} ${bead.skills_required.join(" ")}`;
|
|
17692
|
+
const searchResults = await this.skillsLibrary.search(query, 3);
|
|
17693
|
+
skills = searchResults.map((r) => r.skill);
|
|
17694
|
+
if (skills.length > 0) {
|
|
17695
|
+
logger.info(`\uD83D\uDCDA \u5339\u914D\u6280\u80FD\u6A21\u677F:`);
|
|
17696
|
+
skills.forEach((s) => logger.info(` - ${s.metadata.name}`));
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17510
17699
|
const result = await this.orchestrator.execute({
|
|
17511
17700
|
bead,
|
|
17512
17701
|
spec,
|
|
17513
|
-
skills
|
|
17702
|
+
skills
|
|
17514
17703
|
});
|
|
17515
17704
|
if (this.config.tools.beads.auto_commit && result.artifacts.files.length > 0) {
|
|
17516
17705
|
await this.commitChanges(bead, result.artifacts.commits);
|
|
@@ -17592,6 +17781,7 @@ var init_executor = __esm(() => {
|
|
|
17592
17781
|
init_git();
|
|
17593
17782
|
init_errors();
|
|
17594
17783
|
init_logger();
|
|
17784
|
+
init_library();
|
|
17595
17785
|
});
|
|
17596
17786
|
|
|
17597
17787
|
// src/core/beads/index.ts
|
|
@@ -17650,7 +17840,7 @@ async function checkCompatibility() {
|
|
|
17650
17840
|
compatible: true,
|
|
17651
17841
|
required: ">=18.0.0"
|
|
17652
17842
|
});
|
|
17653
|
-
if (process.env
|
|
17843
|
+
if (process.env["DEBUG"]) {
|
|
17654
17844
|
logger.debug("Compatibility Checks:");
|
|
17655
17845
|
checks.forEach((c) => {
|
|
17656
17846
|
logger.debug(` ${c.tool}: ${c.version} (Required: ${c.required})`);
|
|
@@ -24828,8 +25018,8 @@ specCommand.command("validate").description("\u9A8C\u8BC1\u89C4\u683C\u6587\u686
|
|
|
24828
25018
|
});
|
|
24829
25019
|
// src/commands/plan.ts
|
|
24830
25020
|
init_source();
|
|
24831
|
-
import { existsSync as
|
|
24832
|
-
import { join as
|
|
25021
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
|
|
25022
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
24833
25023
|
init_errors();
|
|
24834
25024
|
init_logger();
|
|
24835
25025
|
var planCommand = new Command("plan").description("\u4ECE\u89C4\u683C\u6587\u6863\u751F\u6210\u4EFB\u52A1\u56FE").option("--visualize", "\u5728\u6D4F\u89C8\u5668\u4E2D\u53EF\u89C6\u5316").option("--output <path>", "\u81EA\u5B9A\u4E49\u8F93\u51FA\u8DEF\u5F84").option("--model <name>", "\u6307\u5B9A\u4F7F\u7528\u7684\u6A21\u578B").option("--dry-run", "\u53EA\u9A8C\u8BC1\uFF0C\u4E0D\u751F\u6210").action(async (options) => {
|
|
@@ -24844,8 +25034,8 @@ var planCommand = new Command("plan").description("\u4ECE\u89C4\u683C\u6587\u686
|
|
|
24844
25034
|
}
|
|
24845
25035
|
const configManager = new ConfigManager(projectRoot);
|
|
24846
25036
|
const config = configManager.get();
|
|
24847
|
-
const specPath =
|
|
24848
|
-
if (!
|
|
25037
|
+
const specPath = join8(projectRoot, config.tools.openspec.path, "spec.md");
|
|
25038
|
+
if (!existsSync9(specPath)) {
|
|
24849
25039
|
throw new AxonError("\u89C4\u683C\u6587\u6863\u4E0D\u5B58\u5728", "PLAN_ERROR", [
|
|
24850
25040
|
"\u4F7F\u7528 `ax spec init` \u521B\u5EFA\u89C4\u683C\u6587\u6863"
|
|
24851
25041
|
]);
|
|
@@ -24873,10 +25063,10 @@ var planCommand = new Command("plan").description("\u4ECE\u89C4\u683C\u6587\u686
|
|
|
24873
25063
|
throw new AxonError("\u4EFB\u52A1\u56FE\u9A8C\u8BC1\u5931\u8D25", "PLAN_ERROR");
|
|
24874
25064
|
}
|
|
24875
25065
|
spinner.succeed("\u4EFB\u52A1\u56FE\u9A8C\u8BC1\u901A\u8FC7");
|
|
24876
|
-
const graphPath = options.output ||
|
|
24877
|
-
const graphDir =
|
|
24878
|
-
if (!
|
|
24879
|
-
|
|
25066
|
+
const graphPath = options.output || join8(projectRoot, config.tools.beads.path, "graph.json");
|
|
25067
|
+
const graphDir = dirname4(graphPath);
|
|
25068
|
+
if (!existsSync9(graphDir)) {
|
|
25069
|
+
mkdirSync5(graphDir, { recursive: true });
|
|
24880
25070
|
}
|
|
24881
25071
|
writeFileSync4(graphPath, JSON.stringify(graph2, null, 2), "utf-8");
|
|
24882
25072
|
logger.blank();
|
|
@@ -25032,180 +25222,9 @@ init_source();
|
|
|
25032
25222
|
import { existsSync as existsSync10 } from "fs";
|
|
25033
25223
|
import { join as join9, basename as basename3 } from "path";
|
|
25034
25224
|
|
|
25035
|
-
// src/core/skills/
|
|
25036
|
-
|
|
25037
|
-
import { readdir, readFile, writeFile } from "fs/promises";
|
|
25038
|
-
import { join as join8, basename as basename2, dirname as dirname4 } from "path";
|
|
25039
|
-
function parseFrontmatter(content) {
|
|
25040
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
25041
|
-
if (!match) {
|
|
25042
|
-
return { metadata: {}, body: content };
|
|
25043
|
-
}
|
|
25044
|
-
const [, frontmatter, body] = match;
|
|
25045
|
-
const metadata = {};
|
|
25046
|
-
for (const line of frontmatter.split(`
|
|
25047
|
-
`)) {
|
|
25048
|
-
const colonIndex = line.indexOf(":");
|
|
25049
|
-
if (colonIndex > 0) {
|
|
25050
|
-
const key = line.slice(0, colonIndex).trim();
|
|
25051
|
-
let value = line.slice(colonIndex + 1).trim();
|
|
25052
|
-
if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
|
|
25053
|
-
try {
|
|
25054
|
-
value = JSON.parse(value);
|
|
25055
|
-
} catch {
|
|
25056
|
-
const strValue = value;
|
|
25057
|
-
const inner = strValue.slice(1, -1).trim();
|
|
25058
|
-
if (inner) {
|
|
25059
|
-
value = inner.split(",").map((s) => s.trim());
|
|
25060
|
-
} else {
|
|
25061
|
-
value = [];
|
|
25062
|
-
}
|
|
25063
|
-
}
|
|
25064
|
-
}
|
|
25065
|
-
metadata[key] = value;
|
|
25066
|
-
}
|
|
25067
|
-
}
|
|
25068
|
-
return { metadata, body: body.trim() };
|
|
25069
|
-
}
|
|
25225
|
+
// src/core/skills/index.ts
|
|
25226
|
+
init_library();
|
|
25070
25227
|
|
|
25071
|
-
class SkillsLibrary {
|
|
25072
|
-
skills = [];
|
|
25073
|
-
indexed = false;
|
|
25074
|
-
paths;
|
|
25075
|
-
constructor(localPath, globalPath) {
|
|
25076
|
-
this.paths = [localPath, globalPath].filter((p) => existsSync9(p));
|
|
25077
|
-
}
|
|
25078
|
-
async index() {
|
|
25079
|
-
this.skills = [];
|
|
25080
|
-
for (const basePath of this.paths) {
|
|
25081
|
-
await this.indexDirectory(basePath);
|
|
25082
|
-
}
|
|
25083
|
-
this.indexed = true;
|
|
25084
|
-
}
|
|
25085
|
-
async indexDirectory(dir) {
|
|
25086
|
-
if (!existsSync9(dir))
|
|
25087
|
-
return;
|
|
25088
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
25089
|
-
for (const entry of entries) {
|
|
25090
|
-
const fullPath = join8(dir, entry.name);
|
|
25091
|
-
if (entry.isDirectory()) {
|
|
25092
|
-
await this.indexDirectory(fullPath);
|
|
25093
|
-
} else if (entry.name.endsWith(".md")) {
|
|
25094
|
-
try {
|
|
25095
|
-
const content = await readFile(fullPath, "utf-8");
|
|
25096
|
-
const { metadata, body } = parseFrontmatter(content);
|
|
25097
|
-
this.skills.push({
|
|
25098
|
-
metadata: {
|
|
25099
|
-
name: metadata["name"] || basename2(fullPath, ".md"),
|
|
25100
|
-
description: metadata["description"] || "",
|
|
25101
|
-
tags: metadata["tags"] || [],
|
|
25102
|
-
models: metadata["models"] || [],
|
|
25103
|
-
tokens_avg: Number(metadata["tokens_avg"]) || 2000,
|
|
25104
|
-
difficulty: metadata["difficulty"] || "medium",
|
|
25105
|
-
last_updated: metadata["last_updated"] || new Date().toISOString()
|
|
25106
|
-
},
|
|
25107
|
-
content: body,
|
|
25108
|
-
path: fullPath
|
|
25109
|
-
});
|
|
25110
|
-
} catch {}
|
|
25111
|
-
}
|
|
25112
|
-
}
|
|
25113
|
-
}
|
|
25114
|
-
async search(query, limit = 5) {
|
|
25115
|
-
if (!this.indexed) {
|
|
25116
|
-
await this.index();
|
|
25117
|
-
}
|
|
25118
|
-
const queryLower = query.toLowerCase();
|
|
25119
|
-
const results = [];
|
|
25120
|
-
for (const skill of this.skills) {
|
|
25121
|
-
let score = 0;
|
|
25122
|
-
const matchedOn = [];
|
|
25123
|
-
if (skill.metadata.name.toLowerCase().includes(queryLower)) {
|
|
25124
|
-
score += 100;
|
|
25125
|
-
matchedOn.push("name");
|
|
25126
|
-
}
|
|
25127
|
-
for (const tag of skill.metadata.tags) {
|
|
25128
|
-
if (tag.toLowerCase().includes(queryLower)) {
|
|
25129
|
-
score += 50;
|
|
25130
|
-
if (!matchedOn.includes("tags"))
|
|
25131
|
-
matchedOn.push("tags");
|
|
25132
|
-
}
|
|
25133
|
-
}
|
|
25134
|
-
if (skill.metadata.description.toLowerCase().includes(queryLower)) {
|
|
25135
|
-
score += 30;
|
|
25136
|
-
matchedOn.push("description");
|
|
25137
|
-
}
|
|
25138
|
-
const contentMatches = (skill.content.toLowerCase().match(new RegExp(queryLower, "g")) || []).length;
|
|
25139
|
-
if (contentMatches > 0) {
|
|
25140
|
-
score += contentMatches * 10;
|
|
25141
|
-
matchedOn.push("content");
|
|
25142
|
-
}
|
|
25143
|
-
if (score > 0) {
|
|
25144
|
-
results.push({ skill, score, matchedOn });
|
|
25145
|
-
}
|
|
25146
|
-
}
|
|
25147
|
-
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
25148
|
-
}
|
|
25149
|
-
async getByPath(path) {
|
|
25150
|
-
if (!this.indexed) {
|
|
25151
|
-
await this.index();
|
|
25152
|
-
}
|
|
25153
|
-
return this.skills.find((s) => s.path === path) || null;
|
|
25154
|
-
}
|
|
25155
|
-
async save(skill, targetPath) {
|
|
25156
|
-
const dir = dirname4(targetPath);
|
|
25157
|
-
if (!existsSync9(dir)) {
|
|
25158
|
-
mkdirSync5(dir, { recursive: true });
|
|
25159
|
-
}
|
|
25160
|
-
const frontmatter = [
|
|
25161
|
-
"---",
|
|
25162
|
-
`name: ${skill.metadata.name}`,
|
|
25163
|
-
`description: ${skill.metadata.description}`,
|
|
25164
|
-
`tags: ${JSON.stringify(skill.metadata.tags)}`,
|
|
25165
|
-
`models: ${JSON.stringify(skill.metadata.models)}`,
|
|
25166
|
-
`tokens_avg: ${skill.metadata.tokens_avg}`,
|
|
25167
|
-
`difficulty: ${skill.metadata.difficulty}`,
|
|
25168
|
-
`last_updated: ${new Date().toISOString().split("T")[0]}`,
|
|
25169
|
-
"---",
|
|
25170
|
-
""
|
|
25171
|
-
].join(`
|
|
25172
|
-
`);
|
|
25173
|
-
const content = frontmatter + skill.content;
|
|
25174
|
-
await writeFile(targetPath, content, "utf-8");
|
|
25175
|
-
this.indexed = false;
|
|
25176
|
-
}
|
|
25177
|
-
async list(filter) {
|
|
25178
|
-
if (!this.indexed) {
|
|
25179
|
-
await this.index();
|
|
25180
|
-
}
|
|
25181
|
-
let result = [...this.skills];
|
|
25182
|
-
if (filter?.tags?.length) {
|
|
25183
|
-
result = result.filter((s) => s.metadata.tags.some((t) => filter.tags?.includes(t)));
|
|
25184
|
-
}
|
|
25185
|
-
if (filter?.difficulty) {
|
|
25186
|
-
result = result.filter((s) => s.metadata.difficulty === filter.difficulty);
|
|
25187
|
-
}
|
|
25188
|
-
return result;
|
|
25189
|
-
}
|
|
25190
|
-
async getStats() {
|
|
25191
|
-
if (!this.indexed) {
|
|
25192
|
-
await this.index();
|
|
25193
|
-
}
|
|
25194
|
-
const byTag = {};
|
|
25195
|
-
const byDifficulty = {};
|
|
25196
|
-
for (const skill of this.skills) {
|
|
25197
|
-
for (const tag of skill.metadata.tags) {
|
|
25198
|
-
byTag[tag] = (byTag[tag] || 0) + 1;
|
|
25199
|
-
}
|
|
25200
|
-
byDifficulty[skill.metadata.difficulty] = (byDifficulty[skill.metadata.difficulty] || 0) + 1;
|
|
25201
|
-
}
|
|
25202
|
-
return {
|
|
25203
|
-
total: this.skills.length,
|
|
25204
|
-
byTag,
|
|
25205
|
-
byDifficulty
|
|
25206
|
-
};
|
|
25207
|
-
}
|
|
25208
|
-
}
|
|
25209
25228
|
// src/commands/skills.ts
|
|
25210
25229
|
init_logger();
|
|
25211
25230
|
init_errors();
|