prjct-cli 0.7.2 → 0.7.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 CHANGED
@@ -7,6 +7,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.3] - 2025-10-05
11
+
12
+ ### Added
13
+
14
+ - **Comprehensive Testing Documentation** - Complete testing guide and setup documentation
15
+ - **TESTING.md** - Full testing guide with:
16
+ - Quick start commands for running tests
17
+ - Vitest workspace architecture (core + website)
18
+ - Detailed configuration for both Node.js and React environments
19
+ - Testing best practices and examples
20
+ - CI/CD integration documentation
21
+ - Troubleshooting guide
22
+ - Complete command reference
23
+ - **README.md Testing Section** - Quick reference with:
24
+ - Quick start commands
25
+ - Test suites overview (Core CLI + Website)
26
+ - CI/CD automation status
27
+ - Link to comprehensive TESTING.md guide
28
+ - **Documentation Quality**: Professional-grade testing documentation for contributors and developers
29
+ - **Coverage**: Documents all 283 tests across both core and website projects
30
+
31
+ ### Fixed
32
+
33
+ - **Lint Errors** - Removed 3 unused variables to pass ESLint
34
+ - Removed unused `globalProjectPath` in commands.js
35
+ - Removed unused `architectSession` require in commands.js
36
+ - Removed unused `fs` import in session-manager.js
37
+
38
+ ### Technical Details
39
+
40
+ - **Testing Stack**: Vitest with workspace configuration for dual environments
41
+ - **Core Tests** (Node.js): 179 tests for agentic system, commands, and utilities
42
+ - **Website Tests** (React): 104 tests with Testing Library for components
43
+ - **CI/CD**: GitHub Actions workflow with parallel test execution
44
+ - **Coverage**: V8 coverage provider with HTML reports
45
+
46
+ ## [0.7.2] - 2025-10-04
47
+
48
+ ### Fixed
49
+
50
+ - **Vercel Deployment Configuration** - Fixed deployment build and configuration
51
+ - Updated vercel build commands to use npm prefix syntax
52
+ - Moved and simplified vercel.json config
53
+ - Added Vercel deployment config and removed CNAME file
54
+ - Fixed build output directory configuration
55
+
56
+ ### Changed
57
+
58
+ - **Version Bump** - Updated from 0.7.1 to 0.7.2
59
+
60
+ ## [0.7.1] - 2025-10-04
61
+
62
+ ### Added
63
+
64
+ - **Command Implementation Status** - Added setup and migrate-all commands
65
+ - Marked roadmap, status, and build commands as implemented in registry
66
+ - Added isSupported flag to agent detection for Claude and Terminal agents
67
+ - Better clarity on which commands are available in each environment
68
+
69
+ ## [0.7.0] - 2025-10-04
70
+
71
+ ### Added
72
+
73
+ - **Vercel Analytics Integration** - Added Vercel Analytics and Speed Insights tracking
74
+ - Real-time performance monitoring for website
75
+ - User analytics and insights
76
+ - Speed metrics tracking
77
+
78
+ - **AI Policy Page** - New dedicated page for AI usage policies
79
+ - Redesigned footer layout with AI Policy link
80
+ - Clear transparency about AI integration
81
+ - Updated navigation structure
82
+
83
+ ### Changed
84
+
85
+ - **Repository Privacy** - Transitioned from open source to proprietary
86
+ - Removed all GitHub repository links from website
87
+ - Removed open source references
88
+ - Updated Terms of Use with proprietary license
89
+ - Repository made private due to AI agent privacy concerns and terms of use compliance
90
+ - FREE tier remains available to all users indefinitely
91
+ - Contact: jlopezlira@gmail.com or jlopezlira.dev
92
+
93
+ - **Deployment Migration** - Migrated from GitHub Pages to Vercel
94
+ - Better build performance and CDN
95
+ - Automatic deployments
96
+ - Improved website hosting
97
+
98
+ - **Workflow Simplification** - Reduced to 5 essential commands
99
+ - Streamlined developer experience
100
+ - Clearer command structure
101
+ - Focus on core functionality
102
+
103
+ - **Documentation Updates**
104
+ - Added session-based architecture documentation with auto-archiving
105
+ - JSONL logs for better performance
106
+ - Moved Changelog link from header to footer
107
+
108
+ ### Fixed
109
+
110
+ - **TypeScript Build Errors** - Resolved all TypeScript errors in website build
111
+ - Removed unused Globe import from Privacy.tsx
112
+ - Fixed type errors across components
113
+
114
+ ### Technical Details
115
+
116
+ - **Session Architecture**: JSONL-based logging with auto-archiving after 30 days
117
+ - **Deployment**: Vercel platform with automatic builds
118
+ - **Analytics**: Vercel Analytics + Speed Insights integration
119
+ - **Privacy**: Repository now private, free tier remains available
120
+
10
121
  ## [0.6.0] - 2025-10-03
