happy-stacks 0.4.0 → 0.5.0

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 (104) hide show
  1. package/README.md +64 -33
  2. package/bin/happys.mjs +44 -1
  3. package/docs/codex-mcp-resume.md +130 -0
  4. package/docs/commit-audits/happy/leeroy-wip.commit-analysis.md +17640 -0
  5. package/docs/commit-audits/happy/leeroy-wip.commit-export.fuller-stat.md +3845 -0
  6. package/docs/commit-audits/happy/leeroy-wip.commit-inventory.md +102 -0
  7. package/docs/commit-audits/happy/leeroy-wip.commit-manual-review.md +1452 -0
  8. package/docs/commit-audits/happy/leeroy-wip.manual-review-queue.md +116 -0
  9. package/docs/happy-development.md +1 -2
  10. package/docs/monorepo-migration.md +286 -0
  11. package/docs/server-flavors.md +19 -3
  12. package/docs/stacks.md +35 -0
  13. package/package.json +1 -1
  14. package/scripts/auth.mjs +21 -3
  15. package/scripts/build.mjs +1 -1
  16. package/scripts/dev.mjs +20 -7
  17. package/scripts/doctor.mjs +0 -4
  18. package/scripts/edison.mjs +2 -2
  19. package/scripts/env.mjs +150 -0
  20. package/scripts/env_cmd.test.mjs +128 -0
  21. package/scripts/init.mjs +5 -2
  22. package/scripts/install.mjs +99 -57
  23. package/scripts/migrate.mjs +3 -12
  24. package/scripts/monorepo.mjs +1096 -0
  25. package/scripts/monorepo_port.test.mjs +1470 -0
  26. package/scripts/review.mjs +715 -24
  27. package/scripts/review_pr.mjs +5 -20
  28. package/scripts/run.mjs +21 -15
  29. package/scripts/setup.mjs +147 -25
  30. package/scripts/setup_pr.mjs +19 -28
  31. package/scripts/stack.mjs +493 -157
  32. package/scripts/stack_archive_cmd.test.mjs +91 -0
  33. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +65 -0
  34. package/scripts/stack_env_cmd.test.mjs +87 -0
  35. package/scripts/stack_happy_cmd.test.mjs +126 -0
  36. package/scripts/stack_interactive_monorepo_group.test.mjs +71 -0
  37. package/scripts/stack_monorepo_defaults.test.mjs +62 -0
  38. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +66 -0
  39. package/scripts/stack_server_flavors_defaults.test.mjs +55 -0
  40. package/scripts/stack_shorthand_cmd.test.mjs +55 -0
  41. package/scripts/stack_wt_list.test.mjs +128 -0
  42. package/scripts/tui.mjs +88 -2
  43. package/scripts/utils/cli/cli_registry.mjs +20 -5
  44. package/scripts/utils/cli/cwd_scope.mjs +56 -2
  45. package/scripts/utils/cli/cwd_scope.test.mjs +40 -7
  46. package/scripts/utils/cli/prereqs.mjs +8 -5
  47. package/scripts/utils/cli/prereqs.test.mjs +34 -0
  48. package/scripts/utils/cli/wizard.mjs +17 -9
  49. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +60 -0
  50. package/scripts/utils/dev/daemon.mjs +14 -1
  51. package/scripts/utils/dev/expo_dev.mjs +188 -4
  52. package/scripts/utils/dev/server.mjs +21 -17
  53. package/scripts/utils/edison/git_roots.mjs +29 -0
  54. package/scripts/utils/edison/git_roots.test.mjs +36 -0
  55. package/scripts/utils/env/env.mjs +7 -3
  56. package/scripts/utils/env/env_file.mjs +4 -2
  57. package/scripts/utils/env/env_file.test.mjs +44 -0
  58. package/scripts/utils/git/worktrees.mjs +63 -12
  59. package/scripts/utils/git/worktrees_monorepo.test.mjs +54 -0
  60. package/scripts/utils/net/tcp_forward.mjs +162 -0
  61. package/scripts/utils/paths/paths.mjs +118 -3
  62. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  63. package/scripts/utils/paths/paths_server_flavors.test.mjs +45 -0
  64. package/scripts/utils/proc/commands.mjs +2 -3
  65. package/scripts/utils/proc/pm.mjs +113 -16
  66. package/scripts/utils/proc/pm_spawn.test.mjs +76 -0
  67. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +142 -0
  68. package/scripts/utils/proc/proc.mjs +68 -10
  69. package/scripts/utils/proc/proc.test.mjs +77 -0
  70. package/scripts/utils/review/chunks.mjs +55 -0
  71. package/scripts/utils/review/chunks.test.mjs +51 -0
  72. package/scripts/utils/review/findings.mjs +165 -0
  73. package/scripts/utils/review/findings.test.mjs +85 -0
  74. package/scripts/utils/review/head_slice.mjs +153 -0
  75. package/scripts/utils/review/head_slice.test.mjs +91 -0
  76. package/scripts/utils/review/instructions/deep.md +20 -0
  77. package/scripts/utils/review/runners/coderabbit.mjs +56 -14
  78. package/scripts/utils/review/runners/coderabbit.test.mjs +59 -0
  79. package/scripts/utils/review/runners/codex.mjs +32 -22
  80. package/scripts/utils/review/runners/codex.test.mjs +35 -0
  81. package/scripts/utils/review/slices.mjs +140 -0
  82. package/scripts/utils/review/slices.test.mjs +32 -0
  83. package/scripts/utils/server/flavor_scripts.mjs +98 -0
  84. package/scripts/utils/server/flavor_scripts.test.mjs +146 -0
  85. package/scripts/utils/server/prisma_import.mjs +37 -0
  86. package/scripts/utils/server/prisma_import.test.mjs +70 -0
  87. package/scripts/utils/server/ui_env.mjs +14 -0
  88. package/scripts/utils/server/ui_env.test.mjs +46 -0
  89. package/scripts/utils/server/validate.mjs +53 -16
  90. package/scripts/utils/server/validate.test.mjs +89 -0
  91. package/scripts/utils/stack/editor_workspace.mjs +4 -4
  92. package/scripts/utils/stack/interactive_stack_config.mjs +185 -0
  93. package/scripts/utils/stack/startup.mjs +113 -13
  94. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +64 -0
  95. package/scripts/utils/stack/startup_server_light_generate.test.mjs +70 -0
  96. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +88 -0
  97. package/scripts/utils/tailscale/ip.mjs +116 -0
  98. package/scripts/utils/ui/ansi.mjs +39 -0
  99. package/scripts/where.mjs +2 -2
  100. package/scripts/worktrees.mjs +627 -137
  101. package/scripts/worktrees_archive_cmd.test.mjs +245 -0
  102. package/scripts/worktrees_cursor_monorepo_root.test.mjs +63 -0
  103. package/scripts/worktrees_list_specs_no_recurse.test.mjs +33 -0
  104. package/scripts/worktrees_monorepo_use_group.test.mjs +67 -0
