bosun 0.40.8 → 0.40.10

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 (74) hide show
  1. package/.env.example +3 -0
  2. package/README.md +11 -1
  3. package/agent/agent-endpoint.mjs +58 -32
  4. package/agent/agent-pool.mjs +9 -1
  5. package/agent/autofix.mjs +14 -17
  6. package/agent/bosun-skills.mjs +7 -112
  7. package/agent/fleet-coordinator.mjs +2 -6
  8. package/agent/skills/skill-codebase-audit.md +111 -0
  9. package/bosun-tui.mjs +141 -0
  10. package/cli.mjs +49 -9
  11. package/config/config.mjs +6 -2
  12. package/config/repo-root.mjs +28 -0
  13. package/desktop/main.mjs +14 -2
  14. package/git/git-safety.mjs +97 -1
  15. package/git/sdk-conflict-resolver.mjs +20 -22
  16. package/github/github-oauth-portal.mjs +24 -11
  17. package/infra/container-runner.mjs +26 -16
  18. package/infra/library-manager.mjs +43 -4
  19. package/infra/monitor.mjs +209 -40
  20. package/infra/runtime-accumulator.mjs +206 -0
  21. package/infra/session-tracker.mjs +18 -0
  22. package/infra/tracing.mjs +293 -0
  23. package/kanban/kanban-adapter.mjs +32 -17
  24. package/kanban/ve-orchestrator.ps1 +5 -0
  25. package/kanban/vk-error-resolver.mjs +52 -48
  26. package/kanban/vk-log-stream.mjs +21 -6
  27. package/lib/codebase-audit.mjs +807 -0
  28. package/lib/session-insights.mjs +78 -33
  29. package/package.json +17 -3
  30. package/server/setup-web-server.mjs +12 -7
  31. package/server/ui-server.mjs +381 -52
  32. package/setup.mjs +18 -11
  33. package/shell/codex-config.mjs +9 -1
  34. package/shell/codex-model-profiles.mjs +9 -1
  35. package/shell/codex-shell.mjs +11 -3
  36. package/task/task-executor.mjs +53 -14
  37. package/task/task-store.mjs +47 -5
  38. package/telegram/telegram-bot.mjs +33 -22
  39. package/tools/prepublish-check.mjs +269 -10
  40. package/tui/app.mjs +160 -0
  41. package/tui/components/status-header.mjs +145 -0
  42. package/tui/lib/ws-bridge.mjs +193 -0
  43. package/tui/screens/agents.mjs +294 -0
  44. package/tui/screens/status.mjs +197 -0
  45. package/tui/screens/tasks.mjs +260 -0
  46. package/ui/app.legacy.js +13 -1464
  47. package/ui/components/session-list.js +53 -22
  48. package/ui/demo-defaults.js +1643 -64
  49. package/ui/demo.html +4 -2
  50. package/ui/index.html +2 -2
  51. package/ui/modules/state.js +13 -0
  52. package/ui/styles/components.css +170 -7
  53. package/ui/tabs/agents.js +250 -86
  54. package/ui/tabs/tasks.js +255 -20
  55. package/ui/tabs/workflow-canvas-utils.mjs +182 -0
  56. package/ui/tabs/workflows.js +515 -175
  57. package/ui/vendor/es-module-shims.js +1172 -753
  58. package/voice/voice-action-dispatcher.mjs +3 -2
  59. package/voice/voice-auth-manager.mjs +10 -1
  60. package/voice/voice-relay.mjs +1 -1
  61. package/voice/voice-tools.mjs +92 -32
  62. package/workflow/manual-flows.mjs +10 -8
  63. package/workflow/pipeline.mjs +319 -0
  64. package/workflow/workflow-contract.mjs +242 -0
  65. package/workflow/workflow-engine.mjs +94 -7
  66. package/workflow/workflow-nodes.mjs +291 -44
  67. package/workflow/workflow-templates.mjs +11 -1
  68. package/workflow-templates/code-quality.mjs +307 -0
  69. package/workflow-templates/issue-continuation.mjs +243 -0
  70. package/workspace/context-cache.mjs +113 -4
  71. package/workspace/shared-state-manager.mjs +14 -7
  72. package/workspace/workspace-manager.mjs +6 -1
  73. package/workspace/workspace-monitor.mjs +25 -10
  74. package/workspace/worktree-manager.mjs +17 -9
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
@@ -144,17 +144,27 @@ Key places to start:
144
144
 
