mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
Address workspace diff review findings
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
4c98a35b8c
commit
af5f646e51
12 files changed
+68
-39
No files matched your search
+31
-20
@@ -62,6 +62,13 @@ jobs:
|
||||
pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile
|
||||
fi
|
||||
|
||||
- name: Upload resolved lockfile
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pr-pnpm-lock-${{ github.run_id }}
|
||||
path: pnpm-lock.yaml
|
||||
retention-days: 1
|
||||
|
||||
verify:
|
||||
needs: [policy]
|
||||
runs-on: ubuntu-latest
|
||||
@@ -82,12 +89,13 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Use policy-resolved lockfile
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-pnpm-lock-${{ github.run_id }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile || {
|
||||
echo "Frozen lockfile install failed; retrying without frozen lockfile because PRs do not commit pnpm-lock.yaml."
|
||||
pnpm install --no-frozen-lockfile
|
||||
}
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck workspaces whose build scripts skip TypeScript
|
||||
run: pnpm run typecheck:build-gaps
|
||||
@@ -138,12 +146,13 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Use policy-resolved lockfile
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-pnpm-lock-${{ github.run_id }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile || {
|
||||
echo "Frozen lockfile install failed; retrying without frozen lockfile because PRs do not commit pnpm-lock.yaml."
|
||||
pnpm install --no-frozen-lockfile
|
||||
}
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run serialized server test shard
|
||||
run: pnpm test:run:serialized -- --shard-index ${{ matrix.shard_index }} --shard-count ${{ matrix.shard_count }}
|
||||
@@ -169,12 +178,13 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Use policy-resolved lockfile
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-pnpm-lock-${{ github.run_id }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile || {
|
||||
echo "Frozen lockfile install failed; retrying without frozen lockfile because PRs do not commit pnpm-lock.yaml."
|
||||
pnpm install --no-frozen-lockfile
|
||||
}
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# `release.sh` always executes its Step 2/7 workspace build, even when
|
||||
# `--skip-verify` bypasses the initial verification gate.
|
||||
@@ -204,12 +214,13 @@ jobs:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Use policy-resolved lockfile
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-pnpm-lock-${{ github.run_id }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile || {
|
||||
echo "Frozen lockfile install failed; retrying without frozen lockfile because PRs do not commit pnpm-lock.yaml."
|
||||
pnpm install --no-frozen-lockfile
|
||||
}
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
@@ -14,6 +14,7 @@ const manifest: PaperclipPluginManifestV1 = {
|
||||
capabilities: [
|
||||
"ui.detailTab.register",
|
||||
"execution.workspaces.read",
|
||||
"project.workspaces.read",
|
||||
],
|
||||
entrypoints: {
|
||||
worker: "./dist/worker.js",
|
||||
|
||||
@@ -47,8 +47,6 @@ export function diffResponse(overrides: Partial<WorkspaceDiffResponse> = {}): Wo
|
||||
return {
|
||||
workspaceId: "11111111-1111-4111-8111-111111111111",
|
||||
companyId: "22222222-2222-4222-8222-222222222222",
|
||||
repoRoot: "/tmp/paperclip",
|
||||
cwd: "/tmp/paperclip",
|
||||
view: "working-tree",
|
||||
baseRef: null,
|
||||
headSha: null,
|
||||
|
||||
@@ -8,6 +8,7 @@ describe("workspace diff plugin", () => {
|
||||
it("declares workspace Changes tabs and host diff capability", () => {
|
||||
expect(manifest.capabilities).toContain("ui.detailTab.register");
|
||||
expect(manifest.capabilities).toContain("execution.workspaces.read");
|
||||
expect(manifest.capabilities).toContain("project.workspaces.read");
|
||||
expect(manifest.ui?.slots).toContainEqual(expect.objectContaining({
|
||||
type: "detailTab",
|
||||
displayName: "Changes",
|
||||
|
||||
@@ -88,8 +88,6 @@ export interface WorkspaceDiffStats {
|
||||
export interface WorkspaceDiffResponse {
|
||||
workspaceId: string;
|
||||
companyId: string;
|
||||
repoRoot: string;
|
||||
cwd: string;
|
||||
view: WorkspaceDiffView;
|
||||
baseRef: string | null;
|
||||
headSha: string | null;
|
||||
|
||||
@@ -20,8 +20,6 @@ describe("workspace diff validators", () => {
|
||||
const response = workspaceDiffResponseSchema.parse({
|
||||
workspaceId: "11111111-1111-4111-8111-111111111111",
|
||||
companyId: "22222222-2222-4222-8222-222222222222",
|
||||
repoRoot: "/tmp/repo",
|
||||
cwd: "/tmp/repo",
|
||||
view: "working-tree",
|
||||
baseRef: null,
|
||||
headSha: "abc123",
|
||||
|
||||
@@ -117,8 +117,6 @@ export const workspaceDiffStatsSchema = z.object({
|
||||
export const workspaceDiffResponseSchema = z.object({
|
||||
workspaceId: z.string().uuid(),
|
||||
companyId: z.string().uuid(),
|
||||
repoRoot: z.string(),
|
||||
cwd: z.string(),
|
||||
view: workspaceDiffViewSchema,
|
||||
baseRef: z.string().nullable(),
|
||||
headSha: z.string().nullable(),
|
||||
|
||||
@@ -91,6 +91,7 @@ describe("workspaceDiffService", () => {
|
||||
await fs.rm(path.join(repoRoot, "delete-me.txt"));
|
||||
await fs.writeFile(path.join(repoRoot, "binary.bin"), Buffer.from([0, 1, 2, 3, 4, 5]));
|
||||
await fs.writeFile(path.join(repoRoot, "untracked.txt"), "brand new\n", "utf8");
|
||||
await fs.writeFile(path.join(repoRoot, "empty-untracked.txt"), "", "utf8");
|
||||
await fs.writeFile(path.join(repoRoot, "oversized.txt"), "x".repeat(WORKSPACE_DIFF_CAPS.maxFileBytes + 1), "utf8");
|
||||
|
||||
const diff = await workspaceDiffService().getDiff(createWorkspace(repoRoot), workingTreeQuery());
|
||||
@@ -126,6 +127,13 @@ describe("workspaceDiffService", () => {
|
||||
additions: 1,
|
||||
});
|
||||
expect(byPath.get("untracked.txt")?.patches[0]?.patch).toContain("+brand new");
|
||||
expect(byPath.get("empty-untracked.txt")?.patches[0]?.patch).toBe([
|
||||
"diff --git a/empty-untracked.txt b/empty-untracked.txt",
|
||||
"new file mode 100644",
|
||||
"--- /dev/null",
|
||||
"+++ b/empty-untracked.txt",
|
||||
"",
|
||||
].join("\n"));
|
||||
expect(byPath.get("binary.bin")).toMatchObject({
|
||||
binary: true,
|
||||
unstaged: true,
|
||||
|
||||
@@ -571,6 +571,11 @@ export function buildHostServices(
|
||||
*/
|
||||
const ensurePluginAvailableForCompany = async (_companyId: string) => {};
|
||||
|
||||
const ensureDeclaredCapability = (capability: import("@paperclipai/shared").PluginCapability) => {
|
||||
if (!options.manifest || options.manifest.capabilities.includes(capability)) return;
|
||||
throw new Error(`Missing required capability: ${capability}`);
|
||||
};
|
||||
|
||||
const getLocalFolderDeclaration = (folderKey: string) =>
|
||||
requireLocalFolderDeclaration(options.manifest?.localFolders, folderKey);
|
||||
|
||||
@@ -1227,6 +1232,7 @@ export function buildHostServices(
|
||||
if (!projectWorkspace) {
|
||||
throw new Error("Workspace not found");
|
||||
}
|
||||
ensureDeclaredCapability("project.workspaces.read");
|
||||
return workspaceDiff.getDiff({
|
||||
id: projectWorkspace.id,
|
||||
companyId: projectWorkspace.companyId,
|
||||
|
||||
@@ -442,17 +442,16 @@ function countAddedLines(content: string) {
|
||||
function buildUntrackedPatch(filePath: string, content: string) {
|
||||
const lines = content.length === 0 ? [] : content.split("\n");
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
||||
const hunkLines = lines.map((line) => `+${line}`).join("\n");
|
||||
const lineCount = countAddedLines(content);
|
||||
return [
|
||||
const header = [
|
||||
`diff --git a/${filePath} b/${filePath}`,
|
||||
"new file mode 100644",
|
||||
"--- /dev/null",
|
||||
`+++ b/${filePath}`,
|
||||
`@@ -0,0 +1,${lineCount} @@`,
|
||||
hunkLines,
|
||||
"",
|
||||
].join("\n");
|
||||
];
|
||||
if (lineCount === 0) return `${header.join("\n")}\n`;
|
||||
const hunkLines = lines.map((line) => `+${line}`).join("\n");
|
||||
return [...header, `@@ -0,0 +1,${lineCount} @@`, hunkLines, ""].join("\n");
|
||||
}
|
||||
|
||||
async function buildUntrackedFilePatch(input: {
|
||||
@@ -674,8 +673,6 @@ export function workspaceDiffService() {
|
||||
return {
|
||||
workspaceId: workspace.id,
|
||||
companyId: workspace.companyId,
|
||||
repoRoot,
|
||||
cwd,
|
||||
view: query.view,
|
||||
baseRef,
|
||||
headSha: await resolveHeadSha(cwd),
|
||||
|
||||
@@ -593,7 +593,11 @@ export function ExecutionWorkspaceDetail() {
|
||||
[project, workspace?.projectWorkspaceId],
|
||||
);
|
||||
|
||||
const { slots: workspacePluginDetailSlots } = usePluginSlots({
|
||||
const {
|
||||
slots: workspacePluginDetailSlots,
|
||||
isLoading: workspacePluginDetailSlotsLoading,
|
||||
errorMessage: workspacePluginDetailSlotsError,
|
||||
} = usePluginSlots({
|
||||
slotTypes: ["detailTab"],
|
||||
entityType: "execution_workspace",
|
||||
companyId: workspace?.companyId ?? null,
|
||||
@@ -1183,6 +1187,14 @@ export function ExecutionWorkspaceDetail() {
|
||||
context={pluginSlotContext}
|
||||
missingBehavior="placeholder"
|
||||
/>
|
||||
) : isExecutionWorkspacePluginTab(activeTab) && workspacePluginDetailSlotsLoading ? (
|
||||
<Card>
|
||||
<CardContent className="py-6 text-sm text-muted-foreground">Loading workspace plugin...</CardContent>
|
||||
</Card>
|
||||
) : isExecutionWorkspacePluginTab(activeTab) && workspacePluginDetailSlotsError ? (
|
||||
<Card>
|
||||
<CardContent className="py-6 text-sm text-destructive">{workspacePluginDetailSlotsError}</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<ExecutionWorkspaceRoutinesList
|
||||
workspace={workspace}
|
||||
|
||||
@@ -52,7 +52,7 @@ function upstreamBaseRef(workspace: ProjectWorkspace) {
|
||||
if (workspace.repoRef) {
|
||||
return workspace.repoRef.startsWith("origin/") ? workspace.repoRef : `origin/${workspace.repoRef.replace(/^refs\/heads\//, "")}`;
|
||||
}
|
||||
return "origin/master";
|
||||
return null;
|
||||
}
|
||||
|
||||
const SOURCE_TYPE_OPTIONS: Array<{ value: ProjectWorkspaceSourceType; label: string; description: string }> = [
|
||||
@@ -431,8 +431,9 @@ export function ProjectWorkspaceDetail() {
|
||||
const params = new URLSearchParams({
|
||||
tab: "changes",
|
||||
diffView: "head",
|
||||
baseRef: upstreamBaseRef(workspace),
|
||||
});
|
||||
const baseRef = upstreamBaseRef(workspace);
|
||||
if (baseRef) params.set("baseRef", baseRef);
|
||||
navigate(`${workspacePath}?${params.toString()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user