@happier-dev/stack 0.1.0-preview.74.1

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 (439) hide show
  1. package/README.md +501 -0
  2. package/bin/hstack.mjs +348 -0
  3. package/docs/codex-mcp-resume.md +129 -0
  4. package/docs/edison.md +74 -0
  5. package/docs/forking-and-branding.md +189 -0
  6. package/docs/happy-development.md +22 -0
  7. package/docs/isolated-linux-vm.md +243 -0
  8. package/docs/menubar.md +244 -0
  9. package/docs/mobile-ios.md +322 -0
  10. package/docs/monorepo-migration.md +20 -0
  11. package/docs/paths-and-env.md +154 -0
  12. package/docs/remote-access.md +43 -0
  13. package/docs/server-flavors.md +147 -0
  14. package/docs/stacks.md +330 -0
  15. package/docs/tauri.md +60 -0
  16. package/docs/worktrees-and-forks.md +133 -0
  17. package/extras/swiftbar/auth-login.sh +29 -0
  18. package/extras/swiftbar/git-cache-refresh.sh +122 -0
  19. package/extras/swiftbar/hstack-term.sh +133 -0
  20. package/extras/swiftbar/hstack.5s.sh +296 -0
  21. package/extras/swiftbar/hstack.sh +35 -0
  22. package/extras/swiftbar/icons/happy-green.png +0 -0
  23. package/extras/swiftbar/icons/happy-orange.png +0 -0
  24. package/extras/swiftbar/icons/happy-red.png +0 -0
  25. package/extras/swiftbar/icons/logo-white.png +0 -0
  26. package/extras/swiftbar/install.sh +265 -0
  27. package/extras/swiftbar/lib/git.sh +629 -0
  28. package/extras/swiftbar/lib/icons.sh +92 -0
  29. package/extras/swiftbar/lib/render.sh +999 -0
  30. package/extras/swiftbar/lib/system.sh +244 -0
  31. package/extras/swiftbar/lib/utils.sh +717 -0
  32. package/extras/swiftbar/set-interval.sh +65 -0
  33. package/extras/swiftbar/set-server-flavor.sh +61 -0
  34. package/extras/swiftbar/wt-pr.sh +140 -0
  35. package/node_modules/@happier-dev/cli-common/README.md +6 -0
  36. package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
  37. package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
  38. package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
  39. package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
  40. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
  41. package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
  42. package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
  43. package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
  44. package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
  45. package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
  46. package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
  47. package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
  48. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
  49. package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
  50. package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
  51. package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
  52. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
  53. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
  54. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
  55. package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
  56. package/node_modules/@happier-dev/cli-common/package.json +26 -0
  57. package/package.json +77 -0
  58. package/scripts/auth.mjs +1829 -0
  59. package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
  60. package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
  61. package/scripts/auth_help_cmd.test.mjs +28 -0
  62. package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
  63. package/scripts/auth_login_force_default.test.mjs +66 -0
  64. package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
  65. package/scripts/auth_login_method_override.test.mjs +67 -0
  66. package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
  67. package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
  68. package/scripts/build.mjs +266 -0
  69. package/scripts/bundleWorkspaceDeps.mjs +38 -0
  70. package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
  71. package/scripts/ci.mjs +135 -0
  72. package/scripts/ci.test.mjs +50 -0
  73. package/scripts/cli-link.mjs +57 -0
  74. package/scripts/completion.mjs +395 -0
  75. package/scripts/contrib.mjs +333 -0
  76. package/scripts/daemon.mjs +1160 -0
  77. package/scripts/daemon.status_scope.test.mjs +51 -0
  78. package/scripts/daemon_cmd.mjs +26 -0
  79. package/scripts/daemon_dist_guard.test.mjs +171 -0
  80. package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
  81. package/scripts/daemon_server_scoped_state.test.mjs +49 -0
  82. package/scripts/daemon_start_verification.integration.test.mjs +296 -0
  83. package/scripts/dev.mjs +545 -0
  84. package/scripts/doctor.mjs +340 -0
  85. package/scripts/doctor_cmd.test.mjs +22 -0
  86. package/scripts/doctor_ui_index_missing.test.mjs +37 -0
  87. package/scripts/eas.mjs +367 -0
  88. package/scripts/eas_platform_parsing.test.mjs +63 -0
  89. package/scripts/edison.mjs +1848 -0
  90. package/scripts/env.mjs +149 -0
  91. package/scripts/env_cmd.test.mjs +118 -0
  92. package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
  93. package/scripts/happier.mjs +82 -0
  94. package/scripts/import.mjs +1327 -0
  95. package/scripts/init.mjs +464 -0
  96. package/scripts/install.mjs +550 -0
  97. package/scripts/lint.mjs +177 -0
  98. package/scripts/menubar.mjs +202 -0
  99. package/scripts/migrate.mjs +318 -0
  100. package/scripts/mobile.mjs +353 -0
  101. package/scripts/mobile_dev_client.mjs +87 -0
  102. package/scripts/monorepo.mjs +2234 -0
  103. package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
  104. package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
  105. package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
  106. package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
  107. package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
  108. package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
  109. package/scripts/pack.mjs +257 -0
  110. package/scripts/pack.test.mjs +68 -0
  111. package/scripts/pglite_lock.integration.test.mjs +152 -0
  112. package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
  113. package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
  114. package/scripts/provision/macos-lima-happy-vm.sh +192 -0
  115. package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
  116. package/scripts/release.mjs +53 -0
  117. package/scripts/release_binary_smoke.integration.test.mjs +138 -0
  118. package/scripts/review.mjs +1752 -0
  119. package/scripts/review_pr.mjs +435 -0
  120. package/scripts/run.mjs +561 -0
  121. package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
  122. package/scripts/self.mjs +465 -0
  123. package/scripts/self_host.mjs +9 -0
  124. package/scripts/self_host_binary_smoke.integration.test.mjs +74 -0
  125. package/scripts/self_host_runtime.mjs +883 -0
  126. package/scripts/self_host_runtime.test.mjs +82 -0
  127. package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
  128. package/scripts/server_flavor.mjs +148 -0
  129. package/scripts/service.mjs +868 -0
  130. package/scripts/service_mode_help.test.mjs +27 -0
  131. package/scripts/setup.mjs +1324 -0
  132. package/scripts/setup_non_interactive_flag.test.mjs +60 -0
  133. package/scripts/setup_pr.mjs +605 -0
  134. package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
  135. package/scripts/stack/command_arguments.mjs +91 -0
  136. package/scripts/stack/copy_auth_from_stack.mjs +111 -0
  137. package/scripts/stack/delegated_script_commands.mjs +92 -0
  138. package/scripts/stack/help_text.mjs +110 -0
  139. package/scripts/stack/port_reservation.mjs +74 -0
  140. package/scripts/stack/repo_checkout_resolution.mjs +31 -0
  141. package/scripts/stack/run_script_with_stack_env.mjs +634 -0
  142. package/scripts/stack/stack_daemon_command.mjs +219 -0
  143. package/scripts/stack/stack_delegated_help.mjs +81 -0
  144. package/scripts/stack/stack_environment.mjs +151 -0
  145. package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
  146. package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
  147. package/scripts/stack/stack_info_snapshot.mjs +167 -0
  148. package/scripts/stack/stack_mobile_install_command.mjs +61 -0
  149. package/scripts/stack/stack_resume_command.mjs +76 -0
  150. package/scripts/stack/stack_stop_command.mjs +34 -0
  151. package/scripts/stack/stack_workspace_command.mjs +83 -0
  152. package/scripts/stack/transient_repo_overrides.mjs +29 -0
  153. package/scripts/stack.mjs +2388 -0
  154. package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
  155. package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
  156. package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
  157. package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
  158. package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
  159. package/scripts/stack_eas_help.test.mjs +72 -0
  160. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
  161. package/scripts/stack_env_cmd.test.mjs +107 -0
  162. package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
  163. package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
  164. package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
  165. package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
  166. package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
  167. package/scripts/stack_monorepo_defaults.test.mjs +31 -0
  168. package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
  169. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
  170. package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
  171. package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
  172. package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
  173. package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
  174. package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
  175. package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
  176. package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
  177. package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
  178. package/scripts/stack_wt_list.test.mjs +117 -0
  179. package/scripts/start_ui_required_default.test.mjs +63 -0
  180. package/scripts/stop.mjs +190 -0
  181. package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
  182. package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
  183. package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
  184. package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
  185. package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
  186. package/scripts/systemd_unit_info.test.mjs +24 -0
  187. package/scripts/tailscale.mjs +490 -0
  188. package/scripts/test_ci.mjs +36 -0
  189. package/scripts/test_cmd.mjs +274 -0
  190. package/scripts/test_cmd.test.mjs +133 -0
  191. package/scripts/test_integration.mjs +33 -0
  192. package/scripts/testkit/auth_testkit.mjs +121 -0
  193. package/scripts/testkit/doctor_testkit.mjs +68 -0
  194. package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
  195. package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
  196. package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
  197. package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
  198. package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
  199. package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
  200. package/scripts/tools.mjs +70 -0
  201. package/scripts/tui.mjs +914 -0
  202. package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
  203. package/scripts/typecheck.mjs +178 -0
  204. package/scripts/ui_gateway.mjs +247 -0
  205. package/scripts/uninstall.mjs +179 -0
  206. package/scripts/utils/auth/credentials_paths.mjs +181 -0
  207. package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
  208. package/scripts/utils/auth/daemon_gate.mjs +66 -0
  209. package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
  210. package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
  211. package/scripts/utils/auth/dev_key.mjs +163 -0
  212. package/scripts/utils/auth/files.mjs +56 -0
  213. package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
  214. package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
  215. package/scripts/utils/auth/handy_master_secret.mjs +42 -0
  216. package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
  217. package/scripts/utils/auth/login_ux.mjs +105 -0
  218. package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
  219. package/scripts/utils/auth/sources.mjs +28 -0
  220. package/scripts/utils/auth/stable_scope_id.mjs +91 -0
  221. package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
  222. package/scripts/utils/auth/stack_guided_login.mjs +438 -0
  223. package/scripts/utils/cli/arg_values.mjs +23 -0
  224. package/scripts/utils/cli/arg_values.test.mjs +43 -0
  225. package/scripts/utils/cli/args.mjs +17 -0
  226. package/scripts/utils/cli/cli.mjs +24 -0
  227. package/scripts/utils/cli/cli_registry.mjs +440 -0
  228. package/scripts/utils/cli/cwd_scope.mjs +158 -0
  229. package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
  230. package/scripts/utils/cli/flags.mjs +17 -0
  231. package/scripts/utils/cli/log_forwarder.mjs +157 -0
  232. package/scripts/utils/cli/normalize.mjs +16 -0
  233. package/scripts/utils/cli/prereqs.mjs +103 -0
  234. package/scripts/utils/cli/prereqs.test.mjs +33 -0
  235. package/scripts/utils/cli/progress.mjs +141 -0
  236. package/scripts/utils/cli/smoke_help.mjs +44 -0
  237. package/scripts/utils/cli/verbosity.mjs +11 -0
  238. package/scripts/utils/cli/wizard.mjs +139 -0
  239. package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
  240. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
  241. package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
  242. package/scripts/utils/crypto/tokens.mjs +14 -0
  243. package/scripts/utils/dev/daemon.mjs +232 -0
  244. package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
  245. package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
  246. package/scripts/utils/dev/expo_dev.mjs +478 -0
  247. package/scripts/utils/dev/expo_dev.test.mjs +89 -0
  248. package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
  249. package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
  250. package/scripts/utils/dev/server.mjs +180 -0
  251. package/scripts/utils/dev_auth_key.mjs +7 -0
  252. package/scripts/utils/edison/git_roots.mjs +30 -0
  253. package/scripts/utils/edison/git_roots.test.mjs +49 -0
  254. package/scripts/utils/env/config.mjs +52 -0
  255. package/scripts/utils/env/dotenv.mjs +32 -0
  256. package/scripts/utils/env/dotenv.test.mjs +32 -0
  257. package/scripts/utils/env/env.mjs +130 -0
  258. package/scripts/utils/env/env_file.mjs +98 -0
  259. package/scripts/utils/env/env_file.test.mjs +49 -0
  260. package/scripts/utils/env/env_local.mjs +25 -0
  261. package/scripts/utils/env/load_env_file.mjs +34 -0
  262. package/scripts/utils/env/read.mjs +30 -0
  263. package/scripts/utils/env/sandbox.mjs +13 -0
  264. package/scripts/utils/env/scrub_env.mjs +69 -0
  265. package/scripts/utils/env/scrub_env.test.mjs +102 -0
  266. package/scripts/utils/env/values.mjs +13 -0
  267. package/scripts/utils/expo/command.mjs +65 -0
  268. package/scripts/utils/expo/expo.mjs +139 -0
  269. package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
  270. package/scripts/utils/expo/metro_ports.mjs +101 -0
  271. package/scripts/utils/expo/metro_ports.test.mjs +35 -0
  272. package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
  273. package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
  274. package/scripts/utils/fs/file_has_content.mjs +10 -0
  275. package/scripts/utils/fs/fs.mjs +11 -0
  276. package/scripts/utils/fs/json.mjs +25 -0
  277. package/scripts/utils/fs/ops.mjs +29 -0
  278. package/scripts/utils/fs/package_json.mjs +8 -0
  279. package/scripts/utils/fs/tail.mjs +12 -0
  280. package/scripts/utils/git/dev_checkout.mjs +127 -0
  281. package/scripts/utils/git/dev_checkout.test.mjs +115 -0
  282. package/scripts/utils/git/git.mjs +67 -0
  283. package/scripts/utils/git/parse_name_status_z.mjs +21 -0
  284. package/scripts/utils/git/refs.mjs +26 -0
  285. package/scripts/utils/git/worktrees.mjs +323 -0
  286. package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
  287. package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
  288. package/scripts/utils/llm/assist.mjs +260 -0
  289. package/scripts/utils/llm/codex_exec.mjs +61 -0
  290. package/scripts/utils/llm/codex_exec.test.mjs +46 -0
  291. package/scripts/utils/llm/hstack_runner.mjs +59 -0
  292. package/scripts/utils/llm/tools.mjs +56 -0
  293. package/scripts/utils/llm/tools.test.mjs +67 -0
  294. package/scripts/utils/menubar/swiftbar.mjs +121 -0
  295. package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
  296. package/scripts/utils/mobile/config.mjs +35 -0
  297. package/scripts/utils/mobile/dev_client_links.mjs +59 -0
  298. package/scripts/utils/mobile/identifiers.mjs +46 -0
  299. package/scripts/utils/mobile/identifiers.test.mjs +41 -0
  300. package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
  301. package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
  302. package/scripts/utils/net/bind_mode.mjs +39 -0
  303. package/scripts/utils/net/dns.mjs +10 -0
  304. package/scripts/utils/net/lan_ip.mjs +24 -0
  305. package/scripts/utils/net/ports.mjs +110 -0
  306. package/scripts/utils/net/tcp_forward.mjs +162 -0
  307. package/scripts/utils/net/url.mjs +30 -0
  308. package/scripts/utils/net/url.test.mjs +29 -0
  309. package/scripts/utils/paths/canonical_home.mjs +15 -0
  310. package/scripts/utils/paths/canonical_home.test.mjs +28 -0
  311. package/scripts/utils/paths/localhost_host.mjs +112 -0
  312. package/scripts/utils/paths/localhost_host.test.mjs +58 -0
  313. package/scripts/utils/paths/paths.mjs +302 -0
  314. package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
  315. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  316. package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
  317. package/scripts/utils/paths/runtime.mjs +41 -0
  318. package/scripts/utils/pglite_lock.mjs +107 -0
  319. package/scripts/utils/proc/commands.mjs +33 -0
  320. package/scripts/utils/proc/exit_cleanup.mjs +57 -0
  321. package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
  322. package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
  323. package/scripts/utils/proc/ownership.mjs +217 -0
  324. package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
  325. package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
  326. package/scripts/utils/proc/package_scripts.mjs +38 -0
  327. package/scripts/utils/proc/package_scripts.test.mjs +58 -0
  328. package/scripts/utils/proc/parallel.mjs +25 -0
  329. package/scripts/utils/proc/pids.mjs +11 -0
  330. package/scripts/utils/proc/pm.mjs +478 -0
  331. package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
  332. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
  333. package/scripts/utils/proc/proc.mjs +331 -0
  334. package/scripts/utils/proc/proc.test.mjs +85 -0
  335. package/scripts/utils/proc/terminate.mjs +69 -0
  336. package/scripts/utils/proc/terminate.test.mjs +54 -0
  337. package/scripts/utils/proc/watch.mjs +63 -0
  338. package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
  339. package/scripts/utils/review/base_ref.mjs +82 -0
  340. package/scripts/utils/review/base_ref.test.mjs +89 -0
  341. package/scripts/utils/review/chunks.mjs +55 -0
  342. package/scripts/utils/review/chunks.test.mjs +107 -0
  343. package/scripts/utils/review/detached_worktree.mjs +61 -0
  344. package/scripts/utils/review/detached_worktree.test.mjs +61 -0
  345. package/scripts/utils/review/findings.mjs +278 -0
  346. package/scripts/utils/review/findings.test.mjs +203 -0
  347. package/scripts/utils/review/head_slice.mjs +132 -0
  348. package/scripts/utils/review/head_slice.test.mjs +117 -0
  349. package/scripts/utils/review/instructions/deep.md +20 -0
  350. package/scripts/utils/review/prompts.mjs +279 -0
  351. package/scripts/utils/review/prompts.test.mjs +77 -0
  352. package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
  353. package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
  354. package/scripts/utils/review/runners/augment.mjs +91 -0
  355. package/scripts/utils/review/runners/augment.test.mjs +64 -0
  356. package/scripts/utils/review/runners/claude.mjs +92 -0
  357. package/scripts/utils/review/runners/claude.test.mjs +47 -0
  358. package/scripts/utils/review/runners/coderabbit.mjs +105 -0
  359. package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
  360. package/scripts/utils/review/runners/codex.mjs +129 -0
  361. package/scripts/utils/review/runners/codex.test.mjs +115 -0
  362. package/scripts/utils/review/slice_mode.mjs +20 -0
  363. package/scripts/utils/review/slice_mode.test.mjs +69 -0
  364. package/scripts/utils/review/sliced_runner.mjs +39 -0
  365. package/scripts/utils/review/sliced_runner.test.mjs +57 -0
  366. package/scripts/utils/review/slices.mjs +140 -0
  367. package/scripts/utils/review/slices.test.mjs +41 -0
  368. package/scripts/utils/review/targets.mjs +23 -0
  369. package/scripts/utils/review/targets.test.mjs +31 -0
  370. package/scripts/utils/review/tool_home_seed.mjs +106 -0
  371. package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
  372. package/scripts/utils/review/uncommitted_ops.mjs +77 -0
  373. package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
  374. package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
  375. package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
  376. package/scripts/utils/server/flavor_scripts.mjs +138 -0
  377. package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
  378. package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
  379. package/scripts/utils/server/mobile_api_url.mjs +60 -0
  380. package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
  381. package/scripts/utils/server/port.mjs +55 -0
  382. package/scripts/utils/server/prisma_import.mjs +36 -0
  383. package/scripts/utils/server/prisma_import.test.mjs +78 -0
  384. package/scripts/utils/server/server.mjs +109 -0
  385. package/scripts/utils/server/ui_build_check.mjs +37 -0
  386. package/scripts/utils/server/ui_build_check.test.mjs +70 -0
  387. package/scripts/utils/server/ui_env.mjs +13 -0
  388. package/scripts/utils/server/ui_env.test.mjs +57 -0
  389. package/scripts/utils/server/urls.mjs +100 -0
  390. package/scripts/utils/server/validate.mjs +60 -0
  391. package/scripts/utils/server/validate.test.mjs +76 -0
  392. package/scripts/utils/service/autostart_darwin.mjs +198 -0
  393. package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
  394. package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
  395. package/scripts/utils/stack/cli_identities.mjs +29 -0
  396. package/scripts/utils/stack/context.mjs +19 -0
  397. package/scripts/utils/stack/dirs.mjs +26 -0
  398. package/scripts/utils/stack/editor_workspace.mjs +126 -0
  399. package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
  400. package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
  401. package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
  402. package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
  403. package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
  404. package/scripts/utils/stack/names.mjs +27 -0
  405. package/scripts/utils/stack/names.test.mjs +26 -0
  406. package/scripts/utils/stack/pr_stack_name.mjs +16 -0
  407. package/scripts/utils/stack/runtime_state.mjs +88 -0
  408. package/scripts/utils/stack/stacks.mjs +40 -0
  409. package/scripts/utils/stack/startup.mjs +370 -0
  410. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
  411. package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
  412. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
  413. package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
  414. package/scripts/utils/stack/stop.mjs +284 -0
  415. package/scripts/utils/stack_context.mjs +1 -0
  416. package/scripts/utils/stack_runtime_state.mjs +1 -0
  417. package/scripts/utils/stacks.mjs +1 -0
  418. package/scripts/utils/tailscale/ip.mjs +116 -0
  419. package/scripts/utils/tauri/stack_overrides.mjs +22 -0
  420. package/scripts/utils/test/collect_test_files.mjs +29 -0
  421. package/scripts/utils/time/get_today_ymd.mjs +7 -0
  422. package/scripts/utils/tui/cleanup.mjs +38 -0
  423. package/scripts/utils/ui/ansi.mjs +47 -0
  424. package/scripts/utils/ui/browser.mjs +31 -0
  425. package/scripts/utils/ui/browser.test.mjs +56 -0
  426. package/scripts/utils/ui/clipboard.mjs +38 -0
  427. package/scripts/utils/ui/layout.mjs +44 -0
  428. package/scripts/utils/ui/qr.mjs +17 -0
  429. package/scripts/utils/ui/terminal_launcher.mjs +129 -0
  430. package/scripts/utils/ui/text.mjs +16 -0
  431. package/scripts/utils/update/auto_update_notice.mjs +93 -0
  432. package/scripts/utils/validate.mjs +5 -0
  433. package/scripts/where.mjs +138 -0
  434. package/scripts/worktrees.mjs +2174 -0
  435. package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
  436. package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
  437. package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
  438. package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
  439. package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
