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,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexStorage - Persistent storage for ProjectIndex
|
|
3
|
+
*
|
|
4
|
+
* Stores:
|
|
5
|
+
* - project-index.json: Main index data
|
|
6
|
+
* - file-scores.json: Calculated scores
|
|
7
|
+
* - checksums.json: For detecting file changes
|
|
8
|
+
*
|
|
9
|
+
* Location: ~/.prjct-cli/projects/{projectId}/index/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import crypto from 'node:crypto'
|
|
13
|
+
import fs from 'node:fs/promises'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import pathManager from '../infrastructure/path-manager'
|
|
16
|
+
import { isNotFoundError } from '../types/fs'
|
|
17
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// TYPES
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export interface LanguageStats {
|
|
24
|
+
count: number // Number of files
|
|
25
|
+
totalLines: number // Total lines of code
|
|
26
|
+
totalSize: number // Total bytes
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ConfigFileEntry {
|
|
30
|
+
path: string
|
|
31
|
+
type: string // "package.json", "Cargo.toml", etc.
|
|
32
|
+
checksum: string
|
|
33
|
+
parsed?: Record<string, unknown>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DirectoryEntry {
|
|
37
|
+
path: string
|
|
38
|
+
type: 'source' | 'test' | 'config' | 'build' | 'vendor' | 'docs' | 'unknown'
|
|
39
|
+
fileCount: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ScoredFile {
|
|
43
|
+
path: string
|
|
44
|
+
score: number
|
|
45
|
+
size: number
|
|
46
|
+
mtime: string // ISO timestamp
|
|
47
|
+
categories?: string[] // Domain categories: ['payments', 'api', 'backend']
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DetectedPattern {
|
|
51
|
+
name: string // "monorepo", "api-first", "component-based"
|
|
52
|
+
confidence: number // 0-1
|
|
53
|
+
evidence: string[] // Files/dirs that evidence this pattern
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DetectedStack {
|
|
57
|
+
ecosystem: string // "JavaScript", "Python", "Rust", etc.
|
|
58
|
+
frameworks: string[] // Detected frameworks
|
|
59
|
+
hasTests: boolean
|
|
60
|
+
hasDocker: boolean
|
|
61
|
+
hasCi: boolean
|
|
62
|
+
buildTool: string | null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ProjectIndex {
|
|
66
|
+
version: string
|
|
67
|
+
projectPath: string
|
|
68
|
+
lastFullScan: string // ISO timestamp
|
|
69
|
+
lastIncrementalUpdate: string // ISO timestamp
|
|
70
|
+
|
|
71
|
+
// Language detection by extension
|
|
72
|
+
languages: Record<string, LanguageStats>
|
|
73
|
+
|
|
74
|
+
// Config files found
|
|
75
|
+
configFiles: ConfigFileEntry[]
|
|
76
|
+
|
|
77
|
+
// Directory structure (top-level relevant)
|
|
78
|
+
directories: DirectoryEntry[]
|
|
79
|
+
|
|
80
|
+
// Files with score > threshold
|
|
81
|
+
relevantFiles: ScoredFile[]
|
|
82
|
+
|
|
83
|
+
// Detected patterns
|
|
84
|
+
patterns: DetectedPattern[]
|
|
85
|
+
|
|
86
|
+
// Stack detection
|
|
87
|
+
detectedStack: DetectedStack
|
|
88
|
+
|
|
89
|
+
// Metrics
|
|
90
|
+
totalFiles: number
|
|
91
|
+
totalSize: number // Total bytes
|
|
92
|
+
totalLines: number // Total LOC
|
|
93
|
+
scanDuration: number // ms
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface FileChecksums {
|
|
97
|
+
version: string
|
|
98
|
+
lastUpdated: string
|
|
99
|
+
checksums: Record<string, string> // path -> checksum
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// DOMAIN & CATEGORY TYPES (for Smart Context Selection)
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A domain discovered by LLM analysis of the project
|
|
108
|
+
*/
|
|
109
|
+
export interface DomainDefinition {
|
|
110
|
+
name: string // "payments", "shipping", "inventory"
|
|
111
|
+
description: string // "Handles payment processing, Stripe integration"
|
|
112
|
+
keywords: string[] // ["stripe", "checkout", "billing"]
|
|
113
|
+
filePatterns: string[] // ["**/payments/**", "**/billing/**"]
|
|
114
|
+
fileCount: number // Number of files in this domain
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Discovered domains for a project
|
|
119
|
+
*/
|
|
120
|
+
export interface DiscoveredDomains {
|
|
121
|
+
version: string
|
|
122
|
+
projectId: string
|
|
123
|
+
domains: DomainDefinition[]
|
|
124
|
+
discoveredAt: string // ISO timestamp
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Category assignment for a single file
|
|
129
|
+
*/
|
|
130
|
+
export interface FileCategory {
|
|
131
|
+
path: string
|
|
132
|
+
categories: string[] // ['payments', 'users', 'api']
|
|
133
|
+
primaryDomain: string // 'payments'
|
|
134
|
+
confidence: number // 0-1
|
|
135
|
+
categorizedAt: string // ISO timestamp
|
|
136
|
+
method: 'llm' | 'heuristic' // How it was categorized
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Cache of file categorizations
|
|
141
|
+
*/
|
|
142
|
+
export interface CategoriesCache {
|
|
143
|
+
version: string
|
|
144
|
+
lastUpdate: string
|
|
145
|
+
fileCategories: FileCategory[]
|
|
146
|
+
domainIndex: Record<string, string[]> // domain -> file paths
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// DEFAULTS
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
export const INDEX_VERSION = '1.0.0'
|
|
154
|
+
|
|
155
|
+
export function getDefaultIndex(projectPath: string): ProjectIndex {
|
|
156
|
+
return {
|
|
157
|
+
version: INDEX_VERSION,
|
|
158
|
+
projectPath,
|
|
159
|
+
lastFullScan: '',
|
|
160
|
+
lastIncrementalUpdate: '',
|
|
161
|
+
languages: {},
|
|
162
|
+
configFiles: [],
|
|
163
|
+
directories: [],
|
|
164
|
+
relevantFiles: [],
|
|
165
|
+
patterns: [],
|
|
166
|
+
detectedStack: {
|
|
167
|
+
ecosystem: 'unknown',
|
|
168
|
+
frameworks: [],
|
|
169
|
+
hasTests: false,
|
|
170
|
+
hasDocker: false,
|
|
171
|
+
hasCi: false,
|
|
172
|
+
buildTool: null,
|
|
173
|
+
},
|
|
174
|
+
totalFiles: 0,
|
|
175
|
+
totalSize: 0,
|
|
176
|
+
totalLines: 0,
|
|
177
|
+
scanDuration: 0,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function getDefaultChecksums(): FileChecksums {
|
|
182
|
+
return {
|
|
183
|
+
version: INDEX_VERSION,
|
|
184
|
+
lastUpdated: '',
|
|
185
|
+
checksums: {},
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// INDEX STORAGE CLASS
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
class IndexStorage {
|
|
194
|
+
/**
|
|
195
|
+
* Get the index directory path for a project
|
|
196
|
+
*/
|
|
197
|
+
getIndexPath(projectId: string): string {
|
|
198
|
+
return path.join(pathManager.getGlobalProjectPath(projectId), 'index')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Ensure index directory exists
|
|
203
|
+
*/
|
|
204
|
+
async ensureIndexDir(projectId: string): Promise<string> {
|
|
205
|
+
const indexPath = this.getIndexPath(projectId)
|
|
206
|
+
await fs.mkdir(indexPath, { recursive: true })
|
|
207
|
+
return indexPath
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ==========================================================================
|
|
211
|
+
// PROJECT INDEX
|
|
212
|
+
// ==========================================================================
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Read the project index
|
|
216
|
+
*/
|
|
217
|
+
async readIndex(projectId: string): Promise<ProjectIndex | null> {
|
|
218
|
+
const filePath = path.join(this.getIndexPath(projectId), 'project-index.json')
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
222
|
+
const index = JSON.parse(content) as ProjectIndex
|
|
223
|
+
|
|
224
|
+
// Version check
|
|
225
|
+
if (index.version !== INDEX_VERSION) {
|
|
226
|
+
// Index is outdated, return null to trigger full rescan
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return index
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (isNotFoundError(error)) {
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
throw error
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Write the project index
|
|
241
|
+
*/
|
|
242
|
+
async writeIndex(projectId: string, index: ProjectIndex): Promise<void> {
|
|
243
|
+
await this.ensureIndexDir(projectId)
|
|
244
|
+
const filePath = path.join(this.getIndexPath(projectId), 'project-index.json')
|
|
245
|
+
await fs.writeFile(filePath, JSON.stringify(index, null, 2), 'utf-8')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if index exists and is valid
|
|
250
|
+
*/
|
|
251
|
+
async hasValidIndex(projectId: string): Promise<boolean> {
|
|
252
|
+
const index = await this.readIndex(projectId)
|
|
253
|
+
return index !== null && index.lastFullScan !== ''
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ==========================================================================
|
|
257
|
+
// FILE CHECKSUMS
|
|
258
|
+
// ==========================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Read file checksums
|
|
262
|
+
*/
|
|
263
|
+
async readChecksums(projectId: string): Promise<FileChecksums> {
|
|
264
|
+
const filePath = path.join(this.getIndexPath(projectId), 'checksums.json')
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
268
|
+
return JSON.parse(content) as FileChecksums
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (isNotFoundError(error)) {
|
|
271
|
+
return getDefaultChecksums()
|
|
272
|
+
}
|
|
273
|
+
throw error
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Write file checksums
|
|
279
|
+
*/
|
|
280
|
+
async writeChecksums(projectId: string, checksums: FileChecksums): Promise<void> {
|
|
281
|
+
await this.ensureIndexDir(projectId)
|
|
282
|
+
const filePath = path.join(this.getIndexPath(projectId), 'checksums.json')
|
|
283
|
+
await fs.writeFile(filePath, JSON.stringify(checksums, null, 2), 'utf-8')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Calculate checksum for a file
|
|
288
|
+
*/
|
|
289
|
+
async calculateChecksum(filePath: string): Promise<string> {
|
|
290
|
+
try {
|
|
291
|
+
const content = await fs.readFile(filePath)
|
|
292
|
+
return crypto.createHash('md5').update(content).digest('hex')
|
|
293
|
+
} catch {
|
|
294
|
+
return ''
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Detect changed files by comparing checksums
|
|
300
|
+
*/
|
|
301
|
+
async detectChangedFiles(
|
|
302
|
+
projectId: string,
|
|
303
|
+
currentFiles: Map<string, string> // path -> checksum
|
|
304
|
+
): Promise<{
|
|
305
|
+
added: string[]
|
|
306
|
+
modified: string[]
|
|
307
|
+
deleted: string[]
|
|
308
|
+
}> {
|
|
309
|
+
const stored = await this.readChecksums(projectId)
|
|
310
|
+
const storedChecksums = stored.checksums
|
|
311
|
+
|
|
312
|
+
const added: string[] = []
|
|
313
|
+
const modified: string[] = []
|
|
314
|
+
const deleted: string[] = []
|
|
315
|
+
|
|
316
|
+
// Check current files against stored
|
|
317
|
+
for (const [filePath, checksum] of currentFiles) {
|
|
318
|
+
if (!(filePath in storedChecksums)) {
|
|
319
|
+
added.push(filePath)
|
|
320
|
+
} else if (storedChecksums[filePath] !== checksum) {
|
|
321
|
+
modified.push(filePath)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check for deleted files
|
|
326
|
+
for (const filePath of Object.keys(storedChecksums)) {
|
|
327
|
+
if (!currentFiles.has(filePath)) {
|
|
328
|
+
deleted.push(filePath)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { added, modified, deleted }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ==========================================================================
|
|
336
|
+
// FILE SCORES
|
|
337
|
+
// ==========================================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Read file scores
|
|
341
|
+
*/
|
|
342
|
+
async readScores(projectId: string): Promise<ScoredFile[]> {
|
|
343
|
+
const filePath = path.join(this.getIndexPath(projectId), 'file-scores.json')
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
347
|
+
const data = JSON.parse(content) as { scores: ScoredFile[] }
|
|
348
|
+
return data.scores || []
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (isNotFoundError(error)) {
|
|
351
|
+
return []
|
|
352
|
+
}
|
|
353
|
+
throw error
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Write file scores
|
|
359
|
+
*/
|
|
360
|
+
async writeScores(projectId: string, scores: ScoredFile[]): Promise<void> {
|
|
361
|
+
await this.ensureIndexDir(projectId)
|
|
362
|
+
const filePath = path.join(this.getIndexPath(projectId), 'file-scores.json')
|
|
363
|
+
const data = {
|
|
364
|
+
version: INDEX_VERSION,
|
|
365
|
+
lastUpdated: getTimestamp(),
|
|
366
|
+
scores,
|
|
367
|
+
}
|
|
368
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ==========================================================================
|
|
372
|
+
// UTILITY METHODS
|
|
373
|
+
// ==========================================================================
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Clear all index data for a project
|
|
377
|
+
*/
|
|
378
|
+
async clearIndex(projectId: string): Promise<void> {
|
|
379
|
+
const indexPath = this.getIndexPath(projectId)
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const files = await fs.readdir(indexPath)
|
|
383
|
+
await Promise.all(files.map((file) => fs.unlink(path.join(indexPath, file))))
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (!isNotFoundError(error)) {
|
|
386
|
+
throw error
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get index age in hours
|
|
393
|
+
*/
|
|
394
|
+
async getIndexAge(projectId: string): Promise<number> {
|
|
395
|
+
const index = await this.readIndex(projectId)
|
|
396
|
+
if (!index || !index.lastFullScan) {
|
|
397
|
+
return Infinity
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const lastScan = new Date(index.lastFullScan)
|
|
401
|
+
const now = new Date()
|
|
402
|
+
return (now.getTime() - lastScan.getTime()) / (1000 * 60 * 60)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ==========================================================================
|
|
406
|
+
// DISCOVERED DOMAINS
|
|
407
|
+
// ==========================================================================
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Read discovered domains for a project
|
|
411
|
+
*/
|
|
412
|
+
async readDomains(projectId: string): Promise<DiscoveredDomains | null> {
|
|
413
|
+
const filePath = path.join(this.getIndexPath(projectId), 'domains.json')
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
417
|
+
const domains = JSON.parse(content) as DiscoveredDomains
|
|
418
|
+
|
|
419
|
+
// Version check
|
|
420
|
+
if (domains.version !== INDEX_VERSION) {
|
|
421
|
+
return null
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return domains
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (isNotFoundError(error)) {
|
|
427
|
+
return null
|
|
428
|
+
}
|
|
429
|
+
throw error
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Write discovered domains
|
|
435
|
+
*/
|
|
436
|
+
async writeDomains(projectId: string, domains: DiscoveredDomains): Promise<void> {
|
|
437
|
+
await this.ensureIndexDir(projectId)
|
|
438
|
+
const filePath = path.join(this.getIndexPath(projectId), 'domains.json')
|
|
439
|
+
await fs.writeFile(filePath, JSON.stringify(domains, null, 2), 'utf-8')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ==========================================================================
|
|
443
|
+
// CATEGORIES CACHE
|
|
444
|
+
// ==========================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Read categories cache
|
|
448
|
+
*/
|
|
449
|
+
async readCategories(projectId: string): Promise<CategoriesCache | null> {
|
|
450
|
+
const filePath = path.join(this.getIndexPath(projectId), 'categories-cache.json')
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
454
|
+
const cache = JSON.parse(content) as CategoriesCache
|
|
455
|
+
|
|
456
|
+
// Version check
|
|
457
|
+
if (cache.version !== INDEX_VERSION) {
|
|
458
|
+
return null
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return cache
|
|
462
|
+
} catch (error) {
|
|
463
|
+
if (isNotFoundError(error)) {
|
|
464
|
+
return null
|
|
465
|
+
}
|
|
466
|
+
throw error
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Write categories cache
|
|
472
|
+
*/
|
|
473
|
+
async writeCategories(projectId: string, cache: CategoriesCache): Promise<void> {
|
|
474
|
+
await this.ensureIndexDir(projectId)
|
|
475
|
+
const filePath = path.join(this.getIndexPath(projectId), 'categories-cache.json')
|
|
476
|
+
await fs.writeFile(filePath, JSON.stringify(cache, null, 2), 'utf-8')
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Get file categories for specific paths
|
|
481
|
+
*/
|
|
482
|
+
async getFileCategories(projectId: string, paths: string[]): Promise<Map<string, string[]>> {
|
|
483
|
+
const cache = await this.readCategories(projectId)
|
|
484
|
+
const result = new Map<string, string[]>()
|
|
485
|
+
|
|
486
|
+
if (!cache) {
|
|
487
|
+
return result
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const pathSet = new Set(paths)
|
|
491
|
+
for (const fc of cache.fileCategories) {
|
|
492
|
+
if (pathSet.has(fc.path)) {
|
|
493
|
+
result.set(fc.path, fc.categories)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return result
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Get files by domain
|
|
502
|
+
*/
|
|
503
|
+
async getFilesByDomain(projectId: string, domain: string): Promise<string[]> {
|
|
504
|
+
const cache = await this.readCategories(projectId)
|
|
505
|
+
if (!cache) {
|
|
506
|
+
return []
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return cache.domainIndex[domain] || []
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export const indexStorage = new IndexStorage()
|
|
514
|
+
export default indexStorage
|
package/core/storage/index.ts
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* For future per-entity storage
|
|
15
15
|
* - getStorage(projectId): data/{entity}s/{id}.json
|
|
16
16
|
*
|
|
17
|
+
* 3. INDEX STORAGE (New)
|
|
18
|
+
* For persistent project scanning with scoring
|
|
19
|
+
* - indexStorage: index/project-index.json, index/checksums.json
|
|
20
|
+
*
|
|
17
21
|
* Structure:
|
|
18
22
|
* ~/.prjct-cli/projects/{projectId}/
|
|
19
23
|
* ├── storage/ # Aggregate JSON (source of truth)
|
|
@@ -27,6 +31,10 @@
|
|
|
27
31
|
* │ ├── next.md
|
|
28
32
|
* │ ├── ideas.md
|
|
29
33
|
* │ └── shipped.md
|
|
34
|
+
* ├── index/ # Project index (persistent scan)
|
|
35
|
+
* │ ├── project-index.json
|
|
36
|
+
* │ ├── file-scores.json
|
|
37
|
+
* │ └── checksums.json
|
|
30
38
|
* ├── data/ # Granular JSON (legacy/future)
|
|
31
39
|
* │ └── ...
|
|
32
40
|
* └── sync/ # Backend sync
|
|
@@ -34,23 +42,43 @@
|
|
|
34
42
|
* └── last-sync.json
|
|
35
43
|
*/
|
|
36
44
|
|
|
37
|
-
// ========== AGGREGATE STORAGE (Recommended) ==========
|
|
38
|
-
export { StorageManager } from './storage-manager'
|
|
39
|
-
export { stateStorage } from './state-storage'
|
|
40
|
-
export { queueStorage } from './queue-storage'
|
|
41
|
-
export { ideasStorage } from './ideas-storage'
|
|
42
|
-
export { shippedStorage } from './shipped-storage'
|
|
43
|
-
|
|
44
|
-
// ========== GRANULAR STORAGE (Legacy) ==========
|
|
45
|
-
export { getStorage, default } from './storage'
|
|
46
|
-
|
|
47
45
|
// Re-export types from canonical location
|
|
48
46
|
export type {
|
|
49
|
-
|
|
47
|
+
AgentUsage,
|
|
48
|
+
DailyStats,
|
|
50
49
|
Idea,
|
|
51
|
-
IdeasJson,
|
|
52
|
-
IdeaStatus,
|
|
53
50
|
IdeaPriority,
|
|
51
|
+
IdeaStatus,
|
|
52
|
+
IdeasJson,
|
|
53
|
+
MetricsJson,
|
|
54
54
|
ShippedFeature,
|
|
55
55
|
ShippedJson,
|
|
56
|
+
Storage,
|
|
56
57
|
} from '../types'
|
|
58
|
+
export { ideasStorage } from './ideas-storage'
|
|
59
|
+
export type {
|
|
60
|
+
CategoriesCache,
|
|
61
|
+
ConfigFileEntry,
|
|
62
|
+
DetectedPattern,
|
|
63
|
+
DetectedStack,
|
|
64
|
+
DirectoryEntry,
|
|
65
|
+
DiscoveredDomains,
|
|
66
|
+
// Smart Context Selection types (PRJ-85)
|
|
67
|
+
DomainDefinition,
|
|
68
|
+
FileCategory,
|
|
69
|
+
FileChecksums,
|
|
70
|
+
LanguageStats,
|
|
71
|
+
ProjectIndex,
|
|
72
|
+
ScoredFile,
|
|
73
|
+
} from './index-storage'
|
|
74
|
+
// ========== INDEX STORAGE (Project scanning) ==========
|
|
75
|
+
export { getDefaultChecksums, getDefaultIndex, INDEX_VERSION, indexStorage } from './index-storage'
|
|
76
|
+
export { metricsStorage } from './metrics-storage'
|
|
77
|
+
export { queueStorage } from './queue-storage'
|
|
78
|
+
export { shippedStorage } from './shipped-storage'
|
|
79
|
+
export { stateStorage } from './state-storage'
|
|
80
|
+
|
|
81
|
+
// ========== GRANULAR STORAGE (Legacy) ==========
|
|
82
|
+
export { default, getStorage } from './storage'
|
|
83
|
+
// ========== AGGREGATE STORAGE (Recommended) ==========
|
|
84
|
+
export { StorageManager } from './storage-manager'
|