prjct-cli 0.8.4 → 0.8.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
@@ -7,6 +7,121 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.8] - 2025-10-06
11
+
12
+ ### Added
13
+
14
+ - **System timestamp tools** - LLM now gets real date/time from system instead of guessing
15
+ - ✅ `GetTimestamp()` tool - Returns ISO timestamp from system clock
16
+ - ✅ `GetDate()` tool - Returns YYYY-MM-DD from system clock
17
+ - ✅ `GetDateTime()` tool - Returns full date/time object with components
18
+ - 🐛 **Bug fixed**: Tasks no longer show January 1st dates when it's October
19
+ - 📊 Impact: Session files now use correct dates, analytics work properly
20
+
21
+ - **Template optimization** - Phase 1 complete: Top 7 critical templates optimized
22
+ - ✅ Reduced from 2006 → 605 lines (69.8% reduction, saved 1401 lines)
23
+ - ✅ All templates now in English only (removed Spanish examples)
24
+ - ✅ Preserved 100% of business logic and decision-making patterns
25
+ - ✅ Removed verbose examples, success criteria, redundant explanations
26
+ - 📊 Impact: Faster LLM processing, lower token usage, clearer instructions
27
+
28
+ - **Legacy installation cleanup** - Automatic detection and removal of curl-based installations
29
+ - ✅ Detects legacy `~/.prjct-cli/` from curl install.sh (pre-v0.8.2)
30
+ - ✅ Migrates project data to npm global location automatically
31
+ - ✅ Removes legacy installation files (bin/, core/, templates/, etc.)
32
+ - ✅ Preserves user data (projects/ directory migrated safely)
33
+ - ✅ Cleans up shell PATH entries (bash/zsh/PowerShell)
34
+ - ✅ Removes legacy symlinks on Unix systems
35
+ - 🎯 **Impact**: Users on old curl installations automatically migrate to npm
36
+ - 🔧 **Windows compatible**: Handles PowerShell profiles, skips Unix-only operations
37
+
38
+ ### Changed
39
+
40
+ - **Critical timestamp rule in CLAUDE.md** - Added prominent warning about LLM timestamp limitations
41
+ - LLM knowledge cutoff is January 2025, cannot generate accurate timestamps
42
+ - All templates now include `timestamp-rule` in frontmatter
43
+ - Templates updated: feature.md, ship.md, now.md, build.md, idea.md
44
+ - Hardcoded example dates replaced with GetTimestamp()/GetDate() tool calls
45
+
46
+ - **Templates optimized** (Phase 1 - Top 7)
47
+ - suggest.md: 555 → 96 lines (82.7% reduction)
48
+ - ask.md: 386 → 73 lines (81.1% reduction)
49
+ - help.md: 348 → 90 lines (74.1% reduction)
50
+ - feature.md: 239 → 93 lines (61.1% reduction)
51
+ - init.md: 210 → 113 lines (46.2% reduction)
52
+ - ship.md: 148 → 79 lines (46.6% reduction)
53
+ - migrate-all.md: 120 → 61 lines (49.2% reduction)
54
+
55
+ - **context-builder.js** - Enhanced timestamp documentation
56
+ - Clarified that timestamps come from system clock, not LLM
57
+ - Added inline comments explaining ISO format and YYYY-MM-DD format
58
+
59
+ - **install.sh deprecation** - Enhanced messaging with legacy detection
60
+ - Now detects if user has legacy curl installation
61
+ - Shows version and location of legacy install
62
+ - Provides clear migration instructions
63
+ - Explains automatic cleanup process
64
+
65
+ - **All Spanish removed from codebase** - 100% English documentation
66
+ - Updated all JSDoc comments to English
67
+ - Removed Spanish examples from templates
68
+ - Fixed: setup.js, postinstall.js, AGENTS.md
69
+
70
+ ### Fixed
71
+
72
+ - **Session date accuracy** - All session files now use correct system date
73
+ - Previously: LLM generated timestamps (often January 1st)
74
+ - Now: System clock provides accurate timestamps via GetTimestamp() tool
75
+ - Duration calculations now accurate
76
+ - Progress tracking and analytics now reliable
77
+
78
+ - **Windows compatibility** - Full cross-platform support
79
+ - Legacy installer detector works on Windows
80
+ - Platform detection via `process.platform === 'win32'`
81
+ - PowerShell profile cleanup (instead of bash/zsh)
82
+ - Skips symlink cleanup on Windows (Unix-only feature)
83
+ - Cross-platform path handling with `os.homedir()` and `path.join()`
84
+ - Command installer already works on Windows (no changes needed)
85
+
86
+ ## [0.8.6] - 2025-10-05
87
+
88
+ ### Fixed
89
+
90
+ - **Critical: Command update system** - Fixed command synchronization to ALWAYS update to latest version
91
+ - ✅ Removed mtime comparison (was causing commands to stay outdated)
92
+ - ✅ ALWAYS overwrites existing commands with latest templates
93
+ - ✅ Guarantees all clients get updated commands on `npm update`
94
+ - ✅ Preserves legacy commands (removed: always 0)
95
+ - 🐛 **Bug fixed**: `now.md`, `done.md` and other commands now update correctly across all client installations
96
+
97
+ - **Setup architecture redesigned** - Dual-defense strategy for reliable setup
98
+ - ✅ First-use setup check in `bin/prjct` (guaranteed execution)
99
+ - ✅ Optional postinstall optimization (when scripts enabled)
100
+ - ✅ Works in CI/CD environments with `--ignore-scripts`
101
+ - ✅ Version tracking via `editors-config.js`
102
+ - ✅ Zero manual steps required
103
+
104
+ ### Changed
105
+
106
+ - **Command sync behavior** (`syncCommands()`)
107
+ - Previous: Compared mtime, only updated if source newer → ❌ Unreliable
108
+ - Now: ALWAYS overwrites existing commands → ✅ Always latest version
109
+ - Preserves legacy/custom commands not in templates
110
+ - Result: `{ added: X, updated: Y, removed: 0 }`
111
+
112
+ - **Setup flow**
113
+ - Centralized all logic in `core/infrastructure/setup.js`
114
+ - `bin/prjct` checks version on every execution
115
+ - `scripts/postinstall.js` simplified to optional helper
116
+ - Auto-setup triggers on version mismatch or first use
117
+
118
+ ### Technical
119
+
120
+ - Reduced `scripts/postinstall.js` from 250 → 69 lines
121
+ - Created `core/infrastructure/setup.js` (167 lines) - single source of truth
122
+ - Modified `bin/prjct` to include version check (34 lines)
123
+ - Removed `.setup-complete` marker (using JSON version tracking instead)
124
+
10
125
  ## [0.8.2] - 2025-10-05
