prjct-cli 0.9.2 → 0.10.2

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/core/__tests__/agentic/memory-system.test.js +263 -0
  3. package/core/__tests__/agentic/plan-mode.test.js +336 -0
  4. package/core/agentic/agent-router.js +253 -186
  5. package/core/agentic/chain-of-thought.js +578 -0
  6. package/core/agentic/command-executor.js +299 -17
  7. package/core/agentic/context-builder.js +208 -8
  8. package/core/agentic/context-filter.js +83 -83
  9. package/core/agentic/ground-truth.js +591 -0
  10. package/core/agentic/loop-detector.js +406 -0
  11. package/core/agentic/memory-system.js +850 -0
  12. package/core/agentic/parallel-tools.js +366 -0
  13. package/core/agentic/plan-mode.js +572 -0
  14. package/core/agentic/prompt-builder.js +127 -2
  15. package/core/agentic/response-templates.js +290 -0
  16. package/core/agentic/semantic-compression.js +517 -0
  17. package/core/agentic/think-blocks.js +657 -0
  18. package/core/agentic/tool-registry.js +32 -0
  19. package/core/agentic/validation-rules.js +380 -0
  20. package/core/command-registry.js +48 -0
  21. package/core/commands.js +128 -60
  22. package/core/context-sync.js +183 -0
  23. package/core/domain/agent-generator.js +77 -46
  24. package/core/domain/agent-loader.js +183 -0
  25. package/core/domain/agent-matcher.js +217 -0
  26. package/core/domain/agent-validator.js +217 -0
  27. package/core/domain/context-estimator.js +175 -0
  28. package/core/domain/product-standards.js +92 -0
  29. package/core/domain/smart-cache.js +157 -0
  30. package/core/domain/task-analyzer.js +353 -0
  31. package/core/domain/tech-detector.js +365 -0
  32. package/package.json +8 -16
  33. package/templates/commands/done.md +7 -0
  34. package/templates/commands/feature.md +8 -0
  35. package/templates/commands/ship.md +8 -0
  36. package/templates/commands/spec.md +128 -0
  37. package/templates/global/CLAUDE.md +17 -0
  38. package/core/__tests__/agentic/agent-router.test.js +0 -398
  39. package/core/__tests__/agentic/command-executor.test.js +0 -223
  40. package/core/__tests__/agentic/context-builder.test.js +0 -160
  41. package/core/__tests__/agentic/context-filter.test.js +0 -494
  42. package/core/__tests__/agentic/prompt-builder.test.js +0 -212
  43. package/core/__tests__/agentic/template-loader.test.js +0 -164
  44. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  45. package/core/__tests__/domain/agent-generator.test.js +0 -296
  46. package/core/__tests__/domain/analyzer.test.js +0 -324
  47. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  48. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  49. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  50. package/core/__tests__/setup.test.js +0 -15
  51. package/core/__tests__/utils/date-helper.test.js +0 -169
  52. package/core/__tests__/utils/file-helper.test.js +0 -258
  53. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