11
122
 
12
123
  ### Changed
package/README.md CHANGED
@@ -14,6 +14,8 @@ Developer momentum tool for indie hackers and small teams.
14
14
 
15
15
  [![Claude Code Ready](https://img.shields.io/badge/Claude%20Code-Ready-6366f1)](CLAUDE.md)
16
16
  [![Claude Desktop Compatible](https://img.shields.io/badge/Claude%20Desktop-Compatible-6366f1)]()
17
+ [![Tests](https://github.com/jlopezlira/prjct-cli/actions/workflows/test.yml/badge.svg)](https://github.com/jlopezlira/prjct-cli/actions/workflows/test.yml)
18
+ [![Website Build](https://img.shields.io/badge/website-deployed-success)](https://prjct.app)
17
19
 
18
20
  ## 🤖 Claude-Native Architecture
19
21
 
@@ -361,6 +363,36 @@ prjct recap # Show progress
361
363
 
362
364
  > 📚 **More workflows:** Visit [prjct.app/workflows-guide](https://prjct.app/workflows-guide) for detailed interactive examples
363
365
 
366
+ ## 🧪 Testing
367
+
368
+ prjct-cli uses **Vitest** with comprehensive test coverage for both the CLI core and website.
369
+
370
+ ### Quick Start
371
+
372
+ ```bash
373
+ # Run all tests
374
+ npm test
375
+
376
+ # Run tests in watch mode
377
+ npm run test:watch
378
+
379
+ # Generate coverage report
380
+ npm run test:coverage
381
+ ```
382
+
383
+ ### Test Suites
384
+
385
+ - **Core CLI** (Node.js) - Agentic system, commands, and utilities
386
+ - **Website** (React + TypeScript) - Component tests with Testing Library
387
+
388
+ ### CI/CD
389
+
390
+ Tests run automatically on every push and pull request via GitHub Actions.
391
+
392
+ [![Tests](https://github.com/jlopezlira/prjct-cli/actions/workflows/test.yml/badge.svg)](https://github.com/jlopezlira/prjct-cli/actions/workflows/test.yml)
393
+
394
+ > 📖 **Full testing guide:** See [TESTING.md](TESTING.md) for detailed documentation, configuration, and best practices.
395
+
364
396
  ## ❓ FAQ
365
397
 
366
398
  **Can I work on multiple tasks?**
@@ -0,0 +1,223 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import commandExecutor from '../../agentic/command-executor.js'
3
+
4
+ describe('Command Executor', () => {
5
+ const testProjectPath = process.cwd()
6
+
7
+ describe('execute()', () => {
8
+ it('should execute command successfully', async () => {
9
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
10
+
11
+ expect(result).toBeDefined()
12
+ expect(result.success).toBe(true)
13
+ })
14
+
15
+ it('should load template', async () => {
16
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
17
+
18
+ expect(result.template).toBeDefined()
19
+ expect(result.template).toHaveProperty('content')
20
+ expect(result.template).toHaveProperty('frontmatter')
21
+ })
22
+
23
+ it('should build context', async () => {
24
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
25
+
26
+ expect(result.context).toBeDefined()
27
+ expect(result.context).toHaveProperty('projectId')
28
+ expect(result.context).toHaveProperty('projectPath')
29
+ expect(result.context).toHaveProperty('paths')
30
+ })
31
+
32
+ it('should load state', async () => {
33
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
34
+
35
+ expect(result.state).toBeDefined()
36
+ expect(typeof result.state).toBe('object')
37
+ })
38
+
39
+ it('should build prompt', async () => {
40
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
41
+
42
+ expect(result.prompt).toBeDefined()
43
+ expect(typeof result.prompt).toBe('string')
44
+ expect(result.prompt.length).toBeGreaterThan(0)
45
+ })
46
+
47
+ it('should pass parameters to context', async () => {
48
+ const params = { taskName: 'Test Task', feature: 'Test Feature' }
49
+ const result = await commandExecutor.execute('now', params, testProjectPath)
50
+
51
+ expect(result.context.params).toEqual(params)
52
+ })
53
+
54
+ it('should handle different commands', async () => {
55
+ const commands = ['now', 'done', 'next', 'ship']
56
+
57
+ for (const cmd of commands) {
58
+ const result = await commandExecutor.execute(cmd, {}, testProjectPath)
59
+ expect(result.success).toBe(true)
60
+ }
61
+ })
62
+
63
+ it('should handle non-existent command', async () => {
64
+ const result = await commandExecutor.execute('nonexistent', {}, testProjectPath)
65
+
66
+ expect(result.success).toBe(false)
67
+ expect(result.error).toBeDefined()
68
+ expect(result.error).toContain('Template not found')
69
+ })
70
+
71
+ it('should include all execution data', async () => {
72
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
73
+
74
+ expect(result).toHaveProperty('success')
75
+ expect(result).toHaveProperty('template')
76
+ expect(result).toHaveProperty('context')
77
+ expect(result).toHaveProperty('state')
78
+ expect(result).toHaveProperty('prompt')
79
+ })
80
+ })
81
+
82
+ describe('executeTool()', () => {
83
+ it('should execute allowed tool', async () => {
84
+ const allowedTools = ['Read', 'Write', 'Bash']
85
+ const result = await commandExecutor.executeTool('Read', [__filename], allowedTools)
86
+
87
+ expect(result).toBeDefined()
88
+ })
89
+
90
+ it('should throw error for disallowed tool', async () => {
91
+ const allowedTools = ['Read']
92
+
93
+ await expect(commandExecutor.executeTool('Write', ['file.txt', 'content'], allowedTools)).rejects.toThrow(
94
+ 'Tool Write not allowed for this command'
95
+ )
96
+ })
97
+
98
+ it('should execute Read tool', async () => {
99
+ const allowedTools = ['Read']
100
+ const content = await commandExecutor.executeTool('Read', [__filename], allowedTools)
101
+
102
+ expect(content).toBeDefined()
103
+ expect(content).toContain('Command Executor')
104
+ })
105
+
106
+ it('should handle tool errors', async () => {
107
+ const allowedTools = ['Read']
108
+
109
+ await expect(commandExecutor.executeTool('UnknownTool', [], allowedTools)).rejects.toThrow()
110
+ })
111
+
112
+ it('should check permissions before execution', async () => {
113
+ const allowedTools = ['Read']
114
+
115
+ // Bash not allowed
116
+ await expect(commandExecutor.executeTool('Bash', ['echo test'], allowedTools)).rejects.toThrow(
117
+ 'not allowed'
118
+ )
119
+ })
120
+ })
121
+
122
+ describe('executeSimple()', () => {
123
+ it('should execute simple command', async () => {
124
+ const executionFn = async (_tools, _context) => {
125
+ return { message: 'Executed successfully' }
126
+ }
127
+
128
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
129
+
130
+ expect(result.success).toBe(true)
131
+ expect(result.result).toEqual({ message: 'Executed successfully' })
132
+ })
133
+
134
+ it('should provide tools to execution function', async () => {
135
+ const executionFn = async (_tools, _context) => {
136
+ expect(_tools).toHaveProperty('read')
137
+ expect(_tools).toHaveProperty('write')
138
+ expect(_tools).toHaveProperty('bash')
139
+ return { tools: 'available' }
140
+ }
141
+
142
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
143
+
144
+ expect(result.success).toBe(true)
145
+ })
146
+
147
+ it('should provide context to execution function', async () => {
148
+ const executionFn = async (_tools, _context) => {
149
+ expect(_context).toBeDefined()
150
+ expect(_context).toHaveProperty('projectId')
151
+ expect(_context).toHaveProperty('projectPath')
152
+ return { context: 'received' }
153
+ }
154
+
155
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
156
+
157
+ expect(result.success).toBe(true)
158
+ })
159
+
160
+ it('should enforce tool permissions', async () => {
161
+ const executionFn = async (_tools, _context) => {
162
+ // Try to use a disallowed tool
163
+ await _tools.write('/test/file.txt', 'content')
164
+ }
165
+
166
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
167
+
168
+ // Should fail if Write is not in allowed tools for 'now'
169
+ expect(result.success).toBe(false)
170
+ })
171
+
172
+ it('should handle execution errors', async () => {
173
+ const executionFn = async (_tools, _context) => {
174
+ throw new Error('Execution failed')
175
+ }
176
+
177
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
178
+
179
+ expect(result.success).toBe(false)
180
+ expect(result.error).toContain('Execution failed')
181
+ })
182
+
183
+ it('should work with allowed tools', async () => {
184
+ const executionFn = async (_tools, _context) => {
185
+ // Read is typically allowed
186
+ const content = await _tools.read(__filename)
187
+ return { readSuccess: content !== null }
188
+ }
189
+
190
+ const result = await commandExecutor.executeSimple('now', executionFn, testProjectPath)
191
+
192
+ expect(result.success).toBe(true)
193
+ expect(result.result.readSuccess).toBe(true)
194
+ })
195
+ })
196
+
197
+ describe('Integration', () => {
198
+ it('should execute full command flow', async () => {
199
+ const result = await commandExecutor.execute('now', { task: 'Test Task' }, testProjectPath)
200
+
201
+ expect(result.success).toBe(true)
202
+ expect(result.template).toBeDefined()
203
+ expect(result.context).toBeDefined()
204
+ expect(result.state).toBeDefined()
205
+ expect(result.prompt).toBeDefined()
206
+
207
+ // Prompt should include the template content
208
+ expect(result.prompt).toContain(result.template.content)
209
+
210
+ // Context should include parameters
211
+ expect(result.context.params.task).toBe('Test Task')
212
+ })
213
+
214
+ it('should build proper prompt structure', async () => {
215
+ const result = await commandExecutor.execute('now', {}, testProjectPath)
216
+
217
+ expect(result.prompt).toContain('# Command Instructions')
218
+ expect(result.prompt).toContain('## Project Context')
219
+ expect(result.prompt).toContain('## Current State')
220
+ expect(result.prompt).toContain('## Execute')
221
+ })
222
+ })
223
+ })
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import contextBuilder from '../../agentic/context-builder.js'
3
+ import path from 'path'
4
+
5
+ describe('Context Builder', () => {
6
+ const testProjectPath = process.cwd()
7
+
8
+ describe('build()', () => {
9
+ it('should build context with all required fields', async () => {
10
+ const context = await contextBuilder.build(testProjectPath)
11
+
12
+ expect(context).toBeDefined()
13
+ expect(context).toHaveProperty('projectId')
14
+ expect(context).toHaveProperty('projectPath')
15
+ expect(context).toHaveProperty('globalPath')
16
+ expect(context).toHaveProperty('paths')
17
+ expect(context).toHaveProperty('params')
18
+ expect(context).toHaveProperty('timestamp')
19
+ expect(context).toHaveProperty('date')
20
+ })
21
+
22
+ it('should include all file paths', async () => {
23
+ const context = await contextBuilder.build(testProjectPath)
24
+
25
+ expect(context.paths).toHaveProperty('now')
26
+ expect(context.paths).toHaveProperty('next')
27
+ expect(context.paths).toHaveProperty('context')
28
+ expect(context.paths).toHaveProperty('shipped')
29
+ expect(context.paths).toHaveProperty('metrics')
30
+ expect(context.paths).toHaveProperty('ideas')
31
+ expect(context.paths).toHaveProperty('roadmap')
32
+ expect(context.paths).toHaveProperty('memory')
33
+ expect(context.paths).toHaveProperty('analysis')
34
+ })
35
+
36
+ it('should use correct project path', async () => {
37
+ const context = await contextBuilder.build(testProjectPath)
38
+
39
+ expect(context.projectPath).toBe(testProjectPath)
40
+ })
41
+
42
+ it('should include command parameters', async () => {
43
+ const params = { taskName: 'test task', feature: 'test feature' }
44
+ const context = await contextBuilder.build(testProjectPath, params)
45
+
46
+ expect(context.params).toEqual(params)
47
+ })
48
+
49
+ it('should include timestamp', async () => {
50
+ const context = await contextBuilder.build(testProjectPath)
51
+
52
+ expect(context.timestamp).toBeDefined()
53
+ expect(typeof context.timestamp).toBe('string')
54
+ expect(new Date(context.timestamp).toString()).not.toBe('Invalid Date')
55
+ })
56
+
57
+ it('should include date', async () => {
58
+ const context = await contextBuilder.build(testProjectPath)
59
+
60
+ expect(context.date).toBeDefined()
61
+ expect(typeof context.date).toBe('string')
62
+ })
63
+
64
+ it('should build global path from project ID', async () => {
65
+ const context = await contextBuilder.build(testProjectPath)
66
+
67
+ expect(context.globalPath).toContain('.prjct-cli')
68
+ expect(context.globalPath).toContain('projects')
69
+ expect(context.globalPath).toContain(context.projectId)
70
+ })
71
+ })
72
+
73
+ describe('loadState()', () => {
74
+ it('should load state from context', async () => {
75
+ const context = await contextBuilder.build(testProjectPath)
76
+ const state = await contextBuilder.loadState(context)
77
+
78
+ expect(state).toBeDefined()
79
+ expect(typeof state).toBe('object')
80
+ })
81
+
82
+ it('should return null for non-existent files', async () => {
83
+ const context = await contextBuilder.build(testProjectPath)
84
+ const state = await contextBuilder.loadState(context)
85
+
86
+ // Some files might not exist
87
+ Object.values(state).forEach((value) => {
88
+ expect(value === null || typeof value === 'string').toBe(true)
89
+ })
90
+ })
91
+
92
+ it('should load existing files as strings', async () => {
93
+ const context = await contextBuilder.build(testProjectPath)
94
+ const state = await contextBuilder.loadState(context)
95
+
96
+ // At least some state values should be strings (if files exist)
97
+ const hasStrings = Object.values(state).some((value) => typeof value === 'string')
98
+ expect(typeof hasStrings).toBe('boolean')
99
+ })
100
+ })
101
+
102
+ describe('fileExists()', () => {
103
+ it('should return true for existing file', async () => {
104
+ const exists = await contextBuilder.fileExists(__filename)
105
+
106
+ expect(exists).toBe(true)
107
+ })
108
+
109
+ it('should return false for non-existent file', async () => {
110
+ const exists = await contextBuilder.fileExists('/nonexistent/path/file.txt')
111
+
112
+ expect(exists).toBe(false)
113
+ })
114
+
115
+ it('should work with package.json', async () => {
116
+ const packagePath = path.join(process.cwd(), 'package.json')
117
+ const exists = await contextBuilder.fileExists(packagePath)
118
+
119
+ expect(exists).toBe(true)
120
+ })
121
+ })
122
+
123
+ describe('Path Construction', () => {
124
+ it('should construct paths with correct structure', async () => {
125
+ const context = await contextBuilder.build(testProjectPath)
126
+
127
+ expect(context.paths.now).toContain('core/now.md')
128
+ expect(context.paths.next).toContain('core/next.md')
129
+ expect(context.paths.context).toContain('core/context.md')
130
+ expect(context.paths.shipped).toContain('progress/shipped.md')
131
+ expect(context.paths.metrics).toContain('progress/metrics.md')
132
+ expect(context.paths.ideas).toContain('planning/ideas.md')
133
+ expect(context.paths.roadmap).toContain('planning/roadmap.md')
134
+ expect(context.paths.memory).toContain('memory/context.jsonl')
135
+ expect(context.paths.analysis).toContain('analysis/repo-summary.md')
136
+ })
137
+
138
+ it('should use global path for all file paths', async () => {
139
+ const context = await contextBuilder.build(testProjectPath)
140
+
141
+ Object.values(context.paths).forEach((filePath) => {
142
+ expect(filePath).toContain(context.globalPath)
143
+ })
144
+ })
145
+ })
146
+
147
+ describe('Empty Parameters', () => {
148
+ it('should handle empty command params', async () => {
149
+ const context = await contextBuilder.build(testProjectPath, {})
150
+
151
+ expect(context.params).toEqual({})
152
+ })
153
+
154
+ it('should handle undefined command params', async () => {
155
+ const context = await contextBuilder.build(testProjectPath)
156
+
157
+ expect(context.params).toEqual({})
158
+ })
159
+ })
160
+ })