11
126
 
12
127
  ### Changed
package/CLAUDE.md CHANGED
@@ -6,6 +6,40 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
6
6
 
7
7
  **prjct-cli** is a developer momentum tool for solo builders, indie hackers, and small teams (2-5 people). Just ship. No BS. Track progress through slash commands without meetings, ceremonies, or traditional PM overhead.
8
8
 
9
+ ## ⚠️ CRITICAL: Timestamp Management
10
+
11
+ **LLM DOES NOT KNOW CURRENT DATE/TIME** - Your knowledge cutoff is January 2025, and you cannot generate accurate timestamps.
12
+
13
+ ### Timestamp Tools (MUST USE)
14
+
15
+ **ALWAYS use these tools for ALL timestamps and dates:**
16
+
17
+ - `GetTimestamp()` → Returns current system time in ISO format (e.g., "2025-10-07T14:30:00.000Z")
18
+ - `GetDate()` → Returns current date in YYYY-MM-DD format (e.g., "2025-10-07")
19
+ - `GetDateTime()` → Returns object with timestamp, date, year, month, day
20
+
21
+ ### Rules
22
+
23
+ 1. **NEVER generate timestamps manually** - All dates like "2025-10-04" or "2025-01-01" are WRONG
24
+ 2. **ALWAYS call GetTimestamp()** when writing to session files (*.jsonl)
25
+ 3. **ALWAYS call GetDate()** when adding entries to index files (shipped.md, roadmap.md, ideas.md)
26
+ 4. **Templates have `timestamp-rule`** in frontmatter - READ AND FOLLOW IT
27
+ 5. **Session files are organized by date** - Use system date to determine correct file path
28
+
29
+ ### Example (CORRECT)
30
+
31
+ ```jsonl
32
+ {"ts":"{GetTimestamp()}","type":"feature_add","name":"auth","tasks":5}
33
+ ```
34
+
35
+ ### Example (WRONG - DO NOT DO THIS)
36
+
37
+ ```jsonl
38
+ {"ts":"2025-10-04T14:30:00Z","type":"feature_add","name":"auth","tasks":5}
39
+ ```
40
+
41
+ **Why this matters**: Without system timestamps, all session data shows January 1st dates, making analytics and progress tracking completely broken.
42
+
9
43
  ## 🚀 Real-World Workflow (Simplified)
