prjct-cli 0.5.0 → 0.6.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 +169 -1
- package/CLAUDE.md +43 -28
- package/README.md +4 -4
- package/bin/prjct +78 -63
- package/core/agent-generator.js +19 -10
- package/core/ascii-graphics.js +433 -0
- package/core/command-registry.js +553 -0
- package/core/commands.js +274 -62
- package/core/task-schema.js +342 -0
- package/package.json +4 -3
- package/templates/agents/AGENTS.md +79 -101
- package/templates/agents/be.template.md +14 -29
- package/templates/agents/coordinator.template.md +34 -0
- package/templates/agents/data.template.md +14 -28
- package/templates/agents/devops.template.md +14 -28
- package/templates/agents/fe.template.md +14 -29
- package/templates/agents/mobile.template.md +14 -28
- package/templates/agents/qa.template.md +14 -41
- package/templates/agents/scribe.template.md +15 -81
- package/templates/agents/security.template.md +14 -28
- package/templates/agents/ux.template.md +14 -36
- package/templates/commands/analyze.md +36 -239
- package/templates/commands/build.md +41 -0
- package/templates/commands/cleanup.md +24 -87
- package/templates/commands/context.md +24 -93
- package/templates/commands/design.md +20 -98
- package/templates/commands/done.md +16 -181
- package/templates/commands/fix.md +27 -66
- package/templates/commands/git.md +33 -60
- package/templates/commands/help.md +18 -52
- package/templates/commands/idea.md +11 -36
- package/templates/commands/init.md +30 -277
- package/templates/commands/next.md +20 -62
- package/templates/commands/now.md +18 -22
- package/templates/commands/progress.md +23 -78
- package/templates/commands/recap.md +22 -74
- package/templates/commands/roadmap.md +21 -90
- package/templates/commands/ship.md +26 -161
- package/templates/commands/status.md +40 -0
- package/templates/commands/stuck.md +21 -33
- package/templates/commands/sync.md +19 -209
- package/templates/commands/task.md +18 -80
- package/templates/commands/test.md +23 -72
- package/templates/commands/workflow.md +20 -212
- package/templates/agents/pm.template.md +0 -84
package/core/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ const { promisify } = require('util')
|
|
|
4
4
|
const { exec: execCallback } = require('child_process')
|
|
5
5
|
const exec = promisify(execCallback)
|
|
6
6
|
const agentDetector = require('./agent-detector')
|
|
7
|
+
const agentGenerator = require('./agent-generator')
|
|
7
8
|
const pathManager = require('./path-manager')
|
|
8
9
|
const configManager = require('./config-manager')
|
|
9
10
|
const authorDetector = require('./author-detector')
|
|
@@ -11,6 +12,8 @@ const migrator = require('./migrator')
|
|
|
11
12
|
const commandInstaller = require('./command-installer')
|
|
12
13
|
const sessionManager = require('./session-manager')
|
|
13
14
|
const analyzer = require('./analyzer')
|
|
15
|
+
const workflowEngine = require('./workflow-engine')
|
|
16
|
+
const workflowPrompts = require('./workflow-prompts')
|
|
14
17
|
const UpdateChecker = require('./update-checker')
|
|
15
18
|
const { VERSION } = require('./version')
|
|
16
19
|
|
|
@@ -1130,6 +1133,9 @@ ${nextStep.agent}
|
|
|
1130
1133
|
}
|
|
1131
1134
|
}
|
|
1132
1135
|
|
|
1136
|
+
const config = await configManager.readConfig(projectPath)
|
|
1137
|
+
const globalProjectPath = pathManager.getProjectRoot(config.projectId)
|
|
1138
|
+
|
|
1133
1139
|
const ideasFile = await this.getFilePath(projectPath, 'planning', 'ideas.md')
|
|
1134
1140
|
const nextFile = await this.getFilePath(projectPath, 'core', 'next.md')
|
|
1135
1141
|
|
|
@@ -1138,17 +1144,41 @@ ${nextStep.agent}
|
|
|
1138
1144
|
await this.agent.writeFile(ideasFile, ideasContent + entry)
|
|
1139
1145
|
|
|
1140
1146
|
let addedToQueue = false
|
|
1147
|
+
let workflowCreated = false
|
|
1148
|
+
let workflowType = null
|
|
1149
|
+
|
|
1141
1150
|
if (text.match(/^(implement|add|create|fix|update|build)/i)) {
|
|
1142
1151
|
const nextContent = await this.agent.readFile(nextFile)
|
|
1143
1152
|
await this.agent.writeFile(nextFile, nextContent + `- ${text}\n`)
|
|
1144
1153
|
addedToQueue = true
|
|
1154
|
+
|
|
1155
|
+
// Auto-detect workflow type and initialize workflow
|
|
1156
|
+
workflowType = workflowEngine.classify(text)
|
|
1157
|
+
if (workflowType) {
|
|
1158
|
+
try {
|
|
1159
|
+
const workflow = await workflowEngine.init(text, workflowType, globalProjectPath)
|
|
1160
|
+
workflowCreated = !!workflow
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
console.warn('⚠️ Could not initialize workflow:', error.message)
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1145
1165
|
}
|
|
1146
1166
|
|
|
1147
|
-
await this.logToMemory(projectPath, 'idea', {
|
|
1167
|
+
await this.logToMemory(projectPath, 'idea', {
|
|
1168
|
+
text,
|
|
1169
|
+
timestamp: this.agent.getTimestamp(),
|
|
1170
|
+
workflow: workflowCreated ? workflowType : null,
|
|
1171
|
+
})
|
|
1148
1172
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1173
|
+
let message = `Idea captured: "${text}"`
|
|
1174
|
+
if (addedToQueue) {
|
|
1175
|
+
message += `\nAlso added to ${this.agentInfo.config.commandPrefix}next queue`
|
|
1176
|
+
}
|
|
1177
|
+
if (workflowCreated) {
|
|
1178
|
+
message += `\n\n🔄 Workflow initialized: ${workflowType}`
|
|
1179
|
+
message += `\nUse ${this.agentInfo.config.commandPrefix}workflow to see steps`
|
|
1180
|
+
message += `\nStart with ${this.agentInfo.config.commandPrefix}now to begin working`
|
|
1181
|
+
}
|
|
1152
1182
|
|
|
1153
1183
|
return {
|
|
1154
1184
|
success: true,
|
|
@@ -2224,54 +2254,55 @@ ${diagram}
|
|
|
2224
2254
|
console.log(chalk.cyan(' 🤖 Perfect context for AI agents'))
|
|
2225
2255
|
console.log('')
|
|
2226
2256
|
|
|
2227
|
-
console.log(chalk.bold.magenta('📦 Setup - Install Commands to
|
|
2257
|
+
console.log(chalk.bold.magenta('📦 Setup - Install Commands to Claude\n'))
|
|
2228
2258
|
|
|
2229
|
-
// Detect
|
|
2259
|
+
// Detect Claude
|
|
2230
2260
|
const commandInstaller = require('./command-installer')
|
|
2231
|
-
const
|
|
2232
|
-
const detectedEditors = Object.entries(detection).filter(([_, info]) => info.detected)
|
|
2261
|
+
const claudeDetected = await commandInstaller.detectClaude()
|
|
2233
2262
|
|
|
2234
|
-
if (
|
|
2263
|
+
if (!claudeDetected) {
|
|
2235
2264
|
return {
|
|
2236
2265
|
success: false,
|
|
2237
2266
|
message: this.agent.formatResponse(
|
|
2238
|
-
'
|
|
2267
|
+
'Claude not detected.\n\nPlease install Claude Code or Claude Desktop first.\n\nVisit: https://claude.ai/download',
|
|
2239
2268
|
'error'
|
|
2240
2269
|
),
|
|
2241
2270
|
}
|
|
2242
2271
|
}
|
|
2243
2272
|
|
|
2244
|
-
console.log(chalk.
|
|
2245
|
-
detectedEditors.forEach(([key, info]) => {
|
|
2246
|
-
const editorName = commandInstaller.editors[key]?.name || key
|
|
2247
|
-
console.log(chalk.green(` [✓] ${editorName} (${info.path})`))
|
|
2248
|
-
})
|
|
2273
|
+
console.log(chalk.green('✓ Claude detected'))
|
|
2249
2274
|
console.log('')
|
|
2250
2275
|
|
|
2251
|
-
//
|
|
2252
|
-
|
|
2276
|
+
// Install commands
|
|
2277
|
+
console.log(chalk.cyan('Installing /p:* commands...'))
|
|
2278
|
+
const installResult = await commandInstaller.installCommands()
|
|
2253
2279
|
|
|
2254
2280
|
if (!installResult.success) {
|
|
2255
2281
|
return {
|
|
2256
2282
|
success: false,
|
|
2257
|
-
message: this.agent.formatResponse(
|
|
2283
|
+
message: this.agent.formatResponse(
|
|
2284
|
+
`Installation failed: ${installResult.error || 'Unknown error'}`,
|
|
2285
|
+
'error'
|
|
2286
|
+
),
|
|
2258
2287
|
}
|
|
2259
2288
|
}
|
|
2260
2289
|
|
|
2261
|
-
// Install Context7 MCP
|
|
2262
|
-
const mcpResult = await commandInstaller.installContext7MCP()
|
|
2263
|
-
|
|
2264
2290
|
// Success message
|
|
2265
|
-
|
|
2291
|
+
const installedCount = installResult.installed?.length || 0
|
|
2292
|
+
const errorCount = installResult.errors?.length || 0
|
|
2266
2293
|
|
|
2267
|
-
|
|
2268
|
-
|
|
2294
|
+
let message = `✅ Successfully installed ${installedCount} commands to Claude\n`
|
|
2295
|
+
message += ` Location: ${installResult.path}\n`
|
|
2296
|
+
|
|
2297
|
+
if (errorCount > 0) {
|
|
2298
|
+
message += `\n⚠️ ${errorCount} command(s) had issues during installation`
|
|
2269
2299
|
}
|
|
2270
2300
|
|
|
2271
2301
|
message += '\n\n✨ prjct is ready to use!'
|
|
2272
2302
|
message += '\n\nNext steps:'
|
|
2273
2303
|
message += '\n cd your-project/'
|
|
2274
2304
|
message += '\n prjct init'
|
|
2305
|
+
message += '\n\nℹ️ Context7 MCP is automatically available in Claude'
|
|
2275
2306
|
|
|
2276
2307
|
return {
|
|
2277
2308
|
success: true,
|
|
@@ -2296,60 +2327,72 @@ ${diagram}
|
|
|
2296
2327
|
try {
|
|
2297
2328
|
await this.initializeAgent()
|
|
2298
2329
|
|
|
2299
|
-
const {
|
|
2300
|
-
force = false,
|
|
2301
|
-
editor = null,
|
|
2302
|
-
createTemplates = false,
|
|
2303
|
-
interactive = true,
|
|
2304
|
-
} = options
|
|
2330
|
+
const { force = false } = options
|
|
2305
2331
|
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2332
|
+
// Detect Claude
|
|
2333
|
+
const commandInstaller = require('./command-installer')
|
|
2334
|
+
const claudeDetected = await commandInstaller.detectClaude()
|
|
2335
|
+
|
|
2336
|
+
if (!claudeDetected) {
|
|
2337
|
+
return {
|
|
2338
|
+
success: false,
|
|
2339
|
+
message: this.agent.formatResponse(
|
|
2340
|
+
'Claude not detected. Please install Claude Code or Claude Desktop first.',
|
|
2341
|
+
'error'
|
|
2342
|
+
),
|
|
2313
2343
|
}
|
|
2314
2344
|
}
|
|
2315
2345
|
|
|
2316
|
-
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2346
|
+
// Check current installation status
|
|
2347
|
+
const status = await commandInstaller.checkInstallation()
|
|
2348
|
+
|
|
2349
|
+
if (status.installed && !force) {
|
|
2350
|
+
const chalk = require('chalk')
|
|
2351
|
+
let report = chalk.green('✓ Commands already installed\n')
|
|
2352
|
+
report += chalk.dim(` Location: ${status.path}\n`)
|
|
2353
|
+
report += chalk.dim(` Commands: ${status.commands.length}\n`)
|
|
2354
|
+
report += '\nUse --force to reinstall'
|
|
2319
2355
|
|
|
2320
|
-
if (detectedEditors.length === 0) {
|
|
2321
2356
|
return {
|
|
2322
|
-
success:
|
|
2323
|
-
message: this.agent.formatResponse(
|
|
2357
|
+
success: true,
|
|
2358
|
+
message: this.agent.formatResponse(report, 'info'),
|
|
2324
2359
|
}
|
|
2325
2360
|
}
|
|
2326
2361
|
|
|
2327
|
-
|
|
2362
|
+
// Install or reinstall commands
|
|
2363
|
+
const installResult = force
|
|
2364
|
+
? await commandInstaller.updateCommands()
|
|
2365
|
+
: await commandInstaller.installCommands()
|
|
2328
2366
|
|
|
2329
|
-
if (
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
installResult = await commandInstaller.installToAll(force)
|
|
2367
|
+
if (!installResult.success) {
|
|
2368
|
+
return {
|
|
2369
|
+
success: false,
|
|
2370
|
+
message: this.agent.formatResponse(
|
|
2371
|
+
`Installation failed: ${installResult.error || 'Unknown error'}`,
|
|
2372
|
+
'error'
|
|
2373
|
+
),
|
|
2374
|
+
}
|
|
2338
2375
|
}
|
|
2339
2376
|
|
|
2340
|
-
//
|
|
2341
|
-
const
|
|
2377
|
+
// Generate report
|
|
2378
|
+
const installedCount = installResult.installed?.length || 0
|
|
2379
|
+
const errorCount = installResult.errors?.length || 0
|
|
2342
2380
|
|
|
2343
|
-
let report =
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
report +=
|
|
2381
|
+
let report = `✅ Successfully ${force ? 'reinstalled' : 'installed'} ${installedCount} commands\n`
|
|
2382
|
+
report += ` Location: ${installResult.path}\n`
|
|
2383
|
+
|
|
2384
|
+
if (errorCount > 0) {
|
|
2385
|
+
report += `\n⚠️ ${errorCount} command(s) had issues\n`
|
|
2386
|
+
installResult.errors.forEach(err => {
|
|
2387
|
+
report += ` - ${err.file}: ${err.error}\n`
|
|
2388
|
+
})
|
|
2348
2389
|
}
|
|
2349
2390
|
|
|
2391
|
+
report += '\n📚 Context7 MCP is automatically available in Claude'
|
|
2392
|
+
|
|
2350
2393
|
return {
|
|
2351
|
-
success:
|
|
2352
|
-
message: this.agent.formatResponse(report,
|
|
2394
|
+
success: true,
|
|
2395
|
+
message: this.agent.formatResponse(report, 'celebrate'),
|
|
2353
2396
|
}
|
|
2354
2397
|
} catch (error) {
|
|
2355
2398
|
await this.initializeAgent()
|
|
@@ -2478,6 +2521,175 @@ ${syncResults.shippedMdUpdated ? `✅ Updated shipped.md (${syncResults.features
|
|
|
2478
2521
|
`
|
|
2479
2522
|
}
|
|
2480
2523
|
|
|
2524
|
+
/**
|
|
2525
|
+
* Sync project state and update AI agents
|
|
2526
|
+
* Re-analyzes the project and regenerates workflow agents in global project directory
|
|
2527
|
+
*
|
|
2528
|
+
* @param {string} [projectPath=process.cwd()] - Project directory path
|
|
2529
|
+
* @returns {Promise<Object>} Result with success status and message
|
|
2530
|
+
*/
|
|
2531
|
+
async sync(projectPath = process.cwd()) {
|
|
2532
|
+
try {
|
|
2533
|
+
await this.initializeAgent()
|
|
2534
|
+
|
|
2535
|
+
// Get project ID from config
|
|
2536
|
+
const config = await configManager.readConfig(projectPath)
|
|
2537
|
+
if (!config || !config.projectId) {
|
|
2538
|
+
return {
|
|
2539
|
+
success: false,
|
|
2540
|
+
message: this.agent.formatResponse(
|
|
2541
|
+
'Project not initialized. Run /p:init first.',
|
|
2542
|
+
'error',
|
|
2543
|
+
),
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
console.log('🔄 Syncing project state...\n')
|
|
2548
|
+
|
|
2549
|
+
// Step 1: Re-run project analysis
|
|
2550
|
+
const analysisResult = await this.analyze({ silent: true }, projectPath)
|
|
2551
|
+
|
|
2552
|
+
if (!analysisResult.success) {
|
|
2553
|
+
return {
|
|
2554
|
+
success: false,
|
|
2555
|
+
message: this.agent.formatResponse(`Sync failed: ${analysisResult.message}`, 'error'),
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
const { analysis } = analysisResult
|
|
2560
|
+
const summary = {
|
|
2561
|
+
commandsFound: analysis.commands.length,
|
|
2562
|
+
featuresFound: analysis.features.length,
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
console.log('📊 Analysis Complete')
|
|
2566
|
+
console.log(` ✅ ${summary.commandsFound} commands detected`)
|
|
2567
|
+
console.log(` ✅ ${summary.featuresFound} features implemented\n`)
|
|
2568
|
+
|
|
2569
|
+
// Step 2: Generate/update all agents in project directory
|
|
2570
|
+
const AgentGenerator = agentGenerator
|
|
2571
|
+
const generator = new AgentGenerator(config.projectId)
|
|
2572
|
+
|
|
2573
|
+
const generatedAgents = await generator.generateAll(analysis)
|
|
2574
|
+
|
|
2575
|
+
// Step 3: Log sync action to memory
|
|
2576
|
+
await this.logAction(projectPath, 'sync', {
|
|
2577
|
+
commandsDetected: summary.commandsFound,
|
|
2578
|
+
featuresDetected: summary.featuresFound,
|
|
2579
|
+
agentsGenerated: generatedAgents.length,
|
|
2580
|
+
agents: generatedAgents,
|
|
2581
|
+
})
|
|
2582
|
+
|
|
2583
|
+
const agentsPath = path.join(
|
|
2584
|
+
pathManager.getProjectRoot(config.projectId),
|
|
2585
|
+
'agents',
|
|
2586
|
+
)
|
|
2587
|
+
|
|
2588
|
+
const message = `
|
|
2589
|
+
🔄 Sync Complete
|
|
2590
|
+
|
|
2591
|
+
📊 Project State:
|
|
2592
|
+
✅ ${summary.commandsFound} commands detected
|
|
2593
|
+
✅ ${summary.featuresFound} features implemented
|
|
2594
|
+
|
|
2595
|
+
🤖 Workflow Agents:
|
|
2596
|
+
✨ Generated ${generatedAgents.length} agents in ${agentsPath}
|
|
2597
|
+
${generatedAgents.map(a => `✅ ${a.toUpperCase()}`).join('\n ')}
|
|
2598
|
+
|
|
2599
|
+
💡 Agents ready for workflow task assignment!
|
|
2600
|
+
`
|
|
2601
|
+
|
|
2602
|
+
return {
|
|
2603
|
+
success: true,
|
|
2604
|
+
message: this.agent.formatResponse(message, 'success'),
|
|
2605
|
+
generatedAgents,
|
|
2606
|
+
}
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
await this.initializeAgent()
|
|
2609
|
+
return {
|
|
2610
|
+
success: false,
|
|
2611
|
+
message: this.agent.formatResponse(`Sync failed: ${error.message}`, 'error'),
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* Show workflow status
|
|
2618
|
+
*
|
|
2619
|
+
* @param {string} [projectPath=process.cwd()] - Project path
|
|
2620
|
+
* @returns {Promise<Object>} Result object with success flag and message
|
|
2621
|
+
*/
|
|
2622
|
+
async workflow(projectPath = process.cwd()) {
|
|
2623
|
+
try {
|
|
2624
|
+
await this.initializeAgent()
|
|
2625
|
+
|
|
2626
|
+
// Auto-init if not configured
|
|
2627
|
+
const initCheck = await this.ensureProjectInit(projectPath)
|
|
2628
|
+
if (!initCheck.success) {
|
|
2629
|
+
return initCheck
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
const config = await configManager.readConfig(projectPath)
|
|
2633
|
+
const globalProjectPath = pathManager.getProjectRoot(config.projectId)
|
|
2634
|
+
|
|
2635
|
+
const workflow = await workflowEngine.load(globalProjectPath)
|
|
2636
|
+
|
|
2637
|
+
if (!workflow || !workflow.active) {
|
|
2638
|
+
return {
|
|
2639
|
+
success: true,
|
|
2640
|
+
message: this.agent.formatResponse(
|
|
2641
|
+
`No active workflow.\n\nStart one with ${this.agentInfo.config.commandPrefix}idea "implement [feature]"`,
|
|
2642
|
+
'info',
|
|
2643
|
+
),
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
const currentStep = workflow.steps[workflow.current]
|
|
2648
|
+
const completedSteps = workflow.steps.slice(0, workflow.current)
|
|
2649
|
+
const remainingSteps = workflow.steps.slice(workflow.current + 1)
|
|
2650
|
+
|
|
2651
|
+
let message = `🔄 Workflow: ${workflow.type}\n`
|
|
2652
|
+
message += `📋 Task: ${workflow.task}\n\n`
|
|
2653
|
+
|
|
2654
|
+
if (completedSteps.length > 0) {
|
|
2655
|
+
message += `✅ Completed:\n`
|
|
2656
|
+
completedSteps.forEach((step) => {
|
|
2657
|
+
message += ` - ${step.name}: ${step.action} (${step.agent})\n`
|
|
2658
|
+
})
|
|
2659
|
+
message += '\n'
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
message += `🎯 Current Step:\n`
|
|
2663
|
+
message += ` - ${currentStep.name}: ${currentStep.action} (${currentStep.agent})\n`
|
|
2664
|
+
if (currentStep.required) {
|
|
2665
|
+
message += ` Required: Yes\n`
|
|
2666
|
+
}
|
|
2667
|
+
message += '\n'
|
|
2668
|
+
|
|
2669
|
+
if (remainingSteps.length > 0) {
|
|
2670
|
+
message += `⏳ Remaining:\n`
|
|
2671
|
+
remainingSteps.forEach((step) => {
|
|
2672
|
+
message += ` - ${step.name}: ${step.action} (${step.agent})\n`
|
|
2673
|
+
})
|
|
2674
|
+
message += '\n'
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
message += `Progress: ${workflow.current + 1}/${workflow.steps.length} steps`
|
|
2678
|
+
|
|
2679
|
+
return {
|
|
2680
|
+
success: true,
|
|
2681
|
+
message: this.agent.formatResponse(message, 'info'),
|
|
2682
|
+
workflow,
|
|
2683
|
+
}
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
await this.initializeAgent()
|
|
2686
|
+
return {
|
|
2687
|
+
success: false,
|
|
2688
|
+
message: this.agent.formatResponse(`Workflow status failed: ${error.message}`, 'error'),
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2481
2693
|
/**
|
|
2482
2694
|
* Detect if project has existing code (for auto-analyze during init)
|
|
2483
2695
|
*
|