145
145
  Bosun enforces a strict quality pipeline in both local hooks and CI:
146
146
 
147
- - **Pre-commit hooks** auto-format and lint staged files.
147
+ - **Pre-commit hooks** run syntax checks and warn when staged source files are missing `CLAUDE:SUMMARY` annotations.
148
148
  - **Pre-push hooks** run targeted checks based on changed files (Go, portal, docs).
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
+
153
+ Notes:
154
+
155
+ - `bosun audit --ci` exits non-zero on missing summaries, stale warnings, stale `INDEX.map` entries, or credential-like secrets.
156
+ - `.githooks/pre-commit` already warns on newly staged files that are missing `CLAUDE:SUMMARY`.
157
+ - GitHub Actions can opt into the audit gate by setting the repository variable `BOSUN_AUDIT_CI=1`.
158
+
152
159
  Local commands you can run any time:
153
160
 
154
161
  ```bash
155
162
  # Syntax + tests for bosun package
156
163
  npm test
157
164
 
165
+ # Annotation conformity gate
166
+ npm run audit:ci
167
+
158
168
  # Prepublish safety checks
159
169
  npm run prepublishOnly
160
170
 
@@ -358,7 +358,11 @@ export class AgentEndpoint {
358
358
  */
359
359
  async _killProcessOnPort(port) {
360
360
  try {
361
- const { execSync, spawnSync } = await import("node:child_process");
361
+ const { spawnSync } = await import("node:child_process");
362
+ const portNumber = Number.parseInt(String(port), 10);
363
+ if (!Number.isInteger(portNumber) || portNumber <= 0 || portNumber > 65535) {
364
+ throw new Error(`invalid port: ${port}`);
365
+ }
362
366
  const isWindows = process.platform === "win32";
363
367
  let output;
364
368
  const pids = new Set();
@@ -402,12 +406,29 @@ export class AgentEndpoint {
402
406
  };
403
407
 
404
408
  if (isWindows) {
405
- // Windows: netstat -ano | findstr
406
- output = execSync(`netstat -ano | findstr ":${port}"`, {
409
+ // Windows: netstat -ano then filter in-process to avoid shell command injection.
410
+ const netstatRes = spawnSync("netstat", ["-ano"], {
407
411
  encoding: "utf8",
408
412
  timeout: 5000,
409
- }).trim();
410
- const lines = output.split("\n").filter((line) => line.includes("LISTENING"));
413
+ windowsHide: true,
414
+ stdio: ["ignore", "pipe", "pipe"],
415
+ });
416
+ if (netstatRes.error) throw netstatRes.error;
417
+ if (netstatRes.status !== 0) {
418
+ throw new Error(
419
+ String(
420
+ netstatRes.stderr ||
421
+ netstatRes.stdout ||
422
+ `netstat exited with status ${netstatRes.status}`,
423
+ ).trim(),
424
+ );
425
+ }
426
+ output = String(netstatRes.stdout || "").trim();
427
+ const lines = output
428
+ .split("\n")
429
+ .filter(
430
+ (line) => line.includes("LISTENING") && line.includes(`:${portNumber}`),
431
+ );
411
432
  for (const line of lines) {
412
433
  const parts = line.trim().split(/\s+/);
413
434
  const pid = parts[parts.length - 1];
@@ -417,29 +438,37 @@ export class AgentEndpoint {
417
438
  }
418
439
  } else {
419
440
  // Linux/macOS: lsof -i
420
- try {
421
- output = execSync(`lsof -ti :${port}`, {
422
- encoding: "utf8",
423
- timeout: 5000,
424
- }).trim();
425
- const pidList = output.split("\n").filter((pid) => pid.trim());
426
- for (const pid of pidList) {
427
- if (pid && /^\d+$/.test(pid) && !protectedPids.has(pid)) {
428
- pids.add(pid);
429
- }
430
- }
431
- if (pidList.length > 0 && pids.size === 0) {
432
- console.log(
433
- `${TAG} Port ${port} held by own process tree (PIDs: ${pidList.join(", ")}) — skipping kill`,
434
- );
435
- return;
436
- }
437
- } catch (lsofErr) {
438
- // lsof returns exit code 1 when no processes found (port is free)
439
- if (lsofErr.status === 1) {
440
- return; // Port is already free
441
+ const lsofRes = spawnSync("lsof", ["-ti", `:${portNumber}`], {
442
+ encoding: "utf8",
443
+ timeout: 5000,
444
+ stdio: ["ignore", "pipe", "pipe"],
445
+ });
446
+ if (lsofRes.error) throw lsofRes.error;
447
+ // lsof returns exit code 1 when no processes found (port is free)
448
+ if (lsofRes.status === 1) {
449
+ return;
450
+ }
451
+ if (lsofRes.status !== 0) {
452
+ throw new Error(
453
+ String(
454
+ lsofRes.stderr ||
455
+ lsofRes.stdout ||
456
+ `lsof exited with status ${lsofRes.status}`,
457
+ ).trim(),
458
+ );
459
+ }
460
+ output = String(lsofRes.stdout || "").trim();
461
+ const pidList = output.split("\n").filter((pid) => pid.trim());
462
+ for (const pid of pidList) {
463
+ if (pid && /^\d+$/.test(pid) && !protectedPids.has(pid)) {
464
+ pids.add(pid);
441
465
  }
442
- throw lsofErr;
466
+ }
467
+ if (pidList.length > 0 && pids.size === 0) {
468
+ console.log(
469
+ `${TAG} Port ${portNumber} held by own process tree (PIDs: ${pidList.join(", ")}) — skipping kill`,
470
+ );
471
+ return;
443
472
  }
444
473
  }
445
474
 
@@ -490,10 +519,7 @@ export class AgentEndpoint {
490
519
  }
491
520
  } else {
492
521
  // Graceful SIGTERM first — only escalate to SIGKILL if still alive
493
- execSync(`kill ${pid}`, {
494
- encoding: "utf8",
495
- timeout: 5000,
496
- });
522
+ process.kill(Number(pid), "SIGTERM");
497
523
  }
498
524
  } catch (killErr) {
499
525
  /* may already be dead — log for diagnostics */
@@ -528,7 +554,7 @@ export class AgentEndpoint {
528
554
  try {
529
555
  process.kill(Number(pid), 0); // probe — throws if dead
530
556
  console.warn(`${TAG} PID ${pid} still alive after SIGTERM — sending SIGKILL`);
531
- execSync(`kill -9 ${pid}`, { encoding: "utf8", timeout: 5000 });
557
+ process.kill(Number(pid), "SIGKILL");
532
558
  } catch {
533
559
  /* already dead — good */
534
560
  }
@@ -682,7 +682,15 @@ async function withTemporaryEnv(overrides, fn) {
682
682
  function buildCodexSdkOptions(envInput = process.env) {
683
683
  const { env: resolvedEnv } = resolveCodexProfileRuntime(envInput);
684
684
  const baseUrl = resolvedEnv.OPENAI_BASE_URL || "";
685
- const isAzure = baseUrl.includes(".openai.azure.com");
685
+ const isAzure = (() => {
686
+ try {
687
+ const parsed = new URL(baseUrl);
688
+ const host = String(parsed.hostname || "").toLowerCase();
689
+ return host === "openai.azure.com" || host.endsWith(".openai.azure.com");
690
+ } catch {
691
+ return false;
692
+ }
693
+ })();
686
694
  const env = { ...resolvedEnv };
687
695
  // Always strip OPENAI_BASE_URL — for Azure we use config overrides,
688
696
  // for non-Azure the CLI should use its built-in endpoint.
package/agent/autofix.mjs CHANGED
@@ -536,7 +536,15 @@ export function runCodexExec(
536
536
  // Otherwise strip OPENAI_BASE_URL so the CLI uses its ChatGPT OAuth.
537
537
  const { env: codexEnv } = resolveCodexProfileRuntime(process.env);
538
538
  const baseUrl = codexEnv.OPENAI_BASE_URL || "";
539
- const isAzure = baseUrl.includes(".openai.azure.com");
539
+ const isAzure = (() => {
540
+ try {
541
+ const parsed = new URL(baseUrl);
542
+ const host = String(parsed.hostname || "").toLowerCase();
543
+ return host === "openai.azure.com" || host.endsWith(".openai.azure.com");
544
+ } catch {
545
+ return false;
546
+ }
547
+ })();
540
548
  if (isAzure) {
541
549
  // Map OPENAI_API_KEY → AZURE_OPENAI_API_KEY for Azure auth header
542
550
  if (codexEnv.OPENAI_API_KEY && !codexEnv.AZURE_OPENAI_API_KEY) {
@@ -568,22 +576,11 @@ export function runCodexExec(
568
576
  // Node double-killing the child with SIGTERM before our handler runs.
569
577
  env: codexEnv,
570
578
  };
571
- if (process.platform === "win32") {
572
- // On Windows, spawn with shell: true so cmd.exe can resolve .cmd/.ps1
573
- // shims (e.g. codex.cmd installed by npm). Without shell: true, Node's
574
- // spawn() looks for a literal "codex" executable which doesn't exist
575
- // on Windows — only codex.cmd does — causing ENOENT (os error 2).
576
- // Arguments are passed as an array so shell word-splitting is safe.
577
- child = spawn("codex", args, {
578
- ...spawnOptions,
579
- shell: true,
580
- });
581
- } else {
582
- child = spawn("codex", args, {
583
- ...spawnOptions,
584
- shell: false,
585
- });
586
- }
579
+ const codexBin = process.platform === "win32" ? "codex.cmd" : "codex";
580
+ child = spawn(codexBin, args, {
581
+ ...spawnOptions,
582
+ shell: false,
583
+ });
587
584
  } catch (err) {
588
585
  return promiseResolve({
589
586
  success: false,
@@ -23,6 +23,10 @@ import { fileURLToPath } from "node:url";
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = dirname(__filename);
25
25
 
26
+ function readBuiltinSkillFile(filename) {
27
+ return readFileSync(resolve(__dirname, "skills", filename), "utf8");
28
+ }
29
+
26
30
  // ── Analytics stream path (same file task-executor writes to) ────────────────
27
31
  const _SKILL_STREAM_PATH = resolve(
28
32
  __dirname,
@@ -765,7 +769,7 @@ await engine.process(data);
765
769
  `,