10
44
 
11
45
  prjct follows your **actual** development workflow with 5 essential commands:
package/bin/prjct CHANGED
@@ -1,181 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * prjct CLI - 100% Agentic Entry Point
4
+ * prjct CLI entry point
5
5
  *
6
- * Uses command-registry as single source of truth.
7
- * Zero hardcoded logic - all commands resolved dynamically.
6
+ * Auto-setup on first use (like Astro, Vite, etc.)
8
7
  */
9
8
 
10
- const PrjctCommands = require('../core/commands')
11
- const registry = require('../core/command-registry')
12
-
13
- async function main() {
14
- const [commandName, ...rawArgs] = process.argv.slice(2)
15
-
16
- // === SPECIAL COMMANDS (version, help) ===
17
-
18
- if (['-v', '--version', 'version'].includes(commandName)) {
19
- const packageJson = require('../package.json')
20
- console.log(`prjct-cli v${packageJson.version}`)
21
- process.exit(0)
22
- }
23
-
24
- if (['-h', '--help', 'help', undefined].includes(commandName)) {
25
- displayHelp()
26
- process.exit(0)
27
- }
28
-
29
- // === DYNAMIC COMMAND EXECUTION ===
9
+ const { VERSION } = require('../core/utils/version')
10
+ const editorsConfig = require('../core/infrastructure/editors-config')
30
11
 
12
+ // Ensure setup has run for this version
13
+ ;(async function ensureSetup() {
31
14
  try {
32
- // 1. Find command in registry
33
- const cmd = registry.getByName(commandName)
34
-
35
- if (!cmd) {
36
- console.error(`Unknown command: ${commandName}`)
37
- console.error(`\nUse 'prjct --help' to see available commands.`)
38
- process.exit(1)
39
- }
40
-
41
- // 2. Check if deprecated (before checking implemented)
42
- if (cmd.deprecated) {
43
- console.error(`Command '${commandName}' is deprecated.`)
44
- if (cmd.replacedBy) {
45
- console.error(`Use 'prjct ${cmd.replacedBy}' instead.`)
46
- }
47
- process.exit(1)
48
- }
49
-
50
- // 3. Check if implemented
51
- if (!cmd.implemented) {
52
- console.error(`Command '${commandName}' exists but is not yet implemented.`)
53
- console.error(`Check the roadmap or contribute: https://github.com/jlopezlira/prjct-cli`)
54
- console.error(`\nUse 'prjct --help' to see available commands.`)
55
- process.exit(1)
56
- }
57
-
58
- // 4. Parse arguments based on command definition
59
- const { parsedArgs, options } = parseCommandArgs(cmd, rawArgs)
60
-
61
- // 5. Instantiate commands handler
62
- const commands = new PrjctCommands()
15
+ const lastVersion = await editorsConfig.getLastVersion()
63
16
 
64
- // 6. Execute command
65
- let result
17
+ if (!lastVersion || lastVersion !== VERSION) {
18
+ console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
19
+ console.log('🔧 One-time setup (v' + VERSION + ')...')
20
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
66
21
 
67
- // Commands with special option handling
68
- if (commandName === 'design') {
69
- const target = parsedArgs.join(' ')
70
- result = await commands.design(target, options)
71
- } else if (commandName === 'analyze') {
72
- result = await commands.analyze(options)
73
- } else if (commandName === 'cleanup') {
74
- result = await commands.cleanup(options)
75
- } else if (commandName === 'setup') {
76
- result = await commands.setup(options)
77
- } else if (commandName === 'migrate-all') {
78
- result = await commands.migrateAll(options)
79
- } else if (commandName === 'progress') {
80
- const period = parsedArgs[0] || 'week'
81
- result = await commands.progress(period)
82
- } else if (commandName === 'build') {
83
- const taskOrNumber = parsedArgs.join(' ')
84
- result = await commands.build(taskOrNumber)
85
- } else {
86
- // Standard commands (most common case)
87
- const param = parsedArgs.join(' ') || null
88
- result = await commands[commandName](param)
89
- }
22
+ const setup = require('../core/infrastructure/setup')
23
+ await setup.run()
90
24
 
91
- // 7. Display result
92
- if (result && result.message) {
93
- console.log(result.message)
25
+ console.log('✓ Setup complete!\n')
94
26
  }
95
-
96
- process.exit(result && result.success ? 0 : 1)
97
27
  } catch (error) {
98
- console.error('Error:', error.message)
99
- if (process.env.DEBUG) {
100
- console.error(error.stack)
101
- }
102
- process.exit(1)
28
+ console.error('\n⚠️ Setup warning:', error.message)
29
+ console.error('You can run setup manually: prjct setup\n')
103
30
  }
104
- }
105
31
 
106
- /**
107
- * Parse command arguments dynamically
108
- */
109
- function parseCommandArgs(cmd, rawArgs) {
110
- const parsedArgs = []
111
- const options = {}
112
-
113
- for (let i = 0; i < rawArgs.length; i++) {
114
- const arg = rawArgs[i]
115
-
116
- if (arg.startsWith('--')) {
117
- // Handle flags
118
- const flagName = arg.slice(2)
119
-
120
- // Check if next arg is a value (doesn't start with --)
121
- if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('--')) {
122
- options[flagName] = rawArgs[++i]
123
- } else {
124
- options[flagName] = true
125
- }
126
- } else {
127
- parsedArgs.push(arg)
128
- }
129
- }
130
-
131
- return { parsedArgs, options }
132
- }
133
-
134
- /**
135
- * Display help using registry
136
- */
137
- function displayHelp() {
138
- const categories = registry.getCategories()
139
- const categorizedCommands = {}
140
-
141
- // Group commands by category (exclude deprecated)
142
- registry.getTerminalCommands().forEach((cmd) => {
143
- if (cmd.deprecated) return
144
-
145
- if (!categorizedCommands[cmd.category]) {
146
- categorizedCommands[cmd.category] = []
147
- }
148
- categorizedCommands[cmd.category].push(cmd)
149
- })
150
-
151
- console.log('prjct - Developer momentum tool for solo builders')
152
- console.log('\nAvailable commands:\n')
153
-
154
- // Display commands by category
155
- Object.entries(categorizedCommands).forEach(([categoryKey, cmds]) => {
156
- const categoryInfo = categories[categoryKey]
157
- console.log(` ${categoryInfo.title}:`)
158
-
159
- cmds.forEach((cmd) => {
160
- const params = cmd.params ? ` ${cmd.params}` : ''
161
- const spacing = ' '.repeat(Math.max(20 - cmd.name.length - params.length, 1))
162
- const impl = cmd.implemented ? '' : ' (not implemented)'
163
- console.log(` ${cmd.name}${params}${spacing}${cmd.description}${impl}`)
164
- })
165
-
166
- console.log('')
167
- })
168
-
169
- const stats = registry.getStats()
170
- console.log(`Total: ${stats.implemented} implemented / ${stats.total} commands`)
171
- console.log('\nFor more info: https://prjct.app')
172
- }
173
-
174
- // Run CLI
175
- main().catch((error) => {
176
- console.error('Fatal error:', error.message)
177
- if (process.env.DEBUG) {
178
- console.error(error.stack)
179
- }
180
- process.exit(1)
181
- })
32
+ // Continue to main CLI logic
33
+ require('../core/index')
34
+ })()
@@ -41,9 +41,10 @@ class ContextBuilder {
41
41
  // Command parameters
42
42
  params: commandParams,
43
43
 
44
- // Timestamps
45
- timestamp: new Date().toISOString(),
46
- date: new Date().toLocaleString(),
44
+ // System timestamps (ALWAYS use these, NEVER generate timestamps)
45
+ // LLM does not know current date/time - these are from system clock
46
+ timestamp: new Date().toISOString(), // ISO format: "2025-10-07T14:30:00.000Z"
47
+ date: new Date().toISOString().split('T')[0], // YYYY-MM-DD: "2025-10-07"
47
48
  }
