prjct-cli 0.6.0 → 0.7.1

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 (83) hide show
  1. package/CHANGELOG.md +67 -6
  2. package/CLAUDE.md +442 -36
  3. package/README.md +47 -54
  4. package/bin/prjct +174 -240
  5. package/core/agentic/command-executor.js +113 -0
  6. package/core/agentic/context-builder.js +85 -0
  7. package/core/agentic/prompt-builder.js +86 -0
  8. package/core/agentic/template-loader.js +104 -0
  9. package/core/agentic/tool-registry.js +117 -0
  10. package/core/command-registry.js +109 -65
  11. package/core/commands.js +2213 -2173
  12. package/core/domain/agent-generator.js +118 -0
  13. package/core/domain/analyzer.js +211 -0
  14. package/core/domain/architect-session.js +300 -0
  15. package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
  16. package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
  17. package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
  18. package/core/{command-installer.js → infrastructure/command-installer.js} +5 -3
  19. package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
  20. package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
  21. package/core/{migrator.js → infrastructure/migrator.js} +34 -19
  22. package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
  23. package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
  24. package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
  25. package/core/{animations-simple.js → utils/animations.js} +3 -23
  26. package/core/utils/date-helper.js +238 -0
  27. package/core/utils/file-helper.js +327 -0
  28. package/core/utils/jsonl-helper.js +206 -0
  29. package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
  30. package/core/utils/session-helper.js +277 -0
  31. package/core/{version.js → utils/version.js} +1 -1
  32. package/package.json +4 -12
  33. package/templates/agents/AGENTS.md +101 -27
  34. package/templates/analysis/analyze.md +84 -0
  35. package/templates/commands/analyze.md +9 -2
  36. package/templates/commands/bug.md +79 -0
  37. package/templates/commands/build.md +5 -2
  38. package/templates/commands/cleanup.md +5 -2
  39. package/templates/commands/design.md +5 -2
  40. package/templates/commands/done.md +4 -2
  41. package/templates/commands/feature.md +113 -0
  42. package/templates/commands/fix.md +41 -10
  43. package/templates/commands/git.md +7 -2
  44. package/templates/commands/help.md +2 -2
  45. package/templates/commands/idea.md +14 -5
  46. package/templates/commands/init.md +62 -7
  47. package/templates/commands/next.md +4 -2
  48. package/templates/commands/now.md +4 -2
  49. package/templates/commands/progress.md +27 -5
  50. package/templates/commands/recap.md +39 -10
  51. package/templates/commands/roadmap.md +19 -5
  52. package/templates/commands/ship.md +118 -16
  53. package/templates/commands/status.md +4 -2
  54. package/templates/commands/sync.md +19 -15
  55. package/templates/commands/task.md +4 -2
  56. package/templates/commands/test.md +5 -2
  57. package/templates/commands/workflow.md +4 -2
  58. package/core/agent-generator.js +0 -525
  59. package/core/analyzer.js +0 -600
  60. package/core/animations.js +0 -277
  61. package/core/ascii-graphics.js +0 -433
  62. package/core/git-integration.js +0 -401
  63. package/core/task-schema.js +0 -342
  64. package/core/workflow-engine.js +0 -213
  65. package/core/workflow-prompts.js +0 -192
  66. package/core/workflow-rules.js +0 -147
  67. package/scripts/post-install.js +0 -121
  68. package/scripts/preuninstall.js +0 -94
  69. package/scripts/verify-installation.sh +0 -158
  70. package/templates/agents/be.template.md +0 -27
  71. package/templates/agents/coordinator.template.md +0 -34
  72. package/templates/agents/data.template.md +0 -27
  73. package/templates/agents/devops.template.md +0 -27
  74. package/templates/agents/fe.template.md +0 -27
  75. package/templates/agents/mobile.template.md +0 -27
  76. package/templates/agents/qa.template.md +0 -27
  77. package/templates/agents/scribe.template.md +0 -29
  78. package/templates/agents/security.template.md +0 -27
  79. package/templates/agents/ux.template.md +0 -27
  80. package/templates/commands/context.md +0 -36
  81. package/templates/commands/stuck.md +0 -36
  82. package/templates/examples/natural-language-examples.md +0 -532
  83. /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
