prjct-cli 1.12.0 → 1.14.0
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 +89 -0
- package/core/__tests__/agentic/analysis-injection.test.ts +377 -0
- package/core/__tests__/domain/velocity.test.ts +623 -0
- package/core/agentic/anti-hallucination.ts +23 -1
- package/core/agentic/orchestrator-executor.ts +59 -3
- package/core/agentic/prompt-builder.ts +53 -1
- package/core/commands/command-data.ts +17 -0
- package/core/commands/commands.ts +12 -0
- package/core/commands/register.ts +6 -0
- package/core/commands/velocity.ts +149 -0
- package/core/domain/velocity.ts +470 -0
- package/core/index.ts +1 -0
- package/core/schemas/index.ts +2 -0
- package/core/schemas/velocity.ts +103 -0
- package/core/storage/index.ts +2 -1
- package/core/storage/velocity-storage.ts +149 -0
- package/core/types/agentic.ts +33 -0
- package/core/types/index.ts +2 -0
- package/dist/bin/prjct.mjs +1103 -362
- package/package.json +1 -1
|
@@ -21,15 +21,19 @@ import { promisify } from 'node:util'
|
|
|
21
21
|
import { findRelevantFiles } from '../context-tools/files-tool'
|
|
22
22
|
import { getRecentFiles } from '../context-tools/recent-tool'
|
|
23
23
|
import { extractSignatures } from '../context-tools/signatures-tool'
|
|
24
|
+
import { calculateVelocity, formatVelocityContext } from '../domain/velocity'
|
|
24
25
|
import configManager from '../infrastructure/config-manager'
|
|
25
26
|
import pathManager from '../infrastructure/path-manager'
|
|
26
|
-
import
|
|
27
|
+
import outcomeRecorder from '../outcomes/recorder'
|
|
28
|
+
import { DEFAULT_VELOCITY_CONFIG } from '../schemas/velocity'
|
|
29
|
+
import { analysisStorage, stateStorage } from '../storage'
|
|
27
30
|
import type {
|
|
28
31
|
LoadedAgent,
|
|
29
32
|
LoadedSkill,
|
|
30
33
|
OrchestratorContext,
|
|
31
34
|
OrchestratorSubtask,
|
|
32
35
|
RealCodebaseContext,
|
|
36
|
+
SealedAnalysisContext,
|
|
33
37
|
} from '../types'
|
|
34
38
|
import { getErrorMessage, isNotFoundError } from '../types/fs'
|
|
35
39
|
import domainClassifier, { type ProjectContext } from './domain-classifier'
|
|
@@ -76,8 +80,12 @@ export class OrchestratorExecutor {
|
|
|
76
80
|
// Step 5: Load skills from agent frontmatter
|
|
77
81
|
const skills = await this.loadSkills(agents)
|
|
78
82
|
|
|
79
|
-
// Step 6: Gather real codebase context
|
|
80
|
-
const realContext = await
|
|
83
|
+
// Step 6: Gather real codebase context, sealed analysis, and velocity in parallel
|
|
84
|
+
const [realContext, sealedAnalysis, velocityContext] = await Promise.all([
|
|
85
|
+
this.gatherRealContext(taskDescription, projectPath),
|
|
86
|
+
this.loadSealedAnalysis(projectId),
|
|
87
|
+
this.loadVelocityContext(projectId),
|
|
88
|
+
])
|
|
81
89
|
|
|
82
90
|
// Step 7: Determine if fragmentation is needed
|
|
83
91
|
const requiresFragmentation = this.shouldFragment(domains, taskDescription)
|
|
@@ -101,6 +109,8 @@ export class OrchestratorExecutor {
|
|
|
101
109
|
conventions: repoAnalysis?.conventions || [],
|
|
102
110
|
},
|
|
103
111
|
realContext,
|
|
112
|
+
sealedAnalysis,
|
|
113
|
+
velocityContext,
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
@@ -197,6 +207,52 @@ export class OrchestratorExecutor {
|
|
|
197
207
|
}
|
|
198
208
|
}
|
|
199
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Load sealed/active analysis from analysis storage (PRJ-260).
|
|
212
|
+
* Returns sealed if available, otherwise draft as fallback.
|
|
213
|
+
* Returns null if no analysis exists (graceful degradation).
|
|
214
|
+
*/
|
|
215
|
+
private async loadSealedAnalysis(projectId: string): Promise<SealedAnalysisContext | null> {
|
|
216
|
+
try {
|
|
217
|
+
const analysis = await analysisStorage.getActive(projectId)
|
|
218
|
+
if (!analysis) return null
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
languages: analysis.languages,
|
|
222
|
+
frameworks: analysis.frameworks,
|
|
223
|
+
packageManager: analysis.packageManager,
|
|
224
|
+
sourceDir: analysis.sourceDir,
|
|
225
|
+
testDir: analysis.testDir,
|
|
226
|
+
fileCount: analysis.fileCount,
|
|
227
|
+
patterns: analysis.patterns,
|
|
228
|
+
antiPatterns: analysis.antiPatterns,
|
|
229
|
+
status: analysis.status ?? 'draft',
|
|
230
|
+
commitHash: analysis.commitHash,
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Graceful degradation — analysis is optional enhancement
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Load velocity context for estimation guidance (PRJ-296).
|
|
240
|
+
* Returns formatted string for prompt injection, or null if no data.
|
|
241
|
+
*/
|
|
242
|
+
private async loadVelocityContext(projectId: string): Promise<string | null> {
|
|
243
|
+
try {
|
|
244
|
+
const outcomes = await outcomeRecorder.getAll(projectId)
|
|
245
|
+
if (outcomes.length === 0) return null
|
|
246
|
+
|
|
247
|
+
const metrics = calculateVelocity(outcomes, DEFAULT_VELOCITY_CONFIG)
|
|
248
|
+
if (metrics.sprints.length === 0) return null
|
|
249
|
+
|
|
250
|
+
return formatVelocityContext(metrics)
|
|
251
|
+
} catch {
|
|
252
|
+
return null
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
200
256
|
/**
|
|
201
257
|
* Load repo-analysis.json for project context
|
|
202
258
|
*/
|
|
@@ -542,10 +542,50 @@ class PromptBuilder {
|
|
|
542
542
|
// =========================================================================
|
|
543
543
|
|
|
544
544
|
if (orchestratorContext) {
|
|
545
|
+
const sa = orchestratorContext.sealedAnalysis
|
|
545
546
|
parts.push('\n## PROJECT ANALYSIS (Sealed)\n')
|
|
546
547
|
parts.push(`**Ecosystem**: ${orchestratorContext.project.ecosystem}\n`)
|
|
547
548
|
parts.push(`**Primary Domain**: ${orchestratorContext.primaryDomain}\n`)
|
|
548
|
-
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n
|
|
549
|
+
parts.push(`**Domains**: ${orchestratorContext.detectedDomains.join(', ')}\n`)
|
|
550
|
+
|
|
551
|
+
// Inject sealed analysis data (PRJ-260)
|
|
552
|
+
if (sa) {
|
|
553
|
+
if (sa.languages.length > 0) {
|
|
554
|
+
parts.push(`**Languages**: ${sa.languages.join(', ')}\n`)
|
|
555
|
+
}
|
|
556
|
+
if (sa.frameworks.length > 0) {
|
|
557
|
+
parts.push(`**Frameworks**: ${sa.frameworks.join(', ')}\n`)
|
|
558
|
+
}
|
|
559
|
+
if (sa.packageManager) {
|
|
560
|
+
parts.push(`**Package Manager**: ${sa.packageManager}\n`)
|
|
561
|
+
}
|
|
562
|
+
if (sa.sourceDir) {
|
|
563
|
+
parts.push(`**Source Dir**: ${sa.sourceDir}\n`)
|
|
564
|
+
}
|
|
565
|
+
if (sa.testDir) {
|
|
566
|
+
parts.push(`**Test Dir**: ${sa.testDir}\n`)
|
|
567
|
+
}
|
|
568
|
+
parts.push(`**Files Analyzed**: ${sa.fileCount}\n`)
|
|
569
|
+
parts.push(
|
|
570
|
+
`**Analysis Status**: ${sa.status}${sa.commitHash ? ` (commit: ${sa.commitHash.slice(0, 8)})` : ''}\n`
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if (sa.patterns.length > 0) {
|
|
574
|
+
parts.push('\n### Code Patterns (Follow These)\n')
|
|
575
|
+
for (const p of sa.patterns) {
|
|
576
|
+
parts.push(`- **${p.name}**: ${p.description}${p.location ? ` (${p.location})` : ''}\n`)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (sa.antiPatterns.length > 0) {
|
|
581
|
+
parts.push('\n### Anti-Patterns (Avoid These)\n')
|
|
582
|
+
for (const ap of sa.antiPatterns) {
|
|
583
|
+
parts.push(`- **${ap.issue}** in \`${ap.file}\` — ${ap.suggestion}\n`)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
parts.push('\n')
|
|
549
589
|
}
|
|
550
590
|
|
|
551
591
|
const needsPatterns = commandContext.patterns
|
|
@@ -648,6 +688,7 @@ class PromptBuilder {
|
|
|
648
688
|
// =========================================================================
|
|
649
689
|
|
|
650
690
|
if (projectPath) {
|
|
691
|
+
const sa = orchestratorContext?.sealedAnalysis
|
|
651
692
|
const groundTruth: ProjectGroundTruth = {
|
|
652
693
|
projectPath,
|
|
653
694
|
language: orchestratorContext?.project?.ecosystem,
|
|
@@ -655,6 +696,10 @@ class PromptBuilder {
|
|
|
655
696
|
domains: this.extractDomains(state),
|
|
656
697
|
fileCount: context.files?.length || context.filteredSize || 0,
|
|
657
698
|
availableAgents: orchestratorContext?.agents?.map((a) => a.name) || [],
|
|
699
|
+
// Inject sealed analysis data for enriched grounding (PRJ-260)
|
|
700
|
+
analysisLanguages: sa?.languages || [],
|
|
701
|
+
analysisFrameworks: sa?.frameworks || [],
|
|
702
|
+
analysisPackageManager: sa?.packageManager,
|
|
658
703
|
}
|
|
659
704
|
parts.push(`\n${buildAntiHallucinationBlock(groundTruth)}\n`)
|
|
660
705
|
} else {
|
|
@@ -721,6 +766,13 @@ class PromptBuilder {
|
|
|
721
766
|
parts.push('\n')
|
|
722
767
|
}
|
|
723
768
|
|
|
769
|
+
// Velocity context (PRJ-296) — estimation guidance from historical data
|
|
770
|
+
if (orchestratorContext?.velocityContext) {
|
|
771
|
+
parts.push('\n### VELOCITY (Historical Estimation Data)\n\n')
|
|
772
|
+
parts.push(orchestratorContext.velocityContext)
|
|
773
|
+
parts.push('\n\n')
|
|
774
|
+
}
|
|
775
|
+
|
|
724
776
|
// Learned patterns
|
|
725
777
|
if (learnedPatterns && Object.keys(learnedPatterns).some((k) => learnedPatterns[k])) {
|
|
726
778
|
parts.push('\n## PROJECT DEFAULTS (apply automatically)\n')
|
|
@@ -193,6 +193,23 @@ export const COMMANDS: CommandMeta[] = [
|
|
|
193
193
|
'Command duration breakdown',
|
|
194
194
|
],
|
|
195
195
|
},
|
|
196
|
+
{
|
|
197
|
+
name: 'velocity',
|
|
198
|
+
group: 'core',
|
|
199
|
+
description: 'Sprint-based velocity dashboard with trend detection and projections',
|
|
200
|
+
usage: { claude: '/p:velocity', terminal: 'prjct velocity [backlogPoints]' },
|
|
201
|
+
params: '[backlogPoints]',
|
|
202
|
+
implemented: true,
|
|
203
|
+
hasTemplate: false,
|
|
204
|
+
requiresProject: true,
|
|
205
|
+
features: [
|
|
206
|
+
'Sprint-by-sprint velocity breakdown',
|
|
207
|
+
'Trend detection (improving/stable/declining)',
|
|
208
|
+
'Estimation accuracy tracking',
|
|
209
|
+
'Over/under estimation pattern detection',
|
|
210
|
+
'Completion projections for backlog',
|
|
211
|
+
],
|
|
212
|
+
},
|
|
196
213
|
{
|
|
197
214
|
name: 'suggest',
|
|
198
215
|
group: 'core',
|
|
@@ -31,6 +31,7 @@ import { PerformanceCommands } from './performance'
|
|
|
31
31
|
import { PlanningCommands } from './planning'
|
|
32
32
|
import { SetupCommands } from './setup'
|
|
33
33
|
import { ShippingCommands } from './shipping'
|
|
34
|
+
import { VelocityCommands } from './velocity'
|
|
34
35
|
import { WorkflowCommands } from './workflow'
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -47,6 +48,7 @@ class PrjctCommands {
|
|
|
47
48
|
private maintenance: MaintenanceCommands
|
|
48
49
|
private analysis: AnalysisCommands
|
|
49
50
|
private setupCmds: SetupCommands
|
|
51
|
+
private velocityCmds: VelocityCommands
|
|
50
52
|
private contextCmds: ContextCommands
|
|
51
53
|
|
|
52
54
|
// Shared state
|
|
@@ -64,6 +66,7 @@ class PrjctCommands {
|
|
|
64
66
|
this.maintenance = new MaintenanceCommands()
|
|
65
67
|
this.analysis = new AnalysisCommands()
|
|
66
68
|
this.setupCmds = new SetupCommands()
|
|
69
|
+
this.velocityCmds = new VelocityCommands()
|
|
67
70
|
this.contextCmds = new ContextCommands()
|
|
68
71
|
|
|
69
72
|
this.agent = null
|
|
@@ -149,6 +152,15 @@ class PrjctCommands {
|
|
|
149
152
|
return this.performanceCmds.perf(period, projectPath)
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
// ========== Velocity Commands ==========
|
|
156
|
+
|
|
157
|
+
async velocity(
|
|
158
|
+
backlogPoints: string = '0',
|
|
159
|
+
projectPath: string = process.cwd()
|
|
160
|
+
): Promise<CommandResult> {
|
|
161
|
+
return this.velocityCmds.velocity(backlogPoints, projectPath)
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
// ========== Maintenance Commands ==========
|
|
153
165
|
|
|
154
166
|
async cleanup(
|
|
@@ -19,6 +19,7 @@ import { commandRegistry } from './registry'
|
|
|
19
19
|
import { SetupCommands } from './setup'
|
|
20
20
|
import { ShippingCommands } from './shipping'
|
|
21
21
|
import { UninstallCommands } from './uninstall'
|
|
22
|
+
import { VelocityCommands } from './velocity'
|
|
22
23
|
import { WorkflowCommands } from './workflow'
|
|
23
24
|
|
|
24
25
|
// Singleton instances of command groups
|
|
@@ -31,6 +32,7 @@ const maintenance = new MaintenanceCommands()
|
|
|
31
32
|
const analysis = new AnalysisCommands()
|
|
32
33
|
const setup = new SetupCommands()
|
|
33
34
|
const context = new ContextCommands()
|
|
35
|
+
const velocityCmd = new VelocityCommands()
|
|
34
36
|
const uninstallCmd = new UninstallCommands()
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -78,6 +80,9 @@ export function registerAllCommands(): void {
|
|
|
78
80
|
// Performance commands
|
|
79
81
|
commandRegistry.registerMethod('perf', performance, 'perf', getMeta('perf'))
|
|
80
82
|
|
|
83
|
+
// Velocity commands
|
|
84
|
+
commandRegistry.registerMethod('velocity', velocityCmd, 'velocity', getMeta('velocity'))
|
|
85
|
+
|
|
81
86
|
// Maintenance commands
|
|
82
87
|
commandRegistry.registerMethod('cleanup', maintenance, 'cleanup', getMeta('cleanup'))
|
|
83
88
|
commandRegistry.registerMethod('design', maintenance, 'design', getMeta('design'))
|
|
@@ -117,5 +122,6 @@ export {
|
|
|
117
122
|
analysis,
|
|
118
123
|
setup,
|
|
119
124
|
context,
|
|
125
|
+
velocityCmd,
|
|
120
126
|
uninstallCmd,
|
|
121
127
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velocity Commands: velocity
|
|
3
|
+
* Sprint-based velocity dashboard
|
|
4
|
+
*
|
|
5
|
+
* @see PRJ-296
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk'
|
|
9
|
+
import { calculateVelocity, projectCompletion } from '../domain/velocity'
|
|
10
|
+
import outcomeRecorder from '../outcomes/recorder'
|
|
11
|
+
import type { VelocityConfig } from '../schemas/velocity'
|
|
12
|
+
import { DEFAULT_VELOCITY_CONFIG } from '../schemas/velocity'
|
|
13
|
+
import { velocityStorage } from '../storage/velocity-storage'
|
|
14
|
+
import type { CommandResult } from '../types'
|
|
15
|
+
import { getErrorMessage } from '../types/fs'
|
|
16
|
+
import { configManager, out, PrjctCommandsBase } from './base'
|
|
17
|
+
|
|
18
|
+
export class VelocityCommands extends PrjctCommandsBase {
|
|
19
|
+
/**
|
|
20
|
+
* prjct velocity - Velocity dashboard
|
|
21
|
+
*/
|
|
22
|
+
async velocity(
|
|
23
|
+
backlogPoints: string = '0',
|
|
24
|
+
projectPath: string = process.cwd()
|
|
25
|
+
): Promise<CommandResult> {
|
|
26
|
+
try {
|
|
27
|
+
const initResult = await this.ensureProjectInit(projectPath)
|
|
28
|
+
if (!initResult.success) return initResult
|
|
29
|
+
|
|
30
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
31
|
+
if (!projectId) {
|
|
32
|
+
out.failWithHint('NO_PROJECT_ID')
|
|
33
|
+
return { success: false, error: 'No project ID found' }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Load velocity config from project config (or defaults)
|
|
37
|
+
const config = await this.loadVelocityConfig(projectPath)
|
|
38
|
+
|
|
39
|
+
// Load all outcomes
|
|
40
|
+
const outcomes = await outcomeRecorder.getAll(projectId)
|
|
41
|
+
|
|
42
|
+
if (outcomes.length === 0) {
|
|
43
|
+
console.log(`\n${chalk.dim('No velocity data yet.')}`)
|
|
44
|
+
console.log(`${chalk.dim('Complete tasks with estimates to build velocity history.')}\n`)
|
|
45
|
+
return { success: true, message: 'No data' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Calculate velocity metrics
|
|
49
|
+
const metrics = calculateVelocity(outcomes, config)
|
|
50
|
+
|
|
51
|
+
// Save for context injection
|
|
52
|
+
await velocityStorage.saveMetrics(projectId, metrics)
|
|
53
|
+
|
|
54
|
+
// Render dashboard
|
|
55
|
+
console.log(
|
|
56
|
+
`\n${chalk.cyan('Sprint Velocity')} ${chalk.dim(`(last ${config.windowSize ?? 6} sprints)`)}`
|
|
57
|
+
)
|
|
58
|
+
console.log('═'.repeat(60))
|
|
59
|
+
|
|
60
|
+
// Sprint table
|
|
61
|
+
const recentSprints = metrics.sprints.slice(-(config.windowSize ?? 6))
|
|
62
|
+
for (const sprint of recentSprints) {
|
|
63
|
+
const accuracyColor =
|
|
64
|
+
sprint.estimationAccuracy >= 80
|
|
65
|
+
? chalk.green
|
|
66
|
+
: sprint.estimationAccuracy >= 60
|
|
67
|
+
? chalk.yellow
|
|
68
|
+
: chalk.red
|
|
69
|
+
console.log(
|
|
70
|
+
` Sprint ${String(sprint.sprintNumber).padStart(2)}: ${chalk.bold(`${sprint.pointsCompleted} pts`)} | ${sprint.tasksCompleted} tasks | accuracy: ${accuracyColor(`${sprint.estimationAccuracy}%`)}`
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log('')
|
|
75
|
+
const trendIcon =
|
|
76
|
+
metrics.velocityTrend === 'improving'
|
|
77
|
+
? chalk.green('↑')
|
|
78
|
+
: metrics.velocityTrend === 'declining'
|
|
79
|
+
? chalk.red('↓')
|
|
80
|
+
: chalk.dim('→')
|
|
81
|
+
console.log(
|
|
82
|
+
` Average: ${chalk.bold(`${metrics.averageVelocity} pts/sprint`)} | Trend: ${trendIcon} ${metrics.velocityTrend}`
|
|
83
|
+
)
|
|
84
|
+
console.log(
|
|
85
|
+
` Estimation accuracy: ${chalk.bold(`${metrics.estimationAccuracy}%`)} ${chalk.dim(`(±${config.accuracyTolerance ?? 20}% tolerance)`)}`
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
// Patterns
|
|
89
|
+
if (metrics.underEstimated.length > 0 || metrics.overEstimated.length > 0) {
|
|
90
|
+
console.log(`\n ${chalk.dim('Patterns:')}`)
|
|
91
|
+
for (const p of metrics.underEstimated) {
|
|
92
|
+
console.log(
|
|
93
|
+
` ${chalk.yellow('⚠')} ${p.category} tasks underestimated by avg ${chalk.bold(`${p.avgVariance}%`)}`
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
for (const p of metrics.overEstimated) {
|
|
97
|
+
console.log(
|
|
98
|
+
` ${chalk.green('✓')} ${p.category} tasks estimated within ${chalk.bold(`${p.avgVariance}%`)}`
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Projection (if backlog points provided)
|
|
104
|
+
const points = parseInt(backlogPoints, 10)
|
|
105
|
+
if (points > 0 && metrics.averageVelocity > 0) {
|
|
106
|
+
const projection = projectCompletion(points, metrics.averageVelocity, config)
|
|
107
|
+
const dateStr = projection.estimatedDate
|
|
108
|
+
? new Date(projection.estimatedDate).toLocaleDateString('en-US', {
|
|
109
|
+
month: 'short',
|
|
110
|
+
day: 'numeric',
|
|
111
|
+
year: 'numeric',
|
|
112
|
+
})
|
|
113
|
+
: 'unknown'
|
|
114
|
+
console.log(`\n ${chalk.dim('Projection:')}`)
|
|
115
|
+
console.log(` Backlog: ${chalk.bold(`${points} pts`)} remaining`)
|
|
116
|
+
console.log(
|
|
117
|
+
` At current velocity: ~${projection.sprints} sprints (${projection.sprints * (config.sprintLengthDays ?? 7)} days)`
|
|
118
|
+
)
|
|
119
|
+
console.log(` Estimated completion: ${chalk.bold(dateStr)}`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log('═'.repeat(60))
|
|
123
|
+
console.log('')
|
|
124
|
+
|
|
125
|
+
return { success: true }
|
|
126
|
+
} catch (error) {
|
|
127
|
+
out.fail(getErrorMessage(error))
|
|
128
|
+
return { success: false, error: getErrorMessage(error) }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Load velocity config from project or use defaults.
|
|
134
|
+
* Velocity config can be added to prjct.config.json as { velocity: { sprintLengthDays, ... } }
|
|
135
|
+
*/
|
|
136
|
+
private async loadVelocityConfig(projectPath: string): Promise<VelocityConfig> {
|
|
137
|
+
try {
|
|
138
|
+
const config = await configManager.readConfig(projectPath)
|
|
139
|
+
// Read velocity config from extended config (not typed in LocalConfig yet)
|
|
140
|
+
const raw = config as Record<string, unknown> | null
|
|
141
|
+
if (raw?.velocity && typeof raw.velocity === 'object') {
|
|
142
|
+
return { ...DEFAULT_VELOCITY_CONFIG, ...(raw.velocity as Partial<VelocityConfig>) }
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Use defaults
|
|
146
|
+
}
|
|
147
|
+
return DEFAULT_VELOCITY_CONFIG
|
|
148
|
+
}
|
|
149
|
+
}
|