bosun 0.40.9 → 0.40.11

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 (45) hide show
  1. package/.env.example +3 -0
  2. package/README.md +0 -20
  3. package/agent/agent-work-analyzer.mjs +32 -9
  4. package/agent/fleet-coordinator.mjs +2 -6
  5. package/cli.mjs +17 -0
  6. package/git/git-safety.mjs +96 -0
  7. package/git/sdk-conflict-resolver.mjs +2 -0
  8. package/infra/library-manager.mjs +289 -70
  9. package/infra/monitor.mjs +382 -46
  10. package/kanban/kanban-adapter.mjs +29 -0
  11. package/kanban/ve-orchestrator.ps1 +5 -0
  12. package/kanban/vk-error-resolver.mjs +52 -48
  13. package/package.json +2 -3
  14. package/server/ui-server.mjs +174 -33
  15. package/setup.mjs +5 -0
  16. package/task/task-claims.mjs +99 -36
  17. package/task/task-cli.mjs +522 -2
  18. package/task/task-executor.mjs +795 -50
  19. package/task/task-store.mjs +88 -4
  20. package/telegram/telegram-bot.mjs +155 -54
  21. package/ui/app.js +16 -1
  22. package/ui/components/chat-view.js +7 -2
  23. package/ui/components/kanban-board.js +19 -10
  24. package/ui/components/session-list.js +82 -27
  25. package/ui/demo-defaults.js +555 -91
  26. package/ui/modules/session-api.js +1 -1
  27. package/ui/modules/state.js +6 -6
  28. package/ui/styles/components.css +170 -7
  29. package/ui/tabs/agents.js +247 -87
  30. package/ui/tabs/dashboard.js +399 -40
  31. package/ui/tabs/library.js +69 -6
  32. package/ui/tabs/tasks.js +255 -20
  33. package/ui/tabs/workflow-canvas-utils.mjs +182 -0
  34. package/ui/tabs/workflows.js +762 -218
  35. package/workflow/workflow-engine.mjs +132 -16
  36. package/workflow/workflow-nodes.mjs +467 -64
  37. package/workflow/workflow-templates.mjs +85 -1
  38. package/workflow-templates/agents.mjs +3 -2
  39. package/workflow-templates/github.mjs +3 -1
  40. package/workflow-templates/reliability.mjs +23 -12
  41. package/workflow-templates/task-batch.mjs +17 -3
  42. package/workflow-templates/task-lifecycle.mjs +84 -13
  43. package/workspace/context-cache.mjs +113 -4
  44. package/workspace/workspace-manager.mjs +6 -1
  45. package/workspace/worktree-manager.mjs +5 -4
package/.env.example CHANGED
@@ -541,6 +541,9 @@ VOICE_DELEGATE_EXECUTOR=codex-sdk
541
541
  # BOSUN_TASK_CONTEXT_MAX_COMMENTS=8
542
542
  # BOSUN_TASK_CONTEXT_MAX_COMMENT_CHARS=1200
543
543
  # BOSUN_TASK_CONTEXT_MAX_ATTACHMENTS=20
544
+ # Immediate cap for known high-volume git outputs in context cache.
545
+ # Set to 0 to disable the cap entirely (default: 8000).
546
+ # BOSUN_GIT_OUTPUT_MAX_CHARS=8000
544
547
  # Max upload size for task/chat attachments (MB)
545
548
  # BOSUN_ATTACHMENT_MAX_MB=25
546
549
 
package/README.md CHANGED
@@ -149,26 +149,6 @@ Bosun enforces a strict quality pipeline in both local hooks and CI:
149
149
  - **Demo load smoke test** runs in `npm test` and blocks push if `site/index.html` or `site/ui/demo.html` fails to load required assets.
150
150
  - **Prepublish checks** validate package contents and release readiness.
151
151
 
152
- ### Codebase annotation audit
153
-
154
- Use `bosun audit` to generate and validate repo-level annotations that help future agents navigate the codebase without extra runtime context:
155
-
156
- ```bash
157
- # Coverage report for supported source files
158
- bosun audit scan
159
-
160
- # Add missing file summaries and risky-function warnings
161
- bosun audit generate
162
- bosun audit warn
163
-
164
- # Rebuild lean manifests and the file responsibility index
165
- bosun audit manifest
166
- bosun audit index
167
- bosun audit trim
168
-
169
- # CI-safe conformity gate
170
- bosun audit --ci
171
- ```
172
152
 
