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.
- package/CHANGELOG.md +67 -6
- package/CLAUDE.md +442 -36
- package/README.md +47 -54
- package/bin/prjct +170 -240
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +106 -62
- package/core/commands.js +2030 -2211
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +4 -12
- package/templates/agents/AGENTS.md +101 -27
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +9 -2
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +5 -2
- package/templates/commands/cleanup.md +5 -2
- package/templates/commands/design.md +5 -2
- package/templates/commands/done.md +4 -2
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +41 -10
- package/templates/commands/git.md +7 -2
- package/templates/commands/help.md +2 -2
- package/templates/commands/idea.md +14 -5
- package/templates/commands/init.md +62 -7
- package/templates/commands/next.md +4 -2
- package/templates/commands/now.md +4 -2
- package/templates/commands/progress.md +27 -5
- package/templates/commands/recap.md +39 -10
- package/templates/commands/roadmap.md +19 -5
- package/templates/commands/ship.md +118 -16
- package/templates/commands/status.md +4 -2
- package/templates/commands/sync.md +19 -15
- package/templates/commands/task.md +4 -2
- package/templates/commands/test.md +5 -2
- package/templates/commands/workflow.md +4 -2
- package/core/agent-generator.js +0 -525
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/ascii-graphics.js +0 -433
- package/core/git-integration.js +0 -401
- package/core/task-schema.js +0 -342
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -27
- package/templates/agents/coordinator.template.md +0 -34
- package/templates/agents/data.template.md +0 -27
- package/templates/agents/devops.template.md +0 -27
- package/templates/agents/fe.template.md +0 -27
- package/templates/agents/mobile.template.md +0 -27
- package/templates/agents/qa.template.md +0 -27
- package/templates/agents/scribe.template.md +0 -29
- package/templates/agents/security.template.md +0 -27
- package/templates/agents/ux.template.md +0 -27
- package/templates/commands/context.md +0 -36
- package/templates/commands/stuck.md +0 -36
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AgentGenerator - Dynamic agent generation for prjct-cli
|
|
7
|
+
*
|
|
8
|
+
* 100% AGENTIC - Claude decides which agents to create based on project analysis.
|
|
9
|
+
* NO predetermined patterns, NO if/else logic, NO assumptions.
|
|
10
|
+
*
|
|
11
|
+
* @version 0.6.0 - Fully agentic refactor
|
|
12
|
+
*/
|
|
13
|
+
class AgentGenerator {
|
|
14
|
+
constructor(projectId = null) {
|
|
15
|
+
this.projectId = projectId
|
|
16
|
+
|
|
17
|
+
// Agents are stored in global project directory
|
|
18
|
+
if (projectId) {
|
|
19
|
+
this.outputDir = path.join(os.homedir(), '.prjct-cli', 'projects', projectId, 'agents')
|
|
20
|
+
} else {
|
|
21
|
+
// Fallback for backwards compatibility
|
|
22
|
+
this.outputDir = path.join(os.homedir(), '.prjct-cli', 'agents')
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a single agent dynamically
|
|
28
|
+
* Claude (the LLM) decides which agents to create based on project analysis
|
|
29
|
+
*
|
|
30
|
+
* @param {string} agentName - Descriptive name (e.g., 'go-backend', 'vuejs-frontend', 'elixir-api')
|
|
31
|
+
* @param {Object} config - Agent configuration
|
|
32
|
+
* @param {string} config.role - Agent's role description
|
|
33
|
+
* @param {string} config.expertise - Technologies and skills (specific versions, tools)
|
|
34
|
+
* @param {string} config.responsibilities - What the agent handles in THIS project
|
|
35
|
+
* @param {Object} config.projectContext - Project-specific context (optional)
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
async generateDynamicAgent(agentName, config) {
|
|
39
|
+
console.log(` 🤖 Generating ${agentName} agent...`)
|
|
40
|
+
|
|
41
|
+
// Ensure output directory exists
|
|
42
|
+
await fs.mkdir(this.outputDir, { recursive: true })
|
|
43
|
+
|
|
44
|
+
// Create agent content
|
|
45
|
+
const content = `# ${config.role || agentName}
|
|
46
|
+
|
|
47
|
+
## Role
|
|
48
|
+
${config.role || 'Specialist for this project'}
|
|
49
|
+
|
|
50
|
+
## Expertise
|
|
51
|
+
${config.expertise || 'Technologies used in this project'}
|
|
52
|
+
|
|
53
|
+
## Responsibilities
|
|
54
|
+
${config.responsibilities || 'Handle specific aspects of development'}
|
|
55
|
+
|
|
56
|
+
## Project Context
|
|
57
|
+
${config.projectContext ? JSON.stringify(config.projectContext, null, 2) : 'No additional context'}
|
|
58
|
+
|
|
59
|
+
## Guidelines
|
|
60
|
+
- Focus on your area of expertise
|
|
61
|
+
- Collaborate with other agents
|
|
62
|
+
- Follow project conventions
|
|
63
|
+
- Ask clarifying questions when needed
|
|
64
|
+
`
|
|
65
|
+
|
|
66
|
+
// Write agent file
|
|
67
|
+
const outputPath = path.join(this.outputDir, `${agentName}.md`)
|
|
68
|
+
await fs.writeFile(outputPath, content, 'utf-8')
|
|
69
|
+
|
|
70
|
+
console.log(` ✅ ${agentName} agent created`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Remove agents that are no longer needed
|
|
75
|
+
* @param {Array} requiredAgents - List of agents that should exist
|
|
76
|
+
* @returns {Promise<Array>} List of removed agents
|
|
77
|
+
*/
|
|
78
|
+
async cleanupObsoleteAgents(requiredAgents) {
|
|
79
|
+
const removed = []
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const files = await fs.readdir(this.outputDir)
|
|
83
|
+
const agentFiles = files.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
84
|
+
|
|
85
|
+
for (const file of agentFiles) {
|
|
86
|
+
const type = file.replace('.md', '')
|
|
87
|
+
|
|
88
|
+
if (!requiredAgents.includes(type)) {
|
|
89
|
+
const filePath = path.join(this.outputDir, file)
|
|
90
|
+
await fs.unlink(filePath)
|
|
91
|
+
removed.push(type)
|
|
92
|
+
console.log(` 🗑️ ${type.toUpperCase()} agent removed (no longer needed)`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error during cleanup:', error.message)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return removed
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* List all existing agents
|
|
104
|
+
* @returns {Promise<Array>} List of agent names
|
|
105
|
+
*/
|
|
106
|
+
async listAgents() {
|
|
107
|
+
try {
|
|
108
|
+
const files = await fs.readdir(this.outputDir)
|
|
109
|
+
return files
|
|
110
|
+
.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
111
|
+
.map((f) => f.replace('.md', ''))
|
|
112
|
+
} catch {
|
|
113
|
+
return []
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = AgentGenerator
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { promisify } = require('util')
|
|
4
|
+
const { exec: execCallback } = require('child_process')
|
|
5
|
+
const exec = promisify(execCallback)
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CodebaseAnalyzer - Provides helpers for project analysis
|
|
9
|
+
*
|
|
10
|
+
* 100% AGENTIC - No predetermined patterns or regex detection.
|
|
11
|
+
* Claude reads the codebase and decides what's relevant.
|
|
12
|
+
*
|
|
13
|
+
* This class only provides I/O helpers. All analysis logic
|
|
14
|
+
* is driven by Claude reading templates/analysis/analyze.md
|
|
15
|
+
*
|
|
16
|
+
* @version 0.6.0 - Fully agentic refactor
|
|
17
|
+
*/
|
|
18
|
+
class CodebaseAnalyzer {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.projectPath = null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize analyzer for a project
|
|
25
|
+
* @param {string} projectPath - Project path
|
|
26
|
+
*/
|
|
27
|
+
init(projectPath = process.cwd()) {
|
|
28
|
+
this.projectPath = projectPath
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read package.json if it exists
|
|
33
|
+
* @returns {Promise<Object|null>}
|
|
34
|
+
*/
|
|
35
|
+
async readPackageJson() {
|
|
36
|
+
try {
|
|
37
|
+
const packagePath = path.join(this.projectPath, 'package.json')
|
|
38
|
+
const content = await fs.readFile(packagePath, 'utf-8')
|
|
39
|
+
return JSON.parse(content)
|
|
40
|
+
} catch {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read Cargo.toml if it exists
|
|
47
|
+
* @returns {Promise<string|null>}
|
|
48
|
+
*/
|
|
49
|
+
async readCargoToml() {
|
|
50
|
+
try {
|
|
51
|
+
const cargoPath = path.join(this.projectPath, 'Cargo.toml')
|
|
52
|
+
return await fs.readFile(cargoPath, 'utf-8')
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read requirements.txt if it exists
|
|
60
|
+
* @returns {Promise<string|null>}
|
|
61
|
+
*/
|
|
62
|
+
async readRequirements() {
|
|
63
|
+
try {
|
|
64
|
+
const reqPath = path.join(this.projectPath, 'requirements.txt')
|
|
65
|
+
return await fs.readFile(reqPath, 'utf-8')
|
|
66
|
+
} catch {
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read go.mod if it exists
|
|
73
|
+
* @returns {Promise<string|null>}
|
|
74
|
+
*/
|
|
75
|
+
async readGoMod() {
|
|
76
|
+
try {
|
|
77
|
+
const goModPath = path.join(this.projectPath, 'go.mod')
|
|
78
|
+
return await fs.readFile(goModPath, 'utf-8')
|
|
79
|
+
} catch {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* List all directories in project root
|
|
86
|
+
* @returns {Promise<string[]>}
|
|
87
|
+
*/
|
|
88
|
+
async listDirectories() {
|
|
89
|
+
try {
|
|
90
|
+
const entries = await fs.readdir(this.projectPath, { withFileTypes: true })
|
|
91
|
+
return entries
|
|
92
|
+
.filter((entry) => entry.isDirectory())
|
|
93
|
+
.map((entry) => entry.name)
|
|
94
|
+
.filter((name) => !name.startsWith('.') && name !== 'node_modules')
|
|
95
|
+
} catch {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get git log (last N commits)
|
|
102
|
+
* @param {number} limit - Number of commits
|
|
103
|
+
* @returns {Promise<string>}
|
|
104
|
+
*/
|
|
105
|
+
async getGitLog(limit = 50) {
|
|
106
|
+
try {
|
|
107
|
+
const { stdout } = await exec(`git log -n ${limit} --pretty=format:"%h|%an|%ar|%s"`, {
|
|
108
|
+
cwd: this.projectPath,
|
|
109
|
+
})
|
|
110
|
+
return stdout
|
|
111
|
+
} catch {
|
|
112
|
+
return ''
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get git statistics
|
|
118
|
+
* @returns {Promise<Object>}
|
|
119
|
+
*/
|
|
120
|
+
async getGitStats() {
|
|
121
|
+
try {
|
|
122
|
+
const { stdout: totalCommits } = await exec('git rev-list --count HEAD', {
|
|
123
|
+
cwd: this.projectPath,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const { stdout: contributors } = await exec('git log --format="%an" | sort -u | wc -l', {
|
|
127
|
+
cwd: this.projectPath,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const { stdout: firstCommit } = await exec(
|
|
131
|
+
'git log --reverse --pretty=format:"%ar" | head -1',
|
|
132
|
+
{ cwd: this.projectPath }
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
totalCommits: parseInt(totalCommits.trim()) || 0,
|
|
137
|
+
contributors: parseInt(contributors.trim()) || 0,
|
|
138
|
+
age: firstCommit.trim() || 'unknown',
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
return {
|
|
142
|
+
totalCommits: 0,
|
|
143
|
+
contributors: 0,
|
|
144
|
+
age: 'unknown',
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Count total files (excluding common ignore patterns)
|
|
151
|
+
* @returns {Promise<number>}
|
|
152
|
+
*/
|
|
153
|
+
async countFiles() {
|
|
154
|
+
try {
|
|
155
|
+
const { stdout } = await exec(
|
|
156
|
+
'find . -type f ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/dist/*" | wc -l',
|
|
157
|
+
{ cwd: this.projectPath }
|
|
158
|
+
)
|
|
159
|
+
return parseInt(stdout.trim()) || 0
|
|
160
|
+
} catch {
|
|
161
|
+
return 0
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if file exists
|
|
167
|
+
* @param {string} filename - Filename to check
|
|
168
|
+
* @returns {Promise<boolean>}
|
|
169
|
+
*/
|
|
170
|
+
async fileExists(filename) {
|
|
171
|
+
try {
|
|
172
|
+
await fs.access(path.join(this.projectPath, filename))
|
|
173
|
+
return true
|
|
174
|
+
} catch {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Read any file in the project
|
|
181
|
+
* @param {string} relativePath - Path relative to project root
|
|
182
|
+
* @returns {Promise<string|null>}
|
|
183
|
+
*/
|
|
184
|
+
async readFile(relativePath) {
|
|
185
|
+
try {
|
|
186
|
+
const fullPath = path.join(this.projectPath, relativePath)
|
|
187
|
+
return await fs.readFile(fullPath, 'utf-8')
|
|
188
|
+
} catch {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Find files matching a pattern
|
|
195
|
+
* @param {string} pattern - Glob pattern
|
|
196
|
+
* @returns {Promise<string[]>}
|
|
197
|
+
*/
|
|
198
|
+
async findFiles(pattern) {
|
|
199
|
+
try {
|
|
200
|
+
const { stdout } = await exec(
|
|
201
|
+
`find . -type f -name "${pattern}" ! -path "*/node_modules/*" ! -path "*/.git/*"`,
|
|
202
|
+
{ cwd: this.projectPath }
|
|
203
|
+
)
|
|
204
|
+
return stdout.trim().split('\n').filter(Boolean)
|
|
205
|
+
} catch {
|
|
206
|
+
return []
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = new CodebaseAnalyzer()
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architect Session Manager
|
|
3
|
+
* Handles conversational state for ARCHITECT MODE (Agent-based, not deterministic)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
class ArchitectSession {
|
|
10
|
+
/**
|
|
11
|
+
* Initialize new architect session
|
|
12
|
+
* @param {string} idea - Project idea
|
|
13
|
+
* @param {string} projectType - Type of project (web, api, mobile, cli, data)
|
|
14
|
+
* @param {string} globalPath - Global project path
|
|
15
|
+
* @returns {Promise<Object>} Session object
|
|
16
|
+
*/
|
|
17
|
+
async init(idea, projectType, globalPath) {
|
|
18
|
+
const session = {
|
|
19
|
+
idea,
|
|
20
|
+
projectType,
|
|
21
|
+
active: true,
|
|
22
|
+
startedAt: new Date().toISOString(),
|
|
23
|
+
conversation: [], // Free-form Q&A logging
|
|
24
|
+
answers: {}, // Key insights for plan generation
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await this.save(session, globalPath)
|
|
28
|
+
return session
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load active session
|
|
33
|
+
*/
|
|
34
|
+
async load(globalPath) {
|
|
35
|
+
try {
|
|
36
|
+
const sessionPath = path.join(globalPath, 'planning', 'architect-session.json')
|
|
37
|
+
const content = await fs.readFile(sessionPath, 'utf-8')
|
|
38
|
+
return JSON.parse(content)
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Save session to disk
|
|
46
|
+
*/
|
|
47
|
+
async save(session, globalPath) {
|
|
48
|
+
const planningDir = path.join(globalPath, 'planning')
|
|
49
|
+
await fs.mkdir(planningDir, { recursive: true })
|
|
50
|
+
|
|
51
|
+
const sessionPath = path.join(planningDir, 'architect-session.json')
|
|
52
|
+
await fs.writeFile(sessionPath, JSON.stringify(session, null, 2))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Log a Q&A pair to the conversation
|
|
57
|
+
* @param {string} question - The question asked by Claude
|
|
58
|
+
* @param {string} answer - User's answer
|
|
59
|
+
* @param {string} globalPath - Global project path
|
|
60
|
+
*/
|
|
61
|
+
async logQA(question, answer, globalPath) {
|
|
62
|
+
const session = await this.load(globalPath)
|
|
63
|
+
|
|
64
|
+
if (!session || !session.active) {
|
|
65
|
+
throw new Error('No active architect session')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
session.conversation.push({
|
|
69
|
+
question,
|
|
70
|
+
answer,
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
await this.save(session, globalPath)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Save key insight for plan generation
|
|
79
|
+
* @param {string} key - Insight key (e.g., 'language', 'framework', 'database')
|
|
80
|
+
* @param {string} value - Insight value
|
|
81
|
+
* @param {string} globalPath - Global project path
|
|
82
|
+
*/
|
|
83
|
+
async saveInsight(key, value, globalPath) {
|
|
84
|
+
const session = await this.load(globalPath)
|
|
85
|
+
|
|
86
|
+
if (!session || !session.active) {
|
|
87
|
+
throw new Error('No active architect session')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
session.answers[key] = value
|
|
91
|
+
await this.save(session, globalPath)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Complete session and generate plan
|
|
96
|
+
* @param {string} globalPath - Global project path
|
|
97
|
+
* @returns {Promise<Object>} Summary object
|
|
98
|
+
*/
|
|
99
|
+
async complete(globalPath) {
|
|
100
|
+
const session = await this.load(globalPath)
|
|
101
|
+
|
|
102
|
+
if (!session || !session.active) {
|
|
103
|
+
throw new Error('No active architect session')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Generate plan MD
|
|
107
|
+
await this.generatePlan(session, globalPath)
|
|
108
|
+
|
|
109
|
+
// Mark session as complete
|
|
110
|
+
session.active = false
|
|
111
|
+
session.completedAt = new Date().toISOString()
|
|
112
|
+
await this.save(session, globalPath)
|
|
113
|
+
|
|
114
|
+
return this.buildSummary(session)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate architect plan MD file
|
|
119
|
+
*/
|
|
120
|
+
async generatePlan(session, globalPath) {
|
|
121
|
+
const plan = this.buildPlanMarkdown(session)
|
|
122
|
+
|
|
123
|
+
const planPath = path.join(globalPath, 'planning', 'architect-session.md')
|
|
124
|
+
await fs.writeFile(planPath, plan)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build plan markdown content
|
|
129
|
+
*/
|
|
130
|
+
buildPlanMarkdown(session) {
|
|
131
|
+
const { projectType, idea, conversation, answers } = session
|
|
132
|
+
|
|
133
|
+
// Build conversation log
|
|
134
|
+
const conversationLog = conversation
|
|
135
|
+
.map((qa, i) => `### Q${i + 1}: ${qa.question}\n**A**: ${qa.answer}\n\n_${qa.timestamp}_`)
|
|
136
|
+
.join('\n\n')
|
|
137
|
+
|
|
138
|
+
// Build stack summary from answers
|
|
139
|
+
const stackSummary = this.buildStackSummary(answers)
|
|
140
|
+
|
|
141
|
+
// Build Context7 queries
|
|
142
|
+
const context7Queries = this.buildContext7Queries(answers)
|
|
143
|
+
|
|
144
|
+
// Build implementation steps
|
|
145
|
+
const steps = this.buildImplementationSteps(session)
|
|
146
|
+
|
|
147
|
+
return `# ARCHITECT SESSION: ${idea}
|
|
148
|
+
|
|
149
|
+
## Project Idea
|
|
150
|
+
${idea}
|
|
151
|
+
|
|
152
|
+
## Project Type
|
|
153
|
+
${projectType}
|
|
154
|
+
|
|
155
|
+
## Discovery Conversation
|
|
156
|
+
|
|
157
|
+
${conversationLog}
|
|
158
|
+
|
|
159
|
+
## Architecture Summary
|
|
160
|
+
|
|
161
|
+
**Stack:**
|
|
162
|
+
${stackSummary}
|
|
163
|
+
|
|
164
|
+
## Implementation Plan
|
|
165
|
+
|
|
166
|
+
**Context7 Queries:**
|
|
167
|
+
${context7Queries.map((q) => `- "${q}"`).join('\n')}
|
|
168
|
+
|
|
169
|
+
**Implementation Steps:**
|
|
170
|
+
${steps.map((step, i) => `${i + 1}. ${step}`).join('\n')}
|
|
171
|
+
|
|
172
|
+
## Execution
|
|
173
|
+
|
|
174
|
+
This plan is ready to be executed.
|
|
175
|
+
|
|
176
|
+
**To generate code:**
|
|
177
|
+
\`\`\`
|
|
178
|
+
/p:architect execute
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
The agent will:
|
|
182
|
+
1. Read this architectural plan
|
|
183
|
+
2. Use Context7 to fetch official documentation
|
|
184
|
+
3. Generate project structure following best practices
|
|
185
|
+
4. Create starter files with boilerplate code
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
Generated: ${new Date().toISOString()}
|
|
189
|
+
`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Build stack summary from answers
|
|
194
|
+
*/
|
|
195
|
+
buildStackSummary(answers) {
|
|
196
|
+
const parts = []
|
|
197
|
+
|
|
198
|
+
for (const [key, value] of Object.entries(answers)) {
|
|
199
|
+
if (value && value !== 'Ninguna' && value !== 'None' && value !== 'Otro') {
|
|
200
|
+
parts.push(`- **${key}**: ${value}`)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return parts.length > 0 ? parts.join('\n') : '- To be determined during implementation'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Build Context7 queries from answers
|
|
209
|
+
*/
|
|
210
|
+
buildContext7Queries(answers) {
|
|
211
|
+
const queries = []
|
|
212
|
+
|
|
213
|
+
if (answers.framework) {
|
|
214
|
+
queries.push(`${answers.framework} getting started`)
|
|
215
|
+
queries.push(`${answers.framework} project structure`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (answers.language && answers.framework) {
|
|
219
|
+
queries.push(`${answers.language} ${answers.framework} best practices`)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (answers.database && answers.language) {
|
|
223
|
+
queries.push(`${answers.database} ${answers.language} integration`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (answers.auth) {
|
|
227
|
+
queries.push(`${answers.auth} implementation ${answers.language || ''}`.trim())
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Always include general queries if specific ones not available
|
|
231
|
+
if (queries.length === 0 && answers.language) {
|
|
232
|
+
queries.push(`${answers.language} project structure`)
|
|
233
|
+
queries.push(`${answers.language} best practices`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return queries
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build implementation steps from session
|
|
241
|
+
*/
|
|
242
|
+
buildImplementationSteps(session) {
|
|
243
|
+
const { answers } = session
|
|
244
|
+
const steps = []
|
|
245
|
+
|
|
246
|
+
// Generic steps - Claude will refine during execution
|
|
247
|
+
if (answers.language) {
|
|
248
|
+
steps.push(`Initialize ${answers.language} project`)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (answers.framework) {
|
|
252
|
+
steps.push(`Setup ${answers.framework}`)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (answers.database) {
|
|
256
|
+
steps.push(`Configure ${answers.database}`)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (answers.auth) {
|
|
260
|
+
steps.push(`Implement ${answers.auth}`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
steps.push('Create project structure')
|
|
264
|
+
steps.push('Generate starter files')
|
|
265
|
+
|
|
266
|
+
if (answers.deployment) {
|
|
267
|
+
steps.push(`Setup ${answers.deployment} configuration`)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return steps
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Build summary of session
|
|
275
|
+
*/
|
|
276
|
+
buildSummary(session) {
|
|
277
|
+
return {
|
|
278
|
+
idea: session.idea,
|
|
279
|
+
projectType: session.projectType,
|
|
280
|
+
conversationLength: session.conversation.length,
|
|
281
|
+
insights: Object.keys(session.answers).length,
|
|
282
|
+
startedAt: session.startedAt,
|
|
283
|
+
completedAt: session.completedAt || new Date().toISOString(),
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Clear architect session
|
|
289
|
+
*/
|
|
290
|
+
async clear(globalPath) {
|
|
291
|
+
try {
|
|
292
|
+
const sessionPath = path.join(globalPath, 'planning', 'architect-session.json')
|
|
293
|
+
await fs.unlink(sessionPath)
|
|
294
|
+
} catch {
|
|
295
|
+
// Ignore if doesn't exist
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = new ArchitectSession()
|
|
@@ -39,9 +39,7 @@ class ClaudeAgent {
|
|
|
39
39
|
if (global.mcp && global.mcp.filesystem) {
|
|
40
40
|
return await global.mcp.filesystem.read(filePath)
|
|
41
41
|
}
|
|
42
|
-
} catch (e) {
|
|
43
|
-
|
|
44
|
-
}
|
|
42
|
+
} catch (e) {}
|
|
45
43
|
|
|
46
44
|
return await fs.readFile(filePath, 'utf8')
|
|
47
45
|
}
|
|
@@ -54,9 +52,7 @@ class ClaudeAgent {
|
|
|
54
52
|
if (global.mcp && global.mcp.filesystem) {
|
|
55
53
|
return await global.mcp.filesystem.write(filePath, content)
|
|
56
54
|
}
|
|
57
|
-
} catch (e) {
|
|
58
|
-
|
|
59
|
-
}
|
|
55
|
+
} catch (e) {}
|
|
60
56
|
|
|
61
57
|
await fs.writeFile(filePath, content, 'utf8')
|
|
62
58
|
}
|
|
@@ -69,9 +65,7 @@ class ClaudeAgent {
|
|
|
69
65
|
if (global.mcp && global.mcp.filesystem) {
|
|
70
66
|
return await global.mcp.filesystem.list(dirPath)
|
|
71
67
|
}
|
|
72
|
-
} catch (e) {
|
|
73
|
-
|
|
74
|
-
}
|
|
68
|
+
} catch (e) {}
|
|
75
69
|
|
|
76
70
|
return await fs.readdir(dirPath)
|
|
77
71
|
}
|
|
@@ -131,7 +125,11 @@ ${data.recentActivity ? '\n' + data.recentActivity : ''}`
|
|
|
131
125
|
*/
|
|
132
126
|
formatProgress(data) {
|
|
133
127
|
const trend =
|
|
134
|
-
data.velocity > data.previousVelocity
|
|
128
|
+
data.velocity > data.previousVelocity
|
|
129
|
+
? '📈'
|
|
130
|
+
: data.velocity < data.previousVelocity
|
|
131
|
+
? '📉'
|
|
132
|
+
: '➡️'
|
|
135
133
|
|
|
136
134
|
return `📊 ${data.period}
|
|
137
135
|
|
|
@@ -145,14 +143,16 @@ ${data.recentFeatures || ''}`
|
|
|
145
143
|
*/
|
|
146
144
|
getHelpContent(issue) {
|
|
147
145
|
const helps = {
|
|
148
|
-
debugging:
|
|
146
|
+
debugging:
|
|
147
|
+
'🔍 1. Isolate code causing error\n2. Add logs at key points\n3. Search exact error message',
|
|
149
148
|
design: '🎨 1. Define problem clearly\n2. Start with simplest solution\n3. Ship MVP, iterate',
|
|
150
149
|
performance:
|
|
151
150
|
'⚡ 1. Profile/measure first\n2. Optimize slowest parts\n3. Cache expensive operations',
|
|
152
151
|
default: '💡 1. Break into smaller tasks\n2. Start with simplest part\n3. Ship it',
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
const helpType =
|
|
154
|
+
const helpType =
|
|
155
|
+
Object.keys(helps).find((key) => issue.toLowerCase().includes(key)) || 'default'
|
|
156
156
|
return helps[helpType]
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -194,9 +194,12 @@ Or type /p:help to see all options`,
|
|
|
194
194
|
Or: /p:now | /p:task | /p:idea`,
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
return
|
|
197
|
+
return (
|
|
198
|
+
suggestions[context] ||
|
|
199
|
+
`What would you like to do?
|
|
198
200
|
|
|
199
201
|
Type /p:help to see all options`
|
|
202
|
+
)
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -175,7 +175,9 @@ class AuthorDetector {
|
|
|
175
175
|
const recommendations = []
|
|
176
176
|
|
|
177
177
|
if (!hasGitHub && !author.github) {
|
|
178
|
-
recommendations.push(
|
|
178
|
+
recommendations.push(
|
|
179
|
+
'Install GitHub CLI (gh) for better collaboration support: https://cli.github.com/'
|
|
180
|
+
)
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
if (!hasGit) {
|