@jvittechs/j 1.0.54 → 1.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -334,20 +334,43 @@ var TaskService = class {
334
334
  * Delete a task completely and clean up depends_on references
335
335
  */
336
336
  async deleteTask(id) {
337
+ return this.deleteTasks([id]);
338
+ }
339
+ /**
340
+ * Delete multiple tasks and clean up depends_on references.
341
+ * Validates all IDs exist before deleting any.
342
+ */
343
+ async deleteTasks(ids) {
337
344
  const tasks = await this.readAll();
338
- const index = tasks.findIndex((t) => t.id === id);
339
- if (index === -1) {
340
- throw new Error(`Task ${id} not found`);
345
+ const deleteSet = new Set(ids);
346
+ const notFound = ids.filter((id) => !tasks.find((t) => t.id === id));
347
+ if (notFound.length > 0) {
348
+ throw new Error(`Task(s) not found: ${notFound.join(", ")}`);
341
349
  }
342
- tasks.splice(index, 1);
343
- for (const t of tasks) {
344
- const depIdx = t.depends_on.indexOf(id);
345
- if (depIdx !== -1) {
346
- t.depends_on.splice(depIdx, 1);
347
- t.updated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
350
+ const remaining = tasks.filter((t) => !deleteSet.has(t.id));
351
+ const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
352
+ for (const t of remaining) {
353
+ const before = t.depends_on.length;
354
+ t.depends_on = t.depends_on.filter((depId) => !deleteSet.has(depId));
355
+ if (t.depends_on.length !== before) {
356
+ t.updated = now;
348
357
  }
349
358
  }
350
- await this.writeAll(tasks);
359
+ await this.writeAll(remaining);
360
+ }
361
+ /**
362
+ * Delete all tasks belonging to a group (parent) and clean up depends_on references.
363
+ * Returns the list of deleted tasks for display purposes.
364
+ */
365
+ async deleteGroup(parentName) {
366
+ const tasks = await this.readAll();
367
+ const groupTasks = tasks.filter((t) => t.parent === parentName);
368
+ if (groupTasks.length === 0) {
369
+ throw new Error(`No tasks found in group: ${parentName}`);
370
+ }
371
+ const deleteIds = groupTasks.map((t) => t.id);
372
+ await this.deleteTasks(deleteIds);
373
+ return groupTasks;
351
374
  }
352
375
  /**
353
376
  * Pick next task: claim for current user
@@ -670,4 +693,4 @@ export {
670
693
  handleTaskSummary,
671
694
  createTaskSummaryCommand
672
695
  };
673
- //# sourceMappingURL=chunk-WPXT7BL7.js.map
696
+ //# sourceMappingURL=chunk-FZBVI5AX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/tasks/summary.ts","../src/services/task.service.ts","../src/types/task.types.ts"],"sourcesContent":["/**\n * jai1 tasks summary [-j]\n */\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport boxen from 'boxen';\nimport { TaskService } from '../../services/task.service.js';\nimport type { TaskSummaryOptions } from '../../types/task.types.js';\n\nexport async function handleTaskSummary(options: TaskSummaryOptions): Promise<void> {\n const service = new TaskService();\n const stats = await service.getStats();\n const tasks = await service.readAll();\n\n if (options.json) {\n console.log(JSON.stringify(stats, null, 2));\n return;\n }\n\n console.log(\n boxen(chalk.cyan.bold('📊 Task Summary'), {\n padding: { left: 1, right: 1, top: 0, bottom: 0 },\n borderStyle: 'round',\n borderColor: 'cyan',\n })\n );\n\n console.log();\n console.log(` 🔵 In Progress: ${chalk.bold(String(stats.in_progress))}`);\n console.log(` 📋 Todo: ${chalk.bold(String(stats.todo))}`);\n console.log(` 🔴 Blocked: ${chalk.bold(String(stats.blocked))}`);\n console.log(` ✅ Done: ${chalk.bold(String(stats.done))}`);\n console.log(` ⚫ Cancelled: ${chalk.bold(String(stats.cancelled))}`);\n console.log(chalk.dim(` ── Total: ${stats.total}`));\n\n // Show in-progress tasks\n const inProgress = tasks.filter((t) => t.status === 'in_progress');\n if (inProgress.length > 0) {\n console.log(chalk.bold('\\n 🔵 Currently working:'));\n for (const t of inProgress) {\n let line = ` ${chalk.dim(t.id)} ${t.title}`;\n if (t.assigned_to) line += chalk.cyan(` @${t.assigned_to}`);\n console.log(line);\n }\n }\n\n // Show ready count\n const ready = await service.getReady();\n if (ready.length > 0) {\n console.log(chalk.dim(`\\n 💡 ${ready.length} tasks ready to pick: jai1 t pick`));\n }\n\n console.log();\n}\n\nexport function createTaskSummaryCommand(): Command {\n return new Command('summary')\n .description('Show task dashboard')\n .option('-j, --json', 'Output JSON')\n .action(async (options: TaskSummaryOptions) => {\n await handleTaskSummary(options);\n });\n}\n","/**\n * Task Service\n * CRUD operations on .jai1/tasks.jsonl\n */\nimport { promises as fs, existsSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { execSync } from 'child_process';\nimport chalk from 'chalk';\nimport { TaskSchema, type Task, type TaskStatusType, type ParentInfo } from '../types/task.types.js';\n\nconst TASKS_FILE = '.jai1/tasks.jsonl';\nconst SYNC_BRANCH = 'jai1';\n\nexport class TaskService {\n private readonly tasksPath: string;\n\n constructor(cwd?: string) {\n this.tasksPath = join(cwd || process.cwd(), TASKS_FILE);\n }\n\n /**\n * Check if .jai1 directory exists in CWD.\n * If not, print a helpful message and exit.\n */\n static ensureJai1Dir(cwd?: string): void {\n const dir = join(cwd || process.cwd(), '.jai1');\n if (!existsSync(dir)) {\n console.error(chalk.red('❌ Thư mục .jai1 không tồn tại trong project này.'));\n console.error('');\n console.error(chalk.dim(' Bạn cần khởi tạo Jai1 framework trước khi sử dụng task management.'));\n console.error('');\n console.error(` ${chalk.bold('Hướng dẫn:')}`);\n console.error(` ${chalk.cyan('jai1 doctor')} Kiểm tra trạng thái cài đặt`);\n console.error(` ${chalk.cyan('jai1 guide')} Hướng dẫn sử dụng đầy đủ`);\n console.error('');\n process.exit(1);\n }\n }\n\n // ============================================\n // READ\n // ============================================\n\n /**\n * Read all tasks from JSONL file\n */\n async readAll(): Promise<Task[]> {\n try {\n await this.ensureTasksFileNotDirectory();\n const content = await fs.readFile(this.tasksPath, 'utf-8');\n const lines = content.trim().split('\\n').filter(Boolean);\n return lines.map((line) => TaskSchema.parse(JSON.parse(line)));\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && (error as { code: string }).code === 'ENOENT') {\n return [];\n }\n throw error;\n }\n }\n\n /**\n * Find task by ID\n */\n async findById(id: string): Promise<Task | null> {\n const tasks = await this.readAll();\n return tasks.find((t) => t.id === id) || null;\n }\n\n /**\n * Filter tasks by criteria\n */\n async filter(criteria: {\n status?: TaskStatusType;\n parent?: string;\n assignee?: string;\n }): Promise<Task[]> {\n const tasks = await this.readAll();\n return tasks.filter((t) => {\n if (criteria.status && t.status !== criteria.status) return false;\n if (criteria.parent && t.parent !== criteria.parent) return false;\n if (criteria.assignee && t.assigned_to !== criteria.assignee) return false;\n return true;\n });\n }\n\n /**\n * Get tasks that are ready to pick:\n * status=todo, all depends_on done or cancelled, assigned_to empty\n */\n async getReady(parent?: string): Promise<Task[]> {\n const tasks = await this.readAll();\n const resolvedIds = new Set(\n tasks.filter((t) => t.status === 'done' || t.status === 'cancelled').map((t) => t.id)\n );\n\n return tasks\n .filter((t) => {\n if (t.status !== 'todo') return false;\n if (t.assigned_to) return false;\n if (parent && t.parent !== parent) return false;\n // All dependencies must be done or cancelled\n if (t.depends_on.length > 0 && !t.depends_on.every((depId) => resolvedIds.has(depId))) {\n return false;\n }\n return true;\n })\n .sort((a, b) => a.priority - b.priority || a.created.localeCompare(b.created));\n }\n\n /**\n * Check if a task is blocked (has unfinished dependencies)\n */\n async isBlocked(task: Task): Promise<{ blocked: boolean; blockedBy: string[] }> {\n if (task.depends_on.length === 0) {\n return { blocked: false, blockedBy: [] };\n }\n const tasks = await this.readAll();\n const resolvedIds = new Set(\n tasks.filter((t) => t.status === 'done' || t.status === 'cancelled').map((t) => t.id)\n );\n const blockedBy = task.depends_on.filter((id) => !resolvedIds.has(id));\n return { blocked: blockedBy.length > 0, blockedBy };\n }\n\n /**\n * Get stats by status\n */\n async getStats(): Promise<{\n total: number;\n todo: number;\n in_progress: number;\n done: number;\n cancelled: number;\n blocked: number;\n }> {\n const tasks = await this.readAll();\n const resolvedIds = new Set(\n tasks.filter((t) => t.status === 'done' || t.status === 'cancelled').map((t) => t.id)\n );\n\n let blocked = 0;\n for (const t of tasks) {\n if (\n t.status === 'todo' &&\n t.depends_on.length > 0 &&\n !t.depends_on.every((id) => resolvedIds.has(id))\n ) {\n blocked++;\n }\n }\n\n return {\n total: tasks.length,\n todo: tasks.filter((t) => t.status === 'todo').length,\n in_progress: tasks.filter((t) => t.status === 'in_progress').length,\n done: tasks.filter((t) => t.status === 'done').length,\n cancelled: tasks.filter((t) => t.status === 'cancelled').length,\n blocked,\n };\n }\n\n /**\n * Get all parent groups with computed status.\n * Groups tasks by parent field, computes stats for each.\n * Status logic:\n * done = all tasks done/cancelled\n * in_progress = any task in_progress\n * ready = all tasks todo + at least 1 ready (deps resolved)\n * todo = otherwise (blocked or waiting)\n */\n async getParents(statusFilter?: string): Promise<ParentInfo[]> {\n const tasks = await this.readAll();\n const resolvedIds = new Set(\n tasks.filter((t) => t.status === 'done' || t.status === 'cancelled').map((t) => t.id)\n );\n\n // Group by parent\n const groups = new Map<string, Task[]>();\n for (const t of tasks) {\n if (!t.parent) continue;\n const list = groups.get(t.parent) || [];\n list.push(t);\n groups.set(t.parent, list);\n }\n\n const parents: ParentInfo[] = [];\n\n for (const [name, groupTasks] of groups) {\n const total = groupTasks.length;\n const done = groupTasks.filter((t) => t.status === 'done').length;\n const cancelled = groupTasks.filter((t) => t.status === 'cancelled').length;\n const inProgress = groupTasks.filter((t) => t.status === 'in_progress').length;\n const todo = groupTasks.filter((t) => t.status === 'todo').length;\n\n // Count blocked & ready within this group\n let blocked = 0;\n let ready = 0;\n for (const t of groupTasks) {\n if (t.status !== 'todo') continue;\n const hasUnresolved =\n t.depends_on.length > 0 &&\n !t.depends_on.every((id) => resolvedIds.has(id));\n if (hasUnresolved) {\n blocked++;\n } else if (!t.assigned_to) {\n ready++;\n }\n }\n\n // Compute status\n let status: ParentInfo['status'];\n if (done + cancelled === total) {\n status = 'done';\n } else if (inProgress > 0) {\n status = 'in_progress';\n } else if (ready > 0) {\n status = 'ready';\n } else {\n status = 'todo';\n }\n\n parents.push({\n name,\n total,\n todo,\n in_progress: inProgress,\n done,\n cancelled,\n blocked,\n ready,\n status,\n });\n }\n\n // Sort by status priority: in_progress > ready > todo > done\n const statusOrder: Record<string, number> = { in_progress: 0, ready: 1, todo: 2, done: 3 };\n parents.sort((a, b) => (statusOrder[a.status] ?? 9) - (statusOrder[b.status] ?? 9));\n\n // Apply filter\n if (statusFilter) {\n return parents.filter((p) => p.status === statusFilter);\n }\n\n return parents;\n }\n\n // ============================================\n // WRITE\n // ============================================\n\n /**\n * Generate next task ID\n */\n async nextId(): Promise<string> {\n const tasks = await this.readAll();\n if (tasks.length === 0) return 'T-001';\n\n const maxNum = Math.max(\n ...tasks.map((t) => parseInt(t.id.replace('T-', ''), 10))\n );\n return `T-${String(maxNum + 1).padStart(3, '0')}`;\n }\n\n /**\n * Add a new task\n */\n async add(data: {\n title: string;\n parent?: string;\n priority?: number;\n tags?: string[];\n }): Promise<Task> {\n const id = await this.nextId();\n const now = new Date().toISOString().split('T')[0]!;\n\n const task = TaskSchema.parse({\n id,\n parent: data.parent || '',\n title: data.title,\n status: 'todo',\n assigned_to: '',\n claimed_at: '',\n priority: data.priority ?? 2,\n depends_on: [],\n created: now,\n updated: now,\n tags: data.tags || [],\n branch: '',\n notes: '',\n });\n\n await this.appendTask(task);\n return task;\n }\n\n /**\n * Update a task by ID\n */\n async update(\n id: string,\n changes: Partial<Pick<Task, 'status' | 'assigned_to' | 'claimed_at' | 'notes' | 'branch'>>\n ): Promise<Task> {\n const tasks = await this.readAll();\n const index = tasks.findIndex((t) => t.id === id);\n\n if (index === -1) {\n throw new Error(`Task ${id} not found`);\n }\n\n const now = new Date().toISOString().split('T')[0]!;\n const existing = tasks[index]!;\n tasks[index] = { ...existing, ...changes, updated: now };\n\n await this.writeAll(tasks);\n return tasks[index]!;\n }\n\n /**\n * Add dependency: child depends on parent\n */\n async addDependency(childId: string, parentId: string): Promise<Task> {\n const tasks = await this.readAll();\n const child = tasks.find((t) => t.id === childId);\n const parent = tasks.find((t) => t.id === parentId);\n\n if (!child) throw new Error(`Task ${childId} not found`);\n if (!parent) throw new Error(`Task ${parentId} not found`);\n\n if (child.depends_on.includes(parentId)) {\n throw new Error(`${childId} already depends on ${parentId}`);\n }\n\n // Circular dependency check\n if (await this.wouldCreateCycle(childId, parentId)) {\n throw new Error(`Adding dependency would create a cycle: ${parentId} → ... → ${childId}`);\n }\n\n child.depends_on.push(parentId);\n child.updated = new Date().toISOString().split('T')[0]!;\n\n await this.writeAll(tasks);\n return child;\n }\n\n /**\n * Mark task as done\n */\n async markDone(id: string): Promise<Task> {\n return this.update(id, { status: 'done' });\n }\n\n /**\n * Get all tasks that depend on a given task ID\n */\n async getDependents(taskId: string): Promise<Task[]> {\n const tasks = await this.readAll();\n return tasks.filter((t) => t.depends_on.includes(taskId));\n }\n\n /**\n * Cancel a task: set status=cancelled, clear assignment\n */\n async cancel(id: string): Promise<Task> {\n return this.update(id, {\n status: 'cancelled',\n assigned_to: '',\n claimed_at: '',\n });\n }\n\n /**\n * Delete a task completely and clean up depends_on references\n */\n async deleteTask(id: string): Promise<void> {\n return this.deleteTasks([id]);\n }\n\n /**\n * Delete multiple tasks and clean up depends_on references.\n * Validates all IDs exist before deleting any.\n */\n async deleteTasks(ids: string[]): Promise<void> {\n const tasks = await this.readAll();\n const deleteSet = new Set(ids);\n\n // Validate all IDs exist\n const notFound = ids.filter((id) => !tasks.find((t) => t.id === id));\n if (notFound.length > 0) {\n throw new Error(`Task(s) not found: ${notFound.join(', ')}`);\n }\n\n // Remove matching tasks\n const remaining = tasks.filter((t) => !deleteSet.has(t.id));\n\n // Clean up depends_on references in remaining tasks\n const now = new Date().toISOString().split('T')[0]!;\n for (const t of remaining) {\n const before = t.depends_on.length;\n t.depends_on = t.depends_on.filter((depId) => !deleteSet.has(depId));\n if (t.depends_on.length !== before) {\n t.updated = now;\n }\n }\n\n await this.writeAll(remaining);\n }\n\n /**\n * Delete all tasks belonging to a group (parent) and clean up depends_on references.\n * Returns the list of deleted tasks for display purposes.\n */\n async deleteGroup(parentName: string): Promise<Task[]> {\n const tasks = await this.readAll();\n const groupTasks = tasks.filter((t) => t.parent === parentName);\n\n if (groupTasks.length === 0) {\n throw new Error(`No tasks found in group: ${parentName}`);\n }\n\n const deleteIds = groupTasks.map((t) => t.id);\n await this.deleteTasks(deleteIds);\n return groupTasks;\n }\n\n /**\n * Pick next task: claim for current user\n */\n async pick(taskId: string): Promise<Task> {\n const username = this.getCurrentUser();\n const now = new Date().toISOString();\n\n return this.update(taskId, {\n status: 'in_progress',\n assigned_to: username,\n claimed_at: now,\n });\n }\n\n // ============================================\n // GIT SYNC\n // ============================================\n\n /**\n * Get the sync branch name\n */\n getSyncBranch(): string {\n return SYNC_BRANCH;\n }\n\n /**\n * Check if a remote branch exists\n */\n private remoteBranchExists(cwd: string, branch: string): boolean {\n try {\n const output = execSync(`git ls-remote --heads origin ${branch}`, { cwd, encoding: 'utf-8' }).trim();\n return output.length > 0;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a local branch exists\n */\n private localBranchExists(cwd: string, branch: string): boolean {\n try {\n execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Stash changes if working tree is dirty, returns true if stash was created\n */\n private stashIfDirty(cwd: string): boolean {\n try {\n execSync('git diff --quiet && git diff --cached --quiet', { cwd, stdio: 'pipe' });\n return false; // clean\n } catch {\n execSync('git stash push -m \"jai1-task-sync-auto-stash\"', { cwd, stdio: 'pipe' });\n return true;\n }\n }\n\n /**\n * Pop stash if it was created by us\n */\n private stashPopIfNeeded(cwd: string, didStash: boolean): void {\n if (!didStash) return;\n try {\n execSync('git stash pop', { cwd, stdio: 'pipe' });\n } catch {\n // stash pop conflict — leave in stash list, user can resolve\n }\n }\n\n /**\n * Create the jai1 orphan branch with just the tasks file.\n * An orphan branch has no shared history with code branches.\n */\n private createOrphanBranch(cwd: string, branch: string): void {\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd, encoding: 'utf-8',\n }).trim();\n\n const didStash = this.stashIfDirty(cwd);\n\n try {\n // Create orphan branch (no parent commits)\n execSync(`git checkout --orphan ${branch}`, { cwd, stdio: 'pipe' });\n // Remove all files from staging\n execSync('git rm -rf .', { cwd, stdio: 'pipe' });\n // Add only the tasks file\n execSync(`git checkout ${currentBranch} -- ${TASKS_FILE}`, { cwd, stdio: 'pipe' });\n execSync(`git add ${TASKS_FILE}`, { cwd, stdio: 'pipe' });\n execSync(`git commit -m \"chore(tasks): init task sync branch\"`, { cwd, stdio: 'pipe' });\n execSync(`git push -u origin ${branch}`, { cwd, stdio: 'pipe' });\n } finally {\n execSync(`git checkout ${currentBranch}`, { cwd, stdio: 'pipe' });\n this.stashPopIfNeeded(cwd, didStash);\n }\n }\n\n /**\n * Commit and push only the tasks file to the sync branch.\n * Works from any branch — pushes only the tasks commit to origin/<SYNC_BRANCH>.\n *\n * Edge cases handled:\n * - jai1 branch doesn't exist → creates orphan branch\n * - On feature branch → cherry-pick onto jai1\n * - Dirty working tree → stash/unstash automatically\n */\n async syncPush(): Promise<void> {\n const cwd = process.cwd();\n const branch = SYNC_BRANCH;\n\n // Check if tasks file exists\n const tasksFullPath = join(cwd, TASKS_FILE);\n if (!existsSync(tasksFullPath)) {\n // No tasks file — nothing to push\n return;\n }\n\n // Stage the tasks file\n execSync(`git add ${TASKS_FILE}`, { cwd, stdio: 'pipe' });\n\n try {\n execSync('git diff --cached --quiet', { cwd, stdio: 'pipe' });\n // No changes staged — nothing to push\n return;\n } catch {\n // Changes exist, continue\n }\n\n // Detect current branch\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd,\n encoding: 'utf-8',\n }).trim();\n\n // If jai1 branch doesn't exist anywhere, create it as orphan\n if (!this.remoteBranchExists(cwd, branch) && !this.localBranchExists(cwd, branch)) {\n // Commit first on current branch so we have the file\n execSync(`git commit -m \"chore(tasks): sync task status\"`, { cwd, stdio: 'pipe' });\n this.createOrphanBranch(cwd, branch);\n return;\n }\n\n // Commit on current branch\n execSync(`git commit -m \"chore(tasks): sync task status\"`, {\n cwd,\n stdio: 'pipe',\n });\n\n if (currentBranch === branch) {\n // Already on sync branch → push normally\n execSync(`git push origin ${branch}`, { cwd, stdio: 'pipe' });\n } else {\n // On a different branch → cherry-pick the commit onto sync branch\n const commitHash = execSync('git rev-parse HEAD', {\n cwd,\n encoding: 'utf-8',\n }).trim();\n\n const didStash = this.stashIfDirty(cwd);\n\n try {\n // Ensure local jai1 branch exists\n if (this.localBranchExists(cwd, branch)) {\n execSync(`git checkout ${branch}`, { cwd, stdio: 'pipe' });\n execSync(`git pull origin ${branch}`, { cwd, stdio: 'pipe' });\n } else {\n // Remote exists but no local — track it\n execSync(`git checkout -b ${branch} origin/${branch}`, { cwd, stdio: 'pipe' });\n }\n\n // Cherry-pick the tasks commit\n try {\n execSync(`git cherry-pick ${commitHash}`, { cwd, stdio: 'pipe' });\n } catch {\n // Cherry-pick conflict — accept theirs for non-tasks files, ours for tasks\n execSync(`git checkout ${commitHash} -- ${TASKS_FILE}`, { cwd, stdio: 'pipe' });\n execSync('git add .', { cwd, stdio: 'pipe' });\n try {\n execSync('git cherry-pick --continue', { cwd, stdio: 'pipe' });\n } catch {\n execSync(`git commit --allow-empty -m \"chore(tasks): sync task status\"`, { cwd, stdio: 'pipe' });\n }\n }\n\n execSync(`git push origin ${branch}`, { cwd, stdio: 'pipe' });\n } finally {\n // Always switch back to original branch\n execSync(`git checkout ${currentBranch}`, { cwd, stdio: 'pipe' });\n this.stashPopIfNeeded(cwd, didStash);\n }\n }\n }\n\n /**\n * Pull tasks from sync branch and merge by timestamp.\n *\n * Edge cases handled:\n * - jai1 branch doesn't exist on remote → returns empty (no-op)\n * - tasks.jsonl doesn't exist on remote branch → returns empty\n */\n async syncPull(): Promise<{ merged: number; conflicts: number }> {\n const cwd = process.cwd();\n const branch = SYNC_BRANCH;\n let merged = 0;\n let conflicts = 0;\n\n // Check if remote branch exists first\n if (!this.remoteBranchExists(cwd, branch)) {\n return { merged: 0, conflicts: 0 };\n }\n\n try {\n // Fetch latest from sync branch\n execSync(`git fetch origin ${branch}`, { cwd, stdio: 'pipe' });\n\n // Read sync branch's tasks\n let remoteContent: string;\n try {\n remoteContent = execSync(`git show origin/${branch}:${TASKS_FILE}`, {\n cwd,\n encoding: 'utf-8',\n });\n } catch {\n // File doesn't exist on sync branch\n return { merged: 0, conflicts: 0 };\n }\n\n const remoteTasks = remoteContent\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map((line: string) => TaskSchema.parse(JSON.parse(line)));\n\n const localTasks = await this.readAll();\n const localMap = new Map(localTasks.map((t) => [t.id, t]));\n\n for (const remoteTask of remoteTasks) {\n const local = localMap.get(remoteTask.id);\n if (!local) {\n // New task from remote → add\n localTasks.push(remoteTask);\n merged++;\n } else if (remoteTask.updated > local.updated) {\n // Remote is newer → replace\n const idx = localTasks.findIndex((t) => t.id === remoteTask.id);\n localTasks[idx] = remoteTask;\n merged++;\n }\n // If local is newer or same → keep local\n }\n\n await this.writeAll(localTasks);\n return { merged, conflicts };\n } catch (error) {\n throw new Error(\n `Sync pull failed: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n // ============================================\n // INTERNAL HELPERS\n // ============================================\n\n private async appendTask(task: Task): Promise<void> {\n await this.ensureTasksFileNotDirectory();\n const dir = dirname(this.tasksPath);\n await fs.mkdir(dir, { recursive: true });\n\n const line = JSON.stringify(task) + '\\n';\n await fs.appendFile(this.tasksPath, line, 'utf-8');\n }\n\n private async writeAll(tasks: Task[]): Promise<void> {\n await this.ensureTasksFileNotDirectory();\n const dir = dirname(this.tasksPath);\n await fs.mkdir(dir, { recursive: true });\n\n const content = tasks.map((t) => JSON.stringify(t)).join('\\n') + '\\n';\n await fs.writeFile(this.tasksPath, content, 'utf-8');\n }\n\n private getCurrentUser(): string {\n try {\n return execSync('git config user.name', { encoding: 'utf-8' }).trim();\n } catch {\n return 'unknown';\n }\n }\n\n private async wouldCreateCycle(childId: string, newDepId: string): Promise<boolean> {\n const tasks = await this.readAll();\n const taskMap = new Map(tasks.map((t) => [t.id, t]));\n\n // BFS from newDepId's dependencies to see if we reach childId\n const visited = new Set<string>();\n const queue = [newDepId];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n if (current === childId) return true;\n if (visited.has(current)) continue;\n visited.add(current);\n\n const task = taskMap.get(current);\n if (task) {\n queue.push(...task.depends_on);\n }\n }\n return false;\n }\n\n private async ensureTasksFileNotDirectory(): Promise<void> {\n try {\n const stat = await fs.stat(this.tasksPath);\n if (stat.isDirectory()) {\n throw new Error(\n `Invalid tasks path: expected file at ${TASKS_FILE} but found a directory. ` +\n `Please remove the directory and re-run the command.`\n );\n }\n } catch (error: unknown) {\n if (error && typeof error === 'object' && 'code' in error && (error as { code: string }).code === 'ENOENT') {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Get tasks file path\n */\n getTasksPath(): string {\n return this.tasksPath;\n }\n}\n","/**\n * Task Management Types\n * Schema for .jai1/tasks.jsonl\n */\nimport { z } from 'zod';\n\n// ============================================\n// ENUMS\n// ============================================\n\nexport const TaskStatus = {\n TODO: 'todo',\n IN_PROGRESS: 'in_progress',\n DONE: 'done',\n CANCELLED: 'cancelled',\n} as const;\n\nexport type TaskStatusType = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport const TaskPriority = {\n CRITICAL: 0,\n HIGH: 1,\n MEDIUM: 2,\n LOW: 3,\n} as const;\n\nexport type TaskPriorityType = (typeof TaskPriority)[keyof typeof TaskPriority];\n\n// ============================================\n// ZOD SCHEMAS\n// ============================================\n\nexport const TaskSchema = z.object({\n id: z.string().regex(/^T-\\d+$/),\n parent: z.string().default(''),\n title: z.string().min(1).max(500),\n status: z.enum(['todo', 'in_progress', 'done', 'cancelled']),\n assigned_to: z.string().default(''),\n claimed_at: z.string().default(''),\n priority: z.number().int().min(0).max(3).default(2),\n depends_on: z.array(z.string()).default([]),\n created: z.string(),\n updated: z.string(),\n tags: z.array(z.string()).default([]),\n branch: z.string().default(''),\n notes: z.string().default(''),\n});\n\nexport type Task = z.infer<typeof TaskSchema>;\n\n// ============================================\n// DISPLAY HELPERS\n// ============================================\n\nexport const STATUS_ICONS: Record<TaskStatusType, string> = {\n todo: '📋',\n in_progress: '🔵',\n done: '✅',\n cancelled: '⚫',\n};\n\nexport const BLOCKED_ICON = '🔴';\n\nexport const PRIORITY_ICONS: Record<number, string> = {\n 0: '🔥',\n 1: '🔴',\n 2: '🟡',\n 3: '🟢',\n};\n\nexport const PRIORITY_LABELS: Record<number, string> = {\n 0: 'Critical',\n 1: 'High',\n 2: 'Medium',\n 3: 'Low',\n};\n\n// ============================================\n// COMMAND OPTION TYPES\n// ============================================\n\nexport interface TaskListOptions {\n status?: TaskStatusType;\n parent?: string;\n json?: boolean;\n}\n\nexport interface TaskAddOptions {\n priority?: number;\n parent?: string;\n tags?: string;\n json?: boolean;\n}\n\nexport interface TaskUpdateOptions {\n status: TaskStatusType;\n json?: boolean;\n}\n\nexport interface TaskShowOptions {\n json?: boolean;\n}\n\nexport interface TaskPickOptions {\n json?: boolean;\n}\n\nexport interface TaskDoneOptions {\n json?: boolean;\n}\n\nexport interface TaskCancelOptions {\n yes?: boolean;\n json?: boolean;\n}\n\nexport interface TaskDeleteOptions {\n yes?: boolean;\n json?: boolean;\n group?: string;\n}\n\nexport interface TaskDepOptions {\n json?: boolean;\n}\n\nexport interface TaskReadyOptions {\n parent?: string;\n json?: boolean;\n}\n\nexport interface TaskSyncOptions {\n pull?: boolean;\n push?: boolean;\n}\n\nexport interface TaskSummaryOptions {\n json?: boolean;\n}\n\nexport interface TaskParentsOptions {\n status?: string; // filter: done, in_progress, todo, ready\n json?: boolean;\n}\n\nexport interface ParentInfo {\n name: string; // e.g. \"feature/auth\"\n total: number;\n todo: number;\n in_progress: number;\n done: number;\n cancelled: number;\n blocked: number;\n ready: number;\n status: 'done' | 'in_progress' | 'ready' | 'todo'; // computed\n}\n"],"mappings":"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","names":["chalk","chalk"]}
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  STATUS_ICONS,
14
14
  TaskService,
15
15
  createTaskSummaryCommand
16
- } from "./chunk-WPXT7BL7.js";
16
+ } from "./chunk-FZBVI5AX.js";
17
17
 
18
18
  // src/utils/node-version-check.ts
19
19
  import chalk from "chalk";
@@ -149,7 +149,7 @@ import { basename as basename5 } from "path";
149
149
  // package.json
150
150
  var package_default = {
151
151
  name: "@jvittechs/j",
152
- version: "1.0.54",
152
+ version: "1.0.56",
153
153
  description: "A unified CLI tool for JV-IT TECHS developers to manage Jai1 Framework. Supports both `j` and `jai1` commands. Please contact TeamAI for usage instructions.",
154
154
  type: "module",
155
155
  bin: {
@@ -11425,7 +11425,7 @@ import { Command as Command71 } from "commander";
11425
11425
  import { Command as Command58 } from "commander";
11426
11426
  import chalk31 from "chalk";
11427
11427
  function createTaskAddCommand() {
11428
- return new Command58("add").description("Add a new task").argument("<title>", "Task title").option("-p, --priority <n>", "Priority: 0=critical, 1=high, 2=medium, 3=low", "2").option("-P, --parent <parent>", "Parent: feature/xxx, plan/xxx, bug/xxx").option("-t, --tags <tags>", "Comma-separated tags").option("-j, --json", "Output JSON").action(async (title, options) => {
11428
+ return new Command58("add").description("Add a new task").argument("<title>", "Task title").option("-p, --priority <n>", "Priority: 0=critical, 1=high, 2=medium, 3=low", "2").option("-P, --parent <parent>", "Parent: feature/xxx, bug/xxx, plan/xxx, task/xxx, prd/xxx").option("-t, --tags <tags>", "Comma-separated tags").option("-j, --json", "Output JSON").action(async (title, options) => {
11429
11429
  const service = new TaskService();
11430
11430
  const priority = Number(options.priority ?? 2);
11431
11431
  if (priority < 0 || priority > 3) {
@@ -12000,60 +12000,103 @@ function createTaskCancelCommand() {
12000
12000
  import { Command as Command70 } from "commander";
12001
12001
  import chalk43 from "chalk";
12002
12002
  import { confirm as confirm13 } from "@inquirer/prompts";
12003
+ function printTaskList(tasks, indent = " ") {
12004
+ for (const t of tasks) {
12005
+ const icon = STATUS_ICONS[t.status] || "\u{1F4CB}";
12006
+ console.log(`${indent}${icon} ${chalk43.dim(t.id)} ${t.title}`);
12007
+ }
12008
+ }
12009
+ function printImpactSummary(tasksToDelete, allDependents) {
12010
+ const inProgress = tasksToDelete.filter((t) => t.status === "in_progress");
12011
+ const todo = tasksToDelete.filter((t) => t.status === "todo");
12012
+ const done = tasksToDelete.filter((t) => t.status === "done");
12013
+ const cancelled = tasksToDelete.filter((t) => t.status === "cancelled");
12014
+ console.log(chalk43.bold(`
12015
+ \u{1F5D1}\uFE0F Delete: ${tasksToDelete.length} task(s)
12016
+ `));
12017
+ if (inProgress.length > 0) {
12018
+ console.log(chalk43.yellow.bold(` \u26A0\uFE0F ${inProgress.length} task(s) \u0111ang IN_PROGRESS:`));
12019
+ printTaskList(inProgress, " ");
12020
+ console.log();
12021
+ }
12022
+ const parts = [];
12023
+ if (todo.length > 0) parts.push(`${todo.length} todo`);
12024
+ if (inProgress.length > 0) parts.push(chalk43.yellow(`${inProgress.length} in_progress`));
12025
+ if (done.length > 0) parts.push(`${done.length} done`);
12026
+ if (cancelled.length > 0) parts.push(`${cancelled.length} cancelled`);
12027
+ console.log(` ${chalk43.dim("Breakdown:")} ${parts.join(", ")}`);
12028
+ console.log(` ${chalk43.dim("Tasks:")}`);
12029
+ printTaskList(tasksToDelete, " ");
12030
+ console.log();
12031
+ if (allDependents.length > 0) {
12032
+ const activeDependents = allDependents.filter(
12033
+ (t) => t.status !== "done" && t.status !== "cancelled"
12034
+ );
12035
+ console.log(
12036
+ chalk43.yellow(` \u26A0\uFE0F ${allDependents.length} task(s) b\xEAn ngo\xE0i c\xF3 depends_on s\u1EBD b\u1ECB \u1EA3nh h\u01B0\u1EDFng:`)
12037
+ );
12038
+ printTaskList(allDependents, " ");
12039
+ if (activeDependents.length > 0) {
12040
+ console.log(
12041
+ chalk43.dim(`
12042
+ \u2192 ${activeDependents.length} task(s) ch\u01B0a done c\xF3 th\u1EC3 b\u1ECB unblock.`)
12043
+ );
12044
+ }
12045
+ console.log();
12046
+ }
12047
+ }
12003
12048
  function createTaskDeleteCommand() {
12004
- return new Command70("delete").description("Delete a task permanently (with dependency impact check)").argument("<id>", "Task ID (e.g. T-001)").option("-y, --yes", "Skip confirmation").option("-j, --json", "Output JSON before deletion").action(async (id, options) => {
12049
+ return new Command70("delete").description("Delete task(s) or entire group permanently (with safe delete)").argument("[ids...]", "Task ID(s) (e.g. T-001 T-002 T-003)").option("-g, --group <name>", "Delete all tasks in a group (e.g. feature/auth)").option("-y, --yes", "Skip confirmation").option("-j, --json", "Output JSON").action(async (ids, options) => {
12005
12050
  const service = new TaskService();
12006
12051
  try {
12007
- const task = await service.findById(id);
12008
- if (!task) {
12009
- console.error(chalk43.red(`\u274C Task ${id} not found`));
12010
- process.exit(1);
12011
- }
12012
- const dependents = await service.getDependents(id);
12013
- const icon = STATUS_ICONS[task.status] || "\u{1F4CB}";
12014
- console.log(chalk43.bold(`
12015
- \u{1F5D1}\uFE0F Delete: ${icon} ${task.id} \u2014 ${task.title}
12016
- `));
12017
- console.log(` ${chalk43.dim("Status:")} ${task.status}`);
12018
- console.log(` ${chalk43.dim("Parent:")} ${task.parent || "(none)"}`);
12019
- if (task.depends_on.length > 0) {
12020
- console.log(` ${chalk43.dim("Depends:")} ${task.depends_on.join(", ")}`);
12052
+ let tasksToDelete;
12053
+ if (options.group) {
12054
+ const allTasks = await service.readAll();
12055
+ tasksToDelete = allTasks.filter((t) => t.parent === options.group);
12056
+ if (tasksToDelete.length === 0) {
12057
+ console.error(chalk43.red(`\u274C No tasks found in group: ${options.group}`));
12058
+ process.exit(1);
12059
+ }
12060
+ } else {
12061
+ if (!ids || ids.length === 0) {
12062
+ console.error(chalk43.red("\u274C C\u1EA7n ch\u1EC9 \u0111\u1ECBnh task ID(s) ho\u1EB7c --group <name>"));
12063
+ console.error(chalk43.dim("\nV\xED d\u1EE5:"));
12064
+ console.error(chalk43.dim(" j t delete T-001"));
12065
+ console.error(chalk43.dim(" j t delete T-001 T-002 T-003"));
12066
+ console.error(chalk43.dim(" j t delete --group feature/auth"));
12067
+ process.exit(1);
12068
+ }
12069
+ tasksToDelete = [];
12070
+ for (const id of ids) {
12071
+ const task = await service.findById(id);
12072
+ if (!task) {
12073
+ console.error(chalk43.red(`\u274C Task ${id} not found`));
12074
+ process.exit(1);
12075
+ }
12076
+ tasksToDelete.push(task);
12077
+ }
12021
12078
  }
12022
- console.log();
12023
- if (dependents.length > 0) {
12024
- const activeDependents = dependents.filter(
12025
- (t) => t.status !== "done" && t.status !== "cancelled"
12026
- );
12027
- const resolvedDependents = dependents.filter(
12028
- (t) => t.status === "done" || t.status === "cancelled"
12029
- );
12030
- console.log(
12031
- chalk43.yellow(
12032
- `\u26A0\uFE0F ${dependents.length} task(s) c\xF3 depends_on \u2192 ${id}:`
12033
- )
12034
- );
12079
+ const deleteIds = new Set(tasksToDelete.map((t) => t.id));
12080
+ const allDependents = [];
12081
+ const seenDeps = /* @__PURE__ */ new Set();
12082
+ for (const task of tasksToDelete) {
12083
+ const dependents = await service.getDependents(task.id);
12035
12084
  for (const dep of dependents) {
12036
- const depIcon = STATUS_ICONS[dep.status] || "\u{1F4CB}";
12037
- console.log(` ${depIcon} ${chalk43.dim(dep.id)} ${dep.title}`);
12038
- }
12039
- console.log(
12040
- chalk43.dim(
12041
- `
12042
- \u2192 Xo\xE1 s\u1EBD remove ${id} kh\u1ECFi depends_on c\u1EE7a c\xE1c task tr\xEAn.`
12043
- )
12044
- );
12045
- if (activeDependents.length > 0) {
12046
- console.log(
12047
- chalk43.dim(
12048
- ` \u2192 ${activeDependents.length} task(s) ch\u01B0a done c\xF3 th\u1EC3 b\u1ECB \u1EA3nh h\u01B0\u1EDFng (unblock).`
12049
- )
12050
- );
12085
+ if (!deleteIds.has(dep.id) && !seenDeps.has(dep.id)) {
12086
+ allDependents.push(dep);
12087
+ seenDeps.add(dep.id);
12088
+ }
12051
12089
  }
12052
- console.log();
12053
12090
  }
12091
+ if (options.group) {
12092
+ console.log(chalk43.bold.cyan(`
12093
+ \u{1F4E6} Group: ${options.group}`));
12094
+ }
12095
+ printImpactSummary(tasksToDelete, allDependents);
12054
12096
  if (!options.yes) {
12097
+ const label = options.group ? `Xo\xE1 v\u0129nh vi\u1EC5n group "${options.group}" (${tasksToDelete.length} tasks)?` : tasksToDelete.length === 1 ? `Xo\xE1 v\u0129nh vi\u1EC5n task ${tasksToDelete[0].id}?` : `Xo\xE1 v\u0129nh vi\u1EC5n ${tasksToDelete.length} task(s)?`;
12055
12098
  const proceed = await confirm13({
12056
- message: `Xo\xE1 v\u0129nh vi\u1EC5n task ${id}?`,
12099
+ message: label,
12057
12100
  default: false
12058
12101
  });
12059
12102
  if (!proceed) {
@@ -12062,15 +12105,26 @@ function createTaskDeleteCommand() {
12062
12105
  }
12063
12106
  }
12064
12107
  if (options.json) {
12065
- console.log(JSON.stringify({ deleted: task, dependentsUpdated: dependents.map((d) => d.id) }, null, 2));
12066
- }
12067
- await service.deleteTask(id);
12108
+ console.log(JSON.stringify({
12109
+ deleted: tasksToDelete,
12110
+ dependentsUpdated: allDependents.map((d) => d.id),
12111
+ group: options.group || null
12112
+ }, null, 2));
12113
+ }
12114
+ const idsToDelete = tasksToDelete.map((t) => t.id);
12115
+ await service.deleteTasks(idsToDelete);
12068
12116
  if (!options.json) {
12069
- console.log(chalk43.green(`\u2705 \u0110\xE3 xo\xE1 ${task.id}: ${task.title}`));
12070
- if (dependents.length > 0) {
12117
+ if (options.group) {
12118
+ console.log(chalk43.green(`\u2705 \u0110\xE3 xo\xE1 group "${options.group}" (${tasksToDelete.length} tasks)`));
12119
+ } else if (tasksToDelete.length === 1) {
12120
+ console.log(chalk43.green(`\u2705 \u0110\xE3 xo\xE1 ${tasksToDelete[0].id}: ${tasksToDelete[0].title}`));
12121
+ } else {
12122
+ console.log(chalk43.green(`\u2705 \u0110\xE3 xo\xE1 ${tasksToDelete.length} task(s): ${idsToDelete.join(", ")}`));
12123
+ }
12124
+ if (allDependents.length > 0) {
12071
12125
  console.log(
12072
12126
  chalk43.dim(
12073
- ` \u0110\xE3 c\u1EADp nh\u1EADt depends_on cho: ${dependents.map((d) => d.id).join(", ")}`
12127
+ ` \u0110\xE3 c\u1EADp nh\u1EADt depends_on cho: ${allDependents.map((d) => d.id).join(", ")}`
12074
12128
  )
12075
12129
  );
12076
12130
  }
@@ -12106,7 +12160,7 @@ function createTasksCommand() {
12106
12160
  cmd.addCommand(createTaskDeleteCommand());
12107
12161
  cmd.addCommand(createTaskGuideCommand());
12108
12162
  cmd.action(async () => {
12109
- const { handleTaskSummary } = await import("./summary-WWNFDPZX.js");
12163
+ const { handleTaskSummary } = await import("./summary-4J2OCCSA.js");
12110
12164
  await handleTaskSummary({ json: false });
12111
12165
  });
12112
12166
  return cmd;
@@ -14998,9 +15052,11 @@ function getInstallCommand(packageManager2) {
14998
15052
  // src/commands/clean.ts
14999
15053
  import { Command as Command90 } from "commander";
15000
15054
  import { confirm as confirm21, select as select6 } from "@inquirer/prompts";
15055
+ import { promises as fs28 } from "fs";
15001
15056
  import { join as join21 } from "path";
15057
+ import { existsSync as existsSync4 } from "fs";
15002
15058
  function createCleanCommand() {
15003
- return new Command90("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
15059
+ return new Command90("clean").description("Clean up backups, cache, IDE configs, and .jai1 directory").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--jai1", "Clean only .jai1/ directory").option("--ide", "Clean only IDE directories (.cursor, .windsurf, .agent, .claude, .opencode)").option("--all", "Clean all (backups + .jai1 + IDE dirs)").action(async (options) => {
15004
15060
  await handleClean(options);
15005
15061
  });
15006
15062
  }
@@ -15019,15 +15075,41 @@ async function handleClean(options) {
15019
15075
  clean: async () => {
15020
15076
  await service.clearBackups(cwd);
15021
15077
  }
15078
+ },
15079
+ {
15080
+ name: "Jai1 Config",
15081
+ description: "Jai1 framework config (.jai1/)",
15082
+ path: join21(cwd, ".jai1"),
15083
+ check: async () => {
15084
+ const exists = existsSync4(join21(cwd, ".jai1"));
15085
+ return { exists };
15086
+ },
15087
+ clean: async () => {
15088
+ await fs28.rm(join21(cwd, ".jai1"), { recursive: true, force: true });
15089
+ }
15022
15090
  }
15023
- // Future targets can be added here:
15024
- // {
15025
- // name: 'Cache',
15026
- // description: 'Downloaded component cache',
15027
- // path: join(homedir(), '.jai1', 'cache'),
15028
- // ...
15029
- // }
15030
15091
  ];
15092
+ const ideDirectories = [
15093
+ { name: "Cursor", dir: ".cursor" },
15094
+ { name: "Windsurf", dir: ".windsurf" },
15095
+ { name: "Antigravity", dir: ".agent" },
15096
+ { name: "Claude Code", dir: ".claude" },
15097
+ { name: "OpenCode", dir: ".opencode" }
15098
+ ];
15099
+ for (const ide of ideDirectories) {
15100
+ const idePath = join21(cwd, ide.dir);
15101
+ if (existsSync4(idePath)) {
15102
+ targets.push({
15103
+ name: `IDE: ${ide.name}`,
15104
+ description: `${ide.name} IDE config (${ide.dir}/)`,
15105
+ path: idePath,
15106
+ check: async () => ({ exists: true }),
15107
+ clean: async () => {
15108
+ await fs28.rm(idePath, { recursive: true, force: true });
15109
+ }
15110
+ });
15111
+ }
15112
+ }
15031
15113
  console.log("\u{1F9F9} Clean up CLI client files\n");
15032
15114
  const availableTargets = [];
15033
15115
  for (const target of targets) {
@@ -15047,6 +15129,26 @@ async function handleClean(options) {
15047
15129
  }
15048
15130
  return;
15049
15131
  }
15132
+ if (options.jai1) {
15133
+ const jai1Target = availableTargets.find(({ target }) => target.name === "Jai1 Config");
15134
+ if (jai1Target) {
15135
+ await cleanTarget(jai1Target.target, options.yes);
15136
+ } else {
15137
+ console.log("\u2728 No .jai1/ directory found.");
15138
+ }
15139
+ return;
15140
+ }
15141
+ if (options.ide) {
15142
+ const ideTargets = availableTargets.filter(({ target }) => target.name.startsWith("IDE:"));
15143
+ if (ideTargets.length === 0) {
15144
+ console.log("\u2728 No IDE directories found.");
15145
+ return;
15146
+ }
15147
+ for (const { target } of ideTargets) {
15148
+ await cleanTarget(target, options.yes);
15149
+ }
15150
+ return;
15151
+ }
15050
15152
  if (options.all) {
15051
15153
  for (const { target } of availableTargets) {
15052
15154
  await cleanTarget(target, options.yes);
@@ -16003,7 +16105,7 @@ async function handleSyncProject(options) {
16003
16105
 
16004
16106
  // src/commands/framework/info.ts
16005
16107
  import { Command as Command94 } from "commander";
16006
- import { promises as fs28 } from "fs";
16108
+ import { promises as fs29 } from "fs";
16007
16109
  import { join as join22 } from "path";
16008
16110
  import { homedir as homedir5 } from "os";
16009
16111
  function createInfoCommand() {
@@ -16055,7 +16157,7 @@ function maskKey4(key) {
16055
16157
  async function getProjectStatus2() {
16056
16158
  const projectJai1 = join22(process.cwd(), ".jai1");
16057
16159
  try {
16058
- await fs28.access(projectJai1);
16160
+ await fs29.access(projectJai1);
16059
16161
  return { exists: true, version: "Synced" };
16060
16162
  } catch {
16061
16163
  return { exists: false };
@@ -16245,9 +16347,9 @@ function createClearBackupsCommand() {
16245
16347
  // src/commands/vscode/index.ts
16246
16348
  import { Command as Command97 } from "commander";
16247
16349
  import { checkbox as checkbox9, confirm as confirm24, select as select7 } from "@inquirer/prompts";
16248
- import fs29 from "fs/promises";
16350
+ import fs30 from "fs/promises";
16249
16351
  import path12 from "path";
16250
- import { existsSync as existsSync4 } from "fs";
16352
+ import { existsSync as existsSync5 } from "fs";
16251
16353
  var PERFORMANCE_GROUPS2 = {
16252
16354
  telemetry: {
16253
16355
  name: "Telemetry",
@@ -16477,14 +16579,14 @@ async function applyGroups2(groupKeys, action) {
16477
16579
  console.log(' \u{1F4A1} Ch\u1EA1y "jai1 vscode list" \u0111\u1EC3 xem danh s\xE1ch nh\xF3m c\xF3 s\u1EB5n.');
16478
16580
  return;
16479
16581
  }
16480
- if (!existsSync4(vscodeDir)) {
16481
- await fs29.mkdir(vscodeDir, { recursive: true });
16582
+ if (!existsSync5(vscodeDir)) {
16583
+ await fs30.mkdir(vscodeDir, { recursive: true });
16482
16584
  console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
16483
16585
  }
16484
16586
  let currentSettings = {};
16485
- if (existsSync4(settingsPath)) {
16587
+ if (existsSync5(settingsPath)) {
16486
16588
  try {
16487
- const content = await fs29.readFile(settingsPath, "utf-8");
16589
+ const content = await fs30.readFile(settingsPath, "utf-8");
16488
16590
  currentSettings = JSON.parse(content);
16489
16591
  console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
16490
16592
  } catch {
@@ -16524,7 +16626,7 @@ async function applyGroups2(groupKeys, action) {
16524
16626
  }
16525
16627
  }
16526
16628
  }
16527
- await fs29.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
16629
+ await fs30.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
16528
16630
  console.log(`
16529
16631
  \u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
16530
16632
  console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
@@ -16532,7 +16634,7 @@ async function applyGroups2(groupKeys, action) {
16532
16634
  async function resetSettings2(groupKeys) {
16533
16635
  const vscodeDir = path12.join(process.cwd(), ".vscode");
16534
16636
  const settingsPath = path12.join(vscodeDir, "settings.json");
16535
- if (!existsSync4(settingsPath)) {
16637
+ if (!existsSync5(settingsPath)) {
16536
16638
  console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
16537
16639
  return;
16538
16640
  }
@@ -16545,7 +16647,7 @@ async function resetSettings2(groupKeys) {
16545
16647
  return;
16546
16648
  }
16547
16649
  if (groupKeys.length === 0) {
16548
- await fs29.unlink(settingsPath);
16650
+ await fs30.unlink(settingsPath);
16549
16651
  console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
16550
16652
  } else {
16551
16653
  await applyGroups2(groupKeys, "disable");