prjct-cli 0.10.6 → 0.10.9

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,112 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.9] - 2025-11-28
4
+
5
+ ### Added
6
+
7
+ - **Code Pattern Detection & Enforcement** - New system to detect and enforce code patterns
8
+ - `templates/analysis/patterns.md` - Template for detecting SOLID, DRY, naming conventions
9
+ - Detects design patterns: Factory, Singleton, Observer, Repository, Strategy
10
+ - Identifies anti-patterns: God classes, spaghetti code, copy-paste, magic numbers
11
+ - Extracts conventions: naming, style, async patterns, error handling
12
+ - Generates recommendations with specific file locations and fixes
13
+
14
+ ### Enhanced
15
+
16
+ - **`/p:sync` Command** - Now includes pattern analysis
17
+ - Samples 5-10 source files across directories
18
+ - Detects SOLID compliance with evidence
19
+ - Extracts naming and style conventions
20
+ - Flags anti-patterns with severity (HIGH/MEDIUM/LOW)
21
+ - Saves analysis to `analysis/patterns.md`
22
+ - Updates CLAUDE.md with patterns summary
23
+
24
+ - **Context Builder** - Added `codePatterns` path for pattern detection
25
+ - All code-modifying commands now load patterns automatically
26
+ - Commands: now, build, feature, design, cleanup, fix, test, spec, work
27
+
28
+ - **Prompt Builder** - Includes patterns in code generation prompts
29
+ - Full patterns file included for code-modifying commands
30
+ - Explicit instruction: "ALL new code MUST respect these patterns"
31
+ - Anti-pattern prevention: "NO anti-patterns allowed"
32
+
33
+ ### Impact
34
+
35
+ - New code automatically follows project conventions
36
+ - Anti-patterns detected and flagged before they're introduced
37
+ - Better code quality, performance, and scalability
38
+ - Reduced technical debt from day one
39
+
40
+ ## [0.10.8] - 2025-11-28
41
+
42
+ ### Added
43
+
44
+ - **Minimal Output System** - New `core/utils/output.js` module for clean, minimal CLI output
45
+ - `spin(msg)` - Spinner animation while working
46
+ - `done(msg)` - Single-line success output (✓)
47
+ - `fail(msg)` - Single-line error output (✗)
48
+ - `warn(msg)` - Single-line warning output (⚠)
49
+
50
+ ### Changed
51
+
52
+ - **Reduced CLI Verbosity** - All major commands now show spinner + 1 line result instead of verbose multi-line output
53
+ - `/p:now` - Shows only task name and status
54
+ - `/p:done` - Shows task and duration in one line
55
+ - `/p:next` - Shows queue count only
56
+ - `/p:ship` - Spinner during steps, single line result with version
57
+ - `/p:feature` - Shows task count created
58
+ - `/p:init` - Spinner during initialization, minimal completion message
59
+ - `/p:bug` - Shows severity in one line
60
+ - `/p:context` - Shows task and queue summary
61
+ - `/p:recap` - Shows shipped/queue/ideas counts
62
+ - `/p:stuck` - Logs issue with minimal output
63
+ - `/p:design` - Shows design type created
64
+ - `/p:cleanup` - Shows items cleaned count
65
+ - `/p:progress` - Shows period metrics summary
66
+ - `/p:roadmap` - Shows features count
67
+
68
+ ### Improved
69
+
70
+ - **Resource Efficiency** - Reduced terminal output significantly, consuming less resources
71
+ - **Cleaner UX** - Focus on essential information, no verbose step-by-step logs
72
+
73
+ ## [0.10.7] - 2025-11-27
74
+
75
+ ### Fixed
76
+
77
+ - **Critical Git Safety** - Added explicit protections against destructive git operations
78
+ - Prohibits `git checkout` without checking for uncommitted changes first
79
+ - Prohibits `git reset --hard` and `git clean -fd` operations
80
+ - Requires `git status` check before any git operation
81
+ - Prevents loss of user's work through accidental git operations
82
+
83
+ - **Anti-Hallucination Improvements** - Enhanced instructions to prevent code hallucinations
84
+ - Explicit "READ CODE FIRST" requirement before modifying files
85
+ - Mandatory pattern matching with existing codebase
86
+ - Improved context awareness with file listings
87
+ - Better state filtering to preserve critical information (up to 2000 chars for critical files)
88
+
89
+ ### Enhanced
90
+
91
+ - **Pattern Detection & Enforcement** - Automatic project pattern detection and enforcement
92
+ - Analysis file (`analysis/repo-summary.md`) automatically loaded for all code modification commands
93
+ - Project patterns extracted and displayed in prompts
94
+ - Explicit instructions to follow existing patterns exactly
95
+ - Step-by-step guide to match code style, structure, and conventions
96
+
97
+ - **Context Improvements** - Better context loading and presentation
98
+ - Analysis included automatically for: now, done, next, ship, work, build, design, cleanup, fix, test
99
+ - Project files listed with instructions to use Read tool
100
+ - State filtering improved to show full content for critical files (now, next, context, analysis)
101
+ - Better truncation strategy (full content < 1000 chars, 2000 chars for critical files)
102
+
103
+ - **Prompt Builder Enhancements** - More comprehensive instructions for Claude
104
+ - Anti-hallucination section with 7 critical rules
105
+ - Git safety rules with explicit prohibitions
106
+ - Pattern detection and enforcement section
107
+ - Available project files listing
108
+ - Improved enforcement section with pattern matching requirement
109
+
3
110
  ## [0.10.6] - 2025-11-27