@@ -0,0 +1,116 @@
1
+ # leeroy-wip manual review queue (oldest → newest)
2
+
3
+ Generated: 2026-01-24
4
+ Source: docs/commit-audits/happy/leeroy-wip.commit-analysis.md
5
+
6
+ Commits flagged for manual review: 71
7
+
8
+ ## Bucket counts
9
+
10
+ - `codex`: 9
11
+ - `cli`: 7
12
+ - `sync`: 5
13
+ - `i18n`: 4
14
+ - `new-session`: 4
15
+ - `claude`: 3
16
+ - `expo-app`: 3
17
+ - `secrets`: 3
18
+ - `tools`: 3
19
+ - `ui`: 3
20
+ - `agent-input`: 2
21
+ - `auth`: 2
22
+ - `permission`: 2
23
+ - `profiles`: 2
24
+ - `resume`: 2
25
+ - `server`: 2
26
+ - `app`: 1
27
+ - `crypto`: 1
28
+ - `experiments`: 1
29
+ - `fork`: 1
30
+ - `happy-cli`: 1
31
+ - `new`: 1
32
+ - `pr107`: 1
33
+ - `reducer`: 1
34
+ - `rpc`: 1
35
+ - `security`: 1
36
+ - `server-light`: 1
37
+ - `settings`: 1
38
+ - `terminal`: 1
39
+ - `tmux`: 1
40
+ - `typecheck`: 1
41
+
42
+ ## Queue
43
+
44
+ Each entry is: `index date short-sha bucket — subject — reasons`
45
+
46
+ - 001 2026-01-17 58528a7c37d3 `crypto` — chore(crypto): patch react-native-libsodium — YES (safety/security-sensitive area)
47
+ - 004 2026-01-17 4890a471df31 `auth` — fix(auth): harden tokenStorage web persistence — YES (safety/security-sensitive area)
48
+ - 005 2026-01-17 4adc41d92af0 `sync` — feat(sync): add permission mode types and mapping — YES (safety/security-sensitive area)
49
+ - 017 2026-01-17 65bae55a0b61 `i18n` — refactor(i18n): separate translation types and content — YES (diff summary skipped (large change (+1756/-1006)))
50
+ - 018 2026-01-17 2993b5c860ff `new-session` — fix(new-session): restore standard modal flow — YES (diff summary skipped (large change (+3142/-1629)))
51
+ - 019 2026-01-17 9e08047d2478 `profiles` — fix(profiles): harden routing, grouping, and editing — YES (diff summary skipped (large change (+1319/-857)))
52
+ - 020 2026-01-17 8d9f56e85e5a `ui` — refactor(ui): unify list selectors and modal primitives — YES (diff summary skipped (too many files (28)); safety/security-sensitive area)
53
+ - 022 2026-01-17 2d4675a5d051 `agent-input` — fix(agent-input): use compact permission badges — YES (safety/security-sensitive area)
54
+ - 027 2026-01-18 7899bbd8433e `profiles` — feat(profiles): add API key requirements flow — YES (safety/security-sensitive area)
55
+ - 028 2026-01-18 0023ba1ea5aa `i18n` — refactor(i18n): update translations and tooling — YES (diff summary skipped (large change (+2913/-143)))
56
+ - 033 2026-01-18 daa0b4527d7f `new-session` — feat(new-session): add api key selection and wizard extraction — YES (diff summary skipped (large change (+1719/-1116)))
57
+ - 036 2026-01-18 97ca69e0a735 `i18n` — refactor(i18n): replace remaining UI literals — YES (safety/security-sensitive area)
58
+ - 038 2026-01-18 7976b877259c `experiments` — feat(experiments): gate Zen, file viewer, and voice auth flow — YES (safety/security-sensitive area)
59
+ - 039 2026-01-21 6ed379f82c34 `ui` — refactor(ui): add modal + popover overlay primitives — YES (diff summary skipped (too many files (43)))
60
+ - 040 2026-01-21 f0787de5308e `sync` — feat(sync): add secrets + terminal settings primitives — YES (diff summary skipped (too many files (60)); safety/security-sensitive area)
61
+ - 041 2026-01-21 66ee1eaf88a6 `secrets` — feat(secrets): add secrets management + requirement resolver — YES (diff summary skipped (too many files (29)); safety/security-sensitive area)
62
+ - 043 2026-01-21 bc779e4f7909 `new-session` — refactor(new-session): integrate secrets + terminal spawn options — YES (safety/security-sensitive area)
63
+ - 049 2026-01-21 2e956f32f220 `new` — fix(new): keep pick screens above iOS modal — YES (safety/security-sensitive area)
64
+ - 057 2026-01-21 ff7bec72358f `secrets` — fix(secrets): tighten callback deps and fix indentation — YES (safety/security-sensitive area)
65
+ - 058 2026-01-21 1bb65f5dd494 `new-session` — fix(new-session): avoid stuck secret requirement modal guard — YES (safety/security-sensitive area)
66
+ - 060 2026-01-22 bb09f91de715 `settings` — fix(settings): keep valid secrets when one entry is invalid — YES (safety/security-sensitive area)
67
+ - 061 2026-01-22 765423af52b4 `agent-input` — fix(agent-input): cycle permission mode from normalized state — YES (safety/security-sensitive area)
68
+ - 063 2026-01-22 2ed3100311c7 `secrets` — fix(secrets): hide values when using secret vault — YES (safety/security-sensitive area)
69
+ - 064 2026-01-22 e5848c480522 `ui` — fix(ui): harden overlays and permission cycling — YES (safety/security-sensitive area)
70
+ - 065 2026-01-13 69fdcff96a4c `sync` — fix(sync): restore session permission mode from last message — YES (safety/security-sensitive area)
71
+ - 066 2026-01-13 9b499c5dccce `sync` — fix(sync): persist permission mode timestamp for restart-safe arbitration — YES (safety/security-sensitive area)
72
+ - 067 2026-01-13 def8852509d0 `sync` — fix(sync): persist permission mode reliably across devices — YES (safety/security-sensitive area)
73
+ - 069 2026-01-06 c06b6202b5d4 `expo-app` — Add copy-to-clipboard button to message blocks — YES (non-Conventional-Commits subject)
74
+ - 072 2026-01-21 82d74454c3c4 `typecheck` — fix(typecheck): restore permission imports and Popover web styles — YES (safety/security-sensitive area)
75
+ - 086 2026-01-13 86330e263595 `security` — fix(security): redact spawn secrets from daemon logs — YES (safety/security-sensitive area)
76
+ - 089 2026-01-13 ef418bc4dda3 `pr107` — fix(pr107): redact profile secrets in doctor + align tmux tmpDir — YES (safety/security-sensitive area)
77
+ - 096 2026-01-15 2973f7fe6861 `codex` — fix(codex): harden MCP command detection — YES (safety/security-sensitive area)
78
+ - 099 2026-01-15 c52227082c2b `tmux` — fix(tmux): correct env, tmpdir, and session selection — YES (safety/security-sensitive area)
79
+ - 113 2026-01-21 51782fdbfbcd `codex` — test(codex): reset transport instances between tests — YES (safety/security-sensitive area)
80
+ - 124 2026-01-13 8b88dcd73d40 `claude` — fix(claude): carry permission mode across remote/local switches — YES (safety/security-sensitive area)
81
+ - 125 2026-01-13 8f0e10c9428b `claude` — fix(claude): publish permission mode in session metadata — YES (safety/security-sensitive area)
82
+ - 126 2026-01-13 ffad20faa406 `cli` — fix(cli): publish permission mode for codex/gemini sessions — YES (safety/security-sensitive area)
83
+ - 137 2026-01-12 68a6ba4bc244 `fork` — feat(fork): enable Codex inactive-session resume via codex-reply — YES (safety/security-sensitive area)
84
+ - 140 2026-01-22 9c40c54018c2 `cli` — Revert "fix(tools): support Windows arm64 tool unpacking" — YES (non-Conventional-Commits subject; revert commit)
85
+ - 141 2026-01-22 ab35b47ff699 `resume` — fix(resume): make inactive resume reliable; gate Codex resume — YES (multiple major areas: cli, expo-app; safety/security-sensitive area)
86
+ - 144 2026-01-22 0140ae276869 `codex` — refactor(codex): install mcp resume server via install-dep — YES (multiple major areas: cli, expo-app; safety/security-sensitive area)
87
+ - 146 2026-01-22 e1deb6db8ddb `codex` — refactor(codex): add dep-status and drop codex-resume RPCs — YES (multiple major areas: cli, expo-app)
88
+ - 148 2026-01-22 8bebf04aca92 `cli` — refactor(cli): modularize capabilities and env preview — YES (diff summary skipped (large change (+1296/-908)); safety/security-sensitive area)
89
+ - 150 2026-01-22 a5cac698f15b `cli` — feat(cli): harden session queue, switching, and lifecycle — YES (safety/security-sensitive area)
90
+ - 151 2026-01-22 3fc704424e8d `app` — feat(app): add pending queue, discard markers, and capabilities — YES (diff summary skipped (too many files (44)))
91
+ - 156 2026-01-22 8c16ee8f9cef `auth` — fix(auth): surface auth failures and gate unauth routes — YES (safety/security-sensitive area)
92
+ - 158 2026-01-22 3b3609fed434 `rpc` — fix(rpc): add structured code for missing RPC methods — YES (multiple major areas: expo-app, server)
93
+ - 160 2026-01-22 7fbe1f1cc727 `permission` — fix(permission): avoid no-op permissionModeUpdatedAt bumps — YES (safety/security-sensitive area)
94
+ - 162 2026-01-22 ee0bec2a0b90 `resume` — Delete INACTIVE_SESSION_RESUME.md — YES (non-Conventional-Commits subject)
95
+ - 165 2026-01-23 8b3f39f22664 `cli` — feat(cli): add interaction.respond for AskUserQuestion — YES (safety/security-sensitive area)
96
+ - 166 2025-12-24 ed4bc007308a `codex` — feat: add execpolicy approval option for Codex — YES (safety/security-sensitive area)
97
+ - 167 2025-12-24 78adc9c58811 `codex` — feat(codex): support execpolicy approvals and MCP tool calls — YES (safety/security-sensitive area)
98
+ - 168 2026-01-22 9dfa09bef8d8 `i18n` — fix(i18n): add Codex execpolicy button text — YES (safety/security-sensitive area)
99
+ - 169 2026-01-22 559d39da116d `reducer` — fix(reducer): keep permission messages idempotent — YES (safety/security-sensitive area)
100
+ - 171 2026-01-19 d06d5a833f8e `claude` — Add signal forwarding to claudeLocal.ts — YES (non-Conventional-Commits subject)
101
+ - 174 2025-12-24 e956463db3e2 `codex` — fix: use runtime execPath for MCP bridge — YES (safety/security-sensitive area)
102
+ - 175 2026-01-23 f0a7d8d0b40c `codex` — fix(codex): use mcp tool call id for approvals — YES (safety/security-sensitive area)
103
+ - 176 2026-01-22 da620b6865ad `server` — feat(server): add full/light flavors with sqlite migrations — YES (diff summary skipped (too many files (38)))
104
+ - 183 2026-01-23 46186162ae14 `happy-cli` — test(happy-cli): fix timer cleanup and MCP schema mocks — YES (safety/security-sensitive area)
105
+ - 208 2026-01-23 61a18ac95e17 `tools` — fix(tools): require permission id for ExitPlanMode actions — YES (safety/security-sensitive area)
106
+ - 209 2026-01-23 5b36c9bf91c1 `tools` — test(tools): avoid null permission in ExitPlanToolView tests — YES (safety/security-sensitive area)
107
+ - 211 2026-01-23 55428fb0253c `tools` — fix(tools): alert when AskUserQuestion permission id is missing — YES (safety/security-sensitive area)
108
+ - 212 2026-01-23 dc6955f88abd `terminal` — fix(terminal): prevent sessionId path traversal in attachment info — YES (safety/security-sensitive area)
109
+ - 226 2026-01-23 c461ed1cb4ef `permission` — feat(permission): add permission mode option helpers — YES (safety/security-sensitive area)
110
+ - 228 2026-01-23 523989b4de8e `server-light` — fix(server-light): avoid master secret race — YES (safety/security-sensitive area)
111
+ - 237 2026-01-23 241f0ae24b1d `cli` — test(cli): prevent legacy sessionId path traversal — YES (safety/security-sensitive area)
112
+ - 238 2026-01-23 a1e4a6dd3fbd `cli` — fix(cli): block legacy sessionId path traversal — YES (safety/security-sensitive area)
113
+ - 241 2026-01-23 1464402758bf `codex` — fix(codex): preserve falsy MCP tool results — YES (safety/security-sensitive area)
114
+ - 247 2026-01-23 24b607abfa07 `server` — Refactor schema sync and centralize Prisma types — YES (non-Conventional-Commits subject; diff summary skipped (too many files (29)))
115
+ - 248 2026-01-23 bf3027799623 `expo-app` — Set EXPO_UNSTABLE_WEB_MODAL env var in Expo scripts — YES (non-Conventional-Commits subject)
116
+ - 249 2026-01-23 c803115dcbdc `expo-app` — Improve postinstall script for symlinked paths and patching — YES (non-Conventional-Commits subject)
@@ -375,7 +375,7 @@ In interactive TTY runs, `happys dev` / `happys start` may auto-open the UI in y
375
375
  - **Dependency install**: ensures component deps are installed when needed.