173
153
  Notes:
174
154
 
@@ -69,9 +69,25 @@ const ALERT_COOLDOWN_RETENTION_MS = Math.max(
69
69
  FAILED_SESSION_TRANSIENT_ALERT_MIN_COOLDOWN_MS * 3,
70
70
  3 * 60 * 60 * 1000,
71
71
  ); // keep cooldown history bounded
72
+ const ALERT_COOLDOWN_REPLAY_MIN_BYTES = 256 * 1024;
73
+ const ALERT_COOLDOWN_REPLAY_DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
74
+ const ALERT_COOLDOWN_REPLAY_MAX_CAP_BYTES = 64 * 1024 * 1024;
75
+
76
+ function normalizeReplayMaxBytes(value) {
77
+ const parsed = Number(value);
78
+ if (!Number.isFinite(parsed) || parsed <= 0) {
79
+ return ALERT_COOLDOWN_REPLAY_DEFAULT_MAX_BYTES;
80
+ }
81
+ const rounded = Math.floor(parsed);
82
+ return Math.min(
83
+ ALERT_COOLDOWN_REPLAY_MAX_CAP_BYTES,
84
+ Math.max(ALERT_COOLDOWN_REPLAY_MIN_BYTES, rounded),
85
+ );
86
+ }
87
+
72
88
  const ALERT_COOLDOWN_REPLAY_MAX_BYTES = Math.max(
73
- 256 * 1024,
74
- Number(process.env.AGENT_ALERT_COOLDOWN_REPLAY_MAX_BYTES || 2 * 1024 * 1024) || 2 * 1024 * 1024,
89
+ ALERT_COOLDOWN_REPLAY_MIN_BYTES,
90
+ normalizeReplayMaxBytes(process.env.AGENT_ALERT_COOLDOWN_REPLAY_MAX_BYTES),
75
91
  );
76
92
 
