mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-29 22:02:04 +00:00
chore: refactor tm-core to host more of our "core" commands (#1331)
This commit is contained in:
13
.github/workflows/extension-ci.yml
vendored
13
.github/workflows/extension-ci.yml
vendored
@@ -6,15 +6,15 @@ on:
|
|||||||
- main
|
- main
|
||||||
- next
|
- next
|
||||||
paths:
|
paths:
|
||||||
- 'apps/extension/**'
|
- "apps/extension/**"
|
||||||
- '.github/workflows/extension-ci.yml'
|
- ".github/workflows/extension-ci.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- next
|
- next
|
||||||
paths:
|
paths:
|
||||||
- 'apps/extension/**'
|
- "apps/extension/**"
|
||||||
- '.github/workflows/extension-ci.yml'
|
- ".github/workflows/extension-ci.yml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -55,7 +55,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
|
||||||
- name: Restore node_modules
|
- name: Restore node_modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -72,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Type Check Extension
|
- name: Type Check Extension
|
||||||
working-directory: apps/extension
|
working-directory: apps/extension
|
||||||
run: npm run check-types
|
run: npm run typecheck
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
@@ -86,7 +85,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
|
||||||
- name: Restore node_modules
|
- name: Restore node_modules
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -137,4 +135,3 @@ jobs:
|
|||||||
apps/extension/vsix-build/*.vsix
|
apps/extension/vsix-build/*.vsix
|
||||||
apps/extension/dist/
|
apps/extension/dist/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/extension-release.yml
vendored
4
.github/workflows/extension-release.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Type Check Extension
|
- name: Type Check Extension
|
||||||
working-directory: apps/extension
|
working-directory: apps/extension
|
||||||
run: npm run check-types
|
run: npm run typecheck
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
@@ -107,4 +107,4 @@ jobs:
|
|||||||
echo "🎉 Extension ${{ github.ref_name }} successfully published!"
|
echo "🎉 Extension ${{ github.ref_name }} successfully published!"
|
||||||
echo "📦 Available on VS Code Marketplace"
|
echo "📦 Available on VS Code Marketplace"
|
||||||
echo "🌍 Available on Open VSX Registry"
|
echo "🌍 Available on Open VSX Registry"
|
||||||
echo "🏷️ GitHub release created: ${{ github.ref_name }}"
|
echo "🏷️ GitHub release created: ${{ github.ref_name }}"
|
||||||
|
|||||||
42
CLAUDE.md
42
CLAUDE.md
@@ -33,6 +33,48 @@
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Architecture Guidelines
|
||||||
|
|
||||||
|
### Business Logic Separation
|
||||||
|
|
||||||
|
**CRITICAL RULE**: ALL business logic must live in `@tm/core`, NOT in presentation layers.
|
||||||
|
|
||||||
|
- **`@tm/core`** (packages/tm-core/):
|
||||||
|
- Contains ALL business logic, domain models, services, and utilities
|
||||||
|
- Provides clean facade APIs through domain objects (tasks, auth, workflow, git, config)
|
||||||
|
- Houses all complexity - parsing, validation, transformations, calculations, etc.
|
||||||
|
- Example: Task ID parsing, subtask extraction, status validation, dependency resolution
|
||||||
|
|
||||||
|
- **`@tm/cli`** (apps/cli/):
|
||||||
|
- Thin presentation layer ONLY
|
||||||
|
- Calls tm-core methods and displays results
|
||||||
|
- Handles CLI-specific concerns: argument parsing, output formatting, user prompts
|
||||||
|
- NO business logic, NO data transformations, NO calculations
|
||||||
|
|
||||||
|
- **`@tm/mcp`** (apps/mcp/):
|
||||||
|
- Thin presentation layer ONLY
|
||||||
|
- Calls tm-core methods and returns MCP-formatted responses
|
||||||
|
- Handles MCP-specific concerns: tool schemas, parameter validation, response formatting
|
||||||
|
- NO business logic, NO data transformations, NO calculations
|
||||||
|
|
||||||
|
- **`apps/extension`** (future):
|
||||||
|
- Thin presentation layer ONLY
|
||||||
|
- Calls tm-core methods and displays in VS Code UI
|
||||||
|
- NO business logic
|
||||||
|
|
||||||
|
**Examples of violations to avoid:**
|
||||||
|
|
||||||
|
- ❌ Creating helper functions in CLI/MCP to parse task IDs → Move to tm-core
|
||||||
|
- ❌ Data transformation logic in CLI/MCP → Move to tm-core
|
||||||
|
- ❌ Validation logic in CLI/MCP → Move to tm-core
|
||||||
|
- ❌ Duplicating logic across CLI and MCP → Implement once in tm-core
|
||||||
|
|
||||||
|
**Correct approach:**
|
||||||
|
- ✅ Add method to TasksDomain: `tasks.get(taskId)` (automatically handles task and subtask IDs)
|
||||||
|
- ✅ CLI calls: `await tmCore.tasks.get(taskId)` (supports "1", "1.2", "HAM-123", "HAM-123.2")
|
||||||
|
- ✅ MCP calls: `await tmCore.tasks.get(taskId)` (same intelligent ID parsing)
|
||||||
|
- ✅ Single source of truth in tm-core
|
||||||
|
|
||||||
## Documentation Guidelines
|
## Documentation Guidelines
|
||||||
|
|
||||||
- **Documentation location**: Write docs in `apps/docs/` (Mintlify site source), not `docs/`
|
- **Documentation location**: Write docs in `apps/docs/` (Mintlify site source), not `docs/`
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
AuthManager,
|
AuthManager,
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
type AuthCredentials
|
type AuthCredentials
|
||||||
} from '@tm/core/auth';
|
} from '@tm/core';
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
import { ContextCommand } from './context.command.js';
|
import { ContextCommand } from './context.command.js';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import { Command } from 'commander';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import ora, { type Ora } from 'ora';
|
import ora, { type Ora } from 'ora';
|
||||||
import {
|
import { createTmCore, type TmCore, type Task, type Subtask } from '@tm/core';
|
||||||
createTaskMasterCore,
|
|
||||||
type TaskMasterCore,
|
|
||||||
type Task,
|
|
||||||
type Subtask
|
|
||||||
} from '@tm/core';
|
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +55,7 @@ export interface AutopilotCommandResult {
|
|||||||
* This is a thin presentation layer over @tm/core's autopilot functionality
|
* This is a thin presentation layer over @tm/core's autopilot functionality
|
||||||
*/
|
*/
|
||||||
export class AutopilotCommand extends Command {
|
export class AutopilotCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: AutopilotCommandResult;
|
private lastResult?: AutopilotCommandResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -164,11 +159,11 @@ export class AutopilotCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize TaskMasterCore
|
* Initialize TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeCore(projectRoot: string): Promise<void> {
|
private async initializeCore(projectRoot: string): Promise<void> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
this.tmCore = await createTmCore({ projectPath: projectRoot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +172,11 @@ export class AutopilotCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
private async loadTask(taskId: string): Promise<Task | null> {
|
private async loadTask(taskId: string): Promise<Task | null> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { task } = await this.tmCore.getTaskWithSubtask(taskId);
|
const { task } = await this.tmCore.tasks.get(taskId);
|
||||||
return task;
|
return task;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
@@ -236,11 +231,7 @@ export class AutopilotCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate task structure and get execution order
|
// Validate task structure and get execution order
|
||||||
const validationResult = await this.validateTaskStructure(
|
const validationResult = await this.validateTaskStructure(taskId, task);
|
||||||
taskId,
|
|
||||||
task,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
return validationResult;
|
return validationResult;
|
||||||
}
|
}
|
||||||
@@ -288,19 +279,23 @@ export class AutopilotCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
private async validateTaskStructure(
|
private async validateTaskStructure(
|
||||||
taskId: string,
|
taskId: string,
|
||||||
task: Task,
|
task: Task
|
||||||
options: AutopilotCommandOptions
|
|
||||||
): Promise<AutopilotCommandResult & { orderedSubtasks?: Subtask[] }> {
|
): Promise<AutopilotCommandResult & { orderedSubtasks?: Subtask[] }> {
|
||||||
const { TaskLoaderService } = await import('@tm/core');
|
if (!this.tmCore) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
taskId,
|
||||||
|
task,
|
||||||
|
error: 'TmCore not initialized'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
console.log(chalk.cyan.bold('Validating task structure...'));
|
console.log(chalk.cyan.bold('Validating task structure...'));
|
||||||
|
|
||||||
const taskLoader = new TaskLoaderService(options.project || process.cwd());
|
const validationResult = await this.tmCore.tasks.loadAndValidate(taskId);
|
||||||
const validationResult = await taskLoader.loadAndValidateTask(taskId);
|
|
||||||
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
await taskLoader.cleanup();
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
taskId,
|
taskId,
|
||||||
@@ -310,12 +305,10 @@ export class AutopilotCommand extends Command {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderedSubtasks = taskLoader.getExecutionOrder(
|
const orderedSubtasks = this.tmCore.tasks.getExecutionOrder(
|
||||||
validationResult.task!
|
validationResult.task!
|
||||||
);
|
);
|
||||||
|
|
||||||
await taskLoader.cleanup();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
taskId,
|
taskId,
|
||||||
@@ -499,7 +492,6 @@ export class AutopilotCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
this.tmCore = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createTaskMasterCore, type WorkflowContext } from '@tm/core';
|
import { createTmCore, type WorkflowContext } from '@tm/core';
|
||||||
import {
|
import {
|
||||||
AutopilotBaseOptions,
|
AutopilotBaseOptions,
|
||||||
hasWorkflowState,
|
hasWorkflowState,
|
||||||
@@ -67,20 +67,19 @@ export class StartCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Task Master Core
|
// Initialize Task Master Core
|
||||||
const tmCore = await createTaskMasterCore({
|
const tmCore = await createTmCore({
|
||||||
projectPath: mergedOptions.projectRoot!
|
projectPath: mergedOptions.projectRoot!
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get current tag from ConfigManager
|
// Get current tag from ConfigManager
|
||||||
const currentTag = tmCore.getActiveTag();
|
const currentTag = tmCore.config.getActiveTag();
|
||||||
|
|
||||||
// Load task
|
// Load task
|
||||||
formatter.info(`Loading task ${taskId}...`);
|
formatter.info(`Loading task ${taskId}...`);
|
||||||
const { task } = await tmCore.getTaskWithSubtask(taskId);
|
const { task } = await tmCore.tasks.get(taskId);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
formatter.error('Task not found', { taskId });
|
formatter.error('Task not found', { taskId });
|
||||||
await tmCore.close();
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +89,6 @@ export class StartCommand extends Command {
|
|||||||
taskId,
|
taskId,
|
||||||
suggestion: `Run: task-master expand --id=${taskId}`
|
suggestion: `Run: task-master expand --id=${taskId}`
|
||||||
});
|
});
|
||||||
await tmCore.close();
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +154,6 @@ export class StartCommand extends Command {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
await tmCore.close();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
formatter.error((error as Error).message);
|
formatter.error((error as Error).message);
|
||||||
if (mergedOptions.verbose) {
|
if (mergedOptions.verbose) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import chalk from 'chalk';
|
|||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import search from '@inquirer/search';
|
import search from '@inquirer/search';
|
||||||
import ora, { Ora } from 'ora';
|
import ora, { Ora } from 'ora';
|
||||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
import { AuthManager, type UserContext } from '@tm/core';
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ import { Command } from 'commander';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import ora, { Ora } from 'ora';
|
import ora, { Ora } from 'ora';
|
||||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
import {
|
||||||
import { TaskMasterCore, type ExportResult } from '@tm/core';
|
AuthManager,
|
||||||
|
type UserContext,
|
||||||
|
type ExportResult,
|
||||||
|
createTmCore,
|
||||||
|
type TmCore
|
||||||
|
} from '@tm/core';
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
|
|
||||||
@@ -28,7 +33,7 @@ export interface ExportCommandResult {
|
|||||||
*/
|
*/
|
||||||
export class ExportCommand extends Command {
|
export class ExportCommand extends Command {
|
||||||
private authManager: AuthManager;
|
private authManager: AuthManager;
|
||||||
private taskMasterCore?: TaskMasterCore;
|
private taskMasterCore?: TmCore;
|
||||||
private lastResult?: ExportCommandResult;
|
private lastResult?: ExportCommandResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -61,7 +66,7 @@ export class ExportCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the TaskMasterCore
|
* Initialize the TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeServices(): Promise<void> {
|
private async initializeServices(): Promise<void> {
|
||||||
if (this.taskMasterCore) {
|
if (this.taskMasterCore) {
|
||||||
@@ -69,8 +74,8 @@ export class ExportCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize TaskMasterCore
|
// Initialize TmCore
|
||||||
this.taskMasterCore = await TaskMasterCore.create({
|
this.taskMasterCore = await createTmCore({
|
||||||
projectPath: process.cwd()
|
projectPath: process.cwd()
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -152,7 +157,8 @@ export class ExportCommand extends Command {
|
|||||||
// Perform export
|
// Perform export
|
||||||
spinner = ora('Exporting tasks...').start();
|
spinner = ora('Exporting tasks...').start();
|
||||||
|
|
||||||
const exportResult = await this.taskMasterCore!.exportTasks({
|
// Use integration domain facade
|
||||||
|
const exportResult = await this.taskMasterCore!.integration.exportTasks({
|
||||||
orgId,
|
orgId,
|
||||||
briefId,
|
briefId,
|
||||||
tag: options?.tag,
|
tag: options?.tag,
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import {
|
import {
|
||||||
createTaskMasterCore,
|
createTmCore,
|
||||||
type Task,
|
type Task,
|
||||||
type TaskStatus,
|
type TaskStatus,
|
||||||
type TaskMasterCore,
|
type TmCore,
|
||||||
TASK_STATUSES,
|
TASK_STATUSES,
|
||||||
OUTPUT_FORMATS,
|
OUTPUT_FORMATS,
|
||||||
STATUS_ICONS,
|
STATUS_ICONS,
|
||||||
type OutputFormat
|
type OutputFormat
|
||||||
} from '@tm/core';
|
} from '@tm/core';
|
||||||
import type { StorageType } from '@tm/core/types';
|
import type { StorageType } from '@tm/core';
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||||
@@ -59,7 +59,7 @@ export interface ListTasksResult {
|
|||||||
* This is a thin presentation layer over @tm/core
|
* This is a thin presentation layer over @tm/core
|
||||||
*/
|
*/
|
||||||
export class ListTasksCommand extends Command {
|
export class ListTasksCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: ListTasksResult;
|
private lastResult?: ListTasksResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -144,11 +144,11 @@ export class ListTasksCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize TaskMasterCore
|
* Initialize TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeCore(projectRoot: string): Promise<void> {
|
private async initializeCore(projectRoot: string): Promise<void> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
this.tmCore = await createTmCore({ projectPath: projectRoot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ export class ListTasksCommand extends Command {
|
|||||||
options: ListCommandOptions
|
options: ListCommandOptions
|
||||||
): Promise<ListTasksResult> {
|
): Promise<ListTasksResult> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build filter
|
// Build filter
|
||||||
@@ -173,7 +173,7 @@ export class ListTasksCommand extends Command {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Call tm-core
|
// Call tm-core
|
||||||
const result = await this.tmCore.getTaskList({
|
const result = await this.tmCore.tasks.list({
|
||||||
tag: options.tag,
|
tag: options.tag,
|
||||||
filter,
|
filter,
|
||||||
includeSubtasks: options.withSubtasks
|
includeSubtasks: options.withSubtasks
|
||||||
@@ -459,7 +459,6 @@ export class ListTasksCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
this.tmCore = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import path from 'node:path';
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
import { createTmCore, type Task, type TmCore } from '@tm/core';
|
||||||
import type { StorageType } from '@tm/core/types';
|
import type { StorageType } from '@tm/core';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||||
@@ -38,7 +38,7 @@ export interface NextTaskResult {
|
|||||||
* This is a thin presentation layer over @tm/core
|
* This is a thin presentation layer over @tm/core
|
||||||
*/
|
*/
|
||||||
export class NextCommand extends Command {
|
export class NextCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: NextTaskResult;
|
private lastResult?: NextTaskResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -104,12 +104,12 @@ export class NextCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize TaskMasterCore
|
* Initialize TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeCore(projectRoot: string): Promise<void> {
|
private async initializeCore(projectRoot: string): Promise<void> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
const resolved = path.resolve(projectRoot);
|
const resolved = path.resolve(projectRoot);
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: resolved });
|
this.tmCore = await createTmCore({ projectPath: resolved });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,18 +120,18 @@ export class NextCommand extends Command {
|
|||||||
options: NextCommandOptions
|
options: NextCommandOptions
|
||||||
): Promise<NextTaskResult> {
|
): Promise<NextTaskResult> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call tm-core to get next task
|
// Call tm-core to get next task
|
||||||
const task = await this.tmCore.getNextTask(options.tag);
|
const task = await this.tmCore.tasks.getNext(options.tag);
|
||||||
|
|
||||||
// Get storage type and active tag
|
// Get storage type and active tag
|
||||||
const storageType = this.tmCore.getStorageType();
|
const storageType = this.tmCore.config.getStorageConfig().type;
|
||||||
if (storageType === 'auto') {
|
if (storageType === 'auto') {
|
||||||
throw new Error('Storage type must be resolved before use');
|
throw new Error('Storage type must be resolved before use');
|
||||||
}
|
}
|
||||||
const activeTag = options.tag || this.tmCore.getActiveTag();
|
const activeTag = options.tag || this.tmCore.config.getActiveTag();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
task,
|
task,
|
||||||
@@ -232,7 +232,6 @@ export class NextCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
this.tmCore = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,8 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import {
|
import { createTmCore, type TmCore, type TaskStatus } from '@tm/core';
|
||||||
createTaskMasterCore,
|
import type { StorageType } from '@tm/core';
|
||||||
type TaskMasterCore,
|
|
||||||
type TaskStatus
|
|
||||||
} from '@tm/core';
|
|
||||||
import type { StorageType } from '@tm/core/types';
|
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +52,7 @@ export interface SetStatusResult {
|
|||||||
* This is a thin presentation layer over @tm/core
|
* This is a thin presentation layer over @tm/core
|
||||||
*/
|
*/
|
||||||
export class SetStatusCommand extends Command {
|
export class SetStatusCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: SetStatusResult;
|
private lastResult?: SetStatusResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -112,7 +108,7 @@ export class SetStatusCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize TaskMaster core
|
// Initialize TaskMaster core
|
||||||
this.tmCore = await createTaskMasterCore({
|
this.tmCore = await createTmCore({
|
||||||
projectPath: options.project || process.cwd()
|
projectPath: options.project || process.cwd()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,7 +124,7 @@ export class SetStatusCommand extends Command {
|
|||||||
|
|
||||||
for (const taskId of taskIds) {
|
for (const taskId of taskIds) {
|
||||||
try {
|
try {
|
||||||
const result = await this.tmCore.updateTaskStatus(
|
const result = await this.tmCore.tasks.updateStatus(
|
||||||
taskId,
|
taskId,
|
||||||
options.status
|
options.status
|
||||||
);
|
);
|
||||||
@@ -168,7 +164,7 @@ export class SetStatusCommand extends Command {
|
|||||||
this.lastResult = {
|
this.lastResult = {
|
||||||
success: true,
|
success: true,
|
||||||
updatedTasks,
|
updatedTasks,
|
||||||
storageType: this.tmCore.getStorageType() as Exclude<
|
storageType: this.tmCore.config.getStorageConfig().type as Exclude<
|
||||||
StorageType,
|
StorageType,
|
||||||
'auto'
|
'auto'
|
||||||
>
|
>
|
||||||
@@ -188,7 +184,6 @@ export class SetStatusCommand extends Command {
|
|||||||
} finally {
|
} finally {
|
||||||
// Clean up resources
|
// Clean up resources
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
import { createTmCore, type Task, type TmCore } from '@tm/core';
|
||||||
import type { StorageType } from '@tm/core/types';
|
import type { StorageType } from '@tm/core';
|
||||||
import * as ui from '../utils/ui.js';
|
import * as ui from '../utils/ui.js';
|
||||||
import { displayError } from '../utils/error-handler.js';
|
import { displayError } from '../utils/error-handler.js';
|
||||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||||
@@ -47,7 +47,7 @@ export interface ShowMultipleTasksResult {
|
|||||||
* This is a thin presentation layer over @tm/core
|
* This is a thin presentation layer over @tm/core
|
||||||
*/
|
*/
|
||||||
export class ShowCommand extends Command {
|
export class ShowCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: ShowTaskResult | ShowMultipleTasksResult;
|
private lastResult?: ShowTaskResult | ShowMultipleTasksResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -133,11 +133,11 @@ export class ShowCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize TaskMasterCore
|
* Initialize TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeCore(projectRoot: string): Promise<void> {
|
private async initializeCore(projectRoot: string): Promise<void> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
this.tmCore = await createTmCore({ projectPath: projectRoot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,18 +149,18 @@ export class ShowCommand extends Command {
|
|||||||
_options: ShowCommandOptions
|
_options: ShowCommandOptions
|
||||||
): Promise<ShowTaskResult> {
|
): Promise<ShowTaskResult> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the task
|
// Get the task
|
||||||
const task = await this.tmCore.getTask(taskId);
|
const result = await this.tmCore.tasks.get(taskId);
|
||||||
|
|
||||||
// Get storage type
|
// Get storage type
|
||||||
const storageType = this.tmCore.getStorageType();
|
const storageType = this.tmCore.config.getStorageConfig().type;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
task,
|
task: result.task,
|
||||||
found: task !== null,
|
found: result.task !== null,
|
||||||
storageType: storageType as Exclude<StorageType, 'auto'>
|
storageType: storageType as Exclude<StorageType, 'auto'>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,7 @@ export class ShowCommand extends Command {
|
|||||||
_options: ShowCommandOptions
|
_options: ShowCommandOptions
|
||||||
): Promise<ShowMultipleTasksResult> {
|
): Promise<ShowMultipleTasksResult> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks: Task[] = [];
|
const tasks: Task[] = [];
|
||||||
@@ -181,16 +181,16 @@ export class ShowCommand extends Command {
|
|||||||
|
|
||||||
// Get each task individually
|
// Get each task individually
|
||||||
for (const taskId of taskIds) {
|
for (const taskId of taskIds) {
|
||||||
const task = await this.tmCore.getTask(taskId);
|
const result = await this.tmCore.tasks.get(taskId);
|
||||||
if (task) {
|
if (result.task) {
|
||||||
tasks.push(task);
|
tasks.push(result.task);
|
||||||
} else {
|
} else {
|
||||||
notFound.push(taskId);
|
notFound.push(taskId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get storage type
|
// Get storage type
|
||||||
const storageType = this.tmCore.getStorageType();
|
const storageType = this.tmCore.config.getStorageConfig().type;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tasks,
|
tasks,
|
||||||
@@ -253,7 +253,7 @@ export class ShowCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display header with storage info
|
// Display header with storage info
|
||||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
const activeTag = this.tmCore?.config.getActiveTag() || 'master';
|
||||||
displayCommandHeader(this.tmCore, {
|
displayCommandHeader(this.tmCore, {
|
||||||
tag: activeTag,
|
tag: activeTag,
|
||||||
storageType: result.storageType
|
storageType: result.storageType
|
||||||
@@ -276,7 +276,7 @@ export class ShowCommand extends Command {
|
|||||||
_options: ShowCommandOptions
|
_options: ShowCommandOptions
|
||||||
): void {
|
): void {
|
||||||
// Display header with storage info
|
// Display header with storage info
|
||||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
const activeTag = this.tmCore?.config.getActiveTag() || 'master';
|
||||||
displayCommandHeader(this.tmCore, {
|
displayCommandHeader(this.tmCore, {
|
||||||
tag: activeTag,
|
tag: activeTag,
|
||||||
storageType: result.storageType
|
storageType: result.storageType
|
||||||
@@ -322,7 +322,6 @@ export class ShowCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
this.tmCore = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import boxen from 'boxen';
|
|||||||
import ora, { type Ora } from 'ora';
|
import ora, { type Ora } from 'ora';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import {
|
import {
|
||||||
createTaskMasterCore,
|
createTmCore,
|
||||||
type TaskMasterCore,
|
type TmCore,
|
||||||
type StartTaskResult as CoreStartTaskResult
|
type StartTaskResult as CoreStartTaskResult
|
||||||
} from '@tm/core';
|
} from '@tm/core';
|
||||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||||
@@ -43,7 +43,7 @@ export interface StartCommandResult extends CoreStartTaskResult {
|
|||||||
* This is a thin presentation layer over @tm/core's TaskExecutionService
|
* This is a thin presentation layer over @tm/core's TaskExecutionService
|
||||||
*/
|
*/
|
||||||
export class StartCommand extends Command {
|
export class StartCommand extends Command {
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
private lastResult?: StartCommandResult;
|
private lastResult?: StartCommandResult;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
@@ -147,7 +147,7 @@ export class StartCommand extends Command {
|
|||||||
// Convert core result to CLI result with storage type
|
// Convert core result to CLI result with storage type
|
||||||
const result: StartCommandResult = {
|
const result: StartCommandResult = {
|
||||||
...coreResult,
|
...coreResult,
|
||||||
storageType: this.tmCore?.getStorageType()
|
storageType: this.tmCore?.config.getStorageConfig().type
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store result for programmatic access
|
// Store result for programmatic access
|
||||||
@@ -180,11 +180,11 @@ export class StartCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize TaskMasterCore
|
* Initialize TmCore
|
||||||
*/
|
*/
|
||||||
private async initializeCore(projectRoot: string): Promise<void> {
|
private async initializeCore(projectRoot: string): Promise<void> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
this.tmCore = await createTmCore({ projectPath: projectRoot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +193,9 @@ export class StartCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
private async performGetNextTask(): Promise<string | null> {
|
private async performGetNextTask(): Promise<string | null> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
return this.tmCore.getNextAvailableTask();
|
return this.tmCore.tasks.getNextAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,11 +204,10 @@ export class StartCommand extends Command {
|
|||||||
private async showPreLaunchMessage(targetTaskId: string): Promise<void> {
|
private async showPreLaunchMessage(targetTaskId: string): Promise<void> {
|
||||||
if (!this.tmCore) return;
|
if (!this.tmCore) return;
|
||||||
|
|
||||||
const { task, subtask, subtaskId } =
|
const { task, isSubtask } = await this.tmCore.tasks.get(targetTaskId);
|
||||||
await this.tmCore.getTaskWithSubtask(targetTaskId);
|
|
||||||
if (task) {
|
if (task) {
|
||||||
const workItemText = subtask
|
const workItemText = isSubtask
|
||||||
? `Subtask #${task.id}.${subtaskId} - ${subtask.title}`
|
? `Subtask #${targetTaskId} - ${task.title}`
|
||||||
: `Task #${task.id} - ${task.title}`;
|
: `Task #${task.id} - ${task.title}`;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -227,7 +226,7 @@ export class StartCommand extends Command {
|
|||||||
options: StartCommandOptions
|
options: StartCommandOptions
|
||||||
): Promise<CoreStartTaskResult> {
|
): Promise<CoreStartTaskResult> {
|
||||||
if (!this.tmCore) {
|
if (!this.tmCore) {
|
||||||
throw new Error('TaskMasterCore not initialized');
|
throw new Error('TmCore not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show spinner for status update if enabled
|
// Show spinner for status update if enabled
|
||||||
@@ -237,7 +236,7 @@ export class StartCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get execution command from tm-core (instead of executing directly)
|
// Get execution command from tm-core (instead of executing directly)
|
||||||
const result = await this.tmCore.startTask(targetTaskId, {
|
const result = await this.tmCore.tasks.start(targetTaskId, {
|
||||||
dryRun: options.dryRun,
|
dryRun: options.dryRun,
|
||||||
force: options.force,
|
force: options.force,
|
||||||
updateStatus: !options.noStatusUpdate
|
updateStatus: !options.noStatusUpdate
|
||||||
@@ -471,7 +470,6 @@ export class StartCommand extends Command {
|
|||||||
*/
|
*/
|
||||||
async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
this.tmCore = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,5 +41,5 @@ export type {
|
|||||||
Task,
|
Task,
|
||||||
TaskStatus,
|
TaskStatus,
|
||||||
TaskPriority,
|
TaskPriority,
|
||||||
TaskMasterCore
|
TmCore
|
||||||
} from '@tm/core';
|
} from '@tm/core';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task, TaskPriority } from '@tm/core/types';
|
import type { Task, TaskPriority } from '@tm/core';
|
||||||
import { getComplexityWithColor } from '../../utils/ui.js';
|
import { getComplexityWithColor } from '../../utils/ui.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core';
|
||||||
import { getComplexityWithColor, getBoxWidth } from '../../utils/ui.js';
|
import { getComplexityWithColor, getBoxWidth } from '../../utils/ui.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import boxen from 'boxen';
|
|||||||
import Table from 'cli-table3';
|
import Table from 'cli-table3';
|
||||||
import { marked, MarkedExtension } from 'marked';
|
import { marked, MarkedExtension } from 'marked';
|
||||||
import { markedTerminal } from 'marked-terminal';
|
import { markedTerminal } from 'marked-terminal';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core';
|
||||||
import {
|
import {
|
||||||
getStatusWithColor,
|
getStatusWithColor,
|
||||||
getPriorityWithColor,
|
getPriorityWithColor,
|
||||||
|
|||||||
@@ -3,73 +3,41 @@
|
|||||||
* Provides DRY utilities for displaying headers and other command output
|
* Provides DRY utilities for displaying headers and other command output
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TaskMasterCore } from '@tm/core';
|
import type { TmCore } from '@tm/core';
|
||||||
import type { StorageType } from '@tm/core/types';
|
import type { StorageType } from '@tm/core';
|
||||||
import { displayHeader, type BriefInfo } from '../ui/index.js';
|
import { displayHeader } from '../ui/index.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Get web app base URL from environment
|
|
||||||
*/
|
|
||||||
function getWebAppUrl(): string | undefined {
|
|
||||||
const baseDomain =
|
|
||||||
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
|
|
||||||
|
|
||||||
if (!baseDomain) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it already includes protocol, use as-is
|
|
||||||
if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) {
|
|
||||||
return baseDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, add protocol based on domain
|
|
||||||
if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) {
|
|
||||||
return `http://${baseDomain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `https://${baseDomain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the command header with appropriate storage information
|
* Display the command header with appropriate storage information
|
||||||
* Handles both API and file storage displays
|
* Handles both API and file storage displays
|
||||||
*/
|
*/
|
||||||
export function displayCommandHeader(
|
export function displayCommandHeader(
|
||||||
tmCore: TaskMasterCore | undefined,
|
tmCore: TmCore | undefined,
|
||||||
options: {
|
options: {
|
||||||
tag?: string;
|
tag?: string;
|
||||||
storageType: Exclude<StorageType, 'auto'>;
|
storageType: Exclude<StorageType, 'auto'>;
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
const { tag, storageType } = options;
|
if (!tmCore) {
|
||||||
|
// Fallback display if tmCore is not available
|
||||||
// Get brief info if using API storage
|
displayHeader({
|
||||||
let briefInfo: BriefInfo | undefined;
|
tag: options.tag || 'master',
|
||||||
if (storageType === 'api' && tmCore) {
|
storageType: options.storageType
|
||||||
const storageInfo = tmCore.getStorageDisplayInfo();
|
});
|
||||||
if (storageInfo) {
|
return;
|
||||||
// Construct full brief info with web app URL
|
|
||||||
briefInfo = {
|
|
||||||
...storageInfo,
|
|
||||||
webAppUrl: getWebAppUrl()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get file path for display (only for file storage)
|
// Get the resolved storage type from tasks domain
|
||||||
// Note: The file structure is fixed for file storage and won't change.
|
const resolvedStorageType = tmCore.tasks.getStorageType();
|
||||||
// This is a display-only relative path, not used for actual file operations.
|
|
||||||
const filePath =
|
|
||||||
storageType === 'file' && tmCore
|
|
||||||
? `.taskmaster/tasks/tasks.json`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
// Display header
|
// Get storage display info from tm-core (single source of truth)
|
||||||
|
const displayInfo = tmCore.auth.getStorageDisplayInfo(resolvedStorageType);
|
||||||
|
|
||||||
|
// Display header with computed display info
|
||||||
displayHeader({
|
displayHeader({
|
||||||
tag: tag || 'master',
|
tag: options.tag || 'master',
|
||||||
filePath: filePath,
|
filePath: displayInfo.filePath,
|
||||||
storageType: storageType === 'api' ? 'api' : 'file',
|
storageType: displayInfo.storageType,
|
||||||
briefInfo: briefInfo
|
briefInfo: displayInfo.briefInfo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import Table from 'cli-table3';
|
import Table from 'cli-table3';
|
||||||
import type { Task, TaskStatus, TaskPriority } from '@tm/core/types';
|
import type { Task, TaskStatus, TaskPriority } from '@tm/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get colored status display with ASCII icons (matches scripts/modules/ui.js style)
|
* Get colored status display with ASCII icons (matches scripts/modules/ui.js style)
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ Workflows upload artifacts that you can download:
|
|||||||
|
|
||||||
- Check extension code compiles locally: `cd apps/extension && npm run build`
|
- Check extension code compiles locally: `cd apps/extension && npm run build`
|
||||||
- Verify tests pass locally: `npm run test`
|
- Verify tests pass locally: `npm run test`
|
||||||
- Check for TypeScript errors: `npm run check-types`
|
- Check for TypeScript errors: `npm run typecheck`
|
||||||
|
|
||||||
#### Packaging Failures
|
#### Packaging Failures
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ npm run build:css
|
|||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Type checking
|
# Type checking
|
||||||
npm run check-types
|
npm run typecheck
|
||||||
|
|
||||||
# Linting
|
# Linting
|
||||||
npm run lint
|
npm run lint
|
||||||
|
|||||||
@@ -237,7 +237,7 @@
|
|||||||
"watch": "npm run watch:js & npm run watch:css",
|
"watch": "npm run watch:js & npm run watch:css",
|
||||||
"watch:js": "node ./esbuild.js --watch",
|
"watch:js": "node ./esbuild.js --watch",
|
||||||
"watch:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch",
|
"watch:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch",
|
||||||
"check-types": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { createTaskMasterCore, type TaskMasterCore } from '@tm/core';
|
import { createTmCore, type TmCore } from '@tm/core';
|
||||||
import type { ExtensionLogger } from '../utils/logger';
|
import type { ExtensionLogger } from '../utils/logger';
|
||||||
|
|
||||||
export interface TerminalExecutionOptions {
|
export interface TerminalExecutionOptions {
|
||||||
@@ -21,7 +21,7 @@ export interface TerminalExecutionResult {
|
|||||||
|
|
||||||
export class TerminalManager {
|
export class TerminalManager {
|
||||||
private terminals = new Map<string, vscode.Terminal>();
|
private terminals = new Map<string, vscode.Terminal>();
|
||||||
private tmCore?: TaskMasterCore;
|
private tmCore?: TmCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private context: vscode.ExtensionContext,
|
private context: vscode.ExtensionContext,
|
||||||
@@ -49,7 +49,7 @@ export class TerminalManager {
|
|||||||
await this.initializeCore();
|
await this.initializeCore();
|
||||||
|
|
||||||
// Use tm-core to start the task (same as CLI)
|
// Use tm-core to start the task (same as CLI)
|
||||||
const startResult = await this.tmCore!.startTask(taskId, {
|
const startResult = await this.tmCore!.tasks.start(taskId, {
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
force: false,
|
force: false,
|
||||||
updateStatus: true
|
updateStatus: true
|
||||||
@@ -110,7 +110,7 @@ export class TerminalManager {
|
|||||||
if (!workspaceRoot) {
|
if (!workspaceRoot) {
|
||||||
throw new Error('No workspace folder found');
|
throw new Error('No workspace folder found');
|
||||||
}
|
}
|
||||||
this.tmCore = await createTaskMasterCore({ projectPath: workspaceRoot });
|
this.tmCore = await createTmCore({ projectPath: workspaceRoot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,13 +144,9 @@ export class TerminalManager {
|
|||||||
});
|
});
|
||||||
this.terminals.clear();
|
this.terminals.clear();
|
||||||
|
|
||||||
|
// Clear tm-core reference (no explicit cleanup needed)
|
||||||
if (this.tmCore) {
|
if (this.tmCore) {
|
||||||
try {
|
this.tmCore = undefined;
|
||||||
await this.tmCore.close();
|
|
||||||
this.tmCore = undefined;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error('Failed to close tm-core:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from '../../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
import type { MCPContext } from '../../shared/types.js';
|
||||||
import { createTaskMasterCore } from '@tm/core';
|
import { createTmCore } from '@tm/core';
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
@@ -83,17 +83,16 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load task data and get current tag
|
// Load task data and get current tag
|
||||||
const core = await createTaskMasterCore({
|
const core = await createTmCore({
|
||||||
projectPath: projectRoot
|
projectPath: projectRoot
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get current tag from ConfigManager
|
// Get current tag from ConfigManager
|
||||||
const currentTag = core.getActiveTag();
|
const currentTag = core.config.getActiveTag();
|
||||||
|
|
||||||
const taskResult = await core.getTaskWithSubtask(taskId);
|
const taskResult = await core.tasks.get(taskId);
|
||||||
|
|
||||||
if (!taskResult || !taskResult.task) {
|
if (!taskResult || !taskResult.task) {
|
||||||
await core.close();
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -108,7 +107,6 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
|
|
||||||
// Validate task has subtasks
|
// Validate task has subtasks
|
||||||
if (!task.subtasks || task.subtasks.length === 0) {
|
if (!task.subtasks || task.subtasks.length === 0) {
|
||||||
await core.close();
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -79,6 +79,7 @@
|
|||||||
"@manypkg/cli": "^0.25.1",
|
"@manypkg/cli": "^0.25.1",
|
||||||
"@tm/ai-sdk-provider-grok-cli": "*",
|
"@tm/ai-sdk-provider-grok-cli": "*",
|
||||||
"@tm/cli": "*",
|
"@tm/cli": "*",
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/marked-terminal": "^6.1.1",
|
"@types/marked-terminal": "^6.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@
|
|||||||
"@manypkg/cli": "^0.25.1",
|
"@manypkg/cli": "^0.25.1",
|
||||||
"@tm/ai-sdk-provider-grok-cli": "*",
|
"@tm/ai-sdk-provider-grok-cli": "*",
|
||||||
"@tm/cli": "*",
|
"@tm/cli": "*",
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/marked-terminal": "^6.1.1",
|
"@types/marked-terminal": "^6.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Authentication module exports
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { AuthManager } from './auth-manager.js';
|
|
||||||
export { CredentialStore } from './credential-store.js';
|
|
||||||
export { OAuthService } from './oauth-service.js';
|
|
||||||
export { SupabaseSessionStorage } from './supabase-session-storage.js';
|
|
||||||
export type {
|
|
||||||
Organization,
|
|
||||||
Brief,
|
|
||||||
RemoteTask
|
|
||||||
} from '../services/organization.service.js';
|
|
||||||
|
|
||||||
export type {
|
|
||||||
AuthCredentials,
|
|
||||||
OAuthFlowOptions,
|
|
||||||
AuthConfig,
|
|
||||||
CliData,
|
|
||||||
UserContext
|
|
||||||
} from './types.js';
|
|
||||||
|
|
||||||
export { AuthenticationError } from './types.js';
|
|
||||||
|
|
||||||
export {
|
|
||||||
DEFAULT_AUTH_CONFIG,
|
|
||||||
getAuthConfig
|
|
||||||
} from './config.js';
|
|
||||||
@@ -8,8 +8,8 @@ export type * from './storage.interface.js';
|
|||||||
export * from './storage.interface.js';
|
export * from './storage.interface.js';
|
||||||
|
|
||||||
// AI Provider interfaces
|
// AI Provider interfaces
|
||||||
export type * from './ai-provider.interface.js';
|
export type * from '../../modules/ai/interfaces/ai-provider.interface.js';
|
||||||
export * from './ai-provider.interface.js';
|
export * from '../../modules/ai/interfaces/ai-provider.interface.js';
|
||||||
|
|
||||||
// Configuration interfaces
|
// Configuration interfaces
|
||||||
export type * from './configuration.interface.js';
|
export type * from './configuration.interface.js';
|
||||||
@@ -164,6 +164,12 @@ export interface IStorage {
|
|||||||
* @returns Promise that resolves to storage statistics
|
* @returns Promise that resolves to storage statistics
|
||||||
*/
|
*/
|
||||||
getStats(): Promise<StorageStats>;
|
getStats(): Promise<StorageStats>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the storage type identifier
|
||||||
|
* @returns The type of storage implementation ('file' or 'api')
|
||||||
|
*/
|
||||||
|
getStorageType(): 'file' | 'api';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,6 +247,7 @@ export abstract class BaseStorage implements IStorage {
|
|||||||
abstract initialize(): Promise<void>;
|
abstract initialize(): Promise<void>;
|
||||||
abstract close(): Promise<void>;
|
abstract close(): Promise<void>;
|
||||||
abstract getStats(): Promise<StorageStats>;
|
abstract getStats(): Promise<StorageStats>;
|
||||||
|
abstract getStorageType(): 'file' | 'api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to generate backup filename
|
* Utility method to generate backup filename
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* Public API for the executors module
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './types.js';
|
|
||||||
export { BaseExecutor } from './base-executor.js';
|
|
||||||
export { ClaudeExecutor } from './claude-executor.js';
|
|
||||||
export { ExecutorFactory } from './executor-factory.js';
|
|
||||||
export {
|
|
||||||
ExecutorService,
|
|
||||||
type ExecutorServiceOptions
|
|
||||||
} from './executor-service.js';
|
|
||||||
@@ -1,117 +1,127 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Main entry point for the tm-core package
|
* @fileoverview Main entry point for @tm/core
|
||||||
* This file exports all public APIs from the core Task Master library
|
* Provides unified access to all Task Master functionality through TmCore
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Export main facade
|
import type { TasksDomain } from './modules/tasks/tasks-domain.js';
|
||||||
export {
|
|
||||||
TaskMasterCore,
|
|
||||||
createTaskMasterCore,
|
|
||||||
type TaskMasterCoreOptions,
|
|
||||||
type ListTasksResult,
|
|
||||||
type StartTaskOptions,
|
|
||||||
type StartTaskResult,
|
|
||||||
type ConflictCheckResult,
|
|
||||||
type ExportTasksOptions,
|
|
||||||
type ExportResult
|
|
||||||
} from './task-master-core.js';
|
|
||||||
|
|
||||||
// Re-export types
|
// ========== Primary API ==========
|
||||||
export type * from './types/index.js';
|
|
||||||
|
|
||||||
// Re-export interfaces (types only to avoid conflicts)
|
/**
|
||||||
export type * from './interfaces/index.js';
|
* Create a new TmCore instance - The ONLY way to use tm-core
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { createTmCore } from '@tm/core';
|
||||||
|
*
|
||||||
|
* const tmcore = await createTmCore({
|
||||||
|
* projectPath: process.cwd()
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Access domains
|
||||||
|
* await tmcore.auth.login({ ... });
|
||||||
|
* const tasks = await tmcore.tasks.list();
|
||||||
|
* await tmcore.workflow.start({ taskId: '1' });
|
||||||
|
* await tmcore.git.commit('feat: add feature');
|
||||||
|
* const config = tmcore.config.get('models.main');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export { createTmCore, TmCore, type TmCoreOptions } from './tm-core.js';
|
||||||
|
|
||||||
// Re-export constants
|
// ========== Type Exports ==========
|
||||||
export * from './constants/index.js';
|
|
||||||
|
|
||||||
// Re-export providers
|
// Common types that consumers need
|
||||||
export * from './providers/index.js';
|
export type * from './common/types/index.js';
|
||||||
|
|
||||||
// Re-export storage (selectively to avoid conflicts)
|
// Common interfaces
|
||||||
export {
|
export type * from './common/interfaces/index.js';
|
||||||
FileStorage,
|
|
||||||
ApiStorage,
|
|
||||||
StorageFactory,
|
|
||||||
type ApiStorageConfig
|
|
||||||
} from './storage/index.js';
|
|
||||||
export { PlaceholderStorage, type StorageAdapter } from './storage/index.js';
|
|
||||||
|
|
||||||
// Re-export parser
|
// Constants
|
||||||
export * from './parser/index.js';
|
export * from './common/constants/index.js';
|
||||||
|
|
||||||
// Re-export utilities
|
// Errors
|
||||||
export * from './utils/index.js';
|
export * from './common/errors/index.js';
|
||||||
|
|
||||||
// Re-export errors
|
// ========== Domain-Specific Type Exports ==========
|
||||||
export * from './errors/index.js';
|
|
||||||
|
|
||||||
// Re-export entities
|
// Task types
|
||||||
export { TaskEntity } from './entities/task.entity.js';
|
export type {
|
||||||
|
TaskListResult,
|
||||||
|
GetTaskListOptions
|
||||||
|
} from './modules/tasks/services/task-service.js';
|
||||||
|
|
||||||
// Re-export authentication
|
export type {
|
||||||
export {
|
StartTaskOptions,
|
||||||
AuthManager,
|
StartTaskResult,
|
||||||
AuthenticationError,
|
ConflictCheckResult
|
||||||
type AuthCredentials,
|
} from './modules/tasks/services/task-execution-service.js';
|
||||||
type OAuthFlowOptions,
|
|
||||||
type AuthConfig
|
|
||||||
} from './auth/index.js';
|
|
||||||
|
|
||||||
// Re-export logger
|
export type {
|
||||||
export { getLogger, createLogger, setGlobalLogger } from './logger/index.js';
|
PreflightResult,
|
||||||
|
CheckResult
|
||||||
|
} from './modules/tasks/services/preflight-checker.service.js';
|
||||||
|
|
||||||
// Re-export executors
|
// Task domain result types
|
||||||
export * from './executors/index.js';
|
export type TaskWithSubtaskResult = Awaited<ReturnType<TasksDomain['get']>>;
|
||||||
|
|
||||||
// Re-export reports
|
// Auth types
|
||||||
export {
|
export type {
|
||||||
ComplexityReportManager,
|
AuthCredentials,
|
||||||
type ComplexityReport,
|
OAuthFlowOptions,
|
||||||
type ComplexityReportMetadata,
|
UserContext
|
||||||
type ComplexityAnalysis,
|
} from './modules/auth/types.js';
|
||||||
type TaskComplexityData
|
export { AuthenticationError } from './modules/auth/types.js';
|
||||||
} from './reports/index.js';
|
|
||||||
|
|
||||||
// Re-export services
|
// Workflow types
|
||||||
export {
|
export type {
|
||||||
PreflightChecker,
|
StartWorkflowOptions,
|
||||||
TaskLoaderService,
|
WorkflowStatus,
|
||||||
type CheckResult,
|
NextAction
|
||||||
type PreflightResult,
|
} from './modules/workflow/services/workflow.service.js';
|
||||||
type TaskValidationResult,
|
|
||||||
type ValidationErrorType,
|
|
||||||
type DependencyIssue
|
|
||||||
} from './services/index.js';
|
|
||||||
|
|
||||||
// Re-export Git adapter
|
|
||||||
export { GitAdapter } from './git/git-adapter.js';
|
|
||||||
export {
|
|
||||||
CommitMessageGenerator,
|
|
||||||
type CommitMessageOptions
|
|
||||||
} from './git/commit-message-generator.js';
|
|
||||||
|
|
||||||
// Re-export workflow orchestrator, state manager, activity logger, and types
|
|
||||||
export { WorkflowOrchestrator } from './workflow/workflow-orchestrator.js';
|
|
||||||
export { WorkflowStateManager } from './workflow/workflow-state-manager.js';
|
|
||||||
export { WorkflowActivityLogger } from './workflow/workflow-activity-logger.js';
|
|
||||||
export type {
|
export type {
|
||||||
WorkflowPhase,
|
WorkflowPhase,
|
||||||
TDDPhase,
|
TDDPhase,
|
||||||
WorkflowContext,
|
WorkflowContext,
|
||||||
WorkflowState,
|
WorkflowState,
|
||||||
WorkflowEvent,
|
TestResult
|
||||||
WorkflowEventData,
|
} from './modules/workflow/types.js';
|
||||||
WorkflowEventListener,
|
|
||||||
SubtaskInfo,
|
|
||||||
TestResult,
|
|
||||||
WorkflowError
|
|
||||||
} from './workflow/types.js';
|
|
||||||
|
|
||||||
// Re-export workflow service
|
// Git types
|
||||||
export { WorkflowService } from './services/workflow.service.js';
|
export type { CommitMessageOptions } from './modules/git/services/commit-message-generator.js';
|
||||||
|
|
||||||
|
// Integration types
|
||||||
export type {
|
export type {
|
||||||
StartWorkflowOptions,
|
ExportTasksOptions,
|
||||||
WorkflowStatus,
|
ExportResult
|
||||||
NextAction
|
} from './modules/integration/services/export.service.js';
|
||||||
} from './services/workflow.service.js';
|
|
||||||
|
// Reports types
|
||||||
|
export type {
|
||||||
|
ComplexityReport,
|
||||||
|
ComplexityReportMetadata,
|
||||||
|
ComplexityAnalysis,
|
||||||
|
TaskComplexityData
|
||||||
|
} from './modules/reports/types.js';
|
||||||
|
|
||||||
|
// ========== Advanced API (for CLI/Extension/MCP) ==========
|
||||||
|
|
||||||
|
// Auth - Advanced
|
||||||
|
export { AuthManager } from './modules/auth/managers/auth-manager.js';
|
||||||
|
|
||||||
|
// Workflow - Advanced
|
||||||
|
export { WorkflowOrchestrator } from './modules/workflow/orchestrators/workflow-orchestrator.js';
|
||||||
|
export { WorkflowStateManager } from './modules/workflow/managers/workflow-state-manager.js';
|
||||||
|
export { WorkflowService } from './modules/workflow/services/workflow.service.js';
|
||||||
|
export type { SubtaskInfo } from './modules/workflow/types.js';
|
||||||
|
|
||||||
|
// Git - Advanced
|
||||||
|
export { GitAdapter } from './modules/git/adapters/git-adapter.js';
|
||||||
|
export { CommitMessageGenerator } from './modules/git/services/commit-message-generator.js';
|
||||||
|
|
||||||
|
// Tasks - Advanced
|
||||||
|
export { PreflightChecker } from './modules/tasks/services/preflight-checker.service.js';
|
||||||
|
export { TaskLoaderService } from './modules/tasks/services/task-loader.service.js';
|
||||||
|
|
||||||
|
// Integration - Advanced
|
||||||
|
export { ExportService } from './modules/integration/services/export.service.js';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Export all from AI module
|
// Export all from AI module
|
||||||
export * from './ai/index.js';
|
export * from './providers/index.js';
|
||||||
|
|
||||||
// Storage providers will be exported here when implemented
|
// Storage providers will be exported here when implemented
|
||||||
// export * from './storage/index.js';
|
// export * from './storage/index.js';
|
||||||
@@ -6,12 +6,15 @@
|
|||||||
import {
|
import {
|
||||||
ERROR_CODES,
|
ERROR_CODES,
|
||||||
TaskMasterError
|
TaskMasterError
|
||||||
} from '../../errors/task-master-error.js';
|
} from '../../../common/errors/task-master-error.js';
|
||||||
import type {
|
import type {
|
||||||
AIOptions,
|
AIOptions,
|
||||||
AIResponse,
|
AIResponse,
|
||||||
IAIProvider
|
IAIProvider,
|
||||||
} from '../../interfaces/ai-provider.interface.js';
|
ProviderUsageStats,
|
||||||
|
ProviderInfo,
|
||||||
|
AIModel
|
||||||
|
} from '../interfaces/ai-provider.interface.js';
|
||||||
|
|
||||||
// Constants for retry logic
|
// Constants for retry logic
|
||||||
const DEFAULT_MAX_RETRIES = 3;
|
const DEFAULT_MAX_RETRIES = 3;
|
||||||
@@ -428,17 +431,10 @@ export abstract class BaseProvider implements IAIProvider {
|
|||||||
options?: AIOptions
|
options?: AIOptions
|
||||||
): AsyncIterator<Partial<AIResponse>>;
|
): AsyncIterator<Partial<AIResponse>>;
|
||||||
abstract isAvailable(): Promise<boolean>;
|
abstract isAvailable(): Promise<boolean>;
|
||||||
abstract getProviderInfo(): import(
|
abstract getProviderInfo(): ProviderInfo;
|
||||||
'../../interfaces/ai-provider.interface.js'
|
abstract getAvailableModels(): AIModel[];
|
||||||
).ProviderInfo;
|
|
||||||
abstract getAvailableModels(): import(
|
|
||||||
'../../interfaces/ai-provider.interface.js'
|
|
||||||
).AIModel[];
|
|
||||||
abstract validateCredentials(): Promise<boolean>;
|
abstract validateCredentials(): Promise<boolean>;
|
||||||
abstract getUsageStats(): Promise<
|
abstract getUsageStats(): Promise<ProviderUsageStats | null>;
|
||||||
| import('../../interfaces/ai-provider.interface.js').ProviderUsageStats
|
|
||||||
| null
|
|
||||||
>;
|
|
||||||
abstract initialize(): Promise<void>;
|
abstract initialize(): Promise<void>;
|
||||||
abstract close(): Promise<void>;
|
abstract close(): Promise<void>;
|
||||||
}
|
}
|
||||||
208
packages/tm-core/src/modules/auth/auth-domain.ts
Normal file
208
packages/tm-core/src/modules/auth/auth-domain.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Auth Domain Facade
|
||||||
|
* Public API for authentication and authorization
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'node:path';
|
||||||
|
import { AuthManager } from './managers/auth-manager.js';
|
||||||
|
import type {
|
||||||
|
AuthCredentials,
|
||||||
|
OAuthFlowOptions,
|
||||||
|
UserContext
|
||||||
|
} from './types.js';
|
||||||
|
import type {
|
||||||
|
Organization,
|
||||||
|
Brief,
|
||||||
|
RemoteTask
|
||||||
|
} from './services/organization.service.js';
|
||||||
|
import type { StorageType } from '../../common/types/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display information for storage context
|
||||||
|
*/
|
||||||
|
export interface StorageDisplayInfo {
|
||||||
|
storageType: Exclude<StorageType, 'auto'>;
|
||||||
|
briefInfo?: {
|
||||||
|
briefId: string;
|
||||||
|
briefName: string;
|
||||||
|
orgSlug?: string;
|
||||||
|
webAppUrl?: string;
|
||||||
|
};
|
||||||
|
filePath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth Domain - Unified API for authentication operations
|
||||||
|
*/
|
||||||
|
export class AuthDomain {
|
||||||
|
private authManager: AuthManager;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.authManager = AuthManager.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Authentication ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is authenticated
|
||||||
|
*/
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return this.authManager.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stored credentials
|
||||||
|
*/
|
||||||
|
getCredentials(): AuthCredentials | null {
|
||||||
|
return this.authManager.getCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate with OAuth flow
|
||||||
|
*/
|
||||||
|
async authenticateWithOAuth(
|
||||||
|
options?: OAuthFlowOptions
|
||||||
|
): Promise<AuthCredentials> {
|
||||||
|
return this.authManager.authenticateWithOAuth(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get OAuth authorization URL
|
||||||
|
*/
|
||||||
|
getAuthorizationUrl(): string | null {
|
||||||
|
return this.authManager.getAuthorizationUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh authentication token
|
||||||
|
*/
|
||||||
|
async refreshToken(): Promise<AuthCredentials> {
|
||||||
|
return this.authManager.refreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout current user
|
||||||
|
*/
|
||||||
|
async logout(): Promise<void> {
|
||||||
|
return this.authManager.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== User Context Management ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user context (org/brief selection)
|
||||||
|
*/
|
||||||
|
getContext(): UserContext | null {
|
||||||
|
return this.authManager.getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user context
|
||||||
|
*/
|
||||||
|
updateContext(context: Partial<UserContext>): void {
|
||||||
|
return this.authManager.updateContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear user context
|
||||||
|
*/
|
||||||
|
clearContext(): void {
|
||||||
|
return this.authManager.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Organization Management ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all organizations for the authenticated user
|
||||||
|
*/
|
||||||
|
async getOrganizations(): Promise<Organization[]> {
|
||||||
|
return this.authManager.getOrganizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific organization by ID
|
||||||
|
*/
|
||||||
|
async getOrganization(orgId: string): Promise<Organization | null> {
|
||||||
|
return this.authManager.getOrganization(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all briefs for a specific organization
|
||||||
|
*/
|
||||||
|
async getBriefs(orgId: string): Promise<Brief[]> {
|
||||||
|
return this.authManager.getBriefs(orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific brief by ID
|
||||||
|
*/
|
||||||
|
async getBrief(briefId: string): Promise<Brief | null> {
|
||||||
|
return this.authManager.getBrief(briefId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tasks for a specific brief
|
||||||
|
*/
|
||||||
|
async getTasks(briefId: string): Promise<RemoteTask[]> {
|
||||||
|
return this.authManager.getTasks(briefId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Display Information ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get storage display information for UI presentation
|
||||||
|
* Includes brief info for API storage, file path for file storage
|
||||||
|
*
|
||||||
|
* @param resolvedStorageType - The actual storage type being used at runtime.
|
||||||
|
* Get this from tmCore.tasks.getStorageType()
|
||||||
|
*/
|
||||||
|
getStorageDisplayInfo(
|
||||||
|
resolvedStorageType: 'file' | 'api'
|
||||||
|
): StorageDisplayInfo {
|
||||||
|
if (resolvedStorageType === 'api') {
|
||||||
|
const context = this.getContext();
|
||||||
|
if (context?.briefId && context?.briefName) {
|
||||||
|
return {
|
||||||
|
storageType: 'api',
|
||||||
|
briefInfo: {
|
||||||
|
briefId: context.briefId,
|
||||||
|
briefName: context.briefName,
|
||||||
|
orgSlug: context.orgSlug,
|
||||||
|
webAppUrl: this.getWebAppUrl()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to file storage display
|
||||||
|
return {
|
||||||
|
storageType: 'file',
|
||||||
|
filePath: path.join('.taskmaster', 'tasks', 'tasks.json')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get web app base URL from environment configuration
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private getWebAppUrl(): string | undefined {
|
||||||
|
const baseDomain =
|
||||||
|
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
|
||||||
|
|
||||||
|
if (!baseDomain) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it already includes protocol, use as-is
|
||||||
|
if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) {
|
||||||
|
return baseDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, add protocol based on domain
|
||||||
|
if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) {
|
||||||
|
return `http://${baseDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://${baseDomain}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
packages/tm-core/src/modules/auth/index.ts
Normal file
29
packages/tm-core/src/modules/auth/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Authentication module exports
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { AuthDomain, type StorageDisplayInfo } from './auth-domain.js';
|
||||||
|
export { AuthManager } from './managers/auth-manager.js';
|
||||||
|
export { CredentialStore } from './services/credential-store.js';
|
||||||
|
export { OAuthService } from './services/oauth-service.js';
|
||||||
|
export { SupabaseSessionStorage } from './services/supabase-session-storage.js';
|
||||||
|
export type {
|
||||||
|
Organization,
|
||||||
|
Brief,
|
||||||
|
RemoteTask
|
||||||
|
} from './services/organization.service.js';
|
||||||
|
|
||||||
|
export type {
|
||||||
|
AuthCredentials,
|
||||||
|
OAuthFlowOptions,
|
||||||
|
AuthConfig,
|
||||||
|
CliData,
|
||||||
|
UserContext
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
export { AuthenticationError } from './types.js';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DEFAULT_AUTH_CONFIG,
|
||||||
|
getAuthConfig
|
||||||
|
} from './config.js';
|
||||||
@@ -8,17 +8,17 @@ import {
|
|||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
AuthConfig,
|
AuthConfig,
|
||||||
UserContext
|
UserContext
|
||||||
} from './types.js';
|
} from '../types.js';
|
||||||
import { CredentialStore } from './credential-store.js';
|
import { CredentialStore } from '../services/credential-store.js';
|
||||||
import { OAuthService } from './oauth-service.js';
|
import { OAuthService } from '../services/oauth-service.js';
|
||||||
import { SupabaseAuthClient } from '../clients/supabase-client.js';
|
import { SupabaseAuthClient } from '../../integration/clients/supabase-client.js';
|
||||||
import {
|
import {
|
||||||
OrganizationService,
|
OrganizationService,
|
||||||
type Organization,
|
type Organization,
|
||||||
type Brief,
|
type Brief,
|
||||||
type RemoteTask
|
type RemoteTask
|
||||||
} from '../services/organization.service.js';
|
} from '../services/organization.service.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authentication manager class
|
* Authentication manager class
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
import { CredentialStore } from './credential-store.js';
|
import { CredentialStore } from '../services/credential-store.js';
|
||||||
import { AuthenticationError } from './types.js';
|
import { AuthenticationError } from '../types.js';
|
||||||
import type { AuthCredentials } from './types.js';
|
import type { AuthCredentials } from '../types.js';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { AuthCredentials, AuthenticationError, AuthConfig } from './types.js';
|
import { AuthCredentials, AuthenticationError, AuthConfig } from '../types.js';
|
||||||
import { getAuthConfig } from './config.js';
|
import { getAuthConfig } from '../config.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CredentialStore manages the persistence and retrieval of authentication credentials.
|
* CredentialStore manages the persistence and retrieval of authentication credentials.
|
||||||
@@ -12,12 +12,12 @@ import {
|
|||||||
OAuthFlowOptions,
|
OAuthFlowOptions,
|
||||||
AuthConfig,
|
AuthConfig,
|
||||||
CliData
|
CliData
|
||||||
} from './types.js';
|
} from '../types.js';
|
||||||
import { CredentialStore } from './credential-store.js';
|
import { CredentialStore } from '../services/credential-store.js';
|
||||||
import { SupabaseAuthClient } from '../clients/supabase-client.js';
|
import { SupabaseAuthClient } from '../../integration/clients/supabase-client.js';
|
||||||
import { getAuthConfig } from './config.js';
|
import { getAuthConfig } from '../config.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
import packageJson from '../../../../package.json' with { type: 'json' };
|
import packageJson from '../../../../../../package.json' with { type: 'json' };
|
||||||
|
|
||||||
export class OAuthService {
|
export class OAuthService {
|
||||||
private logger = getLogger('OAuthService');
|
private logger = getLogger('OAuthService');
|
||||||
@@ -4,9 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { SupabaseClient } from '@supabase/supabase-js';
|
||||||
import { Database } from '../types/database.types.js';
|
import { Database } from '../../../common/types/database.types.js';
|
||||||
import { TaskMasterError, ERROR_CODES } from '../errors/task-master-error.js';
|
import {
|
||||||
import { getLogger } from '../logger/index.js';
|
TaskMasterError,
|
||||||
|
ERROR_CODES
|
||||||
|
} from '../../../common/errors/task-master-error.js';
|
||||||
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization data structure
|
* Organization data structure
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
* auth.json credential storage, maintaining backward compatibility
|
* auth.json credential storage, maintaining backward compatibility
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SupportedStorage } from '@supabase/supabase-js';
|
import type { SupportedStorage } from '@supabase/supabase-js';
|
||||||
import { CredentialStore } from './credential-store.js';
|
import { CredentialStore } from './credential-store.js';
|
||||||
import { AuthCredentials } from './types.js';
|
import type { AuthCredentials } from '../types.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
const STORAGE_KEY = 'sb-taskmaster-auth-token';
|
const STORAGE_KEY = 'sb-taskmaster-auth-token';
|
||||||
|
|
||||||
@@ -29,20 +29,14 @@ export class SupabaseSessionStorage implements SupportedStorage {
|
|||||||
const session = {
|
const session = {
|
||||||
access_token: credentials.token,
|
access_token: credentials.token,
|
||||||
refresh_token: credentials.refreshToken || '',
|
refresh_token: credentials.refreshToken || '',
|
||||||
expires_at: credentials.expiresAt
|
// Don't default to arbitrary values - let Supabase handle refresh
|
||||||
? Math.floor(new Date(credentials.expiresAt).getTime() / 1000)
|
...(credentials.expiresAt && {
|
||||||
: Math.floor(Date.now() / 1000) + 3600, // Default to 1 hour
|
expires_at: Math.floor(new Date(credentials.expiresAt).getTime() / 1000)
|
||||||
|
}),
|
||||||
token_type: 'bearer',
|
token_type: 'bearer',
|
||||||
user: {
|
user: {
|
||||||
id: credentials.userId,
|
id: credentials.userId,
|
||||||
email: credentials.email || '',
|
email: credentials.email || ''
|
||||||
aud: 'authenticated',
|
|
||||||
role: 'authenticated',
|
|
||||||
email_confirmed_at: new Date().toISOString(),
|
|
||||||
app_metadata: {},
|
|
||||||
user_metadata: {},
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return session;
|
return session;
|
||||||
@@ -55,11 +49,14 @@ export class SupabaseSessionStorage implements SupportedStorage {
|
|||||||
sessionData: any
|
sessionData: any
|
||||||
): Partial<AuthCredentials> {
|
): Partial<AuthCredentials> {
|
||||||
try {
|
try {
|
||||||
const session = JSON.parse(sessionData);
|
// Handle both string and object formats (Supabase may pass either)
|
||||||
|
const session =
|
||||||
|
typeof sessionData === 'string' ? JSON.parse(sessionData) : sessionData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: session.access_token,
|
token: session.access_token,
|
||||||
refreshToken: session.refresh_token,
|
refreshToken: session.refresh_token,
|
||||||
userId: session.user?.id || 'unknown',
|
userId: session.user?.id,
|
||||||
email: session.user?.email,
|
email: session.user?.email,
|
||||||
expiresAt: session.expires_at
|
expiresAt: session.expires_at
|
||||||
? new Date(session.expires_at * 1000).toISOString()
|
? new Date(session.expires_at * 1000).toISOString()
|
||||||
@@ -78,21 +75,29 @@ export class SupabaseSessionStorage implements SupportedStorage {
|
|||||||
// Supabase uses a specific key pattern for sessions
|
// Supabase uses a specific key pattern for sessions
|
||||||
if (key === STORAGE_KEY || key.includes('auth-token')) {
|
if (key === STORAGE_KEY || key.includes('auth-token')) {
|
||||||
try {
|
try {
|
||||||
const credentials = this.store.getCredentials({ allowExpired: true });
|
// Get credentials and let Supabase handle expiry/refresh internally
|
||||||
if (credentials && credentials.token) {
|
const credentials = this.store.getCredentials();
|
||||||
// Build and return a session object from our stored credentials
|
|
||||||
const session = this.buildSessionFromCredentials(credentials);
|
// Only return a session if we have BOTH access token AND refresh token
|
||||||
return JSON.stringify(session);
|
// Supabase will handle refresh if session is expired
|
||||||
|
if (!credentials?.token || !credentials?.refreshToken) {
|
||||||
|
this.logger.debug('No valid credentials found');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const session = this.buildSessionFromCredentials(credentials);
|
||||||
|
return JSON.stringify(session);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error getting session:', error);
|
this.logger.error('Error getting session:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Return null if no valid session exists - Supabase expects this
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set item in storage - Supabase will store the session with a specific key
|
* Set item in storage - Supabase will store the session with a specific key
|
||||||
|
* CRITICAL: This is called during refresh token rotation - must be atomic
|
||||||
*/
|
*/
|
||||||
setItem(key: string, value: string): void {
|
setItem(key: string, value: string): void {
|
||||||
// Only handle Supabase session keys
|
// Only handle Supabase session keys
|
||||||
@@ -102,21 +107,64 @@ export class SupabaseSessionStorage implements SupportedStorage {
|
|||||||
|
|
||||||
// Parse the session and update our credentials
|
// Parse the session and update our credentials
|
||||||
const sessionUpdates = this.parseSessionToCredentials(value);
|
const sessionUpdates = this.parseSessionToCredentials(value);
|
||||||
const existingCredentials = this.store.getCredentials();
|
const existingCredentials = this.store.getCredentials({
|
||||||
|
allowExpired: true
|
||||||
|
});
|
||||||
|
|
||||||
if (sessionUpdates.token) {
|
// CRITICAL: Only save if we have both tokens - prevents partial session states
|
||||||
const updatedCredentials: AuthCredentials = {
|
// Refresh token rotation means we MUST persist the new refresh token immediately
|
||||||
...existingCredentials,
|
if (!sessionUpdates.token || !sessionUpdates.refreshToken) {
|
||||||
...sessionUpdates,
|
this.logger.warn(
|
||||||
savedAt: new Date().toISOString(),
|
'Received incomplete session update - skipping save to prevent token rotation issues',
|
||||||
selectedContext: existingCredentials?.selectedContext
|
{
|
||||||
} as AuthCredentials;
|
hasToken: !!sessionUpdates.token,
|
||||||
|
hasRefreshToken: !!sessionUpdates.refreshToken
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.store.saveCredentials(updatedCredentials);
|
// Log the refresh token rotation for debugging
|
||||||
this.logger.info(
|
const isRotation =
|
||||||
'Successfully saved refreshed credentials from Supabase'
|
existingCredentials?.refreshToken !== sessionUpdates.refreshToken;
|
||||||
|
if (isRotation) {
|
||||||
|
this.logger.debug(
|
||||||
|
'Refresh token rotated - storing new refresh token atomically'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build updated credentials - ATOMIC update of both tokens
|
||||||
|
const userId = sessionUpdates.userId ?? existingCredentials?.userId;
|
||||||
|
|
||||||
|
// Runtime assertion: userId is required for AuthCredentials
|
||||||
|
if (!userId) {
|
||||||
|
this.logger.error(
|
||||||
|
'Cannot save credentials: userId is missing from both session update and existing credentials'
|
||||||
|
);
|
||||||
|
throw new Error('Invalid session state: userId is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCredentials: AuthCredentials = {
|
||||||
|
...(existingCredentials ?? {}),
|
||||||
|
token: sessionUpdates.token,
|
||||||
|
refreshToken: sessionUpdates.refreshToken,
|
||||||
|
expiresAt: sessionUpdates.expiresAt,
|
||||||
|
userId,
|
||||||
|
email: sessionUpdates.email ?? existingCredentials?.email,
|
||||||
|
savedAt: new Date().toISOString(),
|
||||||
|
selectedContext: existingCredentials?.selectedContext
|
||||||
|
} as AuthCredentials;
|
||||||
|
|
||||||
|
// Save synchronously to ensure atomicity during refresh
|
||||||
|
this.store.saveCredentials(updatedCredentials);
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
'Successfully saved refreshed credentials from Supabase',
|
||||||
|
{
|
||||||
|
tokenRotated: isRotation,
|
||||||
|
expiresAt: updatedCredentials.expiresAt
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error setting session:', error);
|
this.logger.error('Error setting session:', error);
|
||||||
}
|
}
|
||||||
8
packages/tm-core/src/modules/commands/index.ts
Normal file
8
packages/tm-core/src/modules/commands/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Commands domain - Placeholder for future migration
|
||||||
|
* This module will handle command execution and orchestration
|
||||||
|
* when migrated from scripts/modules/commands.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Migrate commands.js from scripts/modules/
|
||||||
|
// export * from './handlers/command-handler.js';
|
||||||
116
packages/tm-core/src/modules/config/config-domain.ts
Normal file
116
packages/tm-core/src/modules/config/config-domain.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Config Domain Facade
|
||||||
|
* Public API for configuration management
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ConfigManager } from './managers/config-manager.js';
|
||||||
|
import type {
|
||||||
|
PartialConfiguration,
|
||||||
|
RuntimeStorageConfig
|
||||||
|
} from '../../common/interfaces/configuration.interface.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config Domain - Unified API for configuration operations
|
||||||
|
*/
|
||||||
|
export class ConfigDomain {
|
||||||
|
constructor(private configManager: ConfigManager) {}
|
||||||
|
|
||||||
|
// ========== Configuration Access ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full configuration
|
||||||
|
*/
|
||||||
|
getConfig(): PartialConfiguration {
|
||||||
|
return this.configManager.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get storage configuration
|
||||||
|
*/
|
||||||
|
getStorageConfig(): RuntimeStorageConfig {
|
||||||
|
return this.configManager.getStorageConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get model configuration
|
||||||
|
*/
|
||||||
|
getModelConfig() {
|
||||||
|
return this.configManager.getModelConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get response language
|
||||||
|
*/
|
||||||
|
getResponseLanguage(): string {
|
||||||
|
return this.configManager.getResponseLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get project root path
|
||||||
|
*/
|
||||||
|
getProjectRoot(): string {
|
||||||
|
return this.configManager.getProjectRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if API is explicitly configured
|
||||||
|
*/
|
||||||
|
isApiExplicitlyConfigured(): boolean {
|
||||||
|
return this.configManager.isApiExplicitlyConfigured();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Runtime State ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently active tag
|
||||||
|
*/
|
||||||
|
getActiveTag(): string {
|
||||||
|
return this.configManager.getActiveTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active tag
|
||||||
|
*/
|
||||||
|
async setActiveTag(tag: string): Promise<void> {
|
||||||
|
return this.configManager.setActiveTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Configuration Updates ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update configuration
|
||||||
|
*/
|
||||||
|
async updateConfig(updates: PartialConfiguration): Promise<void> {
|
||||||
|
return this.configManager.updateConfig(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set response language
|
||||||
|
*/
|
||||||
|
async setResponseLanguage(language: string): Promise<void> {
|
||||||
|
return this.configManager.setResponseLanguage(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save current configuration
|
||||||
|
*/
|
||||||
|
async saveConfig(): Promise<void> {
|
||||||
|
return this.configManager.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset configuration to defaults
|
||||||
|
*/
|
||||||
|
async reset(): Promise<void> {
|
||||||
|
return this.configManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Utilities ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration sources for debugging
|
||||||
|
*/
|
||||||
|
getConfigSources() {
|
||||||
|
return this.configManager.getConfigSources();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Export the main ConfigManager
|
// Export the main ConfigManager
|
||||||
export { ConfigManager } from './config-manager.js';
|
export { ConfigManager } from './managers/config-manager.js';
|
||||||
|
|
||||||
// Export all configuration services for advanced usage
|
// Export all configuration services for advanced usage
|
||||||
export {
|
export {
|
||||||
@@ -38,7 +38,7 @@ export type {
|
|||||||
ConfigProperty,
|
ConfigProperty,
|
||||||
IConfigurationFactory,
|
IConfigurationFactory,
|
||||||
IConfigurationManager
|
IConfigurationManager
|
||||||
} from '../interfaces/configuration.interface.js';
|
} from '../../common/interfaces/configuration.interface.js';
|
||||||
|
|
||||||
// Re-export default values
|
// Re-export default values
|
||||||
export { DEFAULT_CONFIG_VALUES } from '../interfaces/configuration.interface.js';
|
export { DEFAULT_CONFIG_VALUES } from '../../common/interfaces/configuration.interface.js';
|
||||||
@@ -5,19 +5,19 @@
|
|||||||
|
|
||||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
import { ConfigManager } from './config-manager.js';
|
import { ConfigManager } from './config-manager.js';
|
||||||
import { DEFAULT_CONFIG_VALUES } from '../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { ConfigLoader } from './services/config-loader.service.js';
|
import { ConfigLoader } from '../services/config-loader.service.js';
|
||||||
import { ConfigMerger } from './services/config-merger.service.js';
|
import { ConfigMerger } from '../services/config-merger.service.js';
|
||||||
import { RuntimeStateManager } from './services/runtime-state-manager.service.js';
|
import { RuntimeStateManager } from '../services/runtime-state-manager.service.js';
|
||||||
import { ConfigPersistence } from './services/config-persistence.service.js';
|
import { ConfigPersistence } from '../services/config-persistence.service.js';
|
||||||
import { EnvironmentConfigProvider } from './services/environment-config-provider.service.js';
|
import { EnvironmentConfigProvider } from '../services/environment-config-provider.service.js';
|
||||||
|
|
||||||
// Mock all services
|
// Mock all services
|
||||||
vi.mock('./services/config-loader.service.js');
|
vi.mock('../services/config-loader.service.js');
|
||||||
vi.mock('./services/config-merger.service.js');
|
vi.mock('../services/config-merger.service.js');
|
||||||
vi.mock('./services/runtime-state-manager.service.js');
|
vi.mock('../services/runtime-state-manager.service.js');
|
||||||
vi.mock('./services/config-persistence.service.js');
|
vi.mock('../services/config-persistence.service.js');
|
||||||
vi.mock('./services/environment-config-provider.service.js');
|
vi.mock('../services/environment-config-provider.service.js');
|
||||||
|
|
||||||
describe('ConfigManager', () => {
|
describe('ConfigManager', () => {
|
||||||
let manager: ConfigManager;
|
let manager: ConfigManager;
|
||||||
@@ -361,23 +361,6 @@ describe('ConfigManager', () => {
|
|||||||
|
|
||||||
expect(sources).toEqual(mockSources);
|
expect(sources).toEqual(mockSources);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return no-op function for watch (not implemented)', () => {
|
|
||||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
const callback = vi.fn();
|
|
||||||
|
|
||||||
const unsubscribe = manager.watch(callback);
|
|
||||||
|
|
||||||
expect(warnSpy).toHaveBeenCalledWith(
|
|
||||||
'Configuration watching not yet implemented'
|
|
||||||
);
|
|
||||||
expect(unsubscribe).toBeInstanceOf(Function);
|
|
||||||
|
|
||||||
// Calling unsubscribe should not throw
|
|
||||||
expect(() => unsubscribe()).not.toThrow();
|
|
||||||
|
|
||||||
warnSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('error handling', () => {
|
describe('error handling', () => {
|
||||||
@@ -9,16 +9,16 @@
|
|||||||
import type {
|
import type {
|
||||||
PartialConfiguration,
|
PartialConfiguration,
|
||||||
RuntimeStorageConfig
|
RuntimeStorageConfig
|
||||||
} from '../interfaces/configuration.interface.js';
|
} from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { DEFAULT_CONFIG_VALUES as DEFAULTS } from '../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES as DEFAULTS } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { ConfigLoader } from './services/config-loader.service.js';
|
import { ConfigLoader } from '../services/config-loader.service.js';
|
||||||
import {
|
import {
|
||||||
ConfigMerger,
|
ConfigMerger,
|
||||||
CONFIG_PRECEDENCE
|
CONFIG_PRECEDENCE
|
||||||
} from './services/config-merger.service.js';
|
} from '../services/config-merger.service.js';
|
||||||
import { RuntimeStateManager } from './services/runtime-state-manager.service.js';
|
import { RuntimeStateManager } from '../services/runtime-state-manager.service.js';
|
||||||
import { ConfigPersistence } from './services/config-persistence.service.js';
|
import { ConfigPersistence } from '../services/config-persistence.service.js';
|
||||||
import { EnvironmentConfigProvider } from './services/environment-config-provider.service.js';
|
import { EnvironmentConfigProvider } from '../services/environment-config-provider.service.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigManager orchestrates all configuration services
|
* ConfigManager orchestrates all configuration services
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import { ConfigLoader } from './config-loader.service.js';
|
import { ConfigLoader } from './config-loader.service.js';
|
||||||
import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
|
||||||
|
|
||||||
vi.mock('node:fs', () => ({
|
vi.mock('node:fs', () => ({
|
||||||
promises: {
|
promises: {
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import type { PartialConfiguration } from '../../interfaces/configuration.interface.js';
|
import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import {
|
import {
|
||||||
ERROR_CODES,
|
ERROR_CODES,
|
||||||
TaskMasterError
|
TaskMasterError
|
||||||
} from '../../errors/task-master-error.js';
|
} from '../../../common/errors/task-master-error.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigLoader handles loading configuration from files
|
* ConfigLoader handles loading configuration from files
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* Responsible for merging configurations from multiple sources with precedence
|
* Responsible for merging configurations from multiple sources with precedence
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PartialConfiguration } from '../../interfaces/configuration.interface.js';
|
import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration source with precedence
|
* Configuration source with precedence
|
||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import type { PartialConfiguration } from '../../interfaces/configuration.interface.js';
|
import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import {
|
import {
|
||||||
ERROR_CODES,
|
ERROR_CODES,
|
||||||
TaskMasterError
|
TaskMasterError
|
||||||
} from '../../errors/task-master-error.js';
|
} from '../../../common/errors/task-master-error.js';
|
||||||
import { getLogger } from '../../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistence options
|
* Persistence options
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
* Extracts configuration from environment variables
|
* Extracts configuration from environment variables
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PartialConfiguration } from '../../interfaces/configuration.interface.js';
|
import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { getLogger } from '../../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment variable mapping definition
|
* Environment variable mapping definition
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import { RuntimeStateManager } from './runtime-state-manager.service.js';
|
import { RuntimeStateManager } from './runtime-state-manager.service.js';
|
||||||
import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
|
||||||
|
|
||||||
vi.mock('node:fs', () => ({
|
vi.mock('node:fs', () => ({
|
||||||
promises: {
|
promises: {
|
||||||
@@ -8,9 +8,9 @@ import path from 'node:path';
|
|||||||
import {
|
import {
|
||||||
ERROR_CODES,
|
ERROR_CODES,
|
||||||
TaskMasterError
|
TaskMasterError
|
||||||
} from '../../errors/task-master-error.js';
|
} from '../../../common/errors/task-master-error.js';
|
||||||
import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js';
|
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
|
||||||
import { getLogger } from '../../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime state data structure
|
* Runtime state data structure
|
||||||
8
packages/tm-core/src/modules/dependencies/index.ts
Normal file
8
packages/tm-core/src/modules/dependencies/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Dependencies domain - Placeholder for future migration
|
||||||
|
* This module will handle dependency management, graphs, and validation
|
||||||
|
* when migrated from scripts/modules/dependency-manager.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Migrate dependency-manager.js from scripts/modules/
|
||||||
|
// export * from './services/dependency-manager.js';
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
* Base executor class providing common functionality for all executors
|
* Base executor class providing common functionality for all executors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Task } from '../types/index.js';
|
import type { Task } from '../../../common/types/index.js';
|
||||||
import type { ITaskExecutor, ExecutorType, ExecutionResult } from './types.js';
|
import type { ITaskExecutor, ExecutorType, ExecutionResult } from '../types.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
export abstract class BaseExecutor implements ITaskExecutor {
|
export abstract class BaseExecutor implements ITaskExecutor {
|
||||||
protected readonly logger = getLogger('BaseExecutor');
|
protected readonly logger = getLogger('BaseExecutor');
|
||||||
@@ -3,13 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import type { Task } from '../types/index.js';
|
import type { Task } from '../../../common/types/index.js';
|
||||||
import type {
|
import type {
|
||||||
ExecutorType,
|
ExecutorType,
|
||||||
ExecutionResult,
|
ExecutionResult,
|
||||||
ClaudeExecutorConfig
|
ClaudeExecutorConfig
|
||||||
} from './types.js';
|
} from '../types.js';
|
||||||
import { BaseExecutor } from './base-executor.js';
|
import { BaseExecutor } from '../executors/base-executor.js';
|
||||||
|
|
||||||
export class ClaudeExecutor extends BaseExecutor {
|
export class ClaudeExecutor extends BaseExecutor {
|
||||||
private claudeConfig: ClaudeExecutorConfig;
|
private claudeConfig: ClaudeExecutorConfig;
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
* Factory for creating task executors
|
* Factory for creating task executors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ITaskExecutor, ExecutorOptions, ExecutorType } from './types.js';
|
import type { ITaskExecutor, ExecutorOptions, ExecutorType } from '../types.js';
|
||||||
import { ClaudeExecutor } from './claude-executor.js';
|
import { ClaudeExecutor } from '../executors/claude-executor.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
export class ExecutorFactory {
|
export class ExecutorFactory {
|
||||||
private static logger = getLogger('ExecutorFactory');
|
private static logger = getLogger('ExecutorFactory');
|
||||||
12
packages/tm-core/src/modules/execution/index.ts
Normal file
12
packages/tm-core/src/modules/execution/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Public API for the executors module
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './types.js';
|
||||||
|
export { BaseExecutor } from './executors/base-executor.js';
|
||||||
|
export { ClaudeExecutor } from './executors/claude-executor.js';
|
||||||
|
export { ExecutorFactory } from './executors/executor-factory.js';
|
||||||
|
export {
|
||||||
|
ExecutorService,
|
||||||
|
type ExecutorServiceOptions
|
||||||
|
} from './services/executor-service.js';
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
* Service for managing task execution
|
* Service for managing task execution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Task } from '../types/index.js';
|
import type { Task } from '../../../common/types/index.js';
|
||||||
import type {
|
import type {
|
||||||
ITaskExecutor,
|
ITaskExecutor,
|
||||||
ExecutorOptions,
|
ExecutorOptions,
|
||||||
ExecutionResult,
|
ExecutionResult,
|
||||||
ExecutorType
|
ExecutorType
|
||||||
} from './types.js';
|
} from '../types.js';
|
||||||
import { ExecutorFactory } from './executor-factory.js';
|
import { ExecutorFactory } from '../executors/executor-factory.js';
|
||||||
import { getLogger } from '../logger/index.js';
|
import { getLogger } from '../../../common/logger/index.js';
|
||||||
|
|
||||||
export interface ExecutorServiceOptions {
|
export interface ExecutorServiceOptions {
|
||||||
projectRoot: string;
|
projectRoot: string;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* Executor types and interfaces for Task Master
|
* Executor types and interfaces for Task Master
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Task } from '../types/index.js';
|
import type { Task } from '../../common/types/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported executor types
|
* Supported executor types
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
* @module git-adapter
|
* @module git-adapter
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { simpleGit, type SimpleGit } from 'simple-git';
|
import { simpleGit, type SimpleGit, type StatusResult } from 'simple-git';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -216,14 +216,14 @@ export class GitAdapter {
|
|||||||
* Gets the detailed status of the working tree.
|
* Gets the detailed status of the working tree.
|
||||||
* Returns raw status from simple-git with all file changes.
|
* Returns raw status from simple-git with all file changes.
|
||||||
*
|
*
|
||||||
* @returns {Promise<import('simple-git').StatusResult>} Detailed status object
|
* @returns {Promise<StatusResult>} Detailed status object
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const status = await git.getStatus();
|
* const status = await git.getStatus();
|
||||||
* console.log('Modified files:', status.modified);
|
* console.log('Modified files:', status.modified);
|
||||||
* console.log('Staged files:', status.staged);
|
* console.log('Staged files:', status.staged);
|
||||||
*/
|
*/
|
||||||
async getStatus(): Promise<import('simple-git').StatusResult> {
|
async getStatus(): Promise<StatusResult> {
|
||||||
return await this.git.status();
|
return await this.git.status();
|
||||||
}
|
}
|
||||||
|
|
||||||
247
packages/tm-core/src/modules/git/git-domain.ts
Normal file
247
packages/tm-core/src/modules/git/git-domain.ts
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Git Domain Facade
|
||||||
|
* Public API for Git operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GitAdapter } from './adapters/git-adapter.js';
|
||||||
|
import { CommitMessageGenerator } from './services/commit-message-generator.js';
|
||||||
|
import type { CommitMessageOptions } from './services/commit-message-generator.js';
|
||||||
|
import type { StatusResult } from 'simple-git';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git Domain - Unified API for Git operations
|
||||||
|
*/
|
||||||
|
export class GitDomain {
|
||||||
|
private gitAdapter: GitAdapter;
|
||||||
|
private commitGenerator: CommitMessageGenerator;
|
||||||
|
|
||||||
|
constructor(projectPath: string) {
|
||||||
|
this.gitAdapter = new GitAdapter(projectPath);
|
||||||
|
this.commitGenerator = new CommitMessageGenerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Repository Validation ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if directory is a git repository
|
||||||
|
*/
|
||||||
|
async isGitRepository(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.isGitRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure we're in a valid git repository
|
||||||
|
*/
|
||||||
|
async ensureGitRepository(): Promise<void> {
|
||||||
|
return this.gitAdapter.ensureGitRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get repository root path
|
||||||
|
*/
|
||||||
|
async getRepositoryRoot(): Promise<string> {
|
||||||
|
return this.gitAdapter.getRepositoryRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Working Tree Status ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if working tree is clean
|
||||||
|
*/
|
||||||
|
async isWorkingTreeClean(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.isWorkingTreeClean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get git status
|
||||||
|
*/
|
||||||
|
async getStatus(): Promise<StatusResult> {
|
||||||
|
return this.gitAdapter.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status summary
|
||||||
|
*/
|
||||||
|
async getStatusSummary(): Promise<{
|
||||||
|
isClean: boolean;
|
||||||
|
staged: number;
|
||||||
|
modified: number;
|
||||||
|
deleted: number;
|
||||||
|
untracked: number;
|
||||||
|
totalChanges: number;
|
||||||
|
}> {
|
||||||
|
return this.gitAdapter.getStatusSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are uncommitted changes
|
||||||
|
*/
|
||||||
|
async hasUncommittedChanges(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.hasUncommittedChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are staged changes
|
||||||
|
*/
|
||||||
|
async hasStagedChanges(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.hasStagedChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Branch Operations ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current branch name
|
||||||
|
*/
|
||||||
|
async getCurrentBranch(): Promise<string> {
|
||||||
|
return this.gitAdapter.getCurrentBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all local branches
|
||||||
|
*/
|
||||||
|
async listBranches(): Promise<string[]> {
|
||||||
|
return this.gitAdapter.listBranches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a branch exists
|
||||||
|
*/
|
||||||
|
async branchExists(branchName: string): Promise<boolean> {
|
||||||
|
return this.gitAdapter.branchExists(branchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new branch
|
||||||
|
*/
|
||||||
|
async createBranch(
|
||||||
|
branchName: string,
|
||||||
|
options?: { checkout?: boolean }
|
||||||
|
): Promise<void> {
|
||||||
|
return this.gitAdapter.createBranch(branchName, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checkout an existing branch
|
||||||
|
*/
|
||||||
|
async checkoutBranch(
|
||||||
|
branchName: string,
|
||||||
|
options?: { force?: boolean }
|
||||||
|
): Promise<void> {
|
||||||
|
return this.gitAdapter.checkoutBranch(branchName, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and checkout a new branch
|
||||||
|
*/
|
||||||
|
async createAndCheckoutBranch(branchName: string): Promise<void> {
|
||||||
|
return this.gitAdapter.createAndCheckoutBranch(branchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a branch
|
||||||
|
*/
|
||||||
|
async deleteBranch(
|
||||||
|
branchName: string,
|
||||||
|
options?: { force?: boolean }
|
||||||
|
): Promise<void> {
|
||||||
|
return this.gitAdapter.deleteBranch(branchName, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default branch name
|
||||||
|
*/
|
||||||
|
async getDefaultBranch(): Promise<string> {
|
||||||
|
return this.gitAdapter.getDefaultBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if on default branch
|
||||||
|
*/
|
||||||
|
async isOnDefaultBranch(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.isOnDefaultBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Commit Operations ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage files for commit
|
||||||
|
*/
|
||||||
|
async stageFiles(files: string[]): Promise<void> {
|
||||||
|
return this.gitAdapter.stageFiles(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unstage files
|
||||||
|
*/
|
||||||
|
async unstageFiles(files: string[]): Promise<void> {
|
||||||
|
return this.gitAdapter.unstageFiles(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a commit
|
||||||
|
*/
|
||||||
|
async createCommit(
|
||||||
|
message: string,
|
||||||
|
options?: {
|
||||||
|
metadata?: Record<string, string>;
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
enforceNonDefaultBranch?: boolean;
|
||||||
|
force?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<void> {
|
||||||
|
return this.gitAdapter.createCommit(message, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get commit log
|
||||||
|
*/
|
||||||
|
async getCommitLog(options?: { maxCount?: number }): Promise<any[]> {
|
||||||
|
return this.gitAdapter.getCommitLog(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last commit
|
||||||
|
*/
|
||||||
|
async getLastCommit(): Promise<any> {
|
||||||
|
return this.gitAdapter.getLastCommit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Remote Operations ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if repository has remotes
|
||||||
|
*/
|
||||||
|
async hasRemote(): Promise<boolean> {
|
||||||
|
return this.gitAdapter.hasRemote();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all configured remotes
|
||||||
|
*/
|
||||||
|
async getRemotes(): Promise<any[]> {
|
||||||
|
return this.gitAdapter.getRemotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Commit Message Generation ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a conventional commit message
|
||||||
|
*/
|
||||||
|
generateCommitMessage(options: CommitMessageOptions): string {
|
||||||
|
return this.commitGenerator.generateMessage(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a conventional commit message
|
||||||
|
*/
|
||||||
|
validateCommitMessage(message: string) {
|
||||||
|
return this.commitGenerator.validateConventionalCommit(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a commit message
|
||||||
|
*/
|
||||||
|
parseCommitMessage(message: string) {
|
||||||
|
return this.commitGenerator.parseCommitMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Export GitAdapter
|
// Export GitAdapter
|
||||||
export { GitAdapter } from './git-adapter.js';
|
export { GitAdapter } from './adapters/git-adapter.js';
|
||||||
|
|
||||||
// Export branch name utilities
|
// Export branch name utilities
|
||||||
export {
|
export {
|
||||||
generateBranchName,
|
generateBranchName,
|
||||||
sanitizeBranchName
|
sanitizeBranchName
|
||||||
} from './branch-name-generator.js';
|
} from './services/branch-name-generator.js';
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user