376
376
  - **Schema readiness**:
377
377
  - `happy-server` (Postgres): applies `prisma migrate deploy` (configurable via `HAPPY_STACKS_PRISMA_MIGRATE`)
378
- - `happy-server-light` (SQLite): upstream dev normally runs `prisma db push` (toggle via `HAPPY_STACKS_PRISMA_PUSH`)
378
+ - `happy-server-light` (SQLite): applies `prisma migrate deploy` using the SQLite migration history in the unified server repo
379
379
  - **Auth seeding for new stacks** (non-main + non-interactive default):
380
380
  - Uses the configured seed stack via `HAPPY_STACKS_AUTH_SEED_FROM` (default: `main`) when the stack looks uninitialized.
381
381
  - Recommended for development: create + log into a dedicated seed stack once (usually `dev-auth`) and set:
@@ -628,7 +628,6 @@ Happy Stacks uses `HAPPY_STACKS_*` as the canonical prefix; most settings also a
628
628
  - **Full server infra** (happy-server):
629
629
  - `HAPPY_STACKS_MANAGED_INFRA=0` (disable Docker-managed Postgres/Redis/Minio; provide URLs yourself)
630
630
  - **Prisma behavior**:
631
- - `HAPPY_STACKS_PRISMA_PUSH=0` (server-light: avoid `prisma db push` in dev mode)
632
631
  - `HAPPY_STACKS_PRISMA_MIGRATE=0` (full server: disable `prisma migrate deploy`)