4
111
 
5
112
  ### Enhanced
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Prompt Builder Tests
3
+ * Tests for optimized prompts with compressed rules
4
+ *
5
+ * OPTIMIZED: Tests updated to match compressed prompt structure
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from 'vitest'
9
+ import promptBuilder from '../../agentic/prompt-builder.js'
10
+
11
+ describe('PromptBuilder', () => {
12
+ let builder
13
+
14
+ beforeEach(() => {
15
+ builder = promptBuilder
16
+ builder._currentContext = null
17
+ })
18
+
19
+ describe('Critical Rules (Compressed)', () => {
20
+ it('should include git safety rules', () => {
21
+ builder._currentContext = { files: [], filteredSize: 0 }
22
+ const rules = builder.buildCriticalRules()
23
+
24
+ expect(rules).toContain('GIT SAFETY')
25
+ expect(rules).toContain('checkout')
26
+ expect(rules).toContain('reset')
27
+ })
28
+
29
+ it('should include read-first requirement', () => {
30
+ builder._currentContext = { files: [], filteredSize: 0 }
31
+ const rules = builder.buildCriticalRules()
32
+
33
+ expect(rules).toContain('READ FIRST')
34
+ expect(rules).toContain('Read tool')
35
+ })
36
+
37
+ it('should include pattern matching requirement', () => {
38
+ builder._currentContext = { files: [], filteredSize: 0 }
39
+ const rules = builder.buildCriticalRules()
40
+
41
+ expect(rules).toContain('MATCH PATTERNS')
42
+ })
43
+
44
+ it('should include no-hallucination rules', () => {
45
+ builder._currentContext = { files: [], filteredSize: 0 }
46
+ const rules = builder.buildCriticalRules()
47
+
48
+ expect(rules).toContain('NO HALLUCINATIONS')
49
+ })
50
+
51
+ it('should show file count in context', () => {
52
+ builder._currentContext = { files: ['a.js', 'b.js', 'c.js'] }
53
+ const rules = builder.buildCriticalRules()
54
+
55
+ expect(rules).toContain('3 files available')
56
+ })
57
+ })
58
+
59
+ describe('State Filtering', () => {
60
+ it('should preserve full content for critical files', () => {
61
+ const state = {
62
+ now: '# NOW\n\n**Test task**\n\nStarted: 2025-01-01',
63
+ next: '# NEXT\n\n## Priority Queue\n\n1. Task 1',
64
+ context: 'Project context information',
65
+ analysis: 'Stack: Node.js\nPatterns: ES6 modules',
66
+ metrics: 'Some metrics data'
67
+ }
68
+
69
+ const filtered = builder.filterRelevantState(state)
70
+
71
+ expect(filtered).toContain('### now')
72
+ expect(filtered).toContain('Test task')
73
+ expect(filtered).toContain('### next')
74
+ expect(filtered).toContain('### context')
75
+ expect(filtered).toContain('### analysis')
76
+ })
77
+
78
+ it('should truncate large non-critical files', () => {
79
+ const largeContent = 'x'.repeat(2000)
80
+ const state = {
81
+ now: '# NOW\n\n**Task**',
82
+ largeFile: largeContent
83
+ }
84
+
85
+ const filtered = builder.filterRelevantState(state)
86
+
87
+ expect(filtered).toContain('### now')
88
+ expect(filtered).toContain('### largeFile')
89
+ expect(filtered).toContain('truncated')
90
+ })
91
+
92
+ it('should return null for empty state', () => {
93
+ const filtered = builder.filterRelevantState({})
94
+ expect(filtered).toBeNull()
95
+ })
96
+ })
97
+
98
+ describe('Context Filtering by Command Type', () => {
99
+ it('should include patterns for code commands', () => {
100
+ const template = {
101
+ frontmatter: { description: 'Build feature', name: 'p:build' },
102
+ content: '## Flow\nBuild something'
103
+ }
104
+
105
+ const context = { projectPath: '/test', files: ['file1.js'] }
106
+ const state = { analysis: 'Stack: Node.js, React' }
107
+
108
+ const prompt = builder.build(template, context, state)
109
+
110
+ expect(prompt).toContain('PATTERNS')
111
+ expect(prompt).toContain('Node.js')
112
+ })
113
+
114
+ it('should NOT include patterns for non-code commands', () => {
115
+ const template = {
116
+ frontmatter: { description: 'Show current task', name: 'p:now' },
117
+ content: '## Flow\nShow task'
118
+ }
119
+
120
+ const context = { projectPath: '/test', files: ['file1.js'] }
121
+ const state = { analysis: 'Stack: Node.js, React' }
122
+
123
+ const prompt = builder.build(template, context, state)
124
+
125
+ // Non-code commands should NOT include patterns section
126
+ expect(prompt).not.toContain('## PATTERNS')
127
+ })
128
+ })
129
+
130
+ describe('Project Files Listing', () => {
131
+ it('should list available files when context has files', () => {
132
+ const template = {
133
+ frontmatter: { description: 'Test command' },
134
+ content: '## Flow\nDo something'
135
+ }
136
+
137
+ const context = {
138
+ projectPath: '/test',
139
+ files: ['src/file1.js', 'src/file2.js', 'tests/test1.js']
140
+ }
141
+
142
+ const state = {}
143
+
144
+ const prompt = builder.build(template, context, state)
145
+
146
+ expect(prompt).toContain('AVAILABLE PROJECT FILES')
147
+ expect(prompt).toContain('file1.js')
148
+ expect(prompt).toContain('Read')
149
+ })
150
+
151
+ it('should show project path when no files listed', () => {
152
+ const template = {
153
+ frontmatter: { description: 'Test command' },
154
+ content: '## Flow\nDo something'
155
+ }
156
+
157
+ const context = { projectPath: '/test/project' }
158
+ const state = {}
159
+
160
+ const prompt = builder.build(template, context, state)
161
+
162
+ expect(prompt).toContain('PROJECT FILES')
163
+ expect(prompt).toContain('/test/project')
164
+ })
165
+ })
166
+
167
+ describe('Build Complete Prompt', () => {
168
+ it('should include all critical sections', () => {
169
+ const template = {
170
+ frontmatter: {
171
+ description: 'Test command',
172
+ 'allowed-tools': ['Read', 'Write']
173
+ },
174
+ content: '## Flow\n1. Do step 1\n2. Do step 2'
175
+ }
176
+
177
+ const context = {
178
+ projectPath: '/test',
179
+ params: { task: 'test task' },
180
+ files: ['file1.js']
181
+ }
182
+
183
+ const state = { now: '# NOW\n\n**Current task**' }
184
+
185
+ const prompt = builder.build(template, context, state)
186
+
187
+ expect(prompt).toContain('TASK:')
188
+ expect(prompt).toContain('TOOLS:')
189
+ expect(prompt).toContain('Flow')
190
+ expect(prompt).toContain('RULES (CRITICAL)')
191
+ expect(prompt).toContain('AVAILABLE PROJECT FILES')
192
+ })
193
+
194
+ it('should be concise (under 2000 chars for simple prompt)', () => {
195
+ const template = {
196
+ frontmatter: { description: 'Test', 'allowed-tools': ['Read'] },
197
+ content: '## Flow\n1. Test'
198
+ }
199
+
200
+ const context = { projectPath: '/test', files: ['a.js'] }
201
+ const state = {}
202
+
203
+ const prompt = builder.build(template, context, state)
204
+
205
+ // Optimized prompts should be under 2000 chars for simple cases
206
+ expect(prompt.length).toBeLessThan(2000)
207
+ })
208
+ })
209
+
210
+ describe('Plan Mode (Compressed)', () => {
211
+ it('should include compact plan mode instructions', () => {
212
+ const template = {
213
+ frontmatter: { description: 'Test' },
214
+ content: '## Flow\nTest'
215
+ }
216
+
217
+ const context = { projectPath: '/test' }
218
+ const state = {}
219
+ const planInfo = { isPlanning: true, allowedTools: ['Read', 'Glob'] }
220
+
221
+ const prompt = builder.build(template, context, state, null, null, null, null, planInfo)
222
+
223
+ expect(prompt).toContain('PLAN MODE')
224
+ expect(prompt).toContain('Read-only')
225
+ expect(prompt).toContain('Tools: Read, Glob')
226
+ })
227
+
228
+ it('should include approval required section', () => {
229
+ const template = {
230
+ frontmatter: { description: 'Test' },
231
+ content: '## Flow\nTest'
232
+ }
233
+
234
+ const context = { projectPath: '/test' }
235
+ const state = {}
236
+ const planInfo = { requiresApproval: true }
237
+
238
+ const prompt = builder.build(template, context, state, null, null, null, null, planInfo)
239
+
240
+ expect(prompt).toContain('APPROVAL REQUIRED')
241
+ expect(prompt).toContain('confirmation')
242
+ })
243
+ })
244
+ })
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Output Module Tests
3
+ * Minimal output system for prjct-cli
4
+ */
5
+
6
+ const out = require('../../utils/output')
7
+
8
+ describe('Output Module', () => {
9
+ let consoleLogSpy
10
+ let stdoutWriteSpy
11
+
12
+ beforeEach(() => {
13
+ // Mock console.log
14
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
15
+ // Mock process.stdout.write
16
+ stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => {})
17
+ // Ensure spinner is stopped before each test
18
+ out.stop()
19
+ })
20
+
21
+ afterEach(() => {
22
+ consoleLogSpy.mockRestore()
23
+ stdoutWriteSpy.mockRestore()
24
+ out.stop()
25
+ })
26
+
27
+ describe('done()', () => {
28
+ it('should output success message with checkmark', () => {
29
+ out.done('task completed')
30
+
31
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
32
+ const output = consoleLogSpy.mock.calls[0][0]
33
+ expect(output).toContain('✓')
34
+ expect(output).toContain('task completed')
35
+ })
36
+
37
+ it('should truncate long messages', () => {
38
+ const longMessage = 'a'.repeat(100)
39
+ out.done(longMessage)
40
+
41
+ const output = consoleLogSpy.mock.calls[0][0]
42
+ // Should be truncated to ~65 chars + checkmark
43
+ expect(output.length).toBeLessThan(80)
44
+ })
45
+
46
+ it('should return self for chaining', () => {
47
+ const result = out.done('test')
48
+ expect(result).toBe(out)
49
+ })
50
+ })
51
+
52
+ describe('fail()', () => {
53
+ it('should output error message with X mark', () => {
54
+ out.fail('something failed')
55
+
56
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
57
+ const output = consoleLogSpy.mock.calls[0][0]
58
+ expect(output).toContain('✗')
59
+ expect(output).toContain('something failed')
60
+ })
61
+
62
+ it('should truncate long error messages', () => {
63
+ const longMessage = 'error '.repeat(50)
64
+ out.fail(longMessage)
65
+
66
+ const output = consoleLogSpy.mock.calls[0][0]
67
+ expect(output.length).toBeLessThan(80)
68
+ })
69
+
70
+ it('should return self for chaining', () => {
71
+ const result = out.fail('test')
72
+ expect(result).toBe(out)
73
+ })
74
+ })
75
+
76
+ describe('warn()', () => {
77
+ it('should output warning message with warning symbol', () => {
78
+ out.warn('be careful')
79
+
80
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1)
81
+ const output = consoleLogSpy.mock.calls[0][0]
82
+ expect(output).toContain('⚠')
83
+ expect(output).toContain('be careful')
84
+ })
85
+
86
+ it('should return self for chaining', () => {
87
+ const result = out.warn('test')
88
+ expect(result).toBe(out)
89
+ })
90
+ })
91
+
92
+ describe('spin()', () => {
93
+ it('should start spinner with message', () => {
94
+ vi.useFakeTimers()
95
+
96
+ out.spin('loading')
97
+
98
+ // Advance timer to trigger interval
99
+ vi.advanceTimersByTime(100)
100
+
101
+ expect(stdoutWriteSpy).toHaveBeenCalled()
102
+ const output = stdoutWriteSpy.mock.calls[0][0]
103
+ expect(output).toContain('loading')
104
+
105
+ out.stop()
106
+ vi.useRealTimers()
107
+ })
108
+
109
+ it('should return self for chaining', () => {
110
+ const result = out.spin('test')
111
+ out.stop()
112
+ expect(result).toBe(out)
113
+ })
114
+ })
115
+
116
+ describe('stop()', () => {
117
+ it('should stop spinner and clear line', () => {
118
+ vi.useFakeTimers()
119
+
120
+ out.spin('loading')
121
+ vi.advanceTimersByTime(100)
122
+
123
+ stdoutWriteSpy.mockClear()
124
+ out.stop()
125
+
126
+ // Should write clearing spaces
127
+ expect(stdoutWriteSpy).toHaveBeenCalled()
128
+
129
+ vi.useRealTimers()
130
+ })
131
+
132
+ it('should be safe to call multiple times', () => {
133
+ expect(() => {
134
+ out.stop()
135
+ out.stop()
136
+ out.stop()
137
+ }).not.toThrow()
138
+ })
139
+
140
+ it('should return self for chaining', () => {
141
+ const result = out.stop()
142
+ expect(result).toBe(out)
143
+ })
144
+ })
145
+
146
+ describe('edge cases', () => {
147
+ it('should handle empty messages', () => {
148
+ expect(() => out.done('')).not.toThrow()
149
+ expect(() => out.fail('')).not.toThrow()
150
+ expect(() => out.warn('')).not.toThrow()
151
+ })
152
+
153
+ it('should handle null/undefined messages', () => {
154
+ expect(() => out.done(null)).not.toThrow()
155
+ expect(() => out.done(undefined)).not.toThrow()
156
+ })
157
+
158
+ it('should handle messages with special characters', () => {
159
+ out.done('test with émojis 🎉 and spëcial çhars')
160
+ expect(consoleLogSpy).toHaveBeenCalled()
161
+ })
162
+ })
163
+ })