48
49
  }
49
50
 
@@ -9,6 +9,7 @@ const path = require('path')
9
9
  const { promisify } = require('util')
10
10
  const { exec: execCallback } = require('child_process')
11
11
  const exec = promisify(execCallback)
12
+ const dateHelper = require('../utils/date-helper')
12
13
 
13
14
  class ToolRegistry {
14
15
  constructor() {
@@ -17,6 +18,9 @@ class ToolRegistry {
17
18
  Write: this.write.bind(this),
18
19
  Bash: this.bash.bind(this),
19
20
  Exec: this.bash.bind(this), // Alias
21
+ GetTimestamp: this.getTimestamp.bind(this),
22
+ GetDate: this.getDate.bind(this),
23
+ GetDateTime: this.getDateTime.bind(this),
20
24
  }
21
25
  }
22
26
 
@@ -112,6 +116,37 @@ class ToolRegistry {
112
116
  return []
113
117
  }
114
118
  }
119
+
120
+ /**
121
+ * Get current system timestamp (ISO format)
122
+ * LLM MUST use this instead of generating timestamps
123
+ * @returns {Promise<string>} ISO timestamp (e.g., "2025-10-07T14:30:00.000Z")
124
+ */
125
+ async getTimestamp() {
126
+ return dateHelper.getTimestamp()
127
+ }
128
+
129
+ /**
130
+ * Get current system date (YYYY-MM-DD)
131
+ * LLM MUST use this instead of generating dates
132
+ * @returns {Promise<string>} Date string (e.g., "2025-10-07")
133
+ */
134
+ async getDate() {
135
+ return dateHelper.getTodayKey()
136
+ }
137
+
138
+ /**
139
+ * Get current system date/time components
140
+ * @returns {Promise<Object>} Date components {timestamp, date, year, month, day}
141
+ */
142
+ async getDateTime() {
143
+ const now = new Date()
144
+ return {
145
+ timestamp: dateHelper.getTimestamp(),
146
+ date: dateHelper.getTodayKey(),
147
+ ...dateHelper.getYearMonthDay(now),
148
+ }
149
+ }
115
150
  }