633
632
  - **happy-cli build behavior**:
634
633
  - `HAPPY_STACKS_CLI_BUILD_MODE=auto|always|never`
@@ -0,0 +1,286 @@
1
+ # Monorepo migration (split repos → `slopus/happy`)
2
+
3
+ This doc explains the **recommended, safe, step-by-step** flow to port commits from the legacy split repos (`happy`, `happy-cli`, `happy-server`) into the new `slopus/happy` monorepo layout:
4
+
5
+ - old `happy` (UI) → `expo-app/`
6
+ - old `happy-cli` → `cli/`
7
+ - old `happy-server` → `server/`
8
+
9
+ The tooling used here is `happys monorepo port` (from this repo / Happy Stacks). It ports commits by generating patches (`git format-patch`) and applying them with `git am` into the target monorepo branch.
10
+
11
+ ## Quick start (if you don’t already use Happy Stacks)
12
+
13
+ If you’re an external collaborator and you **don’t** have `happys` installed yet, this is the fastest “migration environment” setup:
14
+
15
+ ```bash
16
+ npx happy-stacks init --install-path
17
+ happys bootstrap --interactive
18
+ happys stack new monorepo-merge --interactive
19
+ ```
20
+
21
+ ### “No install” option (npx-only, port command only)
22
+
23
+ If you *only* want to run the port helper (and you already have local clones of the source repos + target monorepo), you can run it directly via `npx` without installing anything globally:
24
+
25
+ ```bash
26
+ npx --yes happy-stacks monorepo port --help
27
+ ```
28
+
29
+ Example:
30
+
31
+ ```bash
32
+ npx --yes happy-stacks monorepo port \
33
+ --target=/abs/path/to/slopus-happy-monorepo \
34
+ --branch=your-port-branch \
35
+ --base=origin/main \
36
+ --3way \
37
+ --from-happy=/abs/path/to/old-happy \
38
+ --from-happy-base=origin/main
39
+ ```
40
+
41
+ Notes:
42
+ - This “npx-only” mode is great for **one-off ports**, but it won’t manage stacks/worktrees for you.
43
+ - For the full guided flow (stacks + worktrees + repeatable commands), use the installed setup above.
44
+
45
+ ### Port helpers (guide / status / continue)
46
+
47
+ Happy Stacks also provides small helpers that make conflict resolution less error-prone:
48
+
49
+ ```bash
50
+ happys monorepo port guide
51
+ happys monorepo port status --target=/abs/path/to/monorepo
52
+ happys monorepo port continue --target=/abs/path/to/monorepo
53
+ ```
54
+
55
+ - `port guide` is interactive (TTY required) and helps you build the initial port command safely.
56
+ - `port guide` can also **pause on conflicts**, let you resolve them, and then resume until the port completes.
57
+ - `port status` shows whether a `git am` session is in progress, the current patch subject, and conflicted files.
58
+ - `port continue` runs `git am --continue` for the target repo (after you staged resolved files). If you started from `port guide`, it can also resume the remaining port automatically after each continue.
59
+
60
+ How to bring “the changes you want to port” into this environment:
61
+
62
+ - **If you already have local checkouts** of your legacy repos (recommended for forks/branches that aren’t PRs yet):
63
+ - keep them wherever they are
64
+ - you’ll pass their absolute paths to `happys monorepo port` via `--from-happy=...`, `--from-happy-cli=...`, etc.
65
+
66
+ - **If your changes exist as GitHub PRs**, you can let Happy Stacks create a clean worktree from the PR directly:
67
+ - `happys wt pr happy-cli <pr-url-or-number> --use`
68
+ - `happys wt pr happy <pr-url-or-number> --use`
69
+ - (repeat for whichever repos your changes live in)
70
+
71
+ Once you have:
72
+ - a **target monorepo worktree** (from upstream), and
73
+ - one or more **source repos** (paths or worktrees)
74
+
75
+ …continue with the rest of this doc.
76
+
77
+ ## Prereqs
78
+
79
+ - You have a **clean** source worktree/branch for each legacy repo you want to port.
80
+ - No uncommitted changes (unless you intentionally want to include them as new commits first).
81
+ - Ideally based on `upstream/main` (or you know the correct base ref).
82
+ - You have a **clean** target monorepo checkout (a worktree of `slopus/happy`).
83
+ - You are ready to resolve conflicts with `git am` (this is normal for large refactors, i18n changes, renamed files, etc).
84
+
85
+ ## Recommended workflow (interactive, safest)
86
+
87
+ ### 1) Create an isolated stack (don’t touch `main`)
88
+
89
+ Pick a new stack name (example: `monorepo-merge`):
90
+
91
+ ```bash
92
+ happys stack new monorepo-merge --interactive
93
+ ```
94
+
95
+ ### 2) Create a clean monorepo worktree from upstream
96
+
97
+ Create a worktree based on `upstream/main` for the **monorepo** (`slopus/happy`):
98
+
99
+ ```bash
100
+ happys wt new happy tmp/monorepo-port --from=upstream
101
+ ```
102
+
103
+ Point your stack at that worktree (this keeps `main` stable):
104
+
105
+ ```bash
106
+ happys stack wt monorepo-merge -- use happy /absolute/path/to/components/.worktrees/happy/slopus/tmp/monorepo-port
107
+ ```
108
+
109
+ Notes:
110
+ - In monorepo mode, `happy`, `happy-cli`, and `happy-server` are **one git repo**; the stack overrides should point at the same monorepo root.
111
+ - `happys ... wt use happy <monorepo-root>` automatically updates all three component dir overrides together (prevents UI/CLI/server version skew).
112
+ - If you pass a monorepo root, Happy Stacks normalizes component dirs to:
113
+ - `happy` → `.../expo-app`
114
+ - `happy-cli` → `.../cli`
115
+ - `happy-server` → `.../server`
116
+
117
+ ### 3) Create a target branch
118
+
119
+ In the monorepo worktree, create your migration branch from `upstream/main`:
120
+
121
+ ```bash
122
+ happys wt git happy slopus/tmp/monorepo-port -- checkout -b <your-branch-name> upstream/main
123
+ ```
124
+
125
+ Example:
126
+
127
+ ```bash
128
+ happys wt git happy slopus/tmp/monorepo-port -- checkout -b leeroy-wip upstream/main
129
+ ```
130
+
131
+ ### 4) Port the UI commits (old `happy` → `expo-app/`)
132
+
133
+ Run the port in **interactive mode**:
134
+
135
+ - use `--onto-current` to apply onto the branch you already checked out
136
+ - use `--3way` so git can do a 3-way merge and produce conflict markers instead of failing immediately
137
+ - **do not** use `--continue-on-failure` (you want it to stop at the first conflict)
138
+
139
+ ```bash
140
+ happys monorepo port \
141
+ --target=/abs/path/to/monorepo-root \
142
+ --onto-current \
143
+ --3way \
144
+ --from-happy=/abs/path/to/old-happy-repo \
145
+ --from-happy-base=upstream/main
146
+ ```
147
+
148
+ What `--from-happy-base` means:
149
+ - It’s the ref used to compute the patch range (`merge-base(base, HEAD)..HEAD`).
150
+ - If your branch is based on `upstream/main`, this should be `upstream/main`.
151
+
152
+ ### 5) Resolve conflicts (when the port stops)
153
+
154
+ If the port stops, you are now in a normal `git am` session.
155
+
156
+ Helpful commands:
157
+
158
+ ```bash
159
+ git am --show-current-patch=diff
160
+ git status
161
+ ```
162
+
163
+ Fix conflicts in the files with conflict markers:
164
+ - look for `<<<<<<<`, `=======`, `>>>>>>>`
165
+ - edit to the desired final content
166
+ - then stage the resolved files:
167
+
168
+ ```bash
169
+ git add <file...>
170
+ git am --continue
171
+ ```
172
+
173
+ If you decide a specific patch should not be ported:
174
+
175
+ ```bash
176
+ git am --skip
177
+ ```
178
+
179
+ If you want to fully abort the current patch application:
180
+
181
+ ```bash
182
+ git am --abort
183
+ ```
184
+
185
+ Then rerun the `happys monorepo port ... --onto-current ...` command.
186
+
187
+ ### 6) Port the CLI commits (old `happy-cli` → `cli/`)
188
+
189
+ After the UI port completes, port CLI commits onto the same branch:
190
+
191
+ ```bash
192
+ happys monorepo port \
193
+ --target=/abs/path/to/monorepo-root \
194
+ --onto-current \
195
+ --3way \
196
+ --from-happy-cli=/abs/path/to/old-happy-cli-repo \
197
+ --from-happy-cli-base=upstream/main
198
+ ```
199
+
200
+ Resolve conflicts the same way (`git am --continue`).
201
+
202
+ ### 7) Verify what landed
203
+
204
+ Quick sanity checks:
205
+
206
+ ```bash
207
+ # ensure upstream/main is an ancestor of your branch
208
+ git merge-base --is-ancestor upstream/main HEAD
209
+
210
+ # list commits introduced by the port
211
+ git log --oneline upstream/main..HEAD
212
+ ```
213
+
214
+ If you are porting multiple legacy branches, it’s often useful to compare counts:
215
+
216
+ ```bash
217
+ git rev-list --count upstream/main..HEAD
218
+ ```
219
+
220
+ ### 8) Push and open a PR
221
+
222
+ Push to the remote you intend to PR against (example uses `upstream`):
223
+
224
+ ```bash
225
+ git push upstream HEAD:<branch-name>
226
+ ```
227
+
228
+ ## Common failures (expected) and what to do
229
+
230
+ ### “target repo is not clean”
231
+
232
+ `happys monorepo port` refuses to run when the target has local changes.
233
+
234
+ Fix: commit, stash, or reset your target worktree, then re-run.
235
+
236
+ ### “a git am operation is already in progress”
237
+
238
+ You have an unfinished `git am` session from a previous attempt.
239
+
240
+ Fix it first:
241
+
242
+ ```bash
243
+ git am --continue # after resolving conflicts
244
+ # or
245
+ git am --abort
246
+ ```
247
+
248
+ Then re-run `happys monorepo port ...`.
249
+
250
+ ### “patch does not apply” / i18n churn / renamed files
251
+
252
+ This usually means upstream moved and the patch context no longer matches.
253
+
254
+ Recommended: rerun with `--3way` (3-way merge) so you get conflict markers to resolve.
255
+
256
+ ### “already exists in working directory”
257
+
258
+ This often happens when a commit (especially “new file”) was already folded into the monorepo history.
259
+
260
+ The tool auto-skips:
261
+ - patches that are already present (exact-match reverse apply check)
262
+ - pure new-file patches when the target already contains identical content
263
+
264
+ If you still hit this manually during a stopped `git am`, decide whether to:
265
+ - keep the existing file and `git am --skip`, or
266
+ - reconcile content and continue.
267
+
268
+ ### Missing file path errors
269
+
270
+ If the patch references files that no longer exist (or moved) in the monorepo, you’ll need to:
271
+ - map the change to the new file location, or
272
+ - skip that patch if it’s obsolete.
273
+
274
+ Use `git am --show-current-patch=diff` to understand intent, then implement the equivalent change in the monorepo layout and continue.
275
+
276
+ ## Optional: audit mode (best-effort report)
277
+
278
+ If you want a full report of what would apply vs fail (without stopping at the first conflict), you can run:
279
+
280
+ ```bash
281
+ happys monorepo port ... --continue-on-failure --json
282
+ ```
283
+
284
+ This is **not** the recommended way to produce a final clean branch, but it can be useful to:
285
+ - discover the full set of expected conflicts
286
+ - share a machine-readable report for assistance
@@ -4,7 +4,21 @@ Happy Stacks supports two server “flavors”. You can switch between them glob
4
4
 