@@ -0,0 +1,175 @@
1
+ /**
2
+ * ContextEstimator - Pre-filter files before building context
3
+ *
4
+ * Estimates which files are needed based on task analysis
5
+ * BEFORE building full context - saves I/O
6
+ *
7
+ * @version 1.0.0
8
+ */
9
+
10
+ const path = require('path')
11
+ const { glob } = require('glob')
12
+
13
+ class ContextEstimator {
14
+ /**
15
+ * Estimate which files are needed for a task
16
+ * @param {Object} taskAnalysis - Task analysis result
17
+ * @param {string} projectPath - Project path
18
+ * @returns {Promise<Array<string>>} Estimated file paths
19
+ */
20
+ async estimateFiles(taskAnalysis, projectPath) {
21
+ const domain = taskAnalysis.primaryDomain
22
+ const projectTech = taskAnalysis.projectTechnologies || {}
23
+ const semantic = taskAnalysis.semantic || {}
24
+
25
+ // Get patterns for this domain
26
+ const patterns = this.getPatternsForDomain(domain, projectTech)
27
+
28
+ // Expand with semantic understanding
29
+ if (semantic.requiresMultipleAgents && semantic.agents) {
30
+ // Multi-agent task - combine patterns
31
+ const allPatterns = semantic.agents.reduce((acc, agentDomain) => {
32
+ const agentPatterns = this.getPatternsForDomain(agentDomain, projectTech)
33
+ return {
34
+ include: [...acc.include, ...agentPatterns.include],
35
+ extensions: [...acc.extensions, ...agentPatterns.extensions]
36
+ }
37
+ }, { include: [], extensions: [] })
38
+
39
+ patterns.include = [...new Set(allPatterns.include)]
40
+ patterns.extensions = [...new Set(allPatterns.extensions)]
41
+ }
42
+
43
+ // Find files matching patterns
44
+ const files = await this.findMatchingFiles(projectPath, patterns)
45
+
46
+ // Limit to reasonable number
47
+ const maxFiles = 200
48
+ return files.slice(0, maxFiles)
49
+ }
50
+
51
+ /**
52
+ * Get file patterns for a domain
53
+ */
54
+ getPatternsForDomain(domain, projectTech) {
55
+ const patterns = {
56
+ include: [],
57
+ extensions: [],
58
+ exclude: ['node_modules', 'dist', 'build', '.git']
59
+ }
60
+
61
+ switch (domain) {
62
+ case 'frontend':
63
+ patterns.include = ['src', 'components', 'pages', 'views', 'app']
64
+ patterns.extensions = ['.tsx', '.jsx', '.vue', '.svelte', '.css', '.scss', '.styled.js']
65
+
66
+ // Add framework-specific patterns
67
+ if (projectTech.frameworks) {
68
+ if (projectTech.frameworks.some(f => f.toLowerCase().includes('next'))) {
69
+ patterns.include.push('pages', 'app', 'components')
70
+ }
71
+ if (projectTech.frameworks.some(f => f.toLowerCase().includes('react'))) {
72
+ patterns.extensions.push('.tsx', '.jsx')
73
+ }
74
+ }
75
+ break
76
+
77
+ case 'backend':
78
+ patterns.include = ['src', 'lib', 'api', 'routes', 'controllers', 'services', 'app']
79
+ patterns.extensions = ['.js', '.ts', '.py', '.rb', '.go', '.rs']
80
+
81
+ // Framework-specific
82
+ if (projectTech.frameworks) {
83
+ if (projectTech.frameworks.some(f => f.toLowerCase().includes('express'))) {
84
+ patterns.include.push('routes', 'middleware', 'controllers')
85
+ }
86
+ if (projectTech.frameworks.some(f => f.toLowerCase().includes('django'))) {
87
+ patterns.include.push('views', 'urls', 'models')
88
+ }
89
+ }
90
+ break
91
+
92
+ case 'database':
93
+ patterns.include = ['migrations', 'models', 'schemas', 'db', 'database']
94
+ patterns.extensions = ['.sql', '.js', '.ts', '.rb', '.py']
95
+ break
96
+
97
+ case 'qa':
98
+ patterns.include = ['tests', '__tests__', 'spec', 'test']
99
+ patterns.extensions = ['.test.js', '.test.ts', '.spec.js', '.spec.ts']
100
+ break
101
+
102
+ case 'devops':
103
+ patterns.include = ['.github', '.gitlab', 'docker', 'k8s', 'kubernetes', 'terraform']
104
+ patterns.extensions = ['.yml', '.yaml', '.dockerfile', '.sh', '.tf']
105
+ break
106
+
107
+ default:
108
+ // General - include common source directories
109
+ patterns.include = ['src', 'lib', 'app']
110
+ patterns.extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb']
111
+ }
112
+
113
+ return patterns
114
+ }
115
+
116
+ /**
117
+ * Find files matching patterns
118
+ */
119
+ async findMatchingFiles(projectPath, patterns) {
120
+ const files = []
121
+
122
+ try {
123
+ // Build glob patterns
124
+ const globPatterns = []
125
+
126
+ // Add include patterns
127
+ for (const include of patterns.include) {
128
+ for (const ext of patterns.extensions) {
129
+ globPatterns.push(`${include}/**/*${ext}`)
130
+ globPatterns.push(`**/${include}/**/*${ext}`)
131
+ }
132
+ }
133
+
134
+ // Also search root level
135
+ for (const ext of patterns.extensions) {
136
+ globPatterns.push(`*${ext}`)
137
+ }
138
+
139
+ // Execute glob searches
140
+ for (const pattern of globPatterns) {
141
+ try {
142
+ const matches = await glob(pattern, {
143
+ cwd: projectPath,
144
+ ignore: patterns.exclude.map(ex => `**/${ex}/**`),
145
+ nodir: true,
146
+ follow: false
147
+ })
148
+
149
+ if (Array.isArray(matches)) {
150
+ files.push(...matches)
151
+ }
152
+ } catch (error) {
153
+ // Skip invalid patterns
154
+ }
155
+ }
156
+
157
+ // Remove duplicates and sort
158
+ return [...new Set(files)].sort()
159
+ } catch (error) {
160
+ console.error('Error finding files:', error.message)
161
+ return []
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Estimate context size (number of files)
167
+ */
168
+ async estimateContextSize(taskAnalysis, projectPath) {
169
+ const files = await this.estimateFiles(taskAnalysis, projectPath)
170
+ return files.length
171
+ }
172
+ }
173
+
174
+ module.exports = ContextEstimator
175
+
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Modern Product Standards
3
+ * Defines what "Good" looks like for each domain.
4
+ * Used to inject high standards into agent prompts.
5
+ */
6
+
7
+ const ProductStandards = {
8
+ // General standards applicable to all agents
9
+ general: [
10
+ 'SHIP IT: Bias for action. Better to ship and iterate than perfect and delay.',
11
+ 'USER CENTRIC: Always ask "How does this help the user?"',
12
+ 'CLEAN CODE: Write code that is easy to read, test, and maintain.',
13
+ 'NO BS: Avoid over-engineering. Simple is better than complex.'
14
+ ],
15
+
16
+ // Domain-specific standards
17
+ domains: {
18
+ frontend: {
19
+ title: 'Modern Frontend Standards',
20
+ rules: [
21
+ 'PERFORMANCE: Core Web Vitals matter. Optimize LCP, CLS, FID.',
22
+ 'ACCESSIBILITY: Semantic HTML, ARIA labels, keyboard navigation (WCAG 2.1 AA).',
23
+ 'RESPONSIVE: Mobile-first design. Works on all devices.',
24
+ 'UX/UI: Smooth transitions, loading states, error boundaries. No dead clicks.',
25
+ 'STATE: Local state for UI, Global state (Context/Zustand) for data. No prop drilling.'
26
+ ]
27
+ },
28
+ backend: {
29
+ title: 'Robust Backend Standards',
30
+ rules: [
31
+ 'SECURITY: Validate ALL inputs. Sanitize outputs. OWASP Top 10 awareness.',
32
+ 'SCALABILITY: Stateless services. Caching strategies (Redis/CDN).',
33
+ 'RELIABILITY: Graceful error handling. Structured logging. Health checks.',
34
+ 'API DESIGN: RESTful or GraphQL best practices. Consistent response envelopes.',
35
+ 'DB: Indexed queries. Migrations for schema changes. No N+1 queries.'
36
+ ]
37
+ },
38
+ database: {
39
+ title: 'Data Integrity Standards',
40
+ rules: [
41
+ 'INTEGRITY: Foreign keys, constraints, transactions.',
42
+ 'PERFORMANCE: Index usage analysis. Query optimization.',
43
+ 'BACKUPS: Point-in-time recovery awareness.',
44
+ 'MIGRATIONS: Idempotent scripts. Zero-downtime changes.'
45
+ ]
46
+ },
47
+ devops: {
48
+ title: 'Modern Ops Standards',
49
+ rules: [
50
+ 'AUTOMATION: CI/CD for everything. No manual deployments.',
51
+ 'IaC: Infrastructure as Code (Terraform/Pulumi).',
52
+ 'OBSERVABILITY: Metrics, Logs, Traces (OpenTelemetry).',
53
+ 'SECURITY: Least privilege access. Secrets management.'
54
+ ]
55
+ },
56
+ qa: {
57
+ title: 'Quality Assurance Standards',
58
+ rules: [
59
+ 'PYRAMID: Many unit tests, some integration, few E2E.',
60
+ 'COVERAGE: Critical paths must be tested.',
61
+ 'REALISM: Test with realistic data and scenarios.',
62
+ 'SPEED: Fast feedback loops. Parallel execution.'
63
+ ]
64
+ },
65
+ architecture: {
66
+ title: 'System Architecture Standards',
67
+ rules: [
68
+ 'MODULARITY: High cohesion, low coupling.',
69
+ 'EVOLVABILITY: Easy to change. Hard to break.',
70
+ 'SIMPLICITY: Choose boring technology. Innovation tokens are limited.',
71
+ 'DOCS: Architecture Decision Records (ADRs).'
72
+ ]
73
+ }
74
+ },
75
+
76
+ /**
77
+ * Get standards for a specific domain
78
+ * @param {string} domain - The domain (frontend, backend, etc.)
79
+ * @returns {Object} The standards object
80
+ */
81
+ getStandards(domain) {
82
+ const key = domain?.toLowerCase();
83
+ const specific = this.domains[key] || { title: 'General Standards', rules: [] };
84
+
85
+ return {
86
+ title: specific.title,
87
+ rules: [...this.general, ...specific.rules]
88
+ };
89
+ }
90
+ };
91
+
92
+ module.exports = ProductStandards;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * SmartCache - Intelligent Persistent Cache for Agents
3
+ *
4
+ * Cache with specific keys: {projectId}-{domain}-{techStack}
5
+ * Persists to disk for cross-session caching
6
+ * Invalidates only when stack changes
7
+ *
8
+ * @version 1.0.0
9
+ */
10
+
11
+ const fs = require('fs').promises
12
+ const path = require('path')
13
+ const os = require('os')
14
+ const crypto = require('crypto')
15
+
16
+ class SmartCache {
17
+ constructor(projectId = null) {
18
+ this.projectId = projectId
19
+ this.memoryCache = new Map()
20
+ this.cacheDir = path.join(os.homedir(), '.prjct-cli', 'cache')
21
+ this.cacheFile = projectId
22
+ ? path.join(this.cacheDir, `agents-${projectId}.json`)
23
+ : path.join(this.cacheDir, 'agents-global.json')
24
+ }
25
+
26
+ /**
27
+ * Initialize cache - load from disk
28
+ */
29
+ async initialize() {
30
+ try {
31
+ await fs.mkdir(this.cacheDir, { recursive: true })
32
+ await this.loadFromDisk()
33
+ } catch (error) {
34
+ // Cache file doesn't exist yet - that's ok
35
+ this.memoryCache = new Map()
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Generate cache key
41
+ * Format: {projectId}-{domain}-{techStackHash}
42
+ */
43
+ generateKey(projectId, domain, techStack = {}) {
44
+ const techString = JSON.stringify(techStack)
45
+ const techHash = crypto.createHash('md5').update(techString).digest('hex').substring(0, 8)
46
+ return `${projectId || 'global'}-${domain}-${techHash}`
47
+ }
48
+
49
+ /**
50
+ * Get from cache
51
+ */
52
+ async get(key) {
53
+ // Check memory first
54
+ if (this.memoryCache.has(key)) {
55
+ return this.memoryCache.get(key)
56
+ }
57
+
58
+ // Load from disk if not in memory
59
+ await this.loadFromDisk()
60
+ return this.memoryCache.get(key) || null
61
+ }
62
+
63
+ /**
64
+ * Set in cache
65
+ */
66
+ async set(key, value) {
67
+ // Set in memory
68
+ this.memoryCache.set(key, value)
69
+
70
+ // Persist to disk (async, don't wait)
71
+ this.persistToDisk().catch(err => {
72
+ console.error('Cache persist error:', err.message)
73
+ })
74
+ }
75
+
76
+ /**
77
+ * Check if key exists
78
+ */
79
+ async has(key) {
80
+ if (this.memoryCache.has(key)) {
81
+ return true
82
+ }
83
+
84
+ await this.loadFromDisk()
85
+ return this.memoryCache.has(key)
86
+ }
87
+
88
+ /**
89
+ * Clear cache
90
+ */
91
+ async clear() {
92
+ this.memoryCache.clear()
93
+ try {
94
+ await fs.unlink(this.cacheFile)
95
+ } catch {
96
+ // File doesn't exist - that's ok
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Invalidate cache for a project (when stack changes)
102
+ */
103
+ async invalidateProject(projectId) {
104
+ const keysToDelete = []
105
+ for (const key of this.memoryCache.keys()) {
106
+ if (key.startsWith(`${projectId}-`)) {
107
+ keysToDelete.push(key)
108
+ }
109
+ }
110
+
111
+ keysToDelete.forEach(key => this.memoryCache.delete(key))
112
+ await this.persistToDisk()
113
+ }
114
+
115
+ /**
116
+ * Load cache from disk
117
+ */
118
+ async loadFromDisk() {
119
+ try {
120
+ const content = await fs.readFile(this.cacheFile, 'utf-8')
121
+ const data = JSON.parse(content)
122
+
123
+ // Restore to memory cache
124
+ for (const [key, value] of Object.entries(data)) {
125
+ this.memoryCache.set(key, value)
126
+ }
127
+ } catch {
128
+ // File doesn't exist or invalid - start fresh
129
+ this.memoryCache = new Map()
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Persist cache to disk
135
+ */
136
+ async persistToDisk() {
137
+ try {
138
+ const data = Object.fromEntries(this.memoryCache)
139
+ await fs.writeFile(this.cacheFile, JSON.stringify(data, null, 2), 'utf-8')
140
+ } catch (error) {
141
+ // Fail silently - cache is best effort
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Get cache statistics
147
+ */
148
+ getStats() {
149
+ return {
150
+ size: this.memoryCache.size,
151
+ keys: Array.from(this.memoryCache.keys())
152
+ }
153
+ }
154
+ }
155
+
156
+ module.exports = SmartCache
157
+