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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.8] - 2025-11-28
4
+
5
+ ### Added
6
+
7
+ - **Minimal Output System** - New `core/utils/output.js` module for clean, minimal CLI output
8
+ - `spin(msg)` - Spinner animation while working
9
+ - `done(msg)` - Single-line success output (✓)
10
+ - `fail(msg)` - Single-line error output (✗)
11
+ - `warn(msg)` - Single-line warning output (⚠)
12
+
13
+ ### Changed
14
+
15
+ - **Reduced CLI Verbosity** - All major commands now show spinner + 1 line result instead of verbose multi-line output
16
+ - `/p:now` - Shows only task name and status
17
+ - `/p:done` - Shows task and duration in one line
18
+ - `/p:next` - Shows queue count only
19
+ - `/p:ship` - Spinner during steps, single line result with version
20
+ - `/p:feature` - Shows task count created
21
+ - `/p:init` - Spinner during initialization, minimal completion message
22
+ - `/p:bug` - Shows severity in one line
23
+ - `/p:context` - Shows task and queue summary
24
+ - `/p:recap` - Shows shipped/queue/ideas counts
25
+ - `/p:stuck` - Logs issue with minimal output
26
+ - `/p:design` - Shows design type created
27
+ - `/p:cleanup` - Shows items cleaned count
28
+ - `/p:progress` - Shows period metrics summary
29
+ - `/p:roadmap` - Shows features count
30
+
31
+ ### Improved
32
+
33
+ - **Resource Efficiency** - Reduced terminal output significantly, consuming less resources
34
+ - **Cleaner UX** - Focus on essential information, no verbose step-by-step logs
35
+
36
+ ## [0.10.7] - 2025-11-27
37
+
38
+ ### Fixed
39
+
40
+ - **Critical Git Safety** - Added explicit protections against destructive git operations
41
+ - Prohibits `git checkout` without checking for uncommitted changes first
42
+ - Prohibits `git reset --hard` and `git clean -fd` operations
43
+ - Requires `git status` check before any git operation
44
+ - Prevents loss of user's work through accidental git operations
45
+
46
+ - **Anti-Hallucination Improvements** - Enhanced instructions to prevent code hallucinations
47
+ - Explicit "READ CODE FIRST" requirement before modifying files
48
+ - Mandatory pattern matching with existing codebase
49
+ - Improved context awareness with file listings
50
+ - Better state filtering to preserve critical information (up to 2000 chars for critical files)
51
+
52
+ ### Enhanced
53
+
54
+ - **Pattern Detection & Enforcement** - Automatic project pattern detection and enforcement
55
+ - Analysis file (`analysis/repo-summary.md`) automatically loaded for all code modification commands
56
+ - Project patterns extracted and displayed in prompts
57
+ - Explicit instructions to follow existing patterns exactly
58
+ - Step-by-step guide to match code style, structure, and conventions
59
+
60
+ - **Context Improvements** - Better context loading and presentation
61
+ - Analysis included automatically for: now, done, next, ship, work, build, design, cleanup, fix, test
62
+ - Project files listed with instructions to use Read tool
63
+ - State filtering improved to show full content for critical files (now, next, context, analysis)
64
+ - Better truncation strategy (full content < 1000 chars, 2000 chars for critical files)
65
+
66
+ - **Prompt Builder Enhancements** - More comprehensive instructions for Claude
67
+ - Anti-hallucination section with 7 critical rules
68
+ - Git safety rules with explicit prohibitions
69
+ - Pattern detection and enforcement section
70
+ - Available project files listing
71
+ - Improved enforcement section with pattern matching requirement
72
+
3
73
  ## [0.10.6] - 2025-11-27
4
74
 
5
75
  ### 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
+ })