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/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "toolflow",
|
|
3
|
+
"version": "3.1.4",
|
|
4
|
+
"description": "Dynamic tool sandboxing & context dehydrator for Pi: stage-gated tool pruning, 95% token dehydration, physical blast radius guard, and native prompt workbench.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"pi": {
|
|
8
|
+
"extensions": [
|
|
9
|
+
"src/index.ts"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/3ZEROS12/toolflow.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/3ZEROS12/toolflow#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/3ZEROS12/toolflow/issues"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "npx tsx tests/test_suite.ts"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"pi-package",
|
|
25
|
+
"pi-extension",
|
|
26
|
+
"workflow",
|
|
27
|
+
"blueprint",
|
|
28
|
+
"toolflow",
|
|
29
|
+
"tool-pruning",
|
|
30
|
+
"interactive-sop",
|
|
31
|
+
"dehydration",
|
|
32
|
+
"blast-radius-guard"
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": ">=0.80.0"
|
|
36
|
+
},
|
|
37
|
+
"author": "Jason",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.17.0",
|
|
41
|
+
"@earendil-works/pi-coding-agent": ">=0.80.0",
|
|
42
|
+
"@earendil-works/pi-tui": ">=0.80.0",
|
|
43
|
+
"tsx": "^4.19.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import type { BlueprintStage } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export interface ToolCallSecurityEvent {
|
|
5
|
+
toolName: string;
|
|
6
|
+
input?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 工作区防呆与影响面护栏 (Workspace Accident Guard)
|
|
11
|
+
*
|
|
12
|
+
* 设计哲学与安全边界声明:
|
|
13
|
+
* 1. 定位为防呆护栏 (Accident Guard),主要防止 Agent 误修改工作区外的系统目录、.git 内部数据或覆写生产配置;
|
|
14
|
+
* 2. 在无操作系统内核沙盒前提下,明确不承诺抵御恶意的 Shell 混淆攻击;
|
|
15
|
+
* 3. 核心依靠 CWD 物理路径边界硬拦截,而非虚假的用户态正则穷举与多余的外部命令依赖。
|
|
16
|
+
*/
|
|
17
|
+
const WRITE_TOOLS = new Set([
|
|
18
|
+
"write",
|
|
19
|
+
"edit",
|
|
20
|
+
"create",
|
|
21
|
+
"delete",
|
|
22
|
+
"unlink",
|
|
23
|
+
"append",
|
|
24
|
+
"patch",
|
|
25
|
+
"modify",
|
|
26
|
+
"save",
|
|
27
|
+
"overwrite"
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export class BlastRadiusGuard {
|
|
31
|
+
private allowedExactPaths: Set<string> = new Set();
|
|
32
|
+
private allowedGlobPatterns: string[] = [];
|
|
33
|
+
private strictArtifactScope: boolean = false;
|
|
34
|
+
|
|
35
|
+
public setStrictArtifactScope(enabled: boolean): void {
|
|
36
|
+
this.strictArtifactScope = enabled;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static readonly DOS_DEVICE_REGEX =
|
|
40
|
+
/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]|CONIN\$|CONOUT\$)(\..*)?$/i;
|
|
41
|
+
|
|
42
|
+
private criticalConfigPatterns: RegExp[] = [
|
|
43
|
+
/^\.env(\..+)?$/i,
|
|
44
|
+
/^\.git([\\/].+)?$/i,
|
|
45
|
+
/^package-lock\.json$/i,
|
|
46
|
+
/^pnpm-lock\.yaml$/i,
|
|
47
|
+
/^yarn\.lock$/i,
|
|
48
|
+
/^Cargo\.lock$/i,
|
|
49
|
+
/^pnpm-workspace\.yaml$/i,
|
|
50
|
+
/^turbo\.json$/i,
|
|
51
|
+
/^lerna\.json$/i,
|
|
52
|
+
/^nx\.json$/i,
|
|
53
|
+
// DOS 8.3 short name protection
|
|
54
|
+
/^(packag|pnpm-l|pnpm-w|yarn|cargo|turbo|lerna|nx)~\d+(\.(jso|yam|loc|txt))?$/i,
|
|
55
|
+
/^git~\d+([\\/].+)?$/i,
|
|
56
|
+
/^env~\d+(\..*)?$/i
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
private normalizePath(p: string): string {
|
|
60
|
+
let normalized = path.normalize(p).replace(/\\/g, "/");
|
|
61
|
+
if (process.platform === "win32") {
|
|
62
|
+
normalized = normalized
|
|
63
|
+
.split("/")
|
|
64
|
+
.map(seg => seg.replace(/[.\s]+$/, ""))
|
|
65
|
+
.join("/")
|
|
66
|
+
.toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private matchGlob(targetPath: string, globPattern: string): boolean {
|
|
72
|
+
const normTarget = this.normalizePath(targetPath).replace(/^\.\//, "");
|
|
73
|
+
const normPattern = this.normalizePath(globPattern).replace(/^\.\//, "");
|
|
74
|
+
|
|
75
|
+
if (normPattern === normTarget || normPattern === "**" || normPattern === "*") {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let regexStr = "";
|
|
80
|
+
let i = 0;
|
|
81
|
+
const p = normPattern;
|
|
82
|
+
while (i < p.length) {
|
|
83
|
+
if (p.slice(i, i + 3) === "**/") {
|
|
84
|
+
regexStr += "(?:.+/)?";
|
|
85
|
+
i += 3;
|
|
86
|
+
} else if (p.slice(i, i + 2) === "**") {
|
|
87
|
+
regexStr += ".*";
|
|
88
|
+
i += 2;
|
|
89
|
+
} else if (p[i] === "*") {
|
|
90
|
+
regexStr += "[^/]*";
|
|
91
|
+
i++;
|
|
92
|
+
} else if (p[i] === "?") {
|
|
93
|
+
regexStr += "[^/]";
|
|
94
|
+
i++;
|
|
95
|
+
} else if (/[.+^${}()|[\]\\]/.test(p[i])) {
|
|
96
|
+
regexStr += "\\" + p[i];
|
|
97
|
+
i++;
|
|
98
|
+
} else {
|
|
99
|
+
regexStr += p[i];
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const regex = new RegExp(`^${regexStr}$`, process.platform === "win32" ? "i" : undefined);
|
|
105
|
+
return regex.test(normTarget);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 根据当前 Stage 与蓝图设置动态构建写权限白名单
|
|
110
|
+
*/
|
|
111
|
+
public updateAllowedScope(stage: BlueprintStage, cwd: string = process.cwd()) {
|
|
112
|
+
this.allowedExactPaths.clear();
|
|
113
|
+
this.allowedGlobPatterns = [];
|
|
114
|
+
|
|
115
|
+
if (stage.expectedArtifact) {
|
|
116
|
+
this.allowedExactPaths.add(this.normalizePath(path.resolve(cwd, stage.expectedArtifact)));
|
|
117
|
+
}
|
|
118
|
+
if (stage.expectedArtifacts) {
|
|
119
|
+
stage.expectedArtifacts.forEach(p => {
|
|
120
|
+
this.allowedExactPaths.add(this.normalizePath(path.resolve(cwd, p)));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (stage.targetPatterns) {
|
|
124
|
+
stage.targetPatterns.forEach(p => {
|
|
125
|
+
this.allowedGlobPatterns.push(p);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 清空作用域白名单(在会话重置时恢复无白名单约束状态)
|
|
132
|
+
*/
|
|
133
|
+
public clearAllowedScope(): void {
|
|
134
|
+
this.allowedExactPaths.clear();
|
|
135
|
+
this.allowedGlobPatterns = [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 显式追加白名单路径(如阶段中由自愈推导出的动态文件)
|
|
140
|
+
*/
|
|
141
|
+
public allowPath(filePath: string, cwd: string = process.cwd()) {
|
|
142
|
+
this.allowedExactPaths.add(this.normalizePath(path.resolve(cwd, filePath)));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 检查路径是否在工作区根目录范围内 (防止跨盘符 / 越界穿越)
|
|
147
|
+
*/
|
|
148
|
+
public isPathWithinWorkspace(targetPath: string, cwd: string = process.cwd()): boolean {
|
|
149
|
+
// 跨平台盘符越界检测:在 Linux 环境下若路径包含 Windows 绝对盘符(如 D:\...),判定为跨环境外部路径
|
|
150
|
+
if (/^[a-zA-Z]:[/\\]/.test(targetPath)) {
|
|
151
|
+
if (process.platform !== "win32") {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const targetDrive = targetPath[0].toLowerCase();
|
|
155
|
+
const cwdDrive = cwd[0]?.toLowerCase();
|
|
156
|
+
if (targetDrive !== cwdDrive) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const resolved = path.resolve(cwd, targetPath);
|
|
161
|
+
const normResolved = this.normalizePath(resolved);
|
|
162
|
+
const normCwd = this.normalizePath(cwd);
|
|
163
|
+
return normResolved === normCwd || normResolved.startsWith(normCwd + "/");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 静态安全校验接口
|
|
168
|
+
*/
|
|
169
|
+
public validateFileAccess(targetPath: string, cwd: string = process.cwd()): { allowed: boolean; reason?: string } {
|
|
170
|
+
const check = this.verifyToolCall({ toolName: "write", input: { path: targetPath } }, cwd);
|
|
171
|
+
return { allowed: !check.block, reason: check.reason };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 拦截与审计 tool_call
|
|
176
|
+
*/
|
|
177
|
+
public verifyToolCall(
|
|
178
|
+
event: ToolCallSecurityEvent,
|
|
179
|
+
cwd: string = process.cwd()
|
|
180
|
+
): { block: boolean; reason?: string; escalationCandidate?: { filePath: string; absolutePath: string } } {
|
|
181
|
+
const tool = (event.toolName || "").toLowerCase();
|
|
182
|
+
if (WRITE_TOOLS.has(tool)) {
|
|
183
|
+
const targetPath = event.input?.path;
|
|
184
|
+
if (!targetPath || typeof targetPath !== "string") {
|
|
185
|
+
return { block: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// NTFS Alternate Data Streams 防御 (阻止形如 file.ts::$DATA 的流注入)
|
|
189
|
+
const sanitizedTargetPath = targetPath.replace(/^[a-zA-Z]:/, "");
|
|
190
|
+
if (sanitizedTargetPath.includes(":")) {
|
|
191
|
+
return {
|
|
192
|
+
block: true,
|
|
193
|
+
reason: `[ToolFlow 保护红线] 拦截操作:检测到非法 NTFS 附加数据流路径 "${targetPath}"!`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const resolved = path.resolve(cwd, targetPath);
|
|
198
|
+
const relative = path.relative(cwd, resolved);
|
|
199
|
+
const normResolved = this.normalizePath(resolved);
|
|
200
|
+
const normRelative = this.normalizePath(relative);
|
|
201
|
+
const normCwd = this.normalizePath(cwd);
|
|
202
|
+
const baseName = path.basename(resolved);
|
|
203
|
+
const cleanBaseName = baseName.replace(/[.\s]+$/, "").toLowerCase();
|
|
204
|
+
|
|
205
|
+
// 0. Windows DOS 保留设备名物理拦截 (CON, PRN, AUX, NUL, COM1-9, LPT1-9, CONIN$, CONOUT$)
|
|
206
|
+
const rawSegments = targetPath.split(/[\\/]/).map(s => s.replace(/[.\s]+$/, ""));
|
|
207
|
+
const pathSegments = normRelative.split("/");
|
|
208
|
+
if (
|
|
209
|
+
BlastRadiusGuard.DOS_DEVICE_REGEX.test(cleanBaseName) ||
|
|
210
|
+
rawSegments.some(s => BlastRadiusGuard.DOS_DEVICE_REGEX.test(s)) ||
|
|
211
|
+
pathSegments.some(s => BlastRadiusGuard.DOS_DEVICE_REGEX.test(s))
|
|
212
|
+
) {
|
|
213
|
+
return {
|
|
214
|
+
block: true,
|
|
215
|
+
reason: `[ToolFlow 保护红线] 拦截操作:检测到 Windows DOS 保留设备名 "${targetPath}",禁止访问!`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 1. 跨盘符越界与外部驱动器穿越拦截
|
|
220
|
+
const isInsideCwd = this.isPathWithinWorkspace(targetPath, cwd);
|
|
221
|
+
if (!isInsideCwd || pathSegments.some(seg => seg === "..") || (path.isAbsolute(relative) && relative !== resolved)) {
|
|
222
|
+
return {
|
|
223
|
+
block: true,
|
|
224
|
+
reason: `[ToolFlow 保护红线] 拦截操作:检测到试图越界访问工作区外部路径 "${targetPath}"!`
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2. 核心敏感配置文件与 .git 目录物理级绝对拦截 (无条件优先)
|
|
229
|
+
if (pathSegments.some(seg => seg === ".git" || /^git~\d+$/i.test(seg))) {
|
|
230
|
+
return {
|
|
231
|
+
block: true,
|
|
232
|
+
reason: `[ToolFlow 保护红线] 拦截操作:检测到试图访问或篡改 Git 内部结构/越界路径 "${relative}"!`
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const pattern of this.criticalConfigPatterns) {
|
|
237
|
+
if (
|
|
238
|
+
pattern.test(baseName) ||
|
|
239
|
+
pattern.test(cleanBaseName) ||
|
|
240
|
+
pattern.test(normRelative) ||
|
|
241
|
+
pattern.test(relative)
|
|
242
|
+
) {
|
|
243
|
+
return {
|
|
244
|
+
block: true,
|
|
245
|
+
reason: `[ToolFlow 保护红线] 拦截操作:检测到试图修改核心关键文件 "${baseName}",该配置处于受保护状态!`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 3. 智能写操作影响面指引 (核心敏感文件已在步骤 2 严密死锁;默认完全放开普通业务代码自由修改,若显式开启 strictArtifactScope 则严格遵循范围)
|
|
251
|
+
const hasScopeConfigured = this.allowedExactPaths.size > 0 || this.allowedGlobPatterns.length > 0;
|
|
252
|
+
if (hasScopeConfigured) {
|
|
253
|
+
const isExactMatch = this.allowedExactPaths.has(normResolved);
|
|
254
|
+
const isGlobMatch = this.allowedGlobPatterns.some(pat => {
|
|
255
|
+
return (
|
|
256
|
+
this.matchGlob(relative, pat) ||
|
|
257
|
+
this.matchGlob(targetPath, pat) ||
|
|
258
|
+
this.matchGlob(resolved, pat) ||
|
|
259
|
+
this.matchGlob(normRelative, pat)
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!isExactMatch && !isGlobMatch) {
|
|
264
|
+
if (this.strictArtifactScope) {
|
|
265
|
+
return {
|
|
266
|
+
block: true,
|
|
267
|
+
reason: `[ToolFlow 写保护提示] 目标文件 "${relative}" 处于严格限定范围外。如需修改该文件,请调整范围配置或关闭 strictArtifactScope。`,
|
|
268
|
+
escalationCandidate: {
|
|
269
|
+
filePath: relative,
|
|
270
|
+
absolutePath: resolved
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// 默认宽松模式:动态将新创建/修改的文件纳入感知集合,保障探索顺畅与后续可追踪
|
|
275
|
+
this.allowedExactPaths.add(normResolved);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 终端命令逃逸审计 (拦截通过 bash/powershell 直接写入、覆盖或删除绝密配置)
|
|
281
|
+
if (tool === "bash" || tool === "powershell" || tool === "exec") {
|
|
282
|
+
const command = (event.input?.command || event.input?.cmd || "").toString();
|
|
283
|
+
if (command) {
|
|
284
|
+
// 匹配输出重定向(> / >>)、删除指令(rm, del, Remove-Item)或写入流操作
|
|
285
|
+
const isDestructiveOrWrite = /(?:>|>>|\brm\b|\bdel\b|\bRemove-Item\b|\bset-content\b|\bout-file\b)/i.test(command);
|
|
286
|
+
if (isDestructiveOrWrite) {
|
|
287
|
+
for (const pattern of this.criticalConfigPatterns) {
|
|
288
|
+
if (pattern.test(command)) {
|
|
289
|
+
return {
|
|
290
|
+
block: true,
|
|
291
|
+
reason: `[ToolFlow 保护红线] 拦截终端命令:检测到试图通过终端脚本篡改或删除核心敏感文件(匹配受保护目标)!`
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return { block: false };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return { block: false };
|
|
301
|
+
}
|
|
302
|
+
}
|