5
5
  ## What’s the difference?
6
6
 
7
- Both are forks/flavors of the same upstream server repo (`slopus/happy-server`), but optimized for different use cases:
7
+ Both are forks/flavors of the same upstream server repo (`slopus/happy-server`), but optimized for different use cases.
8
+
9
+ ### Unified codebase (recommended)
10
+
11
+ When your `happy-server` checkout includes the light flavor artifacts (notably `prisma/sqlite/schema.prisma` — legacy: `prisma/schema.sqlite.prisma`), Happy Stacks treats it as a **single unified server codebase** that supports both:
12
+
13
+ - `happy-server` (full / Postgres+Redis+S3)
14
+ - `happy-server-light` (light / SQLite+local files, can serve UI)
15
+
16
+ In that setup:
17
+
18
+ - there is **no server code duplication**
19
+ - `happy-server-light` can point at the **same checkout/worktree** as `happy-server`
20
+ - `happys stack new` will default to pinning **both** server component dirs to the same path
21
+ - `happys start/dev --server=happy-server-light` will run `start:light` / `dev:light` when available
8
22
 
9
23
  - **`happy-server-light`** (recommended default)
10
24
  - optimized for local usage
@@ -73,7 +87,7 @@ Notes:
73
87
  - **`happys start`** is “production-like”. It avoids running heavyweight schema sync loops under launchd KeepAlive.
