Skip to content

Commit 4f38910

Browse files
CoderrobCopilot
andauthored
Cleanup refactoring (#2)
* Refactor rendering and validation result handling - Updated the Renderer class to improve section extraction and added detailed JSDoc comments for better documentation. - Refactored ValidationResultRenderer to utilize an output writer interface for consistent message formatting and improved readability. - Enhanced DefaultTaskStore with additional JSDoc comments for clarity on method functionalities. - Improved FileManager with detailed JSDoc comments for file operations. - Updated GitHub types and issues provider to use a unified type helper for type checks. - Refactored ProjectsProvider to streamline task creation and filtering logic. - Enhanced TaskManager with new methods for changelog management and added JSDoc comments for better understanding. - Improved TaskProvider with detailed filtering logic for eligible tasks and added JSDoc comments. - Added unit tests for ObservabilityLogger to ensure logging functionalities work as expected. - Updated UID resolution and audit services with detailed JSDoc comments for clarity on method purposes. - Introduced IOutputWriter interface for consistent CLI output handling and added OutputFormat enum for structured output types. * refactor: reorganize imports and update type definitions across multiple files * fix: import isObject from type.helper for improved type checking * fix: update task summary reference to use task title for changelog entry * Update src/core/storage/task.manager.ts Co-authored-by: Copilot <[email protected]> * Update src/commands/task-management/complete-task.command.ts Co-authored-by: Copilot <[email protected]> * Update src/core/rendering/console-output.writer.ts Co-authored-by: Copilot <[email protected]> * feat: enhance task fixing functionality with TaskFixResult interface and refactor applyBasicFixes method * feat: update writeFormatted method to use OutputFormat enum for better type safety * feat: add validate and fix command for task management - Introduced `ValidateAndFixCommand` for validating tasks and applying automatic fixes. - Implemented `ValidateTasksCommand` for validating all tasks against the schema. - Enhanced date, owner, priority, and status fixers to return updated task objects. - Added unit tests for array and date helper functions. - Refactored environment variable access with `EnvironmentAccessor` interface. - Improved task validation services and context handling. - Updated task-related types to include priority and validation options. - Cleaned up imports and ensured consistent code structure across modules. * Refactor command names to use CommandName enum - Updated ValidateAndFixCommand and ValidateTasksCommand to use CommandName enum for command names. - Removed unnecessary code and improved consistency in command naming. Enhance date.helper tests - Removed redundant test case for number string in date.helper tests. Add env.helper tests - Introduced tests for ProcessEnvironmentAccessor to validate environment variable retrieval and defaults. Add object.helper tests - Implemented tests for safeGet and safeGetRequired functions to ensure safe property access. Add type.helper tests - Created tests for type checking functions including isString, isObject, isEmptyString, isNonEmptyString, isNullOrUndefined, and isTask. Add uid-resolver tests - Added comprehensive tests for Resolver class to validate UID resolution and alias handling. Refactor uid-resolver implementation - Simplified the Resolver class by using a single instance of FileManager. - Improved catalog loading logic for better readability and maintainability. Add json.parser tests - Implemented tests for JSON parsing and writing functions to ensure correct behavior and error handling. Add yaml.parser tests - Created tests for YAML parsing and manipulation functions to validate functionality and error handling. Refactor yaml.parser implementation - Improved the updateYamlBlockById function to enhance readability and maintainability. Fix task-validation.service to remove unnecessary logger parameter - Updated TaskValidationService to simplify fixer creation by removing the logger parameter. Update command types - Introduced new interfaces for command options and task details. - Removed obsolete rendering and task management command types. Refactor validation.factory to simplify fixer creation - Updated createFixer method to remove logger parameter, simplifying the interface. * fix: update chalk mock to use Object.assign for better compatibility --------- Co-authored-by: Copilot <[email protected]>
1 parent 7faae79 commit 4f38910

File tree

112 files changed

+3391
-1103
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+3391
-1103
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ dist
33
.docusaurus
44
.coverage
55
test/validator.test.js
6+
__mocks__
67

CHANGELOG.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ All notable changes to this repository will be documented in this file.
55
Format and process
66

77
- The `Unreleased` section contains completed tasks that have been removed from `TODO.md` and are targeted for the next release.
8-
- integration.test.task.003 — undefined — Test cleanup
9-
- integration.test.task.002 — undefined — Integration test completion
108
- Each entry should include: task id, summary, author, PR or commit link, and a one-line description of the change.
119
- When creating a release, move the entries from `Unreleased` to a new versioned section (e.g. `## [1.0.0] - 2025-09-20`) and include release notes.
1210

__mocks__/chalk.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const chalk = Object.assign((text) => text, {
2+
green: (text) => text,
3+
red: (text) => text,
4+
yellow: (text) => text,
5+
blue: (text) => text,
6+
cyan: (text) => text,
7+
magenta: (text) => text,
8+
white: (text) => text,
9+
black: (text) => text,
10+
gray: (text) => text,
11+
grey: (text) => text,
12+
redBright: (text) => text,
13+
greenBright: (text) => text,
14+
yellowBright: (text) => text,
15+
blueBright: (text) => text,
16+
magentaBright: (text) => text,
17+
cyanBright: (text) => text,
18+
whiteBright: (text) => text,
19+
bgRed: (text) => text,
20+
bgGreen: (text) => text,
21+
bgYellow: (text) => text,
22+
bgBlue: (text) => text,
23+
bgMagenta: (text) => text,
24+
bgCyan: (text) => text,
25+
bgWhite: (text) => text,
26+
bgBlack: (text) => text,
27+
bgRedBright: (text) => text,
28+
bgGreenBright: (text) => text,
29+
bgYellowBright: (text) => text,
30+
bgBlueBright: (text) => text,
31+
bgMagentaBright: (text) => text,
32+
bgCyanBright: (text) => text,
33+
bgWhiteBright: (text) => text,
34+
bold: (text) => text,
35+
dim: (text) => text,
36+
italic: (text) => text,
37+
underline: (text) => text,
38+
inverse: (text) => text,
39+
strikethrough: (text) => text,
40+
reset: (text) => text,
41+
});
42+
43+
module.exports = chalk;

jest.config.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,9 @@ module.exports = {
3535
clearMocks: true,
3636
restoreMocks: true,
3737
resetMocks: true,
38+
transformIgnorePatterns: ['node_modules/(?!chalk)'],
39+
moduleNameMapper: {
40+
'^chalk$': '<rootDir>/__mocks__/chalk.js',
41+
},
42+
forceExit: true,
3843
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Code Quality Standards
2+
3+
## Architectural Rules
4+
5+
### Index File Encapsulation
6+
7+
**NEVER** allow index files to export from outside their directory tree.
8+
9+
**❌ WRONG - Breaks encapsulation:**
10+
11+
```typescript
12+
// src/types/rendering/index.ts
13+
export * from './IRenderer';
14+
export * from './OutputFormat';
15+
export { ConsoleOutputWriter } from '../../core/rendering/console-output.writer'; // ❌ BAD
16+
```
17+
18+
**✅ CORRECT - Maintains encapsulation:**
19+
20+
```typescript
21+
// src/types/rendering/index.ts
22+
export * from './IRenderer';
23+
export * from './OutputFormat';
24+
25+
// Add exports to the appropriate domain index file instead:
26+
// src/core/rendering/index.ts
27+
export { ConsoleOutputWriter } from './console-output.writer';
28+
```
29+
30+
**Rationale:**
31+
32+
- Maintains clear module boundaries and encapsulation
33+
- Prevents circular dependencies
34+
- Makes dependencies explicit and traceable
35+
- Follows domain-driven design principles
36+
- Improves maintainability and refactoring safety
37+
38+
**Enforcement:**
39+
40+
- Code reviews must flag any index file reaching outside its directory
41+
- Automated linting rules should be added to prevent this pattern
42+
- Refactoring should move cross-domain exports to appropriate domain boundaries

src/cli.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import { Command } from 'commander';
33
import pino from 'pino';
44

5+
import { ConsoleOutputWriter } from './core/rendering/console-output.writer';
56
import { getLogger } from './core/system/logger';
67
import { ObservabilityLogger } from './core/system/observability.logger';
7-
import { CommandFactory } from './commands/shared/command.factory';
8-
import { EXIT_CODES } from './constants/exit-codes';
8+
import { EXIT_CODES } from './types/core';
9+
import { CommandFactory } from './commands/command.factory';
910

1011
/**
1112
* Main CLI entry point for the Documentation-Driven Development toolkit.
@@ -36,8 +37,11 @@ const observabilityLogger = new ObservabilityLogger(pinoLogger);
3637
const program = new Command();
3738
program.name('dddctl').description('Documentation-Driven Development CLI').version('1.0.0');
3839

40+
// Create output writer for CLI messaging
41+
const outputWriter = new ConsoleOutputWriter();
42+
3943
// Configure all commands through the factory
40-
CommandFactory.configureProgram(program, baseLogger);
44+
CommandFactory.configureProgram(program, baseLogger, outputWriter);
4145

4246
// Helper function to get safe command name
4347
function getCommandName(): string {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { Command } from 'commander';
2+
3+
import { TaskManager } from '../core/storage';
4+
import { AddTaskArgs, CommandName, EXIT_CODES, ILogger, IOutputWriter } from '../types';
5+
6+
import { AddTaskCommand } from './add-task.command';
7+
8+
// Mock dependencies
9+
jest.mock('../core/storage');
10+
jest.mock('commander');
11+
12+
describe('AddTaskCommand', () => {
13+
let logger: jest.Mocked<ILogger>;
14+
let outputWriter: jest.Mocked<IOutputWriter>;
15+
let command: AddTaskCommand;
16+
let mockTaskManager: jest.Mocked<TaskManager>;
17+
18+
beforeEach(() => {
19+
logger = {
20+
info: jest.fn(),
21+
error: jest.fn(),
22+
warn: jest.fn(),
23+
debug: jest.fn(),
24+
child: jest.fn(),
25+
} as jest.Mocked<ILogger>;
26+
27+
outputWriter = {
28+
info: jest.fn(),
29+
error: jest.fn(),
30+
success: jest.fn(),
31+
warning: jest.fn(),
32+
write: jest.fn(),
33+
newline: jest.fn(),
34+
writeFormatted: jest.fn(),
35+
section: jest.fn(),
36+
keyValue: jest.fn(),
37+
} as jest.Mocked<IOutputWriter>;
38+
39+
mockTaskManager = {
40+
addTaskFromFile: jest.fn(),
41+
} as unknown as jest.Mocked<TaskManager>;
42+
43+
// Mock the TaskManager constructor
44+
(TaskManager as jest.MockedClass<typeof TaskManager>).mockImplementation(() => mockTaskManager);
45+
46+
command = new AddTaskCommand(logger, outputWriter);
47+
jest.clearAllMocks();
48+
});
49+
50+
describe('execute', () => {
51+
const args: AddTaskArgs = { file: 'test-task.md' };
52+
53+
it('should handle successful task addition', async () => {
54+
mockTaskManager.addTaskFromFile.mockReturnValue(true);
55+
56+
await command.execute(args);
57+
58+
expect(mockTaskManager.addTaskFromFile).toHaveBeenCalledWith(args.file);
59+
expect(outputWriter.success).toHaveBeenCalledWith(`Task added to TODO.md from ${args.file}`);
60+
expect(logger.info).toHaveBeenCalledWith('Task added successfully', { file: args.file });
61+
expect(process.exitCode).toBeUndefined();
62+
});
63+
64+
it('should handle failed task addition', async () => {
65+
mockTaskManager.addTaskFromFile.mockReturnValue(false);
66+
67+
await command.execute(args);
68+
69+
expect(mockTaskManager.addTaskFromFile).toHaveBeenCalledWith(args.file);
70+
expect(logger.error).toHaveBeenCalledWith(`Failed to add task from ${args.file}`, {
71+
file: args.file,
72+
});
73+
expect(process.exitCode).toBe(EXIT_CODES.GENERAL_ERROR);
74+
});
75+
76+
it('should handle errors during task addition', async () => {
77+
const error = new Error('File not found');
78+
const mockImplementation = () => {
79+
throw error;
80+
};
81+
mockTaskManager.addTaskFromFile.mockImplementation(mockImplementation);
82+
83+
await command.execute(args);
84+
85+
expect(mockTaskManager.addTaskFromFile).toHaveBeenCalledWith(args.file);
86+
expect(logger.error).toHaveBeenCalledWith(`Error adding task: ${error.message}`, {
87+
error: error.message,
88+
file: args.file,
89+
});
90+
expect(process.exitCode).toBe(EXIT_CODES.NOT_FOUND);
91+
});
92+
93+
it('should handle unknown errors during task addition', async () => {
94+
const error = 'Unknown error';
95+
const mockImplementation = () => {
96+
throw error;
97+
};
98+
mockTaskManager.addTaskFromFile.mockImplementation(mockImplementation);
99+
100+
await command.execute(args);
101+
102+
expect(mockTaskManager.addTaskFromFile).toHaveBeenCalledWith(args.file);
103+
expect(logger.error).toHaveBeenCalledWith(`Error adding task: ${error}`, {
104+
error: error,
105+
file: args.file,
106+
});
107+
expect(process.exitCode).toBe(EXIT_CODES.NOT_FOUND);
108+
});
109+
});
110+
111+
describe('configure', () => {
112+
it('should configure the command with Commander.js', () => {
113+
const parent = {
114+
command: jest.fn().mockReturnValue({
115+
argument: jest.fn().mockReturnThis(),
116+
description: jest.fn().mockReturnThis(),
117+
action: jest.fn().mockReturnThis(),
118+
}),
119+
} as unknown as Command;
120+
121+
AddTaskCommand.configure(parent, logger, outputWriter);
122+
123+
expect(parent.command).toHaveBeenCalledWith(CommandName.ADD);
124+
// Additional assertions can be added if needed for argument and action setup
125+
});
126+
});
127+
});

src/commands/task-management/add-task.command.ts renamed to src/commands/add-task.command.ts

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import chalk from 'chalk';
21
import { Command } from 'commander';
32

4-
import { ILogger } from '../../types/observability';
5-
import { TaskManager } from '../../core/storage/task.manager';
6-
import { AddTaskArgs } from '../../types/tasks';
7-
import { EXIT_CODES } from '../../constants/exit-codes';
8-
import { BaseCommand } from '../shared/base.command';
9-
import { CommandName } from '../../types';
3+
import { ConsoleOutputWriter } from '../core/rendering';
4+
import { TaskManager } from '../core/storage';
5+
import { AddTaskArgs, CommandName, EXIT_CODES, ILogger, IOutputWriter } from '../types';
6+
7+
import { BaseCommand } from './base.command';
108

119
/**
1210
* Command for adding a new task from a file to the TODO.md.
@@ -26,6 +24,13 @@ export class AddTaskCommand extends BaseCommand {
2624
readonly name = CommandName.ADD;
2725
readonly description = 'Add a new task from a file';
2826

27+
constructor(
28+
logger: ILogger,
29+
protected override readonly outputWriter: IOutputWriter = new ConsoleOutputWriter(),
30+
) {
31+
super(logger, outputWriter);
32+
}
33+
2934
/**
3035
* Executes the add task command.
3136
*
@@ -45,19 +50,47 @@ export class AddTaskCommand extends BaseCommand {
4550
try {
4651
const manager = new TaskManager(this.logger);
4752
const added = manager.addTaskFromFile(args.file);
53+
4854
if (added) {
49-
console.log(chalk.green(`Task added to TODO.md from ${args.file}`));
50-
this.logger.info('Task added successfully', { file: args.file });
51-
} else {
52-
this.logger.error(`Failed to add task from ${args.file}`, { file: args.file });
53-
process.exitCode = EXIT_CODES.GENERAL_ERROR;
55+
this.handleSuccessfulAddition(args.file);
56+
return Promise.resolve();
5457
}
58+
59+
this.handleFailedAddition(args.file);
60+
return Promise.resolve();
5561
} catch (error: unknown) {
56-
const message = error instanceof Error ? error.message : String(error);
57-
this.logger.error(`Error adding task: ${message}`, { error: message, file: args.file });
58-
process.exitCode = EXIT_CODES.NOT_FOUND;
62+
this.handleAdditionError(error, args.file);
63+
return Promise.resolve();
5964
}
60-
return Promise.resolve();
65+
}
66+
67+
/**
68+
* Handles successful task addition.
69+
* @param filePath - The path of the file being processed
70+
*/
71+
private handleSuccessfulAddition(filePath: string): void {
72+
this.outputWriter.success(`Task added to TODO.md from ${filePath}`);
73+
this.logger.info('Task added successfully', { file: filePath });
74+
}
75+
76+
/**
77+
* Handles failed task addition.
78+
* @param filePath - The path of the file being processed
79+
*/
80+
private handleFailedAddition(filePath: string): void {
81+
this.logger.error(`Failed to add task from ${filePath}`, { file: filePath });
82+
process.exitCode = EXIT_CODES.GENERAL_ERROR;
83+
}
84+
85+
/**
86+
* Handles errors during task addition.
87+
* @param error - The error that occurred
88+
* @param filePath - The path of the file being processed
89+
*/
90+
private handleAdditionError(error: unknown, filePath: string): void {
91+
const message = error instanceof Error ? error.message : String(error);
92+
this.logger.error(`Error adding task: ${message}`, { error: message, file: filePath });
93+
process.exitCode = EXIT_CODES.NOT_FOUND;
6194
}
6295

6396
/**
@@ -75,13 +108,13 @@ export class AddTaskCommand extends BaseCommand {
75108
* AddTaskCommand.configure(program);
76109
* ```
77110
*/
78-
static configure(parent: Command, logger: ILogger): void {
111+
static configure(parent: Command, logger: ILogger, outputWriter?: IOutputWriter): void {
79112
parent
80113
.command(CommandName.ADD)
81114
.argument('<file>', 'File containing the task to add')
82115
.description('Add a new task from a file')
83116
.action(async (file: string) => {
84-
const cmd = new AddTaskCommand(logger);
117+
const cmd = new AddTaskCommand(logger, outputWriter);
85118
await cmd.execute({ file });
86119
});
87120
}

0 commit comments

Comments
 (0)