Skip to content

Commit 2f53a3c

Browse files
Fixed entrypoints in package.json
1 parent 8f452a1 commit 2f53a3c

File tree

6 files changed

+191
-59
lines changed

6 files changed

+191
-59
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Tasks have a status field that can be one of:
103103
#### Status Transition Rules
104104

105105
The system enforces the following rules for task status transitions:
106+
106107
- Tasks follow a specific workflow with defined valid transitions:
107108
- From `not started`: Can only move to `in progress`
108109
- From `in progress`: Can move to either `done` or back to `not started`
@@ -133,7 +134,7 @@ A typical workflow for an LLM using this task manager would be:
133134
Task approval is controlled exclusively by the human user through the CLI command:
134135

135136
```bash
136-
npm run approve-task -- <projectId> <taskId>
137+
npx task-manager-cli approve-task -- <projectId> <taskId>
137138
```
138139

139140
Options:
@@ -146,16 +147,17 @@ Note: Tasks must be marked as "done" with completed details before they can be a
146147
The CLI provides a command to list all projects and tasks:
147148

148149
```bash
149-
npm run list-tasks
150+
npx task-manager-cli list-tasks
150151
```
151152

152153
To view details of a specific project:
153154

154155
```bash
155-
npm run list-tasks -- -p <projectId>
156+
npx task-manager-cli list-tasks -- -p <projectId>
156157
```
157158

