test: remove repeated setup and strengthen regression proof (#16022)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Its tests protect company boundaries, approvals, durable tasks,
credentials and the board UI.
> - Several owner suites repeat large database fixtures and render
frames.
> - Some tests repeat stronger proof or pass when the behavior they
describe is broken.
> - This pull request shares exact setup, removes proven duplicate cases
and repairs weak observations.
> - It preserves distinct security, lifecycle, concurrency and
user-facing contracts.
> - The benefit is 5,027 fewer test lines and stronger regression
checks.

## Linked Issues or Issue Description

Refs #15978. This continues the merged test-audit cleanup in [PR
#15978](https://github.com/paperclipai/paperclip/pull/15978), which
removed 11,976 test/support lines. The savings in this pull request are
additional.

Related open work: [PR
#13460](https://github.com/paperclipai/paperclip/pull/13460) changes
legacy OAuth parent/version lock ordering. This pull request retains
those production paths and the existing lock-order test. Its secrets
changes compact fixtures and repair the two AWS wrapper exclusion
observations.

Scoped title searches found no open duplicate audit or
fixture-consolidation PR and no matching audit issue. The problem is
described here using the enhancement template.

**What existing behavior does this improve?**

The maintenance and regression value of the existing server, UI, CLI,
shared and wiki-plugin test suites.

**Subsystem affected**

Cross-cutting test code. Two UI modules also lose unused test inspection
exports.

**Current behavior**

Owner tests repeat database rows, service inputs, HTTP setup and render
frames. Some cases repeat stronger assertions. Two AWS lock tests record
only cleanup events, so an AWS provider write can happen inside the held
lock without failing those observations. Several other negative tests
accept or do not observe the wrong result. Worktree fixtures also use a
temporary path alias that conflicts with the existing canonical-path
checks on macOS.

**Proposed behavior**

Use small local helpers that preserve exact inputs, fresh allocations,
callbacks, query order and await positions. Remove only cases with named
stronger keepers. Tighten negative assertions and observe actual AWS
provider entry while a confirmed lock holder remains parked. Resolve
temporary fixture roots to their canonical paths.

**Reason and benefit**

Remove 5,027 net test lines and 15 redundant registered executions.
Remove 11 lines of unused UI inspection exports. Keep every distinct
company, actor, approval, credential, persistence, stale-write and
concurrency contract. No wall-time speedup is claimed for setup
compaction.

**Breaking changes**

None for application behavior, APIs or stored data. The removed exports
expose private UI test state and have no remaining callers.

## What Changed

- Consolidate repeated runtime, workspace, portability, route,
tool-access, policy, gateway and state-service fixtures.
- Share repeated UI render frames and CLI/wiki-plugin setup while
retaining the original actions and expected results.
- Remove 15 repeated case executions only where the audit records an
exact stronger keeper.
- Repair weak HTTP/UI negative assertions and two AWS account-home lock
observations. Keep the existing durable results and cleanup behavior.
- Remove the unused `__inflightGetCount` and `__secondTickInternals`
inspection exports. Keep observable request coalescing and ticker
lifecycle checks.
- Canonicalize temporary worktree fixture paths without weakening
production path checks.
- Record the owner decisions, savings, independent preservation reviews
and validation in [the dated audit
report](https://github.com/paperclipai/paperclip/blob/58c5c4aa241c85f4665a3159c192888bbb2a5cfa/doc/plans/2026-10-12-test-audit-rounds.md)
and [the compact control
evidence](https://github.com/paperclipai/paperclip/blob/58c5c4aa241c85f4665a3159c192888bbb2a5cfa/doc/plans/2026-10-12-test-audit-controls.json).

## Verification

- Complete owner and retained-sibling baselines passed before each audit
cutover. Independent reviewers checked the full original tests, actual
helper bodies, exact inputs, receipt provenance, async order, fresh
allocations and case registrations.
- All 14 UI/route/wiki mutations failed at their intended assertions:
ten UI/API/ticker controls, one route control and three wiki
persistence/receipt controls. Each mutated production source was
restored to its exact original bytes.
- Both old AWS lock tests passed with their respective AWS-only lock
bypass. Both repaired tests failed at the intended assertions with the
same bypasses. These give 16 intended failures and two passing weak
controls across 18 executions. Each source was restored exactly.
- Final complete state-owner/sibling run: 329/329 passed at code
revision `64370831ebe191e5ce092f85f79b6fb661df7e16`.
- Final `pnpm -r typecheck`, `pnpm check:token-gates` and `pnpm build`:
passed at the same code revision.
- Delivery head `58c5c4aa241c85f4665a3159c192888bbb2a5cfa` includes the
evidence commit and master `4b766b872993045a2e787404f0475503683e5ded`.
All 27 audited test files and both application files match the locally
verified code revision byte-for-byte. Current-head CI validates the
integrated branch, including the incoming master changes.
- All eight production-owner SHA guards for the 16 deliberate failing
mutation controls still match the integrated delivery checkout. The
mutation receipts therefore apply to those current owners.
- Full default `pnpm test:run`: **failed** with exit 1 at frozen code
revision `64370831ebe191e5ce092f85f79b6fb661df7e16`; HEAD and tracked
source bytes remained unchanged. The first general-server group reported
878 passing files, four failing files and six skipped files; 17,846
passing cases, 24 failing cases and 105 skipped cases. Later groups did
not run. Failures were `http-log-redaction` (socket hangup),
`public-mcp` (initial 15-second timeout followed by hosted token
“Refresh before subscribing” failures), `remove-test-home` (macOS
`ENOTEMPTY` where permission errors were expected, plus cleanup
failure), and `workspace-git-snapshot-streaming` (300-second timeout).
All four failing test files and the relevant cleanup/MCP production
owners are unchanged by this audit. Full local success is not claimed.
- Focused complete-owner diagnosis at the same frozen revision: 149/152
passed, three failed, zero skipped. `public-mcp` passed 82/82 and
`http-log-redaction` passed 66/66 unchanged. The two `remove-test-home`
cases reproduced macOS `ENOTEMPTY` against an expected `EACCES`/`EPERM`
contract; the 32 MiB workspace snapshot case reproduced its existing
300,000 ms timeout. HEAD and tracked files stayed clean. Exact blob
comparison confirmed all four test files and the relevant cleanup/MCP
owners match both inspected master
`4b766b872993045a2e787404f0475503683e5ded` and the frozen local
revision. The three remaining local failures are unresolved; no timeout,
retry or assertion was weakened.
- Current-head CI: [workflow
38299262149](https://github.com/paperclipai/paperclip/actions/runs/38299262149)
has passed runner selection and created all downstream
test/typecheck/build gates; those gates are queued, with no failures
reported. Greptile completed successfully at
`58c5c4aa241c85f4665a3159c192888bbb2a5cfa` with [5/5 and zero
comments](https://github.com/paperclipai/paperclip/pull/16022#issuecomment-6131457592).
All review threads are resolved or absent. This pull request will remain
open after verification.

Reviewers can reproduce the required workspace checks:

```sh
pnpm -r typecheck
pnpm check:token-gates
pnpm test:run
pnpm build
```

Browser suites were not run. Their sources and user journeys are
unchanged by this audit.

## Risks

- A fixture helper can hide a specialized input or change object
freshness. Full-program expansion checks and independent reviews
preserve the exact old inputs and execution order.
- Removing duplicate proof can remove a distinct contract by mistake.
The audit records the keeper for each deletion; all distinct security
and lifecycle cases remain.
- Concurrency checks can miss progress during a fixed delay. The AWS
repairs record actual provider entry, confirm holder entry, use measured
entry windows, require exact final event order and drain both operations
in `finally`.
- The full local default run failed. Focused diagnosis reproduced two
macOS cleanup failures and one existing snapshot timeout in unchanged
owners; these remain unresolved. Current-head CI is pending. No test
deadline, retry policy, skip or CI shard was weakened to obtain a pass.

> 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 with GPT-6, reasoning, repository inspection, code
execution and delegated audit/review. The runtime does not expose the
exact served model identifier or context-window size; those values are
unavailable and are not inferred from the model catalog.

## 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 searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-12 22:23:11 +00:00
1 parent 9e721264e9
commit dabb8c5daf
32 files changed
+4002 -8288

No files matched your search

+119 -198
View File
@@ -62,6 +62,109 @@ function company(overrides: Record<string, unknown> = {}) {
};
}
function allImportEntities(): CompanyPortabilityPreviewResult["include"] {
return {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
};
}
function sourceCompanyManifest(): NonNullable<CompanyPortabilityPreviewResult["manifest"]["company"]> {
return {
path: "COMPANY.md",
name: "Source Co",
description: null,
logoPath: null,
requireBoardApprovalForNewAgents: false,
feedbackDataSharingEnabled: false,
feedbackDataSharingConsentAt: null,
feedbackDataSharingConsentByUserId: null,
feedbackDataSharingTermsVersion: null,
};
}
function ceoManifest(): CompanyPortabilityPreviewResult["manifest"]["agents"][number] {
return {
slug: "ceo",
name: "CEO",
path: "agents/ceo/AGENT.md",
skills: [],
role: "ceo",
title: null,
icon: null,
capabilities: null,
reportsToSlug: null,
reportsToExistingAgentId: null,
reportsToExistingAgentSlug: null,
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
permissionGrants: [],
budgetMonthlyCents: 0,
metadata: null,
};
}
function alphaProjectManifest(): CompanyPortabilityPreviewResult["manifest"]["projects"][number] {
return {
slug: "alpha",
name: "Alpha",
path: "projects/alpha/PROJECT.md",
description: null,
ownerAgentSlug: null,
leadAgentSlug: null,
targetDate: null,
color: null,
icon: null,
status: null,
executionWorkspacePolicy: null,
workspaces: [],
env: null,
metadata: null,
};
}
function kickoffIssueManifest(): CompanyPortabilityPreviewResult["manifest"]["issues"][number] {
return {
slug: "kickoff",
identifier: null,
title: "Kickoff",
path: "projects/alpha/issues/kickoff/TASK.md",
projectSlug: "alpha",
projectWorkspaceKey: null,
assigneeAgentSlug: "ceo",
description: null,
recurring: false,
routine: null,
legacyRecurrence: null,
status: null,
priority: null,
labelIds: [],
billingCode: null,
executionWorkspaceSettings: null,
assigneeAdapterOverrides: null,
comments: [],
metadata: null,
};
}
function openAiEnvInput(): CompanyPortabilityPreviewResult["envInputs"][number] {
return {
key: "OPENAI_API_KEY",
description: null,
agentSlug: "ceo",
projectSlug: null,
kind: "secret",
requirement: "required",
defaultValue: null,
portability: "portable",
};
}
describe("company CLI commands", () => {
let fetchMock: ReturnType<typeof vi.fn>;
let logSpy: ReturnType<typeof vi.spyOn>;
@@ -298,13 +401,7 @@ describe("buildCompanyDashboardUrl", () => {
describe("renderCompanyImportPreview", () => {
it("summarizes the preview with counts, selection info, and truncated examples", () => {
const preview: CompanyPortabilityPreviewResult = {
include: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
include: allImportEntities(),
targetCompanyId: "company-123",
targetCompanyName: "Imported Co",
collisionStrategy: "rename",
@@ -334,49 +431,14 @@ describe("renderCompanyImportPreview", () => {
companyId: "company-src",
companyName: "Source Co",
},
includes: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
company: {
path: "COMPANY.md",
name: "Source Co",
description: null,
logoPath: null,
requireBoardApprovalForNewAgents: false,
feedbackDataSharingEnabled: false,
feedbackDataSharingConsentAt: null,
feedbackDataSharingConsentByUserId: null,
feedbackDataSharingTermsVersion: null,
},
includes: allImportEntities(),
company: sourceCompanyManifest(),
sidebar: {
agents: ["ceo"],
projects: ["alpha"],
},
agents: [
{
slug: "ceo",
name: "CEO",
path: "agents/ceo/AGENT.md",
skills: [],
role: "ceo",
title: null,
icon: null,
capabilities: null,
reportsToSlug: null,
reportsToExistingAgentId: null,
reportsToExistingAgentSlug: null,
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
permissionGrants: [],
budgetMonthlyCents: 0,
metadata: null,
},
ceoManifest(),
],
skills: [
{
@@ -395,73 +457,20 @@ describe("renderCompanyImportPreview", () => {
},
],
projects: [
{
slug: "alpha",
name: "Alpha",
path: "projects/alpha/PROJECT.md",
description: null,
ownerAgentSlug: null,
leadAgentSlug: null,
targetDate: null,
color: null,
icon: null,
status: null,
executionWorkspacePolicy: null,
workspaces: [],
env: null,
metadata: null,
},
alphaProjectManifest(),
],
issues: [
{
slug: "kickoff",
identifier: null,
title: "Kickoff",
path: "projects/alpha/issues/kickoff/TASK.md",
projectSlug: "alpha",
projectWorkspaceKey: null,
assigneeAgentSlug: "ceo",
description: null,
recurring: false,
routine: null,
legacyRecurrence: null,
status: null,
priority: null,
labelIds: [],
billingCode: null,
executionWorkspaceSettings: null,
assigneeAdapterOverrides: null,
comments: [],
metadata: null,
},
kickoffIssueManifest(),
],
envInputs: [
{
key: "OPENAI_API_KEY",
description: null,
agentSlug: "ceo",
projectSlug: null,
kind: "secret",
requirement: "required",
defaultValue: null,
portability: "portable",
},
openAiEnvInput(),
],
},
files: {
"COMPANY.md": "# Source Co",
},
envInputs: [
{
key: "OPENAI_API_KEY",
description: null,
agentSlug: "ceo",
projectSlug: null,
kind: "secret",
requirement: "required",
defaultValue: null,
portability: "portable",
},
openAiEnvInput(),
],
warnings: ["One warning"],
errors: ["One error"],
@@ -545,13 +554,7 @@ describe("renderCompanyImportResult", () => {
describe("import selection catalog", () => {
it("defaults to everything and keeps project selection separate from task selection", () => {
const preview: CompanyPortabilityPreviewResult = {
include: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
include: allImportEntities(),
targetCompanyId: "company-123",
targetCompanyName: "Imported Co",
collisionStrategy: "rename",
@@ -569,13 +572,7 @@ describe("import selection catalog", () => {
companyId: "company-src",
companyName: "Source Co",
},
includes: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
includes: allImportEntities(),
company: {
path: "COMPANY.md",
name: "Source Co",
@@ -592,26 +589,7 @@ describe("import selection catalog", () => {
projects: ["alpha"],
},
agents: [
{
slug: "ceo",
name: "CEO",
path: "agents/ceo/AGENT.md",
skills: [],
role: "ceo",
title: null,
icon: null,
capabilities: null,
reportsToSlug: null,
reportsToExistingAgentId: null,
reportsToExistingAgentSlug: null,
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
permissionGrants: [],
budgetMonthlyCents: 0,
metadata: null,
},
ceoManifest(),
],
skills: [
{
@@ -630,45 +608,10 @@ describe("import selection catalog", () => {
},
],
projects: [
{
slug: "alpha",
name: "Alpha",
path: "projects/alpha/PROJECT.md",
description: null,
ownerAgentSlug: null,
leadAgentSlug: null,
targetDate: null,
color: null,
icon: null,
status: null,
executionWorkspacePolicy: null,
workspaces: [],
env: null,
metadata: null,
},
alphaProjectManifest(),
],
issues: [
{
slug: "kickoff",
identifier: null,
title: "Kickoff",
path: "projects/alpha/issues/kickoff/TASK.md",
projectSlug: "alpha",
projectWorkspaceKey: null,
assigneeAgentSlug: "ceo",
description: null,
recurring: false,
routine: null,
legacyRecurrence: null,
status: null,
priority: null,
labelIds: [],
billingCode: null,
executionWorkspaceSettings: null,
assigneeAdapterOverrides: null,
comments: [],
metadata: null,
},
kickoffIssueManifest(),
],
envInputs: [],
},
@@ -720,13 +663,7 @@ describe("import selection catalog", () => {
it("includes extension file even when all entities are deselected", () => {
const preview: CompanyPortabilityPreviewResult = {
include: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
include: allImportEntities(),
targetCompanyId: "company-123",
targetCompanyName: "Imported Co",
collisionStrategy: "rename",
@@ -744,24 +681,8 @@ describe("import selection catalog", () => {
companyId: "company-src",
companyName: "Source Co",
},
includes: {
company: true,
agents: true,
projects: true,
issues: true,
skills: true,
},
company: {
path: "COMPANY.md",
name: "Source Co",
description: null,
logoPath: null,
requireBoardApprovalForNewAgents: false,
feedbackDataSharingEnabled: false,
feedbackDataSharingConsentAt: null,
feedbackDataSharingConsentByUserId: null,
feedbackDataSharingTermsVersion: null,
},
includes: allImportEntities(),
company: sourceCompanyManifest(),
sidebar: {
agents: [],
projects: [],
+76 -131
View File
@@ -285,9 +285,37 @@ function buildSourceConfig(): PaperclipConfig {
};
}
function writeSeedConfigFiles(
sourceConfigPath: string,
targetConfigPath: string,
targetRoot: string,
targetPaths: ReturnType<typeof resolveWorktreeLocalPaths>,
sourceConfig: PaperclipConfig,
targetConfig: PaperclipConfig,
): void {
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
}
function initializeTestRepo(repoRoot: string): void {
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
}
describe("worktree helpers", () => {
it("uses the repo-local config for the current worktree", () => {
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-current-worktree-"));
const targetRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-current-worktree-"));
try {
const localConfig = path.join(targetRoot, ".paperclip", "config.json");
fs.mkdirSync(path.dirname(localConfig), { recursive: true });
@@ -307,7 +335,7 @@ describe("worktree helpers", () => {
});
it("uses the repository config from a nested working directory", () => {
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-current-worktree-nested-"));
const targetRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-current-worktree-nested-"));
try {
execFileSync("git", ["init", "-q"], { cwd: targetRoot });
const nestedDirectory = path.join(targetRoot, "packages", "example", "src");
@@ -517,7 +545,7 @@ describe("worktree helpers", () => {
});
it("requires the seed process to own the target embedded Postgres lifecycle", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-live-target-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-live-target-"));
try {
fs.writeFileSync(
path.join(tempRoot, "postmaster.pid"),
@@ -531,18 +559,6 @@ describe("worktree helpers", () => {
}
});
it("surfaces a credential-safe diagnostic when the target shuts down during restore", () => {
expect(formatWorktreeSeedFailureDiagnostic(
"restore",
new Error(
"Failed to restore seed.sql.gz: FATAL: the database system is shutting down; psql error: write EPIPE",
),
)).toBe(
"Target embedded PostgreSQL shut down during restore. Stop any competing worktree service and retry the seed.",
);
expect(formatWorktreeSeedFailureDiagnostic("migrations", new Error("secret connection failure")))
.toBe("Seed failed during migrations.");
});
it("surfaces the missing credential artifact for authenticated seed validation", () => {
expect(formatWorktreeSeedFailureDiagnostic(
@@ -610,7 +626,7 @@ describe("worktree helpers", () => {
itEmbeddedPostgres("recognizes positive legacy database schema evidence", async () => {
const tempDb = await startEmbeddedPostgresTestDatabase("paperclip-worktree-legacy-evidence-");
onTestFinished(() => tempDb.cleanup());
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-legacy-config-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-legacy-config-"));
try {
const configPath = path.join(tempRoot, "config.json");
const sourceConfig = buildSourceConfig();
@@ -644,7 +660,7 @@ describe("worktree helpers", () => {
});
it("ensure-seeded seeds once and fast-exits on the verified manifest", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -661,15 +677,7 @@ describe("worktree helpers", () => {
serverPort: 3199,
databasePort: 54999,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
const seedDatabase = vi.fn().mockResolvedValue({
@@ -715,7 +723,7 @@ describe("worktree helpers", () => {
});
it("treats an unregistered markerless config as a normal non-worktree boot", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unregistered-markerless-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unregistered-markerless-"));
try {
const configPath = path.join(tempRoot, "config.json");
fs.writeFileSync(configPath, `${JSON.stringify(buildSourceConfig())}\n`);
@@ -740,7 +748,7 @@ describe("worktree helpers", () => {
});
it("honors a legacy complete marker without resolving a seed source", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-complete-marker-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-complete-marker-"));
try {
const configPath = path.join(tempRoot, "config.json");
fs.writeFileSync(configPath, `${JSON.stringify(buildSourceConfig())}\n`);
@@ -763,7 +771,7 @@ describe("worktree helpers", () => {
});
it("seeds a configured worktree with no seed markers when no legacy database is present", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unmarked-empty-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unmarked-empty-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -780,15 +788,7 @@ describe("worktree helpers", () => {
serverPort: 3194,
databasePort: 54994,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
const inspectLegacyDatabase = vi.fn().mockResolvedValue(null);
const seedDatabase = vi.fn().mockResolvedValue(mockVerifiedSeedResult());
@@ -810,7 +810,7 @@ describe("worktree helpers", () => {
});
it("adopts a markerless legacy worktree only after validating its database schema", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unmarked-legacy-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unmarked-legacy-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -827,15 +827,7 @@ describe("worktree helpers", () => {
serverPort: 3193,
databasePort: 54993,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
const seedDatabase = vi.fn();
await expect(ensureWorktreeSeeded(
@@ -858,7 +850,7 @@ describe("worktree helpers", () => {
});
it("managed ensure-seeded derives a valid source from the registered base workspace", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-managed-seed-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-managed-seed-"));
try {
const baseRoot = path.join(tempRoot, "base");
const sourceConfigPath = path.join(baseRoot, ".paperclip", "config.json");
@@ -907,7 +899,7 @@ describe("worktree helpers", () => {
it.each(["sibling", "foreign_instance", "symlink", "instance_mismatch"] as const)(
"managed ensure-seeded re-derives a stale %s manifest source from registration",
async (variant) => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), `paperclip-worktree-managed-${variant}-`));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), `paperclip-worktree-managed-${variant}-`));
try {
const baseRoot = path.join(tempRoot, "base");
const canonicalSource = path.join(baseRoot, ".paperclip", "config.json");
@@ -981,7 +973,9 @@ describe("worktree helpers", () => {
);
it("ensure-seeded records a target shutdown diagnostic when restore fails", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-failure-"));
expect(formatWorktreeSeedFailureDiagnostic("migrations", new Error("secret connection failure")))
.toBe("Seed failed during migrations.");
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-failure-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -998,15 +992,7 @@ describe("worktree helpers", () => {
serverPort: 3198,
databasePort: 54998,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
await expect(
@@ -1044,7 +1030,7 @@ describe("worktree helpers", () => {
});
it("serializes concurrent ensure-seeded calls across the seed marker lock", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-lock-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-lock-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -1061,15 +1047,7 @@ describe("worktree helpers", () => {
serverPort: 3197,
databasePort: 54997,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
const seedDatabase = vi.fn(async () => {
@@ -1094,7 +1072,7 @@ describe("worktree helpers", () => {
});
it("records an interrupted phase before retrying to a verified terminal state", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-interrupted-seed-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-interrupted-seed-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "worktree");
@@ -1111,15 +1089,7 @@ describe("worktree helpers", () => {
serverPort: 3196,
databasePort: 54996,
});
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
fs.writeFileSync(
path.join(targetRoot, ".paperclip", ".env"),
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
);
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
const interrupted = readWorktreeSeedManifest(targetConfigPath)!;
fs.writeFileSync(
@@ -1147,7 +1117,7 @@ describe("worktree helpers", () => {
});
it("fails closed instead of racing to reclaim a stale seed lock", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-stale-lock-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-stale-lock-"));
try {
const targetConfigPath = path.join(tempRoot, ".paperclip", "config.json");
const lockPath = path.join(tempRoot, ".paperclip", "seed.lock");
@@ -1413,7 +1383,7 @@ describe("worktree helpers", () => {
});
it("copies the source local_encrypted secrets key into the seeded worktree instance", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-secrets-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-secrets-"));
const originalInlineMasterKey = process.env.PAPERCLIP_SECRETS_MASTER_KEY;
const originalKeyFile = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
try {
@@ -1452,7 +1422,7 @@ describe("worktree helpers", () => {
});
it("writes the source inline secrets master key into the seeded worktree instance", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-secrets-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-secrets-"));
try {
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetKeyPath = path.join(tempRoot, "target", "secrets", "master.key");
@@ -1473,7 +1443,7 @@ describe("worktree helpers", () => {
});
it("creates an explicitly empty worktree without inherited signing secrets or deferred copying", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-empty-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-empty-"));
const originalCwd = process.cwd();
const originalJwt = process.env.PAPERCLIP_AGENT_JWT_SECRET;
const originalSigning = process.env.PAPERCLIP_TOOL_ACTION_SIGNING_SECRET;
@@ -1503,7 +1473,7 @@ describe("worktree helpers", () => {
});
it("persists the current agent jwt secret into the worktree env file", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-jwt-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-jwt-"));
const repoRoot = path.join(tempRoot, "repo");
const originalCwd = process.cwd();
const originalJwtSecret = process.env.PAPERCLIP_AGENT_JWT_SECRET;
@@ -1544,7 +1514,7 @@ describe("worktree helpers", () => {
});
it("preserves repo-managed worktree checkouts when --force re-runs from the source repo", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-force-preserve-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-force-preserve-"));
const repoRoot = path.join(tempRoot, "repo");
const originalCwd = process.cwd();
@@ -1585,7 +1555,7 @@ describe("worktree helpers", () => {
itEmbeddedPostgres(
"seeds a local-trusted implicit board user without a credential account",
async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-local-board-seed-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-local-board-seed-"));
const originalCwd = process.cwd();
onTestFinished(() => {
process.chdir(originalCwd);
@@ -1687,7 +1657,7 @@ describe("worktree helpers", () => {
itEmbeddedPostgres(
"seeds a lagging source whose migration application order differs from filename order",
async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-auth-seed-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-auth-seed-"));
const originalCwd = process.cwd();
onTestFinished(() => {
process.chdir(originalCwd);
@@ -1866,7 +1836,7 @@ describe("worktree helpers", () => {
);
it("avoids ports already claimed by sibling worktree instance configs", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-claimed-ports-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-claimed-ports-"));
const repoRoot = path.join(tempRoot, "repo");
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
const siblingInstanceRoot = path.join(homeDir, "instances", "existing-worktree");
@@ -1947,7 +1917,7 @@ describe("worktree helpers", () => {
});
it("reserves distinct ports for postgres-mode siblings under a custom worktree parent", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-custom-parent-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-custom-parent-"));
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
const customParentDir = path.join(tempRoot, "custom", "workspace-lanes");
const firstWorktreeRoot = path.join(customParentDir, "lane-one");
@@ -2002,7 +1972,7 @@ describe("worktree helpers", () => {
});
it("defaults the seed source config to the current repo-local Paperclip config", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-source-config-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-source-config-"));
const repoRoot = path.join(tempRoot, "repo");
const localConfigPath = path.join(repoRoot, ".paperclip", "config.json");
const originalCwd = process.cwd();
@@ -2027,7 +1997,7 @@ describe("worktree helpers", () => {
});
it("preserves the source config path across worktree:make cwd changes", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-source-override-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-source-override-"));
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
const targetRoot = path.join(tempRoot, "target");
const originalCwd = process.cwd();
@@ -2070,7 +2040,7 @@ describe("worktree helpers", () => {
});
it("derives worktree reseed target paths from the adjacent env file", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-target-"));
const worktreeRoot = path.join(tempRoot, "repo");
const configPath = path.join(worktreeRoot, ".paperclip", "config.json");
const envPath = path.join(worktreeRoot, ".paperclip", ".env");
@@ -2102,7 +2072,7 @@ describe("worktree helpers", () => {
});
it("rejects reseed targets without worktree env metadata", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-missing-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-target-missing-"));
const worktreeRoot = path.join(tempRoot, "repo");
const configPath = path.join(worktreeRoot, ".paperclip", "config.json");
@@ -2127,7 +2097,7 @@ describe("worktree helpers", () => {
});
itEmbeddedPostgres("reseed preserves the current worktree ports, instance id, and branding", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-"));
const repoRoot = path.join(tempRoot, "repo");
const sourceRoot = path.join(tempRoot, "source");
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
@@ -2232,7 +2202,7 @@ describe("worktree helpers", () => {
});
it("restores the current worktree config and instance data if reseed fails", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-rollback-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-rollback-"));
const repoRoot = path.join(tempRoot, "repo");
const sourceRoot = path.join(tempRoot, "source");
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
@@ -2341,18 +2311,13 @@ describe("worktree helpers", () => {
});
it("copies shared git hooks into a linked worktree git dir", () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-hooks-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-hooks-"));
const repoRoot = path.join(tempRoot, "repo");
const worktreePath = path.join(tempRoot, "repo-feature");
try {
fs.mkdirSync(repoRoot, { recursive: true });
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
initializeTestRepo(repoRoot);
const sourceHooksDir = path.join(repoRoot, ".git", "hooks");
const sourceHookPath = path.join(sourceHooksDir, "pre-commit");
@@ -2389,7 +2354,7 @@ describe("worktree helpers", () => {
}, 15_000);
it("creates and initializes a worktree from the top-level worktree:make command", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-make-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-make-"));
const repoRoot = path.join(tempRoot, "repo");
const fakeHome = path.join(tempRoot, "home");
const worktreePath = path.join(fakeHome, "paperclip-make-test");
@@ -2399,12 +2364,7 @@ describe("worktree helpers", () => {
try {
fs.mkdirSync(repoRoot, { recursive: true });
fs.mkdirSync(fakeHome, { recursive: true });
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
initializeTestRepo(repoRoot);
process.chdir(repoRoot);
@@ -2424,18 +2384,13 @@ describe("worktree helpers", () => {
}, 20_000);
it("no-ops on the primary checkout unless --branch is provided", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-primary-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-primary-"));
const repoRoot = path.join(tempRoot, "repo");
const originalCwd = process.cwd();
try {
fs.mkdirSync(repoRoot, { recursive: true });
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
initializeTestRepo(repoRoot);
process.chdir(repoRoot);
await worktreeRepairCommand({});
@@ -2449,7 +2404,7 @@ describe("worktree helpers", () => {
});
it("repairs the current linked worktree when Paperclip metadata is missing", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-current-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-current-"));
const repoRoot = path.join(tempRoot, "repo");
const worktreePath = path.join(repoRoot, ".paperclip", "worktrees", "repair-me");
const sourceConfigPath = path.join(tempRoot, "source-config.json");
@@ -2463,12 +2418,7 @@ describe("worktree helpers", () => {
try {
fs.mkdirSync(repoRoot, { recursive: true });
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
initializeTestRepo(repoRoot);
fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
execFileSync("git", ["worktree", "add", "-b", "repair-me", worktreePath, "HEAD"], {
cwd: repoRoot,
@@ -2496,7 +2446,7 @@ describe("worktree helpers", () => {
}, 20_000);
it("creates and repairs a missing branch worktree when --branch is provided", async () => {
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-branch-"));
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-branch-"));
const repoRoot = path.join(tempRoot, "repo");
const sourceConfigPath = path.join(tempRoot, "source-config.json");
const worktreeHome = path.join(tempRoot, ".paperclip-worktrees");
@@ -2505,12 +2455,7 @@ describe("worktree helpers", () => {
try {
fs.mkdirSync(repoRoot, { recursive: true });
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
initializeTestRepo(repoRoot);
fs.writeFileSync(sourceConfigPath, JSON.stringify(buildSourceConfig(), null, 2), "utf8");
process.chdir(repoRoot);