prjct-cli 0.10.4 → 0.10.5
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 +17 -0
- package/core/context-sync.js +214 -69
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.5] - 2025-11-27
|
|
4
|
+
|
|
5
|
+
### Enhanced
|
|
6
|
+
|
|
7
|
+
- **Rich Project Context** - CLAUDE.md now includes comprehensive project info
|
|
8
|
+
- Quick reference table (project, stack, files, commits, contributors)
|
|
9
|
+
- Full tech stack details (languages, frameworks, dependencies)
|
|
10
|
+
- Project structure (directories)
|
|
11
|
+
- Agent summaries with roles and expertise
|
|
12
|
+
- Current task and priority queue (top 5)
|
|
13
|
+
- Active roadmap features
|
|
14
|
+
- Recent ideas (top 3)
|
|
15
|
+
- Git activity (last 5 commits)
|
|
16
|
+
- Deep dive section with file paths for detailed info
|
|
17
|
+
|
|
18
|
+
- **Reduced LLM Context Overhead** - Claude gets all key info in one file instead of reading multiple
|
|
19
|
+
|
|
3
20
|
## [0.10.4] - 2025-11-27
|
|
4
21
|
|
|
5
22
|
### Fixed
|
package/core/context-sync.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Context Sync - Generates dynamic project context for Claude
|
|
2
|
+
* Context Sync - Generates RICH dynamic project context for Claude
|
|
3
3
|
*
|
|
4
4
|
* Creates ~/.prjct-cli/projects/{id}/CLAUDE.md with:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
5
|
+
* - Full stack info (languages, frameworks, dependencies)
|
|
6
|
+
* - Project structure and stats
|
|
7
|
+
* - Agent expertise summaries
|
|
8
|
+
* - Current task and priority queue
|
|
9
|
+
* - Active features from roadmap
|
|
10
|
+
*
|
|
11
|
+
* GOAL: Give Claude ALL the context it needs WITHOUT extra file reads
|
|
9
12
|
*
|
|
10
13
|
* Called by: /p:sync, /p:analyze, /p:init
|
|
11
14
|
*/
|
|
@@ -15,17 +18,27 @@ const path = require('path')
|
|
|
15
18
|
const os = require('os')
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
|
-
* Generate project context file for Claude
|
|
19
|
-
*
|
|
21
|
+
* Generate RICH project context file for Claude
|
|
22
|
+
* Embeds key information so Claude doesn't need to read multiple files
|
|
20
23
|
*
|
|
21
24
|
* @param {string} projectPath - Local project path
|
|
22
25
|
* @param {string} projectId - Project ID from config
|
|
23
|
-
* @returns {Promise<{agents: string[], stack:
|
|
26
|
+
* @returns {Promise<{agents: string[], stack: object, currentTask: string|null}>}
|
|
24
27
|
*/
|
|
25
28
|
async function generateLocalContext(projectPath, projectId) {
|
|
26
29
|
const globalPath = path.join(os.homedir(), '.prjct-cli/projects', projectId)
|
|
30
|
+
const projectName = path.basename(projectPath)
|
|
31
|
+
|
|
32
|
+
// Helper to read files safely
|
|
33
|
+
const readSafe = async (p) => {
|
|
34
|
+
try {
|
|
35
|
+
return await fs.readFile(p, 'utf-8')
|
|
36
|
+
} catch {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
27
40
|
|
|
28
|
-
// 1. Read ALL
|
|
41
|
+
// 1. Read ALL data sources in parallel
|
|
29
42
|
const agentsDir = path.join(globalPath, 'agents')
|
|
30
43
|
let agentFiles = []
|
|
31
44
|
try {
|
|
@@ -35,91 +48,210 @@ async function generateLocalContext(projectPath, projectId) {
|
|
|
35
48
|
// No agents directory yet
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
const readSafe = async (p) => {
|
|
40
|
-
try {
|
|
41
|
-
return await fs.readFile(p, 'utf-8')
|
|
42
|
-
} catch {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const [repoSummary, now, next, roadmap] = await Promise.all([
|
|
51
|
+
const [repoSummary, now, next, roadmap, ideas] = await Promise.all([
|
|
48
52
|
readSafe(path.join(globalPath, 'analysis/repo-summary.md')),
|
|
49
53
|
readSafe(path.join(globalPath, 'core/now.md')),
|
|
50
54
|
readSafe(path.join(globalPath, 'core/next.md')),
|
|
51
|
-
readSafe(path.join(globalPath, 'planning/roadmap.md'))
|
|
55
|
+
readSafe(path.join(globalPath, 'planning/roadmap.md')),
|
|
56
|
+
readSafe(path.join(globalPath, 'planning/ideas.md'))
|
|
52
57
|
])
|
|
53
58
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
59
|
+
// 2. Extract RICH stack info from repo-summary
|
|
60
|
+
const stackInfo = extractFullStack(repoSummary)
|
|
61
|
+
|
|
62
|
+
// 3. Extract project structure from repo-summary
|
|
63
|
+
const projectStructure = extractProjectStructure(repoSummary)
|
|
64
|
+
|
|
65
|
+
// 4. Extract git stats
|
|
66
|
+
const gitStats = extractGitStats(repoSummary)
|
|
56
67
|
|
|
57
|
-
//
|
|
58
|
-
const
|
|
68
|
+
// 5. Read agent expertise (just the key parts, not full templates)
|
|
69
|
+
const agentExpertise = await extractAgentExpertise(agentsDir, agentFiles)
|
|
59
70
|
|
|
60
|
-
//
|
|
71
|
+
// 6. Extract current task
|
|
61
72
|
const currentTask = extractCurrentTask(now)
|
|
62
73
|
|
|
63
|
-
//
|
|
64
|
-
const nextTasks = extractTopTasks(next,
|
|
74
|
+
// 7. Extract priority queue (top 5)
|
|
75
|
+
const nextTasks = extractTopTasks(next, 5)
|
|
65
76
|
|
|
66
|
-
//
|
|
77
|
+
// 8. Extract active features from roadmap
|
|
67
78
|
const activeFeatures = extractActiveFeatures(roadmap)
|
|
68
79
|
|
|
69
|
-
//
|
|
70
|
-
const
|
|
71
|
-
|
|
80
|
+
// 9. Extract recent ideas
|
|
81
|
+
const recentIdeas = extractRecentIdeas(ideas)
|
|
82
|
+
|
|
83
|
+
// 10. Generate RICH CLAUDE.md content
|
|
84
|
+
const content = `# ${projectName} - Project Context
|
|
85
|
+
<!-- Auto-generated by /p:sync - DO NOT EDIT MANUALLY -->
|
|
72
86
|
<!-- projectId: ${projectId} -->
|
|
73
87
|
<!-- Last sync: ${new Date().toISOString()} -->
|
|
74
88
|
|
|
75
|
-
##
|
|
76
|
-
|
|
89
|
+
## Quick Reference
|
|
90
|
+
|
|
91
|
+
| Attribute | Value |
|
|
92
|
+
|-----------|-------|
|
|
93
|
+
| **Project** | ${projectName} |
|
|
94
|
+
| **Stack** | ${stackInfo.primary || 'Not detected'} |
|
|
95
|
+
| **Files** | ${projectStructure.fileCount || '?'} |
|
|
96
|
+
| **Commits** | ${gitStats.commits || '?'} |
|
|
97
|
+
| **Contributors** | ${gitStats.contributors || '?'} |
|
|
98
|
+
|
|
99
|
+
## Tech Stack
|
|
100
|
+
|
|
101
|
+
${stackInfo.full || 'Run /p:analyze to detect stack'}
|
|
102
|
+
|
|
103
|
+
## Project Structure
|
|
104
|
+
|
|
105
|
+
${projectStructure.directories?.length ? projectStructure.directories.map(d => `- \`${d}/\``).join('\n') : 'Run /p:analyze to detect structure'}
|
|
77
106
|
|
|
78
107
|
## Available Agents
|
|
79
|
-
${agents.length ? agents.map(a => `- ${a}`).join('\n') : 'None. Run /p:sync to generate.'}
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
${
|
|
109
|
+
${agentExpertise.length ? agentExpertise.map(a => `### ${a.name}
|
|
110
|
+
- **Role**: ${a.role}
|
|
111
|
+
- **Expertise**: ${a.expertise}
|
|
112
|
+
`).join('\n') : 'None. Run /p:sync to generate agents.'}
|
|
113
|
+
|
|
114
|
+
## Current Focus
|
|
115
|
+
|
|
116
|
+
${currentTask ? `**Active Task**: ${currentTask}` : 'No active task. Use /p:now to start.'}
|
|
117
|
+
|
|
118
|
+
## Priority Queue
|
|
119
|
+
|
|
120
|
+
${nextTasks.length ? nextTasks.map((t, i) => `${i + 1}. ${t}`).join('\n') : 'Empty queue. Use /p:next to add tasks.'}
|
|
83
121
|
|
|
84
|
-
##
|
|
85
|
-
${nextTasks.length ? nextTasks.map((t, i) => `${i + 1}. ${t}`).join('\n') : 'Empty queue.'}
|
|
122
|
+
## Active Features (Roadmap)
|
|
86
123
|
|
|
87
|
-
|
|
88
|
-
${activeFeatures.length ? activeFeatures.map(f => `- ${f}`).join('\n') : 'None in progress.'}
|
|
124
|
+
${activeFeatures.length ? activeFeatures.map(f => `- [ ] ${f}`).join('\n') : 'No features in progress.'}
|
|
89
125
|
|
|
90
|
-
##
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
126
|
+
## Recent Ideas
|
|
127
|
+
|
|
128
|
+
${recentIdeas.length ? recentIdeas.map(i => `- ${i}`).join('\n') : 'No recent ideas.'}
|
|
129
|
+
|
|
130
|
+
## Git Activity
|
|
131
|
+
|
|
132
|
+
${gitStats.recentCommits?.length ? gitStats.recentCommits.map(c => `- \`${c.hash}\` ${c.message}`).join('\n') : 'No recent commits.'}
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Deep Dive (Read When Needed)
|
|
137
|
+
|
|
138
|
+
For detailed information, read these files:
|
|
139
|
+
- \`~/.prjct-cli/projects/${projectId}/agents/*.md\` - Full agent templates with code patterns
|
|
140
|
+
- \`~/.prjct-cli/projects/${projectId}/analysis/repo-summary.md\` - Complete technical analysis
|
|
141
|
+
- \`~/.prjct-cli/projects/${projectId}/planning/roadmap.md\` - Full feature roadmap
|
|
142
|
+
- \`~/.prjct-cli/projects/${projectId}/memory/context.jsonl\` - Decision history
|
|
95
143
|
`
|
|
96
144
|
|
|
97
|
-
//
|
|
145
|
+
// 11. Write to global storage
|
|
98
146
|
const contextPath = path.join(globalPath, 'CLAUDE.md')
|
|
99
147
|
await fs.writeFile(contextPath, content, 'utf-8')
|
|
100
148
|
|
|
101
|
-
return { agents, stack, currentTask }
|
|
149
|
+
return { agents: agentFiles.map(f => f.replace('.md', '')), stack: stackInfo, currentTask }
|
|
102
150
|
}
|
|
103
151
|
|
|
104
152
|
/**
|
|
105
|
-
* Extract stack from repo-summary
|
|
153
|
+
* Extract FULL stack info from repo-summary
|
|
106
154
|
*/
|
|
107
|
-
function
|
|
108
|
-
if (!summary) return null
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
155
|
+
function extractFullStack(summary) {
|
|
156
|
+
if (!summary) return { primary: null, full: null }
|
|
157
|
+
|
|
158
|
+
const result = { primary: null, full: null, languages: [], frameworks: [], dependencies: [] }
|
|
159
|
+
|
|
160
|
+
// Extract languages (e.g., "### JavaScript/TypeScript")
|
|
161
|
+
const langMatch = summary.match(/###\s*(JavaScript|TypeScript|Python|Go|Rust|Ruby|Java|C#|PHP)/i)
|
|
162
|
+
if (langMatch) result.primary = langMatch[1]
|
|
163
|
+
|
|
164
|
+
// Extract dependencies
|
|
165
|
+
const depsMatch = summary.match(/\*\*Dependencies\*\*:\s*([^\n]+)/i)
|
|
166
|
+
if (depsMatch) result.dependencies = depsMatch[1].split(',').map(d => d.trim())
|
|
167
|
+
|
|
168
|
+
// Extract full stack section (between ## Stack Detected and next ##)
|
|
169
|
+
const stackSection = summary.match(/## Stack Detected\n([\s\S]*?)(?=\n## |$)/i)
|
|
170
|
+
if (stackSection && stackSection[1]) {
|
|
171
|
+
result.full = stackSection[1].trim()
|
|
120
172
|
}
|
|
121
173
|
|
|
122
|
-
return
|
|
174
|
+
return result
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extract project structure from repo-summary
|
|
179
|
+
*/
|
|
180
|
+
function extractProjectStructure(summary) {
|
|
181
|
+
if (!summary) return { fileCount: null, directories: [] }
|
|
182
|
+
|
|
183
|
+
const result = { fileCount: null, directories: [] }
|
|
184
|
+
|
|
185
|
+
// Extract file count
|
|
186
|
+
const fileMatch = summary.match(/\*\*Total Files\*\*:\s*(\d+)/i)
|
|
187
|
+
if (fileMatch) result.fileCount = parseInt(fileMatch[1])
|
|
188
|
+
|
|
189
|
+
// Extract directories
|
|
190
|
+
const dirMatch = summary.match(/\*\*Directories\*\*:\s*([^\n]+)/i)
|
|
191
|
+
if (dirMatch) result.directories = dirMatch[1].split(',').map(d => d.trim())
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Extract git stats from repo-summary
|
|
198
|
+
*/
|
|
199
|
+
function extractGitStats(summary) {
|
|
200
|
+
if (!summary) return { commits: null, contributors: null, recentCommits: [] }
|
|
201
|
+
|
|
202
|
+
const result = { commits: null, contributors: null, recentCommits: [] }
|
|
203
|
+
|
|
204
|
+
// Extract commit count
|
|
205
|
+
const commitMatch = summary.match(/\*\*Total Commits\*\*:\s*(\d+)/i)
|
|
206
|
+
if (commitMatch) result.commits = parseInt(commitMatch[1])
|
|
207
|
+
|
|
208
|
+
// Extract contributors
|
|
209
|
+
const contribMatch = summary.match(/\*\*Contributors\*\*:\s*(\d+)/i)
|
|
210
|
+
if (contribMatch) result.contributors = parseInt(contribMatch[1])
|
|
211
|
+
|
|
212
|
+
// Extract recent commits
|
|
213
|
+
const recentSection = summary.match(/## Recent Activity[\s\S]*?(?=##|$)/i)
|
|
214
|
+
if (recentSection) {
|
|
215
|
+
const commitLines = recentSection[0].match(/- `([a-f0-9]+)` (.+?) \(/g)
|
|
216
|
+
if (commitLines) {
|
|
217
|
+
result.recentCommits = commitLines.slice(0, 5).map(line => {
|
|
218
|
+
const match = line.match(/- `([a-f0-9]+)` (.+?) \(/)
|
|
219
|
+
return match ? { hash: match[1], message: match[2] } : null
|
|
220
|
+
}).filter(Boolean)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Extract agent expertise from agent files
|
|
229
|
+
*/
|
|
230
|
+
async function extractAgentExpertise(agentsDir, agentFiles) {
|
|
231
|
+
const expertise = []
|
|
232
|
+
|
|
233
|
+
for (const file of agentFiles.slice(0, 6)) { // Max 6 agents to keep context small
|
|
234
|
+
try {
|
|
235
|
+
const content = await fs.readFile(path.join(agentsDir, file), 'utf-8')
|
|
236
|
+
const name = file.replace('.md', '')
|
|
237
|
+
|
|
238
|
+
// Extract role
|
|
239
|
+
const roleMatch = content.match(/Role:\s*([^\n]+)/i)
|
|
240
|
+
const role = roleMatch ? roleMatch[1].trim() : 'Specialist'
|
|
241
|
+
|
|
242
|
+
// Extract domain or expertise
|
|
243
|
+
const domainMatch = content.match(/domain[:\s]+([^\n]+)/i) ||
|
|
244
|
+
content.match(/expertise[:\s]+([^\n]+)/i) ||
|
|
245
|
+
content.match(/owner of the ([^\n.]+)/i)
|
|
246
|
+
const expertiseText = domainMatch ? domainMatch[1].trim() : 'General'
|
|
247
|
+
|
|
248
|
+
expertise.push({ name, role, expertise: expertiseText })
|
|
249
|
+
} catch {
|
|
250
|
+
// Skip unreadable agents
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return expertise
|
|
123
255
|
}
|
|
124
256
|
|
|
125
257
|
/**
|
|
@@ -128,11 +260,10 @@ function extractStack(summary) {
|
|
|
128
260
|
function extractCurrentTask(now) {
|
|
129
261
|
if (!now) return null
|
|
130
262
|
|
|
131
|
-
// Skip headers and empty lines, get first content line
|
|
132
263
|
const lines = now.split('\n')
|
|
133
264
|
for (const line of lines) {
|
|
134
265
|
const trimmed = line.trim()
|
|
135
|
-
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('<!--')) {
|
|
266
|
+
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('<!--') && !trimmed.startsWith('No current')) {
|
|
136
267
|
return trimmed
|
|
137
268
|
}
|
|
138
269
|
}
|
|
@@ -164,20 +295,34 @@ function extractActiveFeatures(roadmap) {
|
|
|
164
295
|
let inActive = false
|
|
165
296
|
|
|
166
297
|
for (const line of lines) {
|
|
167
|
-
if (line.includes('Active') || line.includes('In Progress')) {
|
|
298
|
+
if (line.includes('Active') || line.includes('In Progress') || line.includes('Current')) {
|
|
168
299
|
inActive = true
|
|
169
300
|
continue
|
|
170
301
|
}
|
|
171
302
|
if (line.startsWith('## ') && inActive) {
|
|
172
|
-
break
|
|
303
|
+
break
|
|
173
304
|
}
|
|
174
|
-
if (inActive && line.match(
|
|
175
|
-
const feature = line.replace(
|
|
305
|
+
if (inActive && line.match(/^[-*]\s*\[[ x]\]/i)) {
|
|
306
|
+
const feature = line.replace(/^[-*]\s*\[[ x]\]\s*/, '').replace(/\*\*/g, '').trim()
|
|
176
307
|
if (feature) features.push(feature)
|
|
177
308
|
}
|
|
178
309
|
}
|
|
179
310
|
|
|
180
|
-
return features.slice(0,
|
|
311
|
+
return features.slice(0, 5)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Extract recent ideas from ideas.md
|
|
316
|
+
*/
|
|
317
|
+
function extractRecentIdeas(ideas) {
|
|
318
|
+
if (!ideas) return []
|
|
319
|
+
|
|
320
|
+
return ideas
|
|
321
|
+
.split('\n')
|
|
322
|
+
.filter(l => l.match(/^[-*]\s/))
|
|
323
|
+
.slice(0, 3)
|
|
324
|
+
.map(l => l.replace(/^[-*]\s+/, '').trim())
|
|
325
|
+
.filter(Boolean)
|
|
181
326
|
}
|
|
182
327
|
|
|
183
328
|
module.exports = { generateLocalContext }
|