766
770
  },
767
771
  {
768
- filename: "codebase-annotation-audit.md",
772
+ filename: "skill-codebase-audit.md",
769
773
  title: "Codebase Annotation Audit",
770
774
  tags: [
771
775
  "audit", "annotation", "documentation", "summary", "inventory",
@@ -773,117 +777,7 @@ await engine.process(data);
773
777
  "manifest", "conformity", "regeneration", "claude", "copilot",
774
778
  ],
775
779
  scope: "global",
776
- content: `# Skill: Codebase Annotation Audit
777
-
778
- ## Purpose
779
- Systematically audit and annotate a codebase so that *future* AI agents can
780
- navigate it 4× faster, use 20% fewer tokens, and avoid false-positive changes.
781
- This skill is **documentation-only** — it MUST NOT fix bugs, refactor code,
782
- or change program behavior.
783
-
784
- ## Philosophy — LEAN Annotations
785
-
786
- Modern AI coding SDKs (Copilot, Codex, Claude Code) already auto-compact
787
- context. Adding a memory/compaction layer on top is wasteful. What *does* help
788
- is **repo-level documentation** that agents read at the start of a session:
789
- summaries, warnings, architectural notes, and module manifests. These cost zero
790
- runtime tokens and dramatically reduce exploration time.
791
-
792
- ## Annotation Format
793
-
794
- Use structured comment headers that agents are trained to recognize:
795
-
796
- \\\`\\\`\\\`
797
- // BOSUN:SUMMARY — <module-name>
798
- // <1–3 sentence summary of purpose, key types, and public API>
799
- \\\`\\\`\\\`
800
-
801
- \\\`\\\`\\\`
802
- // BOSUN:WARN — <module-name>
803
- // <non-obvious pitfall, race condition, or constraint agents MUST know>
804
- \\\`\\\`\\\`
805
-
806
- - Place annotations at the **top of the file**, after imports / shebang.
807
- - Keep each annotation to ≤ 3 lines.
808
- - Do NOT annotate trivial files (configs, lockfiles, generated code).
809
-
810
- ## 6-Phase Audit Process
811
-
812
- ### Phase 1 — Inventory
813
- Enumerate every source file. For each file record:
814
- | Field | Value |
815
- |-------|-------|
816
- | path | relative from repo root |
817
- | lang | file extension / language |
818
- | lines | line count |
819
- | has_summary | yes / no |
820
- | has_warn | yes / no |
821
- | category | core / util / test / config / generated |
822
-
823
- Output: \\\`.bosun/audit/inventory.json\\\`
824
-
825
- ### Phase 2 — Summaries
826
- For every file where \\\`has_summary === false\\\` and \\\`category !== "generated"\\\`:
827
- 1. Read the file.
828
- 2. Write a \\\`BOSUN:SUMMARY\\\` comment at the top.
829
- 3. Stage the file.
830
-
831
- ### Phase 3 — Warnings
832
- For every file, check for non-obvious constraints:
833
- - Singleton/caching requirements (must be module-scope)
834
- - Async fire-and-forget patterns (unhandled rejections)
835
- - Order-dependent initialization
836
- - Platform-specific behavior (Windows paths, etc.)
837
-
838
- Add \\\`BOSUN:WARN\\\` comments where found.
839
-
840
- ### Phase 4 — Manifest Audit
841
- Ensure \\\`AGENTS.md\\\` (or equivalent) at repo root is accurate:
842
- - Lists all top-level modules with 1-line descriptions.
843
- - Documents build / test / lint commands.
844
- - Documents environment variables.
845
- - Documents commit conventions.
846
- - Lists known constraints or gotchas.
847
-
848
- If the file is outdated or missing sections, append corrections.
849
-
850
- ### Phase 5 — Conformity Check
851
- Re-scan all annotations and validate:
852
- - \\\`BOSUN:SUMMARY\\\` is present in every non-trivial source file.
853
- - \\\`BOSUN:WARN\\\` exists for files with known pitfalls.
854
- - No stale annotations reference symbols/functions that no longer exist.
855
-
856
- Output: \\\`.bosun/audit/conformity-report.json\\\`
857
-
858
- ### Phase 6 — Regeneration Schedule
859
- Annotations rot. Add a \\\`.bosun/audit/schedule.json\\\` with:
860
- \\\`\\\`\\\`json
861
- {
862
- "lastFullAudit": "<ISO timestamp>",
863
- "nextRecommendedAudit": "<ISO timestamp + 30 days>",
864
- "filesAudited": <count>,
865
- "summariesAdded": <count>,
866
- "warningsAdded": <count>,
867
- "conformityScore": <0-100>
868
- }
869
- \\\`\\\`\\\`
870
-
871
- ## Hard Rules
872
-
873
- 1. **Do NOT change program behavior.** Only add/update comments and documentation.
874
- 2. **Do NOT refactor, fix bugs, or rename symbols.** Documentation only.
875
- 3. **Do NOT annotate generated files** (lockfiles, build output, .min.js, etc.).
876
- 4. **Keep summaries ≤ 3 lines.** Agents need density, not essays.
877
- 5. **Keep warnings actionable.** "This is complex" is useless.
878
- "Must call init() before query() — throws otherwise" is helpful.
879
- 6. **Stage files individually** — never \\\`git add .\\\`.
880
- 7. **Commit with** \\\`docs(audit): annotate <module>\\\` — not feat/fix.
881
-
882
- ## Success Metrics
883
- - A/B tested: annotated repos show 4× faster agent navigation.
884
- - 20% fewer tokens consumed per task.
885
- - Zero false-positive code changes from confused agents.
886
- `,
780
+ content: readBuiltinSkillFile("skill-codebase-audit.md"),
887
781
  },