74
88
  - **`happys dev`** is for rapid iteration:
75
89
  - for `happy-server`: Happy Stacks runs `prisma migrate deploy` by default (configurable via `HAPPY_STACKS_PRISMA_MIGRATE`).
76
- - for `happy-server-light`: the upstream dev script runs `prisma db push` by default (configurable via `HAPPY_STACKS_PRISMA_PUSH`).
90
+ - for `happy-server-light`: Happy Stacks runs `prisma migrate deploy` (SQLite migrations) using the unified schema under `prisma/sqlite/schema.prisma`.
77
91
 
78
92
  Important: for a given run (`happys start` / `happys dev`) you choose **one** flavor.
79
93
 
@@ -125,7 +139,9 @@ There are two separate concepts:
125
139
  - controlled by `HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER_LIGHT` and `HAPPY_STACKS_COMPONENT_DIR_HAPPY_SERVER`
126
140
  - easiest via `happys wt use happy-server-light ...` / `happys wt use happy-server ...`
127
141
 
128
- If you set `HAPPY_STACKS_SERVER_COMPONENT=happy-server-light` but accidentally point the *server-light component dir* at a `happy-server` worktree (or vice versa), `happys start/dev/doctor` will refuse to run and print a fix hint.
142
+ If you set `HAPPY_STACKS_SERVER_COMPONENT=happy-server-light` but accidentally point the *server-light component dir* at a postgres-only `happy-server` checkout (or vice versa), `happys start/dev/doctor` will refuse to run and print a fix hint.
143
+
144
+ In unified mode (same repo supports both flavors), it is valid (and recommended) for both component dirs to point at the same checkout.
129
145
 