77
93
  function getAlertCooldownMs(alert) {
@@ -367,15 +383,19 @@ async function processLogFile(startPosition) {
367
383
  * @param {Object} event - Parsed JSONL event
368
384
  */
369
385
  async function analyzeEvent(event) {
370
- const { attempt_id, event_type, timestamp } = event;
386
+ const { event_type, timestamp } = event;
387
+ const attemptId = String(event?.attempt_id || "").trim();
388
+ if (!attemptId) {
389
+ return;
390
+ }
371
391
  const parsedTs = Date.parse(timestamp);
372
392
  const eventTime = Number.isFinite(parsedTs) ? parsedTs : Date.now();
373
393
  const eventIso = new Date(eventTime).toISOString();
374
394
 
375
395
  // Initialize session state if needed
376
- if (!activeSessions.has(attempt_id)) {
377
- activeSessions.set(attempt_id, {
378
- attempt_id,
396
+ if (!activeSessions.has(attemptId)) {
397
+ activeSessions.set(attemptId, {
398
+ attempt_id: attemptId,
379
399
  errors: [],
380
400
  toolCalls: [],
381
401
  lastActivity: eventIso,
@@ -385,7 +405,7 @@ async function analyzeEvent(event) {
385
405
  });
386
406
  }
387
407
 
388
- const session = activeSessions.get(attempt_id);
408
+ const session = activeSessions.get(attemptId);
389
409
  session.lastActivity = eventIso;
390
410
 
391
411
  // Route to specific analyzers
@@ -401,7 +421,7 @@ async function analyzeEvent(event) {
401
421
  break;
402
422
  case "session_end":
403
423
  await analyzeSessionEnd(session, event);
404
- activeSessions.delete(attempt_id);
424
+ activeSessions.delete(attemptId);
405
425
  break;
406
426
  }
407
427
 
@@ -647,7 +667,10 @@ async function emitAlert(alert) {
647
667
  ...alert,
648
668
  };
649
669
 
650
- console.error(`[ALERT] ${alert.type}: ${alert.attempt_id}`);
670
+ const alertScope = String(
671
+ alert?.attempt_id || alert?.task_id || alert?.executor || "unknown",
672
+ );
673
+ console.error(`[ALERT] ${alert.type}: ${alertScope}`);
651
674
 
652
675
  // Append to alerts log
653
676
  try {
@@ -33,6 +33,7 @@ import {
33
33
  selectCoordinator,
34
34
  getPresenceState,
35
35
  } from "../infra/presence.mjs";
36
+ import { sanitizeGitEnv } from "../git/git-safety.mjs";
36
37
 
37
38
  const __dirname = dirname(fileURLToPath(import.meta.url));
38
39
 
@@ -57,11 +58,7 @@ function emitFleetEvent(eventType, eventData = {}, opts = {}) {
57
58
  // ── Repo Fingerprinting ──────────────────────────────────────────────────────
58
59
 
59
60
  function buildGitEnv() {
60
- const env = { ...process.env };
61
- delete env.GIT_DIR;
62
- delete env.GIT_WORK_TREE;
63
- delete env.GIT_INDEX_FILE;
64
- return env;
61
+ return sanitizeGitEnv();
65
62
  }
66
63
 
67
64
  /**
@@ -857,4 +854,3 @@ export function formatFleetSummary() {
857
854
 
858
855
  return lines.join("\n");
859
856
  }
860
-
package/cli.mjs CHANGED
@@ -76,6 +76,7 @@ function showHelp() {
76
76
  bosun [options]
77
77
 
78
78
  COMMANDS
79
+ audit <command> [options] Codebase annotation audit workflows (scan/generate/warn/manifest/index/trim/conformity/migrate)
79
80
  --setup Launch the web-based setup wizard (default)
80
81
  --setup-terminal Run the legacy terminal setup wizard
81
82
  --where Show the resolved bosun config directory
@@ -1237,6 +1238,22 @@ async function main() {
1237
1238
  process.exit(0);
1238
1239
  }
1239
1240
 
1241
+ // Handle 'audit' subcommand before --help so command-specific help works.
1242
+ const auditFlagIndex = args.indexOf("--audit");
1243
+ const auditCommandIndex =
1244
+ args[0] === "audit"
1245
+ ? 0
1246
+ : args[0]?.startsWith("--")
1247
+ ? args.indexOf("audit")
1248
+ : -1;
1249
+ if (auditCommandIndex >= 0 || auditFlagIndex >= 0) {
1250
+ const { runAuditCli } = await import("./lib/codebase-audit.mjs");
1251
+ const commandStartIndex = auditCommandIndex >= 0 ? auditCommandIndex : auditFlagIndex;
1252
+ const auditArgs = args.slice(commandStartIndex + 1);
1253
+ const result = await runAuditCli(auditArgs);
1254
+ process.exit(Number.isInteger(result?.exitCode) ? result.exitCode : 0);
1255
+ }
1256
+
1240
1257
  // Handle --help
1241
1258
  if (args.includes("--help") || args.includes("-h")) {
1242
1259
  showHelp();
@@ -1,14 +1,98 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
 
3
+ const STRIPPED_GIT_ENV_KEYS = [
4
+ "GIT_DIR",
5
+ "GIT_WORK_TREE",
6
+ "GIT_COMMON_DIR",
7
+ "GIT_INDEX_FILE",
8
+ "GIT_OBJECT_DIRECTORY",
9
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
10
+ "GIT_NAMESPACE",
11
+ "GIT_PREFIX",
12
+ "GIT_SUPER_PREFIX",
13
+ ];
14
+
15
+ const BLOCKED_TEST_GIT_IDENTITIES = new Set([
16
+ "test@example.com",
17
+ "bosun-tests@example.com",
18
+ ]);
19
+
20
+ const TEST_FIXTURE_SENTINEL_PATHS = new Set([
21
+ ".github/agents/TaskPlanner.agent.md",
22
+ ]);
23
+
3
24
  function runGit(args, cwd, timeout = 15_000) {
4
25
  return spawnSync("git", args, {
5
26
  cwd,
6
27
  encoding: "utf8",
7
28
  timeout,
8
29
  shell: false,
30
+ env: sanitizeGitEnv(),
9
31
  });
10
32
  }
11
33
 
34
+ export function sanitizeGitEnv(baseEnv = process.env, extraEnv = {}) {
35
+ const env = { ...baseEnv };
36
+ for (const key of STRIPPED_GIT_ENV_KEYS) {
37
+ delete env[key];
38
+ }
39
+ return { ...env, ...extraEnv };
40
+ }
41
+
42
+ function getGitConfig(cwd, key) {
43
+ const result = runGit(["config", "--get", key], cwd, 5_000);
44
+ if (result.status !== 0) return "";
45
+ return String(result.stdout || "").trim();
46
+ }
47
+
48
+ function listTrackedFiles(cwd, ref = "HEAD") {
49
+ const result = runGit(["ls-tree", "-r", "--name-only", ref], cwd, 30_000);
50
+ if (result.status !== 0) return null;
51
+ const out = String(result.stdout || "").trim();
52
+ return out ? out.split("\n").filter(Boolean) : [];
53
+ }
54
+
55
+ function collectBlockedIdentitySignals(cwd) {
56
+ const signals = [];
57
+ const envChecks = [
58
+ ["GIT_AUTHOR_EMAIL", process.env.GIT_AUTHOR_EMAIL],
59
+ ["GIT_COMMITTER_EMAIL", process.env.GIT_COMMITTER_EMAIL],
60
+ ["VE_GIT_AUTHOR_EMAIL", process.env.VE_GIT_AUTHOR_EMAIL],
61
+ ];
62
+ for (const [key, value] of envChecks) {
63
+ const email = String(value || "").trim().toLowerCase();
64
+ if (BLOCKED_TEST_GIT_IDENTITIES.has(email)) {
65
+ signals.push(`${key}=${email}`);
66
+ }
67
+ }
68
+
69
+ const configChecks = [
70
+ ["git config user.email", getGitConfig(cwd, "user.email")],
71
+ ["git config author.email", getGitConfig(cwd, "author.email")],
72
+ ["git config committer.email", getGitConfig(cwd, "committer.email")],
73
+ ];
74
+ for (const [label, value] of configChecks) {
75
+ const email = String(value || "").trim().toLowerCase();
76
+ if (BLOCKED_TEST_GIT_IDENTITIES.has(email)) {
77
+ signals.push(`${label}=${email}`);
78
+ }
79
+ }
80
+
81
+ return signals;
82
+ }
83
+
84
+ function detectKnownFixtureSignature(cwd) {
85
+ const trackedFiles = listTrackedFiles(cwd, "HEAD");
86
+ if (!trackedFiles) return null;
87
+ const sentinelHits = trackedFiles.filter((file) => TEST_FIXTURE_SENTINEL_PATHS.has(file));
88
+ if (sentinelHits.length === 0) return null;
89
+ if (trackedFiles.length > 10) return null;
90
+ return {
91
+ trackedFiles: trackedFiles.length,
92
+ sentinels: sentinelHits,
93
+ };
94
+ }
95
+
12
96
  function countTrackedFiles(cwd, ref) {
13
97
  const result = runGit(["ls-tree", "-r", "--name-only", ref], cwd, 30_000);
14
98
  if (result.status !== 0) return null;
@@ -129,6 +213,18 @@ export function evaluateBranchSafetyForPush(worktreePath, opts = {}) {
129
213
  }
130
214
 
131
215
  const reasons = [];
216
+ const blockedIdentitySignals = collectBlockedIdentitySignals(worktreePath);
217
+ if (blockedIdentitySignals.length > 0) {
218
+ reasons.push(`blocked test git identity detected (${blockedIdentitySignals.join(", ")})`);
219
+ }
220
+
221
+ const fixtureSignature = detectKnownFixtureSignature(worktreePath);
222
+ if (fixtureSignature) {
223
+ reasons.push(
224
+ `HEAD matches known test fixture signature (${fixtureSignature.sentinels.join(", ")} in ${fixtureSignature.trackedFiles} tracked files)`,
225
+ );
226
+ }
227
+
132
228
  if (baseFiles >= 500 && headFiles <= Math.max(25, Math.floor(baseFiles * 0.15))) {
133
229
  reasons.push(`HEAD tracks only ${headFiles}/${baseFiles} files vs ${remoteRef}`);
134
230
  }
@@ -25,6 +25,7 @@ import { resolveCodexProfileRuntime } from "../shell/codex-model-profiles.mjs";
25
25
  import {
26
26
  evaluateBranchSafetyForPush,
27
27
  normalizeBaseBranch,
28
+ sanitizeGitEnv,
28
29
  } from "./git-safety.mjs";
29
30
 
30
31
  // ── Configuration ────────────────────────────────────────────────────────────
@@ -73,6 +74,7 @@ function gitExec(args, cwd, timeoutMs = 30_000) {
73
74
  stdio: ["ignore", "pipe", "pipe"],
74
75
  shell: false,
75
76
  timeout: timeoutMs,
77
+ env: sanitizeGitEnv(),
76
78
  });
77
79
 
78
80
  let stdout = "";