toolflow 3.1.4
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/.github/workflows/ci.yml +31 -0
- package/README.md +106 -0
- package/README_zh.md +109 -0
- package/docs/reports/ADVANCED_EVOLUTION_REPORT.md +44 -0
- package/docs/reports/AUDIT_AND_OPTIMIZATION_REPORT.md +645 -0
- package/docs/reports/COLD_START_REVIEW_EVOLUTION.md +51 -0
- package/docs/reports/DEEP_ECOSYSTEM_EVOLUTION.md +43 -0
- package/docs/reports/MEMORY.md +18 -0
- package/docs/reports/MICHAEL_DISPATCH_RESULT.md +36 -0
- package/docs/reports/OPENSOURCE_INTEGRATION_REPORT.md +43 -0
- package/docs/reports/PHASE_1_OPTIMIZATION_REPORT.md +87 -0
- package/docs/reports/PHASE_2_OPTIMIZATION_REPORT.md +50 -0
- package/docs/reports/PHASE_3_OPTIMIZATION_REPORT.md +24 -0
- package/docs/reports/PHASE_4_OPTIMIZATION_REPORT.md +28 -0
- package/docs/reports/REPORT_TO_MICHAEL.md +101 -0
- package/docs/reports/SIGNOFF_AND_RELEASE_REPORT.md +85 -0
- package/docs/reports/STAFF_ASSIGNMENTS.md +26 -0
- package/docs/reports/TASK_ASSIGNMENTS.md +59 -0
- package/docs/reports/V1_6_0_EVOLUTION_REPORT.md +48 -0
- package/docs/reports/V1_9_0_HOTFIX_REPORT.md +30 -0
- package/docs/reports/V2_0_0_RELEASE_REPORT.md +18 -0
- package/docs/reports/V2_2_0_ZERO_SPECIALIZATION_REPORT.md +24 -0
- package/docs/reports/V2_3_0_EVOLUTION_REPORT.md +12 -0
- package/ecosystem_taxonomy.json +798 -0
- package/package.json +46 -0
- package/src/blast_radius.ts +302 -0
- package/src/deep_ecosystem.ts +523 -0
- package/src/degradation_matrix.ts +180 -0
- package/src/dehydrator.ts +532 -0
- package/src/ecosystem_taxonomy.json +803 -0
- package/src/engine.ts +1510 -0
- package/src/i18n.ts +89 -0
- package/src/index.ts +983 -0
- package/src/json_extractor.ts +57 -0
- package/src/memory.ts +151 -0
- package/src/prompts_manager.ts +262 -0
- package/src/review_isolation.ts +188 -0
- package/src/state.ts +810 -0
- package/src/taxonomy.ts +580 -0
- package/src/types.ts +341 -0
- package/src/ui.ts +1036 -0
- package/src/worker_orchestrator.ts +60 -0
- package/tests/challenger_stress_harness.ts +265 -0
- package/tests/monorepo_multilang_stress.ts +404 -0
- package/tests/sandbox_e2e.ts +167 -0
- package/tests/test_json_extractor.ts +44 -0
- package/tests/test_modules_1_to_4.ts +106 -0
- package/tests/test_suite.ts +1689 -0
- package/tsconfig.json +17 -0
package/src/taxonomy.ts
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { EcosystemTaxonomy, CapabilityItem, LayerType, ProjectFingerprint, ProjectType } from "./types.js";
|
|
7
|
+
|
|
8
|
+
import { extractValidJsonObject } from "./json_extractor.js";
|
|
9
|
+
|
|
10
|
+
const PI_AGENT_BASE = process.env.PI_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const LOCAL_TAXONOMY_PATH = path.resolve(__dirname, "ecosystem_taxonomy.json");
|
|
13
|
+
const ROOT_TAXONOMY_PATH = path.resolve(__dirname, "..", "ecosystem_taxonomy.json");
|
|
14
|
+
// 动态相对寻址:优先包内当前目录与上一级,彻底消除任何写死路径的假设
|
|
15
|
+
const TAXONOMY_PATH = fs.existsSync(LOCAL_TAXONOMY_PATH)
|
|
16
|
+
? LOCAL_TAXONOMY_PATH
|
|
17
|
+
: (fs.existsSync(ROOT_TAXONOMY_PATH) ? ROOT_TAXONOMY_PATH : LOCAL_TAXONOMY_PATH);
|
|
18
|
+
const SETTINGS_PATH = path.join(PI_AGENT_BASE, "settings.json");
|
|
19
|
+
const NPM_MODULES_PATH = path.join(PI_AGENT_BASE, "npm", "node_modules");
|
|
20
|
+
const PROMPTS_PATH = path.join(PI_AGENT_BASE, "prompts");
|
|
21
|
+
|
|
22
|
+
function computeHash(str: string): string {
|
|
23
|
+
return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 清理包名前缀 (如 npm:pi-rewind -> pi-rewind, git:github.com/.../foo -> foo) */
|
|
27
|
+
export function cleanName(raw: string): string {
|
|
28
|
+
let name = raw.replace(/^npm:/, "");
|
|
29
|
+
if (name.startsWith("git:")) {
|
|
30
|
+
const parts = name.split("/");
|
|
31
|
+
name = parts[parts.length - 1] || name;
|
|
32
|
+
}
|
|
33
|
+
name = name.replace(/^@[^/]+\//, "");
|
|
34
|
+
return name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 极速探测本地工程指纹(纯 Node fs/path 同步检测,零外部子进程开销)
|
|
39
|
+
*/
|
|
40
|
+
export function sniffProjectFingerprint(cwd: string = process.cwd()): ProjectFingerprint {
|
|
41
|
+
let projectType: ProjectType = "unknown";
|
|
42
|
+
let mainFramework: string | undefined;
|
|
43
|
+
let packageManager: ProjectFingerprint["packageManager"] = "unknown";
|
|
44
|
+
const coreDependencies: string[] = [];
|
|
45
|
+
let topLevelDirs: string[] = [];
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(cwd)) {
|
|
49
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
50
|
+
const ignoredDirs = new Set(["node_modules", "target", "dist", ".git", ".pi", "build", "out"]);
|
|
51
|
+
topLevelDirs = entries
|
|
52
|
+
.filter(e => e.isDirectory() && !e.name.startsWith(".") && !ignoredDirs.has(e.name.toLowerCase()))
|
|
53
|
+
.map(e => e.name);
|
|
54
|
+
}
|
|
55
|
+
} catch (_) {}
|
|
56
|
+
|
|
57
|
+
// 1. Node / JS / TS 体系
|
|
58
|
+
const pkgJsonPath = path.join(cwd, "package.json");
|
|
59
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
60
|
+
projectType = "node";
|
|
61
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
|
|
62
|
+
packageManager = "pnpm";
|
|
63
|
+
} else if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
|
|
64
|
+
packageManager = "yarn";
|
|
65
|
+
} else if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
|
|
66
|
+
packageManager = "npm";
|
|
67
|
+
} else {
|
|
68
|
+
packageManager = "npm";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
73
|
+
const allDeps = {
|
|
74
|
+
...(pkg.dependencies || {}),
|
|
75
|
+
...(pkg.devDependencies || {}),
|
|
76
|
+
...(pkg.peerDependencies || {})
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const depKeys = Object.keys(allDeps);
|
|
80
|
+
coreDependencies.push(...depKeys.slice(0, 15));
|
|
81
|
+
|
|
82
|
+
// 常见框架嗅探
|
|
83
|
+
if (allDeps["next"]) mainFramework = "Next.js";
|
|
84
|
+
else if (allDeps["react"]) mainFramework = "React";
|
|
85
|
+
else if (allDeps["vue"]) mainFramework = "Vue";
|
|
86
|
+
else if (allDeps["@nestjs/core"]) mainFramework = "NestJS";
|
|
87
|
+
else if (allDeps["express"]) mainFramework = "Express";
|
|
88
|
+
else if (allDeps["@earendil-works/pi-coding-agent"]) mainFramework = "Pi-Extension";
|
|
89
|
+
else if (allDeps["electron"]) mainFramework = "Electron";
|
|
90
|
+
else if (allDeps["typescript"] || fs.existsSync(path.join(cwd, "tsconfig.json"))) mainFramework = "TypeScript";
|
|
91
|
+
} catch (_) {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. Rust 体系
|
|
95
|
+
const cargoPath = path.join(cwd, "Cargo.toml");
|
|
96
|
+
if (fs.existsSync(cargoPath)) {
|
|
97
|
+
projectType = "rust";
|
|
98
|
+
packageManager = "cargo";
|
|
99
|
+
try {
|
|
100
|
+
const cargoContent = fs.readFileSync(cargoPath, "utf-8");
|
|
101
|
+
if (cargoContent.includes("actix-web")) mainFramework = "Actix-Web";
|
|
102
|
+
else if (cargoContent.includes("axum")) mainFramework = "Axum";
|
|
103
|
+
else if (cargoContent.includes("tauri")) mainFramework = "Tauri";
|
|
104
|
+
else if (cargoContent.includes("tokio")) mainFramework = "Tokio";
|
|
105
|
+
} catch (_) {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. Python 体系
|
|
109
|
+
const pyprojectPath = path.join(cwd, "pyproject.toml");
|
|
110
|
+
const reqPath = path.join(cwd, "requirements.txt");
|
|
111
|
+
const pipfilePath = path.join(cwd, "Pipfile");
|
|
112
|
+
if (fs.existsSync(pyprojectPath) || fs.existsSync(reqPath) || fs.existsSync(pipfilePath)) {
|
|
113
|
+
projectType = "python";
|
|
114
|
+
if (fs.existsSync(path.join(cwd, "uv.lock"))) packageManager = "uv";
|
|
115
|
+
else if (fs.existsSync(path.join(cwd, "poetry.lock"))) packageManager = "poetry";
|
|
116
|
+
else packageManager = "pip";
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const pyContent = fs.existsSync(pyprojectPath)
|
|
120
|
+
? fs.readFileSync(pyprojectPath, "utf-8")
|
|
121
|
+
: (fs.existsSync(reqPath) ? fs.readFileSync(reqPath, "utf-8") : "");
|
|
122
|
+
if (pyContent.includes("fastapi")) mainFramework = "FastAPI";
|
|
123
|
+
else if (pyContent.includes("django")) mainFramework = "Django";
|
|
124
|
+
else if (pyContent.includes("flask")) mainFramework = "Flask";
|
|
125
|
+
else if (pyContent.includes("torch")) mainFramework = "PyTorch";
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 4. Go 体系
|
|
130
|
+
const goModPath = path.join(cwd, "go.mod");
|
|
131
|
+
if (fs.existsSync(goModPath)) {
|
|
132
|
+
projectType = "go";
|
|
133
|
+
packageManager = "go";
|
|
134
|
+
try {
|
|
135
|
+
const goContent = fs.readFileSync(goModPath, "utf-8");
|
|
136
|
+
if (goContent.includes("gin-gonic/gin")) mainFramework = "Gin";
|
|
137
|
+
else if (goContent.includes("gofiber/fiber")) mainFramework = "Fiber";
|
|
138
|
+
} catch (_) {}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 5. C/C++ 体系
|
|
142
|
+
if (fs.existsSync(path.join(cwd, "CMakeLists.txt"))) {
|
|
143
|
+
if (projectType === "unknown") projectType = "cpp";
|
|
144
|
+
if (packageManager === "unknown") packageManager = "cmake";
|
|
145
|
+
if (!mainFramework) mainFramework = "CMake";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 6. Monorepo 嗅探
|
|
149
|
+
if (fs.existsSync(path.join(cwd, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cwd, "lerna.json"))) {
|
|
150
|
+
projectType = "monorepo";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 7. Git 状态与受影响模块嗅探(纯 Node 零外部子进程,读取 HEAD 与变更)
|
|
154
|
+
const gitDir = path.join(cwd, ".git");
|
|
155
|
+
const hasGit = fs.existsSync(gitDir);
|
|
156
|
+
let isClean = true;
|
|
157
|
+
let gitBranch: string | undefined;
|
|
158
|
+
const activeModifiedPaths: string[] = [];
|
|
159
|
+
|
|
160
|
+
if (hasGit) {
|
|
161
|
+
try {
|
|
162
|
+
const headFile = path.join(gitDir, "HEAD");
|
|
163
|
+
if (fs.existsSync(headFile)) {
|
|
164
|
+
const headContent = fs.readFileSync(headFile, "utf-8").trim();
|
|
165
|
+
if (headContent.startsWith("ref: refs/heads/")) {
|
|
166
|
+
gitBranch = headContent.replace("ref: refs/heads/", "");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (_) {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
projectType,
|
|
174
|
+
mainFramework,
|
|
175
|
+
packageManager,
|
|
176
|
+
hasGit,
|
|
177
|
+
isClean,
|
|
178
|
+
topLevelDirs,
|
|
179
|
+
coreDependencies,
|
|
180
|
+
gitBranch,
|
|
181
|
+
activeModifiedPaths: activeModifiedPaths.length > 0 ? activeModifiedPaths : undefined
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function scanPrompts(): CapabilityItem[] {
|
|
186
|
+
const prompts: CapabilityItem[] = [];
|
|
187
|
+
if (fs.existsSync(PROMPTS_PATH)) {
|
|
188
|
+
const files = fs.readdirSync(PROMPTS_PATH);
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
if (file.endsWith(".md")) {
|
|
191
|
+
const id = file.replace(/\.md$/, "");
|
|
192
|
+
prompts.push({
|
|
193
|
+
id: `/${id}`,
|
|
194
|
+
name: `/${id}`,
|
|
195
|
+
kind: "prompt",
|
|
196
|
+
layer: "L1_UTILITY",
|
|
197
|
+
description: `全局提示词 [/${id}]`,
|
|
198
|
+
tokenImpact: "minimal",
|
|
199
|
+
triggerWhen: `调用 /${id}`,
|
|
200
|
+
summary: `触发 /${id} 快捷动作`,
|
|
201
|
+
tags: ["#prompt", `#${id}`],
|
|
202
|
+
costLevel: "$0"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (fs.existsSync(NPM_MODULES_PATH)) {
|
|
209
|
+
try {
|
|
210
|
+
const scanDir = (base: string) => {
|
|
211
|
+
const items = fs.readdirSync(base);
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
const full = path.join(base, item);
|
|
214
|
+
if (item.startsWith("@")) {
|
|
215
|
+
scanDir(full);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const pkgPromptsPath = path.join(full, "prompts");
|
|
219
|
+
if (fs.existsSync(pkgPromptsPath)) {
|
|
220
|
+
const files = fs.readdirSync(pkgPromptsPath);
|
|
221
|
+
for (const file of files) {
|
|
222
|
+
if (file.endsWith(".md")) {
|
|
223
|
+
const id = file.replace(/\.md$/, "");
|
|
224
|
+
let layer: LayerType = "L1_UTILITY";
|
|
225
|
+
let tokenImpact: "minimal" | "low" | "medium" | "high" = "low";
|
|
226
|
+
let costLevel: "$0" | "$1" | "$2" | "$3" = "$1";
|
|
227
|
+
if (id.includes("research")) { layer = "L2_PERCEPTION"; tokenImpact = "medium"; costLevel = "$2"; }
|
|
228
|
+
else if (id.includes("parallel") || id.includes("review-loop") || id.includes("council")) { layer = "L3_ORCHESTRATION"; tokenImpact = "high"; costLevel = "$3"; }
|
|
229
|
+
|
|
230
|
+
prompts.push({
|
|
231
|
+
id: `/${id}`,
|
|
232
|
+
name: `/${id}`,
|
|
233
|
+
kind: "prompt",
|
|
234
|
+
layer,
|
|
235
|
+
description: `扩展指令 [/${id}]`,
|
|
236
|
+
tokenImpact,
|
|
237
|
+
triggerWhen: `触发专用流水线 ${id}`,
|
|
238
|
+
summary: `执行专职流水线 ${id}`,
|
|
239
|
+
tags: ["#pipeline", `#${id}`],
|
|
240
|
+
costLevel
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
scanDir(NPM_MODULES_PATH);
|
|
248
|
+
} catch (_) {}
|
|
249
|
+
}
|
|
250
|
+
return prompts;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function inferCapabilityFromMetadata(id: string, name: string, desc: string, kind: "extension" | "skill" | "tool" | "mcp"): CapabilityItem {
|
|
254
|
+
const text = `${id} ${name} ${desc}`.toLowerCase();
|
|
255
|
+
|
|
256
|
+
let layer: LayerType = "L1_UTILITY";
|
|
257
|
+
let tokenImpact: "minimal" | "low" | "medium" | "high" = "low";
|
|
258
|
+
let costLevel: "$0" | "$1" | "$2" | "$3" = "$1";
|
|
259
|
+
const tags: string[] = [`#${kind}`, `#${cleanName(id)}`];
|
|
260
|
+
|
|
261
|
+
let bindingReason = "基础通用能力";
|
|
262
|
+
if (/(review|audit|plannotator|gate|guard|verify|assert|check|lint|test|inspect|validate|assertion|simplify)/.test(text)) {
|
|
263
|
+
layer = "L4_REVIEW_GUARD";
|
|
264
|
+
tokenImpact = "medium";
|
|
265
|
+
costLevel = "$1";
|
|
266
|
+
tags.push("#quality-guard");
|
|
267
|
+
bindingReason = "代码审查走查、断言比对与质量门禁强拦截";
|
|
268
|
+
} else if (/(workflow|subagent|orchestrat|parallel|dag|chain|spawn|lane|pipeline|council|agent|worker|batch|fanout)/.test(text)) {
|
|
269
|
+
layer = "L3_ORCHESTRATION";
|
|
270
|
+
tokenImpact = "high";
|
|
271
|
+
costLevel = "$3";
|
|
272
|
+
tags.push("#orchestration");
|
|
273
|
+
bindingReason = "多任务并发分发与子流程编排调度";
|
|
274
|
+
} else if (/(rewind|undo|git|checkpoint|snapshot|history|clean|format|diff|lock|rollback|sandbox|permission)/.test(text)) {
|
|
275
|
+
layer = "L1_UTILITY";
|
|
276
|
+
tokenImpact = "minimal";
|
|
277
|
+
costLevel = "$0";
|
|
278
|
+
tags.push("#safety");
|
|
279
|
+
bindingReason = "工作区快照管理、影响面锁定与安全回滚";
|
|
280
|
+
} else if (/(search|fetch|web|crawl|scrape|http|net|url|mcp|database|sql|api|doc|query|retrieve|read|browser|chrome|playwright|puppeteer|cdp|computer-use|gui)/.test(text)) {
|
|
281
|
+
layer = "L2_PERCEPTION";
|
|
282
|
+
tokenImpact = "medium";
|
|
283
|
+
costLevel = "$2";
|
|
284
|
+
tags.push("#perception");
|
|
285
|
+
bindingReason = "精准信息检索、浏览器控制与外部数据源接入";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const cleanDesc = desc ? desc.slice(0, 36).replace(/\r?\n/g, " ") : `动态识别组件 [${cleanName(name)}]`;
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
id: cleanName(id),
|
|
292
|
+
name: cleanName(name),
|
|
293
|
+
kind,
|
|
294
|
+
layer,
|
|
295
|
+
description: cleanDesc,
|
|
296
|
+
tokenImpact,
|
|
297
|
+
triggerWhen: `根据任务需求动态调度 ${cleanName(name)}`,
|
|
298
|
+
summary: cleanDesc,
|
|
299
|
+
tags,
|
|
300
|
+
costLevel,
|
|
301
|
+
bindingReason
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function scanMcpServers(): CapabilityItem[] {
|
|
306
|
+
const mcps: CapabilityItem[] = [];
|
|
307
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
308
|
+
try {
|
|
309
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
310
|
+
if (settings.mcpServers && typeof settings.mcpServers === "object") {
|
|
311
|
+
for (const [serverName, serverConf] of Object.entries<any>(settings.mcpServers)) {
|
|
312
|
+
const desc = serverConf?.description || `MCP 协议服务 [${serverName}]`;
|
|
313
|
+
mcps.push(inferCapabilityFromMetadata(serverName, serverName, desc, "mcp"));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (_) {}
|
|
317
|
+
}
|
|
318
|
+
return mcps;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function scanRealPiTools(registeredTools: Array<{ name: string; description?: string }> = []): CapabilityItem[] {
|
|
322
|
+
const tools: CapabilityItem[] = [];
|
|
323
|
+
for (const t of registeredTools) {
|
|
324
|
+
if (!t.name) continue;
|
|
325
|
+
tools.push(inferCapabilityFromMetadata(t.name, t.name, t.description || `宿主注册工具 [${t.name}]`, "tool"));
|
|
326
|
+
}
|
|
327
|
+
return tools;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function scanSkills(): CapabilityItem[] {
|
|
331
|
+
const skills: CapabilityItem[] = [];
|
|
332
|
+
if (fs.existsSync(NPM_MODULES_PATH)) {
|
|
333
|
+
const scanDir = (base: string) => {
|
|
334
|
+
try {
|
|
335
|
+
const items = fs.readdirSync(base);
|
|
336
|
+
for (const item of items) {
|
|
337
|
+
const full = path.join(base, item);
|
|
338
|
+
if (item.startsWith("@")) {
|
|
339
|
+
scanDir(full);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const skillsDir = path.join(full, "skills");
|
|
343
|
+
if (fs.existsSync(skillsDir)) {
|
|
344
|
+
const skillFolders = fs.readdirSync(skillsDir);
|
|
345
|
+
for (const sf of skillFolders) {
|
|
346
|
+
let desc = "";
|
|
347
|
+
const skillMdPath = path.join(skillsDir, sf, "SKILL.md");
|
|
348
|
+
if (fs.existsSync(skillMdPath)) {
|
|
349
|
+
try {
|
|
350
|
+
const content = fs.readFileSync(skillMdPath, "utf8").slice(0, 512);
|
|
351
|
+
const m = content.match(/description:\s*([^\r\n]+)/i);
|
|
352
|
+
if (m) desc = m[1].trim();
|
|
353
|
+
} catch (_) {}
|
|
354
|
+
}
|
|
355
|
+
skills.push(inferCapabilityFromMetadata(sf, sf, desc, "skill"));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch (_) {}
|
|
360
|
+
};
|
|
361
|
+
scanDir(NPM_MODULES_PATH);
|
|
362
|
+
}
|
|
363
|
+
return skills;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function scanExtensions(rawPkgs: string[]): CapabilityItem[] {
|
|
367
|
+
return rawPkgs.map((raw) => {
|
|
368
|
+
const cleaned = cleanName(raw);
|
|
369
|
+
let desc = "";
|
|
370
|
+
|
|
371
|
+
// 动态嗅探 package.json 的 description (处理 npm: 前缀以兼容 Windows 路径)
|
|
372
|
+
const normalizedPkgDir = raw.replace(/^npm:/, "");
|
|
373
|
+
const pkgJsonPath = path.join(NPM_MODULES_PATH, normalizedPkgDir, "package.json");
|
|
374
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
375
|
+
try {
|
|
376
|
+
const pkgData = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
377
|
+
desc = pkgData.description || "";
|
|
378
|
+
} catch (_) {}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return inferCapabilityFromMetadata(cleaned, cleaned, desc, "extension");
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** 生成三元高压缩特征摘要字符串 (< 350 Tokens) */
|
|
386
|
+
export function generateCapabilityCompactDigest(taxonomy: EcosystemTaxonomy): string {
|
|
387
|
+
const lines: string[] = [];
|
|
388
|
+
const allItems = [...taxonomy.extensions, ...taxonomy.skills, ...taxonomy.prompts];
|
|
389
|
+
|
|
390
|
+
for (const item of allItems) {
|
|
391
|
+
const layerTag = item.layer === "L1_UTILITY" ? "INF" : item.layer === "L2_PERCEPTION" ? "DOM" : item.layer === "L3_ORCHESTRATION" ? "ORC" : "GRD";
|
|
392
|
+
const summary = item.summary || item.description;
|
|
393
|
+
const tags = (item.tags || [`#${item.name}`]).join(", ");
|
|
394
|
+
const cost = item.costLevel || "$1";
|
|
395
|
+
lines.push(`[${layerTag}] ${item.name}: ${summary} (${tags}) <${cost}>`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function loadOrRefreshTaxonomy(
|
|
402
|
+
cwd: string = process.cwd(),
|
|
403
|
+
registeredTools: Array<{ name: string; description?: string }> = []
|
|
404
|
+
): EcosystemTaxonomy {
|
|
405
|
+
let pkgNames: string[] = [];
|
|
406
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
407
|
+
try {
|
|
408
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
409
|
+
pkgNames = Array.isArray(settings.packages) ? settings.packages : [];
|
|
410
|
+
} catch (_) {}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const projectFingerprint = sniffProjectFingerprint(cwd);
|
|
414
|
+
const toolNames = registeredTools.map(t => t.name).sort();
|
|
415
|
+
const rawKey = JSON.stringify({ pkgs: pkgNames.sort(), tools: toolNames });
|
|
416
|
+
const currentFingerprint = computeHash(rawKey);
|
|
417
|
+
|
|
418
|
+
if (fs.existsSync(TAXONOMY_PATH)) {
|
|
419
|
+
try {
|
|
420
|
+
const cached = JSON.parse(fs.readFileSync(TAXONOMY_PATH, "utf-8")) as EcosystemTaxonomy;
|
|
421
|
+
// 在纯净 CI/无预装包或命中指纹时,直接复用随包分发的静态分类库
|
|
422
|
+
if (
|
|
423
|
+
(cached.installedFingerprint === currentFingerprint || (pkgNames.length === 0 && toolNames.length === 0)) &&
|
|
424
|
+
cached.skills &&
|
|
425
|
+
cached.prompts &&
|
|
426
|
+
cached.mcps
|
|
427
|
+
) {
|
|
428
|
+
return {
|
|
429
|
+
...cached,
|
|
430
|
+
projectFingerprint,
|
|
431
|
+
availableToolNames: toolNames.length > 0 ? toolNames : cached.availableToolNames
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
} catch (_) {}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const extensions = scanExtensions(pkgNames);
|
|
438
|
+
const skills = scanSkills();
|
|
439
|
+
const prompts = scanPrompts();
|
|
440
|
+
const mcps = scanMcpServers();
|
|
441
|
+
const tools = scanRealPiTools(registeredTools);
|
|
442
|
+
|
|
443
|
+
const summaryByLayer: Record<LayerType, number> = {
|
|
444
|
+
L1_UTILITY: 0,
|
|
445
|
+
L2_PERCEPTION: 0,
|
|
446
|
+
L3_ORCHESTRATION: 0,
|
|
447
|
+
L4_REVIEW_GUARD: 0
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
[...extensions, ...skills, ...prompts, ...mcps, ...tools].forEach(item => {
|
|
451
|
+
if (item.layer && summaryByLayer[item.layer] !== undefined) {
|
|
452
|
+
summaryByLayer[item.layer]++;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const taxonomy: EcosystemTaxonomy = {
|
|
457
|
+
installedFingerprint: currentFingerprint,
|
|
458
|
+
projectFingerprint,
|
|
459
|
+
updatedAt: Date.now(),
|
|
460
|
+
extensions,
|
|
461
|
+
skills,
|
|
462
|
+
prompts,
|
|
463
|
+
mcps,
|
|
464
|
+
tools,
|
|
465
|
+
availableToolNames: toolNames,
|
|
466
|
+
summaryByLayer
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
const parentDir = path.dirname(TAXONOMY_PATH);
|
|
471
|
+
if (!fs.existsSync(parentDir)) {
|
|
472
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
473
|
+
}
|
|
474
|
+
// 比较内容,若无实质变动则不刷新 updatedAt 覆写文件,杜绝 git 伪脏数据
|
|
475
|
+
let shouldWrite = true;
|
|
476
|
+
if (fs.existsSync(TAXONOMY_PATH)) {
|
|
477
|
+
try {
|
|
478
|
+
const raw = fs.readFileSync(TAXONOMY_PATH, "utf-8");
|
|
479
|
+
const parsed = JSON.parse(raw);
|
|
480
|
+
const { updatedAt: _o, ...oldRest } = parsed;
|
|
481
|
+
const { updatedAt: _n, ...newRest } = taxonomy;
|
|
482
|
+
if (JSON.stringify(oldRest) === JSON.stringify(newRest)) {
|
|
483
|
+
shouldWrite = false;
|
|
484
|
+
}
|
|
485
|
+
} catch (_) {}
|
|
486
|
+
}
|
|
487
|
+
if (shouldWrite) {
|
|
488
|
+
fs.writeFileSync(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2), "utf-8");
|
|
489
|
+
}
|
|
490
|
+
} catch (_) {}
|
|
491
|
+
|
|
492
|
+
return taxonomy;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export async function deepAnalyzeTaxonomyWithLLM(
|
|
496
|
+
taxonomy: EcosystemTaxonomy,
|
|
497
|
+
pi: { executePrompt?: (prompt: string) => Promise<string> }
|
|
498
|
+
): Promise<EcosystemTaxonomy> {
|
|
499
|
+
if (!pi || typeof pi.executePrompt !== "function") {
|
|
500
|
+
return taxonomy;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const unclassified = [...taxonomy.extensions, ...taxonomy.skills, ...taxonomy.prompts].filter(
|
|
504
|
+
(i) => i.summary?.includes("动态识别组件") || i.bindingReason?.includes("基础通用能力") || !i.layer
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
if (unclassified.length === 0) {
|
|
508
|
+
return taxonomy;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const prompt = [
|
|
512
|
+
"[DIRECTIVE: CLASSIFY_TOOLSET]",
|
|
513
|
+
"Analyze the following installed items and classify each into: L1_UTILITY | L2_PERCEPTION | L3_ORCHESTRATION | L4_REVIEW_GUARD.",
|
|
514
|
+
"Return JSON format: { items: Array<{ id: string, layer: string, summary: string, tags: string[] }> }",
|
|
515
|
+
"",
|
|
516
|
+
"Items:",
|
|
517
|
+
...unclassified.map((u) => `- ID: ${u.id}, Name: ${u.name}, RawDesc: ${u.description.slice(0, 120)}`)
|
|
518
|
+
].join("\n");
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
const rawRes = await pi.executePrompt(prompt);
|
|
522
|
+
const parsed = extractValidJsonObject(rawRes);
|
|
523
|
+
if (parsed && Array.isArray(parsed.items)) {
|
|
524
|
+
const itemMap = new Map(parsed.items.map((it: any) => [it.id, it]));
|
|
525
|
+
const updateList = (list: CapabilityItem[]) =>
|
|
526
|
+
list.map((item) => {
|
|
527
|
+
const hit: any = itemMap.get(item.id);
|
|
528
|
+
if (hit) {
|
|
529
|
+
return {
|
|
530
|
+
...item,
|
|
531
|
+
layer: (hit.layer as LayerType) || item.layer,
|
|
532
|
+
summary: hit.summary || item.summary,
|
|
533
|
+
tags: Array.isArray(hit.tags) ? hit.tags : item.tags
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return item;
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
taxonomy.extensions = updateList(taxonomy.extensions);
|
|
540
|
+
taxonomy.skills = updateList(taxonomy.skills);
|
|
541
|
+
taxonomy.prompts = updateList(taxonomy.prompts);
|
|
542
|
+
|
|
543
|
+
const summary: Record<LayerType, number> = {
|
|
544
|
+
L1_UTILITY: 0,
|
|
545
|
+
L2_PERCEPTION: 0,
|
|
546
|
+
L3_ORCHESTRATION: 0,
|
|
547
|
+
L4_REVIEW_GUARD: 0
|
|
548
|
+
};
|
|
549
|
+
[...taxonomy.extensions, ...taxonomy.skills, ...taxonomy.prompts].forEach((i) => {
|
|
550
|
+
if (i.layer && summary[i.layer] !== undefined) summary[i.layer]++;
|
|
551
|
+
});
|
|
552
|
+
taxonomy.summaryByLayer = summary;
|
|
553
|
+
try {
|
|
554
|
+
fs.writeFileSync(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2), "utf-8");
|
|
555
|
+
} catch (_) {}
|
|
556
|
+
}
|
|
557
|
+
} catch (_) {}
|
|
558
|
+
|
|
559
|
+
return taxonomy;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 阶段三:MCP 与生态 Skills 联邦动态发现接口 (零 Token 扫描)
|
|
564
|
+
*/
|
|
565
|
+
export async function discoverEcosystemTaxonomy(
|
|
566
|
+
cwd: string = process.cwd(),
|
|
567
|
+
registeredTools: Array<{ name: string; description?: string }> = []
|
|
568
|
+
): Promise<EcosystemTaxonomy> {
|
|
569
|
+
return loadOrRefreshTaxonomy(cwd, registeredTools);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function reflectEnvironmentContext(
|
|
573
|
+
ctx?: any,
|
|
574
|
+
cwd: string = process.cwd()
|
|
575
|
+
): EcosystemTaxonomy {
|
|
576
|
+
const tools = (ctx && typeof ctx.getAllTools === "function")
|
|
577
|
+
? ctx.getAllTools()
|
|
578
|
+
: [];
|
|
579
|
+
return loadOrRefreshTaxonomy(cwd, tools);
|
|
580
|
+
}
|