10 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | gap_closure | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 25-file-system | 07 | execute | 2 |
|
|
true | true |
|
|
Purpose: FILE-08 requires agent-generated files to be stored and linked to tasks/conversations. FILE-11 requires a PLACEHOLDERS.md manifest tracking placeholder assets with replacement chains. The upload API already supports source: "agent_generated" but no code path uses it, and no placeholder tracking exists.
Output: Placeholder service, replace endpoint, updated service methods, updated REQUIREMENTS.md
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/25-file-system/25-01-SUMMARY.md From packages/db/src/schema/chat_files.ts: ```typescript export const chatFiles = pgTable("chat_files", { id: uuid("id").primaryKey().defaultRandom(), source: text("source").notNull(), category: text("category"), projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }), }); ```From server/src/services/chat-files.ts:
export function chatFileService(db: Db) {
return { create(...), getById(...), attachToMessage(...), promoteToProject(...), createReference(...) };
}
From packages/shared/src/validators/chat.ts:
source: z.enum(["user_upload", "agent_generated"]).default("user_upload"),
Task 1: Create placeholderService and add markAsPlaceholder method
server/src/services/placeholder-service.ts, server/src/services/chat-files.ts, packages/shared/src/types/chat.ts, packages/shared/src/index.ts
- server/src/services/chat-files.ts
- packages/shared/src/types/chat.ts
- packages/shared/src/index.ts
- packages/db/src/schema/chat_files.ts
- server/src/home-paths.ts
1. Add ChatPlaceholderEntry type to packages/shared/src/types/chat.ts:
```typescript
export interface ChatPlaceholderEntry {
fileId: string;
filename: string;
description: string;
createdAt: string;
replacedByFileId?: string;
}
```
Export from packages/shared/src/index.ts alongside other chat types.
- Create server/src/services/placeholder-service.ts with three methods:
addEntry(projectDir, entry): Reads existing PLACEHOLDERS.md (or creates new), adds entry to Active Placeholders table, writes back.
replaceEntry(projectDir, oldFileId, newFileId): Reads PLACEHOLDERS.md, moves the entry from Active to Replaced section with replacedByFileId, writes back.
listEntries(projectDir): Reads and returns parsed entries.
The PLACEHOLDERS.md format:
# Placeholder Assets
Auto-maintained by Nexus. Do not edit manually.
## Active Placeholders
| File | Description | File ID |
|------|-------------|---------|
| logo.png | Generated by agent | abc-123 |
## Replaced
| Original | Description | Replaced By |
|----------|-------------|-------------|
| old-logo.png | Generated by agent | def-456 |
Use readFile/writeFile from node:fs/promises. Use existsSync to check if file exists. Use mkdir with recursive:true to ensure projectDir exists before writing.
The serialize function builds the markdown table strings from an array of PlaceholderEntry objects ({ fileId, filename, description, replacedByFileId? }).
The parse function reads markdown tables using a regex like /^|\s*(.+?)\s*|\s*(.+?)\s*|\s*(.+?)\s*|$/ to extract rows, tracking which section (Active vs Replaced) each row belongs to.
- In server/src/services/chat-files.ts, add markAsPlaceholder method to the returned object:
markAsPlaceholder(fileId: string) {
return db
.update(chatFiles)
.set({ category: "placeholder", updatedAt: new Date() })
.where(eq(chatFiles.id, fileId))
.returning()
.then((rows) => rows[0] ?? null);
},
cd /opt/nexus && test -f server/src/services/placeholder-service.ts && echo "placeholder-service exists" && grep "ChatPlaceholderEntry" packages/shared/src/types/chat.ts && grep "markAsPlaceholder" server/src/services/chat-files.ts
- File server/src/services/placeholder-service.ts exists
- Contains addEntry, replaceEntry, listEntries exported methods
- Generates PLACEHOLDERS.md with Active Placeholders and Replaced markdown tables
- packages/shared/src/types/chat.ts contains export interface ChatPlaceholderEntry
- packages/shared/src/index.ts exports ChatPlaceholderEntry
- server/src/services/chat-files.ts contains markAsPlaceholder method
PlaceholderService manages PLACEHOLDERS.md manifest; chatFileService has markAsPlaceholder method
Task 2: Add placeholder and agent-generated file routes
server/src/routes/chat-files.ts, .planning/REQUIREMENTS.md
- server/src/routes/chat-files.ts
- server/src/services/placeholder-service.ts
- server/src/services/chat-files.ts
- server/src/services/git-file-service.ts
- server/src/home-paths.ts
- .planning/REQUIREMENTS.md
1. Update server/src/routes/chat-files.ts:
a. Import placeholderService:
import { placeholderService } from "../services/placeholder-service.js";
b. Inside chatFileRoutes, instantiate:
const phSvc = placeholderService();
c. In the existing POST /conversations/:id/files upload route, after creating the chatFile DB record (after the git commit line from Plan 25-06), add placeholder handling:
// Track placeholder if agent-generated and project-scoped
if (parsedMeta.data.source === "agent_generated" && chatFile.projectId) {
const projectDir = path.join(storageDir, "..", "..", "projects", chatFile.projectId);
phSvc.addEntry(projectDir, {
fileId: chatFile.id,
filename: chatFile.originalFilename,
description: "Generated by agent",
}).catch(() => {});
}
d. Add POST /files/:fileId/replace endpoint for replacing a placeholder with a final asset. Place it near other /files/:fileId routes:
router.post("/files/:fileId/replace", async (req, res) => {
assertBoard(req);
const fileId = req.params.fileId as string;
const oldFile = await fileSvc.getById(fileId);
if (!oldFile) { res.status(404).json({ error: "File not found" }); return; }
assertCompanyAccess(req, oldFile.companyId);
const { newFileId } = req.body ?? {};
if (!newFileId || typeof newFileId !== "string") {
res.status(400).json({ error: "newFileId is required" }); return;
}
const newFile = await fileSvc.getById(newFileId);
if (!newFile) { res.status(404).json({ error: "Replacement file not found" }); return; }
// Update placeholder manifest if project-scoped
if (oldFile.projectId) {
const projectDir = path.join(storageDir, "..", "..", "projects", oldFile.projectId);
await phSvc.replaceEntry(projectDir, fileId, newFileId);
}
// Create reference linking replacement to original context
await fileSvc.createReference({
fileId: newFileId,
conversationId: oldFile.conversationId ?? "",
messageId: oldFile.messageId ?? undefined,
});
res.json({ replaced: fileId, replacedBy: newFileId });
});
- Update .planning/REQUIREMENTS.md:
- Change FILE-08 from
- [ ] **FILE-08**to- [x] **FILE-08** - Change FILE-11 from
- [ ] **FILE-11**to- [x] **FILE-11** - In Traceability table, change FILE-08 and FILE-11 from Pending to Complete cd /opt/nexus && grep -n "placeholderService|phSvc|replace|agent_generated" server/src/routes/chat-files.ts | head -10 && grep "FILE-08|FILE-11" .planning/REQUIREMENTS.md | head -6 <acceptance_criteria>
- server/src/routes/chat-files.ts imports placeholderService
- Contains phSvc.addEntry call for agent_generated files with projectId
- Contains router.post("/files/:fileId/replace") endpoint
- Replace endpoint calls phSvc.replaceEntry and fileSvc.createReference
- .planning/REQUIREMENTS.md contains
- [x] **FILE-08** - .planning/REQUIREMENTS.md contains
- [x] **FILE-11**</acceptance_criteria> Agent-generated files trigger placeholder manifest update; replacement endpoint exists; FILE-08 and FILE-11 marked Complete
- Change FILE-08 from
<success_criteria>
- Agent-generated files uploaded with source=agent_generated trigger PLACEHOLDERS.md update when project-scoped
- POST /files/:fileId/replace updates manifest and creates reference chain
- FILE-08 and FILE-11 marked Complete in REQUIREMENTS.md
- TypeScript compiles without errors </success_criteria>