package/bin/prjct CHANGED
@@ -1,247 +1,181 @@
1
1
  #!/usr/bin/env node
2
- const commands = require('../core/commands');
3
- const registry = require('../core/command-registry');
4
- const args = process.argv.slice(2);
2
+
3
+ /**
4
+ * prjct CLI - 100% Agentic Entry Point
5
+ *
6
+ * Uses command-registry as single source of truth.
7
+ * Zero hardcoded logic - all commands resolved dynamically.
8
+ */
9
+
10
+ const PrjctCommands = require('../core/commands')
11
+ const registry = require('../core/command-registry')
5
12
 
6
13
  async function main() {
7
- const command = args[0];
8
- const params = args.slice(1).join(' ');
9
-
10
- let result;
11
- switch(command) {
12
- case 'start':
13
- result = await commands.start();
14
- break;
15
- case 'init':
16
- result = await commands.init();
17
- break;
18
- case 'now':
19
- result = await commands.now(params || null);
20
- break;
21
- case 'done':
22
- result = await commands.done();
23
- break;
24
- case 'ship':
25
- result = await commands.ship(params);
26
- break;
27
- case 'next':
28
- result = await commands.next();
29
- break;
30
- case 'idea':
31
- result = await commands.idea(params);
32
- break;
33
- case 'recap':
34
- result = await commands.recap();
35
- break;
36
- case 'progress':
37
- result = await commands.progress(params || 'week');
38
- break;
39
- case 'stuck':
40
- result = await commands.stuck(params);
41
- break;
42
- case 'context':
43
- result = await commands.context();
44
- break;
45
- case 'cleanup':
46
- result = await commands.cleanup();
47
- break;
48
- case 'cleanup-advanced':
49
- const cleanupOptions = {};
50
- const cleanupArgs = args.slice(1);
51
- let cleanupTarget = '.';
52
-
53
- for (let i = 0; i < cleanupArgs.length; i++) {
54
- if (cleanupArgs[i] === '--type') {
55
- cleanupOptions.type = cleanupArgs[++i];
56
- } else if (cleanupArgs[i] === '--aggressive') {
57
- cleanupOptions.aggressive = true;
58
- } else if (cleanupArgs[i] === '--dry-run') {
59
- cleanupOptions.dryRun = true;
60
- } else if (!cleanupArgs[i].startsWith('--')) {
61
- cleanupTarget = cleanupArgs[i];
62
- }
63
- }
64
-
65
- result = await commands.cleanupAdvanced(cleanupTarget, cleanupOptions);
66
- break;
67
- case 'design':
68
- const designOptions = {};
69
- const designArgs = args.slice(1);
70
- let designTarget = '';
71
-
72
- for (let i = 0; i < designArgs.length; i++) {
73
- if (designArgs[i] === '--type') {
74
- designOptions.type = designArgs[++i];
75
- } else if (designArgs[i] === '--format') {
76
- designOptions.format = designArgs[++i];
77
- } else if (!designArgs[i].startsWith('--')) {
78
- // Collect all non-flag arguments as the target
79
- const targetParts = [];
80
- while (i < designArgs.length && !designArgs[i].startsWith('--')) {
81
- targetParts.push(designArgs[i]);
82
- i++;
83
- }
84
- i--; // Step back one since the loop will increment
85
- designTarget = targetParts.join(' ');
86
- }
87
- }
88
-
89
- if (!designTarget) {
90
- result = {
91
- success: false,
92
- message: 'Please specify what to design: prjct design "authentication system"'
93
- };
94
- } else {
95
- result = await commands.design(designTarget, designOptions);
96
- }
97
- break;
98
- case 'migrate-all':
99
- const migrateOptions = {};
100
- const migrateArgs = args.slice(1);
101
-
102
- for (let i = 0; i < migrateArgs.length; i++) {
103
- if (migrateArgs[i] === '--deep-scan') {
104
- migrateOptions.deepScan = true;
105
- } else if (migrateArgs[i] === '--remove-legacy') {
106
- migrateOptions.removeLegacy = true;
107
- } else if (migrateArgs[i] === '--dry-run') {
108
- migrateOptions.dryRun = true;
109
- }
110
- }
111
-
112
- result = await commands.migrateAll(migrateOptions);
113
- break;
114
- case 'setup':
115
- const setupOptions = {};
116
- const setupArgs = args.slice(1);
117
-
118
- for (let i = 0; i < setupArgs.length; i++) {
119
- if (setupArgs[i] === '--force') {
120
- setupOptions.force = true;
121
- } else if (setupArgs[i] === '--editor') {
122
- setupOptions.editor = setupArgs[++i];
123
- } else if (setupArgs[i] === '--create-templates') {
124
- setupOptions.createTemplates = true;
125
- } else if (setupArgs[i] === '--no-interactive') {
126
- setupOptions.interactive = false;
127
- }
128
- }
129
-
130
- result = await commands.setup(setupOptions);
131
- break;
132
- case 'analyze':
133
- const analyzeOptions = {};
134
- const analyzeArgs = args.slice(1);
135
-
136
- for (let i = 0; i < analyzeArgs.length; i++) {
137
- if (analyzeArgs[i] === '--sync') {
138
- analyzeOptions.sync = true;
139
- } else if (analyzeArgs[i] === '--report-only') {
140
- analyzeOptions.reportOnly = true;
141
- }
142
- }
143
-
144
- result = await commands.analyze(analyzeOptions);
145
- break;
146
- case 'sync':
147
- result = await commands.sync();
148
- break;
149
- case 'workflow':
150
- result = await commands.workflow();
151
- break;
152
- case '--version':
153
- case '-v':
154
- case 'version':
155
- const packageJson = require('../package.json');
156
- result = {
157
- success: true,
158
- message: `prjct-cli v${packageJson.version}`
159
- };
160
- break;
161
- case '--help':
162
- case '-h':
163
- case 'help':
164
- case undefined:
165
- // Generate help message from registry
166
- const categories = registry.getCategories();
167
- const categorizedCommands = {};
168
-
169
- // Group commands by category
170
- registry.getTerminalCommands().forEach(cmd => {
171
- if (!categorizedCommands[cmd.category]) {
172
- categorizedCommands[cmd.category] = [];
173
- }
174
- categorizedCommands[cmd.category].push(cmd);
175
- });
176
-
177
- let helpMessage = 'Available commands:\n';
178
-
179
- // Display commands by category
180
- Object.entries(categorizedCommands).forEach(([categoryKey, cmds]) => {
181
- const categoryInfo = categories[categoryKey];
182
- helpMessage += `\n ${categoryInfo.title}:\n`;
183
-
184
- cmds.forEach(cmd => {
185
- const params = cmd.params ? ` ${cmd.params}` : '';
186
- const spacing = ' '.repeat(Math.max(18 - cmd.name.length - params.length, 1));
187
- helpMessage += ` ${cmd.name}${params}${spacing}${cmd.description}\n`;
188
- });
189
- });
190
-
191
- const stats = registry.getStats();
192
- helpMessage += `\nTotal: ${stats.implemented} implemented / ${stats.total} total commands`;
193
-
194
- result = {
195
- success: true,
196
- message: helpMessage
197
- };
198
- break;
199
- default:
200
- // Check if command exists in registry but not implemented
201
- const registryCmd = registry.getByName(command);
202
-
203
- if (registryCmd && !registryCmd.implemented) {
204
- result = {
205
- success: false,
206
- message: `Command '${command}' exists but is not yet implemented.
207
- Check the roadmap or contribute: https://github.com/your-org/prjct-cli
208
-
209
- Use 'prjct --help' to see all available commands.`
210
- };
211
- } else {
212
- // Generate error message with available commands from registry
213
- const categories = registry.getCategories();
214
- const categorizedCommands = {};
215
-
216
- registry.getTerminalCommands().forEach(cmd => {
217
- if (!categorizedCommands[cmd.category]) {
218
- categorizedCommands[cmd.category] = [];
219
- }
220
- categorizedCommands[cmd.category].push(cmd);
221
- });
222
-
223
- let errorMessage = `Unknown command: ${command}\n\nAvailable commands:\n`;
224
-
225
- Object.entries(categorizedCommands).forEach(([categoryKey, cmds]) => {
226
- const categoryInfo = categories[categoryKey];
227
- errorMessage += `\n ${categoryInfo.title}:\n`;
228
-
229
- cmds.forEach(cmd => {
230
- const params = cmd.params ? ` ${cmd.params}` : '';
231
- const spacing = ' '.repeat(Math.max(18 - cmd.name.length - params.length, 1));
232
- errorMessage += ` ${cmd.name}${params}${spacing}${cmd.description}\n`;
233
- });
234
- });
235
-
236
- result = {
237
- success: false,
238
- message: errorMessage
239
- };
240
- }
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 ===
30
+
31
+ 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()
63
+
64
+ // 6. Execute command
65
+ let result
66
+
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
+ }
90
+
91
+ // 7. Display result
92
+ if (result && result.message) {
93
+ console.log(result.message)
241
94
  }
242
95
 
243
- console.log(result.message);
244
- process.exit(result.success ? 0 : 1);
96
+ process.exit(result && result.success ? 0 : 1)
97
+ } catch (error) {
98
+ console.error('Error:', error.message)
99
+ if (process.env.DEBUG) {
100
+ console.error(error.stack)
101
+ }
102
+ process.exit(1)
103
+ }
104
+ }
105
+
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')
245
172
  }
