spec-gen-cli 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -55
- package/dist/api/analyze.d.ts.map +1 -1
- package/dist/api/analyze.js +6 -1
- package/dist/api/analyze.js.map +1 -1
- package/dist/api/audit.d.ts +10 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +117 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/generate.d.ts.map +1 -1
- package/dist/api/generate.js +10 -1
- package/dist/api/generate.js.map +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/run.d.ts.map +1 -1
- package/dist/api/run.js +5 -1
- package/dist/api/run.js.map +1 -1
- package/dist/api/types.d.ts +15 -4
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/commands/analyze.d.ts +3 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +112 -17
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +9 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +98 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/drift.d.ts.map +1 -1
- package/dist/cli/commands/drift.js +8 -10
- package/dist/cli/commands/drift.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +15 -37
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +102 -2
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +134 -2
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +9 -47
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +17 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +201 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +7 -8
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/index.js +14 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
- package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
- package/dist/core/analyzer/ai-config-generator.js +85 -0
- package/dist/core/analyzer/ai-config-generator.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +27 -2
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +86 -8
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/codebase-digest.d.ts.map +1 -1
- package/dist/core/analyzer/codebase-digest.js +12 -11
- package/dist/core/analyzer/codebase-digest.js.map +1 -1
- package/dist/core/analyzer/env-extractor.d.ts +33 -0
- package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/env-extractor.js +196 -0
- package/dist/core/analyzer/env-extractor.js.map +1 -0
- package/dist/core/analyzer/http-route-parser.d.ts +36 -1
- package/dist/core/analyzer/http-route-parser.d.ts.map +1 -1
- package/dist/core/analyzer/http-route-parser.js +276 -0
- package/dist/core/analyzer/http-route-parser.js.map +1 -1
- package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
- package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/middleware-extractor.js +195 -0
- package/dist/core/analyzer/middleware-extractor.js.map +1 -0
- package/dist/core/analyzer/schema-extractor.d.ts +41 -0
- package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/schema-extractor.js +229 -0
- package/dist/core/analyzer/schema-extractor.js.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
- package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.js +245 -0
- package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -1
- package/dist/core/generator/openspec-format-generator.js +8 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -1
- package/dist/core/generator/spec-pipeline.d.ts +9 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
- package/dist/core/generator/spec-pipeline.js +94 -2
- package/dist/core/generator/spec-pipeline.js.map +1 -1
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -1
- package/dist/core/generator/stages/stage1-survey.js +43 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -1
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
- package/dist/core/generator/stages/stage2-entities.js +6 -2
- package/dist/core/generator/stages/stage2-entities.js.map +1 -1
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
- package/dist/core/generator/stages/stage3-services.js +9 -2
- package/dist/core/generator/stages/stage3-services.js.map +1 -1
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
- package/dist/core/generator/stages/stage4-api.js +6 -2
- package/dist/core/generator/stages/stage4-api.js.map +1 -1
- package/dist/core/services/llm-service.d.ts +26 -10
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +171 -16
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts +32 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.js +185 -2
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
- package/dist/core/verifier/verification-engine.d.ts +67 -6
- package/dist/core/verifier/verification-engine.d.ts.map +1 -1
- package/dist/core/verifier/verification-engine.js +316 -90
- package/dist/core/verifier/verification-engine.js.map +1 -1
- package/dist/types/index.d.ts +70 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/pipeline.d.ts +9 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/dist/utils/command-helpers.d.ts +30 -0
- package/dist/utils/command-helpers.d.ts.map +1 -1
- package/dist/utils/command-helpers.js +69 -1
- package/dist/utils/command-helpers.js.map +1 -1
- package/examples/bmad/README.md +113 -0
- package/examples/bmad/agents/architect.md +226 -0
- package/examples/bmad/agents/dev-brownfield.md +69 -0
- package/examples/bmad/setup/architect.customize.yaml +14 -0
- package/examples/bmad/tasks/implement-story.md +254 -0
- package/examples/bmad/tasks/onboarding.md +169 -0
- package/examples/bmad/tasks/refactor.md +178 -0
- package/examples/bmad/tasks/sprint-planning.md +168 -0
- package/examples/bmad/templates/story.md +108 -0
- package/examples/cline-workflows/spec-gen-analyze-codebase.md +100 -0
- package/examples/cline-workflows/spec-gen-check-spec-drift.md +102 -0
- package/examples/cline-workflows/spec-gen-execute-refactor.md +194 -0
- package/examples/cline-workflows/spec-gen-implement-feature.md +238 -0
- package/examples/cline-workflows/spec-gen-plan-refactor.md +255 -0
- package/examples/cline-workflows/spec-gen-refactor-codebase.md +16 -0
- package/examples/drift-demo/openspec/config.yaml +14 -0
- package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
- package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
- package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
- package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
- package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
- package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
- package/examples/drift-demo/package.json +21 -0
- package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
- package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
- package/examples/drift-demo/src/auth/auth-service.ts +45 -0
- package/examples/drift-demo/src/database/connection.ts +27 -0
- package/examples/drift-demo/src/index.ts +16 -0
- package/examples/drift-demo/src/projects/project-model.ts +15 -0
- package/examples/drift-demo/src/projects/project-service.ts +34 -0
- package/examples/drift-demo/src/tasks/task-model.ts +37 -0
- package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
- package/examples/drift-demo/src/tasks/task-service.ts +60 -0
- package/examples/drift-demo/src/utils/validation.ts +11 -0
- package/examples/drift-demo/tests/auth.test.ts +4 -0
- package/examples/drift-demo/tests/tasks.test.ts +4 -0
- package/examples/drift-demo/tsconfig.json +10 -0
- package/examples/drift-test/run-drift-test.sh +1087 -0
- package/examples/gsd/README.md +119 -0
- package/examples/gsd/commands/gsd/spec-gen-drift.md +111 -0
- package/examples/gsd/commands/gsd/spec-gen-orient.md +191 -0
- package/examples/mistral-vibe/README.md +101 -0
- package/examples/mistral-vibe/antipatterns-template.md +18 -0
- package/examples/mistral-vibe/skills/spec-gen-analyze-codebase/SKILL.md +123 -0
- package/examples/mistral-vibe/skills/spec-gen-brainstorm/SKILL.md +379 -0
- package/examples/mistral-vibe/skills/spec-gen-debug/SKILL.md +320 -0
- package/examples/mistral-vibe/skills/spec-gen-execute-refactor/SKILL.md +210 -0
- package/examples/mistral-vibe/skills/spec-gen-generate/SKILL.md +245 -0
- package/examples/mistral-vibe/skills/spec-gen-implement-story/SKILL.md +274 -0
- package/examples/mistral-vibe/skills/spec-gen-plan-refactor/SKILL.md +251 -0
- package/examples/openspec-analysis/README.md +59 -0
- package/examples/openspec-analysis/SUMMARY.md +72 -0
- package/examples/openspec-analysis/config.json +16 -0
- package/examples/openspec-analysis/dependencies.mermaid +35 -0
- package/examples/openspec-analysis/dependency-graph.json +12116 -0
- package/examples/openspec-analysis/llm-context.json +119 -0
- package/examples/openspec-analysis/repo-structure.json +871 -0
- package/examples/openspec-cli/README.md +67 -0
- package/examples/openspec-cli/openspec/config.yaml +26 -0
- package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
- package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
- package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
- package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
- package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
- package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
- package/examples/spec-kit/README.md +104 -0
- package/examples/spec-kit/commands/drift.md +87 -0
- package/examples/spec-kit/commands/orient.md +138 -0
- package/examples/spec-kit/extension.yml +54 -0
- package/package.json +3 -6
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import bcrypt from 'bcrypt';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
|
|
4
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret';
|
|
5
|
+
|
|
6
|
+
export interface AuthResult {
|
|
7
|
+
token: string;
|
|
8
|
+
expiresIn: number;
|
|
9
|
+
userId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class AuthService {
|
|
13
|
+
async login(email: string, password: string): Promise<AuthResult> {
|
|
14
|
+
const user = await this.findUserByEmail(email);
|
|
15
|
+
if (!user) throw new Error('User not found');
|
|
16
|
+
|
|
17
|
+
const valid = await bcrypt.compare(password, user.passwordHash);
|
|
18
|
+
if (!valid) throw new Error('Invalid credentials');
|
|
19
|
+
|
|
20
|
+
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET, { expiresIn: '24h' });
|
|
21
|
+
return { token, expiresIn: 86400, userId: user.id };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async register(email: string, password: string, name: string): Promise<AuthResult> {
|
|
25
|
+
const existing = await this.findUserByEmail(email);
|
|
26
|
+
if (existing) throw new Error('Email already registered');
|
|
27
|
+
|
|
28
|
+
const passwordHash = await bcrypt.hash(password, 12);
|
|
29
|
+
const user = await this.createUser({ email, passwordHash, name, role: 'user' });
|
|
30
|
+
|
|
31
|
+
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET, { expiresIn: '24h' });
|
|
32
|
+
return { token, expiresIn: 86400, userId: user.id };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
verifyToken(token: string): { userId: string; role: string } | null {
|
|
36
|
+
try {
|
|
37
|
+
return jwt.verify(token, JWT_SECRET) as { userId: string; role: string };
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async findUserByEmail(email: string) { return null as any; }
|
|
44
|
+
private async createUser(data: any) { return { id: 'new-id', ...data }; }
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
|
|
3
|
+
const pool = new Pool({
|
|
4
|
+
connectionString: process.env.DATABASE_URL || 'postgresql://localhost:5432/taskflow',
|
|
5
|
+
max: 20,
|
|
6
|
+
idleTimeoutMillis: 30000,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export async function query(sql: string, params?: any[]) {
|
|
10
|
+
const client = await pool.connect();
|
|
11
|
+
try {
|
|
12
|
+
return await client.query(sql, params);
|
|
13
|
+
} finally {
|
|
14
|
+
client.release();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function healthCheck(): Promise<boolean> {
|
|
19
|
+
try {
|
|
20
|
+
await query('SELECT 1');
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { pool };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { authRouter } from './auth/auth-routes.js';
|
|
3
|
+
import { taskRouter } from './tasks/task-routes.js';
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
|
|
8
|
+
app.use('/api/auth', authRouter);
|
|
9
|
+
app.use('/api/tasks', taskRouter);
|
|
10
|
+
|
|
11
|
+
app.get('/health', (_, res) => res.json({ status: 'ok' }));
|
|
12
|
+
|
|
13
|
+
const PORT = process.env.PORT || 3000;
|
|
14
|
+
app.listen(PORT, () => console.log(`TaskFlow API listening on port ${PORT}`));
|
|
15
|
+
|
|
16
|
+
export { app };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
ownerId: string;
|
|
6
|
+
members: string[];
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
updatedAt: Date;
|
|
9
|
+
isArchived: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CreateProjectInput {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Project, CreateProjectInput } from './project-model.js';
|
|
2
|
+
|
|
3
|
+
export class ProjectService {
|
|
4
|
+
async createProject(input: CreateProjectInput, userId: string): Promise<Project> {
|
|
5
|
+
if (!input.name?.trim()) throw new Error('Project name is required');
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
id: `proj_${Date.now()}`,
|
|
9
|
+
name: input.name.trim(),
|
|
10
|
+
description: input.description || '',
|
|
11
|
+
ownerId: userId,
|
|
12
|
+
members: [userId],
|
|
13
|
+
createdAt: new Date(),
|
|
14
|
+
updatedAt: new Date(),
|
|
15
|
+
isArchived: false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getProject(projectId: string): Promise<Project | null> {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async addMember(projectId: string, userId: string): Promise<void> {
|
|
24
|
+
const project = await this.getProject(projectId);
|
|
25
|
+
if (!project) throw new Error('Project not found');
|
|
26
|
+
if (project.members.includes(userId)) throw new Error('User is already a member');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async archiveProject(projectId: string, userId: string): Promise<void> {
|
|
30
|
+
const project = await this.getProject(projectId);
|
|
31
|
+
if (!project) throw new Error('Project not found');
|
|
32
|
+
if (project.ownerId !== userId) throw new Error('Only the owner can archive a project');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled';
|
|
2
|
+
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
|
|
3
|
+
|
|
4
|
+
export interface Task {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
status: TaskStatus;
|
|
9
|
+
priority: TaskPriority;
|
|
10
|
+
assigneeId: string | null;
|
|
11
|
+
projectId: string;
|
|
12
|
+
createdBy: string;
|
|
13
|
+
createdAt: Date;
|
|
14
|
+
updatedAt: Date;
|
|
15
|
+
dueDate: Date | null;
|
|
16
|
+
tags: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CreateTaskInput {
|
|
20
|
+
title: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
priority?: TaskPriority;
|
|
23
|
+
assigneeId?: string;
|
|
24
|
+
projectId: string;
|
|
25
|
+
dueDate?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UpdateTaskInput {
|
|
30
|
+
title?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
status?: TaskStatus;
|
|
33
|
+
priority?: TaskPriority;
|
|
34
|
+
assigneeId?: string | null;
|
|
35
|
+
dueDate?: string | null;
|
|
36
|
+
tags?: string[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { TaskService } from './task-service.js';
|
|
3
|
+
import { requireAuth } from '../auth/auth-middleware.js';
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
const taskService = new TaskService();
|
|
7
|
+
|
|
8
|
+
router.use(requireAuth);
|
|
9
|
+
|
|
10
|
+
router.post('/', async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const task = await taskService.createTask(req.body, (req as any).userId);
|
|
13
|
+
res.status(201).json(task);
|
|
14
|
+
} catch (err: any) {
|
|
15
|
+
res.status(400).json({ error: err.message });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
router.get('/:id', async (req, res) => {
|
|
20
|
+
const task = await taskService.getTask(req.params.id);
|
|
21
|
+
if (!task) return res.status(404).json({ error: 'Task not found' });
|
|
22
|
+
res.json(task);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.patch('/:id', async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const task = await taskService.updateTask(req.params.id, req.body);
|
|
28
|
+
res.json(task);
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
res.status(400).json({ error: err.message });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
router.delete('/:id', async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
await taskService.deleteTask(req.params.id);
|
|
37
|
+
res.status(204).send();
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
res.status(404).json({ error: err.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
router.get('/', async (req, res) => {
|
|
44
|
+
const { projectId, status, assigneeId } = req.query;
|
|
45
|
+
if (!projectId) return res.status(400).json({ error: 'projectId is required' });
|
|
46
|
+
const tasks = await taskService.listTasks(projectId as string, {
|
|
47
|
+
status: status as any,
|
|
48
|
+
assigneeId: assigneeId as string,
|
|
49
|
+
});
|
|
50
|
+
res.json(tasks);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export { router as taskRouter };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Task, CreateTaskInput, UpdateTaskInput, TaskStatus } from './task-model.js';
|
|
2
|
+
|
|
3
|
+
export class TaskService {
|
|
4
|
+
async createTask(input: CreateTaskInput, userId: string): Promise<Task> {
|
|
5
|
+
if (!input.title?.trim()) throw new Error('Task title is required');
|
|
6
|
+
if (!input.projectId) throw new Error('Project ID is required');
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
id: `task_${Date.now()}`,
|
|
10
|
+
title: input.title.trim(),
|
|
11
|
+
description: input.description || '',
|
|
12
|
+
status: 'todo',
|
|
13
|
+
priority: input.priority || 'medium',
|
|
14
|
+
assigneeId: input.assigneeId || null,
|
|
15
|
+
projectId: input.projectId,
|
|
16
|
+
createdBy: userId,
|
|
17
|
+
createdAt: new Date(),
|
|
18
|
+
updatedAt: new Date(),
|
|
19
|
+
dueDate: input.dueDate ? new Date(input.dueDate) : null,
|
|
20
|
+
tags: input.tags || [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getTask(taskId: string): Promise<Task | null> {
|
|
25
|
+
return null; // DB lookup
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async updateTask(taskId: string, input: UpdateTaskInput): Promise<Task> {
|
|
29
|
+
const task = await this.getTask(taskId);
|
|
30
|
+
if (!task) throw new Error('Task not found');
|
|
31
|
+
|
|
32
|
+
// Validate status transitions
|
|
33
|
+
if (input.status) {
|
|
34
|
+
this.validateStatusTransition(task.status, input.status);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { ...task, ...input, updatedAt: new Date() } as Task;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async deleteTask(taskId: string): Promise<void> {
|
|
41
|
+
const task = await this.getTask(taskId);
|
|
42
|
+
if (!task) throw new Error('Task not found');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async listTasks(projectId: string, filters?: { status?: TaskStatus; assigneeId?: string }): Promise<Task[]> {
|
|
46
|
+
return []; // DB query with filters
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private validateStatusTransition(current: TaskStatus, next: TaskStatus): void {
|
|
50
|
+
const allowed: Record<TaskStatus, TaskStatus[]> = {
|
|
51
|
+
'todo': ['in_progress', 'cancelled'],
|
|
52
|
+
'in_progress': ['done', 'todo', 'cancelled'],
|
|
53
|
+
'done': ['todo'],
|
|
54
|
+
'cancelled': ['todo'],
|
|
55
|
+
};
|
|
56
|
+
if (!allowed[current]?.includes(next)) {
|
|
57
|
+
throw new Error(`Cannot transition from ${current} to ${next}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function isValidEmail(email: string): boolean {
|
|
2
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function isValidUUID(id: string): boolean {
|
|
6
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function sanitizeString(input: string): string {
|
|
10
|
+
return input.trim().replace(/[<>&'"]/g, '');
|
|
11
|
+
}
|