import { writable, get } from "svelte/store"; import { invoke } from "@tauri-apps/api/core"; export interface PrdTask { id: string; title: string; prompt: string; priority: "high" | "medium" | "low"; dependsOn?: string[]; } export interface PrdFile { version: 1; goal: string; tasks: PrdTask[]; } export const PRD_FILENAME = "hikari-tasks.json"; function buildPrdPrompt(userGoal: string, workingDirectory: string): string { return `Please create a PRD task breakdown for the following goal and write it as \`hikari-tasks.json\` in the working directory. Goal: ${userGoal} Write the file to \`${workingDirectory}/hikari-tasks.json\` containing valid JSON in this exact format: \`\`\`json { "version": 1, "goal": "", "tasks": [ { "id": "task-1", "title": "", "prompt": "", "priority": "", "dependsOn": [] } ] } \`\`\` Guidelines: - Break the goal into 3–10 concrete, actionable tasks - Each task should be completable in a single Claude Code session - Prompts should be specific and actionable, not vague - Order tasks logically (dependencies first) - Assign priority: high for critical path, medium for features, low for polish/cleanup - Fill in \`dependsOn\` with IDs of tasks that must complete before this one (use \`[]\` if none) - Write only the JSON file — no explanations needed`; } function createPrdStore() { const goal = writable(""); const tasks = writable([]); const isGenerating = writable(false); const isLoaded = writable(false); const isLoading = writable(false); const isSaving = writable(false); let idCounter = 0; async function loadFromFile(workingDirectory: string): Promise { isLoading.set(true); try { const path = `${workingDirectory}/${PRD_FILENAME}`; const content = await invoke("read_file_content", { path }); const data = JSON.parse(content) as PrdFile; goal.set(data.goal); tasks.set(data.tasks); isLoaded.set(true); } catch (error) { console.error("Failed to load PRD file:", error); isLoaded.set(false); } finally { isLoading.set(false); } } async function saveToFile(workingDirectory: string): Promise { isSaving.set(true); try { const path = `${workingDirectory}/${PRD_FILENAME}`; const data: PrdFile = { version: 1, goal: get(goal), tasks: get(tasks), }; await invoke("write_file_content", { path, content: JSON.stringify(data, null, 2) }); return true; } catch (error) { console.error("Failed to save PRD file:", error); return false; } finally { isSaving.set(false); } } async function generatePrd( userGoal: string, workingDirectory: string, conversationId: string ): Promise { isGenerating.set(true); goal.set(userGoal); try { const prompt = buildPrdPrompt(userGoal, workingDirectory); await invoke("send_prompt", { conversationId, message: prompt }); } catch (error) { console.error("Failed to generate PRD:", error); isGenerating.set(false); } } function finishGenerating(): void { isGenerating.set(false); } async function executePrd(workingDirectory: string, conversationId: string): Promise { await saveToFile(workingDirectory); const currentTasks = get(tasks); const currentGoal = get(goal); const taskList = currentTasks .map((t, i) => `${i + 1}. [${t.priority}] **${t.title}**\n ${t.prompt}`) .join("\n\n"); const prompt = `Please execute the following task list for the goal: "${currentGoal}" Work through each task in order, completing it fully before moving to the next: ${taskList} The task list is also saved in \`${workingDirectory}/hikari-tasks.json\` for reference.`; await invoke("send_prompt", { conversationId, message: prompt }); } function addTask(task: Omit): void { idCounter += 1; const id = `task-${idCounter}`; tasks.update((current) => [...current, { ...task, id }]); } function updateTask(id: string, changes: Partial>): void { tasks.update((current) => current.map((t) => (t.id === id ? { ...t, ...changes } : t))); } function removeTask(id: string): void { tasks.update((current) => current.filter((t) => t.id !== id)); } function moveTaskUp(id: string): void { tasks.update((current) => { const index = current.findIndex((t) => t.id === id); if (index <= 0) return current; const result = [...current]; [result[index - 1], result[index]] = [result[index], result[index - 1]]; return result; }); } function moveTaskDown(id: string): void { tasks.update((current) => { const index = current.findIndex((t) => t.id === id); if (index < 0 || index >= current.length - 1) return current; const result = [...current]; [result[index], result[index + 1]] = [result[index + 1], result[index]]; return result; }); } function reset(): void { goal.set(""); tasks.set([]); isLoaded.set(false); isGenerating.set(false); idCounter = 0; } return { goal: { subscribe: goal.subscribe }, tasks: { subscribe: tasks.subscribe }, isGenerating: { subscribe: isGenerating.subscribe }, isLoaded: { subscribe: isLoaded.subscribe }, isLoading: { subscribe: isLoading.subscribe }, isSaving: { subscribe: isSaving.subscribe }, loadFromFile, saveToFile, generatePrd, finishGenerating, executePrd, addTask, updateTask, removeTask, moveTaskUp, moveTaskDown, reset, }; } export const prdStore = createPrdStore();