@bolloon/bolloon-agent 0.1.1 → 0.1.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.
Files changed (200) hide show
  1. package/bin/bolloon-cli.cjs +157 -0
  2. package/bin/bolloon-daemon.sh +207 -0
  3. package/bin/bolloon.cmd +11 -0
  4. package/dist/agents/constraint-layer.js +10 -15
  5. package/dist/agents/pi-sdk.js +433 -106
  6. package/dist/agents/protocol.js +82 -1
  7. package/dist/agents/subagent-manager.js +2 -2
  8. package/dist/agents/workflow-engine.js +15 -20
  9. package/dist/agents/workflow-pivot-loop.js +541 -0
  10. package/dist/bollharness/src/index.js +5 -0
  11. package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
  12. package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
  13. package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
  14. package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
  15. package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
  16. package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
  17. package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
  18. package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
  19. package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
  20. package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
  21. package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
  22. package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
  23. package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
  24. package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
  25. package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
  26. package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
  27. package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
  28. package/dist/bollharness/src/scripts/checks/finding.js +13 -0
  29. package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
  30. package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
  31. package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
  32. package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
  33. package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
  34. package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
  35. package/dist/bollharness/src/scripts/context_router.js +67 -0
  36. package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
  37. package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
  38. package/dist/bollharness/src/scripts/guard_router.js +158 -0
  39. package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
  40. package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
  41. package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
  42. package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
  43. package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
  44. package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
  45. package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
  46. package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
  47. package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
  48. package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
  49. package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
  50. package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
  51. package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
  52. package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
  53. package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
  54. package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
  55. package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
  56. package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
  57. package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
  58. package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
  59. package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
  60. package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
  61. package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
  62. package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
  63. package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
  64. package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
  65. package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
  66. package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
  67. package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
  68. package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
  69. package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
  70. package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
  71. package/dist/bollharness/src/scripts/sanitize.js +6 -0
  72. package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
  73. package/dist/bollharness-integration/context-chain-router.js +383 -0
  74. package/dist/bollharness-integration/context-router-judgment.js +13 -21
  75. package/dist/bollharness-integration/context-router.js +22 -64
  76. package/dist/bollharness-integration/gate-state-machine.js +14 -19
  77. package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
  78. package/dist/bollharness-integration/guard-checker.js +21 -68
  79. package/dist/bollharness-integration/index.js +14 -124
  80. package/dist/bollharness-integration/integration.js +13 -20
  81. package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
  82. package/dist/bollharness-integration/skill-adapter.js +18 -64
  83. package/dist/cli-entry.js +261 -0
  84. package/dist/constraint-runtime/src/commands.js +17 -7
  85. package/dist/constraint-runtime/src/constraint/budget.js +1 -6
  86. package/dist/constraint-runtime/src/constraint/permission.js +1 -6
  87. package/dist/constraint-runtime/src/models.js +1 -3
  88. package/dist/constraint-runtime/src/tools.js +17 -7
  89. package/dist/constraints/index.js +1 -7
  90. package/dist/documents/reader.js +8 -49
  91. package/dist/heartbeat/DaemonManager.js +242 -0
  92. package/dist/heartbeat/HealthMonitor.js +285 -0
  93. package/dist/heartbeat/StartupVerifier.js +205 -0
  94. package/dist/heartbeat/Watchdog.js +168 -0
  95. package/dist/heartbeat/index.js +84 -0
  96. package/dist/heartbeat/types.js +5 -0
  97. package/dist/index.js +381 -28
  98. package/dist/llm/config-store.js +31 -57
  99. package/dist/llm/llm-judgment-client.js +389 -0
  100. package/dist/llm/pi-ai.js +9 -52
  101. package/dist/network/agent-network.js +46 -90
  102. package/dist/network/hybrid-messenger.js +125 -0
  103. package/dist/network/iroh-bootstrap.js +38 -0
  104. package/dist/network/iroh-discovery.js +145 -0
  105. package/dist/network/iroh-integration.js +9 -16
  106. package/dist/network/iroh-transport.js +10 -48
  107. package/dist/network/p2p.js +23 -62
  108. package/dist/network/storage/adapters/json-adapter.js +4 -42
  109. package/dist/network/storage/index.js +147 -0
  110. package/dist/network/storage/types.js +14 -0
  111. package/dist/pi-ecosystem/index.js +233 -0
  112. package/dist/pi-ecosystem-colony/index.js +29 -90
  113. package/dist/pi-ecosystem-goals/index.js +20 -74
  114. package/dist/pi-ecosystem-judgment/decision.js +29 -47
  115. package/dist/pi-ecosystem-judgment/distillation.js +16 -29
  116. package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
  117. package/dist/pi-ecosystem-judgment/index.js +21 -74
  118. package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
  119. package/dist/pi-ecosystem-mcp/index.js +24 -78
  120. package/dist/pi-ecosystem-subagents/index.js +20 -69
  121. package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
  122. package/dist/social/ant-colony/PheromoneEngine.js +11 -49
  123. package/dist/social/ant-colony/index.js +6 -0
  124. package/dist/social/ant-colony/types.js +4 -8
  125. package/dist/social/channels/ChannelManager.js +8 -46
  126. package/dist/social/channels/DiapChannelBridge.js +9 -47
  127. package/dist/social/channels/InterestMatcher.js +2 -7
  128. package/dist/social/channels/channel-agent-session.js +309 -0
  129. package/dist/social/channels/channel-heartbeat-agent.js +494 -0
  130. package/dist/social/channels/diap-doc-parser.js +204 -0
  131. package/dist/social/channels/harness-workflow-integrator.js +446 -0
  132. package/dist/social/channels/index.js +9 -0
  133. package/dist/social/channels/types.js +3 -7
  134. package/dist/social/global-shared-context.js +6 -47
  135. package/dist/social/heartbeat.js +29 -72
  136. package/dist/social/persona/enhanced-persona.js +299 -0
  137. package/dist/web/client.js +302 -136
  138. package/dist/web/components/p2p/index.js +159 -9
  139. package/dist/web/components/p2p/p2p-connection.js +136 -0
  140. package/dist/web/components/p2p/p2p-manager.js +24 -0
  141. package/dist/web/components/p2p/p2p-store-memory.js +1 -1
  142. package/dist/web/components/p2p/types.js +7 -0
  143. package/dist/web/index.html +5 -0
  144. package/dist/web/style.css +118 -0
  145. package/package.json +12 -6
  146. package/scripts/build-cli.js +206 -0
  147. package/scripts/postinstall.js +153 -0
  148. package/src/agents/pi-sdk.ts +347 -28
  149. package/src/agents/protocol.ts +95 -1
  150. package/src/agents/workflow-pivot-loop.ts +674 -0
  151. package/src/bollharness/CLAUDE.md +73 -0
  152. package/src/bollharness/README.md +143 -0
  153. package/src/bollharness/README.zh-CN.md +131 -0
  154. package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
  155. package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
  156. package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
  157. package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
  158. package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
  159. package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
  160. package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
  161. package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
  162. package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
  163. package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
  164. package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
  165. package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
  166. package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
  167. package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
  168. package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
  169. package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
  170. package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
  171. package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
  172. package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
  173. package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
  174. package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
  175. package/src/cli-entry.ts +304 -0
  176. package/src/heartbeat/DaemonManager.ts +283 -0
  177. package/src/heartbeat/HealthMonitor.ts +316 -0
  178. package/src/heartbeat/StartupVerifier.ts +223 -0
  179. package/src/heartbeat/Watchdog.ts +198 -0
  180. package/src/heartbeat/index.ts +108 -0
  181. package/src/heartbeat/types.ts +82 -0
  182. package/src/llm/config-store.ts +23 -5
  183. package/src/network/iroh-transport.ts +3 -3
  184. package/src/web/client.js +302 -136
  185. package/src/web/components/p2p/P2PModal.tsx +91 -3
  186. package/src/web/components/p2p/index.ts +171 -9
  187. package/src/web/components/p2p/p2p-connection.ts +153 -1
  188. package/src/web/components/p2p/p2p-manager.ts +39 -1
  189. package/src/web/components/p2p/p2p-store-memory.ts +1 -1
  190. package/src/web/components/p2p/p2p-tools.ts +315 -0
  191. package/src/web/components/p2p/types.ts +58 -0
  192. package/src/web/design.md +99 -0
  193. package/src/web/index.html +5 -0
  194. package/src/web/server.ts +353 -36
  195. package/src/web/style.css +118 -0
  196. package/tsconfig.cli.json +16 -0
  197. package/tsconfig.electron.json +1 -1
  198. package/tsconfig.json +1 -2
  199. package/dist/web/server.js +0 -1647
  200. package/dist/web/server.js.map +0 -1