@@ -0,0 +1,117 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { run, runCapture } from '../proc/proc.mjs';
7
+ import { createHeadSliceCommits, getChangedOps } from './head_slice.mjs';
8
+
9
+ function gitEnv() {
10
+ const clean = {};
11
+ for (const [k, v] of Object.entries(process.env)) {
12
+ if (k.startsWith('HAPPIER_STACK_')) continue;
13
+ clean[k] = v;
14
+ }
15
+ return {
16
+ ...clean,
17
+ GIT_AUTHOR_NAME: 'Test',
18
+ GIT_AUTHOR_EMAIL: 'test@example.com',
19
+ GIT_COMMITTER_NAME: 'Test',
20
+ GIT_COMMITTER_EMAIL: 'test@example.com',
21
+ };
22
+ }
23
+
24
+ test('createHeadSliceCommits produces a focused diff while keeping full HEAD code', async (t) => {
25
+ const repo = await mkdtemp(join(tmpdir(), 'happy-review-head-slice-'));
26
+ const env = gitEnv();
27
+
28
+ const wt = join(repo, 'wt');
29
+ try {
30
+ await run('git', ['init', '-q'], { cwd: repo, env });
31
+ await run('git', ['checkout', '-q', '-b', 'main'], { cwd: repo, env });
32
+ await mkdir(join(repo, 'apps', 'ui'), { recursive: true });
33
+ await mkdir(join(repo, 'apps', 'cli'), { recursive: true });
34
+ await mkdir(join(repo, 'apps', 'server'), { recursive: true });
35
+ await writeFile(join(repo, 'apps', 'ui', 'a.txt'), 'base-a\n', 'utf-8');
36
+ await writeFile(join(repo, 'apps', 'cli', 'c.txt'), 'base-c\n', 'utf-8');
37
+ await writeFile(join(repo, 'apps', 'server', 'b.txt'), 'base-b\n', 'utf-8');
38
+ await run('git', ['add', '.'], { cwd: repo, env });
39
+ await run('git', ['commit', '-q', '-m', 'chore: base'], { cwd: repo, env });
40
+
41
+ // HEAD commit with mixed changes across areas.
42
+ await writeFile(join(repo, 'apps', 'ui', 'a.txt'), 'head-a\n', 'utf-8');
43
+ await writeFile(join(repo, 'apps', 'ui', 'new.txt'), 'new\n', 'utf-8');
44
+ await writeFile(join(repo, 'apps', 'cli', 'c.txt'), 'head-c\n', 'utf-8');
45
+ await rm(join(repo, 'apps', 'server', 'b.txt'));
46
+ await run('git', ['add', '-A'], { cwd: repo, env });
47
+ await run('git', ['commit', '-q', '-m', 'feat: head'], { cwd: repo, env });
48
+
49
+ const headCommit = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: repo, env })).trim();
50
+ const baseCommit = (await runCapture('git', ['rev-parse', 'HEAD^'], { cwd: repo, env })).trim();
51
+
52
+ // Create an ephemeral worktree to run the slice commit builder in isolation.
53
+ await run('git', ['worktree', 'add', '--detach', wt, baseCommit], { cwd: repo, env });
54
+
55
+ const ops = await getChangedOps({ cwd: repo, baseRef: baseCommit, headRef: headCommit, env });
56
+ const { baseSliceCommit, headSliceCommit } = await createHeadSliceCommits({
57
+ cwd: wt,
58
+ env,
59
+ baseRef: baseCommit,
60
+ headCommit,
61
+ ops,
62
+ slicePaths: ['apps/ui/a.txt', 'apps/ui/new.txt'],
63
+ label: 'apps/ui',
64
+ });
65
+
66
+ // Working tree should match full HEAD.
67
+ const a = await readFile(join(wt, 'apps', 'ui', 'a.txt'), 'utf-8');
68
+ const c = await readFile(join(wt, 'apps', 'cli', 'c.txt'), 'utf-8');
69
+ assert.equal(a, 'head-a\n');
70
+ assert.equal(c, 'head-c\n');
71
+ await assert.rejects(async () => await readFile(join(wt, 'apps', 'server', 'b.txt'), 'utf-8'));
72
+
73
+ // Diff between slice commits should include only apps/ui changes.
74
+ const diffNames = (
75
+ await runCapture('git', ['diff', '--name-only', `${baseSliceCommit}...${headSliceCommit}`], { cwd: wt, env })
76
+ )
77
+ .trim()
78
+ .split('\n')
79
+ .filter(Boolean)
80
+ .sort();
81
+ assert.deepEqual(diffNames, ['apps/ui/a.txt', 'apps/ui/new.txt']);
82
+ } finally {
83
+ try {
84
+ await run('git', ['worktree', 'remove', '--force', wt], { cwd: repo, env });
85
+ await run('git', ['worktree', 'prune'], { cwd: repo, env });
86
+ } catch {
87
+ // ignore cleanup errors (best-effort)
88
+ }
89
+ await rm(repo, { recursive: true, force: true });
90
+ }
91
+ });
92
+
93
+ test('getChangedOps tracks rename as remove plus checkout', async () => {
94
+ const repo = await mkdtemp(join(tmpdir(), 'happy-review-head-slice-rename-'));
95
+ const env = gitEnv();
96
+
97
+ try {
98
+ await run('git', ['init', '-q'], { cwd: repo, env });
99
+ await run('git', ['checkout', '-q', '-b', 'main'], { cwd: repo, env });
100
+ await mkdir(join(repo, 'apps', 'ui'), { recursive: true });
101
+ await writeFile(join(repo, 'apps', 'ui', 'old.txt'), 'old\n', 'utf-8');
102
+ await run('git', ['add', '.'], { cwd: repo, env });
103
+ await run('git', ['commit', '-q', '-m', 'chore: base'], { cwd: repo, env });
104
+
105
+ const baseCommit = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: repo, env })).trim();
106
+ await run('git', ['mv', 'apps/ui/old.txt', 'apps/ui/new.txt'], { cwd: repo, env });
107
+ await run('git', ['commit', '-q', '-m', 'refactor: rename'], { cwd: repo, env });
108
+ const headCommit = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: repo, env })).trim();
109
+
110
+ const ops = await getChangedOps({ cwd: repo, baseRef: baseCommit, headRef: headCommit, env });
111
+ assert.deepEqual([...ops.checkout].sort(), ['apps/ui/new.txt']);
112
+ assert.deepEqual([...ops.remove].sort(), ['apps/ui/old.txt']);
113
+ assert.deepEqual([...ops.all].sort(), ['apps/ui/new.txt', 'apps/ui/old.txt']);
114
+ } finally {
115
+ await rm(repo, { recursive: true, force: true });
116
+ }
117
+ });
@@ -0,0 +1,20 @@
1
+ You are running a deep, long-form code review.
2
+
3
+ Goals:
4
+ - Find correctness bugs, edge cases, and regressions vs upstream/main.
5
+ - Find performance problems (big-O, unnecessary allocations, redundant work) and reliability issues.
6
+ - Find security and safety issues (filesystem access, env handling, process spawning, injection risks).
7
+ - Find maintainability issues (duplication, unclear ownership boundaries, inconsistent patterns).
8
+ - Ensure i18n coverage is complete: do not introduce hardcoded user-visible strings.
9
+
10
+ Constraints:
11
+ - Prefer fixes that are unified/coherent and avoid duplicating logic.
12
+ - Avoid “brittle” tests that assert on wording/phrasing or hardcoded text; test real behavior and observable outcomes.
13
+ - Do not suggest broad refactors unless clearly justified and low-risk.
14
+ - Treat every recommendation as a suggestion: validate against best practices and the existing codebase patterns; do not propose changes that conflict with project invariants.
15
+ - If a recommendation is uncertain, depends on product/UX decisions, or might have hidden tradeoffs, explicitly mark it as "needs discussion".
16
+
17
+ Output:
18
+ - Provide specific, actionable recommendations with file paths and a brief rationale.
19
+ - Call out any items that are uncertain or require product/UX decisions separately.
20
+ - Be exhaustive: include all findings you notice, not only the highest-signal ones.
@@ -0,0 +1,279 @@
1
+ function normalizeChangeType(raw) {
2
+ const t = String(raw ?? '').trim().toLowerCase();
3
+ if (!t) return 'committed';
4
+ if (t === 'committed' || t === 'uncommitted' || t === 'all') return t;
5
+ // Be defensive: unknown values shouldn't crash prompt building.
6
+ return 'committed';
7
+ }
8
+
9
+ function codexScopePathForComponent(component) {
10
+ switch (component) {
11
+ case 'happier-ui':
12
+ return 'apps/ui';
13
+ case 'happier-cli':
14
+ return 'apps/cli';
15
+ case 'happier-server-light':
16
+ case 'happier-server':
17
+ return 'apps/server';
18
+ default:
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function normalizeScopePaths(raw) {
24
+ const arr = Array.isArray(raw) ? raw : [];
25
+ const cleaned = arr
26
+ .map((p) => String(p ?? '').trim().replace(/\/+$/g, ''))
27
+ .filter(Boolean);
28
+ return Array.from(new Set(cleaned));
29
+ }
30
+
31
+ function buildPromptBaseLines({ title, baseRef, changeType, scopePath, deep, customPrompt }) {
32
+ const ct = normalizeChangeType(changeType);
33
+ const committedCmd = scopePath
34
+ ? `git diff ${baseRef}...HEAD -- ${scopePath}/`
35
+ : `git diff ${baseRef}...HEAD`;
36
+ const uncommittedCmd = scopePath
37
+ ? `git diff HEAD -- ${scopePath}/`
38
+ : `git diff HEAD`;
39
+ const cmds = ct === 'committed' ? [committedCmd] : ct === 'uncommitted' ? [uncommittedCmd] : [committedCmd, uncommittedCmd];
40
+
41
+ const focusLines = deep
42
+ ? [
43
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
44
+ '- Prefer unified/coherent fixes; avoid duplication.',
45
+ '- Avoid brittle tests that assert on wording/phrasing/config; test real behavior and observable outcomes.',
46
+ '- Ensure i18n coverage is complete: do not introduce hardcoded user-visible strings; add translation keys across locales as needed.',
47
+ '- Be exhaustive: list all findings you notice, not only the highest-signal ones.',
48
+ ]
49
+ : [
50
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
51
+ '- Prefer unified/coherent fixes; avoid duplication.',
52
+ '- Avoid low-signal nits and docs-only wording feedback unless it affects correctness, safety, or long-term maintainability.',
53
+ '- Limit to the highest-signal items (aim for <= 15 findings).',
54
+ ];
55
+
56
+ const extra = String(customPrompt ?? '').trim();
57
+ const customLines = extra
58
+ ? [
59
+ 'Custom focus:',
60
+ extra,
61
+ '',
62
+ ]
63
+ : [];
64
+
65
+ return [
66
+ title,
67
+ '',
68
+ `Change type: ${ct}`,
69
+ ct === 'uncommitted' ? 'Base for review: (uncommitted-only)' : `Base for review: ${baseRef}`,
70
+ scopePath ? `Scope: ${scopePath}/` : 'Scope: full repo',
71
+ '',
72
+ 'Instructions:',
73
+ ...cmds.map((c) => `- Use: ${c}`),
74
+ ct !== 'committed'
75
+ ? '- Include untracked files (if any): git status --porcelain=v1'
76
+ : null,
77
+ ...customLines,
78
+ ...focusLines,
79
+ '- Do not wait for other agents/reviewers and do not attempt to coordinate with them. Produce your own review output immediately.',
80
+ "- Treat every recommendation as a suggestion: validate it against best practices and this codebase's existing patterns. Do not propose changes that violate project invariants.",
81
+ '- Clearly mark any item that is uncertain, has tradeoffs, or needs product/UX decisions as "needs discussion".',
82
+ '',
83
+ 'Output format:',
84
+ '- Start with a short overall verdict.',
85
+ '- Then list findings as bullets with severity (blocker/major/minor/nit) and a concrete fix suggestion.',
86
+ '',
87
+ 'Machine-readable output (required):',
88
+ '- After your review, output a JSON array of findings preceded by a line containing exactly: ===FINDINGS_JSON===',
89
+ '- Each finding should include: severity, file, (optional) lines, title, description, recommendation, needsDiscussion (boolean).',
90
+ ]
91
+ .filter(Boolean)
92
+ .join('\n');
93
+ }
94
+
95
+ function buildAuditPromptBaseLines({ title, scopePaths, deep, customPrompt }) {
96
+ const paths = normalizeScopePaths(scopePaths);
97
+ const scopeLine = paths.length ? `Scope paths: ${paths.join(', ')}` : 'Scope paths: (not specified)';
98
+ const focusLines = deep
99
+ ? [
100
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
101
+ '- Prefer unified/coherent fixes; avoid duplication.',
102
+ '- Avoid brittle tests that assert on wording/phrasing/config; test real behavior and observable outcomes.',
103
+ '- Be exhaustive: list all findings you notice, not only the highest-signal ones.',
104
+ ]
105
+ : [
106
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
107
+ '- Prefer unified/coherent fixes; avoid duplication.',
108
+ '- Avoid low-signal nits and docs-only wording feedback unless it affects correctness, safety, or long-term maintainability.',
109
+ '- Limit to the highest-signal items (aim for <= 15 findings).',
110
+ ];
111
+
112
+ const extra = String(customPrompt ?? '').trim();
113
+ const customLines = extra
114
+ ? [
115
+ 'Custom focus:',
116
+ extra,
117
+ '',
118
+ ]
119
+ : [];
120
+
121
+ return [
122
+ title,
123
+ '',
124
+ 'Mode: audit (no diff).',
125
+ scopeLine,
126
+ '',
127
+ 'Instructions:',
128
+ '- Inspect the current repository state directly (not a diff).',
129
+ '- Prefer starting from the scope paths above; follow cross-references as needed, but keep findings grounded in concrete code.',
130
+ ...customLines,
131
+ ...focusLines,
132
+ '- Do not wait for other agents/reviewers and do not attempt to coordinate with them. Produce your own review output immediately.',
133
+ "- Treat every recommendation as a suggestion: validate it against best practices and this codebase's existing patterns. Do not propose changes that violate project invariants.",
134
+ '- Clearly mark any item that is uncertain, has tradeoffs, or needs product/UX decisions as "needs discussion".',
135
+ '',
136
+ 'Output format:',
137
+ '- Start with a short overall verdict.',
138
+ '- Then list findings as bullets with severity (blocker/major/minor/nit) and a concrete fix suggestion.',
139
+ '',
140
+ 'Machine-readable output (required):',
141
+ '- After your review, output a JSON array of findings preceded by a line containing exactly: ===FINDINGS_JSON===',
142
+ '- Each finding should include: severity, file, (optional) lines, title, description, recommendation, needsDiscussion (boolean).',
143
+ ].join('\n');
144
+ }
145
+
146
+ export function buildCodexDeepPrompt({ component, baseRef, changeType, customPrompt }) {
147
+ const scopePath = codexScopePathForComponent(component);
148
+ return buildPromptBaseLines({
149
+ title: 'Run a deep, long-form code review.',
150
+ baseRef,
151
+ changeType,
152
+ scopePath,
153
+ deep: true,
154
+ customPrompt,
155
+ });
156
+ }
157
+
158
+ export function buildCodexNormalPrompt({ component, baseRef, changeType, customPrompt }) {
159
+ const scopePath = codexScopePathForComponent(component);
160
+ return buildPromptBaseLines({
161
+ title: 'Run a focused code review.',
162
+ baseRef,
163
+ changeType,
164
+ scopePath,
165
+ deep: false,
166
+ customPrompt,
167
+ });
168
+ }
169
+
170
+ export function buildCodexMonorepoDeepPrompt({ baseRef, changeType, customPrompt }) {
171
+ return buildPromptBaseLines({
172
+ title: 'Run a deep, long-form code review on the monorepo.',
173
+ baseRef,
174
+ changeType,
175
+ scopePath: null,
176
+ deep: true,
177
+ customPrompt,
178
+ });
179
+ }
180
+
181
+ export function buildCodexMonorepoNormalPrompt({ baseRef, changeType, customPrompt }) {
182
+ return buildPromptBaseLines({
183
+ title: 'Run a focused code review on the monorepo.',
184
+ baseRef,
185
+ changeType,
186
+ scopePath: null,
187
+ deep: false,
188
+ customPrompt,
189
+ });
190
+ }
191
+
192
+ export function buildCodexAuditPrompt({ component, deep = true, scopePaths = [], customPrompt }) {
193
+ const scopePath = codexScopePathForComponent(component);
194
+ const paths = normalizeScopePaths(scopePaths);
195
+ // If no explicit paths are provided, fall back to the component scope path for signal.
196
+ const effective = paths.length ? paths : scopePath ? [scopePath] : [];
197
+ return buildAuditPromptBaseLines({
198
+ title: deep ? 'Run a deep, long-form repository audit.' : 'Run a focused repository audit.',
199
+ scopePaths: effective,
200
+ deep: Boolean(deep),
201
+ customPrompt,
202
+ });
203
+ }
204
+
205
+ export function buildCodexMonorepoAuditPrompt({ deep = true, scopePaths = [], customPrompt }) {
206
+ return buildAuditPromptBaseLines({
207
+ title: deep ? 'Run a deep, long-form repository audit on the monorepo.' : 'Run a focused repository audit on the monorepo.',
208
+ scopePaths,
209
+ deep: Boolean(deep),
210
+ customPrompt,
211
+ });
212
+ }
213
+
214
+ export function buildCodexMonorepoSlicePrompt({ sliceLabel, baseCommit, baseRef, deep = true, customPrompt }) {
215
+ const diffCmd = `git -C \"$(git rev-parse --show-toplevel)\" diff ${baseCommit}...HEAD`;
216
+ const focusLines = deep
217
+ ? [
218
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
219
+ '- Prefer unified/coherent fixes; avoid duplication.',
220
+ '- Avoid brittle tests that assert on wording/phrasing/config; test real behavior and observable outcomes.',
221
+ '- Ensure i18n coverage is complete: do not introduce hardcoded user-visible strings; add translation keys across locales as needed.',
222
+ '- Be exhaustive within this slice: list all findings you notice, not only the highest-signal ones.',
223
+ ]
224
+ : [
225
+ '- Focus on correctness, edge cases, reliability, performance, and security.',
226
+ '- Prefer unified/coherent fixes; avoid duplication.',
227
+ '- Avoid low-signal nits and docs-only wording feedback unless it affects correctness, safety, or long-term maintainability.',
228
+ '- Limit to the highest-signal items (aim for <= 15 findings).',
229
+ ];
230
+
231
+ const extra = String(customPrompt ?? '').trim();
232
+ const customLines = extra
233
+ ? [
234
+ '',
235
+ 'Custom focus:',
236
+ extra,
237
+ ]
238
+ : [];
239
+
240
+ return [
241
+ deep ? 'Run a deep, long-form code review on the monorepo.' : 'Run a focused code review on the monorepo.',
242
+ '',
243
+ `Base ref: ${baseRef}`,
244
+ `Slice: ${sliceLabel}`,
245
+ '',
246
+ 'Important:',
247
+ '- The base commit for this slice is synthetic: it represents upstream plus all NON-slice changes.',
248
+ '- Therefore, the diff below contains ONLY the changes for this slice, but the checked-out code is the full final HEAD.',
249
+ '',
250
+ 'Instructions:',
251
+ `- Use: ${diffCmd}`,
252
+ '- You may inspect any file in the repo for cross-references (server/cli/ui), but keep findings scoped to this slice diff.',
253
+ ...customLines,
254
+ ...focusLines,
255
+ '- Do not wait for other agents/reviewers and do not attempt to coordinate with them. Produce your own review output immediately.',
256
+ "- Treat every recommendation as a suggestion: validate it against best practices and this codebase's existing patterns. Do not propose changes that violate project invariants.",
257
+ '- Clearly mark any item that is uncertain, has tradeoffs, or needs product/UX decisions as "needs discussion".',
258
+ '',
259
+ 'Output format:',
260
+ '- Start with a short overall verdict.',
261
+ '- Then list findings as bullets with severity (blocker/major/minor/nit) and a concrete fix suggestion.',
262
+ '',
263
+ 'Machine-readable output (required):',
264
+ '- After your review, output a JSON array of findings preceded by a line containing exactly: ===FINDINGS_JSON===',
265
+ '- Each finding should include: severity, file, (optional) lines, title, description, recommendation, needsDiscussion (boolean).',
266
+ ].join('\n');
267
+ }
268
+
269
+ export function buildUncommittedSlicePrompt({ sliceLabel, basePrompt }) {
270
+ return [
271
+ `Slice: ${sliceLabel}`,
272
+ '- The worktree is pre-scoped for this slice only.',
273
+ '- Files outside this slice may be intentionally absent in this temporary worktree.',
274
+ '- Do not file "missing file/module" findings unless the missing path is part of this slice diff.',
275
+ '- Review only the uncommitted changes currently present in this worktree.',
276
+ '',
277
+ basePrompt,
278
+ ].join('\n');
279
+ }
@@ -0,0 +1,77 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import {
5
+ buildCodexDeepPrompt,
6
+ buildCodexNormalPrompt,
7
+ buildCodexMonorepoDeepPrompt,
8
+ buildCodexMonorepoNormalPrompt,
9
+ buildCodexAuditPrompt,
10
+ buildCodexMonorepoAuditPrompt,
11
+ buildCodexMonorepoSlicePrompt,
12
+ } from './prompts.mjs';
13
+
14
+ test('monorepo normal prompt is focused and includes findings JSON marker', () => {
15
+ const prompt = buildCodexMonorepoNormalPrompt({ baseRef: 'upstream/main', changeType: 'uncommitted' });
16
+ assert.match(prompt, /focused code review on the monorepo/i);
17
+ assert.match(prompt, /git diff HEAD/i);
18
+ assert.match(prompt, /===FINDINGS_JSON===/);
19
+ assert.match(prompt, /Do not wait for other agents\/reviewers/i);
20
+ assert.doesNotMatch(prompt, /Be exhaustive/i);
21
+ });
22
+
23
+ test('monorepo deep prompt is exhaustive and includes findings JSON marker', () => {
24
+ const prompt = buildCodexMonorepoDeepPrompt({ baseRef: 'upstream/main', changeType: 'committed' });
25
+ assert.match(prompt, /deep, long-form code review on the monorepo/i);
26
+ assert.match(prompt, /git diff upstream\/main\.\.\.HEAD/i);
27
+ assert.match(prompt, /===FINDINGS_JSON===/);
28
+ assert.match(prompt, /Be exhaustive/i);
29
+ });
30
+
31
+ test('component normal prompt scopes diff to component path', () => {
32
+ const prompt = buildCodexNormalPrompt({ component: 'happier-ui', baseRef: 'upstream/main', changeType: 'committed' });
33
+ assert.match(prompt, /Scope: apps\/ui\//);
34
+ assert.match(prompt, /git diff upstream\/main\.\.\.HEAD -- apps\/ui\//);
35
+ });
36
+
37
+ test('component deep prompt scopes diff to component path', () => {
38
+ const prompt = buildCodexDeepPrompt({ component: 'happier-cli', baseRef: 'upstream/main', changeType: 'committed' });
39
+ assert.match(prompt, /Scope: apps\/cli\//);
40
+ assert.match(prompt, /git diff upstream\/main\.\.\.HEAD -- apps\/cli\//);
41
+ });
42
+
43
+ test('monorepo slice prompt toggles focused vs deep wording', () => {
44
+ const deep = buildCodexMonorepoSlicePrompt({ sliceLabel: 'apps/cli', baseCommit: 'abc123', baseRef: 'upstream/main', deep: true });
45
+ const focused = buildCodexMonorepoSlicePrompt({ sliceLabel: 'apps/cli', baseCommit: 'abc123', baseRef: 'upstream/main', deep: false });
46
+ assert.match(deep, /deep, long-form/i);
47
+ assert.match(focused, /focused code review/i);
48
+ });
49
+
50
+ test('prompts support appending custom focus instructions', () => {
51
+ const prompt = buildCodexMonorepoNormalPrompt({
52
+ baseRef: 'upstream/main',
53
+ changeType: 'uncommitted',
54
+ customPrompt: 'Focus specifically on machine_id_conflict behavior and cross-account machine claiming risks.',
55
+ });
56
+ assert.match(prompt, /Custom focus:/i);
57
+ assert.match(prompt, /machine_id_conflict/i);
58
+ assert.match(prompt, /===FINDINGS_JSON===/);
59
+ });
60
+
61
+ test('audit prompts do not require git diff and include scope paths', () => {
62
+ const prompt = buildCodexMonorepoAuditPrompt({
63
+ deep: true,
64
+ scopePaths: ['apps/cli/src/daemon', 'apps/server/sources/app/api/routes/machines'],
65
+ customPrompt: 'Focus on machine identity invariants across accounts.',
66
+ });
67
+ assert.match(prompt, /Mode: audit/i);
68
+ assert.match(prompt, /Scope paths:/i);
69
+ assert.doesNotMatch(prompt, /git diff/i);
70
+ assert.match(prompt, /===FINDINGS_JSON===/);
71
+ });
72
+
73
+ test('component audit prompt falls back to component scope path when no explicit scope paths provided', () => {
74
+ const prompt = buildCodexAuditPrompt({ component: 'happier-ui', deep: false, scopePaths: [] });
75
+ assert.match(prompt, /Scope paths: apps\/ui/i);
76
+ assert.doesNotMatch(prompt, /git diff/i);
77
+ });
@@ -0,0 +1,12 @@
1
+ export async function runReviewersSafe({ reviewers, runReviewer, onError }) {
2
+ const list = Array.isArray(reviewers) ? reviewers : [];
3
+ const run = typeof runReviewer === 'function' ? runReviewer : async () => null;
4
+ const handleError = typeof onError === 'function' ? onError : (_reviewer, error) => ({ ok: false, error });
5
+
6
+ const settled = await Promise.allSettled(list.map((r) => run(r)));
7
+ return settled.map((s, i) => {
8
+ if (s.status === 'fulfilled') return s.value;
9
+ return handleError(list[i], s.reason);
10
+ });
11
+ }
12
+
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+
4
+ import { runReviewersSafe } from './run_reviewers_safe.mjs';
5
+
6
+ test('runReviewersSafe returns values in reviewer order', async () => {
7
+ const reviewers = ['a', 'b', 'c'];
8
+ const seen = [];
9
+
10
+ const res = await runReviewersSafe({
11
+ reviewers,
12
+ runReviewer: async (r) => {
13
+ seen.push(r);
14
+ return { reviewer: r, ok: true };
15
+ },
16
+ onError: (reviewer, error) => ({ reviewer, ok: false, error }),
17
+ });
18
+
19
+ assert.deepEqual(seen, reviewers);
20
+ assert.deepEqual(
21
+ res.map((r) => r.reviewer),
22
+ reviewers
23
+ );
24
+ assert.equal(res.every((r) => r.ok), true);
25
+ });
26
+
27
+ test('runReviewersSafe converts thrown errors into onError results', async () => {
28
+ const reviewers = ['ok', 'boom', 'ok2'];
29
+
30
+ const res = await runReviewersSafe({
31
+ reviewers,
32
+ runReviewer: async (r) => {
33
+ if (r === 'boom') throw new Error('kaboom');
34
+ return { reviewer: r, ok: true };
35
+ },
36
+ onError: (reviewer, error) => ({ reviewer, ok: false, message: String(error?.message ?? error) }),
37
+ });
38
+
39
+ assert.deepEqual(res.map((r) => r.reviewer), reviewers);
40
+ assert.equal(res[0].ok, true);
41
+ assert.equal(res[1].ok, false);
42
+ assert.match(res[1].message, /kaboom/);
43
+ assert.equal(res[2].ok, true);
44
+ });
45
+
@@ -0,0 +1,91 @@
1
+ import { runCaptureResult } from '../../proc/proc.mjs';
2
+
3
+ export function detectAugmentAuthError({ stdout, stderr }) {
4
+ const combined = `${stdout ?? ''}\n${stderr ?? ''}`;
5
+ return combined.includes('Authentication failed') && combined.includes("Run 'auggie login'");
6
+ }
7
+
8
+ function parsePositiveInt(raw) {
9
+ const n = Number(String(raw ?? '').trim());
10
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
11
+ }
12
+
13
+ function resolveAugmentKeepaliveMs(env) {
14
+ const specific = parsePositiveInt(env?.HAPPIER_STACK_REVIEW_AUGMENT_KEEPALIVE_MS);
15
+ if (specific !== null) return specific;
16
+ const global = parsePositiveInt(env?.HAPPIER_STACK_REVIEW_KEEPALIVE_MS);
17
+ if (global !== null) return global;
18
+ return 30_000;
19
+ }
20
+
21
+ export function buildAugmentReviewArgs({
22
+ prompt,
23
+ workspaceRoot,
24
+ cacheDir,
25
+ model,
26
+ rulesFiles = [],
27
+ retryTimeoutSec,
28
+ maxTurns,
29
+ } = {}) {
30
+ const args = ['--print', '--quiet', '--dont-save-session', '--ask', '--output-format', 'text'];
31
+
32
+ const wr = String(workspaceRoot ?? '').trim();
33
+ if (wr) args.push('--workspace-root', wr);
34
+
35
+ const cd = String(cacheDir ?? '').trim();
36
+ if (cd) args.push('--augment-cache-dir', cd);
37
+
38
+ const m = String(model ?? '').trim();
39
+ if (m) args.push('--model', m);
40
+
41
+ const rt = String(retryTimeoutSec ?? '').trim();
42
+ if (rt) args.push('--retry-timeout', rt);
43
+
44
+ const mt = String(maxTurns ?? '').trim();
45
+ if (mt) args.push('--max-turns', mt);
46
+
47
+ for (const rf of Array.isArray(rulesFiles) ? rulesFiles : []) {
48
+ const p = String(rf ?? '').trim();
49
+ if (!p) continue;
50
+ args.push('--rules', p);
51
+ }
52
+
53
+ const p = String(prompt ?? '').trim();
54
+ if (!p) throw new Error('[review] augment: missing prompt');
55
+ args.push(p);
56
+ return args;
57
+ }
58
+
59
+ export async function runAugmentReview({
60
+ repoDir,
61
+ prompt,
62
+ env,
63
+ streamLabel,
64
+ teeFile,
65
+ teeLabel,
66
+ cacheDir,
67
+ model,
68
+ rulesFiles = [],
69
+ retryTimeoutSec = 60 * 60 * 2,
70
+ maxTurns,
71
+ } = {}) {
72
+ const args = buildAugmentReviewArgs({
73
+ prompt,
74
+ workspaceRoot: repoDir,
75
+ cacheDir,
76
+ model,
77
+ rulesFiles,
78
+ retryTimeoutSec,
79
+ maxTurns,
80
+ });
81
+ const heartbeatMs = resolveAugmentKeepaliveMs(env ?? {});
82
+ const res = await runCaptureResult('auggie', args, {
83
+ cwd: repoDir,
84
+ env: env ?? {},
85
+ streamLabel,
86
+ teeFile,
87
+ teeLabel,
88
+ heartbeatMs,
89
+ });
90
+ return { ...res, stdout: res.out, stderr: res.err };
91
+ }