forked from farhoodlabs/paperclip
ece8a51e22
## Thinking Path > - Paperclip orchestrates AI agents for zero-human companies. > - This branch accumulated multiple already-tested control-plane, adapter runtime, invite, workspace, plugin, and UI quality fixes on the primary Paperclip checkout. > - `origin/master` advanced while those commits were still local, so the branch needed to be preserved and reconciled before review. > - Splitting the branch commit-by-commit against the new base produced overlapping conflicts with recently merged upstream PRs. > - This pull request keeps the remaining branch as one standalone PR because the final diff is 38 files after removing screenshot artifacts, under Greptile's 100-file cap, and can be merged independently after review. > - The benefit is that none of the local work is lost, the branch is now based on current `origin/master`, and reviewers can evaluate the reconciled changes in one place. ## What Changed - Merged the local accumulated branch with current `origin/master` and resolved the invite-flow overlaps from the newer upstream companies query helper. - Preserved the local fixes for invite existing-member behavior, invite link copy fallback, reusable workspace selection, worktree auth, static SPA fallback, markdown wrapping, plugin slot registration, cloud upstream UX/server polish, project sorting, and related tests. - Removed screenshot artifacts from the PR per review request. - Kept the PR under the requested file limit: 38 files changed, with no `pnpm-lock.yaml` or `.github/workflows/*` changes. ## Verification - `NODE_ENV=test pnpm exec vitest run ui/src/pages/CompanyInvites.test.tsx ui/src/pages/InviteLanding.test.tsx ui/src/pages/Projects.test.tsx ui/src/plugins/slots.test.ts ui/src/components/MarkdownBody.test.tsx server/src/__tests__/invite-accept-existing-member.test.ts server/src/__tests__/static-index-html.test.ts server/src/__tests__/execution-workspaces-service.test.ts server/src/__tests__/better-auth.test.ts server/src/__tests__/worktree-config.test.ts` - `NODE_ENV=test pnpm --filter @paperclipai/ui typecheck` - `NODE_ENV=test pnpm --filter @paperclipai/server typecheck` - Confirmed `git diff --name-only origin/master...HEAD | wc -l` is `38`. - Confirmed no PR diff entries match `pnpm-lock.yaml`, `.github/workflows/*`, or `screenshots/*`. ## Risks - Medium review risk because this is a bundled rescue PR rather than several narrow feature PRs. - Invite flow and company cache behavior overlapped with newer upstream changes; the merge resolution intentionally keeps the shared `companiesListQueryOptions` helper while preserving local existing-member invite behavior. - Visual review evidence is no longer attached in-repo because screenshots were removed from this PR per review request. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent, with repository tool access, terminal execution, and git/GitHub CLI operations. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] UI screenshots were intentionally removed from this PR per review request - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: CodexCoder <codexcoder@paperclip.local>
112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
bootstrapDevRunnerWorktreeEnv,
|
|
isLinkedGitWorktreeCheckout,
|
|
resolveWorktreeEnvFilePath,
|
|
} from "../dev-runner-worktree.ts";
|
|
|
|
const tempRoots = new Set<string>();
|
|
|
|
afterEach(() => {
|
|
for (const root of tempRoots) {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
tempRoots.clear();
|
|
});
|
|
|
|
function createTempRoot(prefix: string): string {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
tempRoots.add(root);
|
|
return root;
|
|
}
|
|
|
|
describe("dev-runner worktree env bootstrap", () => {
|
|
it("detects linked git worktrees from .git files", () => {
|
|
const root = createTempRoot("paperclip-dev-runner-worktree-");
|
|
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
|
|
|
|
expect(isLinkedGitWorktreeCheckout(root)).toBe(true);
|
|
});
|
|
|
|
it("loads repo-local Paperclip env for initialized worktrees without overriding explicit env", () => {
|
|
const root = createTempRoot("paperclip-dev-runner-worktree-env-");
|
|
fs.mkdirSync(path.join(root, ".paperclip"), { recursive: true });
|
|
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
|
|
fs.writeFileSync(
|
|
resolveWorktreeEnvFilePath(root),
|
|
[
|
|
"PAPERCLIP_HOME=/tmp/paperclip-worktrees",
|
|
"PAPERCLIP_INSTANCE_ID=feature-worktree",
|
|
"PAPERCLIP_IN_WORKTREE=true",
|
|
"PAPERCLIP_WORKTREE_NAME=feature-worktree",
|
|
"PAPERCLIP_OPTIONAL= # comment-only value",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
|
|
const env: NodeJS.ProcessEnv = {
|
|
PAPERCLIP_INSTANCE_ID: "already-set",
|
|
};
|
|
const result = bootstrapDevRunnerWorktreeEnv(root, env);
|
|
|
|
expect(result).toEqual({
|
|
envPath: resolveWorktreeEnvFilePath(root),
|
|
missingEnv: false,
|
|
});
|
|
expect(env.PAPERCLIP_HOME).toBe("/tmp/paperclip-worktrees");
|
|
expect(env.PAPERCLIP_INSTANCE_ID).toBe("already-set");
|
|
expect(env.PAPERCLIP_IN_WORKTREE).toBe("true");
|
|
expect(env.PAPERCLIP_OPTIONAL).toBe("");
|
|
});
|
|
|
|
it("repairs stale migrated config paths before loading worktree env", () => {
|
|
const root = createTempRoot("paperclip-dev-runner-worktree-migrated-env-");
|
|
const localConfigPath = path.join(root, ".paperclip", "config.json");
|
|
const worktreesDir = path.join(root, ".paperclip-worktrees");
|
|
fs.mkdirSync(path.dirname(localConfigPath), { recursive: true });
|
|
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
|
|
fs.writeFileSync(localConfigPath, "{}\n", "utf8");
|
|
fs.writeFileSync(
|
|
resolveWorktreeEnvFilePath(root),
|
|
[
|
|
"PAPERCLIP_HOME=/old/home/.paperclip-worktrees",
|
|
"PAPERCLIP_INSTANCE_ID=feature-worktree",
|
|
"PAPERCLIP_CONFIG=/old/home/paperclip/.paperclip/worktrees/feature/.paperclip/config.json",
|
|
"PAPERCLIP_CONTEXT=/old/home/.paperclip-worktrees/context.json",
|
|
"PAPERCLIP_IN_WORKTREE=true",
|
|
"PAPERCLIP_WORKTREE_NAME=feature-worktree",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
|
|
const env: NodeJS.ProcessEnv = {
|
|
PAPERCLIP_WORKTREES_DIR: worktreesDir,
|
|
};
|
|
const result = bootstrapDevRunnerWorktreeEnv(root, env);
|
|
|
|
expect(result).toEqual({
|
|
envPath: resolveWorktreeEnvFilePath(root),
|
|
missingEnv: false,
|
|
});
|
|
expect(env.PAPERCLIP_HOME).toBe(worktreesDir);
|
|
expect(env.PAPERCLIP_CONFIG).toBe(localConfigPath);
|
|
expect(env.PAPERCLIP_CONTEXT).toBe(path.join(worktreesDir, "context.json"));
|
|
expect(env.PAPERCLIP_INSTANCE_ID).toBe("feature-worktree");
|
|
});
|
|
|
|
it("reports uninitialized linked worktrees so dev runner can fail fast", () => {
|
|
const root = createTempRoot("paperclip-dev-runner-worktree-missing-");
|
|
fs.writeFileSync(path.join(root, ".git"), "gitdir: /tmp/paperclip/.git/worktrees/feature\n", "utf8");
|
|
|
|
expect(bootstrapDevRunnerWorktreeEnv(root, {})).toEqual({
|
|
envPath: resolveWorktreeEnvFilePath(root),
|
|
missingEnv: true,
|
|
});
|
|
});
|
|
});
|