prjct-cli 0.6.0 → 0.7.0

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 +170 -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 +106 -62
  11. package/core/commands.js +2030 -2211
  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} +4 -2
  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
@@ -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()
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Prompt Builder
3
+ * Builds prompts for Claude based on templates and context
4
+ * Claude decides what to do - NO if/else logic here
5
+ */
6
+
7
+ class PromptBuilder {
8
+ /**
9
+ * Build execution prompt for Claude
10
+ * @param {Object} template - Template from template-loader
11
+ * @param {Object} context - Context from context-builder
12
+ * @param {Object} state - Current state from context-builder
13
+ * @returns {string} Prompt for Claude
14
+ */
15
+ build(template, context, state) {
16
+ const parts = []
17
+
18
+ // 1. Command instructions from template
19
+ parts.push('# Command Instructions\n')
20
+ parts.push(template.content)
21
+ parts.push('\n')
22
+
23
+ // 2. Allowed tools
24
+ if (template.frontmatter['allowed-tools']) {
25
+ parts.push('## Allowed Tools\n')
26
+ parts.push(`You can use: ${template.frontmatter['allowed-tools'].join(', ')}\n\n`)
27
+ }
28
+
29
+ // 3. Project context
30
+ parts.push('## Project Context\n')
31
+ parts.push(`- Project ID: ${context.projectId}\n`)
32
+ parts.push(`- Timestamp: ${context.timestamp}\n`)
33
+ parts.push('\n')
34
+
35
+ // 4. Current state (only non-null files)
36
+ parts.push('## Current State\n')
37
+ for (const [key, content] of Object.entries(state)) {
38
+ if (content && content.trim()) {
39
+ parts.push(`### ${key}\n`)
40
+ parts.push('```\n')
41
+ parts.push(content)
42
+ parts.push('\n```\n\n')
43
+ }
44
+ }
45
+
46
+ // 5. Command parameters
47
+ if (Object.keys(context.params).length > 0) {
48
+ parts.push('## Parameters\n')
49
+ for (const [key, value] of Object.entries(context.params)) {
50
+ parts.push(`- ${key}: ${value}\n`)
51
+ }
52
+ parts.push('\n')
53
+ }
54
+
55
+ // 6. Final instruction
56
+ parts.push('## Execute\n')
57
+ parts.push('Based on the instructions above, execute the command.\n')
58
+ parts.push('Use ONLY the allowed tools.\n')
59
+ parts.push('Make decisions based on context - do not follow rigid if/else rules.\n')
60
+
61
+ return parts.join('')
62
+ }
63
+
64
+ /**
65
+ * Build analysis prompt
66
+ * Used for tasks that need Claude to analyze before acting
67
+ * @param {string} analysisType - Type of analysis
68
+ * @param {Object} context - Context
69
+ * @returns {string} Analysis prompt
70
+ */
71
+ buildAnalysis(analysisType, context) {
72
+ const parts = []
73
+
74
+ parts.push(`# Analyze: ${analysisType}\n\n`)
75
+ parts.push('Read the project context and provide your analysis.\n')
76
+ parts.push('No predetermined patterns - decide based on what you find.\n\n')
77
+
78
+ parts.push('## Project Context\n')
79
+ parts.push(`- Path: ${context.projectPath}\n`)
80
+ parts.push(`- ID: ${context.projectId}\n\n`)
81
+
82
+ return parts.join('')
83
+ }
84
+ }
85
+
86
+ module.exports = new PromptBuilder()
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Template Loader
3
+ * Loads command templates with frontmatter parsing
4
+ * Templates define what Claude should do - NO if/else logic
5
+ */
6
+
7
+ const fs = require('fs').promises
8
+ const path = require('path')
9
+
10
+ class TemplateLoader {
11
+ constructor() {
12
+ this.templatesDir = path.join(__dirname, '..', '..', 'templates', 'commands')
13
+ this.cache = new Map()
14
+ }
15
+
16
+ /**
17
+ * Load template with frontmatter
18
+ * @param {string} commandName - Command name (e.g., 'now', 'done', 'ship')
19
+ * @returns {Promise<{frontmatter: Object, content: string}>}
20
+ */
21
+ async load(commandName) {
22
+ // Check cache first
23
+ if (this.cache.has(commandName)) {
24
+ return this.cache.get(commandName)
25
+ }
26
+
27
+ const templatePath = path.join(this.templatesDir, `${commandName}.md`)
28
+
29
+ try {
30
+ const rawContent = await fs.readFile(templatePath, 'utf-8')
31
+ const parsed = this.parseFrontmatter(rawContent)
32
+
33
+ // Cache result
34
+ this.cache.set(commandName, parsed)
35
+
36
+ return parsed
37
+ } catch (error) {
38
+ throw new Error(`Template not found: ${commandName}.md`)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Parse frontmatter from markdown
44
+ * @param {string} content - Raw markdown content
45
+ * @returns {Object} Parsed template with frontmatter and content
46
+ */
47
+ parseFrontmatter(content) {
48
+ const frontmatterRegex = /^---\n([\s\S]+?)\n---\n([\s\S]*)$/
49
+ const match = content.match(frontmatterRegex)
50
+
51
+ if (!match) {
52
+ return {
53
+ frontmatter: {},
54
+ content: content.trim(),
55
+ }
56
+ }
57
+
58
+ const [, frontmatterText, mainContent] = match
59
+ const frontmatter = {}
60
+
61
+ // Parse frontmatter lines
62
+ frontmatterText.split('\n').forEach((line) => {
63
+ const [key, ...valueParts] = line.split(':')
64
+ if (key && valueParts.length > 0) {
65
+ const value = valueParts.join(':').trim()
66
+
67
+ // Parse arrays
68
+ if (value.startsWith('[') && value.endsWith(']')) {
69
+ frontmatter[key.trim()] = value
70
+ .slice(1, -1)
71
+ .split(',')
72
+ .map((v) => v.trim())
73
+ } else {
74
+ // Remove quotes if present
75
+ frontmatter[key.trim()] = value.replace(/^["']|["']$/g, '')
76
+ }
77
+ }
78
+ })
79
+
80
+ return {
81
+ frontmatter,
82
+ content: mainContent.trim(),
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get allowed tools for a command
88
+ * @param {string} commandName - Command name
89
+ * @returns {Promise<string[]>}
90
+ */
91
+ async getAllowedTools(commandName) {
92
+ const template = await this.load(commandName)
93
+ return template.frontmatter['allowed-tools'] || []
94
+ }
95
+
96
+ /**
97
+ * Clear cache (useful for testing)
98
+ */
99
+ clearCache() {
100
+ this.cache.clear()
101
+ }
102
+ }
103
+
104
+ module.exports = new TemplateLoader()
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Tool Registry
3
+ * Maps allowed-tools from templates to actual functions
4
+ * Simple I/O operations - NO business logic, NO if/else
5
+ */
6
+
7
+ const fs = require('fs').promises
8
+ const path = require('path')
9
+ const { promisify } = require('util')
10
+ const { exec: execCallback } = require('child_process')
11
+ const exec = promisify(execCallback)
12
+
13
+ class ToolRegistry {
14
+ constructor() {
15
+ this.tools = {
16
+ Read: this.read.bind(this),
17
+ Write: this.write.bind(this),
18
+ Bash: this.bash.bind(this),
19
+ Exec: this.bash.bind(this), // Alias
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Get tool function by name
25
+ * @param {string} toolName - Tool name (e.g., 'Read', 'Write')
26
+ * @returns {Function}
27
+ */
28
+ get(toolName) {
29
+ const tool = this.tools[toolName]
30
+ if (!tool) {
31
+ throw new Error(`Unknown tool: ${toolName}`)
32
+ }
33
+ return tool
34
+ }
35
+
36
+ /**
37
+ * Check if tool is allowed
38
+ * @param {string} toolName - Tool name
39
+ * @param {string[]} allowedTools - List of allowed tools
40
+ * @returns {boolean}
41
+ */
42
+ isAllowed(toolName, allowedTools) {
43
+ return allowedTools.includes(toolName)
44
+ }
45
+
46
+ /**
47
+ * Read file
48
+ * @param {string} filePath - File path
49
+ * @returns {Promise<string>}
50
+ */
51
+ async read(filePath) {
52
+ try {
53
+ return await fs.readFile(filePath, 'utf-8')
54
+ } catch (error) {
55
+ return null
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Write file
61
+ * @param {string} filePath - File path
62
+ * @param {string} content - Content to write
63
+ * @returns {Promise<void>}
64
+ */
65
+ async write(filePath, content) {
66
+ // Ensure directory exists
67
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
68
+ await fs.writeFile(filePath, content, 'utf-8')
69
+ }
70
+
71
+ /**
72
+ * Execute bash command
73
+ * @param {string} command - Command to execute
74
+ * @returns {Promise<{stdout: string, stderr: string}>}
75
+ */
76
+ async bash(command) {
77
+ try {
78
+ const { stdout, stderr } = await exec(command)
79
+ return { stdout, stderr }
80
+ } catch (error) {
81
+ return {
82
+ stdout: '',
83
+ stderr: error.message,
84
+ error: true,
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Check if file exists
91
+ * @param {string} filePath - File path
92
+ * @returns {Promise<boolean>}
93
+ */
94
+ async exists(filePath) {
95
+ try {
96
+ await fs.access(filePath)
97
+ return true
98
+ } catch {
99
+ return false
100
+ }
101
+ }
102
+
103
+ /**
104
+ * List directory
105
+ * @param {string} dirPath - Directory path
106
+ * @returns {Promise<string[]>}
107
+ */
108
+ async list(dirPath) {
109
+ try {
110
+ return await fs.readdir(dirPath)
111
+ } catch {
112
+ return []
113
+ }
114
+ }
115
+ }
116
+
117
+ module.exports = new ToolRegistry()