Compare commits
19 Commits
task-maste
...
claude/iss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57ed3a37e4 | ||
|
|
3b3dbabed1 | ||
|
|
af53525cbc | ||
|
|
b7f32eac5a | ||
|
|
044a7bfc98 | ||
|
|
51a351760c | ||
|
|
732b2c61ad | ||
|
|
4c2801d5eb | ||
|
|
c911608f60 | ||
|
|
8f1497407f | ||
|
|
10b64ec6f5 | ||
|
|
1a1879483b | ||
|
|
d691cbb7ae | ||
|
|
1b7c9637a5 | ||
|
|
9ff5f158d5 | ||
|
|
b2ff06e8c5 | ||
|
|
6438f6c7c8 | ||
|
|
6bbd777552 | ||
|
|
100482722f |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix module not found for new 0.27.0 release
|
||||
23
.changeset/intelligent-scan-command.md
Normal file
23
.changeset/intelligent-scan-command.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add intelligent `scan` command for automated codebase analysis
|
||||
|
||||
Introduces a comprehensive project scanning feature that intelligently analyzes codebases using ast-grep and AI-powered analysis. The new `task-master scan` command provides:
|
||||
|
||||
- **Multi-phase Analysis**: Performs iterative scanning (project type identification → entry points → core structure → recursive deepening)
|
||||
- **AST-grep Integration**: Uses ast-grep as an AI SDK tool for advanced code structure analysis
|
||||
- **AI Enhancement**: Optional AI-powered analysis for intelligent project understanding
|
||||
- **Structured Output**: Generates detailed JSON reports with file/directory summaries
|
||||
- **Transparent Logging**: Clear progress indicators showing each analysis phase
|
||||
- **Configurable Options**: Supports custom include/exclude patterns, scan depth, and output paths
|
||||
|
||||
This feature addresses the challenge of quickly understanding existing project structures when adopting Task Master, significantly streamlining initial setup and project onboarding.
|
||||
|
||||
Usage:
|
||||
```bash
|
||||
task-master scan --output=project_scan.json
|
||||
task-master scan --include="*.js,*.ts" --exclude="*.test.*" --depth=3
|
||||
task-master scan --no-ai # Skip AI analysis for faster results
|
||||
```
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,5 +1,41 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.27.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1254](https://github.com/eyaltoledano/claude-task-master/pull/1254) [`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed issue where `tm show` command could not find subtasks using dotted notation IDs (e.g., '8.1').
|
||||
- The command now properly searches within parent task subtasks and returns the correct subtask information.
|
||||
|
||||
## 0.27.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1248](https://github.com/eyaltoledano/claude-task-master/pull/1248) [`044a7bf`](https://github.com/eyaltoledano/claude-task-master/commit/044a7bfc98049298177bc655cf341d7a8b6a0011) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix set-status for subtasks:
|
||||
- Parent tasks are now set as `done` when subtasks are all `done`
|
||||
- Parent tasks are now set as `in-progress` when at least one subtask is `in-progress` or `done`
|
||||
|
||||
## 0.27.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1232](https://github.com/eyaltoledano/claude-task-master/pull/1232) [`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix module not found for new 0.27.0 release
|
||||
|
||||
- [#1233](https://github.com/eyaltoledano/claude-task-master/pull/1233) [`c911608`](https://github.com/eyaltoledano/claude-task-master/commit/c911608f60454253f4e024b57ca84e5a5a53f65c) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix Zed MCP configuration by adding required "source" property
|
||||
- Add "source": "custom" property to task-master-ai server in Zed settings.json
|
||||
|
||||
## 0.27.1-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1233](https://github.com/eyaltoledano/claude-task-master/pull/1233) [`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - One last testing final final
|
||||
|
||||
## 0.27.1-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1232](https://github.com/eyaltoledano/claude-task-master/pull/1232) [`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix module not found for new 0.27.0 release
|
||||
|
||||
## 0.27.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @tm/cli
|
||||
|
||||
## null
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
## 0.27.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -18,7 +18,8 @@ export * as ui from './utils/ui.js';
|
||||
export {
|
||||
checkForUpdate,
|
||||
performAutoUpdate,
|
||||
displayUpgradeNotification
|
||||
displayUpgradeNotification,
|
||||
compareVersions
|
||||
} from './utils/auto-update.js';
|
||||
|
||||
// Re-export commonly used types from tm-core
|
||||
|
||||
@@ -7,7 +7,6 @@ import https from 'https';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import boxen from 'boxen';
|
||||
import packageJson from '../../../../package.json' with { type: 'json' };
|
||||
|
||||
export interface UpdateInfo {
|
||||
currentVersion: string;
|
||||
@@ -16,15 +15,18 @@ export interface UpdateInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current version from package.json
|
||||
* Get current version from build-time injected environment variable
|
||||
*/
|
||||
function getCurrentVersion(): string {
|
||||
try {
|
||||
return packageJson.version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read package.json for version info');
|
||||
return '0.0.0';
|
||||
// Version is injected at build time via TM_PUBLIC_VERSION
|
||||
const version = process.env.TM_PUBLIC_VERSION;
|
||||
if (version && version !== 'unknown') {
|
||||
return version;
|
||||
}
|
||||
|
||||
// Fallback for development or if injection failed
|
||||
console.warn('Could not read version from TM_PUBLIC_VERSION, using fallback');
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,7 +35,7 @@ function getCurrentVersion(): string {
|
||||
* @param v2 - Second version
|
||||
* @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2
|
||||
*/
|
||||
function compareVersions(v1: string, v2: string): number {
|
||||
export function compareVersions(v1: string, v2: string): number {
|
||||
const toParts = (v: string) => {
|
||||
const [core, pre = ''] = v.split('-', 2);
|
||||
const nums = core.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# docs
|
||||
|
||||
## 0.0.4
|
||||
|
||||
## 0.0.3
|
||||
|
||||
## 0.0.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"private": true,
|
||||
"description": "Task Master documentation powered by Mintlify",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# Change Log
|
||||
|
||||
## 0.25.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d)]:
|
||||
- task-master-ai@0.27.3
|
||||
|
||||
## 0.25.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`044a7bf`](https://github.com/eyaltoledano/claude-task-master/commit/044a7bfc98049298177bc655cf341d7a8b6a0011)]:
|
||||
- task-master-ai@0.27.2
|
||||
|
||||
## 0.25.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e), [`c911608`](https://github.com/eyaltoledano/claude-task-master/commit/c911608f60454253f4e024b57ca84e5a5a53f65c), [`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9)]:
|
||||
- task-master-ai@0.27.1
|
||||
|
||||
## 0.25.2-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1a18794`](https://github.com/eyaltoledano/claude-task-master/commit/1a1879483b86c118a4e46c02cbf4acebfcf6bcf9)]:
|
||||
- task-master-ai@0.27.1-rc.1
|
||||
|
||||
## 0.25.2-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f487736`](https://github.com/eyaltoledano/claude-task-master/commit/f487736670ef8c484059f676293777eabb249c9e)]:
|
||||
- task-master-ai@0.27.1-rc.0
|
||||
|
||||
## 0.25.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"displayName": "TaskMaster",
|
||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||
"version": "0.25.1",
|
||||
"version": "0.25.4",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
@@ -240,7 +240,7 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"task-master-ai": "*"
|
||||
"task-master-ai": "0.27.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.0",
|
||||
"version": "0.27.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.0",
|
||||
"version": "0.27.3",
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
@@ -351,15 +351,15 @@
|
||||
}
|
||||
},
|
||||
"apps/docs": {
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"devDependencies": {
|
||||
"mintlify": "^4.2.111"
|
||||
}
|
||||
},
|
||||
"apps/extension": {
|
||||
"version": "0.25.1",
|
||||
"version": "0.25.4",
|
||||
"dependencies": {
|
||||
"task-master-ai": "*"
|
||||
"task-master-ai": "0.27.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.0",
|
||||
"version": "0.27.3",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -53,6 +53,7 @@
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
||||
"@ast-grep/cli": "^0.29.0",
|
||||
"@ai-sdk/anthropic": "^1.2.10",
|
||||
"@ai-sdk/azure": "^1.3.17",
|
||||
"@ai-sdk/google": "^1.2.13",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# @tm/build-config
|
||||
|
||||
## null
|
||||
|
||||
## 1.0.1
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## null
|
||||
|
||||
## 0.26.1
|
||||
|
||||
All notable changes to the @task-master/tm-core package will be documented in this file.
|
||||
|
||||
@@ -3,7 +3,17 @@
|
||||
* This file defines the contract for all storage implementations
|
||||
*/
|
||||
|
||||
import type { Task, TaskMetadata } from '../types/index.js';
|
||||
import type { Task, TaskMetadata, TaskStatus } from '../types/index.js';
|
||||
|
||||
/**
|
||||
* Result type for updateTaskStatus operations
|
||||
*/
|
||||
export interface UpdateStatusResult {
|
||||
success: boolean;
|
||||
oldStatus: TaskStatus;
|
||||
newStatus: TaskStatus;
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for storage operations on tasks
|
||||
@@ -54,6 +64,19 @@ export interface IStorage {
|
||||
tag?: string
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID
|
||||
* @param taskId - ID of the task or subtask (e.g., "1" or "1.2")
|
||||
* @param newStatus - New status to set
|
||||
* @param tag - Optional tag context for the task
|
||||
* @returns Promise that resolves to update result with old and new status
|
||||
*/
|
||||
updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult>;
|
||||
|
||||
/**
|
||||
* Delete a task by ID
|
||||
* @param taskId - ID of the task to delete
|
||||
@@ -191,6 +214,11 @@ export abstract class BaseStorage implements IStorage {
|
||||
updates: Partial<Task>,
|
||||
tag?: string
|
||||
): Promise<void>;
|
||||
abstract updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult>;
|
||||
abstract deleteTask(taskId: string, tag?: string): Promise<void>;
|
||||
abstract exists(tag?: string): Promise<boolean>;
|
||||
abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
|
||||
|
||||
@@ -135,15 +135,28 @@ export class TaskService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single task by ID
|
||||
* Get a single task by ID - delegates to storage layer
|
||||
*/
|
||||
async getTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||
const result = await this.getTaskList({
|
||||
tag,
|
||||
includeSubtasks: true
|
||||
});
|
||||
// Use provided tag or get active tag
|
||||
const activeTag = tag || this.getActiveTag();
|
||||
|
||||
return result.tasks.find((t) => t.id === taskId) || null;
|
||||
try {
|
||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
||||
return await this.storage.loadTask(String(taskId), activeTag);
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
`Failed to get task ${taskId}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{
|
||||
operation: 'getTask',
|
||||
resource: 'task',
|
||||
taskId: String(taskId),
|
||||
tag: activeTag
|
||||
},
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,7 +459,7 @@ export class TaskService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task status
|
||||
* Update task status - delegates to storage layer which handles storage-specific logic
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string | number,
|
||||
@@ -468,49 +481,28 @@ export class TaskService {
|
||||
|
||||
// Use provided tag or get active tag
|
||||
const activeTag = tag || this.getActiveTag();
|
||||
|
||||
const taskIdStr = String(taskId);
|
||||
|
||||
// TODO: For now, assume it's a regular task and just try to update directly
|
||||
// In the future, we can add subtask support if needed
|
||||
if (taskIdStr.includes('.')) {
|
||||
throw new TaskMasterError(
|
||||
'Subtask status updates not yet supported in API storage',
|
||||
ERROR_CODES.NOT_IMPLEMENTED
|
||||
);
|
||||
}
|
||||
|
||||
// Get the current task to get old status (simple, direct approach)
|
||||
let currentTask: Task | null;
|
||||
try {
|
||||
// Try to get the task directly
|
||||
currentTask = await this.storage.loadTask(taskIdStr, activeTag);
|
||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
||||
return await this.storage.updateTaskStatus(
|
||||
taskIdStr,
|
||||
newStatus,
|
||||
activeTag
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
`Failed to load task ${taskIdStr}`,
|
||||
ERROR_CODES.TASK_NOT_FOUND,
|
||||
{ taskId: taskIdStr },
|
||||
`Failed to update task status for ${taskIdStr}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{
|
||||
operation: 'updateTaskStatus',
|
||||
resource: 'task',
|
||||
taskId: taskIdStr,
|
||||
newStatus,
|
||||
tag: activeTag
|
||||
},
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentTask) {
|
||||
throw new TaskMasterError(
|
||||
`Task ${taskIdStr} not found`,
|
||||
ERROR_CODES.TASK_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
const oldStatus = currentTask.status;
|
||||
|
||||
// Simple, direct update - just change the status
|
||||
await this.storage.updateTask(taskIdStr, { status: newStatus }, activeTag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: taskIdStr
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,15 @@
|
||||
|
||||
import type {
|
||||
IStorage,
|
||||
StorageStats
|
||||
StorageStats,
|
||||
UpdateStatusResult
|
||||
} from '../interfaces/storage.interface.js';
|
||||
import type { Task, TaskMetadata, TaskTag } from '../types/index.js';
|
||||
import type {
|
||||
Task,
|
||||
TaskMetadata,
|
||||
TaskTag,
|
||||
TaskStatus
|
||||
} from '../types/index.js';
|
||||
import { ERROR_CODES, TaskMasterError } from '../errors/task-master-error.js';
|
||||
import { TaskRepository } from '../repositories/task-repository.interface.js';
|
||||
import { SupabaseTaskRepository } from '../repositories/supabase-task-repository.js';
|
||||
@@ -485,6 +491,62 @@ export class ApiStorage implements IStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID - for API storage
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
await this.ensureInitialized();
|
||||
|
||||
try {
|
||||
const existingTask = await this.retryOperation(() =>
|
||||
this.repository.getTask(this.projectId, taskId)
|
||||
);
|
||||
|
||||
if (!existingTask) {
|
||||
throw new Error(`Task ${taskId} not found`);
|
||||
}
|
||||
|
||||
const oldStatus = existingTask.status;
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId
|
||||
};
|
||||
}
|
||||
|
||||
// Update the task/subtask status
|
||||
await this.retryOperation(() =>
|
||||
this.repository.updateTask(this.projectId, taskId, {
|
||||
status: newStatus,
|
||||
updatedAt: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
|
||||
// Note: Parent status auto-adjustment is handled by the backend API service
|
||||
// which has its own business logic for managing task relationships
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
'Failed to update task status via API',
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{ operation: 'updateTaskStatus', taskId, newStatus, tag },
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available tags
|
||||
*/
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
* @fileoverview Refactored file-based storage implementation for Task Master
|
||||
*/
|
||||
|
||||
import type { Task, TaskMetadata } from '../../types/index.js';
|
||||
import type { Task, TaskMetadata, TaskStatus } from '../../types/index.js';
|
||||
import type {
|
||||
IStorage,
|
||||
StorageStats
|
||||
StorageStats,
|
||||
UpdateStatusResult
|
||||
} from '../../interfaces/storage.interface.js';
|
||||
import { FormatHandler } from './format-handler.js';
|
||||
import { FileOperations } from './file-operations.js';
|
||||
@@ -104,9 +105,65 @@ export class FileStorage implements IStorage {
|
||||
|
||||
/**
|
||||
* Load a single task by ID from the tasks.json file
|
||||
* Handles both regular tasks and subtasks (with dotted notation like "1.2")
|
||||
*/
|
||||
async loadTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||
const tasks = await this.loadTasks(tag);
|
||||
|
||||
// Check if this is a subtask (contains a dot)
|
||||
if (taskId.includes('.')) {
|
||||
const [parentId, subtaskId] = taskId.split('.');
|
||||
const parentTask = tasks.find((t) => String(t.id) === parentId);
|
||||
|
||||
if (!parentTask || !parentTask.subtasks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subtask = parentTask.subtasks.find(
|
||||
(st) => String(st.id) === subtaskId
|
||||
);
|
||||
if (!subtask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toFullSubId = (maybeDotId: string | number): string => {
|
||||
const depId = String(maybeDotId);
|
||||
return depId.includes('.') ? depId : `${parentTask.id}.${depId}`;
|
||||
};
|
||||
const resolvedDependencies =
|
||||
subtask.dependencies?.map((dep) => toFullSubId(dep)) ?? [];
|
||||
|
||||
// Return a Task-like object for the subtask with the full dotted ID
|
||||
// Following the same pattern as findTaskById in utils.js
|
||||
const subtaskResult = {
|
||||
...subtask,
|
||||
id: taskId, // Use the full dotted ID
|
||||
title: subtask.title || `Subtask ${subtaskId}`,
|
||||
description: subtask.description || '',
|
||||
status: subtask.status || 'pending',
|
||||
priority: subtask.priority || parentTask.priority || 'medium',
|
||||
dependencies: resolvedDependencies,
|
||||
details: subtask.details || '',
|
||||
testStrategy: subtask.testStrategy || '',
|
||||
subtasks: [],
|
||||
tags: parentTask.tags || [],
|
||||
assignee: subtask.assignee || parentTask.assignee,
|
||||
complexity: subtask.complexity || parentTask.complexity,
|
||||
createdAt: subtask.createdAt || parentTask.createdAt,
|
||||
updatedAt: subtask.updatedAt || parentTask.updatedAt,
|
||||
// Add reference to parent task for context (like utils.js does)
|
||||
parentTask: {
|
||||
id: parentTask.id,
|
||||
title: parentTask.title,
|
||||
status: parentTask.status
|
||||
},
|
||||
isSubtask: true
|
||||
};
|
||||
|
||||
return subtaskResult;
|
||||
}
|
||||
|
||||
// Handle regular task lookup
|
||||
return tasks.find((task) => String(task.id) === String(taskId)) || null;
|
||||
}
|
||||
|
||||
@@ -281,6 +338,156 @@ export class FileStorage implements IStorage {
|
||||
await this.saveTasks(tasks, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update task or subtask status by ID - handles file storage logic with parent/subtask relationships
|
||||
*/
|
||||
async updateTaskStatus(
|
||||
taskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
const tasks = await this.loadTasks(tag);
|
||||
|
||||
// Check if this is a subtask (contains a dot)
|
||||
if (taskId.includes('.')) {
|
||||
return this.updateSubtaskStatusInFile(tasks, taskId, newStatus, tag);
|
||||
}
|
||||
|
||||
// Handle regular task update
|
||||
const taskIndex = tasks.findIndex((t) => String(t.id) === String(taskId));
|
||||
|
||||
if (taskIndex === -1) {
|
||||
throw new Error(`Task ${taskId} not found`);
|
||||
}
|
||||
|
||||
const oldStatus = tasks[taskIndex].status;
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: String(taskId)
|
||||
};
|
||||
}
|
||||
|
||||
tasks[taskIndex] = {
|
||||
...tasks[taskIndex],
|
||||
status: newStatus,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
await this.saveTasks(tasks, tag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: String(taskId)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update subtask status within file storage - handles parent status auto-adjustment
|
||||
*/
|
||||
private async updateSubtaskStatusInFile(
|
||||
tasks: Task[],
|
||||
subtaskId: string,
|
||||
newStatus: TaskStatus,
|
||||
tag?: string
|
||||
): Promise<UpdateStatusResult> {
|
||||
// Parse the subtask ID to get parent ID and subtask ID
|
||||
const parts = subtaskId.split('.');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
`Invalid subtask ID format: ${subtaskId}. Expected format: parentId.subtaskId`
|
||||
);
|
||||
}
|
||||
|
||||
const [parentId, subIdRaw] = parts;
|
||||
const subId = subIdRaw.trim();
|
||||
if (!/^\d+$/.test(subId)) {
|
||||
throw new Error(
|
||||
`Invalid subtask ID: ${subId}. Subtask ID must be a positive integer.`
|
||||
);
|
||||
}
|
||||
const subtaskNumericId = Number(subId);
|
||||
|
||||
// Find the parent task
|
||||
const parentTaskIndex = tasks.findIndex(
|
||||
(t) => String(t.id) === String(parentId)
|
||||
);
|
||||
|
||||
if (parentTaskIndex === -1) {
|
||||
throw new Error(`Parent task ${parentId} not found`);
|
||||
}
|
||||
|
||||
const parentTask = tasks[parentTaskIndex];
|
||||
|
||||
// Find the subtask within the parent task
|
||||
const subtaskIndex = parentTask.subtasks.findIndex(
|
||||
(st) => st.id === subtaskNumericId || String(st.id) === subId
|
||||
);
|
||||
|
||||
if (subtaskIndex === -1) {
|
||||
throw new Error(
|
||||
`Subtask ${subtaskId} not found in parent task ${parentId}`
|
||||
);
|
||||
}
|
||||
|
||||
const oldStatus = parentTask.subtasks[subtaskIndex].status || 'pending';
|
||||
if (oldStatus === newStatus) {
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: subtaskId
|
||||
};
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Update the subtask status
|
||||
parentTask.subtasks[subtaskIndex] = {
|
||||
...parentTask.subtasks[subtaskIndex],
|
||||
status: newStatus,
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
// Auto-adjust parent status based on subtask statuses
|
||||
const subs = parentTask.subtasks;
|
||||
let parentNewStatus = parentTask.status;
|
||||
if (subs.length > 0) {
|
||||
const norm = (s: any) => s.status || 'pending';
|
||||
const isDoneLike = (s: any) => {
|
||||
const st = norm(s);
|
||||
return st === 'done' || st === 'completed';
|
||||
};
|
||||
const allDone = subs.every(isDoneLike);
|
||||
const anyInProgress = subs.some((s) => norm(s) === 'in-progress');
|
||||
const anyDone = subs.some(isDoneLike);
|
||||
if (allDone) parentNewStatus = 'done';
|
||||
else if (anyInProgress || anyDone) parentNewStatus = 'in-progress';
|
||||
}
|
||||
|
||||
// Always bump updatedAt; update status only if changed
|
||||
tasks[parentTaskIndex] = {
|
||||
...parentTask,
|
||||
...(parentNewStatus !== parentTask.status
|
||||
? { status: parentNewStatus }
|
||||
: {}),
|
||||
updatedAt: now
|
||||
};
|
||||
|
||||
await this.saveTasks(tasks, tag);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
oldStatus,
|
||||
newStatus,
|
||||
taskId: subtaskId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a task
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import readline from 'readline';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import figlet from 'figlet';
|
||||
import boxen from 'boxen';
|
||||
@@ -49,9 +47,6 @@ import {
|
||||
GITIGNORE_FILE
|
||||
} from '../src/constants/paths.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Define log levels
|
||||
const LOG_LEVELS = {
|
||||
debug: 0,
|
||||
@@ -619,13 +614,7 @@ function createProjectStructure(
|
||||
|
||||
// Copy .gitignore with GitTasks preference
|
||||
try {
|
||||
const gitignoreTemplatePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'assets',
|
||||
'gitignore'
|
||||
);
|
||||
const templateContent = fs.readFileSync(gitignoreTemplatePath, 'utf8');
|
||||
const templateContent = readAsset('gitignore', 'utf8');
|
||||
manageGitignoreFile(
|
||||
path.join(targetDir, GITIGNORE_FILE),
|
||||
templateContent,
|
||||
|
||||
@@ -53,6 +53,8 @@ import {
|
||||
validateStrength
|
||||
} from './task-manager.js';
|
||||
|
||||
import { scanProject } from './task-manager/scan-project/index.js';
|
||||
|
||||
import {
|
||||
moveTasksBetweenTags,
|
||||
MoveTaskError,
|
||||
@@ -5067,6 +5069,110 @@ Examples:
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// scan command
|
||||
programInstance
|
||||
.command('scan')
|
||||
.description('Intelligently scan and analyze the project codebase structure')
|
||||
.option(
|
||||
'--output <file>',
|
||||
'Path to save scan results (JSON format)',
|
||||
'project_scan.json'
|
||||
)
|
||||
.option(
|
||||
'--include <patterns>',
|
||||
'Comma-separated list of file patterns to include (e.g., "*.js,*.ts")'
|
||||
)
|
||||
.option(
|
||||
'--exclude <patterns>',
|
||||
'Comma-separated list of file patterns to exclude (e.g., "*.log,tmp/*")'
|
||||
)
|
||||
.option(
|
||||
'--depth <number>',
|
||||
'Maximum directory depth to scan',
|
||||
'5'
|
||||
)
|
||||
.option('--debug', 'Enable debug output')
|
||||
.option('--no-ai', 'Skip AI-powered analysis (faster but less detailed)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Initialize TaskMaster to get project root
|
||||
const taskMaster = initTaskMaster({});
|
||||
const projectRoot = taskMaster.getProjectRoot();
|
||||
|
||||
if (!projectRoot) {
|
||||
console.error(chalk.red('Error: Could not determine project root.'));
|
||||
console.log(chalk.yellow('Make sure you are in a valid project directory.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`🔍 Starting intelligent scan of project: ${projectRoot}`));
|
||||
console.log(chalk.gray(`Output will be saved to: ${options.output}`));
|
||||
|
||||
// Parse options
|
||||
const scanOptions = {
|
||||
outputPath: path.isAbsolute(options.output)
|
||||
? options.output
|
||||
: path.join(projectRoot, options.output),
|
||||
includeFiles: options.include ? options.include.split(',').map(s => s.trim()) : [],
|
||||
excludeFiles: options.exclude ? options.exclude.split(',').map(s => s.trim()) : undefined,
|
||||
scanDepth: parseInt(options.depth, 10),
|
||||
debug: options.debug || false,
|
||||
reportProgress: true,
|
||||
skipAI: options.noAi || false
|
||||
};
|
||||
|
||||
// Perform the scan
|
||||
const spinner = ora('Scanning project structure...').start();
|
||||
|
||||
try {
|
||||
const result = await scanProject(projectRoot, scanOptions);
|
||||
|
||||
spinner.stop();
|
||||
|
||||
if (result.success) {
|
||||
console.log(chalk.green('✅ Project scan completed successfully!'));
|
||||
console.log(chalk.cyan('\n📊 Scan Summary:'));
|
||||
console.log(chalk.white(` Project Type: ${result.data.scanSummary.projectType}`));
|
||||
console.log(chalk.white(` Total Files: ${result.data.stats.totalFiles}`));
|
||||
console.log(chalk.white(` Languages: ${result.data.scanSummary.languages.join(', ')}`));
|
||||
console.log(chalk.white(` Code Lines: ${result.data.scanSummary.codeMetrics.totalLines}`));
|
||||
console.log(chalk.white(` Functions: ${result.data.scanSummary.codeMetrics.totalFunctions}`));
|
||||
console.log(chalk.white(` Classes: ${result.data.scanSummary.codeMetrics.totalClasses}`));
|
||||
|
||||
if (result.data.scanSummary.recommendations.length > 0) {
|
||||
console.log(chalk.yellow('\n💡 Recommendations:'));
|
||||
result.data.scanSummary.recommendations.forEach(rec => {
|
||||
console.log(chalk.white(` • ${rec}`));
|
||||
});
|
||||
}
|
||||
|
||||
console.log(chalk.green(`\n📄 Detailed results saved to: ${scanOptions.outputPath}`));
|
||||
} else {
|
||||
console.error(chalk.red('❌ Project scan failed:'));
|
||||
console.error(chalk.red(` ${result.error.message}`));
|
||||
if (scanOptions.debug && result.error.stack) {
|
||||
console.error(chalk.gray(` ${result.error.stack}`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.stop();
|
||||
console.error(chalk.red(`❌ Scan failed: ${error.message}`));
|
||||
if (scanOptions.debug) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error initializing scan: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.on('error', function (err) {
|
||||
console.error(chalk.red(`Error: ${err.message}`));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
return programInstance;
|
||||
}
|
||||
|
||||
@@ -5077,27 +5183,9 @@ Examples:
|
||||
function setupCLI() {
|
||||
// Create a new program instance
|
||||
const programInstance = new Command()
|
||||
.name('dev')
|
||||
.name('task-master')
|
||||
.description('AI-driven development task management')
|
||||
.version(() => {
|
||||
// Read version directly from package.json ONLY
|
||||
try {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonPath, 'utf8')
|
||||
);
|
||||
return packageJson.version;
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fall back to 'unknown'
|
||||
log(
|
||||
'warn',
|
||||
'Could not read package.json for version info in .version()'
|
||||
);
|
||||
}
|
||||
return 'unknown'; // Default fallback if package.json fails
|
||||
})
|
||||
.version(process.env.TM_PUBLIC_VERSION || 'unknown')
|
||||
.helpOption('-h, --help', 'Display help')
|
||||
.addHelpCommand(false); // Disable default help command
|
||||
|
||||
@@ -5126,8 +5214,9 @@ function setupCLI() {
|
||||
*/
|
||||
async function runCLI(argv = process.argv) {
|
||||
try {
|
||||
// Display banner if not in a pipe
|
||||
if (process.stdout.isTTY) {
|
||||
// Display banner if not in a pipe (except for init command which has its own banner)
|
||||
const isInitCommand = argv.includes('init');
|
||||
if (process.stdout.isTTY && !isInitCommand) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
|
||||
328
scripts/modules/task-manager/scan-project/ai-analysis.js
Normal file
328
scripts/modules/task-manager/scan-project/ai-analysis.js
Normal file
@@ -0,0 +1,328 @@
|
||||
/**
|
||||
* AI-powered analysis for project scanning
|
||||
*/
|
||||
import { ScanLoggingConfig } from './scan-config.js';
|
||||
|
||||
// Dynamically import AI service with fallback
|
||||
async function getAiService(options) {
|
||||
try {
|
||||
const { getAiService: aiService } = await import('../../ai-services-unified.js');
|
||||
return aiService(options);
|
||||
} catch (error) {
|
||||
throw new Error(`AI service not available: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze project structure using AI
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} config - Scan configuration
|
||||
* @returns {Promise<Object>} AI-enhanced analysis
|
||||
*/
|
||||
export async function analyzeWithAI(scanResults, config) {
|
||||
const logger = new ScanLoggingConfig(config.mcpLog, config.reportProgress);
|
||||
logger.info('Starting AI-powered analysis...');
|
||||
|
||||
try {
|
||||
// Step 1: Project Type Analysis
|
||||
const projectTypeAnalysis = await analyzeProjectType(scanResults, config, logger);
|
||||
|
||||
// Step 2: Entry Points Analysis
|
||||
const entryPointsAnalysis = await analyzeEntryPoints(scanResults, projectTypeAnalysis, config, logger);
|
||||
|
||||
// Step 3: Core Structure Analysis
|
||||
const coreStructureAnalysis = await analyzeCoreStructure(scanResults, entryPointsAnalysis, config, logger);
|
||||
|
||||
// Step 4: Recursive Analysis (if needed)
|
||||
const detailedAnalysis = await performDetailedAnalysis(scanResults, coreStructureAnalysis, config, logger);
|
||||
|
||||
// Combine all analyses
|
||||
const enhancedAnalysis = {
|
||||
projectType: projectTypeAnalysis,
|
||||
entryPoints: entryPointsAnalysis,
|
||||
coreStructure: coreStructureAnalysis,
|
||||
detailed: detailedAnalysis,
|
||||
summary: generateProjectSummary(scanResults, projectTypeAnalysis, coreStructureAnalysis)
|
||||
};
|
||||
|
||||
logger.info('AI analysis completed successfully');
|
||||
return enhancedAnalysis;
|
||||
} catch (error) {
|
||||
logger.error(`AI analysis failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Analyze project type using AI
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} config - Scan configuration
|
||||
* @param {ScanLoggingConfig} logger - Logger instance
|
||||
* @returns {Promise<Object>} Project type analysis
|
||||
*/
|
||||
async function analyzeProjectType(scanResults, config, logger) {
|
||||
logger.info('[Scan #1]: Analyzing project type and structure...');
|
||||
|
||||
const prompt = `Given this root directory structure and files, identify the type of project and key characteristics:
|
||||
|
||||
Root files: ${JSON.stringify(scanResults.rootFiles, null, 2)}
|
||||
Directory structure: ${JSON.stringify(scanResults.directories, null, 2)}
|
||||
|
||||
Please analyze:
|
||||
1. Project type (e.g., Node.js, React, Laravel, Python, etc.)
|
||||
2. Programming languages used
|
||||
3. Frameworks and libraries
|
||||
4. Build tools and configuration
|
||||
5. Files or folders that should be excluded from further analysis (logs, binaries, etc.)
|
||||
|
||||
Respond with a JSON object containing your analysis.`;
|
||||
|
||||
try {
|
||||
const aiService = getAiService({ projectRoot: config.projectRoot });
|
||||
const response = await aiService.generateStructuredOutput({
|
||||
prompt,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
projectType: { type: 'string' },
|
||||
languages: { type: 'array', items: { type: 'string' } },
|
||||
frameworks: { type: 'array', items: { type: 'string' } },
|
||||
buildTools: { type: 'array', items: { type: 'string' } },
|
||||
excludePatterns: { type: 'array', items: { type: 'string' } },
|
||||
confidence: { type: 'number' },
|
||||
reasoning: { type: 'string' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`[Scan #1]: Detected ${response.projectType} project`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.warn(`[Scan #1]: AI analysis failed, using fallback detection`);
|
||||
// Fallback to rule-based detection
|
||||
return scanResults.projectType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Analyze entry points using AI
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} projectTypeAnalysis - Project type analysis
|
||||
* @param {Object} config - Scan configuration
|
||||
* @param {ScanLoggingConfig} logger - Logger instance
|
||||
* @returns {Promise<Object>} Entry points analysis
|
||||
*/
|
||||
async function analyzeEntryPoints(scanResults, projectTypeAnalysis, config, logger) {
|
||||
logger.info('[Scan #2]: Identifying main entry points and core files...');
|
||||
|
||||
const prompt = `Based on the project type "${projectTypeAnalysis.projectType}" and these files, identify the main entry points and core files:
|
||||
|
||||
Available files: ${JSON.stringify(scanResults.fileList.slice(0, 50), null, 2)}
|
||||
Project type: ${projectTypeAnalysis.projectType}
|
||||
Languages: ${JSON.stringify(projectTypeAnalysis.languages)}
|
||||
Frameworks: ${JSON.stringify(projectTypeAnalysis.frameworks)}
|
||||
|
||||
Please identify:
|
||||
1. Main entry points (files that start the application)
|
||||
2. Configuration files
|
||||
3. Core application files
|
||||
4. Important directories to analyze further
|
||||
|
||||
Respond with a structured JSON object.`;
|
||||
|
||||
try {
|
||||
const aiService = getAiService({ projectRoot: config.projectRoot });
|
||||
const response = await aiService.generateStructuredOutput({
|
||||
prompt,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
entryPoints: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
description: { type: 'string' }
|
||||
}
|
||||
}
|
||||
},
|
||||
configFiles: { type: 'array', items: { type: 'string' } },
|
||||
coreFiles: { type: 'array', items: { type: 'string' } },
|
||||
importantDirectories: { type: 'array', items: { type: 'string' } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`[Scan #2]: Found ${response.entryPoints.length} entry points`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.warn(`[Scan #2]: AI analysis failed, using basic detection`);
|
||||
return {
|
||||
entryPoints: scanResults.projectType.entryPoints.map(ep => ({ path: ep, type: 'main', description: 'Main entry point' })),
|
||||
configFiles: [],
|
||||
coreFiles: [],
|
||||
importantDirectories: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Analyze core structure using AI
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} entryPointsAnalysis - Entry points analysis
|
||||
* @param {Object} config - Scan configuration
|
||||
* @param {ScanLoggingConfig} logger - Logger instance
|
||||
* @returns {Promise<Object>} Core structure analysis
|
||||
*/
|
||||
async function analyzeCoreStructure(scanResults, entryPointsAnalysis, config, logger) {
|
||||
logger.info('[Scan #3]: Analyzing core structure and key directories...');
|
||||
|
||||
const prompt = `Based on the entry points and project structure, analyze the core architecture:
|
||||
|
||||
Entry points: ${JSON.stringify(entryPointsAnalysis.entryPoints, null, 2)}
|
||||
Important directories: ${JSON.stringify(entryPointsAnalysis.importantDirectories)}
|
||||
File analysis: ${JSON.stringify(scanResults.detailedFiles.slice(0, 20), null, 2)}
|
||||
|
||||
Please analyze:
|
||||
1. Directory-level summaries and purposes
|
||||
2. File relationships and dependencies
|
||||
3. Key architectural patterns
|
||||
4. Data flow and component relationships
|
||||
|
||||
Respond with a structured analysis.`;
|
||||
|
||||
try {
|
||||
const aiService = getAiService({ projectRoot: config.projectRoot });
|
||||
const response = await aiService.generateStructuredOutput({
|
||||
prompt,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
directories: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
purpose: { type: 'string' },
|
||||
importance: { type: 'string' },
|
||||
keyFiles: { type: 'array', items: { type: 'string' } },
|
||||
description: { type: 'string' }
|
||||
}
|
||||
}
|
||||
},
|
||||
architecture: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pattern: { type: 'string' },
|
||||
layers: { type: 'array', items: { type: 'string' } },
|
||||
dataFlow: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`[Scan #3]: Analyzed ${Object.keys(response.directories || {}).length} directories`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.warn(`[Scan #3]: AI analysis failed, using basic structure`);
|
||||
return {
|
||||
directories: {},
|
||||
architecture: {
|
||||
pattern: 'unknown',
|
||||
layers: [],
|
||||
dataFlow: 'unknown'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4: Perform detailed analysis on specific files/directories
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} coreStructureAnalysis - Core structure analysis
|
||||
* @param {Object} config - Scan configuration
|
||||
* @param {ScanLoggingConfig} logger - Logger instance
|
||||
* @returns {Promise<Object>} Detailed analysis
|
||||
*/
|
||||
async function performDetailedAnalysis(scanResults, coreStructureAnalysis, config, logger) {
|
||||
logger.info('[Scan #4+]: Performing detailed file-level analysis...');
|
||||
|
||||
const importantFiles = scanResults.detailedFiles
|
||||
.filter(file => file.functions?.length > 0 || file.classes?.length > 0)
|
||||
.slice(0, 10); // Limit to most important files
|
||||
|
||||
if (importantFiles.length === 0) {
|
||||
logger.info('No files requiring detailed analysis found');
|
||||
return { files: {} };
|
||||
}
|
||||
|
||||
const prompt = `Analyze these key files in detail:
|
||||
|
||||
${importantFiles.map(file => `
|
||||
File: ${file.path}
|
||||
Functions: ${JSON.stringify(file.functions)}
|
||||
Classes: ${JSON.stringify(file.classes)}
|
||||
Imports: ${JSON.stringify(file.imports)}
|
||||
Size: ${file.size} bytes, ${file.lines} lines
|
||||
`).join('\n')}
|
||||
|
||||
For each file, provide:
|
||||
1. Purpose and responsibility
|
||||
2. Key functions and their roles
|
||||
3. Dependencies and relationships
|
||||
4. Importance to the overall architecture
|
||||
|
||||
Respond with detailed analysis for each file.`;
|
||||
|
||||
try {
|
||||
const aiService = getAiService({ projectRoot: config.projectRoot });
|
||||
const response = await aiService.generateStructuredOutput({
|
||||
prompt,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
files: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
purpose: { type: 'string' },
|
||||
keyFunctions: { type: 'array', items: { type: 'string' } },
|
||||
dependencies: { type: 'array', items: { type: 'string' } },
|
||||
importance: { type: 'string' },
|
||||
description: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`[Scan #4+]: Detailed analysis completed for ${Object.keys(response.files || {}).length} files`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.warn(`[Scan #4+]: Detailed analysis failed`);
|
||||
return { files: {} };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a comprehensive project summary
|
||||
* @param {Object} scanResults - Raw scan results
|
||||
* @param {Object} projectTypeAnalysis - Project type analysis
|
||||
* @param {Object} coreStructureAnalysis - Core structure analysis
|
||||
* @returns {Object} Project summary
|
||||
*/
|
||||
function generateProjectSummary(scanResults, projectTypeAnalysis, coreStructureAnalysis) {
|
||||
return {
|
||||
overview: `${projectTypeAnalysis.projectType} project with ${scanResults.stats.totalFiles} files across ${scanResults.stats.totalDirectories} directories`,
|
||||
languages: projectTypeAnalysis.languages,
|
||||
frameworks: projectTypeAnalysis.frameworks,
|
||||
architecture: coreStructureAnalysis.architecture?.pattern || 'standard',
|
||||
complexity: scanResults.stats.totalFiles > 100 ? 'high' : scanResults.stats.totalFiles > 50 ? 'medium' : 'low',
|
||||
keyComponents: Object.keys(coreStructureAnalysis.directories || {}).slice(0, 5)
|
||||
};
|
||||
}
|
||||
3
scripts/modules/task-manager/scan-project/index.js
Normal file
3
scripts/modules/task-manager/scan-project/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// Main entry point for scan-project module
|
||||
export { default } from './scan-project.js';
|
||||
export { default as scanProject } from './scan-project.js';
|
||||
61
scripts/modules/task-manager/scan-project/scan-config.js
Normal file
61
scripts/modules/task-manager/scan-project/scan-config.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Configuration classes for project scanning functionality
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration object for scan operations
|
||||
*/
|
||||
export class ScanConfig {
|
||||
constructor({
|
||||
projectRoot,
|
||||
outputPath = null,
|
||||
includeFiles = [],
|
||||
excludeFiles = ['node_modules', '.git', 'dist', 'build', '*.log'],
|
||||
scanDepth = 5,
|
||||
mcpLog = false,
|
||||
reportProgress = false,
|
||||
debug = false
|
||||
} = {}) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.outputPath = outputPath;
|
||||
this.includeFiles = includeFiles;
|
||||
this.excludeFiles = excludeFiles;
|
||||
this.scanDepth = scanDepth;
|
||||
this.mcpLog = mcpLog;
|
||||
this.reportProgress = reportProgress;
|
||||
this.debug = debug;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging configuration for scan operations
|
||||
*/
|
||||
export class ScanLoggingConfig {
|
||||
constructor(mcpLog = false, reportProgress = false) {
|
||||
this.mcpLog = mcpLog;
|
||||
this.reportProgress = reportProgress;
|
||||
}
|
||||
|
||||
report(message, level = 'info') {
|
||||
if (this.reportProgress || this.mcpLog) {
|
||||
const prefix = this.mcpLog ? '[MCP]' : '[SCAN]';
|
||||
console.log(`${prefix} ${level.toUpperCase()}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
debug(message) {
|
||||
this.report(message, 'debug');
|
||||
}
|
||||
|
||||
info(message) {
|
||||
this.report(message, 'info');
|
||||
}
|
||||
|
||||
warn(message) {
|
||||
this.report(message, 'warn');
|
||||
}
|
||||
|
||||
error(message) {
|
||||
this.report(message, 'error');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user