prjct-cli 0.5.1 → 0.7.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +220 -7
  2. package/CLAUDE.md +476 -55
  3. package/README.md +48 -55
  4. package/bin/prjct +170 -225
  5. package/core/agentic/command-executor.js +113 -0
  6. package/core/agentic/context-builder.js +85 -0
  7. package/core/agentic/prompt-builder.js +86 -0
  8. package/core/agentic/template-loader.js +104 -0
  9. package/core/agentic/tool-registry.js +117 -0
  10. package/core/command-registry.js +597 -0
  11. package/core/commands.js +2046 -2028
  12. package/core/domain/agent-generator.js +118 -0
  13. package/core/domain/analyzer.js +211 -0
  14. package/core/domain/architect-session.js +300 -0
  15. package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
  16. package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
  17. package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
  18. package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
  19. package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
  20. package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
  21. package/core/{migrator.js → infrastructure/migrator.js} +34 -19
  22. package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
  23. package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
  24. package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
  25. package/core/{animations-simple.js → utils/animations.js} +3 -23
  26. package/core/utils/date-helper.js +238 -0
  27. package/core/utils/file-helper.js +327 -0
  28. package/core/utils/jsonl-helper.js +206 -0
  29. package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
  30. package/core/utils/session-helper.js +277 -0
  31. package/core/{version.js → utils/version.js} +1 -1
  32. package/package.json +5 -12
  33. package/templates/agents/AGENTS.md +151 -99
  34. package/templates/analysis/analyze.md +84 -0
  35. package/templates/commands/analyze.md +37 -233
  36. package/templates/commands/bug.md +79 -0
  37. package/templates/commands/build.md +44 -0
  38. package/templates/commands/cleanup.md +24 -84
  39. package/templates/commands/design.md +20 -95
  40. package/templates/commands/done.md +17 -180
  41. package/templates/commands/feature.md +113 -0
  42. package/templates/commands/fix.md +58 -66
  43. package/templates/commands/git.md +35 -57
  44. package/templates/commands/help.md +18 -52
  45. package/templates/commands/idea.md +18 -34
  46. package/templates/commands/init.md +65 -257
  47. package/templates/commands/next.md +20 -60
  48. package/templates/commands/now.md +21 -23
  49. package/templates/commands/progress.md +40 -73
  50. package/templates/commands/recap.md +52 -75
  51. package/templates/commands/roadmap.md +30 -85
  52. package/templates/commands/ship.md +93 -126
  53. package/templates/commands/status.md +42 -0
  54. package/templates/commands/sync.md +19 -205
  55. package/templates/commands/task.md +19 -79
  56. package/templates/commands/test.md +25 -71
  57. package/templates/commands/workflow.md +20 -210
  58. package/core/agent-generator.js +0 -516
  59. package/core/analyzer.js +0 -600
  60. package/core/animations.js +0 -277
  61. package/core/git-integration.js +0 -401
  62. package/core/workflow-engine.js +0 -213
  63. package/core/workflow-prompts.js +0 -192
  64. package/core/workflow-rules.js +0 -147
  65. package/scripts/post-install.js +0 -121
  66. package/scripts/preuninstall.js +0 -94
  67. package/scripts/verify-installation.sh +0 -158
  68. package/templates/agents/be.template.md +0 -42
  69. package/templates/agents/data.template.md +0 -41
  70. package/templates/agents/devops.template.md +0 -41
  71. package/templates/agents/fe.template.md +0 -42
  72. package/templates/agents/mobile.template.md +0 -41
  73. package/templates/agents/pm.template.md +0 -84
  74. package/templates/agents/qa.template.md +0 -54
  75. package/templates/agents/scribe.template.md +0 -95
  76. package/templates/agents/security.template.md +0 -41
  77. package/templates/agents/ux.template.md +0 -49
  78. package/templates/commands/context.md +0 -105
  79. package/templates/commands/stuck.md +0 -48
  80. package/templates/examples/natural-language-examples.md +0 -532
  81. /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