116
151
 
117
152
  module.exports = new ToolRegistry()
package/core/commands.js CHANGED
@@ -1126,6 +1126,81 @@ class PrjctCommands {
1126
1126
  * /p:design - Design system architecture, APIs, and components
1127
1127
  * AGENTIC EXECUTION
1128
1128
  */
1129
+ /**
1130
+ * Memory cleanup helper
1131
+ * Rotates large JSONL files, archives old sessions, reports disk usage
1132
+ * @private
1133
+ */
1134
+ async _cleanupMemory(projectPath) {
1135
+ const projectId = await configManager.getProjectId(projectPath)
1136
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
1137
+
1138
+ console.log('📊 Analyzing disk usage...\n')
1139
+
1140
+ const results = {
1141
+ rotated: [],
1142
+ archived: [],
1143
+ totalSize: 0,
1144
+ freedSpace: 0,
1145
+ }
1146
+
1147
+ // 1. Check and rotate large JSONL files
1148
+ const jsonlFiles = [
1149
+ pathManager.getFilePath(projectId, 'memory', 'context.jsonl'),
1150
+ pathManager.getFilePath(projectId, 'progress', 'shipped.md'),
1151
+ pathManager.getFilePath(projectId, 'planning', 'ideas.md'),
1152
+ ]
1153
+
1154
+ for (const filePath of jsonlFiles) {
1155
+ try {
1156
+ const sizeMB = await jsonlHelper.getFileSizeMB(filePath)
1157
+ if (sizeMB > 0) {
1158
+ results.totalSize += sizeMB
1159
+
1160
+ const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(filePath, 10)
1161
+ if (rotated) {
1162
+ results.rotated.push(path.basename(filePath))
1163
+ results.freedSpace += sizeMB
1164
+ }
1165
+ }
1166
+ } catch (error) {
1167
+ // File doesn't exist, skip
1168
+ }
1169
+ }
1170
+
1171
+ // 2. Report disk usage
1172
+ console.log('💾 Disk Usage Report:\n')
1173
+ console.log(` Total size: ${results.totalSize.toFixed(2)}MB`)
1174
+ console.log(` Rotated files: ${results.rotated.length}`)
1175
+
1176
+ if (results.rotated.length > 0) {
1177
+ console.log(` Freed space: ${results.freedSpace.toFixed(2)}MB\n`)
1178
+ results.rotated.forEach((file) => console.log(` ✓ ${file}`))
1179
+ } else {
1180
+ console.log(' ✓ No rotation needed - all files under 10MB\n')
1181
+ }
1182
+
1183
+ // 3. Suggestions
1184
+ console.log('\n💡 Recommendations:\n')
1185
+ console.log(' 1. Claude Code: Compact conversation regularly')
1186
+ console.log(' 2. Exclude from Spotlight: System Settings → Privacy')
1187
+ console.log(' 3. Clear npm cache: npm cache clean --force\n')
1188
+
1189
+ return { success: true, results }
1190
+ }
1191
+
1192
+ /**
1193
+ * Internal cleanup helper for memory during normal cleanup
1194
+ * @private
1195
+ */
1196
+ async _cleanupMemoryInternal(projectPath) {
1197
+ const projectId = await configManager.getProjectId(projectPath)
1198
+
1199
+ // Silently rotate large files
1200
+ const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
1201
+ await jsonlHelper.rotateJsonLinesIfNeeded(memoryPath, 10)
1202
+ }
1203
+
1129
1204
  async design(target = null, options = {}, projectPath = process.cwd()) {
1130
1205
  try {
1131
1206
  const initResult = await this.ensureProjectInit(projectPath)
@@ -1575,6 +1650,13 @@ Process flow for ${target}.
1575
1650
  const initResult = await this.ensureProjectInit(projectPath)
1576
1651
  if (!initResult.success) return initResult
1577
1652
 
1653
+ const isMemoryMode = _options.memory === true || _options.type === 'memory'
1654
+
1655
+ if (isMemoryMode) {
1656
+ console.log('🧹 Memory cleanup...\n')
1657
+ return await this._cleanupMemory(projectPath)
1658
+ }
1659
+
1578
1660
  console.log('🧹 Cleaning up project...\n')
1579
1661
 
1580
1662
  const context = await contextBuilder.build(projectPath)
@@ -1649,6 +1731,8 @@ Process flow for ${target}.
1649
1731
  console.log('✅ Cleanup complete!\n')
1650
1732
  cleaned.forEach((item) => console.log(` • ${item}`))
1651
1733
 
1734
+ await this._cleanupMemoryInternal(projectPath)
1735
+
1652
1736
  await this.logToMemory(projectPath, 'cleanup_performed', {
1653
1737
  items: cleaned.length,
1654
1738
  timestamp: dateHelper.getTimestamp(),