prjct-cli 0.44.1 → 0.45.3
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 +114 -0
- package/bin/prjct.ts +131 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +287 -29
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +49 -8
- package/core/commands/commands.ts +60 -23
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +14 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -12
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +143 -0
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +170 -329
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -13
- package/core/storage/metrics-storage.ts +320 -0
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recent Tool - Find "hot" files based on git history
|
|
3
|
+
*
|
|
4
|
+
* Identifies recently and frequently modified files,
|
|
5
|
+
* which are likely to be relevant for current work.
|
|
6
|
+
*
|
|
7
|
+
* @module context-tools/recent-tool
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { exec as execCallback } from 'node:child_process'
|
|
12
|
+
import { promisify } from 'node:util'
|
|
13
|
+
import type { HotFile, RecentToolOutput } from './types'
|
|
14
|
+
|
|
15
|
+
const exec = promisify(execCallback)
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Constants
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Files to ignore in recent analysis
|
|
23
|
+
*/
|
|
24
|
+
const IGNORE_PATTERNS = [
|
|
25
|
+
'package-lock.json',
|
|
26
|
+
'yarn.lock',
|
|
27
|
+
'pnpm-lock.yaml',
|
|
28
|
+
'bun.lockb',
|
|
29
|
+
'.gitignore',
|
|
30
|
+
'.env',
|
|
31
|
+
'.env.local',
|
|
32
|
+
'*.md',
|
|
33
|
+
'CHANGELOG.md',
|
|
34
|
+
'LICENSE',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Main Functions
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get recently modified files based on git history
|
|
43
|
+
*
|
|
44
|
+
* @param projectPath - Project root path
|
|
45
|
+
* @param options - Analysis options
|
|
46
|
+
* @returns Hot files sorted by heat score
|
|
47
|
+
*/
|
|
48
|
+
export async function getRecentFiles(
|
|
49
|
+
projectPath: string = process.cwd(),
|
|
50
|
+
options: {
|
|
51
|
+
commits?: number // Number of commits to analyze (default 30)
|
|
52
|
+
branch?: boolean // Only files changed in current branch vs main
|
|
53
|
+
maxFiles?: number // Max files to return (default 50)
|
|
54
|
+
} = {}
|
|
55
|
+
): Promise<RecentToolOutput> {
|
|
56
|
+
const commits = options.commits ?? 30
|
|
57
|
+
const maxFiles = options.maxFiles ?? 50
|
|
58
|
+
const branchOnly = options.branch ?? false
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
let hotFiles: HotFile[] = []
|
|
62
|
+
let branchOnlyFiles: string[] = []
|
|
63
|
+
let analysisWindow = `${commits} commits`
|
|
64
|
+
|
|
65
|
+
if (branchOnly) {
|
|
66
|
+
// Get files changed only in current branch
|
|
67
|
+
const result = await getBranchOnlyFiles(projectPath)
|
|
68
|
+
hotFiles = result.hotFiles
|
|
69
|
+
branchOnlyFiles = result.branchOnlyFiles
|
|
70
|
+
analysisWindow = result.analysisWindow
|
|
71
|
+
} else {
|
|
72
|
+
// Get files from recent commits
|
|
73
|
+
hotFiles = await getHotFilesFromCommits(projectPath, commits)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Filter and limit
|
|
77
|
+
hotFiles = hotFiles.filter((f) => !shouldIgnore(f.path)).slice(0, maxFiles)
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
hotFiles,
|
|
81
|
+
branchOnlyFiles,
|
|
82
|
+
metrics: {
|
|
83
|
+
commitsAnalyzed: commits,
|
|
84
|
+
totalFilesChanged: hotFiles.length,
|
|
85
|
+
filesReturned: Math.min(hotFiles.length, maxFiles),
|
|
86
|
+
analysisWindow,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
} catch (_error) {
|
|
90
|
+
// Git not available or not a repo
|
|
91
|
+
return {
|
|
92
|
+
hotFiles: [],
|
|
93
|
+
branchOnlyFiles: [],
|
|
94
|
+
metrics: {
|
|
95
|
+
commitsAnalyzed: 0,
|
|
96
|
+
totalFilesChanged: 0,
|
|
97
|
+
filesReturned: 0,
|
|
98
|
+
analysisWindow: 'N/A (git error)',
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Helper Functions
|
|
106
|
+
// =============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get hot files from recent commits
|
|
110
|
+
*/
|
|
111
|
+
async function getHotFilesFromCommits(projectPath: string, commits: number): Promise<HotFile[]> {
|
|
112
|
+
// Get file change counts and last modified times
|
|
113
|
+
const { stdout } = await exec(
|
|
114
|
+
`git log -${commits} --pretty=format:"%ct" --name-only | awk '
|
|
115
|
+
/^[0-9]+$/ { timestamp=$1; next }
|
|
116
|
+
NF {
|
|
117
|
+
count[$0]++
|
|
118
|
+
if (!lastmod[$0] || timestamp > lastmod[$0]) lastmod[$0]=timestamp
|
|
119
|
+
}
|
|
120
|
+
END {
|
|
121
|
+
for (f in count) print count[f], lastmod[f], f
|
|
122
|
+
}
|
|
123
|
+
' | sort -rn`,
|
|
124
|
+
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const hotFiles: HotFile[] = []
|
|
128
|
+
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
129
|
+
const now = Math.floor(Date.now() / 1000)
|
|
130
|
+
|
|
131
|
+
// Find max changes for normalization
|
|
132
|
+
let maxChanges = 1
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
135
|
+
if (match) {
|
|
136
|
+
const changes = parseInt(match[1], 10)
|
|
137
|
+
if (changes > maxChanges) maxChanges = changes
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
143
|
+
if (!match) continue
|
|
144
|
+
|
|
145
|
+
const changes = parseInt(match[1], 10)
|
|
146
|
+
const timestamp = parseInt(match[2], 10)
|
|
147
|
+
const filePath = match[3]
|
|
148
|
+
|
|
149
|
+
const secondsAgo = now - timestamp
|
|
150
|
+
const daysAgo = Math.floor(secondsAgo / 86400)
|
|
151
|
+
const hoursAgo = Math.floor(secondsAgo / 3600)
|
|
152
|
+
|
|
153
|
+
// Calculate heat score (combination of recency and frequency)
|
|
154
|
+
const recencyScore = Math.max(0, 1 - daysAgo / 30) // Decay over 30 days
|
|
155
|
+
const frequencyScore = changes / maxChanges
|
|
156
|
+
const heatScore = recencyScore * 0.6 + frequencyScore * 0.4
|
|
157
|
+
|
|
158
|
+
// Format last changed
|
|
159
|
+
let lastChanged: string
|
|
160
|
+
if (hoursAgo < 1) {
|
|
161
|
+
lastChanged = 'just now'
|
|
162
|
+
} else if (hoursAgo < 24) {
|
|
163
|
+
lastChanged = `${hoursAgo}h ago`
|
|
164
|
+
} else if (daysAgo < 7) {
|
|
165
|
+
lastChanged = `${daysAgo}d ago`
|
|
166
|
+
} else if (daysAgo < 30) {
|
|
167
|
+
lastChanged = `${Math.floor(daysAgo / 7)}w ago`
|
|
168
|
+
} else {
|
|
169
|
+
lastChanged = `${Math.floor(daysAgo / 30)}mo ago`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
hotFiles.push({
|
|
173
|
+
path: filePath,
|
|
174
|
+
changes,
|
|
175
|
+
heatScore: Math.round(heatScore * 100) / 100,
|
|
176
|
+
lastChanged,
|
|
177
|
+
lastChangedAt: new Date(timestamp * 1000).toISOString(),
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sort by heat score
|
|
182
|
+
return hotFiles.sort((a, b) => b.heatScore - a.heatScore)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get files changed only in current branch vs main
|
|
187
|
+
*/
|
|
188
|
+
async function getBranchOnlyFiles(projectPath: string): Promise<{
|
|
189
|
+
hotFiles: HotFile[]
|
|
190
|
+
branchOnlyFiles: string[]
|
|
191
|
+
analysisWindow: string
|
|
192
|
+
}> {
|
|
193
|
+
// Get current branch
|
|
194
|
+
const { stdout: branchOutput } = await exec('git branch --show-current', {
|
|
195
|
+
cwd: projectPath,
|
|
196
|
+
})
|
|
197
|
+
const _currentBranch = branchOutput.trim()
|
|
198
|
+
|
|
199
|
+
// Determine base branch (main or master)
|
|
200
|
+
let baseBranch = 'main'
|
|
201
|
+
try {
|
|
202
|
+
await exec('git rev-parse --verify main', { cwd: projectPath })
|
|
203
|
+
} catch {
|
|
204
|
+
baseBranch = 'master'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Get files changed in this branch
|
|
208
|
+
const { stdout: diffOutput } = await exec(`git diff --name-only ${baseBranch}...HEAD`, {
|
|
209
|
+
cwd: projectPath,
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const branchOnlyFiles = diffOutput.trim().split('\n').filter(Boolean)
|
|
213
|
+
|
|
214
|
+
// Get change counts for these files in the branch
|
|
215
|
+
const { stdout: logOutput } = await exec(
|
|
216
|
+
`git log ${baseBranch}..HEAD --pretty=format:"%ct" --name-only | awk '
|
|
217
|
+
/^[0-9]+$/ { timestamp=$1; next }
|
|
218
|
+
NF {
|
|
219
|
+
count[$0]++
|
|
220
|
+
if (!lastmod[$0] || timestamp > lastmod[$0]) lastmod[$0]=timestamp
|
|
221
|
+
}
|
|
222
|
+
END {
|
|
223
|
+
for (f in count) print count[f], lastmod[f], f
|
|
224
|
+
}
|
|
225
|
+
'`,
|
|
226
|
+
{ cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
const hotFiles: HotFile[] = []
|
|
230
|
+
const lines = logOutput.trim().split('\n').filter(Boolean)
|
|
231
|
+
const now = Math.floor(Date.now() / 1000)
|
|
232
|
+
|
|
233
|
+
// Find max changes for normalization
|
|
234
|
+
let maxChanges = 1
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
237
|
+
if (match) {
|
|
238
|
+
const changes = parseInt(match[1], 10)
|
|
239
|
+
if (changes > maxChanges) maxChanges = changes
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
245
|
+
if (!match) continue
|
|
246
|
+
|
|
247
|
+
const changes = parseInt(match[1], 10)
|
|
248
|
+
const timestamp = parseInt(match[2], 10)
|
|
249
|
+
const filePath = match[3]
|
|
250
|
+
|
|
251
|
+
const secondsAgo = now - timestamp
|
|
252
|
+
const daysAgo = Math.floor(secondsAgo / 86400)
|
|
253
|
+
const hoursAgo = Math.floor(secondsAgo / 3600)
|
|
254
|
+
|
|
255
|
+
const recencyScore = Math.max(0, 1 - daysAgo / 14) // Faster decay for branch
|
|
256
|
+
const frequencyScore = changes / maxChanges
|
|
257
|
+
const heatScore = recencyScore * 0.5 + frequencyScore * 0.5
|
|
258
|
+
|
|
259
|
+
let lastChanged: string
|
|
260
|
+
if (hoursAgo < 1) {
|
|
261
|
+
lastChanged = 'just now'
|
|
262
|
+
} else if (hoursAgo < 24) {
|
|
263
|
+
lastChanged = `${hoursAgo}h ago`
|
|
264
|
+
} else {
|
|
265
|
+
lastChanged = `${daysAgo}d ago`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
hotFiles.push({
|
|
269
|
+
path: filePath,
|
|
270
|
+
changes,
|
|
271
|
+
heatScore: Math.round(heatScore * 100) / 100,
|
|
272
|
+
lastChanged,
|
|
273
|
+
lastChangedAt: new Date(timestamp * 1000).toISOString(),
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
hotFiles: hotFiles.sort((a, b) => b.heatScore - a.heatScore),
|
|
279
|
+
branchOnlyFiles,
|
|
280
|
+
analysisWindow: `${baseBranch}..HEAD`,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if a file should be ignored
|
|
286
|
+
*/
|
|
287
|
+
function shouldIgnore(filePath: string): boolean {
|
|
288
|
+
const fileName = filePath.split('/').pop() || ''
|
|
289
|
+
|
|
290
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
291
|
+
if (pattern.startsWith('*.')) {
|
|
292
|
+
// Extension pattern
|
|
293
|
+
if (fileName.endsWith(pattern.slice(1))) return true
|
|
294
|
+
} else {
|
|
295
|
+
// Exact match
|
|
296
|
+
if (fileName === pattern) return true
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// =============================================================================
|
|
304
|
+
// Exports
|
|
305
|
+
// =============================================================================
|
|
306
|
+
|
|
307
|
+
export default { getRecentFiles }
|