@bolloon/bolloon-agent 0.1.34 → 0.1.35

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 (60) hide show
  1. package/.auto-evolve-calls +1 -0
  2. package/.last-auto-evolve-baseline +1 -0
  3. package/Bolloon.md +103 -0
  4. package/dist/agents/pi-sdk.js +264 -12
  5. package/dist/bootstrap/bootstrap.js +114 -0
  6. package/dist/bootstrap/context-collector.js +296 -0
  7. package/dist/bootstrap/lifecycle-hooks.js +109 -0
  8. package/dist/bootstrap/project-context.js +151 -0
  9. package/dist/index.js +11 -0
  10. package/dist/llm/pi-ai.js +31 -21
  11. package/dist/pi-ecosystem-judgment/adaptive-scan.js +231 -0
  12. package/dist/pi-ecosystem-judgment/causal-judge.js +449 -0
  13. package/dist/pi-ecosystem-judgment/detect-hook.js +168 -0
  14. package/dist/pi-ecosystem-judgment/distill-prompt.js +226 -0
  15. package/dist/pi-ecosystem-judgment/evolve-judgment.js +170 -0
  16. package/dist/pi-ecosystem-judgment/human-value-pipeline.js +21 -0
  17. package/dist/pi-ecosystem-judgment/human-value-store.js +283 -22
  18. package/dist/pi-ecosystem-judgment/injection-gate.js +166 -0
  19. package/dist/pi-ecosystem-judgment/monitor-gate.js +188 -0
  20. package/dist/security/builtin-guards.js +124 -0
  21. package/dist/security/context-router-tool.js +106 -0
  22. package/dist/security/react-harness.js +143 -0
  23. package/dist/security/tool-gate.js +235 -0
  24. package/dist/utils/auto-evolve-policy.js +117 -0
  25. package/dist/utils/clamp.js +7 -0
  26. package/dist/utils/double.js +6 -0
  27. package/dist/web/client.js +668 -204
  28. package/dist/web/index.html +24 -4
  29. package/dist/web/server.js +531 -10
  30. package/lefthook.yml +29 -0
  31. package/package.json +3 -2
  32. package/scripts/auto-evolve-loop.ts +376 -0
  33. package/scripts/auto-evolve-oneshot.sh +155 -0
  34. package/scripts/auto-evolve-snapshot.sh +136 -0
  35. package/scripts/detect-schema-changes.sh +48 -0
  36. package/scripts/diff-reviewer.ts +159 -0
  37. package/scripts/weekly-report.ts +364 -0
  38. package/src/agents/pi-sdk.ts +293 -15
  39. package/src/bootstrap/bootstrap.ts +132 -0
  40. package/src/bootstrap/context-collector.ts +342 -0
  41. package/src/bootstrap/lifecycle-hooks.ts +176 -0
  42. package/src/bootstrap/project-context.ts +163 -0
  43. package/src/index.ts +11 -0
  44. package/src/llm/pi-ai.ts +33 -22
  45. package/src/security/builtin-guards.ts +162 -0
  46. package/src/security/context-router-tool.ts +122 -0
  47. package/src/security/react-harness.ts +177 -0
  48. package/src/security/tool-gate.ts +294 -0
  49. package/src/utils/auto-evolve-policy.ts +138 -0
  50. package/src/utils/clamp.ts +5 -0
  51. package/src/web/client.js +668 -204
  52. package/src/web/index.html +24 -4
  53. package/src/web/server.ts +596 -10
  54. package/staging/auto-evolve/clean-001/.review-verdict +9 -0
  55. package/staging/auto-evolve/clean-001/clean-001.patch +14 -0
  56. package/staging/auto-evolve/e2e-001/.patch-id +1 -0
  57. package/staging/auto-evolve/e2e-001/.review-verdict +12 -0
  58. package/staging/auto-evolve/e2e-001/e2e-001.patch +11 -0
  59. package/staging/auto-evolve/test-bad/.review-verdict +12 -0
  60. package/staging/auto-evolve/test-bad/test-bad.patch +11 -0
