prjct-cli 0.6.0 → 0.7.1
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/CHANGELOG.md +67 -6
- package/CLAUDE.md +442 -36
- package/README.md +47 -54
- package/bin/prjct +174 -240
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +109 -65
- package/core/commands.js +2213 -2173
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +5 -3
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +4 -12
- package/templates/agents/AGENTS.md +101 -27
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +9 -2
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +5 -2
- package/templates/commands/cleanup.md +5 -2
- package/templates/commands/design.md +5 -2
- package/templates/commands/done.md +4 -2
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +41 -10
- package/templates/commands/git.md +7 -2
- package/templates/commands/help.md +2 -2
- package/templates/commands/idea.md +14 -5
- package/templates/commands/init.md +62 -7
- package/templates/commands/next.md +4 -2
- package/templates/commands/now.md +4 -2
- package/templates/commands/progress.md +27 -5
- package/templates/commands/recap.md +39 -10
- package/templates/commands/roadmap.md +19 -5
- package/templates/commands/ship.md +118 -16
- package/templates/commands/status.md +4 -2
- package/templates/commands/sync.md +19 -15
- package/templates/commands/task.md +4 -2
- package/templates/commands/test.md +5 -2
- package/templates/commands/workflow.md +4 -2
- package/core/agent-generator.js +0 -525
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/ascii-graphics.js +0 -433
- package/core/git-integration.js +0 -401
- package/core/task-schema.js +0 -342
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -27
- package/templates/agents/coordinator.template.md +0 -34
- package/templates/agents/data.template.md +0 -27
- package/templates/agents/devops.template.md +0 -27
- package/templates/agents/fe.template.md +0 -27
- package/templates/agents/mobile.template.md +0 -27
- package/templates/agents/qa.template.md +0 -27
- package/templates/agents/scribe.template.md +0 -29
- package/templates/agents/security.template.md +0 -27
- package/templates/agents/ux.template.md +0 -27
- package/templates/commands/context.md +0 -36
- package/templates/commands/stuck.md +0 -36
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
package/core/task-schema.js
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task Metadata Schema and Agent Types
|
|
3
|
-
*
|
|
4
|
-
* Defines the structure for task tracking with agent assignment,
|
|
5
|
-
* time estimation, and complexity scoring.
|
|
6
|
-
*
|
|
7
|
-
* @version 0.6.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Agent Types - Technical specialists for task assignment
|
|
12
|
-
*/
|
|
13
|
-
const AGENT_TYPES = {
|
|
14
|
-
'backend-architect': {
|
|
15
|
-
name: 'Backend Architect',
|
|
16
|
-
specialization: 'Server-side architecture, APIs, databases',
|
|
17
|
-
keywords: ['api', 'backend', 'server', 'database', 'endpoint', 'migration', 'schema'],
|
|
18
|
-
icon: '🏗️',
|
|
19
|
-
estimatedEfficiency: 1.0, // baseline
|
|
20
|
-
},
|
|
21
|
-
'frontend-developer': {
|
|
22
|
-
name: 'Frontend Developer',
|
|
23
|
-
specialization: 'UI/UX, components, responsive design',
|
|
24
|
-
keywords: ['ui', 'frontend', 'component', 'design', 'layout', 'responsive', 'css', 'style'],
|
|
25
|
-
icon: '🎨',
|
|
26
|
-
estimatedEfficiency: 1.0,
|
|
27
|
-
},
|
|
28
|
-
'fullstack-engineer': {
|
|
29
|
-
name: 'Fullstack Engineer',
|
|
30
|
-
specialization: 'End-to-end feature development',
|
|
31
|
-
keywords: ['fullstack', 'feature', 'integration', 'end-to-end', 'complete'],
|
|
32
|
-
icon: '⚡',
|
|
33
|
-
estimatedEfficiency: 0.9, // slightly slower due to context switching
|
|
34
|
-
},
|
|
35
|
-
'devops-specialist': {
|
|
36
|
-
name: 'DevOps Specialist',
|
|
37
|
-
specialization: 'CI/CD, deployment, infrastructure',
|
|
38
|
-
keywords: ['deploy', 'ci/cd', 'docker', 'kubernetes', 'infrastructure', 'pipeline', 'build'],
|
|
39
|
-
icon: '🚀',
|
|
40
|
-
estimatedEfficiency: 1.1, // faster at automation
|
|
41
|
-
},
|
|
42
|
-
'security-engineer': {
|
|
43
|
-
name: 'Security Engineer',
|
|
44
|
-
specialization: 'Authentication, authorization, security',
|
|
45
|
-
keywords: [
|
|
46
|
-
'auth',
|
|
47
|
-
'security',
|
|
48
|
-
'authentication',
|
|
49
|
-
'authorization',
|
|
50
|
-
'encryption',
|
|
51
|
-
'jwt',
|
|
52
|
-
'oauth',
|
|
53
|
-
],
|
|
54
|
-
icon: '🔒',
|
|
55
|
-
estimatedEfficiency: 0.8, // slower due to thorough security review
|
|
56
|
-
},
|
|
57
|
-
'data-engineer': {
|
|
58
|
-
name: 'Data Engineer',
|
|
59
|
-
specialization: 'Data processing, analytics, ETL',
|
|
60
|
-
keywords: ['data', 'analytics', 'etl', 'pipeline', 'processing', 'warehouse', 'query'],
|
|
61
|
-
icon: '📊',
|
|
62
|
-
estimatedEfficiency: 0.9,
|
|
63
|
-
},
|
|
64
|
-
'qa-engineer': {
|
|
65
|
-
name: 'QA Engineer',
|
|
66
|
-
specialization: 'Testing, quality assurance, automation',
|
|
67
|
-
keywords: ['test', 'testing', 'qa', 'quality', 'automation', 'e2e', 'integration'],
|
|
68
|
-
icon: '🧪',
|
|
69
|
-
estimatedEfficiency: 1.0,
|
|
70
|
-
},
|
|
71
|
-
'performance-engineer': {
|
|
72
|
-
name: 'Performance Engineer',
|
|
73
|
-
specialization: 'Optimization, scaling, performance',
|
|
74
|
-
keywords: ['performance', 'optimize', 'scaling', 'cache', 'speed', 'bottleneck'],
|
|
75
|
-
icon: '⚡',
|
|
76
|
-
estimatedEfficiency: 0.85, // slower due to profiling and measurement
|
|
77
|
-
},
|
|
78
|
-
'general-developer': {
|
|
79
|
-
name: 'General Developer',
|
|
80
|
-
specialization: 'General-purpose development',
|
|
81
|
-
keywords: ['fix', 'update', 'improve', 'refactor', 'cleanup'],
|
|
82
|
-
icon: '👨💻',
|
|
83
|
-
estimatedEfficiency: 1.0,
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Complexity Levels
|
|
89
|
-
*/
|
|
90
|
-
const COMPLEXITY = {
|
|
91
|
-
trivial: {
|
|
92
|
-
level: 1,
|
|
93
|
-
name: 'Trivial',
|
|
94
|
-
description: 'Simple changes, typos, configuration',
|
|
95
|
-
estimatedHours: 0.5,
|
|
96
|
-
multiplier: 0.5,
|
|
97
|
-
examples: ['Fix typo', 'Update config value', 'Change color'],
|
|
98
|
-
},
|
|
99
|
-
simple: {
|
|
100
|
-
level: 2,
|
|
101
|
-
name: 'Simple',
|
|
102
|
-
description: 'Straightforward implementation, single file',
|
|
103
|
-
estimatedHours: 2,
|
|
104
|
-
multiplier: 1.0,
|
|
105
|
-
examples: ['Add validation', 'Create simple component', 'Update documentation'],
|
|
106
|
-
},
|
|
107
|
-
moderate: {
|
|
108
|
-
level: 3,
|
|
109
|
-
name: 'Moderate',
|
|
110
|
-
description: 'Multiple files, some complexity',
|
|
111
|
-
estimatedHours: 4,
|
|
112
|
-
multiplier: 2.0,
|
|
113
|
-
examples: ['Implement new feature', 'Refactor module', 'Add API endpoint'],
|
|
114
|
-
},
|
|
115
|
-
complex: {
|
|
116
|
-
level: 4,
|
|
117
|
-
name: 'Complex',
|
|
118
|
-
description: 'System-wide changes, architecture',
|
|
119
|
-
estimatedHours: 8,
|
|
120
|
-
multiplier: 4.0,
|
|
121
|
-
examples: ['Authentication system', 'Database migration', 'Performance optimization'],
|
|
122
|
-
},
|
|
123
|
-
epic: {
|
|
124
|
-
level: 5,
|
|
125
|
-
name: 'Epic',
|
|
126
|
-
description: 'Major feature, multiple systems',
|
|
127
|
-
estimatedHours: 16,
|
|
128
|
-
multiplier: 8.0,
|
|
129
|
-
examples: ['Payment integration', 'Real-time messaging', 'Admin dashboard'],
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Task Status
|
|
135
|
-
*/
|
|
136
|
-
const TASK_STATUS = {
|
|
137
|
-
pending: 'Waiting to start',
|
|
138
|
-
active: 'Currently working on',
|
|
139
|
-
blocked: 'Blocked by dependency',
|
|
140
|
-
completed: 'Successfully finished',
|
|
141
|
-
cancelled: 'Cancelled/discarded',
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Task Schema
|
|
146
|
-
*/
|
|
147
|
-
class TaskSchema {
|
|
148
|
-
/**
|
|
149
|
-
* Create a new task
|
|
150
|
-
*/
|
|
151
|
-
static create(data) {
|
|
152
|
-
const now = new Date().toISOString()
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
id: data.id || this.generateId(),
|
|
156
|
-
title: data.title,
|
|
157
|
-
description: data.description || null,
|
|
158
|
-
|
|
159
|
-
// Agent & Developer
|
|
160
|
-
assignedAgent: data.assignedAgent || this.detectAgent(data.title),
|
|
161
|
-
githubDev: data.githubDev || null, // Will be populated from git config
|
|
162
|
-
|
|
163
|
-
// Complexity & Time
|
|
164
|
-
complexity: data.complexity || this.estimateComplexity(data.title, data.description),
|
|
165
|
-
estimatedTime: data.estimatedTime || this.estimateTime(data.complexity),
|
|
166
|
-
actualTime: null,
|
|
167
|
-
|
|
168
|
-
// Status & Tracking
|
|
169
|
-
status: data.status || 'pending',
|
|
170
|
-
priority: data.priority || 5,
|
|
171
|
-
blocked: data.blocked || false,
|
|
172
|
-
blockedBy: data.blockedBy || null,
|
|
173
|
-
|
|
174
|
-
// Timestamps
|
|
175
|
-
createdAt: data.createdAt || now,
|
|
176
|
-
startedAt: data.startedAt || null,
|
|
177
|
-
completedAt: data.completedAt || null,
|
|
178
|
-
|
|
179
|
-
// Metadata
|
|
180
|
-
tags: data.tags || [],
|
|
181
|
-
notes: data.notes || null,
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Generate unique task ID
|
|
187
|
-
*/
|
|
188
|
-
static generateId() {
|
|
189
|
-
return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Auto-detect appropriate agent based on task description
|
|
194
|
-
*/
|
|
195
|
-
static detectAgent(title, description = '') {
|
|
196
|
-
const text = `${title} ${description}`.toLowerCase()
|
|
197
|
-
let bestMatch = 'general-developer'
|
|
198
|
-
let highestScore = 0
|
|
199
|
-
|
|
200
|
-
for (const [agentType, config] of Object.entries(AGENT_TYPES)) {
|
|
201
|
-
const score = config.keywords.filter((keyword) => text.includes(keyword)).length
|
|
202
|
-
|
|
203
|
-
if (score > highestScore) {
|
|
204
|
-
highestScore = score
|
|
205
|
-
bestMatch = agentType
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return bestMatch
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Estimate complexity based on keywords and description
|
|
214
|
-
*/
|
|
215
|
-
static estimateComplexity(title, description = '') {
|
|
216
|
-
const text = `${title} ${description}`.toLowerCase()
|
|
217
|
-
|
|
218
|
-
// Epic indicators
|
|
219
|
-
if (
|
|
220
|
-
text.match(
|
|
221
|
-
/system|payment|real-time|dashboard|integration|authentication|migration|architecture/i,
|
|
222
|
-
)
|
|
223
|
-
) {
|
|
224
|
-
return 'epic'
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Complex indicators
|
|
228
|
-
if (text.match(/refactor|optimization|security|database|multiple|complex/i)) {
|
|
229
|
-
return 'complex'
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Moderate indicators
|
|
233
|
-
if (text.match(/feature|implement|api|endpoint|component|module/i)) {
|
|
234
|
-
return 'moderate'
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Simple indicators
|
|
238
|
-
if (text.match(/add|update|fix|change|simple|small/i)) {
|
|
239
|
-
return 'simple'
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Trivial indicators
|
|
243
|
-
if (text.match(/typo|config|color|text|minor|tiny/i)) {
|
|
244
|
-
return 'trivial'
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return 'simple' // default
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Estimate time based on complexity and agent efficiency
|
|
252
|
-
*/
|
|
253
|
-
static estimateTime(complexity, agentType = 'general-developer') {
|
|
254
|
-
const complexityData = COMPLEXITY[complexity]
|
|
255
|
-
const agentData = AGENT_TYPES[agentType]
|
|
256
|
-
|
|
257
|
-
if (!complexityData || !agentData) {
|
|
258
|
-
return '2-4 hours'
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const baseHours = complexityData.estimatedHours
|
|
262
|
-
const adjustedHours = baseHours * agentData.estimatedEfficiency
|
|
263
|
-
|
|
264
|
-
// Format as range
|
|
265
|
-
const low = Math.floor(adjustedHours * 0.75)
|
|
266
|
-
const high = Math.ceil(adjustedHours * 1.25)
|
|
267
|
-
|
|
268
|
-
if (adjustedHours < 1) {
|
|
269
|
-
return `${Math.round(adjustedHours * 60)} minutes`
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return `${low}-${high} hours`
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Start a task (move to active)
|
|
277
|
-
*/
|
|
278
|
-
static start(task) {
|
|
279
|
-
return {
|
|
280
|
-
...task,
|
|
281
|
-
status: 'active',
|
|
282
|
-
startedAt: new Date().toISOString(),
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Complete a task
|
|
288
|
-
*/
|
|
289
|
-
static complete(task) {
|
|
290
|
-
const completedAt = new Date().toISOString()
|
|
291
|
-
const actualTime = this.calculateActualTime(task.startedAt, completedAt)
|
|
292
|
-
|
|
293
|
-
return {
|
|
294
|
-
...task,
|
|
295
|
-
status: 'completed',
|
|
296
|
-
completedAt,
|
|
297
|
-
actualTime,
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Calculate actual time spent
|
|
303
|
-
*/
|
|
304
|
-
static calculateActualTime(startedAt, completedAt) {
|
|
305
|
-
if (!startedAt) return null
|
|
306
|
-
|
|
307
|
-
const start = new Date(startedAt)
|
|
308
|
-
const end = new Date(completedAt)
|
|
309
|
-
const diffMs = end - start
|
|
310
|
-
const hours = Math.floor(diffMs / (1000 * 60 * 60))
|
|
311
|
-
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
|
|
312
|
-
|
|
313
|
-
if (hours === 0) {
|
|
314
|
-
return `${minutes}m`
|
|
315
|
-
}
|
|
316
|
-
return `${hours}h ${minutes}m`
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Validate task schema
|
|
321
|
-
*/
|
|
322
|
-
static validate(task) {
|
|
323
|
-
const errors = []
|
|
324
|
-
|
|
325
|
-
if (!task.title) errors.push('Task title is required')
|
|
326
|
-
if (!AGENT_TYPES[task.assignedAgent]) errors.push('Invalid agent type')
|
|
327
|
-
if (!COMPLEXITY[task.complexity]) errors.push('Invalid complexity level')
|
|
328
|
-
if (!TASK_STATUS[task.status]) errors.push('Invalid task status')
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
valid: errors.length === 0,
|
|
332
|
-
errors,
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
module.exports = {
|
|
338
|
-
TaskSchema,
|
|
339
|
-
AGENT_TYPES,
|
|
340
|
-
COMPLEXITY,
|
|
341
|
-
TASK_STATUS,
|
|
342
|
-
}
|
package/core/workflow-engine.js
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflow Engine
|
|
3
|
-
* Orchestrates adaptive agent workflows based on project capabilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs').promises
|
|
7
|
-
const path = require('path')
|
|
8
|
-
const capabilities = require('./project-capabilities')
|
|
9
|
-
const rules = require('./workflow-rules')
|
|
10
|
-
|
|
11
|
-
class WorkflowEngine {
|
|
12
|
-
/**
|
|
13
|
-
* Initialize workflow for task
|
|
14
|
-
* @param {string} task - Task description
|
|
15
|
-
* @param {string} type - Workflow type (ui, api, bug, refactor, feature)
|
|
16
|
-
* @param {string} dataPath - Project data path
|
|
17
|
-
* @returns {Promise<Object>} Workflow object
|
|
18
|
-
*/
|
|
19
|
-
async init(task, type, dataPath) {
|
|
20
|
-
// Detect project capabilities
|
|
21
|
-
const projectPath = path.dirname(path.dirname(dataPath))
|
|
22
|
-
const caps = await capabilities.detect(projectPath)
|
|
23
|
-
|
|
24
|
-
// Get base workflow
|
|
25
|
-
const base = rules[type] || rules.ui
|
|
26
|
-
|
|
27
|
-
// Map all steps - mark for prompting if capability missing
|
|
28
|
-
const steps = base.map((s, index) => ({
|
|
29
|
-
...s,
|
|
30
|
-
index,
|
|
31
|
-
status: 'pending',
|
|
32
|
-
skipped: false,
|
|
33
|
-
needsPrompt: !s.required && s.prompt && !caps[s.needs],
|
|
34
|
-
}))
|
|
35
|
-
|
|
36
|
-
// Track which capabilities are missing
|
|
37
|
-
const missingCapabilities = base
|
|
38
|
-
.filter(s => !s.required && s.needs && !caps[s.needs])
|
|
39
|
-
.map(s => s.needs)
|
|
40
|
-
|
|
41
|
-
const workflow = {
|
|
42
|
-
task,
|
|
43
|
-
type,
|
|
44
|
-
caps,
|
|
45
|
-
steps,
|
|
46
|
-
missingCapabilities,
|
|
47
|
-
current: 0,
|
|
48
|
-
active: true,
|
|
49
|
-
createdAt: new Date().toISOString(),
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await this.save(workflow, dataPath)
|
|
53
|
-
return workflow
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get current step info
|
|
58
|
-
*/
|
|
59
|
-
async getCurrent(dataPath) {
|
|
60
|
-
const wf = await this.load(dataPath)
|
|
61
|
-
if (!wf || !wf.active) return null
|
|
62
|
-
|
|
63
|
-
return wf.steps[wf.current]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Advance to next step
|
|
68
|
-
*/
|
|
69
|
-
async next(dataPath) {
|
|
70
|
-
const wf = await this.load(dataPath)
|
|
71
|
-
if (!wf) return null
|
|
72
|
-
|
|
73
|
-
// Mark current step as completed
|
|
74
|
-
wf.steps[wf.current].status = 'completed'
|
|
75
|
-
wf.steps[wf.current].completedAt = new Date().toISOString()
|
|
76
|
-
|
|
77
|
-
// Move to next
|
|
78
|
-
wf.current++
|
|
79
|
-
|
|
80
|
-
// Check if workflow complete
|
|
81
|
-
if (wf.current >= wf.steps.length) {
|
|
82
|
-
wf.active = false
|
|
83
|
-
wf.completedAt = new Date().toISOString()
|
|
84
|
-
await this.save(wf, dataPath)
|
|
85
|
-
return null
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Mark next step as in progress
|
|
89
|
-
wf.steps[wf.current].status = 'in_progress'
|
|
90
|
-
wf.steps[wf.current].startedAt = new Date().toISOString()
|
|
91
|
-
|
|
92
|
-
await this.save(wf, dataPath)
|
|
93
|
-
return wf.steps[wf.current]
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Skip current step
|
|
98
|
-
*/
|
|
99
|
-
async skip(dataPath, reason = 'User skipped') {
|
|
100
|
-
const wf = await this.load(dataPath)
|
|
101
|
-
if (!wf) return null
|
|
102
|
-
|
|
103
|
-
wf.steps[wf.current].skipped = true
|
|
104
|
-
wf.steps[wf.current].status = 'skipped'
|
|
105
|
-
wf.steps[wf.current].skipReason = reason
|
|
106
|
-
wf.steps[wf.current].skippedAt = new Date().toISOString()
|
|
107
|
-
|
|
108
|
-
return await this.next(dataPath)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Insert installation step before current step
|
|
113
|
-
*/
|
|
114
|
-
async insertInstall(dataPath, installTask) {
|
|
115
|
-
const wf = await this.load(dataPath)
|
|
116
|
-
if (!wf) return null
|
|
117
|
-
|
|
118
|
-
const currentIndex = wf.current
|
|
119
|
-
|
|
120
|
-
// Insert install task at current position
|
|
121
|
-
wf.steps.splice(currentIndex, 0, {
|
|
122
|
-
...installTask,
|
|
123
|
-
index: currentIndex,
|
|
124
|
-
status: 'in_progress',
|
|
125
|
-
insertedAt: new Date().toISOString(),
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// Reindex all subsequent steps
|
|
129
|
-
for (let i = currentIndex + 1; i < wf.steps.length; i++) {
|
|
130
|
-
wf.steps[i].index = i
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
await this.save(wf, dataPath)
|
|
134
|
-
return wf.steps[currentIndex]
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Classify task type from description
|
|
139
|
-
* @param {string} text - Task description
|
|
140
|
-
* @returns {string} Workflow type
|
|
141
|
-
*/
|
|
142
|
-
classify(text) {
|
|
143
|
-
const t = text.toLowerCase()
|
|
144
|
-
|
|
145
|
-
if (/button|form|modal|card|component|menu|nav|input/.test(t)) return 'ui'
|
|
146
|
-
if (/endpoint|api|service|route|controller/.test(t)) return 'api'
|
|
147
|
-
if (/bug|fix|error|issue|broken/.test(t)) return 'bug'
|
|
148
|
-
if (/refactor|improve|optimize|clean/.test(t)) return 'refactor'
|
|
149
|
-
if (/feature|functionality|module/.test(t)) return 'feature'
|
|
150
|
-
|
|
151
|
-
return 'ui' // Default
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Get workflow status
|
|
156
|
-
*/
|
|
157
|
-
async getStatus(dataPath) {
|
|
158
|
-
const wf = await this.load(dataPath)
|
|
159
|
-
if (!wf) return null
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
task: wf.task,
|
|
163
|
-
type: wf.type,
|
|
164
|
-
active: wf.active,
|
|
165
|
-
current: wf.current,
|
|
166
|
-
total: wf.steps.length,
|
|
167
|
-
steps: wf.steps.map(s => ({
|
|
168
|
-
name: s.name,
|
|
169
|
-
status: s.status,
|
|
170
|
-
agent: s.agent,
|
|
171
|
-
})),
|
|
172
|
-
skipped: wf.skipped,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Load workflow from file
|
|
178
|
-
*/
|
|
179
|
-
async load(dataPath) {
|
|
180
|
-
try {
|
|
181
|
-
const workflowPath = path.join(dataPath, 'workflow', 'state.json')
|
|
182
|
-
const content = await fs.readFile(workflowPath, 'utf8')
|
|
183
|
-
return JSON.parse(content)
|
|
184
|
-
} catch {
|
|
185
|
-
return null
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Save workflow to file
|
|
191
|
-
*/
|
|
192
|
-
async save(workflow, dataPath) {
|
|
193
|
-
const workflowDir = path.join(dataPath, 'workflow')
|
|
194
|
-
await fs.mkdir(workflowDir, { recursive: true })
|
|
195
|
-
|
|
196
|
-
const workflowPath = path.join(workflowDir, 'state.json')
|
|
197
|
-
await fs.writeFile(workflowPath, JSON.stringify(workflow, null, 2))
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Clear workflow
|
|
202
|
-
*/
|
|
203
|
-
async clear(dataPath) {
|
|
204
|
-
try {
|
|
205
|
-
const workflowPath = path.join(dataPath, 'workflow', 'state.json')
|
|
206
|
-
await fs.unlink(workflowPath)
|
|
207
|
-
} catch {
|
|
208
|
-
// Ignore if doesn't exist
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
module.exports = new WorkflowEngine()
|