130
146
  `happys wt use` also prevents the most common mismatch when selecting server worktrees inside `components/` / `components/.worktrees/`.
131
147
 
package/docs/stacks.md CHANGED
@@ -238,6 +238,41 @@ Cloned-repo fallback (before you run `happys init`):
238
238
  2. `<repo>/env.local` (optional overrides)
239
239
  3. `HAPPY_STACKS_ENV_FILE` (stack env)
240
240
 
241
+ ## Manage per-stack environment variables (including API keys)
242
+
243
+ To add/update environment variables in a stack env file from the CLI:
244
+
245
+ ```bash
246
+ happys stack env <stack> set KEY=VALUE [KEY2=VALUE2...]
247
+ ```
248
+
249
+ To remove keys:
250
+
251
+ ```bash
252
+ happys stack env <stack> unset KEY [KEY2...]
253
+ ```
254
+
255
+ To inspect:
256
+
257
+ ```bash
258
+ happys stack env <stack> get KEY
259
+ happys stack env <stack> list
260
+ happys stack env <stack> path
261
+ ```
262
+
263
+ Notes:
264
+
265
+ - This is the recommended place for **provider API keys** the daemon needs (example: `OPENAI_API_KEY`).
266
+ - Changes apply on the **next start** of the stack/daemon. Restart to pick them up:
267
+ - `main`: `happys start --restart`
268
+ - named stack: `happys stack start <stack> -- --restart` (or `happys stack dev <stack> -- --restart`)
269
+
270
+ Self-host shortcut (defaults to `main` when not running under a stack wrapper):
271
+
272
+ ```bash
273
+ happys env set OPENAI_API_KEY=sk-...
274
+ ```
275
+
241
276
  ## Daemon auth + “no machine” on first run
