updating paths
This commit is contained in:
@@ -175,27 +175,27 @@ describe("worktree helpers", () => {
|
||||
it("rebinds same-repo workspace paths onto the current worktree root", () => {
|
||||
expect(
|
||||
rebindWorkspaceCwd({
|
||||
sourceRepoRoot: "/Users/nmurray/paperclip",
|
||||
targetRepoRoot: "/Users/nmurray/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/nmurray/paperclip",
|
||||
sourceRepoRoot: "/Users/example/paperclip",
|
||||
targetRepoRoot: "/Users/example/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/example/paperclip",
|
||||
}),
|
||||
).toBe("/Users/nmurray/paperclip-pr-432");
|
||||
).toBe("/Users/example/paperclip-pr-432");
|
||||
|
||||
expect(
|
||||
rebindWorkspaceCwd({
|
||||
sourceRepoRoot: "/Users/nmurray/paperclip",
|
||||
targetRepoRoot: "/Users/nmurray/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/nmurray/paperclip/packages/db",
|
||||
sourceRepoRoot: "/Users/example/paperclip",
|
||||
targetRepoRoot: "/Users/example/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/example/paperclip/packages/db",
|
||||
}),
|
||||
).toBe("/Users/nmurray/paperclip-pr-432/packages/db");
|
||||
).toBe("/Users/example/paperclip-pr-432/packages/db");
|
||||
});
|
||||
|
||||
it("does not rebind paths outside the source repo root", () => {
|
||||
expect(
|
||||
rebindWorkspaceCwd({
|
||||
sourceRepoRoot: "/Users/nmurray/paperclip",
|
||||
targetRepoRoot: "/Users/nmurray/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/nmurray/other-project",
|
||||
sourceRepoRoot: "/Users/example/paperclip",
|
||||
targetRepoRoot: "/Users/example/paperclip-pr-432",
|
||||
workspaceCwd: "/Users/example/other-project",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
62
doc/experimental/issue-worktree-support.md
Normal file
62
doc/experimental/issue-worktree-support.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Issue worktree support
|
||||
|
||||
Status: experimental, runtime-only, not shipping as a user-facing feature yet.
|
||||
|
||||
This branch contains the runtime and seeding work needed for issue-scoped worktrees:
|
||||
|
||||
- project execution workspace policy support
|
||||
- issue-level execution workspace settings
|
||||
- git worktree realization for isolated issue execution
|
||||
- optional command-based worktree provisioning
|
||||
- seeded worktree fixes for secrets key compatibility
|
||||
- seeded project workspace rebinding to the current git worktree
|
||||
|
||||
We are intentionally not shipping the UI for this yet. The runtime code remains in place, but the main UI entrypoints are hard-gated off for now.
|
||||
|
||||
## What works today
|
||||
|
||||
- projects can carry execution workspace policy in the backend
|
||||
- issues can carry execution workspace settings in the backend
|
||||
- heartbeat execution can realize isolated git worktrees
|
||||
- runtime can run a project-defined provision command inside the derived worktree
|
||||
- seeded worktree instances can keep local-encrypted secrets working
|
||||
- seeded worktree instances can rebind same-repo project workspace paths onto the current git worktree
|
||||
|
||||
## Hidden UI entrypoints
|
||||
|
||||
These are the current user-facing UI surfaces for the feature, now intentionally disabled:
|
||||
|
||||
- project settings:
|
||||
- `ui/src/components/ProjectProperties.tsx`
|
||||
- execution workspace policy controls
|
||||
- git worktree base ref / branch template / parent dir
|
||||
- provision / teardown command inputs
|
||||
|
||||
- issue creation:
|
||||
- `ui/src/components/NewIssueDialog.tsx`
|
||||
- isolated issue checkout toggle
|
||||
- defaulting issue execution workspace settings from project policy
|
||||
|
||||
- issue editing:
|
||||
- `ui/src/components/IssueProperties.tsx`
|
||||
- issue-level workspace mode toggle
|
||||
- defaulting issue execution workspace settings when project changes
|
||||
|
||||
- agent/runtime settings:
|
||||
- `ui/src/adapters/runtime-json-fields.tsx`
|
||||
- runtime services JSON field, which is part of the broader workspace-runtime support surface
|
||||
|
||||
## Why the UI is hidden
|
||||
|
||||
- the runtime behavior is still being validated
|
||||
- the workflow and operator ergonomics are not final
|
||||
- we do not want to expose a partially-baked user-facing feature in issues, projects, or settings
|
||||
|
||||
## Re-enable plan
|
||||
|
||||
When this is ready to ship:
|
||||
|
||||
- re-enable the gated UI sections in the files above
|
||||
- review wording and defaults for project and issue controls
|
||||
- decide which agent/runtime settings should remain advanced-only
|
||||
- add end-to-end product-level verification for the full UI workflow
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@@ -14,9 +14,6 @@ importers:
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.2
|
||||
version: 1.58.2
|
||||
cross-env:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0
|
||||
esbuild:
|
||||
specifier: ^0.27.3
|
||||
version: 0.27.3
|
||||
@@ -71,9 +68,6 @@ importers:
|
||||
drizzle-orm:
|
||||
specifier: 0.38.4
|
||||
version: 0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4)
|
||||
embedded-postgres:
|
||||
specifier: ^18.1.0-beta.16
|
||||
version: 18.1.0-beta.16
|
||||
picocolors:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@@ -327,9 +321,6 @@ importers:
|
||||
'@types/ws':
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
cross-env:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0
|
||||
supertest:
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.2
|
||||
@@ -998,9 +989,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@epic-web/invariant@1.0.0':
|
||||
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
@@ -3436,11 +3424,6 @@ packages:
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cross-env@10.1.0:
|
||||
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -6758,8 +6741,6 @@ snapshots:
|
||||
'@embedded-postgres/windows-x64@18.1.0-beta.16':
|
||||
optional: true
|
||||
|
||||
'@epic-web/invariant@1.0.0': {}
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
@@ -9274,11 +9255,6 @@ snapshots:
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cross-env@10.1.0:
|
||||
dependencies:
|
||||
'@epic-web/invariant': 1.0.0
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
@@ -2,6 +2,9 @@ import { useEffect, useState } from "react";
|
||||
import type { AdapterConfigFieldsProps } from "./types";
|
||||
import { Field, help } from "../components/agent-config-primitives";
|
||||
|
||||
// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship.
|
||||
const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false;
|
||||
|
||||
const inputClass =
|
||||
"w-full rounded-md border border-border px-2.5 py-1.5 bg-transparent outline-none text-sm font-mono placeholder:text-muted-foreground/40";
|
||||
|
||||
@@ -57,6 +60,10 @@ export function RuntimeServicesJsonField({
|
||||
config,
|
||||
mark,
|
||||
}: JsonFieldProps) {
|
||||
if (!SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existing = formatJsonObject(config.workspaceRuntime);
|
||||
const [draft, setDraft] = useState(existing);
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
||||
import { User, Hexagon, ArrowUpRight, Tag, Plus, Trash2 } from "lucide-react";
|
||||
import { AgentIcon } from "./AgentIconPicker";
|
||||
|
||||
// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship.
|
||||
const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false;
|
||||
|
||||
interface IssuePropertiesProps {
|
||||
issue: Issue;
|
||||
onUpdate: (data: Record<string, unknown>) => void;
|
||||
@@ -179,7 +182,9 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
const currentProject = issue.projectId
|
||||
? orderedProjects.find((project) => project.id === issue.projectId) ?? null
|
||||
: null;
|
||||
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
||||
const currentProjectExecutionWorkspacePolicy = SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI
|
||||
? currentProject?.executionWorkspacePolicy ?? null
|
||||
: null;
|
||||
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
|
||||
const usesIsolatedExecutionWorkspace = issue.executionWorkspaceSettings?.mode === "isolated"
|
||||
? true
|
||||
@@ -435,7 +440,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
onClick={() => {
|
||||
onUpdate({
|
||||
projectId: p.id,
|
||||
executionWorkspaceSettings: p.executionWorkspacePolicy?.enabled
|
||||
executionWorkspaceSettings: SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI && p.executionWorkspacePolicy?.enabled
|
||||
? { mode: p.executionWorkspacePolicy.defaultMode === "isolated" ? "isolated" : "project_primary" }
|
||||
: null,
|
||||
});
|
||||
|
||||
@@ -44,6 +44,8 @@ import { InlineEntitySelector, type InlineEntityOption } from "./InlineEntitySel
|
||||
|
||||
const DRAFT_KEY = "paperclip:issue-draft";
|
||||
const DEBOUNCE_MS = 800;
|
||||
// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship.
|
||||
const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false;
|
||||
|
||||
/** Return black or white hex based on background luminance (WCAG perceptual weights). */
|
||||
function getContrastTextColor(hexColor: string): string {
|
||||
@@ -426,7 +428,9 @@ export function NewIssueDialog() {
|
||||
chrome: assigneeChrome,
|
||||
});
|
||||
const selectedProject = orderedProjects.find((project) => project.id === projectId);
|
||||
const executionWorkspacePolicy = selectedProject?.executionWorkspacePolicy;
|
||||
const executionWorkspacePolicy = SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI
|
||||
? selectedProject?.executionWorkspacePolicy
|
||||
: null;
|
||||
const executionWorkspaceSettings = executionWorkspacePolicy?.enabled
|
||||
? {
|
||||
mode: useIsolatedExecutionWorkspace ? "isolated" : "project_primary",
|
||||
@@ -472,7 +476,9 @@ export function NewIssueDialog() {
|
||||
const currentPriority = priorities.find((p) => p.value === priority);
|
||||
const currentAssignee = (agents ?? []).find((a) => a.id === assigneeId);
|
||||
const currentProject = orderedProjects.find((project) => project.id === projectId);
|
||||
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
||||
const currentProjectExecutionWorkspacePolicy = SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI
|
||||
? currentProject?.executionWorkspacePolicy ?? null
|
||||
: null;
|
||||
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
|
||||
const assigneeOptionsTitle =
|
||||
assigneeAdapterType === "claude_local"
|
||||
@@ -514,7 +520,7 @@ export function NewIssueDialog() {
|
||||
const handleProjectChange = useCallback((nextProjectId: string) => {
|
||||
setProjectId(nextProjectId);
|
||||
const nextProject = orderedProjects.find((project) => project.id === nextProjectId);
|
||||
const policy = nextProject?.executionWorkspacePolicy;
|
||||
const policy = SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI ? nextProject?.executionWorkspacePolicy : null;
|
||||
executionWorkspaceDefaultProjectId.current = nextProjectId || null;
|
||||
setUseIsolatedExecutionWorkspace(Boolean(policy?.enabled && policy.defaultMode === "isolated"));
|
||||
}, [orderedProjects]);
|
||||
@@ -527,7 +533,11 @@ export function NewIssueDialog() {
|
||||
if (!project) return;
|
||||
executionWorkspaceDefaultProjectId.current = projectId;
|
||||
setUseIsolatedExecutionWorkspace(
|
||||
Boolean(project.executionWorkspacePolicy?.enabled && project.executionWorkspacePolicy.defaultMode === "isolated"),
|
||||
Boolean(
|
||||
SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI &&
|
||||
project.executionWorkspacePolicy?.enabled &&
|
||||
project.executionWorkspacePolicy.defaultMode === "isolated",
|
||||
),
|
||||
);
|
||||
}, [newIssueOpen, orderedProjects, projectId]);
|
||||
const modelOverrideOptions = useMemo<InlineEntityOption[]>(
|
||||
|
||||
@@ -26,6 +26,9 @@ const PROJECT_STATUSES = [
|
||||
{ value: "cancelled", label: "Cancelled" },
|
||||
];
|
||||
|
||||
// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship.
|
||||
const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false;
|
||||
|
||||
interface ProjectPropertiesProps {
|
||||
project: Project;
|
||||
onUpdate?: (data: Record<string, unknown>) => void;
|
||||
@@ -707,6 +710,8 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
||||
)}
|
||||
</div>
|
||||
|
||||
{SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI && (
|
||||
<>
|
||||
<Separator className="my-4" />
|
||||
|
||||
<div className="py-1.5 space-y-2">
|
||||
@@ -945,6 +950,8 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user