Skip to content

Commit fc7b68b

Browse files
committed
fix: properly implement stable project IDs for Sprint 1
- Fix generateProjectId to check for existing ID before creating new one - Add workspace filtering to task history based on project ID/workspace path - Add migration logic when generating project ID for existing workspace - Add missing translation keys - Update tests to cover new functionality This ensures that task history actually follows the project when it's moved, which was the core requirement that was missing from the original implementation.
1 parent 64fbf59 commit fc7b68b

File tree

5 files changed

+90
-8
lines changed

5 files changed

+90
-8
lines changed

src/activate/registerCommands.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,38 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
228228
}
229229

230230
try {
231+
// Check if project ID already exists
232+
const { getProjectId } = await import("../utils/projectId")
233+
const existingId = await getProjectId(workspacePath)
234+
235+
if (existingId) {
236+
vscode.window.showInformationMessage(
237+
t("common:info.project_id_already_exists", { projectId: existingId }),
238+
)
239+
return
240+
}
241+
231242
const projectId = await generateProjectId(workspacePath)
232-
vscode.window.showInformationMessage(t("common:info.project_id_generated", { projectId }))
233243

234-
// Notify the provider to update any cached state
244+
// Migrate existing tasks to use the new project ID
235245
const visibleProvider = getVisibleProviderOrLog(outputChannel)
236246
if (visibleProvider) {
247+
const migrated = await visibleProvider.migrateTasksToProjectId(workspacePath, projectId)
248+
249+
if (migrated > 0) {
250+
vscode.window.showInformationMessage(
251+
t("common:info.project_id_generated_with_migration", {
252+
projectId,
253+
count: migrated,
254+
}),
255+
)
256+
} else {
257+
vscode.window.showInformationMessage(t("common:info.project_id_generated", { projectId }))
258+
}
259+
237260
await visibleProvider.postStateToWebview()
261+
} else {
262+
vscode.window.showInformationMessage(t("common:info.project_id_generated", { projectId }))
238263
}
239264
} catch (error) {
240265
vscode.window.showErrorMessage(

src/core/webview/ClineProvider.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,16 @@ export class ClineProvider
16431643
const currentMode = mode ?? defaultModeSlug
16441644
const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)
16451645

1646+
// Get the workspace storage key (project ID or workspace path)
1647+
const { getWorkspaceStorageKey } = await import("../../utils/projectId")
1648+
const workspaceStorageKey = await getWorkspaceStorageKey(cwd)
1649+
1650+
// Filter task history to only show tasks for the current workspace
1651+
const filteredTaskHistory = (taskHistory || [])
1652+
.filter((item: HistoryItem) => item.workspace === workspaceStorageKey)
1653+
.filter((item: HistoryItem) => item.ts && item.task)
1654+
.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts)
1655+
16461656
return {
16471657
version: this.context.extension?.packageJSON?.version ?? "",
16481658
apiConfiguration,
@@ -1664,12 +1674,10 @@ export class ClineProvider
16641674
autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
16651675
uriScheme: vscode.env.uriScheme,
16661676
currentTaskItem: this.getCurrentCline()?.taskId
1667-
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
1677+
? filteredTaskHistory.find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
16681678
: undefined,
16691679
clineMessages: this.getCurrentCline()?.clineMessages || [],
1670-
taskHistory: (taskHistory || [])
1671-
.filter((item: HistoryItem) => item.ts && item.task)
1672-
.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
1680+
taskHistory: filteredTaskHistory,
16731681
soundEnabled: soundEnabled ?? false,
16741682
ttsEnabled: ttsEnabled ?? false,
16751683
ttsSpeed: ttsSpeed ?? 1.0,
@@ -2114,6 +2122,32 @@ export class ClineProvider
21142122
...gitInfo,
21152123
}
21162124
}
2125+
2126+
/**
2127+
* Migrate existing tasks to use the new project ID
2128+
* @param workspacePath The workspace path to migrate from
2129+
* @param projectId The new project ID to migrate to
2130+
* @returns The number of tasks migrated
2131+
*/
2132+
async migrateTasksToProjectId(workspacePath: string, projectId: string): Promise<number> {
2133+
const taskHistory = this.getGlobalState("taskHistory") ?? []
2134+
let migratedCount = 0
2135+
2136+
// Update all tasks that match the workspace path
2137+
const updatedHistory = taskHistory.map((item: HistoryItem) => {
2138+
if (item.workspace === workspacePath) {
2139+
migratedCount++
2140+
return { ...item, workspace: projectId }
2141+
}
2142+
return item
2143+
})
2144+
2145+
if (migratedCount > 0) {
2146+
await this.updateGlobalState("taskHistory", updatedHistory)
2147+
}
2148+
2149+
return migratedCount
2150+
}
21172151
}
21182152

21192153
class OrganizationAllowListViolationError extends Error {

src/i18n/locales/en/common.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@
120120
"image_copied_to_clipboard": "Image data URI copied to clipboard",
121121
"image_saved": "Image saved to {{path}}",
122122
"mode_exported": "Mode '{{mode}}' exported successfully",
123-
"mode_imported": "Mode imported successfully"
123+
"mode_imported": "Mode imported successfully",
124+
"project_id_generated": "Project ID generated: {{projectId}}",
125+
"project_id_already_exists": "Project ID already exists: {{projectId}}",
126+
"project_id_generated_with_migration": "Project ID generated: {{projectId}}. Migrated {{count}} task(s) to use the new ID.",
127+
"project_id_generation_failed": "Failed to generate project ID: {{error}}"
124128
},
125129
"answers": {
126130
"yes": "Yes",

src/utils/__tests__/projectId.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ describe("projectId", () => {
7676

7777
describe("generateProjectId", () => {
7878
it("should generate and save a new project ID", async () => {
79+
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
7980
vi.mocked(fs.writeFile).mockResolvedValue()
8081

8182
const result = await generateProjectId(mockWorkspaceRoot)
@@ -84,7 +85,18 @@ describe("projectId", () => {
8485
expect(fs.writeFile).toHaveBeenCalledWith(mockProjectIdPath, result, "utf8")
8586
})
8687

88+
it("should return existing project ID if already exists", async () => {
89+
vi.mocked(fileExistsAtPath).mockResolvedValue(true)
90+
vi.mocked(fs.readFile).mockResolvedValue(mockProjectId)
91+
92+
const result = await generateProjectId(mockWorkspaceRoot)
93+
94+
expect(result).toBe(mockProjectId)
95+
expect(fs.writeFile).not.toHaveBeenCalled()
96+
})
97+
8798
it("should throw error if write fails", async () => {
99+
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
88100
vi.mocked(fs.writeFile).mockRejectedValue(new Error("Write failed"))
89101

90102
await expect(generateProjectId(mockWorkspaceRoot)).rejects.toThrow("Write failed")

src/utils/projectId.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,18 @@ export async function getProjectId(workspaceRoot: string): Promise<string | null
3838

3939
/**
4040
* Generates a new project ID and writes it to the .rooprojectid file.
41+
* If a project ID already exists, returns the existing one.
4142
*
4243
* @param workspaceRoot The root directory of the workspace
43-
* @returns The generated project ID
44+
* @returns The generated or existing project ID
4445
*/
4546
export async function generateProjectId(workspaceRoot: string): Promise<string> {
47+
// Check if project ID already exists
48+
const existingId = await getProjectId(workspaceRoot)
49+
if (existingId) {
50+
return existingId
51+
}
52+
4653
const projectId = uuidv4()
4754
const projectIdPath = path.join(workspaceRoot, PROJECT_ID_FILENAME)
4855

0 commit comments

Comments
 (0)