246
173
 
247
- main().catch(console.error);
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
+ })
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Command Executor
3
+ * Executes commands using templates - Claude decides everything
4
+ * ZERO if/else business logic
5
+ */
6
+
7
+ const templateLoader = require('./template-loader')
8
+ const contextBuilder = require('./context-builder')
9
+ const promptBuilder = require('./prompt-builder')
10
+ const toolRegistry = require('./tool-registry')
11
+
12
+ class CommandExecutor {
13
+ /**
14
+ * Execute a command agentically
15
+ * @param {string} commandName - Command name (e.g., 'now', 'done')
16
+ * @param {Object} params - Command parameters
17
+ * @param {string} projectPath - Project path
18
+ * @returns {Promise<Object>} Execution result
19
+ */
20
+ async execute(commandName, params, projectPath) {
21
+ try {
22
+ // 1. Load template
23
+ const template = await templateLoader.load(commandName)
24
+
25
+ // 2. Build context
26
+ const context = await contextBuilder.build(projectPath, params)
27
+
28
+ // 3. Load current state
29
+ const state = await contextBuilder.loadState(context)
30
+
31
+ // 4. Build prompt for Claude
32
+ const prompt = promptBuilder.build(template, context, state)
33
+
34
+ // 5. Execute (in real implementation, this would call Claude)
35
+ // For now, we return structured data that Claude can work with
36
+ return {
37
+ success: true,
38
+ template,
39
+ context,
40
+ state,
41
+ prompt,
42
+ // In production: result from Claude's execution
43
+ }
44
+ } catch (error) {
45
+ return {
46
+ success: false,
47
+ error: error.message,
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Execute tool with permission check
54
+ * @param {string} toolName - Tool name
55
+ * @param {Array} args - Tool arguments
56
+ * @param {string[]} allowedTools - Allowed tools for this command
57
+ * @returns {Promise<any>}
58
+ */
59
+ async executeTool(toolName, args, allowedTools) {
60
+ // Check if tool is allowed
61
+ if (!toolRegistry.isAllowed(toolName, allowedTools)) {
62
+ throw new Error(`Tool ${toolName} not allowed for this command`)
63
+ }
64
+
65
+ // Get tool function
66
+ const tool = toolRegistry.get(toolName)
67
+
68
+ // Execute tool
69
+ return await tool(...args)
70
+ }
71
+
72
+ /**
73
+ * Simple execution for direct tool access
74
+ * Used by legacy commands during migration
75
+ * @param {string} commandName - Command name
76
+ * @param {Function} executionFn - Function that uses tools
77
+ * @param {string} projectPath - Project path
78
+ * @returns {Promise<Object>}
79
+ */
80
+ async executeSimple(commandName, executionFn, projectPath) {
81
+ try {
82
+ // Load template to get allowed tools
83
+ const template = await templateLoader.load(commandName)
84
+ const allowedTools = template.frontmatter['allowed-tools'] || []
85
+
86
+ // Build context
87
+ const context = await contextBuilder.build(projectPath)
88
+
89
+ // Create tools proxy that checks permissions
90
+ const tools = {
91
+ read: async (filePath) => this.executeTool('Read', [filePath], allowedTools),
92
+ write: async (filePath, content) =>
93
+ this.executeTool('Write', [filePath, content], allowedTools),
94
+ bash: async (command) => this.executeTool('Bash', [command], allowedTools),
95
+ }
96
+
97
+ // Execute user function with tools
98
+ const result = await executionFn(tools, context)
99
+
100
+ return {
101
+ success: true,
102
+ result,
103
+ }
104
+ } catch (error) {
105
+ return {
106
+ success: false,
107
+ error: error.message,
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ module.exports = new CommandExecutor()
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Context Builder
3
+ * Builds project context for Claude to make decisions
4
+ * NO if/else logic - just data collection
5
+ */
6
+
7
+ const fs = require('fs').promises
8
+ const pathManager = require('../infrastructure/path-manager')
9
+ const configManager = require('../infrastructure/config-manager')
10
+
11
+ class ContextBuilder {
12
+ /**
13
+ * Build full project context for Claude
14
+ * @param {string} projectPath - Local project path
15
+ * @param {Object} commandParams - Command-specific parameters
16
+ * @returns {Promise<Object>} Context object
17
+ */
18
+ async build(projectPath, commandParams = {}) {
19
+ const projectId = await configManager.getProjectId(projectPath)
20
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
21
+
22
+ return {
23
+ // Project identification
24
+ projectId,
25
+ projectPath,
26
+ globalPath,
27
+
28
+ // File paths
29
+ paths: {
30
+ now: pathManager.getFilePath(projectId, 'core', 'now.md'),
31
+ next: pathManager.getFilePath(projectId, 'core', 'next.md'),
32
+ context: pathManager.getFilePath(projectId, 'core', 'context.md'),
33
+ shipped: pathManager.getFilePath(projectId, 'progress', 'shipped.md'),
34
+ metrics: pathManager.getFilePath(projectId, 'progress', 'metrics.md'),
35
+ ideas: pathManager.getFilePath(projectId, 'planning', 'ideas.md'),
36
+ roadmap: pathManager.getFilePath(projectId, 'planning', 'roadmap.md'),
37
+ memory: pathManager.getFilePath(projectId, 'memory', 'context.jsonl'),
38
+ analysis: pathManager.getFilePath(projectId, 'analysis', 'repo-summary.md'),
39
+ },
40
+
41
+ // Command parameters
42
+ params: commandParams,
43
+
44
+ // Timestamps
45
+ timestamp: new Date().toISOString(),
46
+ date: new Date().toLocaleString(),
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Load current project state
52
+ * @param {Object} context - Context from build()
53
+ * @returns {Promise<Object>} Current state
54
+ */
55
+ async loadState(context) {
56
+ const state = {}
57
+
58
+ // Read all core files
59
+ for (const [key, filePath] of Object.entries(context.paths)) {
60
+ try {
61
+ state[key] = await fs.readFile(filePath, 'utf-8')
62
+ } catch {
63
+ state[key] = null
64
+ }
65
+ }
66
+
67
+ return state
68
+ }
69
+
70
+ /**
71
+ * Check file existence
72
+ * @param {string} filePath - File path
73
+ * @returns {Promise<boolean>}
74
+ */
75
+ async fileExists(filePath) {
76
+ try {
77
+ await fs.access(filePath)
78
+ return true
79
+ } catch {
80
+ return false
81
+ }
82
+ }
83
+ }
84
+
85
+ module.exports = new ContextBuilder()