prjct-cli 0.10.6 → 0.10.8

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.
@@ -9,7 +9,7 @@
9
9
 
10
10
  const fs = require('fs').promises
11
11
  const path = require('path')
12
- const TechDetector = require('./tech-detector')
12
+ const analyzer = require('./analyzer')
13
13
  const configManager = require('../infrastructure/config-manager')
14
14
  const pathManager = require('../infrastructure/path-manager')
15
15
 
@@ -17,7 +17,6 @@ class TaskAnalyzer {
17
17
  constructor(projectPath) {
18
18
  this.projectPath = projectPath
19
19
  this.projectId = null
20
- this.techDetector = null
21
20
  this.taskHistory = null
22
21
  }
23
22
 
@@ -26,17 +25,21 @@ class TaskAnalyzer {
26
25
  */
27
26
  async initialize() {
28
27
  this.projectId = await configManager.getProjectId(this.projectPath)
29
- this.techDetector = new TechDetector(this.projectPath)
28
+ analyzer.init(this.projectPath)
30
29
  await this.loadTaskHistory()
31
30
  }
32
31
 
33
32
  /**
34
33
  * Deep semantic analysis of a task
34
+ *
35
+ * 100% AGENTIC: No hardcoded patterns. Uses task description
36
+ * and historical patterns only. Claude decides domain relevance.
37
+ *
35
38
  * @param {Object} task - Task object {description, type}
36
39
  * @returns {Promise<Object>} Analysis result
37
40
  */
38
41
  async analyzeTask(task) {
39
- if (!this.techDetector) {
42
+ if (!this.projectId) {
40
43
  await this.initialize()
41
44
  }
42
45
 
@@ -44,109 +47,68 @@ class TaskAnalyzer {
44
47
  const type = (task.type || '').toLowerCase()
45
48
  const fullText = `${description} ${type}`.trim()
46
49
 
47
- // Get project technologies
48
- const projectTech = await this.techDetector.detectAll()
49
-
50
- // Multi-domain detection
51
- const domains = this.detectDomains(fullText, projectTech)
50
+ // Get raw project data (no categorization)
51
+ const projectData = {
52
+ packageJson: await analyzer.readPackageJson(),
53
+ extensions: await analyzer.getFileExtensions(),
54
+ directories: await analyzer.listDirectories(),
55
+ configFiles: await analyzer.listConfigFiles()
56
+ }
52
57
 
53
- // Semantic understanding
54
- const semantic = this.analyzeSemantics(fullText, projectTech)
58
+ // Semantic understanding (intent-based, not keyword-based)
59
+ const semantic = this.analyzeSemantics(fullText)
55
60
 
56
61
  // Historical patterns
57
62
  const historical = await this.analyzeHistory(fullText)
58
63
 
59
64
  // Complexity estimation
60
- const complexity = this.estimateComplexity(fullText, domains)
65
+ const complexity = this.estimateComplexity(fullText)
61
66
 
62
- // Combine all signals
63
- const primaryDomain = this.selectPrimaryDomain(domains, semantic, historical)
64
- const confidence = this.calculateConfidence(domains, semantic, historical)
67
+ // Primary domain from history and intent (not hardcoded patterns)
68
+ const primaryDomain = this.selectPrimaryDomain(semantic, historical)
69
+ const confidence = this.calculateConfidence(semantic, historical)
65
70
 
66
71
  return {
67
72
  primaryDomain,
68
- domains, // All detected domains
69
73
  confidence,
70
74
  semantic,
71
75
  historical,
72
76
  complexity,
73
- projectTechnologies: projectTech,
74
- matchedKeywords: domains[primaryDomain]?.keywords || [],
75
- reason: this.buildReason(primaryDomain, domains, semantic, historical),
76
- alternatives: this.getAlternatives(primaryDomain, domains)
77
+ projectData, // Raw data for Claude to analyze
78
+ matchedKeywords: [], // No keyword matching - Claude decides
79
+ reason: this.buildReason(primaryDomain, semantic, historical),
80
+ alternatives: ['full-stack', 'generalist']
77
81
  }
78
82
  }
79
83
 
80
84
  /**
81
- * Detect multiple domains from task description
85
+ * Domain detection removed - 100% AGENTIC
86
+ *
87
+ * NO hardcoded keyword lists or framework categorization.
88
+ * Claude analyzes the task description and project context
89
+ * to determine the appropriate domain.
90
+ *
91
+ * This method is kept for backward compatibility but returns empty.
92
+ * Use analyzeTask() which provides raw data for Claude.
82
93
  */
83
- detectDomains(text, projectTech) {
84
- const domains = {}
85
-
86
- // Enhanced patterns with project context
87
- const patterns = {
88
- frontend: [
89
- 'component', 'ui', 'user interface', 'frontend', 'client',
90
- 'style', 'css', 'layout', 'responsive', 'design',
91
- 'page', 'view', 'template', 'render', 'display',
92
- 'button', 'form', 'input', 'modal', 'dialog',
93
- ...(projectTech.frameworks.filter(f => ['react', 'vue', 'angular', 'svelte', 'next', 'nuxt'].includes(f.toLowerCase())).map(f => f.toLowerCase()))
94
- ],
95
- backend: [
96
- 'api', 'server', 'endpoint', 'route', 'middleware',
97
- 'auth', 'authentication', 'authorization', 'jwt', 'session',
98
- 'backend', 'service', 'controller', 'handler',
99
- 'database', 'query', 'model', 'schema',
100
- ...(projectTech.frameworks.filter(f => ['express', 'fastify', 'django', 'flask', 'rails', 'phoenix'].includes(f.toLowerCase())).map(f => f.toLowerCase()))
101
- ],
102
- database: [
103
- 'database', 'db', 'query', 'migration', 'schema', 'model',
104
- 'sql', 'data', 'table', 'collection', 'index', 'relation',
105
- 'postgres', 'mysql', 'mongodb', 'redis'
106
- ],
107
- devops: [
108
- 'deploy', 'deployment', 'docker', 'kubernetes', 'k8s',
109
- 'ci/cd', 'pipeline', 'build', 'ship', 'release',
110
- 'production', 'infrastructure', 'container', 'orchestration'
111
- ],
112
- qa: [
113
- 'test', 'testing', 'bug', 'error', 'fix', 'debug', 'issue',
114
- 'quality', 'coverage', 'unit test', 'integration test',
115
- 'e2e', 'spec', 'assertion', 'validation'
116
- ],
117
- architecture: [
118
- 'design', 'architecture', 'pattern', 'structure',
119
- 'refactor', 'refactoring', 'organize', 'plan',
120
- 'feature', 'system', 'module', 'component design'
121
- ]
122
- }
123
-
124
- // Score each domain
125
- for (const [domain, keywords] of Object.entries(patterns)) {
126
- const matches = keywords.filter(keyword => text.includes(keyword))
127
- if (matches.length > 0) {
128
- domains[domain] = {
129
- keywords: matches,
130
- count: matches.length,
131
- score: matches.length + (matches.length > 2 ? 1 : 0) // Bonus for multiple matches
132
- }
133
- }
134
- }
135
-
136
- return domains
94
+ detectDomains(text) {
95
+ // No hardcoded patterns - Claude decides domain
96
+ return {}
137
97
  }
138
98
 
139
99
  /**
140
- * Semantic analysis - understand intent, not just keywords
100
+ * Semantic analysis - understand intent
101
+ *
102
+ * Only detects basic intent (create, fix, improve, test).
103
+ * Claude handles detailed domain analysis.
141
104
  */
142
- analyzeSemantics(text, projectTech) {
105
+ analyzeSemantics(text) {
143
106
  const semantic = {
144
107
  intent: null,
145
- requiresMultipleAgents: false,
146
- complexity: 'medium'
108
+ requiresMultipleAgents: false
147
109
  }
148
110
 
149
- // Detect intent patterns
111
+ // Detect basic intent patterns (these are universal, not tech-specific)
150
112
  if (text.match(/\b(create|add|build|implement|make)\b/)) {
151
113
  semantic.intent = 'create'
152
114
  } else if (text.match(/\b(fix|repair|debug|resolve)\b/)) {
@@ -157,18 +119,8 @@ class TaskAnalyzer {
157
119
  semantic.intent = 'test'
158
120
  }
159
121
 
160
- // Detect multi-agent requirements
161
- if (text.match(/\b(api|endpoint).*\b(test|spec)\b/) ||
162
- text.match(/\b(test|spec).*\b(api|endpoint)\b/)) {
163
- semantic.requiresMultipleAgents = true
164
- semantic.agents = ['backend', 'qa']
165
- }
166
-
167
- if (text.match(/\b(component|ui).*\b(test|spec)\b/) ||
168
- text.match(/\b(test|spec).*\b(component|ui)\b/)) {
169
- semantic.requiresMultipleAgents = true
170
- semantic.agents = ['frontend', 'qa']
171
- }
122
+ // No hardcoded multi-agent detection
123
+ // Claude decides if multiple agents are needed based on context
172
124
 
173
125
  return semantic
174
126
  }
@@ -210,71 +162,61 @@ class TaskAnalyzer {
210
162
  }
211
163
 
212
164
  /**
213
- * Estimate task complexity
165
+ * Estimate task complexity based on intent words
214
166
  */
215
- estimateComplexity(text, domains) {
216
- let complexity = 'medium'
217
-
218
- // Complexity indicators
219
- const simpleIndicators = ['add', 'create', 'simple', 'basic']
220
- const complexIndicators = ['refactor', 'optimize', 'architecture', 'migration', 'redesign']
167
+ estimateComplexity(text) {
168
+ // Simple complexity indicators (universal, not tech-specific)
169
+ const simpleIndicators = ['add', 'create', 'simple', 'basic', 'small']
170
+ const complexIndicators = ['refactor', 'optimize', 'architecture', 'migration', 'redesign', 'overhaul']
221
171
 
222
172
  const simpleCount = simpleIndicators.filter(ind => text.includes(ind)).length
223
173
  const complexCount = complexIndicators.filter(ind => text.includes(ind)).length
224
174
 
225
175
  if (complexCount > simpleCount) {
226
- complexity = 'high'
176
+ return 'high'
227
177
  } else if (simpleCount > 0 && complexCount === 0) {
228
- complexity = 'low'
178
+ return 'low'
229
179
  }
230
180
 
231
- // Multiple domains = more complex
232
- if (Object.keys(domains).length > 1) {
233
- complexity = 'high'
234
- }
235
-
236
- return complexity
181
+ return 'medium'
237
182
  }
238
183
 
239
184
  /**
240
- * Select primary domain from all signals
185
+ * Select primary domain from history and semantics
186
+ *
187
+ * 100% AGENTIC: No keyword matching. Uses only:
188
+ * - Historical patterns from past tasks
189
+ * - Basic intent detection
190
+ * Claude decides actual domain based on project context.
241
191
  */
242
- selectPrimaryDomain(domains, semantic, historical) {
243
- // Priority: historical > semantic > keyword matching
244
- if (historical.suggestedDomain && historical.confidence > 0.7) {
192
+ selectPrimaryDomain(semantic, historical) {
193
+ // Priority: historical > default
194
+ if (historical && historical.suggestedDomain && historical.confidence > 0.7) {
245
195
  return historical.suggestedDomain
246
196
  }
247
197
 
248
- if (semantic.agents && semantic.agents.length > 0) {
249
- return semantic.agents[0] // Primary agent for multi-agent tasks
198
+ // Map intent to suggested domain (loose mapping, Claude refines)
199
+ if (semantic && semantic.intent === 'test') {
200
+ return 'qa'
250
201
  }
251
202
 
252
- // Find domain with highest score
253
- const sorted = Object.entries(domains)
254
- .sort((a, b) => b[1].score - a[1].score)
255
-
256
- return sorted.length > 0 ? sorted[0][0] : 'generalist'
203
+ // Default: generalist (Claude decides based on context)
204
+ return 'generalist'
257
205
  }
258
206
 
259
207
  /**
260
- * Calculate confidence in domain selection
208
+ * Calculate confidence based on available signals
261
209
  */
262
- calculateConfidence(domains, semantic, historical) {
210
+ calculateConfidence(semantic, historical) {
263
211
  let confidence = 0.5 // Base confidence
264
212
 
265
- // Boost from keyword matches
266
- const primaryDomain = this.selectPrimaryDomain(domains, semantic, historical)
267
- if (domains[primaryDomain]) {
268
- confidence += domains[primaryDomain].score * 0.1
269
- }
270
-
271
213
  // Boost from historical patterns
272
- if (historical.confidence > 0) {
214
+ if (historical && historical.confidence > 0) {
273
215
  confidence += historical.confidence * 0.3
274
216
  }
275
217
 
276
218
  // Boost from semantic understanding
277
- if (semantic.intent) {
219
+ if (semantic && semantic.intent) {
278
220
  confidence += 0.1
279
221
  }
280
222
 
@@ -284,31 +226,18 @@ class TaskAnalyzer {
284
226
  /**
285
227
  * Build human-readable reason
286
228
  */
287
- buildReason(primaryDomain, domains, semantic, historical) {
229
+ buildReason(primaryDomain, semantic, historical) {
288
230
  const parts = []
289
231
 
290
- if (historical.suggestedDomain && historical.confidence > 0.7) {
291
- parts.push(`Historical pattern: similar tasks used ${primaryDomain}`)
232
+ if (historical && historical.suggestedDomain && historical.confidence > 0.7) {
233
+ parts.push(`Historical: similar tasks used ${primaryDomain}`)
292
234
  }
293
235
 
294
- if (domains[primaryDomain]) {
295
- parts.push(`Keywords: ${domains[primaryDomain].keywords.join(', ')}`)
296
- }
297
-
298
- if (semantic.intent) {
236
+ if (semantic && semantic.intent) {
299
237
  parts.push(`Intent: ${semantic.intent}`)
300
238
  }
301
239
 
302
- return parts.join(' | ') || `Detected ${primaryDomain} domain`
303
- }
304
-
305
- /**
306
- * Get alternative domains
307
- */
308
- getAlternatives(primaryDomain, domains) {
309
- return Object.keys(domains)
310
- .filter(d => d !== primaryDomain)
311
- .sort((a, b) => domains[b].score - domains[a].score)
240
+ return parts.join(' | ') || 'Claude will analyze task in context'
312
241
  }
313
242
 
314
243
  /**
@@ -5,6 +5,7 @@
5
5
 
6
6
  const path = require('path');
7
7
  const fs = require('fs').promises;
8
+ const log = require('../utils/logger');
8
9
 
9
10
  class TaskStack {
10
11
  constructor(projectPath) {
@@ -168,7 +169,7 @@ class TaskStack {
168
169
  try {
169
170
  entries.push(JSON.parse(line));
170
171
  } catch (error) {
171
- console.error('Error parsing stack line:', error);
172
+ log.error('Error parsing stack line:', error.message);
172
173
  }
173
174
  }
174
175
 
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Logger - Centralized logging with levels
3
+ *
4
+ * Silent by default. Enable with:
5
+ * PRJCT_DEBUG=1 (all logs)
6
+ * PRJCT_DEBUG=error (errors only)
7
+ * PRJCT_DEBUG=warn (errors + warnings)
8
+ * PRJCT_DEBUG=info (errors + warnings + info)
9
+ * PRJCT_DEBUG=debug (everything)
10
+ *
11
+ * Usage:
12
+ * const log = require('./utils/logger')
13
+ * log.debug('Processing files...')
14
+ * log.info('Task started')
15
+ * log.warn('Cache miss')
16
+ * log.error('Failed to load', error.message)
17
+ */
18
+
19
+ const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 }
20
+
21
+ const debugEnv = process.env.PRJCT_DEBUG || process.env.DEBUG || ''
22
+ const isEnabled = debugEnv === '1' || debugEnv === 'true' || debugEnv.includes('prjct')
23
+
24
+ // Determine log level
25
+ let currentLevel = -1 // disabled by default
26
+ if (isEnabled) {
27
+ if (debugEnv === '1' || debugEnv === 'true' || debugEnv === 'prjct') {
28
+ currentLevel = LEVELS.debug // all logs
29
+ } else if (LEVELS[debugEnv] !== undefined) {
30
+ currentLevel = LEVELS[debugEnv]
31
+ } else {
32
+ currentLevel = LEVELS.debug
33
+ }
34
+ }
35
+
36
+ // No-op function for disabled logs
37
+ const noop = () => {}
38
+
39
+ // Create logger methods
40
+ const logger = {
41
+ error: currentLevel >= LEVELS.error
42
+ ? (...args) => console.error('[prjct:error]', ...args)
43
+ : noop,
44
+
45
+ warn: currentLevel >= LEVELS.warn
46
+ ? (...args) => console.warn('[prjct:warn]', ...args)
47
+ : noop,
48
+
49
+ info: currentLevel >= LEVELS.info
50
+ ? (...args) => console.log('[prjct:info]', ...args)
51
+ : noop,
52
+
53
+ debug: currentLevel >= LEVELS.debug
54
+ ? (...args) => console.log('[prjct:debug]', ...args)
55
+ : noop,
56
+
57
+ // Check if logging is enabled (useful for expensive log prep)
58
+ isEnabled: () => currentLevel >= 0,
59
+
60
+ // Get current level name
61
+ level: () => Object.keys(LEVELS).find(k => LEVELS[k] === currentLevel) || 'disabled'
62
+ }
63
+
64
+ module.exports = logger
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Minimal Output System for prjct-cli
3
+ * Spinner while working → Single line result
4
+ */
5
+
6
+ const chalk = require('chalk')
7
+
8
+ const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
9
+ const SPEED = 80
10
+
11
+ let interval = null
12
+ let frame = 0
13
+
14
+ const truncate = (s, max = 50) => (s && s.length > max ? s.slice(0, max - 1) + '…' : s || '')
15
+ const clear = () => process.stdout.write('\r' + ' '.repeat(70) + '\r')
16
+
17
+ const out = {
18
+ spin(msg) {
19
+ this.stop()
20
+ interval = setInterval(() => {
21
+ process.stdout.write(`\r${chalk.cyan(FRAMES[frame++ % 10])} ${truncate(msg, 55)}`)
22
+ }, SPEED)
23
+ return this
24
+ },
25
+
26
+ done(msg) {
27
+ this.stop()
28
+ console.log(`${chalk.green('✓')} ${truncate(msg, 65)}`)
29
+ return this
30
+ },
31
+
32
+ fail(msg) {
33
+ this.stop()
34
+ console.log(`${chalk.red('✗')} ${truncate(msg, 65)}`)
35
+ return this
36
+ },
37
+
38
+ warn(msg) {
39
+ this.stop()
40
+ console.log(`${chalk.yellow('⚠')} ${truncate(msg, 65)}`)
41
+ return this
42
+ },
43
+
44
+ stop() {
45
+ if (interval) {
46
+ clearInterval(interval)
47
+ interval = null
48
+ clear()
49
+ }
50
+ return this
51
+ }
52
+ }
53
+
54
+ module.exports = out
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
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": {
@@ -0,0 +1,78 @@
1
+ ---
2
+ allowed-tools: [Read]
3
+ description: 'Determine which agent should handle a task - Claude decides'
4
+ ---
5
+
6
+ # Agent Routing Instructions
7
+
8
+ ## Objective
9
+
10
+ Determine the best agent for a task by analyzing the ACTUAL task and project context.
11
+
12
+ ## Step 1: Understand the Task
13
+
14
+ Read the task description and identify:
15
+
16
+ - What files will be modified?
17
+ - What type of work is involved?
18
+ - What knowledge is required?
19
+
20
+ ## Step 2: Read Project Context
21
+
22
+ If project analysis exists, read it to understand:
23
+
24
+ - What technologies are actually used
25
+ - How the project is structured
26
+ - What patterns are established
27
+
28
+ ## Step 3: Match Task to Agent
29
+
30
+ Based on your analysis, determine which agent is best suited:
31
+
32
+ **DO NOT assume:**
33
+ - "React" = frontend agent
34
+ - "Express" = backend agent
35
+ - "Database" = database agent
36
+
37
+ **DO analyze:**
38
+ - What the task actually requires
39
+ - What files will be touched
40
+ - What expertise is needed
41
+
42
+ ## Available Agent Types
43
+
44
+ Consider these agent specializations:
45
+
46
+ - **Coordinator**: High-level planning, task breakdown
47
+ - **Frontend/UX**: UI components, user experience, styling
48
+ - **Backend**: API endpoints, server logic, business rules
49
+ - **Database**: Schema design, queries, migrations
50
+ - **DevOps/QA**: Testing, CI/CD, deployment, infrastructure
51
+ - **Full-stack**: Cross-cutting concerns, multiple layers
52
+
53
+ ## Decision Process
54
+
55
+ 1. Read task description
56
+ 2. Identify primary area of work
57
+ 3. Consider secondary areas
58
+ 4. Choose agent with best expertise match
59
+
60
+ ## Output
61
+
62
+ Return the recommended agent with reasoning:
63
+
64
+ ```json
65
+ {
66
+ "agent": "recommended-agent-type",
67
+ "reasoning": "why this agent is best for the task",
68
+ "confidence": "high|medium|low",
69
+ "alternatives": ["other agents that could help"]
70
+ }
71
+ ```
72
+
73
+ ## Rules
74
+
75
+ - **Task-driven, not tech-driven** - Focus on what needs to be done
76
+ - **Context matters** - Same tech can mean different things in different projects
77
+ - **Be flexible** - One project's "backend" might be another's "full-stack"
78
+ - **Explain your reasoning** - Don't just pick, justify
@@ -0,0 +1,77 @@
1
+ ---
2
+ allowed-tools: [Glob, Read]
3
+ description: 'Filter relevant context for a task - Claude decides what matters'
4
+ ---
5
+
6
+ # Context Filtering Instructions
7
+
8
+ ## Objective
9
+
10
+ Determine which files and directories are relevant for a given task.
11
+
12
+ ## Step 1: Get Real File Extensions
13
+
14
+ Instead of assuming extensions, get the ACTUAL extensions in the project:
15
+
16
+ ```bash
17
+ # Get real extensions (analyzer.getFileExtensions())
18
+ ```
19
+
20
+ Use only extensions that EXIST in this project.
21
+
22
+ ## Step 2: Identify Relevant Directories
23
+
24
+ Based on the task, identify which directories matter:
25
+
26
+ **DO NOT use hardcoded lists like:**
27
+ - "components" for frontend
28
+ - "routes" for backend
29
+
30
+ **DO analyze:**
31
+ - Where does this project put similar code?
32
+ - What directory structure does it use?
33
+ - What naming conventions are followed?
34
+
35
+ ## Step 3: Filter by Task Requirements
36
+
37
+ For the specific task:
38
+
39
+ 1. What type of files will be modified?
40
+ 2. What related files might need updates?
41
+ 3. What config files are relevant?
42
+
43
+ ## Step 4: Exclude Non-Relevant
44
+
45
+ Always exclude:
46
+ - `node_modules/`, `vendor/`, etc. (dependencies)
47
+ - `.git/` (version control)
48
+ - `dist/`, `build/`, `target/` (build outputs)
49
+ - Generated files
50
+
51
+ ## Output
52
+
53
+ Return filtering patterns:
54
+
55
+ ```json
56
+ {
57
+ "include": {
58
+ "extensions": [".actual", ".extensions", ".found"],
59
+ "directories": ["actual/", "directories/", "in/project/"],
60
+ "files": ["specific-files-if-known"]
61
+ },
62
+ "exclude": {
63
+ "directories": ["node_modules/", ".git/", "dist/"],
64
+ "patterns": ["*.min.js", "*.map"]
65
+ },
66
+ "priority": ["most-relevant-paths-first"],
67
+ "reasoning": "why these patterns were chosen"
68
+ }
69
+ ```
70
+
71
+ ## Rules
72
+
73
+ - **Use REAL extensions** - Only include extensions that exist in the project
74
+ - **No assumptions** - Don't assume directory names
75
+ - **Task-specific** - Different tasks need different context
76
+ - **Efficient** - Don't include everything, focus on what matters
77
+ - **Explain choices** - Document why certain patterns were included/excluded