@@ -0,0 +1,67 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export const FRAGMENTS_DIR = path.join(__dirname, "..", "context-fragments");
4
+ export const CONTEXT_MAP = {
5
+ "bridge_agent/": ["bridge-constitution"],
6
+ "backend/product/bridge/": ["bridge-constitution"],
7
+ "mcp-server/": ["mcp-parity"],
8
+ "mcp-server-node/": ["mcp-parity"],
9
+ "backend/product/routes/protocol.py": ["protocol-consumers", "contract-consumers"],
10
+ "backend/product/protocol/": ["protocol-consumers"],
11
+ "backend/product/routes/": ["contract-consumers"],
12
+ "backend/product/db/crud_events.py": ["run-events-consumers"],
13
+ "backend/product/auth/": ["auth-consumers"],
14
+ "backend/product/db/": ["db-shared-structures"],
15
+ "backend/product/catalyst/": ["catalyst-distributed"],
16
+ "docs/issues/": ["fixed-three-layers", "closure-checklist"],
17
+ "scenes/": ["scene-fidelity", "two-language"],
18
+ "website/app/[scene]/": ["scene-fidelity", "two-language"],
19
+ "website/components/scene/": ["scene-fidelity", "two-language"],
20
+ "CLAUDE.md": ["truth-source-hierarchy"],
21
+ "MEMORY.md": ["truth-source-hierarchy"],
22
+ "docs/INDEX.md": ["truth-source-hierarchy"],
23
+ "mcp-server/pyproject.toml": ["version-sources"],
24
+ "mcp-server-node/package.json": ["version-sources"],
25
+ "website/": ["two-language"],
26
+ "docs/decisions/": ["artifact-linkage"],
27
+ "backend/product/": ["issue-first"],
28
+ "backend/server.py": ["issue-first"],
29
+ "mcp-server/boll_mcp/": ["issue-first"],
30
+ "mcp-server-node/src/": ["issue-first"],
31
+ "website/app/": ["issue-first"],
32
+ };
33
+ export const FALLBACK_FRAGMENTS = ["general-dev-principles"];
34
+ export function match(filePath) {
35
+ if (!filePath)
36
+ return [];
37
+ if (path.isAbsolute(filePath))
38
+ return [];
39
+ const normalized = path.normalize(filePath).replace(/\\/g, "/");
40
+ if (normalized.startsWith("../") || normalized === ".." || normalized.includes("/../")) {
41
+ return [];
42
+ }
43
+ const matched = [];
44
+ const sortedPatterns = Object.keys(CONTEXT_MAP).sort((a, b) => b.length - a.length);
45
+ for (const pattern of sortedPatterns) {
46
+ if (normalized.startsWith(pattern) || normalized.endsWith(pattern)) {
47
+ matched.push(...CONTEXT_MAP[pattern]);
48
+ }
49
+ }
50
+ return [...new Set(matched)];
51
+ }
52
+ export function loadFragment(name) {
53
+ if (!name)
54
+ return "";
55
+ const candidate = path.join(FRAGMENTS_DIR, `${name}.md`);
56
+ try {
57
+ const resolved = path.resolve(candidate);
58
+ const fragmentsDirResolved = path.resolve(FRAGMENTS_DIR);
59
+ if (!resolved.startsWith(fragmentsDirResolved))
60
+ return "";
61
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
62
+ return fs.readFileSync(resolved, "utf-8").trim();
63
+ }
64
+ }
65
+ catch { }
66
+ return "";
67
+ }
@@ -0,0 +1,157 @@
1
+ import * as fs from "fs";
2
+ const PROD_IP = "47.118.31.230";
3
+ const BRIDGE_VPS_HOSTS = ["46.250.229.84"];
4
+ const GUARDED_HOSTS = [PROD_IP, ...BRIDGE_VPS_HOSTS];
5
+ const DEPLOY_SH_PATTERN = /^bash\s+scripts\/deploy\.sh(\s+--(dry-run|yes))*\s*$/;
6
+ const SSH_READONLY_CMDS = new Set([
7
+ "journalctl", "cat", "ls", "head", "tail", "grep",
8
+ "ss", "curl", "dig", "status", "git", "file", "stat",
9
+ ]);
10
+ const SYSTEMCTL_WRITE_OPS = new Set(["restart", "stop", "start"]);
11
+ function getCommand() {
12
+ try {
13
+ const input = JSON.parse(fs.readFileSync(0, "utf-8"));
14
+ return input.tool_input?.command ?? null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ function isDeploySh(cmd) {
21
+ return DEPLOY_SH_PATTERN.test(cmd.trim());
22
+ }
23
+ function hasGuardedHost(cmd) {
24
+ return GUARDED_HOSTS.some(host => cmd.includes(host));
25
+ }
26
+ function whichGuardedHost(cmd) {
27
+ for (const host of GUARDED_HOSTS) {
28
+ if (cmd.includes(host))
29
+ return host;
30
+ }
31
+ return null;
32
+ }
33
+ function isCompoundCommand(cmd) {
34
+ let stripped = cmd.replace(/"[^"]*"/g, "");
35
+ stripped = stripped.replace(/'[^']*'/g, "");
36
+ return /[;&|]{1,2}/.test(stripped);
37
+ }
38
+ function checkScpDirection(cmd) {
39
+ const parts = cmd.split(/\s+/);
40
+ if (!parts.length || parts[0] !== "scp")
41
+ return "none";
42
+ const args = parts.slice(1).filter(p => !p.startsWith("-"));
43
+ if (args.length < 2)
44
+ return "none";
45
+ const lastArg = args[args.length - 1];
46
+ if (GUARDED_HOSTS.some(host => lastArg.includes(host))) {
47
+ return "upload";
48
+ }
49
+ for (const arg of args.slice(0, -1)) {
50
+ if (GUARDED_HOSTS.some(host => arg.includes(host))) {
51
+ return "download";
52
+ }
53
+ }
54
+ return "none";
55
+ }
56
+ function checkSshCommand(cmd) {
57
+ if (!cmd.includes("ssh") || !hasGuardedHost(cmd))
58
+ return "none";
59
+ const quoted = cmd.match(/"([^"]*)"/)?.[1] ?? cmd.match(/'([^']*)'/)?.[1];
60
+ if (!quoted)
61
+ return "none";
62
+ const remoteCmd = quoted.trim();
63
+ const parts = remoteCmd.split(/\s+/);
64
+ if (!parts.length)
65
+ return "none";
66
+ let idx = 0;
67
+ if (parts[0] === "sudo") {
68
+ idx = 1;
69
+ while (idx < parts.length && parts[idx].startsWith("-")) {
70
+ if (parts[idx] === "-u" && idx + 1 < parts.length) {
71
+ idx += 2;
72
+ }
73
+ else {
74
+ idx += 1;
75
+ }
76
+ }
77
+ }
78
+ const firstWord = parts[idx] ?? "";
79
+ if (firstWord === "systemctl") {
80
+ if (idx + 1 < parts.length && SYSTEMCTL_WRITE_OPS.has(parts[idx + 1])) {
81
+ return "write";
82
+ }
83
+ return "readonly";
84
+ }
85
+ if (SSH_READONLY_CMDS.has(firstWord)) {
86
+ return "readonly";
87
+ }
88
+ return "write";
89
+ }
90
+ function checkRsync(cmd) {
91
+ if (!cmd.includes("rsync") || !hasGuardedHost(cmd))
92
+ return "none";
93
+ const parts = cmd.split(/\s+/);
94
+ if (parts.includes("-n") || cmd.includes("--dry-run")) {
95
+ return "dryrun";
96
+ }
97
+ for (const p of parts) {
98
+ if (p.startsWith("-") && !p.startsWith("--") && p.includes("n")) {
99
+ return "dryrun";
100
+ }
101
+ }
102
+ return "write";
103
+ }
104
+ function block(reason, host = null) {
105
+ let guidance;
106
+ if (host === PROD_IP) {
107
+ guidance = `请使用标准部署流程:
108
+ 后端: bash scripts/deploy.sh --yes
109
+ Demo 正式: bash scripts/deploy-demo.sh <name> --channel prod --yes
110
+ Demo 内测: bash scripts/deploy-demo.sh <name> --channel preview --yes
111
+ Edge/Nginx: bash scripts/deploy-edge.sh --yes
112
+ 详见 CLAUDE.md Development Commands。`;
113
+ }
114
+ else if (BRIDGE_VPS_HOSTS.includes(host ?? "")) {
115
+ guidance = `Bridge VPS 必须走 git pull 更新路径:
116
+ ssh root@${host} 'sudo -u boll git -C /opt/boll pull --ff-only'
117
+ scp/rsync 直传会重新制造 orphan worktree。
118
+ 详见 docs/issues/guard-20260408-0445-bridge-vps-orphan-worktree.md。`;
119
+ }
120
+ else {
121
+ guidance = "未识别的受保护服务器目标,请确认 deploy 路径。";
122
+ }
123
+ process.stderr.write(`BLOCKED: ${reason}\n${guidance}\n`);
124
+ process.exit(1);
125
+ }
126
+ export function main() {
127
+ const cmd = getCommand();
128
+ if (!cmd)
129
+ process.exit(0);
130
+ if (!hasGuardedHost(cmd))
131
+ process.exit(0);
132
+ const host = whichGuardedHost(cmd);
133
+ if (isCompoundCommand(cmd)) {
134
+ block("检测到复合命令中包含受保护服务器操作,禁止绕过标准部署路径", host);
135
+ }
136
+ if (isDeploySh(cmd))
137
+ process.exit(0);
138
+ const scpDir = checkScpDirection(cmd);
139
+ if (scpDir === "upload")
140
+ block("禁止手动 scp 上传到受保护服务器", host);
141
+ if (scpDir === "download")
142
+ process.exit(0);
143
+ const sshType = checkSshCommand(cmd);
144
+ if (sshType === "write")
145
+ block("禁止手动对受保护服务器执行写操作", host);
146
+ if (sshType === "readonly")
147
+ process.exit(0);
148
+ const rsyncType = checkRsync(cmd);
149
+ if (rsyncType === "write")
150
+ block("禁止手动 rsync 写入受保护服务器", host);
151
+ if (rsyncType === "dryrun")
152
+ process.exit(0);
153
+ block("检测到未识别的受保护服务器操作", host);
154
+ }
155
+ if (require.main === module) {
156
+ main();
157
+ }
@@ -0,0 +1,192 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { match, loadFragment, FALLBACK_FRAGMENTS } from "./context_router";
4
+ import { readAllSignals, runGuards, writeSessionSignal } from "./guard_router";
5
+ const INJECTED_TTL = 3600;
6
+ const INJECTED_FILE = path.join(process.cwd(), ".boll", "guard", "injected.json");
7
+ const METRICS_DIR = path.join(process.cwd(), ".boll", "metrics");
8
+ const METRICS_FILE = path.join(METRICS_DIR, "guard-events.jsonl");
9
+ function emitMetric(event, data = {}) {
10
+ try {
11
+ if (!fs.existsSync(METRICS_DIR)) {
12
+ fs.mkdirSync(METRICS_DIR, { recursive: true });
13
+ }
14
+ const record = {
15
+ ts: new Date().toISOString(),
16
+ session_pid: process.ppid,
17
+ event,
18
+ ...data,
19
+ };
20
+ fs.appendFileSync(METRICS_FILE, JSON.stringify(record) + "\n", "utf-8");
21
+ }
22
+ catch { }
23
+ }
24
+ function readInjected() {
25
+ if (!fs.existsSync(INJECTED_FILE))
26
+ return new Set();
27
+ try {
28
+ const data = JSON.parse(fs.readFileSync(INJECTED_FILE, "utf-8"));
29
+ if (Date.now() / 1000 - data.timestamp > INJECTED_TTL)
30
+ return new Set();
31
+ return new Set(data.fragments || []);
32
+ }
33
+ catch {
34
+ return new Set();
35
+ }
36
+ }
37
+ function writeInjected(fragments) {
38
+ const guardDir = path.join(process.cwd(), ".boll", "guard");
39
+ if (!fs.existsSync(guardDir)) {
40
+ fs.mkdirSync(guardDir, { recursive: true });
41
+ }
42
+ const data = { timestamp: Date.now() / 1000, fragments: Array.from(fragments).sort() };
43
+ fs.writeFileSync(INJECTED_FILE, JSON.stringify(data, null, 2), "utf-8");
44
+ }
45
+ function getFilePath() {
46
+ if (process.argv.includes("--dry-run")) {
47
+ const idx = process.argv.indexOf("--dry-run");
48
+ if (idx + 1 < process.argv.length) {
49
+ return process.argv[idx + 1];
50
+ }
51
+ return null;
52
+ }
53
+ try {
54
+ const input = JSON.parse(fs.readFileSync(0, "utf-8"));
55
+ return input.tool_input?.file_path ?? input.tool_input?.path ?? null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function makeRelative(filePath, repoRoot) {
62
+ try {
63
+ const resolved = path.resolve(filePath);
64
+ return path.relative(repoRoot, resolved);
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ function appendFindings(outputParts, findings) {
71
+ outputParts.push("\n\n## Guard Findings\n");
72
+ for (const raw of findings) {
73
+ const f = raw;
74
+ const blockingTag = f.blocking ? " [blocking]" : "";
75
+ const skills = f.required_skills.join(", ");
76
+ const skillsLine = skills ? `\n required_skills: ${skills}` : "";
77
+ outputParts.push(`- ${f.severity}${blockingTag} ${f.category}: ${f.message}${skillsLine}`);
78
+ }
79
+ }
80
+ const REPO_ROOT = process.cwd();
81
+ const checksDir = path.join(REPO_ROOT, "src", "scripts", "checks");
82
+ export async function main() {
83
+ const startMs = Date.now();
84
+ const checkOnly = process.argv.includes("--check-only");
85
+ const once = process.argv.includes("--once");
86
+ const dryRun = process.argv.includes("--dry-run");
87
+ emitMetric("hook_trigger", {
88
+ mode: checkOnly ? "check_only" : "post_tool_use",
89
+ once,
90
+ dry_run: dryRun,
91
+ });
92
+ if (once) {
93
+ const guardDir = path.join(process.cwd(), ".boll", "guard");
94
+ const sessionFile = path.join(guardDir, `once-${process.pid}.flag`);
95
+ if (fs.existsSync(sessionFile)) {
96
+ try {
97
+ const ts = parseFloat(fs.readFileSync(sessionFile, "utf-8").trim());
98
+ if (Date.now() / 1000 - ts < 3600) {
99
+ process.exit(0);
100
+ }
101
+ }
102
+ catch { }
103
+ }
104
+ if (!fs.existsSync(guardDir)) {
105
+ fs.mkdirSync(guardDir, { recursive: true });
106
+ }
107
+ fs.writeFileSync(sessionFile, String(Date.now() / 1000), "utf-8");
108
+ }
109
+ const outputParts = [];
110
+ if (checkOnly) {
111
+ const signal = readAllSignals(process.pid);
112
+ const findings = signal.findings;
113
+ if (findings.length) {
114
+ appendFindings(outputParts, findings);
115
+ process.stderr.write(outputParts.join("\n") + "\n");
116
+ emitMetric("check_only_findings", {
117
+ findings_count: findings.length,
118
+ elapsed_ms: Date.now() - startMs,
119
+ });
120
+ process.exit(0);
121
+ }
122
+ process.exit(0);
123
+ }
124
+ let filePath = getFilePath();
125
+ if (!filePath)
126
+ process.exit(0);
127
+ filePath = makeRelative(filePath, REPO_ROOT);
128
+ if (!filePath) {
129
+ emitMetric("path_rejected", { reason: "outside_repo_or_unresolvable" });
130
+ process.exit(0);
131
+ }
132
+ const alreadyInjected = readInjected();
133
+ let fragments = match(filePath);
134
+ if (!fragments.length) {
135
+ fragments = Array.isArray(FALLBACK_FRAGMENTS) ? FALLBACK_FRAGMENTS : [FALLBACK_FRAGMENTS];
136
+ }
137
+ const newFragments = fragments.filter(f => !alreadyInjected.has(f));
138
+ const contentParts = [];
139
+ for (const name of newFragments) {
140
+ const text = loadFragment(name);
141
+ if (text)
142
+ contentParts.push(text);
143
+ }
144
+ if (newFragments.length) {
145
+ const totalBytes = contentParts.reduce((sum, p) => sum + Buffer.byteLength(p, "utf-8"), 0);
146
+ emitMetric("fragment_inject", {
147
+ file_path: filePath,
148
+ fragments: newFragments,
149
+ count: newFragments.length,
150
+ bytes: totalBytes,
151
+ est_tokens: Math.ceil(totalBytes / 4),
152
+ });
153
+ }
154
+ if (contentParts.length) {
155
+ outputParts.push("## Context\n");
156
+ outputParts.push(contentParts.join("\n\n---\n\n"));
157
+ newFragments.forEach(f => alreadyInjected.add(f));
158
+ writeInjected(alreadyInjected);
159
+ }
160
+ const findings = await runGuards(filePath, checksDir);
161
+ if (findings.length) {
162
+ const blockingCount = findings.filter(f => f.blocking).length;
163
+ const categories = {};
164
+ const severities = {};
165
+ for (const f of findings) {
166
+ categories[f.category] = (categories[f.category] || 0) + 1;
167
+ severities[f.severity] = (severities[f.severity] || 0) + 1;
168
+ }
169
+ emitMetric("guard_findings", {
170
+ file_path: filePath,
171
+ findings_count: findings.length,
172
+ blocking_count: blockingCount,
173
+ categories,
174
+ severities,
175
+ });
176
+ writeSessionSignal(findings);
177
+ appendFindings(outputParts, findings);
178
+ }
179
+ emitMetric("hook_done", {
180
+ file_path: filePath,
181
+ had_output: outputParts.length > 0,
182
+ elapsed_ms: Date.now() - startMs,
183
+ });
184
+ if (outputParts.length) {
185
+ process.stderr.write(outputParts.join("\n") + "\n");
186
+ process.exit(2);
187
+ }
188
+ process.exit(0);
189
+ }
190
+ if (require.main === module) {
191
+ main();
192
+ }
@@ -0,0 +1,158 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export const GUARD_MAP = {
4
+ "bridge_agent/": ["check_bridge_deps"],
5
+ "backend/product/bridge/": ["check_bridge_deps"],
6
+ "mcp-server/": ["check_mcp_parity"],
7
+ "mcp-server-node/": ["check_mcp_parity"],
8
+ "docs/issues/": ["check_issue_closure"],
9
+ "backend/product/routes/": ["check_doc_freshness"],
10
+ "docs/ROADMAP.md": ["check_doc_freshness"],
11
+ "CLAUDE.md": ["check_doc_freshness"],
12
+ "docs/magic/": ["check_doc_freshness"],
13
+ ".boll/rules/backend-routes.md": ["check_doc_freshness"],
14
+ ".boll/settings.json": ["check_hook_installed"],
15
+ ".githooks/": ["check_hook_installed"],
16
+ "scripts/context_router.ts": ["check_fragment_integrity"],
17
+ "scripts/context-fragments/": ["check_fragment_integrity"],
18
+ };
19
+ export const DEFAULT_GUARDS = [];
20
+ export const CATEGORY_TO_SKILLS = {
21
+ closure_semantics: ["lead", "boll-ops"],
22
+ contract_drift: ["boll-dev", "boll-eng-test"],
23
+ bridge_boundary: ["boll-bridge", "boll-ops"],
24
+ policy_freeze: ["lead", "arch", "plan-lock"],
25
+ doc_integrity: ["boll-ops"],
26
+ version_drift: ["boll-ops"],
27
+ artifact_linkage: ["lead"],
28
+ governance_bootstrap: ["boll-ops"],
29
+ };
30
+ const SESSION_TTL_SECONDS = 3600;
31
+ export function route(filePath) {
32
+ const matched = [];
33
+ const sortedPatterns = Object.keys(GUARD_MAP).sort((a, b) => b.length - a.length);
34
+ for (const pattern of sortedPatterns) {
35
+ if (filePath.startsWith(pattern) || filePath === pattern.replace(/\/$/, "")) {
36
+ matched.push(...GUARD_MAP[pattern]);
37
+ }
38
+ }
39
+ if (matched.length === 0) {
40
+ return [...DEFAULT_GUARDS];
41
+ }
42
+ return [...new Set(matched)];
43
+ }
44
+ export async function runGuards(filePath, checksDir) {
45
+ const guardNames = route(filePath);
46
+ const findings = [];
47
+ for (const name of guardNames) {
48
+ try {
49
+ const checkModule = await import(path.join(checksDir, `${name}.js`));
50
+ if (!checkModule.run) {
51
+ findings.push({
52
+ severity: "P1",
53
+ message: `${name} has no run() function`,
54
+ file: `scripts/checks/${name}.ts`,
55
+ blocking: false,
56
+ category: "governance_bootstrap",
57
+ problem_class: "unknown",
58
+ required_skills: [],
59
+ required_reads: [],
60
+ });
61
+ continue;
62
+ }
63
+ const result = checkModule.run(checksDir, { mode: "full" });
64
+ if (Array.isArray(result)) {
65
+ findings.push(...result);
66
+ }
67
+ else {
68
+ findings.push(result);
69
+ }
70
+ }
71
+ catch (exc) {
72
+ findings.push({
73
+ severity: "P0",
74
+ message: `Failed to import guard ${name}: ${exc}`,
75
+ file: `scripts/checks/${name}.ts`,
76
+ blocking: true,
77
+ category: "governance_bootstrap",
78
+ problem_class: "unknown",
79
+ required_skills: [],
80
+ required_reads: [],
81
+ });
82
+ }
83
+ }
84
+ return findings;
85
+ }
86
+ function getGuardDir() {
87
+ return path.join(process.cwd(), ".boll", "guard");
88
+ }
89
+ export function writeSessionSignal(findings) {
90
+ const guardDir = getGuardDir();
91
+ if (!fs.existsSync(guardDir)) {
92
+ fs.mkdirSync(guardDir, { recursive: true });
93
+ }
94
+ const pid = process.pid;
95
+ const target = path.join(guardDir, `session-${pid}.json`);
96
+ const tmp = path.join(guardDir, `session-${pid}.tmp`);
97
+ const data = {
98
+ pid,
99
+ timestamp: Date.now() / 1000,
100
+ findings,
101
+ };
102
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
103
+ fs.renameSync(tmp, target);
104
+ return target;
105
+ }
106
+ export function readAllSignals(pid) {
107
+ const guardDir = getGuardDir();
108
+ if (!fs.existsSync(guardDir)) {
109
+ return { severity: null, blocking: false, required_skills: [], findings: [] };
110
+ }
111
+ const severityOrder = { P0: 0, P1: 1, P2: 2 };
112
+ const now = Date.now() / 1000;
113
+ const allFindings = [];
114
+ let maxSeverity = null;
115
+ let blocking = false;
116
+ const skills = new Set();
117
+ let paths;
118
+ if (pid !== undefined) {
119
+ paths = [path.join(guardDir, `session-${pid}.json`)];
120
+ }
121
+ else {
122
+ paths = fs.readdirSync(guardDir)
123
+ .filter(f => f.startsWith("session-") && f.endsWith(".json"))
124
+ .map(f => path.join(guardDir, f));
125
+ }
126
+ for (const p of paths) {
127
+ if (!fs.existsSync(p))
128
+ continue;
129
+ try {
130
+ const data = JSON.parse(fs.readFileSync(p, "utf-8"));
131
+ const ts = data.timestamp;
132
+ if (now - ts > SESSION_TTL_SECONDS) {
133
+ try {
134
+ fs.unlinkSync(p);
135
+ }
136
+ catch { }
137
+ continue;
138
+ }
139
+ for (const f of data.findings) {
140
+ allFindings.push(f);
141
+ const sev = f.severity;
142
+ if (maxSeverity === null || severityOrder[sev] < severityOrder[maxSeverity]) {
143
+ maxSeverity = sev;
144
+ }
145
+ if (f.blocking)
146
+ blocking = true;
147
+ f.required_skills.forEach(s => skills.add(s));
148
+ }
149
+ }
150
+ catch { }
151
+ }
152
+ return {
153
+ severity: maxSeverity,
154
+ blocking,
155
+ required_skills: Array.from(skills).sort(),
156
+ findings: allFindings,
157
+ };
158
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ process.stdout.write(`\n\n## Bollharness Context\n\n` +
3
+ `bollharness is an AI Agent Session Governance Framework.\n\n` +
4
+ `For more information, see CLAUDE.md in the project root.\n`);
5
+ process.exit(0);
6
+ export {};
@@ -0,0 +1,6 @@
1
+ export function main() {
2
+ process.exit(0);
3
+ }
4
+ if (require.main === module) {
5
+ main();
6
+ }
@@ -0,0 +1,6 @@
1
+ export function main() {
2
+ process.exit(0);
3
+ }
4
+ if (require.main === module) {
5
+ main();
6
+ }
@@ -0,0 +1,6 @@
1
+ export function main() {
2
+ process.exit(0);
3
+ }
4
+ if (require.main === module) {
5
+ main();
6
+ }