@bolloon/bolloon-agent 0.1.37 → 0.1.39

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.
Files changed (31) hide show
  1. package/dist/agents/constraint-layer.js +1 -2
  2. package/package.json +1 -1
  3. package/scripts/auto-evolve-oneshot.sh +155 -0
  4. package/scripts/auto-evolve-snapshot.sh +136 -0
  5. package/scripts/build-cli.js +216 -0
  6. package/scripts/detect-schema-changes.sh +48 -0
  7. package/scripts/postinstall.js +153 -0
  8. package/dist/bollharness-integration/llm/pi-ai.js +0 -397
  9. package/dist/bollharness-integration/pi-ecosystem-colony/index.js +0 -365
  10. package/dist/bollharness-integration/pi-ecosystem-goals/index.js +0 -458
  11. package/dist/bollharness-integration/pi-ecosystem-judgment/decision.js +0 -300
  12. package/dist/bollharness-integration/pi-ecosystem-judgment/distillation.js +0 -291
  13. package/dist/bollharness-integration/pi-ecosystem-judgment/index.js +0 -445
  14. package/dist/bollharness-integration/pi-ecosystem-mcp/index.js +0 -331
  15. package/dist/bollharness-integration/pi-ecosystem-subagents/index.js +0 -303
  16. package/dist/constraints/commands.js +0 -100
  17. package/dist/constraints/permissions.js +0 -37
  18. package/dist/constraints/runtime.js +0 -135
  19. package/dist/constraints/session.js +0 -48
  20. package/dist/constraints/system-init.js +0 -51
  21. package/dist/constraints/tools.js +0 -104
  22. package/dist/llm/minimax-provider.js +0 -46
  23. package/dist/llm/minimax.js +0 -45
  24. package/dist/pi-ecosystem-colony/index.js +0 -365
  25. package/dist/runtime/context/minimax-prompt.js +0 -178
  26. package/dist/runtime/context/sys-prompt.js +0 -1
  27. package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
  28. package/dist/social/ant-colony/PheromoneEngine.js +0 -227
  29. package/dist/social/ant-colony/index.js +0 -6
  30. package/dist/social/ant-colony/types.js +0 -24
  31. package/dist/utils/double.js +0 -6
@@ -2,8 +2,7 @@
2
2
  * ConstraintLayer - Guardrails for safe autonomous document processing
3
3
  * Part of OpenClaw dual-layer architecture (Constraint Layer + Execution Layer)
4
4
  */