@@ -1,277 +0,0 @@
1
- /**
2
- * Cool animations and visual effects for prjct
3
- * Using Catppuccin color palette
4
- */
5
-
6
- const chalk = require('chalk')
7
-
8
- if (!chalk.supportsColor) {
9
- chalk.level = 3 // Full RGB color support
10
- process.env.FORCE_COLOR = '3'
11
- }
12
-
13
- const catppuccin = {
14
-
15
- rosewater: '#f5e0dc',
16
- flamingo: '#f2cdcd',
17
- pink: '#f5c2e7',
18
- mauve: '#cba6f7',
19
- red: '#f38ba8',
20
- maroon: '#eba0ac',
21
- peach: '#fab387',
22
- yellow: '#f9e2af',
23
- green: '#a6e3a1',
24
- teal: '#94e2d5',
25
- sky: '#89dceb',
26
- sapphire: '#74c7ec',
27
- blue: '#89b4fa',
28
- lavender: '#b4befe',
29
-
30
- text: '#cdd6f4',
31
- subtext1: '#bac2de',
32
- subtext0: '#a6adc8',
33
- overlay2: '#9399b2',
34
- overlay1: '#7f849c',
35
- overlay0: '#6c7086',
36
- surface2: '#585b70',
37
- surface1: '#45475a',
38
- surface0: '#313244',
39
- base: '#1e1e2e',
40
- mantle: '#181825',
41
- crust: '#11111b',
42
- }
43
-
44
- const colors = {
45
- success: chalk.hex(catppuccin.green),
46
- error: chalk.hex(catppuccin.red),
47
- warning: chalk.hex(catppuccin.yellow),
48
- info: chalk.hex(catppuccin.blue),
49
- ship: chalk.hex(catppuccin.sapphire),
50
- celebrate: chalk.hex(catppuccin.pink),
51
- focus: chalk.hex(catppuccin.teal),
52
- idea: chalk.hex(catppuccin.yellow),
53
- progress: chalk.hex(catppuccin.lavender),
54
- task: chalk.hex(catppuccin.mauve),
55
- primary: chalk.hex(catppuccin.mauve),
56
- secondary: chalk.hex(catppuccin.sky),
57
- text: chalk.hex(catppuccin.text),
58
- subtext: chalk.hex(catppuccin.subtext1),
59
- dim: chalk.hex(catppuccin.overlay1),
60
- }
61
-
62
- const frames = {
63
- rocket: [
64
- ' 🚀 ',
65
- ' 🚀 ',
66
- ' 🚀 ',
67
- ' 🚀 ',
68
- ' 🚀 ',
69
- '🚀 ',
70
- ],
71
- sparkles: [
72
- '✨ ・ 。゚☆: *.☽ .* :☆゚. ✨',
73
- '・ 。゚☆: *.☽ .* :☆゚. ✨ ・',
74
- '。゚☆: *.☽ .* :☆゚. ✨ ・ 。゚',
75
- '☆: *.☽ .* :☆゚. ✨ ・ 。゚☆:',
76
- ],
77
- loading: [
78
- '⠋',
79
- '⠙',
80
- '⠹',
81
- '⠸',
82
- '⠼',
83
- '⠴',
84
- '⠦',
85
- '⠧',
86
- '⠇',
87
- '⠏',
88
- ],
89
- progress: [
90
- '[ ]',
91
- '[▓ ]',
92
- '[▓▓ ]',
93
- '[▓▓▓ ]',
94
- '[▓▓▓▓ ]',
95
- '[▓▓▓▓▓ ]',
96
- '[▓▓▓▓▓▓ ]',
97
- '[▓▓▓▓▓▓▓ ]',
98
- '[▓▓▓▓▓▓▓▓ ]',
99
- '[▓▓▓▓▓▓▓▓▓ ]',
100
- '[▓▓▓▓▓▓▓▓▓▓]',
101
- ],
102
- celebration: [
103
- '🎉',
104
- '🎊',
105
- '✨',
106
- '🌟',
107
- '⭐',
108
- '💫',
109
- '🎆',
110
- '🎇',
111
- ],
112
- }
113
-
114
- const banners = {
115
- ship: `
116
- ╔════════════════════════════════════════════╗
117
- ║ 🚀 ${colors.ship.bold('S H I P P E D !')} 🚀 ║
118
- ╚════════════════════════════════════════════╝`,
119
-
120
- success: `
121
- ✨ ${colors.success.bold('Success!')} ✨`,
122
-
123
- error: `
124
- ❌ ${colors.error.bold('Error')} ❌`,
125
-
126
- welcome: `
127
- ${colors.primary('╔══════════════════════════════════════════════════╗')}
128
- ${colors.primary('║')} ${colors.text.bold('🚀 prjct')}${colors.primary('/')}${colors.secondary.bold('cli')} ${colors.primary('║')}
129
- ${colors.primary('║')} ${colors.dim('Ship faster with zero friction')} ${colors.primary('║')}
130
- ${colors.primary('╚══════════════════════════════════════════════════╝')}`,
131
-
132
- cleanup: `
133
- ${colors.focus('🧹 ✨ Cleanup Magic ✨ 🧹')}`,
134
-
135
- focus: `
136
- ${colors.focus('━━━━━━━━━━━━━━━━━━━━━━━')}
137
- ${colors.focus.bold(' 🎯 FOCUS MODE 🎯 ')}
138
- ${colors.focus('━━━━━━━━━━━━━━━━━━━━━━━')}`,
139
- }
140
-
141
- async function animate(frames, duration = 100) {
142
- for (const frame of frames) {
143
- process.stdout.write('\r' + frame)
144
- await sleep(duration)
145
- }
146
- process.stdout.write('\r' + ' '.repeat(30) + '\r')
147
- }
148
-
149
- async function typeWriter(text, delay = 30) {
150
- for (let i = 0; i <= text.length; i++) {
151
- process.stdout.write('\r' + text.slice(0, i) + (i < text.length ? '▋' : ''))
152
- await sleep(delay)
153
- }
154
- process.stdout.write('\n')
155
- }
156
-
157
- async function progressBar(duration = 1000, label = 'Processing') {
158
- const steps = 20
159
- const stepDuration = duration / steps
160
-
161
- for (let i = 0; i <= steps; i++) {
162
- const percent = Math.round((i / steps) * 100)
163
- const filled = '▓'.repeat(i)
164
- const empty = '░'.repeat(steps - i)
165
- const bar = `${colors.dim(label)} [${colors.primary(filled)}${colors.dim(empty)}] ${colors.text(percent + '%')}`
166
- process.stdout.write('\r' + bar)
167
- await sleep(stepDuration)
168
- }
169
- process.stdout.write('\n')
170
- }
171
-
172
- async function sparkle(message) {
173
- const sparkles = ['✨', '⭐', '💫', '🌟']
174
- let output = ''
175
-
176
- for (let i = 0; i < 3; i++) {
177
- const spark = sparkles[Math.floor(Math.random() * sparkles.length)]
178
- output = `${spark} ${message} ${spark}`
179
- process.stdout.write('\r' + output)
180
- await sleep(200)
181
- process.stdout.write('\r' + ' '.repeat(output.length))
182
- await sleep(100)
183
- }
184
-
185
- console.log(output)
186
- }
187
-
188
- function sleep(ms) {
189
- return new Promise(resolve => setTimeout(resolve, ms))
190
- }
191
-
192
- function formatShip(feature, count) {
193
- const banner = banners.ship
194
- const stats = `
195
- ${colors.text('Feature:')} ${colors.ship.bold(feature)}
196
- ${colors.text('Total shipped:')} ${colors.success.bold(count)}
197
- ${colors.text('Velocity:')} ${colors.celebrate('🔥 On fire!')}
198
- `
199
-
200
- return banner + stats
201
- }
202
-
203
- function formatFocus(task, timestamp) {
204
- const banner = banners.focus
205
- const info = `
206
- ${colors.text('Current task:')} ${colors.focus.bold(task)}
207
- ${colors.dim('Started:')} ${colors.subtext(timestamp)}
208
- `
209
-
210
- return banner + info
211
- }
212
-
213
- function formatSuccess(message) {
214
- return `${colors.success('✅')} ${colors.text(message)}`
215
- }
216
-
217
- function formatError(message) {
218
- return `${colors.error('❌')} ${colors.text(message)}`
219
- }
220
-
221
- function formatIdea(idea) {
222
- return `
223
- ${colors.idea('💡 Idea captured!')}
224
- ${colors.text('―'.repeat(30))}
225
- ${colors.subtext(idea)}
226
- ${colors.text('―'.repeat(30))}
227
- ${colors.dim('Added to your ideas backlog')}
228
- `
229
- }
230
-
231
- function formatCleanup(filesRemoved, tasksArchived, spaceFeed) {
232
- return `
233
- ${banners.cleanup}
234
-
235
- ${colors.text('🗑️ Files removed:')} ${colors.success.bold(filesRemoved)}
236
- ${colors.text('📦 Tasks archived:')} ${colors.success.bold(tasksArchived)}
237
- ${colors.text('💾 Space freed:')} ${colors.success.bold(spaceFeed + ' MB')}
238
-
239
- ${colors.celebrate('✨ Your project is clean and lean!')}
240
- `
241
- }
242
-
243
- function formatRecap(data) {
244
- const divider = colors.primary('━'.repeat(40))
245
-
246
- return `
247
- ${divider}
248
- ${colors.primary.bold('📊 PROJECT RECAP')}
249
- ${divider}
250
-
251
- ${colors.text('🎯 Current focus:')} ${data.currentTask || colors.dim('No active task')}
252
- ${colors.text('🚀 Shipped this week:')} ${colors.success.bold(data.shippedCount)}
253
- ${colors.text('📝 Queued tasks:')} ${colors.info.bold(data.queuedCount)}
254
- ${colors.text('💡 Ideas captured:')} ${colors.idea.bold(data.ideasCount)}
255
-
256
- ${divider}
257
- ${colors.dim('Keep shipping! 🚀')}
258
- `
259
- }
260
-
261
- module.exports = {
262
- colors,
263
- frames,
264
- banners,
265
- animate,
266
- typeWriter,
267
- progressBar,
268
- sparkle,
269
- formatShip,
270
- formatFocus,
271
- formatSuccess,
272
- formatError,
273
- formatIdea,
274
- formatCleanup,
275
- formatRecap,
276
- catppuccin,
277
- }
@@ -1,401 +0,0 @@
1
- const { execSync } = require('child_process')
2
- const fs = require('fs').promises
3
-
4
- /**
5
- * GitIntegration - Git repository analysis and validation
6
- *
7
- * Provides git integration for prjct-cli to analyze repository state,
8
- * validate user claims against actual commits, and track project progress.
9
- *
10
- * @version 0.5.0
11
- */
12
- class GitIntegration {
13
- constructor(projectPath = process.cwd()) {
14
- this.projectPath = projectPath
15
- }
16
-
17
- /**
18
- * Check if current directory is a git repository
19
- * @returns {Promise<boolean>} True if git repo exists
20
- */
21
- async isGitRepo() {
22
- try {
23
- execSync('git rev-parse --git-dir', {
24
- cwd: this.projectPath,
25
- stdio: 'ignore',
26
- })
27
- return true
28
- } catch {
29
- return false
30
- }
31
- }
32
-
33
- /**
34
- * Get information about the last commit
35
- * @returns {Promise<Object|null>} Commit info or null if no commits
36
- */
37
- async getLastCommit() {
38
- if (!(await this.isGitRepo())) {
39
- return null
40
- }
41
-
42
- try {
43
- const output = execSync(
44
- 'git log -1 --format="%H|%s|%ar|%an"',
45
- {
46
- cwd: this.projectPath,
47
- encoding: 'utf-8',
48
- },
49
- )
50
-
51
- const [hash, message, timeAgo, author] = output.trim().split('|')
52
-
53
- return {
54
- hash: hash.substring(0, 7), // Short hash
55
- fullHash: hash,
56
- message,
57
- timeAgo,
58
- author,
59
- }
60
- } catch {
61
- return null // No commits yet
62
- }
63
- }
64
-
65
- /**
66
- * Get working directory status
67
- * @returns {Promise<Object|null>} Status info or null if not git repo
68
- */
69
- async getWorkingDirStatus() {
70
- if (!(await this.isGitRepo())) {
71
- return null
72
- }
73
-
74
- try {
75
- const status = execSync('git status --porcelain', {
76
- cwd: this.projectPath,
77
- encoding: 'utf-8',
78
- })
79
-
80
- const lines = status.trim().split('\n').filter(Boolean)
81
-
82
- const modified = lines.filter(
83
- l => l.startsWith(' M') || l.startsWith('M'),
84
- ).length
85
- const added = lines.filter(
86
- l => l.startsWith('A') || l.startsWith('??'),
87
- ).length
88
- const deleted = lines.filter(
89
- l => l.startsWith(' D') || l.startsWith('D'),
90
- ).length
91
- const renamed = lines.filter(l => l.startsWith('R')).length
92
-
93
- return {
94
- modified,
95
- added,
96
- deleted,
97
- renamed,
98
- totalChanges: lines.length,
99
- isClean: lines.length === 0,
100
- files: lines.map(l => l.substring(3)), // Remove status prefix
101
- }
102
- } catch {
103
- return null
104
- }
105
- }
106
-
107
- /**
108
- * Get diff summary between HEAD and working directory
109
- * @returns {Promise<Object|null>} Diff summary or null
110
- */
111
- async getDiffSummary() {
112
- if (!(await this.isGitRepo())) {
113
- return null
114
- }
115
-
116
- try {
117
- const diff = execSync('git diff HEAD --name-only', {
118
- cwd: this.projectPath,
119
- encoding: 'utf-8',
120
- })
121
-
122
- const files = diff.trim().split('\n').filter(Boolean)
123
-
124
- return {
125
- files,
126
- count: files.length,
127
- }
128
- } catch {
129
- return null
130
- }
131
- }
132
-
133
- /**
134
- * Get files changed since a specific time
135
- * @param {Date|number} since - Timestamp or Date object
136
- * @returns {Promise<Array>} Array of changed files
137
- */
138
- async getChangesSince(since) {
139
- if (!(await this.isGitRepo())) {
140
- return []
141
- }
142
-
143
- try {
144
- const timestamp =
145
- since instanceof Date ? since.toISOString() : new Date(since).toISOString()
146
-
147
- const files = execSync(
148
- `git log --since="${timestamp}" --name-only --pretty=format:`,
149
- {
150
- cwd: this.projectPath,
151
- encoding: 'utf-8',
152
- },
153
- )
154
-
155
- return [...new Set(files.trim().split('\n').filter(Boolean))]
156
- } catch {
157
- return []
158
- }
159
- }
160
-
161
- /**
162
- * Validate user claim against git state
163
- * @param {string} claim - User's claim (e.g., "login is complete")
164
- * @returns {Promise<Object>} Validation result
165
- */
166
- async validateUserClaim(claim) {
167
- if (!(await this.isGitRepo())) {
168
- return {
169
- valid: true,
170
- warning: null,
171
- note: 'Not a git repository - cannot validate against commits',
172
- }
173
- }
174
-
175
- const lastCommit = await this.getLastCommit()
176
- const workingStatus = await this.getWorkingDirStatus()
177
-
178
- if (!lastCommit) {
179
- return {
180
- valid: true,
181
- warning: null,
182
- note: 'No commits yet - cannot validate',
183
- }
184
- }
185
-
186
- // Extract keywords from claim
187
- const keywords = this.extractKeywords(claim)
188
- const completionClaimed = /complete|done|finished|ready|shipped/i.test(claim)
189
-
190
- // Check if keywords appear in last commit
191
- const inLastCommit = keywords.some(keyword =>
192
- lastCommit.message.toLowerCase().includes(keyword),
193
- )
194
-
195
- // Check if there are uncommitted changes
196
- const hasUncommittedChanges = !workingStatus.isClean
197
-
198
- // Validation logic
199
- if (completionClaimed && !inLastCommit && hasUncommittedChanges) {
200
- return {
201
- valid: false,
202
- warning: `⚠️ Discrepancy detected: You claim "${claim}" but it's not in the last commit`,
203
- details: {
204
- lastCommit: lastCommit.message,
205
- uncommittedFiles: workingStatus.totalChanges,
206
- suggestion:
207
- 'Consider committing your changes if the work is truly complete',
208
- },
209
- }
210
- }
211
-
212
- if (completionClaimed && !inLastCommit && !hasUncommittedChanges) {
213
- return {
214
- valid: true,
215
- warning: `ℹ️ Note: "${claim}" not mentioned in recent commits`,
216
- details: {
217
- lastCommit: lastCommit.message,
218
- note: 'Work may have been completed in earlier commits',
219
- },
220
- }
221
- }
222
-
223
- return {
224
- valid: true,
225
- warning: null,
226
- note: inLastCommit
227
- ? `✅ Confirmed in last commit: "${lastCommit.message}"`
228
- : null,
229
- }
230
- }
231
-
232
- /**
233
- * Extract meaningful keywords from a claim
234
- * @param {string} claim - User's claim
235
- * @returns {Array<string>} Extracted keywords
236
- */
237
- extractKeywords(claim) {
238
- // Remove common words
239
- const stopWords = new Set([
240
- 'the',
241
- 'is',
242
- 'are',
243
- 'was',
244
- 'were',
245
- 'a',
246
- 'an',
247
- 'and',
248
- 'or',
249
- 'but',
250
- 'in',
251
- 'on',
252
- 'at',
253
- 'to',
254
- 'for',
255
- 'of',
256
- 'with',
257
- 'by',
258
- 'from',
259
- 'up',
260
- 'about',
261
- 'into',
262
- 'through',
263
- 'during',
264
- 'before',
265
- 'after',
266
- 'above',
267
- 'below',
268
- 'between',
269
- 'under',
270
- 'complete',
271
- 'done',
272
- 'finished',
273
- 'ready',
274
- 'shipped',
275
- ])
276
-
277
- return claim
278
- .toLowerCase()
279
- .split(/\s+/)
280
- .filter(word => word.length > 2 && !stopWords.has(word))
281
- }
282
-
283
- /**
284
- * Get git statistics for analysis
285
- * @returns {Promise<Object>} Git statistics
286
- */
287
- async getGitStats() {
288
- if (!(await this.isGitRepo())) {
289
- return {
290
- isGitRepo: false,
291
- hasCommits: false,
292
- totalCommits: 0,
293
- contributors: [],
294
- lastCommit: null,
295
- workingStatus: null,
296
- }
297
- }
298
-
299
- try {
300
- // Total commits
301
- const totalCommits = parseInt(
302
- execSync('git rev-list --count HEAD', {
303
- cwd: this.projectPath,
304
- encoding: 'utf-8',
305
- }).trim(),
306
- )
307
-
308
- // Contributors
309
- const contributorsOutput = execSync(
310
- 'git log --format="%an" | sort -u',
311
- {
312
- cwd: this.projectPath,
313
- encoding: 'utf-8',
314
- },
315
- )
316
- const contributors = contributorsOutput.trim().split('\n').filter(Boolean)
317
-
318
- const lastCommit = await this.getLastCommit()
319
- const workingStatus = await this.getWorkingDirStatus()
320
-
321
- return {
322
- isGitRepo: true,
323
- hasCommits: totalCommits > 0,
324
- totalCommits,
325
- contributors,
326
- lastCommit,
327
- workingStatus,
328
- }
329
- } catch (error) {
330
- return {
331
- isGitRepo: true,
332
- hasCommits: false,
333
- totalCommits: 0,
334
- contributors: [],
335
- lastCommit: null,
336
- workingStatus: null,
337
- error: error.message,
338
- }
339
- }
340
- }
341
-
342
- /**
343
- * Check if a specific feature/file is in git history
344
- * @param {string} searchTerm - Term to search for
345
- * @returns {Promise<boolean>} True if found in history
346
- */
347
- async isInGitHistory(searchTerm) {
348
- if (!(await this.isGitRepo())) {
349
- return false
350
- }
351
-
352
- try {
353
- const result = execSync(
354
- `git log --all --grep="${searchTerm}" --oneline`,
355
- {
356
- cwd: this.projectPath,
357
- encoding: 'utf-8',
358
- },
359
- )
360
-
361
- return result.trim().length > 0
362
- } catch {
363
- return false
364
- }
365
- }
366
-
367
- /**
368
- * Get branch information
369
- * @returns {Promise<Object|null>} Branch info or null
370
- */
371
- async getBranchInfo() {
372
- if (!(await this.isGitRepo())) {
373
- return null
374
- }
375
-
376
- try {
377
- const currentBranch = execSync('git branch --show-current', {
378
- cwd: this.projectPath,
379
- encoding: 'utf-8',
380
- }).trim()
381
-
382
- const allBranches = execSync('git branch --list', {
383
- cwd: this.projectPath,
384
- encoding: 'utf-8',
385
- })
386
- .trim()
387
- .split('\n')
388
- .map(b => b.trim().replace('* ', ''))
389
-
390
- return {
391
- current: currentBranch,
392
- all: allBranches,
393
- count: allBranches.length,
394
- }
395
- } catch {
396
- return null
397
- }
398
- }
399
- }
400
-
401
- module.exports = new GitIntegration()