158159
This command displays information about all projects in the system or a specific project, including:
160+
159161
- Project ID and initial prompt
160162
- Completion status
161163
- Task details (title, description, status, approval)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"type": "module",
88
"bin": {
99
"taskqueue-mcp": "dist/index.js",
10-
"task-manager-cli": "dist/src/cli.js"
10+
"task-manager-cli": "dist/src/client/index.js"
1111
},
1212
"files": [
1313
"dist/index.js",

src/client/cli.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#!/usr/bin/env node
2-
31
import { Command } from "commander";
42
import chalk from "chalk";
53
import {
@@ -12,7 +10,6 @@ import { TaskManager } from "../server/TaskManager.js";
1210
import { createError, normalizeError } from "../utils/errors.js";
1311
import { formatCliError } from "./errors.js";
1412
import fs from "fs/promises";
15-
import type { StandardResponse } from "../types/index.js";
1613

1714
const program = new Command();
1815

@@ -575,4 +572,5 @@ function collect(value: string, previous: string[]) {
575572
return previous.concat([value]);
576573
}
577574

578-
program.parse(process.argv);
575+
// Export program for testing purposes
576+
export { program };

src/client/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env node
2+
3+
import { program } from './cli.js';
4+
5+
// Parse the command line arguments
6+
program.parse(process.argv);

tests/integration/cli.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as os from "node:os";
55
import { promisify } from "util";
66

77
const execAsync = promisify(exec);
8-
const CLI_PATH = path.resolve(process.cwd(), "src/client/cli.ts");
8+
const CLI_PATH = path.resolve(process.cwd(), "dist/src/client/index.js");
99

1010
describe("CLI Integration Tests", () => {
1111
let tempDir: string;

tests/unit/cli.test.ts

Lines changed: 176 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,179 @@
1-
import { exec } from "child_process";
2-
import * as fs from "node:fs/promises";
3-
import * as path from "node:path";
4-
import { promisify } from "util";
5-
6-
const execAsync = promisify(exec);
7-
const CLI_PATH = path.resolve(process.cwd(), "src/client/cli.ts");
8-
const TASK_MANAGER_FILE_PATH = path.resolve(process.cwd(), "tests/unit/test-tasks.json");
9-
const TEMP_DIR = path.resolve(process.cwd(), "tests/unit/temp");
10-
11-
describe("CLI Unit Tests", () => {
12-
beforeEach(async () => {
13-
// Create a test file
14-
const testFile = path.join(TEMP_DIR, "test-spec.txt");
15-
await fs.writeFile(testFile, "Test specification content");
1+
import { describe, it, expect, jest, beforeEach, beforeAll } from '@jest/globals';
2+
import type { TaskManager as TaskManagerType } from '../../src/server/TaskManager.js';
3+
import type { StandardResponse, ProjectCreationSuccessData } from '../../src/types/index.js';
4+
import type { readFile as ReadFileType } from 'node:fs/promises';
5+
6+
// --- Mock Dependencies ---
7+
8+
// Mock TaskManager
9+
const mockGenerateProjectPlan = jest.fn() as jest.MockedFunction<typeof TaskManagerType.prototype.generateProjectPlan>;
10+
const mockReadProject = jest.fn() as jest.MockedFunction<typeof TaskManagerType.prototype.readProject>;
11+
const mockListProjects = jest.fn() as jest.MockedFunction<typeof TaskManagerType.prototype.listProjects>;
12+
13+
jest.unstable_mockModule('../../src/server/TaskManager.js', () => ({
14+
TaskManager: jest.fn().mockImplementation(() => ({
15+
generateProjectPlan: mockGenerateProjectPlan,
16+
readProject: mockReadProject, // Include in mock
17+
listProjects: mockListProjects, // Include in mock
18+
// Add mocks for other methods used by other commands if testing them later
19+
approveTaskCompletion: jest.fn(),
20+
approveProjectCompletion: jest.fn(),
21+
listTasks: jest.fn(),
22+
// ... other methods
23+
})),
24+
}));
25+
26+
// Mock fs/promises
27+
const mockReadFile = jest.fn();
28+
jest.unstable_mockModule('node:fs/promises', () => ({
29+
readFile: mockReadFile,
30+
default: { readFile: mockReadFile } // Handle default export if needed
31+
}));
32+
33+
// Mock chalk - disable color codes
34+
jest.unstable_mockModule('chalk', () => ({
35+
default: {
36+
blue: (str: string) => str,
37+
red: (str: string) => str,
38+
green: (str: string) => str,
39+
yellow: (str: string) => str,
40+
cyan: (str: string) => str,
41+
bold: (str: string) => str,
42+
gray: (str: string) => str,
43+
},
44+
}));
45+
46+
// --- Setup & Teardown ---
47+
48+
let program: any; // To hold the imported commander program
49+
let consoleLogSpy: ReturnType<typeof jest.spyOn>; // Use inferred type
50+
let consoleErrorSpy: ReturnType<typeof jest.spyOn>; // Use inferred type
51+
let processExitSpy: ReturnType<typeof jest.spyOn>; // Use inferred type
52+
let TaskManager: typeof TaskManagerType;
53+
let readFile: jest.MockedFunction<typeof ReadFileType>;
54+
55+
beforeAll(async () => {
56+
// Dynamically import the CLI module *after* mocks are set up
57+
const cliModule = await import('../../src/client/cli.js');
58+
program = cliModule.program; // Assuming program is exported
59+
60+
// Import mocked types/modules
61+
const TmModule = await import('../../src/server/TaskManager.js');
62+
TaskManager = TmModule.TaskManager;
63+
const fsPromisesMock = await import('node:fs/promises');
64+
readFile = fsPromisesMock.readFile as jest.MockedFunction<typeof ReadFileType>;
65+
});
66+
67+
beforeEach(() => {
68+
// Reset mocks and spies before each test
69+
jest.clearAllMocks();
70+
mockGenerateProjectPlan.mockReset();
71+
mockReadFile.mockReset();
72+
mockReadProject.mockReset(); // Reset new mock
73+
mockListProjects.mockReset(); // Reset new mock
74+
75+
// Spy on console and process.exit
76+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
77+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
78+
// Prevent tests from exiting and throw instead
79+
processExitSpy = jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined): never => { // Correct signature
80+
throw new Error(`process.exit called with code ${code ?? 'undefined'}`);
1681
});
82+
});
1783

18-
afterEach(async () => {
19-
await fs.rm(TEMP_DIR, { recursive: true, force: true });
84+
afterEach(() => {
85+
// Restore spies
86+
consoleLogSpy.mockRestore();
87+
consoleErrorSpy.mockRestore();
88+
processExitSpy.mockRestore();
89+
});
90+
91+
// --- Test Suites ---
92+
93+
describe('CLI Commands', () => {
94+
describe('generate-plan', () => {
95+
it('should call TaskManager.generateProjectPlan with correct arguments and log success', async () => {
96+
// Arrange: Mock TaskManager response
97+
const mockSuccessResponse: StandardResponse<ProjectCreationSuccessData> = {
98+
status: 'success',
99+
data: {
100+
projectId: 'proj-123',
101+
totalTasks: 2,
102+
tasks: [
103+
{ id: 'task-1', title: 'Task 1', description: 'Desc 1' },
104+
{ id: 'task-2', title: 'Task 2', description: 'Desc 2' },
105+
],
106+
message: 'Project proj-123 created.',
107+
},
108+
};
109+
mockGenerateProjectPlan.mockResolvedValue(mockSuccessResponse);
110+
111+
const testPrompt = 'Create a test plan';
112+
const testProvider = 'openai';
113+
const testModel = 'gpt-4o-mini';
114+
115+
// Act: Simulate running the CLI command
116+
// Arguments: command, options...
117+
await program.parseAsync(
118+
[
119+
'generate-plan',
120+
'--prompt',
121+
testPrompt,
122+
'--provider',
123+
testProvider,
124+
'--model',
125+
testModel,
126+
],
127+
{ from: 'user' } // Important: indicates these are user-provided args
128+
);
129+
130+
// Assert
131+
// 1. TaskManager initialization (implicitly tested by mock setup)
132+
// Ensure TaskManager constructor was called (likely once due to preAction hook)
133+
expect(TaskManager).toHaveBeenCalledTimes(1);
134+
135+
// 2. generateProjectPlan call
136+
expect(mockGenerateProjectPlan).toHaveBeenCalledTimes(1);
137+
expect(mockGenerateProjectPlan).toHaveBeenCalledWith({
138+
prompt: testPrompt,
139+
provider: testProvider,
140+
model: testModel,
141+
attachments: [], // No attachments in this test
142+
});
143+
144+
// 3. Console output
145+
expect(consoleLogSpy).toHaveBeenCalledWith(
146+
expect.stringContaining('Generating project plan from prompt...')
147+
);
148+
expect(consoleLogSpy).toHaveBeenCalledWith(
149+
expect.stringContaining('✅ Project plan generated successfully!')
150+
);
151+
expect(consoleLogSpy).toHaveBeenCalledWith(
152+
expect.stringContaining('Project ID: proj-123')
153+
);
154+
expect(consoleLogSpy).toHaveBeenCalledWith(
155+
expect.stringContaining('Total Tasks: 2')
156+
);
157+
expect(consoleLogSpy).toHaveBeenCalledWith(
158+
expect.stringContaining('task-1:')
159+
);
160+
expect(consoleLogSpy).toHaveBeenCalledWith(
161+
expect.stringContaining('Title: Task 1')
162+
);
163+
expect(consoleLogSpy).toHaveBeenCalledWith(
164+
expect.stringContaining('Description: Desc 1')
165+
);
166+
// Check for the TaskManager message as well
167+
expect(consoleLogSpy).toHaveBeenCalledWith(
168+
expect.stringContaining('Project proj-123 created.')
169+
);
170+
171+
172+
// 4. No errors or exits
173+
expect(consoleErrorSpy).not.toHaveBeenCalled();
174+
expect(processExitSpy).not.toHaveBeenCalled();
175+
});
20176
});
21-
22-
// TODO: Rewrite these as unit tests
23-
it.skip("should generate a project plan with default options", async () => {
24-
const { stdout } = await execAsync(
25-
`TASK_MANAGER_FILE_PATH=${TASK_MANAGER_FILE_PATH} tsx ${CLI_PATH} generate-plan --prompt "Create a simple todo app"`
26-
);
27-
28-
expect(stdout).toContain("Project plan generated successfully!");
29-
expect(stdout).toContain("Project ID:");
30-
expect(stdout).toContain("Total Tasks:");
31-
expect(stdout).toContain("Tasks:");
32-
}, 10000);
33-
34-
it.skip("should generate a plan with custom provider and model", async () => {
35-
const { stdout } = await execAsync(
36-
`TASK_MANAGER_FILE_PATH=${TASK_MANAGER_FILE_PATH} tsx ${CLI_PATH} generate-plan --prompt "Create a todo app" --provider google --model gemini-1.5-pro`
37-
);
38-
39-
expect(stdout).toContain("Project plan generated successfully!");
40-
}, 10000);
41-
42-
it.skip("should handle file attachments", async () => {
43-
// Create a test file
44-
const testFile = path.join(TEMP_DIR, "test-spec.txt");
45-
await fs.writeFile(testFile, "Test specification content");
46-
47-
const { stdout } = await execAsync(
48-
`TASK_MANAGER_FILE_PATH=${TASK_MANAGER_FILE_PATH} tsx ${CLI_PATH} generate-plan --prompt "Create based on spec" --attachment ${testFile}`
49-
);
50-
51-
expect(stdout).toContain("Project plan generated successfully!");
52-
}, 10000);
53-
});
177+
178+
// Add describe blocks for other commands (approve, finalize, list) later
179+
});

0 commit comments

Comments
 (0)