5
- import { ToolPermissionContext } from '../constraint-runtime/src/constraint/permission.js';
6
- import { BudgetTracker } from '../constraint-runtime/src/constraint/budget.js';
5
+ import { ToolPermissionContext, BudgetTracker } from '@bolloon/constraint-runtime';
7
6
  /**
8
7
  * System prompt for constraint layer
9
8
  * Defines boundaries and autonomous permissions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
6
6
  "main": "dist/cli.js",
@@ -0,0 +1,155 @@
1
+ #!/bin/bash
2
+ # auto-evolve-oneshot.sh — 阶段 D 单次修复 (shell 版)
3
+ #
4
+ # 流程 (全本地, 用 MINIMAX_API_KEY 调 LLM):
5
+ # 1. 跑 vitest, 抓 fail
6
+ # 2. 让 LLM 修
7
+ # 3. 解析 LLM 输出的 diff, 写到 staging
8
+ # 4. 跑 reviewer (护栏 4)
9
+ # 5. PASS → git apply + commit (护栏 1 拦)
10
+
11
+ set -uo pipefail # 不加 -e: vitest 失败要继续
12
+
13
+ REPO="$(cd "$(dirname "$0")/.." && pwd)"
14
+ cd "$REPO"
15
+
16
+ echo "[oneshot] REPO=$REPO"
17
+
18
+ # 1. 跑 vitest
19
+ npx vitest run --reporter=json --no-color 2>/dev/null > /tmp/vt-out.json
20
+ TOTAL_FAIL=$(python3 -c "
21
+ import json
22
+ d = json.load(open('/tmp/vt-out.json'))
23
+ print(sum(1 for f in d.get('testResults',[]) for a in f.get('assertionResults',[]) if a.get('status')=='failed'))
24
+ ")
25
+ echo "[oneshot] vitest failed=$TOTAL_FAIL"
26
+
27
+ if [ "$TOTAL_FAIL" = "0" ]; then
28
+ echo "[oneshot] ✅ 全部通过, 不需要修"
29
+ exit 0
30
+ fi
31
+
32
+ # 2. 抽 fail 信息
33
+ FAIL_SUMMARY=$(python3 -c "
34
+ import json
35
+ d = json.load(open('/tmp/vt-out.json'))
36
+ for f in d.get('testResults', []):
37
+ for a in f.get('assertionResults', []):
38
+ if a.get('status') == 'failed':
39
+ print('FILE:', f.get('name'))
40
+ print('TEST:', a.get('fullName') or a.get('title'))
41
+ print('ERROR:')
42
+ for m in a.get('failureMessages', [])[:2]:
43
+ print(m[:600])
44
+ print('---')
45
+ " | head -60)
46
+ echo "[oneshot] 2. 调 LLM 修..."
47
+
48
+ # 3. 用 LLM 生成修复
49
+ # prompt: 要 LLM 输出 ```diff ... ``` 块
50
+ # 3. 写 prompt 到文件 (避免 shell 反引号冲突)
51
+ PROMPT_FILE="/tmp/oneshot-prompt.txt"
52
+ cat > "$PROMPT_FILE" <<PROMPT_EOF
53
+ 你是一个谨慎的代码修复助手. 你的工作是修复失败的测试.
54
+
55
+ 约束: 改动最小, 不改测试, 不引入 any/unknown/@ts-ignore.
56
+
57
+ 输出格式: 严格只输出一个 \`\`\`diff ... \`\`\` 块, 第一个字符 \`\`\`diff, 最后一个字符 \`\`\`. 中间是 unified diff (--- a/path +++ b/path 风格). 不要在 diff 块外输出任何文字.
58
+
59
+ FAIL 信息:
60
+ $(cat /tmp/vt-out.json | python3 -c "
61
+ import json
62
+ d = json.load(open('/tmp/vt-out.json'))
63
+ for f in d.get('testResults', []):
64
+ for a in f.get('assertionResults', []):
65
+ if a.get('status') == 'failed':
66
+ print('FILE:', f.get('name'))
67
+ print('TEST:', a.get('fullName') or a.get('title'))
68
+ print('ERROR:')
69
+ for m in a.get('failureMessages', [])[:2]:
70
+ print(m[:600])
71
+ print('---')
72
+ " | head -60)
73
+
74
+ 请**只**输出 diff 块:
75
+ PROMPT_EOF
76
+
77
+ LLM_OUTPUT=$(npx tsx -r dotenv/config -e "
78
+ import { initMinimax } from './src/llm/pi-ai.js';
79
+ import * as fs from 'fs';
80
+ const prompt = fs.readFileSync('$PROMPT_FILE', 'utf-8');
81
+ const client = initMinimax();
82
+ const text = await client.generateText({ messages: [{ role: 'user', content: prompt }], maxTokens: 4096, temperature: 0.2 });
83
+ process.stdout.write(text || '');
84
+ " 2>/tmp/llm-err.log)
85
+
86
+ if [ -z "$LLM_OUTPUT" ]; then
87
+ echo "[oneshot] ❌ LLM 没返回"
88
+ exit 2
89
+ fi
90
+
91
+ # 4. 解析 diff
92
+ DIFF=$(echo "$LLM_OUTPUT" | python3 -c "
93
+ import sys, re
94
+ text = sys.stdin.read()
95
+ m = re.search(r'\`\`\`diff\s*([\s\S]*?)\`\`\`', text)
96
+ if m:
97
+ diff = m.group(1).strip()
98
+ if not diff.endswith('\n'):
99
+ diff += '\n'
100
+ print(diff)
101
+ else:
102
+ sys.exit(1)
103
+ " 2>/dev/null) || {
104
+ echo "[oneshot] ❌ LLM 输出没 diff 块"
105
+ echo "--- LLM 原始输出 (前 800) ---"
106
+ echo "$LLM_OUTPUT" | head -c 800
107
+ echo ""
108
+ exit 3
109
+ }
110
+
111
+ echo "[oneshot] 拿到 diff: $(echo "$DIFF" | wc -l) lines"
112
+
113
+ # 5. 写 staging
114
+ ID="oneshot-$(date +%s)"
115
+ mkdir -p "staging/auto-evolve/$ID"
116
+ echo "$DIFF" > "staging/auto-evolve/$ID/$ID.patch"
117
+ echo "$ID" > "staging/auto-evolve/$ID/.patch-id"
118
+ echo "[oneshot] patch 写到 staging/auto-evolve/$ID/"
119
+
120
+ # 6. 跑 reviewer
121
+ echo "[oneshot] 3. 跑 reviewer..."
122
+ npx tsx -r dotenv/config scripts/diff-reviewer.ts "$ID" > /tmp/reviewer.log 2>&1 || true
123
+ VERDICT=$(python3 -c "
124
+ import json
125
+ try:
126
+ print(json.load(open('staging/auto-evolve/$ID/.review-verdict')).get('verdict','UNKNOWN'))
127
+ except:
128
+ print('UNKNOWN')
129
+ ")
130
+ echo "[oneshot] reviewer verdict: $VERDICT"
131
+
132
+ # 7. apply + commit
133
+ if [ "$VERDICT" = "PASS" ]; then
134
+ echo "[oneshot] 4. apply + commit"
135
+ if git apply --recount --whitespace=fix "staging/auto-evolve/$ID/$ID.patch" 2>/tmp/apply.err; then
136
+ git add -A
137
+ git commit -m "auto-evolve: $ID (LLM 修复)"
138
+ echo "[oneshot] ✅ 提交成功"
139
+ # 验证
140
+ npx vitest run --reporter=json --no-color 2>/dev/null > /tmp/vt-after.json
141
+ AFTER_FAIL=$(python3 -c "
142
+ import json
143
+ print(sum(1 for f in json.load(open('/tmp/vt-after.json')).get('testResults',[]) for a in f.get('assertionResults',[]) if a.get('status')=='failed'))
144
+ ")
145
+ echo "[oneshot] 修复后 fail: $AFTER_FAIL (之前 $TOTAL_FAIL)"
146
+ else
147
+ echo "[oneshot] ❌ git apply 失败:"
148
+ cat /tmp/apply.err
149
+ exit 4
150
+ fi
151
+ else
152
+ echo "[oneshot] ❌ reviewer verdict=$VERDICT, 不 apply"
153
+ cat /tmp/reviewer.log | head -10
154
+ exit 5
155
+ fi
@@ -0,0 +1,136 @@
1
+ #!/bin/bash
2
+ # auto-evolve-snapshot.sh — 阶段 C 护栏 2 + 3
3
+ #
4
+ # 用法 (LLM 改源码前必调):
5
+ # bash scripts/auto-evolve-snapshot.sh # 打 baseline tag + 记当前 HEAD
6
+ # bash scripts/auto-evolve-snapshot.sh apply <patch-id> # 人类批准后合并 staging → main
7
+ # bash scripts/auto-evolve-snapshot.sh list # 列所有 baseline
8
+ # bash scripts/auto-evolve-snapshot.sh rollback <tag> # 回滚到指定 baseline
9
+ #
10
+ # 流程:
11
+ # 1. LLM 改之前调 snapshot: 当前 HEAD 打 auto-evolve-baseline-<ts> tag
12
+ # 2. LLM 改 staging/auto-evolve/<patch-id>/ (不进 src/)
13
+ # 3. 护栏 1 (lefthook) 跑 vitest + tsc, 坏就 abort
14
+ # 4. 护栏 4 (reviewer hook) 审 diff, 通过才准 apply
15
+ # 5. 人类调 apply: git apply staging/auto-evolve/<patch-id>/*.patch → src/
16
+ # 6. 出问题: 调 rollback → git reset --hard <tag>
17
+
18
+ set -euo pipefail
19
+
20
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
21
+ cd "$REPO_ROOT"
22
+
23
+ STAGING_DIR="staging/auto-evolve"
24
+ TAG_PREFIX="auto-evolve-baseline-"
25
+
26
+ cmd="${1:-snapshot}"
27
+ patch_id="${2:-}"
28
+
29
+ case "$cmd" in
30
+ snapshot)
31
+ # 护栏 2: 打 baseline tag
32
+ if ! git diff --quiet HEAD 2>/dev/null; then
33
+ echo "❌ 有未提交改动. 先 git commit 或 git stash"
34
+ exit 1
35
+ fi
36
+ ts="$(date -u +%Y%m%dT%H%M%SZ)"
37
+ tag="${TAG_PREFIX}${ts}"
38
+ git tag -a "$tag" -m "auto-evolve baseline @ ${ts}" HEAD
39
+ echo "✅ baseline tag: $tag"
40
+ echo "$tag" > .last-auto-evolve-baseline
41
+ echo " 回滚命令: bash $0 rollback $tag"
42
+ ;;
43
+
44
+ prepare)
45
+ # LLM 改之前调: 创建 staging 目录
46
+ if [ -z "$patch_id" ]; then
47
+ echo "用法: $0 prepare <patch-id>"
48
+ exit 1
49
+ fi
50
+ mkdir -p "$STAGING_DIR/$patch_id"
51
+ echo "$patch_id" > "$STAGING_DIR/$patch_id/.patch-id"
52
+ echo "✅ staging 创建: $STAGING_DIR/$patch_id/"
53
+ echo " LLM 改完后把 patch 放这里: $STAGING_DIR/$patch_id/*.patch"
54
+ ;;
55
+
56
+ apply)
57
+ # 人类批准: 把 staging 的 patch 合并到 src/
58
+ if [ -z "$patch_id" ]; then
59
+ echo "用法: $0 apply <patch-id>"
60
+ exit 1
61
+ fi
62
+ patch_dir="$STAGING_DIR/$patch_id"
63
+ if [ ! -d "$patch_dir" ]; then
64
+ echo "❌ staging 不存在: $patch_dir"
65
+ exit 1
66
+ fi
67
+ # 必须先 snapshot
68
+ if [ ! -f .last-auto-evolve-baseline ]; then
69
+ echo "❌ 没有 baseline. 先跑: $0 snapshot"
70
+ exit 1
71
+ fi
72
+ baseline="$(cat .last-auto-evolve-baseline)"
73
+
74
+ # 应用所有 patch
75
+ applied=0
76
+ for p in "$patch_dir"/*.patch; do
77
+ [ -e "$p" ] || continue
78
+ echo " applying: $p"
79
+ if ! git apply --check "$p"; then
80
+ echo "❌ patch 不可用: $p (可能已应用过)"
81
+ exit 1
82
+ fi
83
+ git apply "$p"
84
+ applied=$((applied + 1))
85
+ done
86
+
87
+ if [ $applied -eq 0 ]; then
88
+ echo "❌ staging 里没有 .patch 文件"
89
+ exit 1
90
+ fi
91
+
92
+ echo "✅ 应用 $applied 个 patch"
93
+ echo " 建议现在跑: npm test + git commit -m 'auto-evolve: $patch_id'"
94
+ echo " 出问题回滚: $0 rollback $baseline"
95
+ ;;
96
+
97
+ rollback)
98
+ # 回滚到指定 baseline
99
+ if [ -z "$patch_id" ]; then
100
+ echo "用法: $0 rollback <tag>"
101
+ echo "可用的 baseline:"
102
+ git tag -l "${TAG_PREFIX}*" | sort -r | head -10
103
+ exit 1
104
+ fi
105
+ if ! git tag -l | grep -qx "$patch_id"; then
106
+ echo "❌ tag 不存在: $patch_id"
107
+ exit 1
108
+ fi
109
+ echo "⚠️ 将回滚到 $patch_id (hard reset, 丢弃之后所有改动)"
110
+ read -p "确认? [y/N] " -n 1 -r
111
+ echo
112
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
113
+ git reset --hard "$patch_id"
114
+ echo "✅ 回滚到 $patch_id"
115
+ else
116
+ echo "取消"
117
+ fi
118
+ ;;
119
+
120
+ list)
121
+ echo "auto-evolve baselines (最近 10):"
122
+ git tag -l "${TAG_PREFIX}*" | sort -r | head -10 | while read tag; do
123
+ msg="$(git tag -l --format='%(contents)' "$tag" | head -1)"
124
+ echo " $tag — $msg"
125
+ done
126
+ if [ -f .last-auto-evolve-baseline ]; then
127
+ echo ""
128
+ echo "当前 baseline: $(cat .last-auto-evolve-baseline)"
129
+ fi
130
+ ;;
131
+
132
+ *)
133
+ echo "用法: $0 {snapshot|prepare <id>|apply <id>|rollback <tag>|list}"
134
+ exit 1
135
+ ;;
136
+ esac
@@ -0,0 +1,216 @@
1
+ /**
2
+ * CLI 构建脚本
3
+ *
4
+ * 生成 bin/bolloon.js 和 bin/bolloon.cmd 入口文件
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const rootDir = path.join(__dirname, '..');
14
+
15
+ const binDir = path.join(rootDir, 'bin');
16
+ if (!fs.existsSync(binDir)) {
17
+ fs.mkdirSync(binDir, { recursive: true });
18
+ }
19
+
20
+ // Windows 批处理入口
21
+ const winContent = `@echo off
22
+ set "BOLLOON_ROOT=%~dp0"
23
+ set "BOLLOON_ROOT=%BOLLOON_ROOT:~0,-1%"
24
+
25
+ REM 确定入口文件
26
+ set "ENTRY=%BOLLOON_ROOT%\\dist\\index.js"
27
+ if not exist "%ENTRY%" (
28
+ set "ENTRY=%BOLLOON_ROOT%\\src\\index.ts"
29
+ )
30
+
31
+ node "%ENTRY%" %*
32
+ `;
33
+
34
+ fs.writeFileSync(path.join(binDir, 'bolloon.cmd'), winContent);
35
+
36
+ // Unix/Linux/Mac 入口脚本
37
+ const unixContent = `#!/usr/bin/env node
38
+ const path = require("path");
39
+ const { spawn } = require("child_process");
40
+ const fs = require("fs");
41
+
42
+ const RESET = "\\x1b[0m";
43
+ const BOLD = "\\x1b[1m";
44
+ const CYAN = "\\x1b[36m";
45
+ const GREEN = "\\x1b[32m";
46
+ const MAGENTA = "\\x1b[35m";
47
+
48
+ function log(msg, color) {
49
+ console.log((color || RESET) + msg + RESET);
50
+ }
51
+
52
+ function printBanner() {
53
+ console.log("\\n" + CYAN + BOLD + [
54
+ " ╔═══════════════════════════════════════════╗",
55
+ " ║ 🤖 Bolloon Agent ║",
56
+ " ║ P2P AI Document Processor ║",
57
+ " ╚═══════════════════════════════════════════╝"
58
+ ].join("\\n") + RESET + "\\n");
59
+ }
60
+
61
+ function getMainEntry() {
62
+ const distDir = path.dirname(require.main.filename);
63
+ const distIndex = path.join(distDir, "index.js");
64
+ if (fs.existsSync(distIndex)) {
65
+ return distIndex;
66
+ }
67
+ return path.join(process.cwd(), "src", "index.ts");
68
+ }
69
+
70
+ function parseArgs() {
71
+ const args = process.argv.slice(2);
72
+ if (args.length === 0) {
73
+ return { mode: "gui", args: [] };
74
+ }
75
+ const first = args[0];
76
+ switch (first) {
77
+ case "-v":
78
+ case "--version":
79
+ return { mode: "version", args: [] };
80
+ case "-h":
81
+ case "--help":
82
+ return { mode: "help", args: [] };
83
+ case "-g":
84
+ case "--gui":
85
+ return { mode: "gui", args: args.slice(1) };
86
+ case "-w":
87
+ case "--web":
88
+ return { mode: "web", args: args.slice(1) };
89
+ case "-c":
90
+ case "--cli":
91
+ return { mode: "cli", args: args.slice(1) };
92
+ default:
93
+ return { mode: "passthrough", args };
94
+ }
95
+ }
96
+
97
+ async function startElectron(additionalArgs) {
98
+ try {
99
+ const electron = require("electron");
100
+ const distDir = path.dirname(require.main.filename);
101
+ let mainPath = path.join(distDir, "electron.js");
102
+ if (!fs.existsSync(mainPath)) {
103
+ mainPath = path.join(process.cwd(), "src", "electron.ts");
104
+ }
105
+ log("启动 Electron...", CYAN);
106
+ const child = spawn(electron, [mainPath, ...additionalArgs], {
107
+ stdio: "inherit",
108
+ env: { ...process.env, NODE_ENV: "development" }
109
+ });
110
+ child.on("error", (err) => {
111
+ log("Electron 启动失败: " + err.message, MAGENTA);
112
+ process.exit(1);
113
+ });
114
+ child.on("exit", (code) => process.exit(code || 0));
115
+ } catch (err) {
116
+ log("Electron 不可用,切换到 Web 模式...", CYAN);
117
+ await startWebServer(additionalArgs);
118
+ }
119
+ }
120
+
121
+ async function startWebServer(additionalArgs) {
122
+ const mainPath = getMainEntry();
123
+ const webArgs = ["--web", ...additionalArgs];
124
+ log("启动 Web 服务...", CYAN);
125
+ const child = spawn(process.execPath, [mainPath, ...webArgs], { stdio: "inherit" });
126
+ child.on("error", (err) => {
127
+ log("Web 服务启动失败: " + err.message, MAGENTA);
128
+ process.exit(1);
129
+ });
130
+ child.on("exit", (code) => process.exit(code || 0));
131
+ }
132
+
133
+ async function startCLI(additionalArgs) {
134
+ const mainPath = getMainEntry();
135
+ log("启动命令行界面...", CYAN);
136
+ const child = spawn(process.execPath, [mainPath, ...additionalArgs], { stdio: "inherit" });
137
+ child.on("error", (err) => {
138
+ log("CLI 启动失败: " + err.message, MAGENTA);
139
+ process.exit(1);
140
+ });
141
+ child.on("exit", (code) => process.exit(code || 0));
142
+ }
143
+
144
+ async function main() {
145
+ const { mode, args } = parseArgs();
146
+ switch (mode) {
147
+ case "version":
148
+ console.log("Bolloon Agent v0.1.1");
149
+ break;
150
+ case "help":
151
+ printBanner();
152
+ console.log(BOLD + "用法:" + RESET + " bolloon [选项] [命令] [参数]");
153
+ console.log(BOLD + "选项:" + RESET + " --gui, -g 启动图形界面");
154
+ console.log(" --web, -w 启动 Web UI");
155
+ console.log(" --cli, -c 启动命令行界面");
156
+ console.log(" --version, -v 显示版本");
157
+ console.log(" --help, -h 显示帮助");
158
+ console.log(BOLD + "示例:" + RESET + " bolloon # 启动图形界面");
159
+ console.log(" bolloon --web # 启动 Web UI");
160
+ console.log(" bolloon --read file # 读取文档");
161
+ break;
162
+ case "gui":
163
+ printBanner();
164
+ await startElectron(args);
165
+ break;
166
+ case "web":
167
+ printBanner();
168
+ await startWebServer(args);
169
+ break;
170
+ case "cli":
171
+ await startCLI(args);
172
+ break;
173
+ case "passthrough":
174
+ const mainPath = getMainEntry();
175
+ const child = spawn(process.execPath, [mainPath, ...args], { stdio: "inherit" });
176
+ child.on("error", (err) => {
177
+ log("执行失败: " + err.message, MAGENTA);
178
+ process.exit(1);
179
+ });
180
+ child.on("exit", (code) => process.exit(code || 0));
181
+ break;
182
+ default:
183
+ log("未知模式: " + mode, MAGENTA);
184
+ printBanner();
185
+ console.log("输入 --help 查看帮助");
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ main().catch((err) => {
191
+ console.error("Fatal error:", err);
192
+ process.exit(1);
193
+ });
194
+ `;
195
+
196
+ fs.writeFileSync(path.join(binDir, 'bolloon.cjs'), unixContent);
197
+
198
+ // 确保 bin/bolloon.js 存在(npm link 需要)
199
+ // 优先用符号链接(POSIX),Windows 上若权限不足则退化为复制文件
200
+ const jsSymlink = path.join(binDir, 'bolloon.js');
201
+ if (fs.existsSync(jsSymlink)) fs.unlinkSync(jsSymlink);
202
+ try {
203
+ fs.symlinkSync('bolloon.cjs', jsSymlink);
204
+ } catch (err) {
205
+ if (err && err.code === 'EPERM') {
206
+ fs.copyFileSync(path.join(binDir, 'bolloon.cjs'), jsSymlink);
207
+ console.warn(' ⚠ symlink 不支持(Windows),已退化为文件复制');
208
+ } else {
209
+ throw err;
210
+ }
211
+ }
212
+
213
+ console.log("✓ CLI 构建完成");
214
+ console.log(" bin/bolloon.cjs - CommonJS 入口");
215
+ console.log(" bin/bolloon.js - 符号链接 -> bolloon.cjs");
216
+ console.log(" bin/bolloon.cmd - Windows 入口");
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # detect-schema-changes.sh — 阶段 C 护栏 6
3
+ #
4
+ # 检查 LLM 改的文件里有没有动 schema (interface / type / enum 声明)
5
+ # 有则强制要求 reviewer 双签 (在 .auto-evolve-review-required 文件)
6
+ #
7
+ # 用法 (在 staging 准备好 patch 后, apply 前):
8
+ # bash scripts/detect-schema-changes.sh <patch-id>
9
+
10
+ set -euo pipefail
11
+
12
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
13
+ cd "$REPO_ROOT"
14
+
15
+ patch_id="${1:-}"
16
+ if [ -z "$patch_id" ]; then
17
+ echo "用法: $0 <patch-id>"
18
+ exit 1
19
+ fi
20
+
21
+ staging_dir="staging/auto-evolve/$patch_id"
22
+ if [ ! -d "$staging_dir" ]; then
23
+ echo "❌ staging 不存在: $staging_dir"
24
+ exit 1
25
+ fi
26
+
27
+ flag="$staging_dir/.schema-changed"
28
+ rm -f "$flag"
29
+
30
+ found=0
31
+ for patch in "$staging_dir"/*.patch; do
32
+ [ -e "$patch" ] || continue
33
+ # 检查 patch 里有没有新增/修改 interface、type、enum 声明
34
+ if grep -E '^\+.*(interface |type [A-Z][a-zA-Z0-9_]* =|enum [A-Z][a-zA-Z0-9_]*)' "$patch" > /dev/null 2>&1; then
35
+ found=1
36
+ echo "⚠️ schema 改动检测到: $patch"
37
+ grep -E '^\+.*(interface |type [A-Z][a-zA-Z0-9_]* =|enum [A-Z][a-zA-Z0-9_]*)' "$patch" | head -3
38
+ fi
39
+ done
40
+
41
+ if [ $found -eq 1 ]; then
42
+ touch "$flag"
43
+ echo ""
44
+ echo "🚨 schema 改动标记: $flag"
45
+ echo " 护栏 6 触发: apply 时会强制要求双签 reviewer"
46
+ else
47
+ echo "✅ 无 schema 改动, 走单签"
48
+ fi