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.
- package/CHANGELOG.md +220 -7
- package/CLAUDE.md +476 -55
- package/README.md +48 -55
- package/bin/prjct +170 -225
- 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 +597 -0
- package/core/commands.js +2046 -2028
- 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} +4 -2
- 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 +5 -12
- package/templates/agents/AGENTS.md +151 -99
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +37 -233
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +44 -0
- package/templates/commands/cleanup.md +24 -84
- package/templates/commands/design.md +20 -95
- package/templates/commands/done.md +17 -180
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +58 -66
- package/templates/commands/git.md +35 -57
- package/templates/commands/help.md +18 -52
- package/templates/commands/idea.md +18 -34
- package/templates/commands/init.md +65 -257
- package/templates/commands/next.md +20 -60
- package/templates/commands/now.md +21 -23
- package/templates/commands/progress.md +40 -73
- package/templates/commands/recap.md +52 -75
- package/templates/commands/roadmap.md +30 -85
- package/templates/commands/ship.md +93 -126
- package/templates/commands/status.md +42 -0
- package/templates/commands/sync.md +19 -205
- package/templates/commands/task.md +19 -79
- package/templates/commands/test.md +25 -71
- package/templates/commands/workflow.md +20 -210
- package/core/agent-generator.js +0 -516
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/git-integration.js +0 -401
- 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 -42
- package/templates/agents/data.template.md +0 -41
- package/templates/agents/devops.template.md +0 -41
- package/templates/agents/fe.template.md +0 -42
- package/templates/agents/mobile.template.md +0 -41
- package/templates/agents/pm.template.md +0 -84
- package/templates/agents/qa.template.md +0 -54
- package/templates/agents/scribe.template.md +0 -95
- package/templates/agents/security.template.md +0 -41
- package/templates/agents/ux.template.md +0 -49
- package/templates/commands/context.md +0 -105
- package/templates/commands/stuck.md +0 -48
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
package/core/animations.js
DELETED
|
@@ -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
|
-
}
|
package/core/git-integration.js
DELETED
|
@@ -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()
|