@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,146 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { execSync } from "child_process";
4
+ const CODE_EXTENSIONS = new Set([".py", ".ts", ".tsx", ".js", ".jsx"]);
5
+ const CONFIG_EXTENSIONS = new Set([".json", ".yml", ".yaml", ".toml"]);
6
+ const TEST_PREFIXES = ["tests/", "test_"];
7
+ const ARTIFACT_PREFIXES = ["docs/issues/", "docs/decisions/"];
8
+ const BUILD_DIRS = new Set([".open-next", "dist", "build", ".next", "node_modules"]);
9
+ function isCodeFile(filePath) {
10
+ const p = path.parse(filePath);
11
+ if (!CODE_EXTENSIONS.has(p.ext))
12
+ return false;
13
+ const parts = p.dir.split(path.sep);
14
+ for (const part of parts) {
15
+ if (part.startsWith("test_") || part === "tests")
16
+ return false;
17
+ }
18
+ for (const part of parts) {
19
+ if (BUILD_DIRS.has(part))
20
+ return false;
21
+ }
22
+ return true;
23
+ }
24
+ function isArtifactFile(filePath) {
25
+ return ARTIFACT_PREFIXES.some(prefix => filePath.startsWith(prefix)) && filePath.endsWith(".md");
26
+ }
27
+ function extractScope(artifactPath) {
28
+ try {
29
+ const text = fs.readFileSync(artifactPath, "utf-8");
30
+ if (!text.startsWith("---"))
31
+ return [];
32
+ const end = text.indexOf("\n---", 3);
33
+ if (end === -1)
34
+ return [];
35
+ const frontmatter = text.slice(3, end);
36
+ const scopes = [];
37
+ let inScope = false;
38
+ for (const line of frontmatter.split("\n")) {
39
+ const stripped = line.trim();
40
+ if (stripped.startsWith("scope:")) {
41
+ const value = stripped.slice(6).trim();
42
+ if (value && !value.startsWith("[")) {
43
+ scopes.push(value);
44
+ inScope = false;
45
+ }
46
+ else if (value.startsWith("[")) {
47
+ const inner = value.replace(/[\[\] ]/g, "");
48
+ scopes.push(...inner.split(",").filter(s => s));
49
+ inScope = false;
50
+ }
51
+ else {
52
+ inScope = true;
53
+ }
54
+ continue;
55
+ }
56
+ if (inScope) {
57
+ if (stripped.startsWith("- ")) {
58
+ scopes.push(stripped.slice(2).trim().replace(/^["']|["']$/g, ""));
59
+ }
60
+ else if (stripped && !stripped.startsWith("#")) {
61
+ inScope = false;
62
+ }
63
+ }
64
+ }
65
+ return scopes.filter(s => s);
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ function checkScopeBinding(codeFiles, artifactFiles, repoRoot) {
72
+ const allScopes = [];
73
+ for (const af of artifactFiles) {
74
+ allScopes.push(...extractScope(path.join(repoRoot, af)));
75
+ }
76
+ if (allScopes.length === 0)
77
+ return [];
78
+ const uncovered = [];
79
+ for (const cf of codeFiles) {
80
+ if (!allScopes.some(scope => cf.startsWith(scope))) {
81
+ uncovered.push(cf);
82
+ }
83
+ }
84
+ if (uncovered.length === 0)
85
+ return [];
86
+ return [{
87
+ severity: "P2",
88
+ message: `Scope binding gap: ${uncovered.length} code file(s) not covered by any artifact scope. Uncovered: ${uncovered.slice(0, 5).join(", ")}${uncovered.length > 5 ? ` (and ${uncovered.length - 5} more)` : ""}. Add a scope field to the relevant artifact doc.`,
89
+ file: uncovered[0],
90
+ blocking: false,
91
+ category: "scope_binding",
92
+ problem_class: "unknown",
93
+ required_skills: ["lead"],
94
+ required_reads: [],
95
+ }];
96
+ }
97
+ function getChangedFiles(mode, repoRoot) {
98
+ if (mode === "staged") {
99
+ try {
100
+ const result = execSync("git diff --cached --name-only", { cwd: repoRoot, encoding: "utf-8" });
101
+ return result.split("\n").map(l => l.trim()).filter(l => l);
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
107
+ else if (mode === "ci") {
108
+ try {
109
+ const result = execSync("git diff origin/main..HEAD --name-only", { cwd: repoRoot, encoding: "utf-8" });
110
+ return result.split("\n").map(l => l.trim()).filter(l => l);
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ return [];
117
+ }
118
+ export function run(repoRoot, mode = "full") {
119
+ if (mode === "full")
120
+ return [];
121
+ const files = getChangedFiles(mode, repoRoot);
122
+ if (!files.length)
123
+ return [];
124
+ const codeFiles = files.filter(f => isCodeFile(f));
125
+ const artifactFiles = files.filter(f => isArtifactFile(f));
126
+ const hasCode = codeFiles.length > 0;
127
+ const hasArtifact = artifactFiles.length > 0;
128
+ const findings = [];
129
+ if (hasCode && !hasArtifact) {
130
+ findings.push({
131
+ severity: "P1",
132
+ message: `Code changes without artifact documentation. ${codeFiles.length} code file(s) changed but no docs/issues/ or docs/decisions/ file included. Add an issue or decision doc.`,
133
+ file: codeFiles[0],
134
+ blocking: true,
135
+ category: "artifact_linkage",
136
+ problem_class: "unknown",
137
+ required_skills: ["lead"],
138
+ required_reads: [],
139
+ });
140
+ return findings;
141
+ }
142
+ if (hasCode && hasArtifact) {
143
+ findings.push(...checkScopeBinding(codeFiles, artifactFiles, repoRoot));
144
+ }
145
+ return findings;
146
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,135 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ function countRouteDecorators(repoRoot) {
4
+ const routesDir = path.join(repoRoot, "backend", "product", "routes");
5
+ if (!fs.existsSync(routesDir))
6
+ return 0;
7
+ let count = 0;
8
+ try {
9
+ const files = fs.readdirSync(routesDir).filter(f => f.endsWith(".py"));
10
+ for (const file of files) {
11
+ const content = fs.readFileSync(path.join(routesDir, file), "utf-8");
12
+ const matches = content.match(/@router\.(get|post|put|patch|delete|websocket)\(/g);
13
+ if (matches)
14
+ count += matches.length;
15
+ }
16
+ }
17
+ catch { }
18
+ return count;
19
+ }
20
+ function countScenes(repoRoot) {
21
+ const scenesDir = path.join(repoRoot, "scenes");
22
+ if (!fs.existsSync(scenesDir))
23
+ return 0;
24
+ try {
25
+ return fs.readdirSync(scenesDir).filter(f => {
26
+ const fullPath = path.join(scenesDir, f);
27
+ return fs.statSync(fullPath).isDirectory() && !f.startsWith(".");
28
+ }).length;
29
+ }
30
+ catch {
31
+ return 0;
32
+ }
33
+ }
34
+ function countADRs(repoRoot) {
35
+ const decisions = path.join(repoRoot, "docs", "decisions");
36
+ if (!fs.existsSync(decisions))
37
+ return 0;
38
+ try {
39
+ return fs.readdirSync(decisions).filter(f => f.startsWith("ADR-") && f.endsWith(".md")).length;
40
+ }
41
+ catch {
42
+ return 0;
43
+ }
44
+ }
45
+ function countPlans(repoRoot) {
46
+ const decisions = path.join(repoRoot, "docs", "decisions");
47
+ if (!fs.existsSync(decisions))
48
+ return 0;
49
+ try {
50
+ return fs.readdirSync(decisions).filter(f => f.startsWith("PLAN-") && f.endsWith(".md")).length;
51
+ }
52
+ catch {
53
+ return 0;
54
+ }
55
+ }
56
+ function extractRoadmapNumbers(repoRoot) {
57
+ const roadmap = path.join(repoRoot, "docs", "ROADMAP.md");
58
+ if (!fs.existsSync(roadmap))
59
+ return {};
60
+ const content = fs.readFileSync(roadmap, "utf-8");
61
+ const numbers = {};
62
+ const regex = /\|\s*(\S[^|]+?)\s*\|\s*(\S[^|]+?)\s*\|/g;
63
+ let match;
64
+ while ((match = regex.exec(content)) !== null) {
65
+ const key = match[1].trim();
66
+ const val = match[2].trim();
67
+ if (key !== "指标" && key !== "---") {
68
+ numbers[key] = val;
69
+ }
70
+ }
71
+ return numbers;
72
+ }
73
+ export function run(repoRoot, mode = "full") {
74
+ const findings = [];
75
+ const actualRoutes = countRouteDecorators(repoRoot);
76
+ const routesDoc = path.join(repoRoot, ".boll", "rules", "backend-routes.md");
77
+ const claudeMd = path.join(repoRoot, "CLAUDE.md");
78
+ if (fs.existsSync(routesDoc)) {
79
+ const routesContent = fs.readFileSync(routesDoc, "utf-8");
80
+ const routeClaims = routesContent.match(/-\s+`(?:GET|POST|PUT|PATCH|DELETE|WS)\s+/g) || [];
81
+ const docRoutes = routeClaims.length;
82
+ if (actualRoutes - docRoutes > 5) {
83
+ findings.push({
84
+ severity: "P1",
85
+ message: `.boll/rules/backend-routes.md documents ${docRoutes} routes but code has ${actualRoutes} decorators (gap: ${actualRoutes - docRoutes})`,
86
+ file: ".boll/rules/backend-routes.md",
87
+ blocking: false,
88
+ category: "doc_integrity",
89
+ problem_class: "unknown",
90
+ required_skills: [],
91
+ required_reads: [],
92
+ });
93
+ }
94
+ }
95
+ const actualADRs = countADRs(repoRoot);
96
+ const actualPlans = countPlans(repoRoot);
97
+ const roadmapNums = extractRoadmapNumbers(repoRoot);
98
+ if ("ADR" in roadmapNums) {
99
+ const match = roadmapNums["ADR"].match(/\d+/);
100
+ if (match) {
101
+ const claimed = parseInt(match[0], 10);
102
+ if (actualADRs - claimed > 2) {
103
+ findings.push({
104
+ severity: "P2",
105
+ message: `ROADMAP claims ${claimed} ADRs but ${actualADRs} exist`,
106
+ file: "docs/ROADMAP.md",
107
+ blocking: false,
108
+ category: "doc_integrity",
109
+ problem_class: "unknown",
110
+ required_skills: [],
111
+ required_reads: [],
112
+ });
113
+ }
114
+ }
115
+ }
116
+ if ("PLAN" in roadmapNums) {
117
+ const match = roadmapNums["PLAN"].match(/\d+/);
118
+ if (match) {
119
+ const claimed = parseInt(match[0], 10);
120
+ if (actualPlans - claimed > 3) {
121
+ findings.push({
122
+ severity: "P2",
123
+ message: `ROADMAP claims ${claimed} PLANs but ${actualPlans} exist`,
124
+ file: "docs/ROADMAP.md",
125
+ blocking: false,
126
+ category: "doc_integrity",
127
+ problem_class: "unknown",
128
+ required_skills: [],
129
+ required_reads: [],
130
+ });
131
+ }
132
+ }
133
+ }
134
+ return findings;
135
+ }
@@ -0,0 +1,31 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export function run(repoRoot, mode = "full") {
4
+ const findings = [];
5
+ const docFiles = ["CLAUDE.md", "docs/ROADMAP.md", "README.md"];
6
+ const maxAgeDays = 7;
7
+ for (const docFile of docFiles) {
8
+ const fullPath = path.join(repoRoot, docFile);
9
+ if (!fs.existsSync(fullPath))
10
+ continue;
11
+ try {
12
+ const stats = fs.statSync(fullPath);
13
+ const ageMs = Date.now() - stats.mtimeMs;
14
+ const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
15
+ if (ageDays > maxAgeDays) {
16
+ findings.push({
17
+ severity: "P2",
18
+ message: `${docFile} has not been updated in ${ageDays} days (threshold: ${maxAgeDays})`,
19
+ file: docFile,
20
+ blocking: false,
21
+ category: "doc_freshness",
22
+ problem_class: "unknown",
23
+ required_skills: [],
24
+ required_reads: [],
25
+ });
26
+ }
27
+ }
28
+ catch { }
29
+ }
30
+ return findings;
31
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "staged")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,34 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export function run(repoRoot, mode = "full") {
4
+ const findings = [];
5
+ const fragmentDir = path.join(repoRoot, "scripts", "context-fragments");
6
+ const routerFile = path.join(repoRoot, "src", "scripts", "context_router.ts");
7
+ if (!fs.existsSync(routerFile)) {
8
+ findings.push({
9
+ severity: "P1",
10
+ message: "context_router.ts not found",
11
+ file: "src/scripts/context_router.ts",
12
+ blocking: false,
13
+ category: "governance_bootstrap",
14
+ problem_class: "unknown",
15
+ required_skills: [],
16
+ required_reads: [],
17
+ });
18
+ return findings;
19
+ }
20
+ if (!fs.existsSync(fragmentDir)) {
21
+ findings.push({
22
+ severity: "P2",
23
+ message: "context-fragments directory not found",
24
+ file: "scripts/context-fragments/",
25
+ blocking: false,
26
+ category: "governance_bootstrap",
27
+ problem_class: "unknown",
28
+ required_skills: [],
29
+ required_reads: [],
30
+ });
31
+ return findings;
32
+ }
33
+ return findings;
34
+ }
@@ -0,0 +1,63 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export function run(repoRoot, mode = "full") {
4
+ const findings = [];
5
+ const hookFiles = [
6
+ "src/scripts/hooks/loop-detection.ts",
7
+ "src/scripts/hooks/risk-tracker.ts",
8
+ "src/scripts/hooks/stop-evaluator.ts",
9
+ "src/scripts/hooks/guard-feedback.ts",
10
+ "src/scripts/hooks/deploy-guard.ts",
11
+ ];
12
+ const settingsFile = path.join(repoRoot, ".boll", "settings.json");
13
+ if (!fs.existsSync(settingsFile)) {
14
+ findings.push({
15
+ severity: "P1",
16
+ message: ".boll/settings.json does not exist",
17
+ file: ".boll/settings.json",
18
+ blocking: false,
19
+ category: "general",
20
+ problem_class: "unknown",
21
+ required_skills: [],
22
+ required_reads: [],
23
+ });
24
+ return findings;
25
+ }
26
+ try {
27
+ const settings = JSON.parse(fs.readFileSync(settingsFile, "utf-8"));
28
+ const registeredHooks = settings.hooks || {};
29
+ for (const [stage, hooks] of Object.entries(registeredHooks)) {
30
+ const hookList = hooks;
31
+ for (const hookGroup of hookList) {
32
+ for (const hook of hookGroup.hooks || []) {
33
+ const cmd = hook.command || "";
34
+ if (cmd.includes("python3") && cmd.includes(".py")) {
35
+ findings.push({
36
+ severity: "P1",
37
+ message: `Hook in ${stage} still references Python script: ${cmd}`,
38
+ file: ".boll/settings.json",
39
+ blocking: false,
40
+ category: "governance_bootstrap",
41
+ problem_class: "unknown",
42
+ required_skills: [],
43
+ required_reads: [],
44
+ });
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ catch (exc) {
51
+ findings.push({
52
+ severity: "P1",
53
+ message: `Failed to parse settings.json: ${exc}`,
54
+ file: ".boll/settings.json",
55
+ blocking: false,
56
+ category: "governance_bootstrap",
57
+ problem_class: "unknown",
58
+ required_skills: [],
59
+ required_reads: [],
60
+ });
61
+ }
62
+ return findings;
63
+ }
@@ -0,0 +1,41 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ export function run(repoRoot, mode = "full") {
4
+ const findings = [];
5
+ const docsIssuesDir = path.join(repoRoot, "docs", "issues");
6
+ if (!fs.existsSync(docsIssuesDir)) {
7
+ return findings;
8
+ }
9
+ const issueFiles = fs.readdirSync(docsIssuesDir).filter(f => f.endsWith(".md"));
10
+ for (const issueFile of issueFiles) {
11
+ const issuePath = path.join(docsIssuesDir, issueFile);
12
+ const content = fs.readFileSync(issuePath, "utf-8");
13
+ const hasStatus = /^(status|State):\s*/im.test(content);
14
+ const hasResolution = /^(resolution|Closed):\s*/im.test(content);
15
+ if (!hasStatus) {
16
+ findings.push({
17
+ severity: "P2",
18
+ message: `Issue ${issueFile} missing status field`,
19
+ file: `docs/issues/${issueFile}`,
20
+ blocking: false,
21
+ category: "doc_integrity",
22
+ problem_class: "unknown",
23
+ required_skills: [],
24
+ required_reads: [],
25
+ });
26
+ }
27
+ if (!hasResolution) {
28
+ findings.push({
29
+ severity: "P2",
30
+ message: `Issue ${issueFile} missing resolution/closed field`,
31
+ file: `docs/issues/${issueFile}`,
32
+ blocking: false,
33
+ category: "doc_integrity",
34
+ problem_class: "unknown",
35
+ required_skills: [],
36
+ required_reads: [],
37
+ });
38
+ }
39
+ }
40
+ return findings;
41
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,48 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ const SENSITIVE_PATTERNS = [
4
+ { pattern: /api[_-]?key["\s:=]+["'][a-zA-Z0-9]{20,}["']/gi, severity: "P0" },
5
+ { pattern: /password["\s:=]+["'][^"']{8,}["']/gi, severity: "P0" },
6
+ { pattern: /secret["\s:=]+["'][a-zA-Z0-9]{16,}["']/gi, severity: "P0" },
7
+ { pattern: /token["\s:=]+["'][a-zA-Z0-9]{20,}["']/gi, severity: "P1" },
8
+ { pattern: /bearer\s+[a-zA-Z0-9]{20,}/gi, severity: "P1" },
9
+ ];
10
+ export function run(repoRoot, mode = "full") {
11
+ const findings = [];
12
+ if (mode !== "full" && mode !== "ci")
13
+ return findings;
14
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".json", ".yaml", ".yml"];
15
+ function scanDir(dir) {
16
+ if (!fs.existsSync(dir))
17
+ return;
18
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ const fullPath = path.join(dir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
23
+ scanDir(fullPath);
24
+ }
25
+ }
26
+ else if (extensions.includes(path.extname(entry.name))) {
27
+ const content = fs.readFileSync(fullPath, "utf-8");
28
+ for (const { pattern, severity } of SENSITIVE_PATTERNS) {
29
+ const matches = content.match(pattern);
30
+ if (matches) {
31
+ findings.push({
32
+ severity,
33
+ message: `Potential secret detected: ${matches[0].slice(0, 30)}...`,
34
+ file: path.relative(repoRoot, fullPath),
35
+ blocking: severity === "P0",
36
+ category: "security",
37
+ problem_class: "secret_exposure",
38
+ required_skills: [],
39
+ required_reads: [],
40
+ });
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ scanDir(repoRoot);
47
+ return findings;
48
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,13 @@
1
+ export function createFinding(params) {
2
+ return {
3
+ severity: params.severity,
4
+ message: params.message,
5
+ file: params.file,
6
+ line: params.line,
7
+ blocking: params.blocking ?? false,
8
+ category: params.category ?? "general",
9
+ problem_class: params.problem_class ?? "unknown",
10
+ required_skills: params.required_skills ?? [],
11
+ required_reads: params.required_reads ?? [],
12
+ };
13
+ }
@@ -0,0 +1,20 @@
1
+ import { execSync } from "child_process";
2
+ const REPO_ROOT = process.cwd();
3
+ export function main() {
4
+ const args = process.argv.slice(2);
5
+ if (args[0] === "next") {
6
+ try {
7
+ const result = execSync("git log --oneline -1", { cwd: REPO_ROOT, encoding: "utf-8" });
8
+ const match = result.match(/ADR-(\d+)/);
9
+ if (match) {
10
+ const nextNum = parseInt(match[1], 10) + 1;
11
+ console.log(`ADR-${nextNum}`);
12
+ }
13
+ }
14
+ catch { }
15
+ }
16
+ process.exit(0);
17
+ }
18
+ if (require.main === module) {
19
+ main();
20
+ }
@@ -0,0 +1,6 @@
1
+ export function run(repoRoot, mode = "full") {
2
+ const findings = [];
3
+ if (mode !== "full" && mode !== "ci")
4
+ return findings;
5
+ return findings;
6
+ }
@@ -0,0 +1,8 @@
1
+ const REPO_ROOT = process.cwd();
2
+ export function main() {
3
+ console.log("Detecting rebase triggers...");
4
+ process.exit(0);
5
+ }
6
+ if (require.main === module) {
7
+ main();
8
+ }
@@ -0,0 +1,8 @@
1
+ const REPO_ROOT = process.cwd();
2
+ export function main() {
3
+ console.log("Scanning subprocess configurations...");
4
+ process.exit(0);
5
+ }
6
+ if (require.main === module) {
7
+ main();
8
+ }
@@ -0,0 +1,8 @@
1
+ const REPO_ROOT = process.cwd();
2
+ export function main() {
3
+ console.log("Scanning for artifact verification...");
4
+ process.exit(0);
5
+ }
6
+ if (require.main === module) {
7
+ main();
8
+ }
@@ -0,0 +1,8 @@
1
+ const REPO_ROOT = process.cwd();
2
+ export function main() {
3
+ console.log("Scanning YAML schemas...");
4
+ process.exit(0);
5
+ }
6
+ if (require.main === module) {
7
+ main();
8
+ }