@@ -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,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
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * diff-reviewer.ts — 阶段 C 护栏 4
4
+ *
5
+ * 2nd LLM call 审第 1 个的 diff:
6
+ * - 默认用 Haiku (便宜, ~$0.001/diff)
7
+ * - schema 改动升级 Sonnet (护栏 6 双签)
8
+ *
9
+ * 输入: patch 文件 (git format-patch 风格)
10
+ * 输出: .review-verdict 写到 staging/<patch-id>/, 含 PASS/FAIL + 原因
11
+ *
12
+ * 用法:
13
+ * tsx scripts/diff-reviewer.ts <patch-id> [--model sonnet]
14
+ */
15
+
16
+ import * as fs from 'fs/promises';
17
+ import * as path from 'path';
18
+ // Anthropic SDK 在 review() 里 lazy import — 无 API key 或没装 SDK 都不应炸
19
+
20
+ const STAGING_DIR = 'staging/auto-evolve';
21
+ const ENV_KEY_MODEL = 'AUTO_EVOLVE_REVIEW_MODEL';
22
+
23
+ const REVIEW_PROMPT = `你是一个严格的代码审查员. 审查以下 git diff (LLM 自动生成的源码改动).
24
+
25
+ 只回答 JSON, 字段:
26
+ {
27
+ "verdict": "PASS" | "FAIL",
28
+ "concerns": ["concern 1", "concern 2", ...],
29
+ "suggestions": ["suggestion 1", ...]
30
+ }
31
+
32
+ PASS 条件 (全部满足才 PASS):
33
+ 1. 改动能解决它声称要解决的问题
34
+ 2. 没引入明显 bug (空指针, 内存泄漏, 死循环, race condition)
35
+ 3. 没引入安全漏洞 (injection, XSS, 越权)
36
+ 4. 边界条件考虑 (空数组, undefined, 极值, 并发)
37
+ 5. 改动局限在它声称的范围, 不夹带私货
38
+ 6. 跟现有代码风格一致
39
+
40
+ FAIL 条件 (任一即 FAIL):
41
+ - 满足上面任一 PASS 条件的反例
42
+ - 引入了新依赖 (必须在 concerns 里说)
43
+ - 删除了测试 / 注释掉失败用例
44
+ - 用了 any / unknown / @ts-ignore 偷懒
45
+
46
+ DIFF:
47
+ {{DIFF}}
48
+ `;
49
+
50
+ function degradedGrepReview(patchContent: string): { verdict: string; concerns: string[]; suggestions: string[]; raw: string } {
51
+ console.warn('[diff-reviewer] ⚠️ 降级 grep 检查 (无 API key 或无 SDK)');
52
+ const concerns: string[] = [];
53
+ if (/\+.*\bany\b/.test(patchContent)) concerns.push('使用了 any');
54
+ if (/\+.*\b@ts-ignore\b/.test(patchContent)) concerns.push('使用了 @ts-ignore');
55
+ if (/\+.*\bconsole\.log\b/.test(patchContent)) concerns.push('留了 console.log');
56
+ if (/\-.*test\(/.test(patchContent)) concerns.push('删除了测试');
57
+ return {
58
+ verdict: concerns.length > 0 ? 'FAIL' : 'PASS',
59
+ concerns,
60
+ suggestions: [],
61
+ raw: '(degraded grep mode)',
62
+ };
63
+ }
64
+
65
+ async function review(patchContent: string, model: string): Promise<{ verdict: string; concerns: string[]; suggestions: string[]; raw: string }> {
66
+ // 无 API key → 直接降级, 不 import SDK (避免 no-ANTHROPIC 环境下也炸)
67
+ if (!process.env.ANTHROPIC_API_KEY) {
68
+ return degradedGrepReview(patchContent);
69
+ }
70
+
71
+ // 有 API key → 尝试 lazy import SDK
72
+ let Anthropic: any;
73
+ try {
74
+ ({ Anthropic } = await import('@anthropic-ai/sdk'));
75
+ } catch (err) {
76
+ console.warn('[diff-reviewer] ⚠️ @anthropic-ai/sdk 未装, 降级 grep');
77
+ return degradedGrepReview(patchContent);
78
+ }
79
+
80
+ const client = new Anthropic();
81
+ const prompt = REVIEW_PROMPT.replace('{{DIFF}}', patchContent.slice(0, 20000)); // 限长
82
+ const resp = await client.messages.create({
83
+ model,
84
+ max_tokens: 1024,
85
+ messages: [{ role: 'user', content: prompt }],
86
+ });
87
+ const text = resp.content[0].type === 'text' ? resp.content[0].text : '';
88
+ // 解析 JSON
89
+ const m = /\{[\s\S]*\}/.exec(text);
90
+ if (!m) {
91
+ return { verdict: 'FAIL', concerns: ['LLM 没返回 JSON'], suggestions: [], raw: text };
92
+ }
93
+ try {
94
+ const parsed = JSON.parse(m[0]);
95
+ return {
96
+ verdict: parsed.verdict === 'PASS' ? 'PASS' : 'FAIL',
97
+ concerns: parsed.concerns || [],
98
+ suggestions: parsed.suggestions || [],
99
+ raw: text,
100
+ };
101
+ } catch {
102
+ return { verdict: 'FAIL', concerns: ['JSON 解析失败'], suggestions: [], raw: text };
103
+ }
104
+ }
105
+
106
+ async function main() {
107
+ const args = process.argv.slice(2);
108
+ const patchId = args[0];
109
+ if (!patchId) {
110
+ console.error('用法: tsx diff-reviewer.ts <patch-id> [--model sonnet]');
111
+ process.exit(1);
112
+ }
113
+ const forceModel = args.includes('--model') ? args[args.indexOf('--model') + 1] : null;
114
+
115
+ const stagingDir = path.join(STAGING_DIR, patchId);
116
+ const schemaFlag = path.join(stagingDir, '.schema-changed');
117
+ const isSchemaChange = await fs.access(schemaFlag).then(() => true).catch(() => false);
118
+
119
+ const defaultModel = isSchemaChange ? 'claude-sonnet-4-6' : 'claude-haiku-4-5';
120
+ const model = forceModel || process.env[ENV_KEY_MODEL] || defaultModel;
121
+
122
+ console.log(`[diff-reviewer] patch=${patchId} schema=${isSchemaChange} model=${model}`);
123
+
124
+ // 合并所有 patch
125
+ const files = await fs.readdir(stagingDir);
126
+ const patches = files.filter((f) => f.endsWith('.patch'));
127
+ if (patches.length === 0) {
128
+ console.error('❌ staging 里没有 .patch 文件');
129
+ process.exit(1);
130
+ }
131
+
132
+ let combined = '';
133
+ for (const p of patches) {
134
+ combined += `\n=== ${p} ===\n` + (await fs.readFile(path.join(stagingDir, p), 'utf-8'));
135
+ }
136
+
137
+ const r = await review(combined, model);
138
+
139
+ const verdict = {
140
+ patchId,
141
+ model,
142
+ schemaChange: isSchemaChange,
143
+ verdict: r.verdict,
144
+ concerns: r.concerns,
145
+ suggestions: r.suggestions,
146
+ reviewedAt: new Date().toISOString(),
147
+ };
148
+ await fs.writeFile(path.join(stagingDir, '.review-verdict'), JSON.stringify(verdict, null, 2));
149
+
150
+ console.log(`[diff-reviewer] verdict=${r.verdict} concerns=${r.concerns.length}`);
151
+ if (r.verdict === 'FAIL') {
152
+ for (const c of r.concerns) console.log(` - ${c}`);
153
+ process.exit(2);
154
+ } else {
155
+ for (const s of r.suggestions) console.log(` 💡 ${s}`);
156
+ }
157
+ }
158
+
159
+ main().catch((e) => { console.error(e); process.exit(1); });