happy-stacks 0.6.11 → 0.6.13
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/docs/commit-audits/happy/_tools/generate-plans.mjs +453 -0
- package/docs/commit-audits/happy/_tools/generate-pr-assignment.mjs +430 -0
- package/docs/commit-audits/happy/_tools/init-pr-assignment-working.mjs +107 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-analysis.md +1849 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-export.fuller-stat.md +747 -1
- package/docs/commit-audits/happy/leeroy-wip.commit-index.json +11740 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-index.tsv +252 -0
- package/docs/commit-audits/happy/leeroy-wip.commit-inventory.md +18 -11
- package/docs/commit-audits/happy/leeroy-wip.commit-manual-review.md +1297 -140
- package/docs/commit-audits/happy/leeroy-wip.maintainers-overview.draft.md +448 -0
- package/docs/commit-audits/happy/leeroy-wip.pr-assignment.draft.tsv +252 -0
- package/docs/commit-audits/happy/leeroy-wip.pr-assignment.working.tsv +288 -0
- package/docs/commit-audits/happy/leeroy-wip.pr-catalog.draft.md +245 -0
- package/docs/commit-audits/happy/leeroy-wip.pr-stack-plan.draft.md +350 -0
- package/docs/commit-audits/happy/leeroy-wip.rewrite-deferred-fragments.tsv +65 -0
- package/docs/commit-audits/happy/leeroy-wip.rewrite-ledger.tsv +56 -0
- package/docs/commit-audits/happy/leeroy-wip.rewrite-process.md +240 -0
- package/docs/commit-audits/happy/leeroy-wip.rewrite-status.tsv +39 -0
- package/docs/commit-audits/happy/leeroy-wip.split-plan.draft.md +93 -0
- package/docs/commit-audits/happy/leeroy-wip.topic-buckets.md +76 -0
- package/docs/commit-audits/happy/pr-desc.extraction-ledger.tsv +279 -0
- package/docs/commit-audits/happy/pr-desc.original.md +0 -0
- package/docs/commit-audits/happy/pr-desc.post-audit-extraction-ledger.tsv +54 -0
- package/docs/commit-audits/happy/pr-desc.working-document.md +536 -0
- package/docs/happy-development.md +18 -1
- package/docs/isolated-linux-vm.md +23 -1
- package/docs/stacks.md +21 -1
- package/package.json +1 -1
- package/scripts/auth.mjs +46 -8
- package/scripts/daemon.mjs +44 -21
- package/scripts/doctor.mjs +2 -2
- package/scripts/doctor_cmd.test.mjs +67 -0
- package/scripts/happy.mjs +18 -5
- package/scripts/import.mjs +19 -13
- package/scripts/monorepo.mjs +414 -23
- package/scripts/monorepo_port.test.mjs +136 -0
- package/scripts/provision/linux-ubuntu-review-pr.sh +5 -1
- package/scripts/provision/macos-lima-happy-vm.sh +34 -2
- package/scripts/review.mjs +347 -124
- package/scripts/review_pr.mjs +78 -2
- package/scripts/run.mjs +2 -1
- package/scripts/stack.mjs +265 -19
- package/scripts/stack_daemon_cmd.test.mjs +196 -0
- package/scripts/stack_happy_cmd.test.mjs +103 -0
- package/scripts/utils/cli/prereqs.mjs +12 -1
- package/scripts/utils/cli/wizard.mjs +19 -1
- package/scripts/utils/dev/daemon.mjs +3 -1
- package/scripts/utils/llm/assist.mjs +64 -31
- package/scripts/utils/llm/happys_runner.mjs +60 -0
- package/scripts/utils/llm/tools.mjs +3 -3
- package/scripts/utils/proc/pm.mjs +1 -1
- package/scripts/utils/review/detached_worktree.mjs +61 -0
- package/scripts/utils/review/detached_worktree.test.mjs +62 -0
- package/scripts/utils/review/findings.mjs +133 -20
- package/scripts/utils/review/findings.test.mjs +88 -1
- package/scripts/utils/review/runners/augment.mjs +71 -0
- package/scripts/utils/review/runners/augment.test.mjs +42 -0
- package/scripts/utils/review/runners/coderabbit.mjs +54 -10
- package/scripts/utils/review/runners/coderabbit.test.mjs +15 -48
- package/scripts/utils/review/sliced_runner.mjs +39 -0
- package/scripts/utils/review/sliced_runner.test.mjs +47 -0
- package/scripts/utils/review/tool_home_seed.mjs +99 -0
- package/scripts/utils/review/tool_home_seed.test.mjs +113 -0
- package/scripts/utils/stack/cli_identities.mjs +29 -0
- package/scripts/utils/stack/startup.mjs +45 -7
- package/scripts/worktrees.mjs +8 -5
|
@@ -275,6 +275,76 @@ test('monorepo port can clone the target monorepo into a new directory', async (
|
|
|
275
275
|
assert.equal(content, 'v2\n');
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
+
test('monorepo port guide auto-clones target when --target does not exist', async (t) => {
|
|
279
|
+
const root = await withTempRoot(t);
|
|
280
|
+
const seedMono = join(root, 'seed-mono');
|
|
281
|
+
const target = join(root, 'target-guide-autoclone'); // does not exist yet
|
|
282
|
+
const sourceCli = join(root, 'source-cli');
|
|
283
|
+
const env = gitEnv();
|
|
284
|
+
|
|
285
|
+
// Seed monorepo repo that will be cloned into `target`
|
|
286
|
+
await initMonorepoStub({ dir: seedMono, env, seed: { 'cli/hello.txt': 'v1\n' } });
|
|
287
|
+
|
|
288
|
+
// Source CLI repo with one change commit (v1 -> v2)
|
|
289
|
+
const base = await initSplitRepoStub({ dir: sourceCli, env, name: 'cli', seed: { 'hello.txt': 'v1\n' } });
|
|
290
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'v2\n', 'utf-8');
|
|
291
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env });
|
|
292
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env });
|
|
293
|
+
|
|
294
|
+
// Guide requires a TTY, but with all args provided it should not prompt.
|
|
295
|
+
// We spawn so the guide sees a TTY (required), but still feed no input.
|
|
296
|
+
const child = spawn(
|
|
297
|
+
process.execPath,
|
|
298
|
+
[
|
|
299
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
300
|
+
'port',
|
|
301
|
+
'guide',
|
|
302
|
+
`--target=${target}`,
|
|
303
|
+
`--target-repo=${seedMono}`,
|
|
304
|
+
'--branch=port/test-guide-autoclone',
|
|
305
|
+
'--3way',
|
|
306
|
+
`--from-happy-cli=${sourceCli}`,
|
|
307
|
+
`--from-happy-cli-base=${base}`,
|
|
308
|
+
'--json',
|
|
309
|
+
],
|
|
310
|
+
{
|
|
311
|
+
cwd: process.cwd(),
|
|
312
|
+
env: { ...env, HAPPY_STACKS_TEST_TTY: '1', HAPPY_STACKS_DISABLE_LLM_AUTOEXEC: '1' },
|
|
313
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
t.after(() => {
|
|
317
|
+
try {
|
|
318
|
+
child.kill('SIGKILL');
|
|
319
|
+
} catch {
|
|
320
|
+
// ignore
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
let out = '';
|
|
324
|
+
let err = '';
|
|
325
|
+
let exitCode = null;
|
|
326
|
+
child.stdout?.on('data', (d) => (out += d.toString()));
|
|
327
|
+
child.stderr?.on('data', (d) => (err += d.toString()));
|
|
328
|
+
child.on('exit', (code) => {
|
|
329
|
+
exitCode = code;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const waitForExit = async (timeoutMs) => {
|
|
333
|
+
const started = Date.now();
|
|
334
|
+
while (Date.now() - started < timeoutMs) {
|
|
335
|
+
if (exitCode != null) return;
|
|
336
|
+
// eslint-disable-next-line no-await-in-loop
|
|
337
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
338
|
+
}
|
|
339
|
+
throw new Error(`timeout waiting for guide to exit\nstdout:\n${out}\nstderr:\n${err}`);
|
|
340
|
+
};
|
|
341
|
+
await waitForExit(20_000);
|
|
342
|
+
assert.equal(exitCode, 0, `expected guide to exit 0\nstdout:\n${out}\nstderr:\n${err}`);
|
|
343
|
+
|
|
344
|
+
const content = (await readFile(join(target, 'cli', 'hello.txt'), 'utf-8')).toString();
|
|
345
|
+
assert.equal(content, 'v2\n');
|
|
346
|
+
});
|
|
347
|
+
|
|
278
348
|
test('monorepo port accepts source repo URLs by cloning them into a temp checkout', async (t) => {
|
|
279
349
|
const root = await withTempRoot(t);
|
|
280
350
|
const target = join(root, 'target-mono');
|
|
@@ -721,6 +791,72 @@ test('monorepo port continue runs git am --continue after conflicts are resolved
|
|
|
721
791
|
assert.equal(content, 'value=source\n');
|
|
722
792
|
});
|
|
723
793
|
|
|
794
|
+
test('monorepo port continue --stage stages conflicted files before continuing', async (t) => {
|
|
795
|
+
const root = await withTempRoot(t);
|
|
796
|
+
const target = join(root, 'target-mono');
|
|
797
|
+
const sourceCli = join(root, 'source-cli');
|
|
798
|
+
|
|
799
|
+
// Target monorepo stub with cli/hello.txt="value=target".
|
|
800
|
+
await mkdir(target, { recursive: true });
|
|
801
|
+
await run('git', ['init', '-q'], { cwd: target, env: gitEnv() });
|
|
802
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: target, env: gitEnv() });
|
|
803
|
+
await mkdir(join(target, 'expo-app'), { recursive: true });
|
|
804
|
+
await mkdir(join(target, 'cli'), { recursive: true });
|
|
805
|
+
await mkdir(join(target, 'server'), { recursive: true });
|
|
806
|
+
await writeFile(join(target, 'expo-app', 'package.json'), '{}\n', 'utf-8');
|
|
807
|
+
await writeFile(join(target, 'cli', 'package.json'), '{}\n', 'utf-8');
|
|
808
|
+
await writeFile(join(target, 'server', 'package.json'), '{}\n', 'utf-8');
|
|
809
|
+
await writeFile(join(target, 'cli', 'hello.txt'), 'value=target\n', 'utf-8');
|
|
810
|
+
await run('git', ['add', '.'], { cwd: target, env: gitEnv() });
|
|
811
|
+
await run('git', ['commit', '-q', '-m', 'chore: init monorepo'], { cwd: target, env: gitEnv() });
|
|
812
|
+
|
|
813
|
+
// Source CLI repo with base="value=base" and a commit changing to "value=source".
|
|
814
|
+
await mkdir(sourceCli, { recursive: true });
|
|
815
|
+
await run('git', ['init', '-q'], { cwd: sourceCli, env: gitEnv() });
|
|
816
|
+
await run('git', ['checkout', '-q', '-b', 'main'], { cwd: sourceCli, env: gitEnv() });
|
|
817
|
+
await writeFile(join(sourceCli, 'package.json'), '{}\n', 'utf-8');
|
|
818
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=base\n', 'utf-8');
|
|
819
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env: gitEnv() });
|
|
820
|
+
await run('git', ['commit', '-q', '-m', 'chore: init cli'], { cwd: sourceCli, env: gitEnv() });
|
|
821
|
+
const base = (await runCapture('git', ['rev-parse', 'HEAD'], { cwd: sourceCli, env: gitEnv() })).trim();
|
|
822
|
+
await writeFile(join(sourceCli, 'hello.txt'), 'value=source\n', 'utf-8');
|
|
823
|
+
await run('git', ['add', '.'], { cwd: sourceCli, env: gitEnv() });
|
|
824
|
+
await run('git', ['commit', '-q', '-m', 'feat: update hello'], { cwd: sourceCli, env: gitEnv() });
|
|
825
|
+
|
|
826
|
+
// Start a port that will stop with an am conflict.
|
|
827
|
+
await assert.rejects(
|
|
828
|
+
async () =>
|
|
829
|
+
await runCapture(
|
|
830
|
+
process.execPath,
|
|
831
|
+
[
|
|
832
|
+
join(process.cwd(), 'scripts', 'monorepo.mjs'),
|
|
833
|
+
'port',
|
|
834
|
+
`--target=${target}`,
|
|
835
|
+
`--branch=port/test-continue-stage`,
|
|
836
|
+
`--from-happy-cli=${sourceCli}`,
|
|
837
|
+
`--from-happy-cli-base=${base}`,
|
|
838
|
+
'--3way',
|
|
839
|
+
],
|
|
840
|
+
{ cwd: process.cwd(), env: gitEnv() }
|
|
841
|
+
)
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
// Resolve the conflict by choosing "value=source", but DO NOT stage it.
|
|
845
|
+
await writeFile(join(target, 'cli', 'hello.txt'), 'value=source\n', 'utf-8');
|
|
846
|
+
|
|
847
|
+
const out = await runCapture(
|
|
848
|
+
process.execPath,
|
|
849
|
+
[join(process.cwd(), 'scripts', 'monorepo.mjs'), 'port', 'continue', `--target=${target}`, '--stage', '--json'],
|
|
850
|
+
{ cwd: process.cwd(), env: gitEnv() }
|
|
851
|
+
);
|
|
852
|
+
const parsed = JSON.parse(out.trim());
|
|
853
|
+
assert.equal(parsed.ok, true);
|
|
854
|
+
assert.equal(parsed.inProgress, false);
|
|
855
|
+
|
|
856
|
+
const content = (await readFile(join(target, 'cli', 'hello.txt'), 'utf-8')).toString();
|
|
857
|
+
assert.equal(content, 'value=source\n');
|
|
858
|
+
});
|
|
859
|
+
|
|
724
860
|
test('monorepo port guide refuses to run in non-tty mode', async (t) => {
|
|
725
861
|
const root = await withTempRoot(t);
|
|
726
862
|
const target = join(root, 'target-mono');
|
|
@@ -73,7 +73,11 @@ corepack prepare pnpm@latest --activate >/dev/null 2>&1 || true
|
|
|
73
73
|
|
|
74
74
|
echo "[provision] configuring happy-stacks VM defaults (ports)..."
|
|
75
75
|
# When port-forwarding a VM to the macOS host, it's convenient to avoid using the host's default ports (3005/8081).
|
|
76
|
-
# Persist these as happy-stacks home defaults so `
|
|
76
|
+
# Persist these as happy-stacks *home* defaults so non-sandbox `happys ...` commands pick them up automatically.
|
|
77
|
+
#
|
|
78
|
+
# NOTE: `npx happy-stacks review-pr ...` runs in a fully isolated sandbox (separate HOME), so it will NOT read
|
|
79
|
+
# this file by default. For review-pr in a VM, pass `--vm-ports` (or explicit `--stack-port-start=...`) to
|
|
80
|
+
# force the port ranges inside the sandbox.
|
|
77
81
|
HS_HOME="${HOME}/.happy-stacks"
|
|
78
82
|
mkdir -p "$HS_HOME"
|
|
79
83
|
ENV_LOCAL="${HS_HOME}/env.local"
|
|
@@ -48,15 +48,23 @@ fi
|
|
|
48
48
|
|
|
49
49
|
VM_NAME="${1:-happy-test}"
|
|
50
50
|
TEMPLATE="${LIMA_TEMPLATE:-ubuntu-24.04}"
|
|
51
|
+
LIMA_MEMORY="${LIMA_MEMORY:-8GiB}"
|
|
52
|
+
TEMPLATE_LOCATOR="${TEMPLATE}"
|
|
53
|
+
if [[ "${TEMPLATE_LOCATOR}" == template://* ]]; then
|
|
54
|
+
TEMPLATE_LOCATOR="template:${TEMPLATE_LOCATOR#template://}"
|
|
55
|
+
elif [[ "${TEMPLATE_LOCATOR}" != template:* ]]; then
|
|
56
|
+
TEMPLATE_LOCATOR="template:${TEMPLATE_LOCATOR}"
|
|
57
|
+
fi
|
|
51
58
|
LIMA_DIR="${HOME}/.lima/${VM_NAME}"
|
|
52
59
|
LIMA_YAML="${LIMA_DIR}/lima.yaml"
|
|
53
60
|
|
|
54
61
|
echo "[lima] vm: ${VM_NAME}"
|
|
55
62
|
echo "[lima] template: ${TEMPLATE}"
|
|
63
|
+
echo "[lima] memory: ${LIMA_MEMORY} (override with LIMA_MEMORY=...)"
|
|
56
64
|
|
|
57
65
|
if [[ ! -f "${LIMA_YAML}" ]]; then
|
|
58
66
|
echo "[lima] creating VM..."
|
|
59
|
-
limactl create --name "${VM_NAME}" --tty=false "
|
|
67
|
+
limactl create --name "${VM_NAME}" --tty=false "${TEMPLATE_LOCATOR}"
|
|
60
68
|
fi
|
|
61
69
|
|
|
62
70
|
if [[ ! -f "${LIMA_YAML}" ]]; then
|
|
@@ -70,14 +78,17 @@ limactl stop "${VM_NAME}" >/dev/null 2>&1 || true
|
|
|
70
78
|
echo "[lima] configuring port forwarding (localhost)..."
|
|
71
79
|
cp -a "${LIMA_YAML}" "${LIMA_YAML}.bak.$(date +%Y%m%d-%H%M%S)"
|
|
72
80
|
|
|
73
|
-
python3 - <<'PY'
|
|
81
|
+
VM_NAME="${VM_NAME}" LIMA_YAML="${LIMA_YAML}" LIMA_MEMORY="${LIMA_MEMORY}" python3 - <<'PY'
|
|
74
82
|
import os, re
|
|
75
83
|
from pathlib import Path
|
|
76
84
|
|
|
77
85
|
vm_name = os.environ["VM_NAME"]
|
|
78
86
|
path = Path(os.environ["LIMA_YAML"])
|
|
87
|
+
memory = os.environ.get("LIMA_MEMORY", "8GiB")
|
|
79
88
|
text = path.read_text(encoding="utf-8")
|
|
80
89
|
|
|
90
|
+
MEM_MARK_BEGIN = "# --- happy-stacks vm sizing (added by happy-local) ---"
|
|
91
|
+
MEM_MARK_END = "# --- /happy-stacks vm sizing ---"
|
|
81
92
|
MARK_BEGIN = "# --- happy-stacks port forwards (added by happy-local) ---"
|
|
82
93
|
MARK_END = "# --- /happy-stacks port forwards ---"
|
|
83
94
|
|
|
@@ -86,6 +97,12 @@ entries = [
|
|
|
86
97
|
" - guestPortRange: [18000, 19099]\n hostPortRange: [18000, 19099]\n",
|
|
87
98
|
]
|
|
88
99
|
|
|
100
|
+
mem_block = (
|
|
101
|
+
f"\n{MEM_MARK_BEGIN}\n"
|
|
102
|
+
f'memory: "{memory}"\n'
|
|
103
|
+
f"{MEM_MARK_END}\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
89
106
|
block_as_section = (
|
|
90
107
|
f"\n{MARK_BEGIN}\n"
|
|
91
108
|
"portForwards:\n"
|
|
@@ -99,6 +116,20 @@ block_as_list_items = (
|
|
|
99
116
|
f" # --- /happy-stacks port forwards ---\n"
|
|
100
117
|
)
|
|
101
118
|
|
|
119
|
+
if MEM_MARK_BEGIN in text and MEM_MARK_END in text:
|
|
120
|
+
text = re.sub(
|
|
121
|
+
re.escape(MEM_MARK_BEGIN) + r"[\\s\\S]*?" + re.escape(MEM_MARK_END) + r"\\n?",
|
|
122
|
+
mem_block.strip("\n") + "\n",
|
|
123
|
+
text,
|
|
124
|
+
flags=re.MULTILINE,
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
m = re.search(r"^memory:\\s*.*$", text, flags=re.MULTILINE)
|
|
128
|
+
if m:
|
|
129
|
+
text = re.sub(r"^memory:\\s*.*$", f'memory: "{memory}"', text, flags=re.MULTILINE)
|
|
130
|
+
else:
|
|
131
|
+
text = text.rstrip() + mem_block
|
|
132
|
+
|
|
102
133
|
if MARK_BEGIN in text and MARK_END in text:
|
|
103
134
|
text = re.sub(
|
|
104
135
|
re.escape(MARK_BEGIN) + r"[\\s\\S]*?" + re.escape(MARK_END) + r"\\n?",
|
|
@@ -138,5 +169,6 @@ Then:
|
|
|
138
169
|
|
|
139
170
|
Tip:
|
|
140
171
|
Open the printed URLs on your macOS host via http://localhost:<port> or http://*.localhost:<port>.
|
|
172
|
+
For `npx happy-stacks review-pr ...` inside the VM, pass `--vm-ports` so stack ports land in the forwarded ranges.
|
|
141
173
|
EOF
|
|
142
174
|
|