242
277
 
243
278
  On a **fresh machine** (or any new stack), the daemon may need to authenticate before it can register a “machine”.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "happy-stacks",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "packageManager": "pnpm@10.18.3",
6
6
  "bin": {
7
7
  "happys": "./bin/happys.mjs",
package/scripts/auth.mjs CHANGED
@@ -16,6 +16,7 @@ import { dirname } from 'node:path';
16
16
  import { parseEnvToObject } from './utils/env/dotenv.mjs';
17
17
  import { ensureDepsInstalled, pmExecBin } from './utils/proc/pm.mjs';
18
18
  import { applyHappyServerMigrations, ensureHappyServerManagedInfra } from './utils/server/infra/happy_server_infra.mjs';
19
+ import { resolvePrismaClientImportForServerComponent, resolveServerLightPrismaMigrateDeployArgs } from './utils/server/flavor_scripts.mjs';
19
20
  import { clearDevAuthKey, readDevAuthKey, writeDevAuthKey } from './utils/auth/dev_key.mjs';
20
21
  import { getExpoStatePaths, isStateProcessRunning } from './utils/expo/expo.mjs';
21
22
  import { resolveAuthSeedFromEnv } from './utils/stack/startup.mjs';
@@ -227,6 +228,15 @@ async function seedAccountsFromSourceDbToTargetDb({
227
228
  const sourceCwd = resolveServerComponentDir({ rootDir, serverComponent: fromServerComponent });
228
229
  const targetCwd = resolveServerComponentDir({ rootDir, serverComponent: targetServerComponent });
229
230
 
231
+ const sourceClientImport = resolvePrismaClientImportForServerComponent({
232
+ serverComponentName: fromServerComponent,
233
+ serverDir: sourceCwd,
234
+ });
235
+ const targetClientImport = resolvePrismaClientImportForServerComponent({
236
+ serverComponentName: targetServerComponent,
237
+ serverDir: targetCwd,
238
+ });
239
+
230
240
  const listScript = `
231
241
  process.on('uncaughtException', (e) => {
232
242
  console.error(e instanceof Error ? e.message : String(e));
@@ -236,7 +246,11 @@ process.on('unhandledRejection', (e) => {
236
246
  console.error(e instanceof Error ? e.message : String(e));
237
247
  process.exit(1);
238
248
  });
239
- import { PrismaClient } from '@prisma/client';
249
+ const mod = await import(${JSON.stringify(sourceClientImport)});
250
+ const PrismaClient = mod?.PrismaClient ?? mod?.default?.PrismaClient;
251
+ if (!PrismaClient) {
252
+ throw new Error('Failed to load PrismaClient for DB seed (source).');
253
+ }
240
254
  const db = new PrismaClient();
241
255
  try {
242
256
  const accounts = await db.account.findMany({ select: { id: true, publicKey: true } });
@@ -255,7 +269,11 @@ process.on('unhandledRejection', (e) => {
255
269
  console.error(e instanceof Error ? e.message : String(e));
256
270
  process.exit(1);
257
271
  });
258
- import { PrismaClient } from '@prisma/client';
272
+ const mod = await import(${JSON.stringify(targetClientImport)});
273
+ const PrismaClient = mod?.PrismaClient ?? mod?.default?.PrismaClient;
274
+ if (!PrismaClient) {
275
+ throw new Error('Failed to load PrismaClient for DB seed (target).');
276
+ }
259
277
  import fs from 'node:fs';
260
278
  const FORCE = ${force ? 'true' : 'false'};
261
279
  const raw = fs.readFileSync(0, 'utf8').trim();
@@ -633,7 +651,7 @@ async function cmdCopyFrom({ argv, json }) {
633
651
  await pmExecBin({
634
652
  dir: serverDirForPrisma,
635
653
  bin: 'prisma',
636
- args: ['db', 'push'],
654
+ args: resolveServerLightPrismaMigrateDeployArgs({ serverDir: serverDirForPrisma }),
637
655
  env: { ...process.env, DATABASE_URL: targetDatabaseUrl },
638
656
  quiet: json,
639
657
  }).catch(() => {});
package/scripts/build.mjs CHANGED
@@ -15,7 +15,7 @@ import { getInvokedCwd, inferComponentFromCwd } from './utils/cli/cwd_scope.mjs'
15
15
  * Build a lightweight static web UI bundle (no Expo dev server).
16
16
  *
17
17
  * Output directory default: ~/.happy/stacks/main/ui (legacy: ~/.happy/local/ui)
18
- * Server will serve it at / when HAPPY_SERVER_LIGHT_UI_DIR is set.
18
+ * Server will serve it at / when HAPPY_SERVER_UI_DIR is set.
19
19
  * (Legacy /ui paths are redirected to /.)
20
20
  */
21
21