888
782
  {
889
783
  filename: "custom-tool-creation.md",
@@ -1298,3 +1192,4 @@ export function buildRelevantSkillsPromptBlock(bosunHome, taskTitle, taskDescrip
1298
1192
 
1299
1193
  return lines.join("\n").trim();
1300
1194
  }
1195
+
@@ -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
-
@@ -0,0 +1,111 @@
1
+ # Skill: Codebase Annotation Audit
2
+
3
+ ## Purpose
4
+ Systematically audit and annotate a codebase so that *future* AI agents can
5
+ navigate it 4× faster, use 20% fewer tokens, and avoid false-positive changes.
6
+ This skill is **documentation-only** — it MUST NOT fix bugs, refactor code,
7
+ or change program behavior.
8
+
9
+ ## Philosophy — LEAN Annotations
10
+
11
+ Modern AI coding SDKs (Copilot, Codex, Claude Code) already auto-compact
12
+ context. Adding a memory/compaction layer on top is wasteful. What *does* help
13
+ is **repo-level documentation** that agents read at the start of a session:
14
+ summaries, warnings, architectural notes, and module manifests. These cost zero
15
+ runtime tokens and dramatically reduce exploration time.
16
+
17
+ ## Annotation Format
18
+
19
+ Use structured comment headers that agents are trained to recognize:
20
+
21
+ ```
22
+ // CLAUDE:SUMMARY — <module-name>
23
+ // <1–3 sentence summary of purpose, key types, and public API>
24
+ ```
25
+
26
+ ```
27
+ // CLAUDE:WARN — <module-name>
28
+ // <non-obvious pitfall, race condition, or constraint agents MUST know>
29
+ ```
30
+
31
+ - Place annotations at the **top of the file**, after imports / shebang.
32
+ - Keep each annotation to ≤ 3 lines.
33
+ - Do NOT annotate trivial files (configs, lockfiles, generated code).
34
+
35
+ ## 6-Phase Audit
36
+
37
+ ### Phase 1 — Inventory
38
+ Enumerate every source file. For each file record:
39
+ | Field | Value |
40
+ |-------|-------|
41
+ | path | relative from repo root |
42
+ | lang | file extension / language |
43
+ | lines | line count |
44
+ | has_summary | yes / no |
45
+ | has_warn | yes / no |
46
+ | category | core / util / test / config / generated |
47
+
48
+ Output: `.bosun/audit/inventory.json`
49
+
50
+ ### Phase 2 — Summaries
51
+ For every file where `has_summary === false` and `category !== "generated"`:
52
+ 1. Read the file.
53
+ 2. Write a `CLAUDE:SUMMARY` comment at the top.
54
+ 3. Stage the file.
55
+
56
+ ### Phase 3 — Warnings
57
+ For every file, check for non-obvious constraints:
58
+ - Singleton/caching requirements (must be module-scope)
59
+ - Async fire-and-forget patterns (unhandled rejections)
60
+ - Order-dependent initialization
61
+ - Platform-specific behavior (Windows paths, etc.)
62
+
63
+ Add `CLAUDE:WARN` comments where found.
64
+
65
+ ### Phase 4 — Manifest Audit
66
+ Ensure `AGENTS.md` (or equivalent) at repo root is accurate:
67
+ - Lists all top-level modules with 1-line descriptions.
68
+ - Documents build / test / lint commands.
69
+ - Documents environment variables.
70
+ - Documents commit conventions.
71
+ - Lists known constraints or gotchas.
72
+
73
+ If the file is outdated or missing sections, append corrections.
74
+
75
+ ### Phase 5 — Conformity Check
76
+ Re-scan all annotations and validate:
77
+ - `CLAUDE:SUMMARY` is present in every non-trivial source file.
78
+ - `CLAUDE:WARN` exists for files with known pitfalls.
79
+ - No stale annotations reference symbols/functions that no longer exist.
80
+
81
+ Output: `.bosun/audit/conformity-report.json`
82
+
83
+ ### Phase 6 — Regeneration Schedule
84
+ Annotations rot. Add a `.bosun/audit/schedule.json` with:
85
+ ```json
86
+ {
87
+ "lastFullAudit": "<ISO timestamp>",
88
+ "nextRecommendedAudit": "<ISO timestamp + 30 days>",
89
+ "filesAudited": <count>,
90
+ "summariesAdded": <count>,
91
+ "warningsAdded": <count>,
92
+ "conformityScore": <0-100>
93
+ }
94
+ ```
95
+
96
+ ## Hard Rules
97
+
98
+ 1. **Do NOT change program behavior.** Only add/update comments and documentation.
99
+ 2. **Do NOT refactor, fix bugs, or rename symbols.** Documentation only.
100
+ 3. **Do NOT annotate generated files** (lockfiles, build output, `.min.js`, etc.).
101
+ 4. **Keep summaries ≤ 3 lines.** Agents need density, not essays.
102
+ 5. **Keep warnings actionable.** "This is complex" is useless.
103
+ "Must call init() before query() — throws otherwise" is helpful.
104
+ 6. **Stage files individually** — never `git add .`.
105
+ 7. **Commit with** `docs(audit): annotate <module>` — not `feat`/`fix`.
106
+
107
+ ## Success Metrics
108
+ - A/B tested: annotated repos show 4× faster agent navigation.
109
+ - 20% fewer tokens consumed per task.
110
+ - Zero false-positive code changes from confused agents.
111
+
package/bosun-tui.mjs ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * bosun-tui — Terminal User Interface for Bosun
5
+ *
6
+ * A terminal-based UI for monitoring Bosun agents, tasks, and workflows.
7
+ * Built with Ink (React-like CLI framework).
8
+ *
9
+ * Usage:
10
+ * bosun-tui # Start the TUI
11
+ * bosun-tui --help # Show help
12
+ * bosun-tui --port 3080 # Connect to specific port
13
+ */
14
+
15
+ import { resolve, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ import { readFileSync, existsSync } from "node:fs";
18
+
19
+ const __dirname = dirname(fileURLToPath(new URL(".", import.meta.url)));
20
+
21
+ function showHelp() {
22
+ const version = JSON.parse(
23
+ readFileSync(resolve(__dirname, "package.json"), "utf8"),
24
+ ).version;
25
+
26
+ console.log(`
27
+ bosun-tui v${version}
28
+ Terminal User Interface for Bosun
29
+
30
+ USAGE
31
+ bosun-tui [options]
32
+
33
+ OPTIONS
34
+ --port <n> UI server port to connect (default: 3080 or TELEGRAM_UI_PORT env)
35
+ --host <host> UI server host (default: localhost)
36
+ --connect Connect to existing UI server (don't start monitor)
37
+ --screen <name> Initial screen (tasks|agents|status)
38
+ --refresh <ms> Stats refresh interval (default: 2000ms)
39
+ --help Show this help
40
+ --version Show version
41
+
42
+ SCREENS
43
+ tasks Kanban board with task CRUD
44
+ agents Live agent session table
45
+ status System status overview
46
+
47
+ KEYBOARD NAVIGATION
48
+ Tab / Shift+Tab Navigate between panels
49
+ ↑↓←→ Navigate within panels
50
+ Enter Select / Execute action
51
+ Esc Back / Close modal
52
+ c Create new task (tasks screen)
53
+ r Refresh data
54
+ q Quit
55
+
56
+ EXAMPLES
57
+ bosun-tui --port 3080
58
+ bosun-tui --screen tasks
59
+ bosun-tui --connect --port 3080
60
+ `);
61
+ }
62
+
63
+ function getArgValue(flag, defaultValue = "") {
64
+ const args = process.argv.slice(2);
65
+ const match = args.find((arg) => arg.startsWith(`${flag}=`));
66
+ if (match) {
67
+ return match.slice(flag.length + 1).trim();
68
+ }
69
+ const idx = args.indexOf(flag);
70
+ if (idx >= 0 && args[idx + 1] && !args[idx + 1].startsWith("--")) {
71
+ return args[idx + 1].trim();
72
+ }
73
+ return defaultValue;
74
+ }
75
+
76
+ function getArgFlag(flag) {
77
+ const args = process.argv.slice(2);
78
+ return args.includes(flag);
79
+ }
80
+
81
+ async function main() {
82
+ const args = process.argv.slice(2);
83
+
84
+ if (getArgFlag("--help") || args.includes("-h")) {
85
+ showHelp();
86
+ process.exit(0);
87
+ }
88
+
89
+ if (getArgFlag("--version") || args.includes("-v")) {
90
+ const version = JSON.parse(
91
+ readFileSync(resolve(__dirname, "package.json"), "utf8"),
92
+ ).version;
93
+ console.log(`bosun-tui v${version}`);
94
+ process.exit(0);
95
+ }
96
+
97
+ const port = Number(getArgValue("--port", process.env.TELEGRAM_UI_PORT || "3080")) || 3080;
98
+ const host = getArgValue("--host", "localhost");
99
+ const connectOnly = getArgFlag("--connect");
100
+ const initialScreen = getArgValue("--screen", "status");
101
+ const refreshMs = Number(getArgValue("--refresh", "2000")) || 2000;
102
+
103
+ console.log(`[bosun-tui] Starting...`);
104
+ console.log(`[bosun-tui] Connecting to ${host}:${port}`);
105
+
106
+ try {
107
+ const { render } = await import("ink");
108
+ const importErrors = [];
109
+
110
+ let App;
111
+ try {
112
+ const appModule = await import("./tui/app.mjs");
113
+ App = appModule.default;
114
+ } catch (importErr) {
115
+ importErrors.push(`App: ${importErr.message}`);
116
+ console.error(`[bosun-tui] Failed to import TUI app: ${importErr.message}`);
117
+ console.log(`[bosun-tui] TUI requires ink. Install with: npm install ink`);
118
+ process.exit(1);
119
+ }
120
+
121
+ const { waitUntilExit } = await import("ink");
122
+
123
+ const app = render(
124
+ App({
125
+ host,
126
+ port,
127
+ connectOnly,
128
+ initialScreen,
129
+ refreshMs,
130
+ }),
131
+ );
132
+
133
+ process.exitCode = await waitUntilExit(app);
134
+ } catch (err) {
135
+ console.error(`[bosun-tui] Failed to start: ${err.message}`);
136
+ console.log(`[bosun-tui] Ensure bosun is running or use --connect to connect to an existing UI server`);
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ main();