prjct-cli 0.10.3 → 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 CHANGED
@@ -1,5 +1,31 @@
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
+
20
+ ## [0.10.4] - 2025-11-27
21
+
22
+ ### Fixed
23
+
24
+ - **Commands Module Export** - Fixed `require('./commands').sync is not a function` error
25
+ - Module now exports singleton instance for direct use
26
+ - Class still available via `require('./commands').PrjctCommands`
27
+ - Enables Claude to call commands directly: `require('./commands').sync()`
28
+
3
29
  ## [0.10.3] - 2025-11-27
4
30
 
5
31
  ### Fixed
package/core/commands.js CHANGED
@@ -3014,4 +3014,10 @@ Agent: ${agent}
3014
3014
  }
3015
3015
  }
3016
3016
 
3017
- module.exports = PrjctCommands
3017
+ // Export both class and singleton instance
3018
+ // Class for CLI (new PrjctCommands())
3019
+ // Instance for direct use (require('./commands').sync())
3020
+ const instance = new PrjctCommands()
3021
+
3022
+ module.exports = instance
3023
+ module.exports.PrjctCommands = PrjctCommands
@@ -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
- * - Stack info
6
- * - Available agents (dynamic, varies per project)
7
- * - Current task
8
- * - Priority queue
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
- * 100% DYNAMIC - reads whatever agents exist
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: string|null, currentTask: string|null}>}
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 agents that exist (dynamic - varies per project)
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
- // 2. Read core files (may not exist)
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
- // 3. Extract stack from repo-summary
55
- const stack = extractStack(repoSummary)
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
- // 4. Generate agent list with clean names
58
- const agents = agentFiles.map(f => f.replace('.md', ''))
68
+ // 5. Read agent expertise (just the key parts, not full templates)
69
+ const agentExpertise = await extractAgentExpertise(agentsDir, agentFiles)
59
70
 
60
- // 5. Extract current task
71
+ // 6. Extract current task
61
72
  const currentTask = extractCurrentTask(now)
62
73
 
63
- // 6. Extract top 3 from next.md
64
- const nextTasks = extractTopTasks(next, 3)
74
+ // 7. Extract priority queue (top 5)
75
+ const nextTasks = extractTopTasks(next, 5)
65
76
 
66
- // 7. Extract active features from roadmap
77
+ // 8. Extract active features from roadmap
67
78
  const activeFeatures = extractActiveFeatures(roadmap)
68
79
 
69
- // 8. Generate CLAUDE.md content
70
- const content = `# Project Context
71
- <!-- Auto-generated by /p:sync - DO NOT EDIT -->
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
- ## Stack
76
- ${stack || 'Run /p:analyze to detect stack'}
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
- ## Current Task
82
- ${currentTask || 'None. Use /p:now to start.'}
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
- ## Next Up
85
- ${nextTasks.length ? nextTasks.map((t, i) => `${i + 1}. ${t}`).join('\n') : 'Empty queue.'}
122
+ ## Active Features (Roadmap)
86
123
 
87
- ## Active Features
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
- ## Data Location
91
- All project data: ~/.prjct-cli/projects/${projectId}/
92
- - agents/ - Read for detailed patterns and code examples
93
- - analysis/repo-summary.md - Full technical analysis
94
- - planning/roadmap.md - Features and roadmap
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
- // 9. Write to global storage (NOT in repo)
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 extractStack(summary) {
108
- if (!summary) return null
109
-
110
- // Try multiple patterns
111
- const patterns = [
112
- /\*\*(?:Stack|Framework|Language)\*\*[:\s|]+([^\n|]+)/i,
113
- /\| \*\*Type\*\* \| ([^\|]+) \|/i,
114
- /## Tech Stack[\s\S]*?### (?:Frontend|Backend)\n- \*\*([^*]+)\*\*/i
115
- ]
116
-
117
- for (const pattern of patterns) {
118
- const match = summary.match(pattern)
119
- if (match) return match[1].trim()
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 null
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 // End of active section
303
+ break
173
304
  }
174
- if (inActive && line.match(/^- \[ \]/)) {
175
- const feature = line.replace(/^- \[ \]\s*\*\*/, '').replace(/\*\*.*$/, '').trim()
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, 3) // Max 3
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 }
package/core/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * This file is required by bin/prjct after setup verification
5
5
  */
6
6
 
7
- const PrjctCommands = require('./commands')
7
+ const { PrjctCommands } = require('./commands')
8
8
  const registry = require('./command-registry')
9
9
 
10
10
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
5
5
  "main": "core/index.js",
6
6
  "bin": {