@happier-dev/stack 0.1.0-preview.5.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.
- package/README.md +501 -0
- package/bin/hstack.mjs +348 -0
- package/docs/codex-mcp-resume.md +129 -0
- package/docs/edison.md +74 -0
- package/docs/forking-and-branding.md +189 -0
- package/docs/happy-development.md +22 -0
- package/docs/isolated-linux-vm.md +243 -0
- package/docs/menubar.md +244 -0
- package/docs/mobile-ios.md +322 -0
- package/docs/monorepo-migration.md +20 -0
- package/docs/paths-and-env.md +154 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +147 -0
- package/docs/stacks.md +330 -0
- package/docs/tauri.md +60 -0
- package/docs/worktrees-and-forks.md +133 -0
- package/extras/swiftbar/auth-login.sh +29 -0
- package/extras/swiftbar/git-cache-refresh.sh +122 -0
- package/extras/swiftbar/hstack-term.sh +133 -0
- package/extras/swiftbar/hstack.5s.sh +296 -0
- package/extras/swiftbar/hstack.sh +35 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +265 -0
- package/extras/swiftbar/lib/git.sh +629 -0
- package/extras/swiftbar/lib/icons.sh +92 -0
- package/extras/swiftbar/lib/render.sh +999 -0
- package/extras/swiftbar/lib/system.sh +244 -0
- package/extras/swiftbar/lib/utils.sh +717 -0
- package/extras/swiftbar/set-interval.sh +65 -0
- package/extras/swiftbar/set-server-flavor.sh +61 -0
- package/extras/swiftbar/wt-pr.sh +140 -0
- package/node_modules/@happier-dev/cli-common/README.md +6 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js +4 -0
- package/node_modules/@happier-dev/cli-common/dist/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts +18 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js +25 -0
- package/node_modules/@happier-dev/cli-common/dist/links/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js +2 -0
- package/node_modules/@happier-dev/cli-common/dist/links.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts +67 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js +259 -0
- package/node_modules/@happier-dev/cli-common/dist/update/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts +17 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.d.ts.map +1 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js +80 -0
- package/node_modules/@happier-dev/cli-common/dist/workspaces/index.js.map +1 -0
- package/node_modules/@happier-dev/cli-common/package.json +26 -0
- package/package.json +77 -0
- package/scripts/auth.mjs +1829 -0
- package/scripts/auth_copy_from_pglite_lock_in_use.integration.test.mjs +90 -0
- package/scripts/auth_copy_from_runCapture.integration.test.mjs +447 -0
- package/scripts/auth_help_cmd.test.mjs +28 -0
- package/scripts/auth_login_flow_in_tty.test.mjs +100 -0
- package/scripts/auth_login_force_default.test.mjs +66 -0
- package/scripts/auth_login_guided_server_no_expo.test.mjs +126 -0
- package/scripts/auth_login_method_override.test.mjs +67 -0
- package/scripts/auth_login_print_includes_configure_links.test.mjs +99 -0
- package/scripts/auth_status_server_validation.integration.test.mjs +140 -0
- package/scripts/build.mjs +266 -0
- package/scripts/bundleWorkspaceDeps.mjs +38 -0
- package/scripts/bundleWorkspaceDeps.test.mjs +77 -0
- package/scripts/ci.mjs +135 -0
- package/scripts/ci.test.mjs +50 -0
- package/scripts/cli-link.mjs +57 -0
- package/scripts/completion.mjs +395 -0
- package/scripts/contrib.mjs +333 -0
- package/scripts/daemon.mjs +1160 -0
- package/scripts/daemon.status_scope.test.mjs +51 -0
- package/scripts/daemon_cmd.mjs +26 -0
- package/scripts/daemon_dist_guard.test.mjs +171 -0
- package/scripts/daemon_invalid_auth_reseed_stack_name.integration.test.mjs +608 -0
- package/scripts/daemon_server_scoped_state.test.mjs +49 -0
- package/scripts/daemon_start_verification.integration.test.mjs +296 -0
- package/scripts/dev.mjs +545 -0
- package/scripts/doctor.mjs +340 -0
- package/scripts/doctor_cmd.test.mjs +22 -0
- package/scripts/doctor_ui_index_missing.test.mjs +37 -0
- package/scripts/eas.mjs +367 -0
- package/scripts/eas_platform_parsing.test.mjs +63 -0
- package/scripts/edison.mjs +1848 -0
- package/scripts/env.mjs +149 -0
- package/scripts/env_cmd.test.mjs +118 -0
- package/scripts/exit_cleanup_kills_detached_children_on_crash.integration.test.mjs +80 -0
- package/scripts/happier.mjs +82 -0
- package/scripts/import.mjs +1327 -0
- package/scripts/init.mjs +464 -0
- package/scripts/install.mjs +550 -0
- package/scripts/lint.mjs +177 -0
- package/scripts/menubar.mjs +202 -0
- package/scripts/migrate.mjs +318 -0
- package/scripts/mobile.mjs +353 -0
- package/scripts/mobile_dev_client.mjs +87 -0
- package/scripts/monorepo.mjs +2234 -0
- package/scripts/monorepo_port.apply.integration.test.mjs +680 -0
- package/scripts/monorepo_port.conflicts.integration.test.mjs +454 -0
- package/scripts/monorepo_port.validation.integration.test.mjs +486 -0
- package/scripts/orchestrated_stack_auth_flow.test.mjs +134 -0
- package/scripts/orchestrated_stack_auth_flow_resolve_port.test.mjs +98 -0
- package/scripts/orchestrated_stack_auth_flow_webapp_url.test.mjs +119 -0
- package/scripts/pack.mjs +257 -0
- package/scripts/pack.test.mjs +68 -0
- package/scripts/pglite_lock.integration.test.mjs +152 -0
- package/scripts/provision/linux-ubuntu-e2e.sh +132 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +66 -0
- package/scripts/provision/macos-lima-happy-vm.sh +192 -0
- package/scripts/provision/macos-lima-hstack-e2e.sh +100 -0
- package/scripts/release.mjs +53 -0
- package/scripts/release_binary_smoke.integration.test.mjs +159 -0
- package/scripts/review.mjs +1752 -0
- package/scripts/review_pr.mjs +435 -0
- package/scripts/run.mjs +561 -0
- package/scripts/run_script_with_stack_env.restart_port_reuse.test.mjs +30 -0
- package/scripts/self.mjs +465 -0
- package/scripts/self_host.mjs +9 -0
- package/scripts/self_host_binary_smoke.integration.test.mjs +94 -0
- package/scripts/self_host_runtime.mjs +883 -0
- package/scripts/self_host_runtime.test.mjs +82 -0
- package/scripts/self_host_systemd.real.integration.test.mjs +367 -0
- package/scripts/server_flavor.mjs +148 -0
- package/scripts/service.mjs +868 -0
- package/scripts/service_mode_help.test.mjs +27 -0
- package/scripts/setup.mjs +1324 -0
- package/scripts/setup_non_interactive_flag.test.mjs +60 -0
- package/scripts/setup_pr.mjs +605 -0
- package/scripts/setup_pr_orchestrated_auth_flow_util_import.test.mjs +117 -0
- package/scripts/stack/command_arguments.mjs +91 -0
- package/scripts/stack/copy_auth_from_stack.mjs +111 -0
- package/scripts/stack/delegated_script_commands.mjs +92 -0
- package/scripts/stack/help_text.mjs +110 -0
- package/scripts/stack/port_reservation.mjs +74 -0
- package/scripts/stack/repo_checkout_resolution.mjs +31 -0
- package/scripts/stack/run_script_with_stack_env.mjs +634 -0
- package/scripts/stack/stack_daemon_command.mjs +219 -0
- package/scripts/stack/stack_delegated_help.mjs +81 -0
- package/scripts/stack/stack_environment.mjs +151 -0
- package/scripts/stack/stack_environment.sanitization.test.mjs +75 -0
- package/scripts/stack/stack_happier_passthrough_command.mjs +63 -0
- package/scripts/stack/stack_info_snapshot.mjs +167 -0
- package/scripts/stack/stack_mobile_install_command.mjs +61 -0
- package/scripts/stack/stack_resume_command.mjs +76 -0
- package/scripts/stack/stack_stop_command.mjs +34 -0
- package/scripts/stack/stack_workspace_command.mjs +83 -0
- package/scripts/stack/transient_repo_overrides.mjs +29 -0
- package/scripts/stack.mjs +2388 -0
- package/scripts/stack_archive_cmd.integration.test.mjs +31 -0
- package/scripts/stack_audit_fix_light_env.test.mjs +129 -0
- package/scripts/stack_background_pinned_stack_json.test.mjs +81 -0
- package/scripts/stack_copy_auth_server_scoped.test.mjs +243 -0
- package/scripts/stack_daemon_cmd.integration.test.mjs +484 -0
- package/scripts/stack_eas_help.test.mjs +72 -0
- package/scripts/stack_editor_workspace_monorepo_root.test.mjs +102 -0
- package/scripts/stack_env_cmd.test.mjs +107 -0
- package/scripts/stack_guided_login_bundle_error_parse.test.mjs +20 -0
- package/scripts/stack_guided_login_inner_invocation.test.mjs +46 -0
- package/scripts/stack_happy_cmd.integration.test.mjs +263 -0
- package/scripts/stack_info_snapshot_running_status.test.mjs +186 -0
- package/scripts/stack_interactive_monorepo_group.test.mjs +128 -0
- package/scripts/stack_monorepo_defaults.test.mjs +31 -0
- package/scripts/stack_monorepo_repo_dev_token.test.mjs +32 -0
- package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +37 -0
- package/scripts/stack_new_name_normalize_cmd.test.mjs +38 -0
- package/scripts/stack_pr_name_normalize_cmd.test.mjs +84 -0
- package/scripts/stack_resume_cmd.integration.test.mjs +134 -0
- package/scripts/stack_server_flavors_defaults.test.mjs +64 -0
- package/scripts/stack_shorthand_cmd.integration.test.mjs +74 -0
- package/scripts/stack_stop_sweeps_legacy_infra_without_kind.integration.test.mjs +44 -0
- package/scripts/stack_stop_sweeps_when_runtime_missing.integration.test.mjs +42 -0
- package/scripts/stack_stop_sweeps_when_runtime_stale.integration.test.mjs +50 -0
- package/scripts/stack_wt_list.test.mjs +117 -0
- package/scripts/start_ui_required_default.test.mjs +63 -0
- package/scripts/stop.mjs +190 -0
- package/scripts/stopStackWithEnv_no_autosweep_when_runtime_missing.integration.test.mjs +95 -0
- package/scripts/swiftbar_git_monorepo_cmd.test.mjs +75 -0
- package/scripts/swiftbar_render_monorepo_wt_actions.integration.test.mjs +116 -0
- package/scripts/swiftbar_utils_cmd.test.mjs +92 -0
- package/scripts/swiftbar_wt_pr_backcompat.test.mjs +162 -0
- package/scripts/systemd_unit_info.test.mjs +24 -0
- package/scripts/tailscale.mjs +490 -0
- package/scripts/test_ci.mjs +36 -0
- package/scripts/test_cmd.mjs +274 -0
- package/scripts/test_cmd.test.mjs +133 -0
- package/scripts/test_integration.mjs +33 -0
- package/scripts/testkit/auth_testkit.mjs +121 -0
- package/scripts/testkit/doctor_testkit.mjs +68 -0
- package/scripts/testkit/monorepo_port_testkit.mjs +157 -0
- package/scripts/testkit/stack_archive_command_testkit.mjs +55 -0
- package/scripts/testkit/stack_new_monorepo_testkit.mjs +83 -0
- package/scripts/testkit/stack_script_command_testkit.mjs +27 -0
- package/scripts/testkit/stack_stop_sweeps_testkit.mjs +172 -0
- package/scripts/testkit/worktrees_monorepo_testkit.mjs +53 -0
- package/scripts/tools.mjs +70 -0
- package/scripts/tui.mjs +914 -0
- package/scripts/tui_stopStackForTuiExit_no_autosweep.integration.test.mjs +95 -0
- package/scripts/typecheck.mjs +178 -0
- package/scripts/ui_gateway.mjs +247 -0
- package/scripts/uninstall.mjs +179 -0
- package/scripts/utils/auth/credentials_paths.mjs +181 -0
- package/scripts/utils/auth/credentials_paths.test.mjs +187 -0
- package/scripts/utils/auth/daemon_gate.mjs +66 -0
- package/scripts/utils/auth/daemon_gate.test.mjs +116 -0
- package/scripts/utils/auth/decode_jwt_payload_unsafe.mjs +16 -0
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/guided_pr_auth.mjs +86 -0
- package/scripts/utils/auth/guided_stack_web_login.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +42 -0
- package/scripts/utils/auth/interactive_stack_auth.mjs +70 -0
- package/scripts/utils/auth/login_ux.mjs +105 -0
- package/scripts/utils/auth/orchestrated_stack_auth_flow.mjs +291 -0
- package/scripts/utils/auth/sources.mjs +28 -0
- package/scripts/utils/auth/stable_scope_id.mjs +91 -0
- package/scripts/utils/auth/stable_scope_id.test.mjs +51 -0
- package/scripts/utils/auth/stack_guided_login.mjs +438 -0
- package/scripts/utils/cli/arg_values.mjs +23 -0
- package/scripts/utils/cli/arg_values.test.mjs +43 -0
- package/scripts/utils/cli/args.mjs +17 -0
- package/scripts/utils/cli/cli.mjs +24 -0
- package/scripts/utils/cli/cli_registry.mjs +440 -0
- package/scripts/utils/cli/cwd_scope.mjs +158 -0
- package/scripts/utils/cli/cwd_scope.test.mjs +154 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/log_forwarder.mjs +157 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/cli/prereqs.mjs +103 -0
- package/scripts/utils/cli/prereqs.test.mjs +33 -0
- package/scripts/utils/cli/progress.mjs +141 -0
- package/scripts/utils/cli/smoke_help.mjs +44 -0
- package/scripts/utils/cli/verbosity.mjs +11 -0
- package/scripts/utils/cli/wizard.mjs +139 -0
- package/scripts/utils/cli/wizard_promptSelect.test.mjs +44 -0
- package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +132 -0
- package/scripts/utils/cli/wizard_worktree_slug.test.mjs +33 -0
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +232 -0
- package/scripts/utils/dev/daemon_watch_resilience.test.mjs +224 -0
- package/scripts/utils/dev/expo_dev.buildEnv.test.mjs +35 -0
- package/scripts/utils/dev/expo_dev.mjs +478 -0
- package/scripts/utils/dev/expo_dev.test.mjs +89 -0
- package/scripts/utils/dev/expo_dev_restart_port_reservation.test.mjs +120 -0
- package/scripts/utils/dev/expo_dev_verbose_logs.test.mjs +60 -0
- package/scripts/utils/dev/server.mjs +180 -0
- package/scripts/utils/dev_auth_key.mjs +7 -0
- package/scripts/utils/edison/git_roots.mjs +30 -0
- package/scripts/utils/edison/git_roots.test.mjs +49 -0
- package/scripts/utils/env/config.mjs +52 -0
- package/scripts/utils/env/dotenv.mjs +32 -0
- package/scripts/utils/env/dotenv.test.mjs +32 -0
- package/scripts/utils/env/env.mjs +130 -0
- package/scripts/utils/env/env_file.mjs +98 -0
- package/scripts/utils/env/env_file.test.mjs +49 -0
- package/scripts/utils/env/env_local.mjs +25 -0
- package/scripts/utils/env/load_env_file.mjs +34 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/sandbox.mjs +13 -0
- package/scripts/utils/env/scrub_env.mjs +69 -0
- package/scripts/utils/env/scrub_env.test.mjs +102 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/expo/command.mjs +65 -0
- package/scripts/utils/expo/expo.mjs +139 -0
- package/scripts/utils/expo/expo_state_running.test.mjs +48 -0
- package/scripts/utils/expo/metro_ports.mjs +101 -0
- package/scripts/utils/expo/metro_ports.test.mjs +35 -0
- package/scripts/utils/fs/atomic_dir_swap.mjs +55 -0
- package/scripts/utils/fs/atomic_dir_swap.test.mjs +54 -0
- package/scripts/utils/fs/file_has_content.mjs +10 -0
- package/scripts/utils/fs/fs.mjs +11 -0
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/dev_checkout.mjs +127 -0
- package/scripts/utils/git/dev_checkout.test.mjs +115 -0
- package/scripts/utils/git/git.mjs +67 -0
- package/scripts/utils/git/parse_name_status_z.mjs +21 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/git/worktrees.mjs +323 -0
- package/scripts/utils/git/worktrees_monorepo.test.mjs +60 -0
- package/scripts/utils/git/worktrees_pathstyle.test.mjs +53 -0
- package/scripts/utils/llm/assist.mjs +260 -0
- package/scripts/utils/llm/codex_exec.mjs +61 -0
- package/scripts/utils/llm/codex_exec.test.mjs +46 -0
- package/scripts/utils/llm/hstack_runner.mjs +59 -0
- package/scripts/utils/llm/tools.mjs +56 -0
- package/scripts/utils/llm/tools.test.mjs +67 -0
- package/scripts/utils/menubar/swiftbar.mjs +121 -0
- package/scripts/utils/menubar/swiftbar.test.mjs +85 -0
- package/scripts/utils/mobile/config.mjs +35 -0
- package/scripts/utils/mobile/dev_client_links.mjs +59 -0
- package/scripts/utils/mobile/identifiers.mjs +46 -0
- package/scripts/utils/mobile/identifiers.test.mjs +41 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.mjs +128 -0
- package/scripts/utils/mobile/ios_xcodeproj_patch.test.mjs +131 -0
- package/scripts/utils/net/bind_mode.mjs +39 -0
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/net/lan_ip.mjs +24 -0
- package/scripts/utils/net/ports.mjs +110 -0
- package/scripts/utils/net/tcp_forward.mjs +162 -0
- package/scripts/utils/net/url.mjs +30 -0
- package/scripts/utils/net/url.test.mjs +29 -0
- package/scripts/utils/paths/canonical_home.mjs +15 -0
- package/scripts/utils/paths/canonical_home.test.mjs +28 -0
- package/scripts/utils/paths/localhost_host.mjs +112 -0
- package/scripts/utils/paths/localhost_host.test.mjs +58 -0
- package/scripts/utils/paths/paths.mjs +302 -0
- package/scripts/utils/paths/paths_env_win32.test.mjs +36 -0
- package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
- package/scripts/utils/paths/paths_server_flavors.test.mjs +50 -0
- package/scripts/utils/paths/runtime.mjs +41 -0
- package/scripts/utils/pglite_lock.mjs +107 -0
- package/scripts/utils/proc/commands.mjs +33 -0
- package/scripts/utils/proc/exit_cleanup.mjs +57 -0
- package/scripts/utils/proc/happy_monorepo_deps.mjs +37 -0
- package/scripts/utils/proc/happy_monorepo_deps.test.mjs +89 -0
- package/scripts/utils/proc/ownership.mjs +217 -0
- package/scripts/utils/proc/ownership_killProcessGroupOwnedByStack.test.mjs +216 -0
- package/scripts/utils/proc/ownership_listPidsWithEnvNeedles.test.mjs +88 -0
- package/scripts/utils/proc/package_scripts.mjs +38 -0
- package/scripts/utils/proc/package_scripts.test.mjs +58 -0
- package/scripts/utils/proc/parallel.mjs +25 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +478 -0
- package/scripts/utils/proc/pm_spawn.integration.test.mjs +131 -0
- package/scripts/utils/proc/pm_stack_cache_env.test.mjs +313 -0
- package/scripts/utils/proc/proc.mjs +331 -0
- package/scripts/utils/proc/proc.test.mjs +85 -0
- package/scripts/utils/proc/terminate.mjs +69 -0
- package/scripts/utils/proc/terminate.test.mjs +54 -0
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/review/augment_runner_integration.test.mjs +105 -0
- package/scripts/utils/review/base_ref.mjs +82 -0
- package/scripts/utils/review/base_ref.test.mjs +89 -0
- package/scripts/utils/review/chunks.mjs +55 -0
- package/scripts/utils/review/chunks.test.mjs +107 -0
- package/scripts/utils/review/detached_worktree.mjs +61 -0
- package/scripts/utils/review/detached_worktree.test.mjs +61 -0
- package/scripts/utils/review/findings.mjs +278 -0
- package/scripts/utils/review/findings.test.mjs +203 -0
- package/scripts/utils/review/head_slice.mjs +132 -0
- package/scripts/utils/review/head_slice.test.mjs +117 -0
- package/scripts/utils/review/instructions/deep.md +20 -0
- package/scripts/utils/review/prompts.mjs +279 -0
- package/scripts/utils/review/prompts.test.mjs +77 -0
- package/scripts/utils/review/run_reviewers_safe.mjs +12 -0
- package/scripts/utils/review/run_reviewers_safe.test.mjs +45 -0
- package/scripts/utils/review/runners/augment.mjs +91 -0
- package/scripts/utils/review/runners/augment.test.mjs +64 -0
- package/scripts/utils/review/runners/claude.mjs +92 -0
- package/scripts/utils/review/runners/claude.test.mjs +47 -0
- package/scripts/utils/review/runners/coderabbit.mjs +105 -0
- package/scripts/utils/review/runners/coderabbit.test.mjs +32 -0
- package/scripts/utils/review/runners/codex.mjs +129 -0
- package/scripts/utils/review/runners/codex.test.mjs +115 -0
- package/scripts/utils/review/slice_mode.mjs +20 -0
- package/scripts/utils/review/slice_mode.test.mjs +69 -0
- package/scripts/utils/review/sliced_runner.mjs +39 -0
- package/scripts/utils/review/sliced_runner.test.mjs +57 -0
- package/scripts/utils/review/slices.mjs +140 -0
- package/scripts/utils/review/slices.test.mjs +41 -0
- package/scripts/utils/review/targets.mjs +23 -0
- package/scripts/utils/review/targets.test.mjs +31 -0
- package/scripts/utils/review/tool_home_seed.mjs +106 -0
- package/scripts/utils/review/tool_home_seed.test.mjs +124 -0
- package/scripts/utils/review/uncommitted_ops.mjs +77 -0
- package/scripts/utils/review/uncommitted_ops.test.mjs +117 -0
- package/scripts/utils/sandbox/review_pr_sandbox.mjs +105 -0
- package/scripts/utils/server/apply_server_light_env_defaults.mjs +14 -0
- package/scripts/utils/server/flavor_scripts.mjs +138 -0
- package/scripts/utils/server/flavor_scripts.test.mjs +115 -0
- package/scripts/utils/server/infra/happy_server_infra.mjs +444 -0
- package/scripts/utils/server/mobile_api_url.mjs +60 -0
- package/scripts/utils/server/mobile_api_url.test.mjs +58 -0
- package/scripts/utils/server/port.mjs +55 -0
- package/scripts/utils/server/prisma_import.mjs +36 -0
- package/scripts/utils/server/prisma_import.test.mjs +78 -0
- package/scripts/utils/server/server.mjs +109 -0
- package/scripts/utils/server/ui_build_check.mjs +37 -0
- package/scripts/utils/server/ui_build_check.test.mjs +70 -0
- package/scripts/utils/server/ui_env.mjs +13 -0
- package/scripts/utils/server/ui_env.test.mjs +57 -0
- package/scripts/utils/server/urls.mjs +100 -0
- package/scripts/utils/server/validate.mjs +60 -0
- package/scripts/utils/server/validate.test.mjs +76 -0
- package/scripts/utils/service/autostart_darwin.mjs +198 -0
- package/scripts/utils/service/autostart_darwin.test.mjs +49 -0
- package/scripts/utils/service/autostart_darwin_keepalive.test.mjs +19 -0
- package/scripts/utils/stack/cli_identities.mjs +29 -0
- package/scripts/utils/stack/context.mjs +19 -0
- package/scripts/utils/stack/dirs.mjs +26 -0
- package/scripts/utils/stack/editor_workspace.mjs +126 -0
- package/scripts/utils/stack/interactive_stack_config.mjs +266 -0
- package/scripts/utils/stack/interactive_stack_config.port_validation.test.mjs +93 -0
- package/scripts/utils/stack/interactive_stack_config.remote_validation.test.mjs +122 -0
- package/scripts/utils/stack/interactive_stack_config.stack_name_validation.test.mjs +76 -0
- package/scripts/utils/stack/interactive_stack_config_testkit.mjs +18 -0
- package/scripts/utils/stack/names.mjs +27 -0
- package/scripts/utils/stack/names.test.mjs +26 -0
- package/scripts/utils/stack/pr_stack_name.mjs +16 -0
- package/scripts/utils/stack/runtime_state.mjs +88 -0
- package/scripts/utils/stack/stacks.mjs +40 -0
- package/scripts/utils/stack/startup.mjs +370 -0
- package/scripts/utils/stack/startup_server_light_dirs.test.mjs +119 -0
- package/scripts/utils/stack/startup_server_light_generate.test.mjs +20 -0
- package/scripts/utils/stack/startup_server_light_legacy.test.mjs +79 -0
- package/scripts/utils/stack/startup_server_light_testkit.mjs +106 -0
- package/scripts/utils/stack/stop.mjs +284 -0
- package/scripts/utils/stack_context.mjs +1 -0
- package/scripts/utils/stack_runtime_state.mjs +1 -0
- package/scripts/utils/stacks.mjs +1 -0
- package/scripts/utils/tailscale/ip.mjs +116 -0
- package/scripts/utils/tauri/stack_overrides.mjs +22 -0
- package/scripts/utils/test/collect_test_files.mjs +29 -0
- package/scripts/utils/time/get_today_ymd.mjs +7 -0
- package/scripts/utils/tui/cleanup.mjs +38 -0
- package/scripts/utils/ui/ansi.mjs +47 -0
- package/scripts/utils/ui/browser.mjs +31 -0
- package/scripts/utils/ui/browser.test.mjs +56 -0
- package/scripts/utils/ui/clipboard.mjs +38 -0
- package/scripts/utils/ui/layout.mjs +44 -0
- package/scripts/utils/ui/qr.mjs +17 -0
- package/scripts/utils/ui/terminal_launcher.mjs +129 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/utils/update/auto_update_notice.mjs +93 -0
- package/scripts/utils/validate.mjs +5 -0
- package/scripts/where.mjs +138 -0
- package/scripts/worktrees.mjs +2174 -0
- package/scripts/worktrees_archive_cmd.integration.test.mjs +228 -0
- package/scripts/worktrees_cursor_monorepo_root.test.mjs +23 -0
- package/scripts/worktrees_list_specs_no_recurse.test.mjs +32 -0
- package/scripts/worktrees_monorepo_testkit.test.mjs +29 -0
- package/scripts/worktrees_monorepo_use_group.test.mjs +41 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
function parseLineRange(raw) {
|
|
2
|
+
const s = String(raw ?? '').trim();
|
|
3
|
+
// Common CodeRabbit format: "17 to 31"
|
|
4
|
+
const m = s.match(/^(\d+)\s+to\s+(\d+)$/i);
|
|
5
|
+
if (m) return { start: Number(m[1]), end: Number(m[2]) };
|
|
6
|
+
const n = s.match(/^(\d+)$/);
|
|
7
|
+
if (n) {
|
|
8
|
+
const v = Number(n[1]);
|
|
9
|
+
return { start: v, end: v };
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseCodeRabbitPlainOutput(text) {
|
|
15
|
+
const lines = String(text ?? '').split('\n');
|
|
16
|
+
const findings = [];
|
|
17
|
+
|
|
18
|
+
let current = null;
|
|
19
|
+
let mode = null; // 'comment' | 'prompt' | null
|
|
20
|
+
|
|
21
|
+
function flush() {
|
|
22
|
+
if (!current) return;
|
|
23
|
+
const comment = (current._commentLines ?? []).join('\n').trim();
|
|
24
|
+
const prompt = (current._promptLines ?? []).join('\n').trim();
|
|
25
|
+
const title =
|
|
26
|
+
current.title ??
|
|
27
|
+
comment
|
|
28
|
+
.split('\n')
|
|
29
|
+
.map((l) => l.trim())
|
|
30
|
+
.filter(Boolean)[0] ??
|
|
31
|
+
'';
|
|
32
|
+
|
|
33
|
+
findings.push({
|
|
34
|
+
reviewer: 'coderabbit',
|
|
35
|
+
file: current.file ?? '',
|
|
36
|
+
lines: current.lines ?? null,
|
|
37
|
+
type: current.type ?? '',
|
|
38
|
+
title,
|
|
39
|
+
comment,
|
|
40
|
+
prompt: prompt || null,
|
|
41
|
+
});
|
|
42
|
+
current = null;
|
|
43
|
+
mode = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
47
|
+
const line = lines[i];
|
|
48
|
+
// The raw log files written by the review runner prefix each line with "[label] ".
|
|
49
|
+
// Strip that prefix so we can parse both prefixed logs and unprefixed stdout.
|
|
50
|
+
const trimmed = line.trimEnd().replace(/^\[[^\]]+\]\s*/g, '');
|
|
51
|
+
|
|
52
|
+
if (trimmed.startsWith('============================================================================')) {
|
|
53
|
+
flush();
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (trimmed.startsWith('File: ')) {
|
|
57
|
+
flush();
|
|
58
|
+
current = { _commentLines: [], _promptLines: [] };
|
|
59
|
+
current.file = trimmed.slice('File: '.length).trim();
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!current) continue;
|
|
63
|
+
|
|
64
|
+
if (trimmed.startsWith('Line: ')) {
|
|
65
|
+
const range = parseLineRange(trimmed.slice('Line: '.length).trim());
|
|
66
|
+
current.lines = range;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (trimmed.startsWith('Type: ')) {
|
|
70
|
+
current.type = trimmed.slice('Type: '.length).trim();
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (trimmed === 'Comment:') {
|
|
74
|
+
mode = 'comment';
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (trimmed === 'Prompt for AI Agent:') {
|
|
78
|
+
mode = 'prompt';
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (mode === 'comment') {
|
|
83
|
+
// Title is first non-empty comment line.
|
|
84
|
+
if (!current.title && trimmed.trim()) current.title = trimmed.trim();
|
|
85
|
+
current._commentLines.push(trimmed);
|
|
86
|
+
} else if (mode === 'prompt') {
|
|
87
|
+
current._promptLines.push(trimmed);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
flush();
|
|
92
|
+
// Drop empty placeholders
|
|
93
|
+
return findings.filter((f) => f.file && f.title);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseCodexReviewText(reviewText) {
|
|
97
|
+
const s = String(reviewText ?? '');
|
|
98
|
+
const marker = '===FINDINGS_JSON===';
|
|
99
|
+
const idx = s.indexOf(marker);
|
|
100
|
+
if (idx >= 0) {
|
|
101
|
+
let jsonText = s.slice(idx + marker.length).trim();
|
|
102
|
+
if (!jsonText) return [];
|
|
103
|
+
|
|
104
|
+
// The raw log files written by the review runner prefix each line with "[label] ".
|
|
105
|
+
// Strip that prefix so we can parse both prefixed logs and unprefixed stdout.
|
|
106
|
+
jsonText = jsonText
|
|
107
|
+
.split('\n')
|
|
108
|
+
.map((line) => String(line ?? '').replace(/^\[[^\]]+\]\s*/g, ''))
|
|
109
|
+
.join('\n')
|
|
110
|
+
.trim();
|
|
111
|
+
|
|
112
|
+
// Some reviewers wrap the JSON in a fenced code block:
|
|
113
|
+
// ===FINDINGS_JSON===
|
|
114
|
+
// ```json
|
|
115
|
+
// [...]
|
|
116
|
+
// ```
|
|
117
|
+
//
|
|
118
|
+
// Strip the outer fence so JSON.parse can succeed.
|
|
119
|
+
const fence = jsonText.match(/^```[a-z0-9_-]*\s*\n([\s\S]*?)\n```/i);
|
|
120
|
+
if (fence?.[1]) jsonText = fence[1].trim();
|
|
121
|
+
|
|
122
|
+
let parsed;
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(jsonText);
|
|
125
|
+
} catch {
|
|
126
|
+
parsed = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Some tools append non-JSON metadata after the array (e.g. "Request ID: ...").
|
|
130
|
+
// As a last resort, try to parse the first top-level JSON array substring.
|
|
131
|
+
if (!Array.isArray(parsed)) {
|
|
132
|
+
const firstBracket = jsonText.indexOf('[');
|
|
133
|
+
const lastBracket = jsonText.lastIndexOf(']');
|
|
134
|
+
if (firstBracket >= 0 && lastBracket > firstBracket) {
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(jsonText.slice(firstBracket, lastBracket + 1));
|
|
137
|
+
} catch {
|
|
138
|
+
// ignore
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (Array.isArray(parsed)) {
|
|
143
|
+
return parsed
|
|
144
|
+
.map((x) => ({
|
|
145
|
+
reviewer: 'codex',
|
|
146
|
+
severity: x?.severity ?? null,
|
|
147
|
+
file: x?.file ?? null,
|
|
148
|
+
lines: x?.lines ?? null,
|
|
149
|
+
title: x?.title ?? null,
|
|
150
|
+
recommendation: x?.recommendation ?? null,
|
|
151
|
+
needsDiscussion: Boolean(x?.needsDiscussion),
|
|
152
|
+
}))
|
|
153
|
+
.filter((x) => x.file && x.title);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Fallback: Codex sometimes returns a human-readable list like:
|
|
158
|
+
// - [P2] Thing — /abs/path/.project/review-worktrees/codex-.../cli/src/foo.ts:10-12
|
|
159
|
+
//
|
|
160
|
+
// Parse these into structured findings so they appear in triage even when the
|
|
161
|
+
// JSON trailer is missing.
|
|
162
|
+
const priorityToSeverity = { 1: 'blocker', 2: 'major', 3: 'minor', 4: 'nit' };
|
|
163
|
+
const lines = s.split('\n');
|
|
164
|
+
const findings = [];
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
|
|
167
|
+
function stripPrefix(line) {
|
|
168
|
+
return String(line ?? '').replace(/^\[[^\]]+\]\s*/g, '').trim();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalizePath(rawPath) {
|
|
172
|
+
const p = String(rawPath ?? '').trim();
|
|
173
|
+
const marker2 = '/.project/review-worktrees/';
|
|
174
|
+
const i = p.indexOf(marker2);
|
|
175
|
+
if (i < 0) return p;
|
|
176
|
+
const rest = p.slice(i + marker2.length);
|
|
177
|
+
const slash = rest.indexOf('/');
|
|
178
|
+
if (slash < 0) return p;
|
|
179
|
+
return rest.slice(slash + 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const rawLine of lines) {
|
|
183
|
+
const line = stripPrefix(rawLine);
|
|
184
|
+
const m = line.match(/^- \[P([1-4])\]\s+(.+?)\s+—\s+(.+)$/);
|
|
185
|
+
if (!m) continue;
|
|
186
|
+
|
|
187
|
+
const priority = Number(m[1]);
|
|
188
|
+
const title = String(m[2]).trim();
|
|
189
|
+
const pathPart = String(m[3]).trim();
|
|
190
|
+
|
|
191
|
+
let file = pathPart;
|
|
192
|
+
let range = null;
|
|
193
|
+
|
|
194
|
+
const lastColon = pathPart.lastIndexOf(':');
|
|
195
|
+
if (lastColon > 0) {
|
|
196
|
+
const suffix = pathPart.slice(lastColon + 1).trim();
|
|
197
|
+
const rm = suffix.match(/^(\d+)(?:-(\d+))?$/);
|
|
198
|
+
if (rm) {
|
|
199
|
+
const start = Number(rm[1]);
|
|
200
|
+
const end = Number(rm[2] ?? rm[1]);
|
|
201
|
+
range = { start, end };
|
|
202
|
+
file = pathPart.slice(0, lastColon);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const normalizedFile = normalizePath(file);
|
|
207
|
+
const severity = priorityToSeverity[priority] ?? null;
|
|
208
|
+
const key = `${normalizedFile}:${range?.start ?? ''}-${range?.end ?? ''}:${title}`;
|
|
209
|
+
if (seen.has(key)) continue;
|
|
210
|
+
seen.add(key);
|
|
211
|
+
findings.push({
|
|
212
|
+
reviewer: 'codex',
|
|
213
|
+
severity,
|
|
214
|
+
file: normalizedFile,
|
|
215
|
+
lines: range,
|
|
216
|
+
title,
|
|
217
|
+
recommendation: null,
|
|
218
|
+
needsDiscussion: false,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return findings.filter((x) => x.file && x.title);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function formatTriageMarkdown({ runLabel, baseRef, findings }) {
|
|
226
|
+
const items = Array.isArray(findings) ? findings : [];
|
|
227
|
+
const header = [
|
|
228
|
+
`# Review triage: ${runLabel}`,
|
|
229
|
+
'',
|
|
230
|
+
`- Base ref: ${baseRef ?? ''}`,
|
|
231
|
+
`- Findings: ${items.length}`,
|
|
232
|
+
'',
|
|
233
|
+
'## Trust checklist (READ THIS FIRST)',
|
|
234
|
+
'',
|
|
235
|
+
'Before you act on reviewer output:',
|
|
236
|
+
'1) Load this file into your context (human/LLM) so you follow the workflow end-to-end.',
|
|
237
|
+
'2) Treat every suggestion as a suggestion: verify against best practices + project invariants.',
|
|
238
|
+
'3) If you are unsure, do not apply; mark **Needs discussion** and capture rationale.',
|
|
239
|
+
'4) Do not skip nits by default: apply them when they improve long-term maintainability without risk.',
|
|
240
|
+
'5) Use web search sparingly when needed to validate best practices, but prefer primary sources/docs.',
|
|
241
|
+
'',
|
|
242
|
+
'## Mandatory workflow',
|
|
243
|
+
'',
|
|
244
|
+
'For each finding below:',
|
|
245
|
+
'1) Open the referenced file/lines in the *validation worktree* (committed-only).',
|
|
246
|
+
'2) Decide if it is a real bug/risk/correctness gap, already fixed, expected behavior, or style preference.',
|
|
247
|
+
'3) Record a final decision + rationale here (`apply` / `adjust` / `defer`).',
|
|
248
|
+
'4) If `apply/adjust`: implement in the main worktree as a clean commit (no unrelated changes), then sync that commit to validation.',
|
|
249
|
+
'',
|
|
250
|
+
'Notes:',
|
|
251
|
+
'- Treat reviewer output as suggestions; verify against best practices and codebase invariants before applying.',
|
|
252
|
+
'- Avoid brittle tests that assert on wording/phrasing/config; test observable behavior.',
|
|
253
|
+
'',
|
|
254
|
+
].join('\n');
|
|
255
|
+
|
|
256
|
+
const body = items
|
|
257
|
+
.map((f) => {
|
|
258
|
+
const lines = f.lines?.start ? `${f.lines.start}-${f.lines.end ?? f.lines.start}` : '';
|
|
259
|
+
const meta = [
|
|
260
|
+
`- [ ] \`${f.id ?? ''}\` reviewer=\`${f.reviewer ?? ''}\`${f.severity ? ` severity=\`${f.severity}\`` : ''}${
|
|
261
|
+
f.type ? ` type=\`${f.type}\`` : ''
|
|
262
|
+
} \`${f.file ?? ''}\`${lines ? ` (lines ${lines})` : ''}: ${f.title ?? ''}`,
|
|
263
|
+
f.sourceLog ? ` - Source log: \`${f.sourceLog}\`` : null,
|
|
264
|
+
' - Final decision: **TBD** (apply|adjust|defer)',
|
|
265
|
+
' - Verified in validation worktree: **TBD**',
|
|
266
|
+
' - Rationale: **TBD**',
|
|
267
|
+
' - Action taken: **TBD**',
|
|
268
|
+
' - Commit: **TBD**',
|
|
269
|
+
' - Needs discussion: **TBD**',
|
|
270
|
+
];
|
|
271
|
+
if (f.comment) meta.push(` - Reviewer detail: ${String(f.comment).split('\n')[0].trim()}`);
|
|
272
|
+
if (f.recommendation) meta.push(` - Reviewer suggested fix: ${String(f.recommendation).split('\n')[0].trim()}`);
|
|
273
|
+
return meta.filter(Boolean).join('\n');
|
|
274
|
+
})
|
|
275
|
+
.join('\n\n');
|
|
276
|
+
|
|
277
|
+
return `${header}${body ? `${body}\n` : ''}`;
|
|
278
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { formatTriageMarkdown, parseCodeRabbitPlainOutput, parseCodexReviewText } from './findings.mjs';
|
|
5
|
+
|
|
6
|
+
function joinLines(lines) {
|
|
7
|
+
return lines.join('\n');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function makeCodeRabbitBlock({ file, line, type, commentLines = [], promptLines = [] }) {
|
|
11
|
+
const lines = [
|
|
12
|
+
'============================================================================',
|
|
13
|
+
`File: ${file}`,
|
|
14
|
+
`Line: ${line}`,
|
|
15
|
+
`Type: ${type}`,
|
|
16
|
+
'',
|
|
17
|
+
'Comment:',
|
|
18
|
+
...commentLines,
|
|
19
|
+
];
|
|
20
|
+
if (promptLines.length) {
|
|
21
|
+
lines.push('', 'Prompt for AI Agent:', ...promptLines);
|
|
22
|
+
}
|
|
23
|
+
return lines;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeCodexFindingsJsonBlock(findings, { fenced = false } = {}) {
|
|
27
|
+
const json = JSON.stringify(findings, null, 2);
|
|
28
|
+
if (!fenced) return ['===FINDINGS_JSON===', json];
|
|
29
|
+
return ['===FINDINGS_JSON===', '```json', json, '```'];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function withLabelPrefix(lines, label) {
|
|
33
|
+
return lines.map((line) => `${label}${line}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('parseCodeRabbitPlainOutput parses CodeRabbit plain blocks', () => {
|
|
37
|
+
const out = joinLines([
|
|
38
|
+
...makeCodeRabbitBlock({
|
|
39
|
+
file: 'apps/cli/src/utils/spawnHappyCLI.invocation.test.ts',
|
|
40
|
+
line: '17 to 31',
|
|
41
|
+
type: 'potential_issue',
|
|
42
|
+
commentLines: ['Dynamic imports may be cached, causing test isolation issues.', '', 'Some more details.'],
|
|
43
|
+
promptLines: ['Do the thing.'],
|
|
44
|
+
}),
|
|
45
|
+
...makeCodeRabbitBlock({
|
|
46
|
+
file: 'apps/ui/sources/app/(app)/_layout.tsx',
|
|
47
|
+
line: '29 to 35',
|
|
48
|
+
type: 'potential_issue',
|
|
49
|
+
commentLines: ['Hooks order violation: useUnistyles() called after conditional return.', '', 'More details.'],
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const findings = parseCodeRabbitPlainOutput(out);
|
|
54
|
+
assert.equal(findings.length, 2);
|
|
55
|
+
assert.equal(findings[0].file, 'apps/cli/src/utils/spawnHappyCLI.invocation.test.ts');
|
|
56
|
+
assert.deepEqual(findings[0].lines, { start: 17, end: 31 });
|
|
57
|
+
assert.equal(findings[0].type, 'potential_issue');
|
|
58
|
+
assert.equal(findings[0].title, 'Dynamic imports may be cached, causing test isolation issues.');
|
|
59
|
+
assert.match(findings[0].comment, /Some more details/);
|
|
60
|
+
assert.match(findings[0].prompt, /Do the thing/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('parseCodeRabbitPlainOutput supports log-prefixed lines and single-line range', () => {
|
|
64
|
+
const label = '[monorepo:coderabbit:1/3] ';
|
|
65
|
+
const out = joinLines(
|
|
66
|
+
withLabelPrefix(
|
|
67
|
+
makeCodeRabbitBlock({
|
|
68
|
+
file: 'apps/stack/scripts/review.mjs',
|
|
69
|
+
line: '42',
|
|
70
|
+
type: 'nit',
|
|
71
|
+
commentLines: ['Prefer a clearer constant name.'],
|
|
72
|
+
}),
|
|
73
|
+
label
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const findings = parseCodeRabbitPlainOutput(out);
|
|
78
|
+
assert.equal(findings.length, 1);
|
|
79
|
+
assert.equal(findings[0].file, 'apps/stack/scripts/review.mjs');
|
|
80
|
+
assert.deepEqual(findings[0].lines, { start: 42, end: 42 });
|
|
81
|
+
assert.equal(findings[0].type, 'nit');
|
|
82
|
+
assert.equal(findings[0].title, 'Prefer a clearer constant name.');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('parseCodexReviewText extracts findings JSON trailer', () => {
|
|
86
|
+
const review = joinLines([
|
|
87
|
+
'Overall verdict: looks good.',
|
|
88
|
+
'',
|
|
89
|
+
...makeCodexFindingsJsonBlock([
|
|
90
|
+
{
|
|
91
|
+
severity: 'major',
|
|
92
|
+
file: 'apps/server/sources/main.light.ts',
|
|
93
|
+
title: 'Do not exit after startup',
|
|
94
|
+
recommendation: 'Remove process.exit(0) on success.',
|
|
95
|
+
},
|
|
96
|
+
]),
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const findings = parseCodexReviewText(review);
|
|
100
|
+
assert.equal(findings.length, 1);
|
|
101
|
+
assert.equal(findings[0].file, 'apps/server/sources/main.light.ts');
|
|
102
|
+
assert.equal(findings[0].severity, 'major');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('parseCodexReviewText extracts findings JSON trailer even when fenced', () => {
|
|
106
|
+
const review = joinLines(['All good.', '', ...makeCodexFindingsJsonBlock([
|
|
107
|
+
{
|
|
108
|
+
severity: 'minor',
|
|
109
|
+
file: 'cli/src/foo.ts',
|
|
110
|
+
title: 'Prefer explicit return type',
|
|
111
|
+
recommendation: 'Add an explicit return type for clarity.',
|
|
112
|
+
},
|
|
113
|
+
], { fenced: true })]);
|
|
114
|
+
|
|
115
|
+
const findings = parseCodexReviewText(review);
|
|
116
|
+
assert.equal(findings.length, 1);
|
|
117
|
+
assert.equal(findings[0].file, 'cli/src/foo.ts');
|
|
118
|
+
assert.equal(findings[0].severity, 'minor');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('parseCodexReviewText extracts findings JSON trailer when lines are log-prefixed', () => {
|
|
122
|
+
const label = '[monorepo:augment:4/39] ';
|
|
123
|
+
const prefixed = withLabelPrefix(
|
|
124
|
+
makeCodexFindingsJsonBlock(
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
severity: 'major',
|
|
128
|
+
file: 'cli/src/x.ts',
|
|
129
|
+
title: 'Fix thing',
|
|
130
|
+
recommendation: 'Do it.',
|
|
131
|
+
needsDiscussion: false,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
{ fenced: true }
|
|
135
|
+
).concat(['', 'Request ID: abc']),
|
|
136
|
+
label
|
|
137
|
+
);
|
|
138
|
+
const review = joinLines([`${label}some preamble`, ...prefixed]);
|
|
139
|
+
|
|
140
|
+
const findings = parseCodexReviewText(review);
|
|
141
|
+
assert.equal(findings.length, 1);
|
|
142
|
+
assert.equal(findings[0].file, 'cli/src/x.ts');
|
|
143
|
+
assert.equal(findings[0].severity, 'major');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('parseCodexReviewText falls back to parsing [P#] bullet lines', () => {
|
|
147
|
+
const review = joinLines([
|
|
148
|
+
'[monorepo:codex:2/21] Review comment:',
|
|
149
|
+
'[monorepo:codex:2/21] - [P1] Fix thing one — /Users/me/repo/.project/review-worktrees/codex-2-of-21-abc/apps/cli/src/foo.ts:10-12',
|
|
150
|
+
'[monorepo:codex:2/21] - [P3] Fix thing two — /Users/me/repo/.project/review-worktrees/codex-2-of-21-abc/apps/ui/sources/bar.tsx:7',
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const findings = parseCodexReviewText(review);
|
|
154
|
+
assert.equal(findings.length, 2);
|
|
155
|
+
assert.equal(findings[0].file, 'apps/cli/src/foo.ts');
|
|
156
|
+
assert.deepEqual(findings[0].lines, { start: 10, end: 12 });
|
|
157
|
+
assert.equal(findings[0].severity, 'blocker');
|
|
158
|
+
assert.equal(findings[0].title, 'Fix thing one');
|
|
159
|
+
assert.equal(findings[1].file, 'apps/ui/sources/bar.tsx');
|
|
160
|
+
assert.deepEqual(findings[1].lines, { start: 7, end: 7 });
|
|
161
|
+
assert.equal(findings[1].severity, 'minor');
|
|
162
|
+
assert.equal(findings[1].title, 'Fix thing two');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('parseCodexReviewText falls back when marker exists but JSON is missing/invalid', () => {
|
|
166
|
+
const review = joinLines([
|
|
167
|
+
'instructions...',
|
|
168
|
+
'===FINDINGS_JSON===',
|
|
169
|
+
'this is not json',
|
|
170
|
+
'[monorepo:codex:2/21] - [P2] Fix thing — /Users/me/repo/.project/review-worktrees/codex-2-of-21-abc/apps/server/src/x.ts:1-2',
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const findings = parseCodexReviewText(review);
|
|
174
|
+
assert.equal(findings.length, 1);
|
|
175
|
+
assert.equal(findings[0].file, 'apps/server/src/x.ts');
|
|
176
|
+
assert.deepEqual(findings[0].lines, { start: 1, end: 2 });
|
|
177
|
+
assert.equal(findings[0].severity, 'major');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('parseCodexReviewText returns empty list when no findings marker/bullets exist', () => {
|
|
181
|
+
const findings = parseCodexReviewText('No actionable findings.');
|
|
182
|
+
assert.deepEqual(findings, []);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('formatTriageMarkdown includes required workflow fields', () => {
|
|
186
|
+
const md = formatTriageMarkdown({
|
|
187
|
+
runLabel: 'review-123',
|
|
188
|
+
baseRef: 'upstream/main',
|
|
189
|
+
findings: [
|
|
190
|
+
{
|
|
191
|
+
reviewer: 'coderabbit',
|
|
192
|
+
id: 'CR-001',
|
|
193
|
+
file: 'cli/src/x.ts',
|
|
194
|
+
title: 'Thing',
|
|
195
|
+
type: 'potential_issue',
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
});
|
|
199
|
+
assert.match(md, /Trust checklist/i);
|
|
200
|
+
assert.match(md, /Final decision: \*\*TBD\*\*/);
|
|
201
|
+
assert.match(md, /Verified in validation worktree:/);
|
|
202
|
+
assert.match(md, /Commit:/);
|
|
203
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { runCapture } from '../proc/proc.mjs';
|
|
2
|
+
import { parseNameStatusZ } from '../git/parse_name_status_z.mjs';
|
|
3
|
+
|
|
4
|
+
function normalizePath(p) {
|
|
5
|
+
return String(p ?? '').replace(/\\/g, '/').replace(/^\/+/, '');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function getChangedOps({ cwd, baseRef, headRef = 'HEAD', env = process.env } = {}) {
|
|
9
|
+
const out = await runCapture('git', ['diff', '--name-status', '--find-renames', '-z', `${baseRef}...${headRef}`], { cwd, env });
|
|
10
|
+
const entries = parseNameStatusZ(out);
|
|
11
|
+
const checkout = new Set();
|
|
12
|
+
const remove = new Set();
|
|
13
|
+
for (const e of entries) {
|
|
14
|
+
if (e.code === 'A' || e.code === 'M' || e.code === 'T') {
|
|
15
|
+
checkout.add(normalizePath(e.path));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (e.code === 'D') {
|
|
19
|
+
remove.add(normalizePath(e.path));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (e.code === 'R' || e.code === 'C') {
|
|
23
|
+
if (e.from) remove.add(normalizePath(e.from));
|
|
24
|
+
if (e.to) checkout.add(normalizePath(e.to));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const all = new Set([...checkout, ...remove]);
|
|
29
|
+
return { checkout, remove, all };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function subset(set, allowed) {
|
|
33
|
+
const out = new Set();
|
|
34
|
+
for (const v of set) {
|
|
35
|
+
if (allowed.has(v)) out.add(v);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function difference(set, blocked) {
|
|
41
|
+
const out = new Set();
|
|
42
|
+
for (const v of set) {
|
|
43
|
+
if (!blocked.has(v)) out.add(v);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function batched(args, batchSize, fn) {
|
|
49
|
+
const list = Array.from(args);
|
|
50
|
+
for (let i = 0; i < list.length; i += batchSize) {
|
|
51
|
+
// eslint-disable-next-line no-await-in-loop
|
|
52
|
+
await fn(list.slice(i, i + batchSize));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function gitCommit({ cwd, env, message }) {
|
|
57
|
+
await runCapture(
|
|
58
|
+
'git',
|
|
59
|
+
[
|
|
60
|
+
'-c',
|
|
61
|
+
'user.name=Happier Review',
|
|
62
|
+
'-c',
|
|
63
|
+
'user.email=review@happier.local',
|
|
64
|
+
'-c',
|
|
65
|
+
'commit.gpgsign=false',
|
|
66
|
+
'commit',
|
|
67
|
+
'-q',
|
|
68
|
+
'--no-verify',
|
|
69
|
+
'-m',
|
|
70
|
+
message,
|
|
71
|
+
],
|
|
72
|
+
{ cwd, env }
|
|
73
|
+
);
|
|
74
|
+
const sha = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd, env })).trim();
|
|
75
|
+
return sha;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function applyOpsFromHead({ cwd, env, headCommit, checkoutPaths, removePaths }) {
|
|
79
|
+
if (removePaths.size) {
|
|
80
|
+
await batched(Array.from(removePaths), 200, async (batch) => {
|
|
81
|
+
await runCapture('git', ['rm', '-q', '--ignore-unmatch', '--', ...batch], { cwd, env });
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (checkoutPaths.size) {
|
|
85
|
+
// Prefer batching over pathspec-from-file to maximize compatibility.
|
|
86
|
+
await batched(Array.from(checkoutPaths), 100, async (batch) => {
|
|
87
|
+
await runCapture('git', ['checkout', headCommit, '--', ...batch], { cwd, env });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Stage all changes introduced by the operations.
|
|
91
|
+
await runCapture('git', ['add', '-A'], { cwd, env });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create two local commits inside an ephemeral worktree:
|
|
96
|
+
* - baseSliceCommit: baseRef plus all NON-slice changes from headCommit
|
|
97
|
+
* - headSliceCommit: baseSliceCommit plus slice changes from headCommit (resulting tree equals headCommit)
|
|
98
|
+
*
|
|
99
|
+
* These commits are intended solely for review tooling (CodeRabbit/Codex) so the reviewer sees:
|
|
100
|
+
* - full, final code at HEAD
|
|
101
|
+
* - a focused diff for the slice (baseSliceCommit..headSliceCommit)
|
|
102
|
+
*/
|
|
103
|
+
export async function createHeadSliceCommits({
|
|
104
|
+
cwd,
|
|
105
|
+
env = process.env,
|
|
106
|
+
baseRef,
|
|
107
|
+
headCommit,
|
|
108
|
+
ops,
|
|
109
|
+
slicePaths,
|
|
110
|
+
label = 'slice',
|
|
111
|
+
} = {}) {
|
|
112
|
+
const sliceSet = new Set((Array.isArray(slicePaths) ? slicePaths : []).map(normalizePath).filter(Boolean));
|
|
113
|
+
const sliceCheckout = subset(ops.checkout, sliceSet);
|
|
114
|
+
const sliceRemove = subset(ops.remove, sliceSet);
|
|
115
|
+
const nonSliceCheckout = difference(ops.checkout, sliceSet);
|
|
116
|
+
const nonSliceRemove = difference(ops.remove, sliceSet);
|
|
117
|
+
|
|
118
|
+
// Start from baseRef.
|
|
119
|
+
await runCapture('git', ['checkout', '-q', '--detach', baseRef], { cwd, env });
|
|
120
|
+
|
|
121
|
+
// Commit non-slice changes.
|
|
122
|
+
await applyOpsFromHead({ cwd, env, headCommit, checkoutPaths: nonSliceCheckout, removePaths: nonSliceRemove });
|
|
123
|
+
const baseSliceCommit = await gitCommit({ cwd, env, message: `chore(review): base for ${label}` });
|
|
124
|
+
|
|
125
|
+
// Commit slice changes.
|
|
126
|
+
await applyOpsFromHead({ cwd, env, headCommit, checkoutPaths: sliceCheckout, removePaths: sliceRemove });
|
|
127
|
+
const headSliceCommit = await gitCommit({ cwd, env, message: `chore(review): ${label}` });
|
|
128
|
+
|
|
129
|
+
// Ensure working tree is at the head slice commit for downstream tools.
|
|
130
|
+
await runCapture('git', ['checkout', '-q', headSliceCommit], { cwd, env });
|
|
131
|
+
return { baseSliceCommit, headSliceCommit };
|
|
132
|
+
}
|