claude-brain 0.30.2 → 0.30.3
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/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
|
@@ -1,756 +1,756 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Init Project Handler
|
|
3
|
-
* Analyzes an existing codebase and creates project in vault
|
|
4
|
-
* Similar to `claude init` - reads files to extract project info
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { Logger } from 'pino'
|
|
8
|
-
import type { ToolResponse } from '@/tools/types'
|
|
9
|
-
import { getVaultService, getMemoryService, isServicesInitialized } from '@/server/services'
|
|
10
|
-
import { ResponseFormatter } from '@/server/utils/response-formatter'
|
|
11
|
-
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
12
|
-
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
13
|
-
import { PackManager, PackLoader, type PackLoadResult } from '@/packs/index'
|
|
14
|
-
import fs from 'fs/promises'
|
|
15
|
-
import path from 'path'
|
|
16
|
-
import { fileURLToPath } from 'node:url'
|
|
17
|
-
import { dirname, resolve } from 'node:path'
|
|
18
|
-
|
|
19
|
-
interface InitProjectInput {
|
|
20
|
-
project_path?: string
|
|
21
|
-
project_name?: string
|
|
22
|
-
save_to_memory?: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ProjectAnalysis {
|
|
26
|
-
name: string
|
|
27
|
-
description: string
|
|
28
|
-
techStack: string[]
|
|
29
|
-
frameworks: string[]
|
|
30
|
-
languages: string[]
|
|
31
|
-
packageManager: string | null
|
|
32
|
-
structure: string[]
|
|
33
|
-
keyFiles: string[]
|
|
34
|
-
scripts: Record<string, string>
|
|
35
|
-
dependencies: string[]
|
|
36
|
-
devDependencies: string[]
|
|
37
|
-
hasTests: boolean
|
|
38
|
-
hasTypeScript: boolean
|
|
39
|
-
hasLinting: boolean
|
|
40
|
-
hasFormatting: boolean
|
|
41
|
-
gitIgnorePatterns: string[]
|
|
42
|
-
conventions: string[]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function handleInitProject(
|
|
46
|
-
args: unknown,
|
|
47
|
-
logger: Logger
|
|
48
|
-
): Promise<ToolResponse> {
|
|
49
|
-
try {
|
|
50
|
-
const input = args as InitProjectInput
|
|
51
|
-
const {
|
|
52
|
-
project_path = process.cwd(),
|
|
53
|
-
project_name,
|
|
54
|
-
save_to_memory = true
|
|
55
|
-
} = input
|
|
56
|
-
|
|
57
|
-
logger.info({ project_path, project_name }, 'Initializing project analysis')
|
|
58
|
-
|
|
59
|
-
if (!isServicesInitialized()) {
|
|
60
|
-
throw new McpError(
|
|
61
|
-
ErrorCode.InternalError,
|
|
62
|
-
'Services not initialized'
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Verify path exists
|
|
67
|
-
try {
|
|
68
|
-
const stats = await fs.stat(project_path)
|
|
69
|
-
if (!stats.isDirectory()) {
|
|
70
|
-
throw new McpError(
|
|
71
|
-
ErrorCode.InvalidParams,
|
|
72
|
-
`Path is not a directory: ${project_path}`
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
} catch (error) {
|
|
76
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
77
|
-
throw new McpError(
|
|
78
|
-
ErrorCode.InvalidParams,
|
|
79
|
-
`Directory not found: ${project_path}`
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
throw error
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Analyze the project
|
|
86
|
-
const analysis = await analyzeProject(project_path, logger)
|
|
87
|
-
|
|
88
|
-
// Use provided name or detected name
|
|
89
|
-
const finalName = project_name || analysis.name
|
|
90
|
-
const normalizedName = finalName
|
|
91
|
-
.toLowerCase()
|
|
92
|
-
.replace(/\s+/g, '-')
|
|
93
|
-
.replace(/[^a-z0-9-]/g, '')
|
|
94
|
-
|
|
95
|
-
if (!normalizedName || normalizedName.length < 2) {
|
|
96
|
-
throw new McpError(
|
|
97
|
-
ErrorCode.InvalidParams,
|
|
98
|
-
'Could not determine project name. Please provide project_name parameter.'
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const vault = getVaultService()
|
|
103
|
-
|
|
104
|
-
// Check if project already exists
|
|
105
|
-
const existingProjects = await vault.listProjects()
|
|
106
|
-
const projectExists = existingProjects.includes(normalizedName)
|
|
107
|
-
|
|
108
|
-
if (!projectExists) {
|
|
109
|
-
// Create new project
|
|
110
|
-
await vault.createProject(normalizedName)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Update project files with analysis
|
|
114
|
-
const projectPaths = vault.getProjectPaths(normalizedName)
|
|
115
|
-
|
|
116
|
-
// Update context.md
|
|
117
|
-
const contextContent = generateContextContent(analysis)
|
|
118
|
-
await vault.writer.writeMarkdownFile(
|
|
119
|
-
projectPaths.context,
|
|
120
|
-
{
|
|
121
|
-
type: 'project-context',
|
|
122
|
-
project: normalizedName,
|
|
123
|
-
status: 'active',
|
|
124
|
-
created: new Date().toISOString().split('T')[0],
|
|
125
|
-
updated: new Date().toISOString().split('T')[0],
|
|
126
|
-
tech_stack: analysis.techStack,
|
|
127
|
-
languages: analysis.languages,
|
|
128
|
-
frameworks: analysis.frameworks,
|
|
129
|
-
tags: detectTags(analysis),
|
|
130
|
-
description: analysis.description,
|
|
131
|
-
analyzed_from: project_path
|
|
132
|
-
},
|
|
133
|
-
contextContent,
|
|
134
|
-
{ createBackup: projectExists }
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
// Update standards.md
|
|
138
|
-
const standardsContent = generateStandardsContent(analysis)
|
|
139
|
-
await vault.writer.writeMarkdownFile(
|
|
140
|
-
projectPaths.standards,
|
|
141
|
-
{
|
|
142
|
-
type: 'coding-standards',
|
|
143
|
-
project: normalizedName,
|
|
144
|
-
languages: analysis.languages,
|
|
145
|
-
frameworks: analysis.frameworks,
|
|
146
|
-
last_updated: new Date().toISOString().split('T')[0]
|
|
147
|
-
},
|
|
148
|
-
standardsContent,
|
|
149
|
-
{ createBackup: projectExists }
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
// Save to memory for semantic search
|
|
153
|
-
if (save_to_memory) {
|
|
154
|
-
const memory = getMemoryService()
|
|
155
|
-
|
|
156
|
-
// Store project overview as a decision/context
|
|
157
|
-
const projectSummary = `
|
|
158
|
-
Project: ${normalizedName}
|
|
159
|
-
Description: ${analysis.description}
|
|
160
|
-
Tech Stack: ${analysis.techStack.join(', ')}
|
|
161
|
-
Languages: ${analysis.languages.join(', ')}
|
|
162
|
-
Frameworks: ${analysis.frameworks.join(', ')}
|
|
163
|
-
Key Files: ${analysis.keyFiles.join(', ')}
|
|
164
|
-
Has Tests: ${analysis.hasTests}
|
|
165
|
-
Has TypeScript: ${analysis.hasTypeScript}
|
|
166
|
-
`.trim()
|
|
167
|
-
|
|
168
|
-
await memory.rememberDecision(
|
|
169
|
-
normalizedName,
|
|
170
|
-
'Project initialization and setup',
|
|
171
|
-
`Initialized ${normalizedName} with Claude Brain`,
|
|
172
|
-
projectSummary,
|
|
173
|
-
{
|
|
174
|
-
tags: ['project-init', 'setup', ...analysis.languages, ...analysis.frameworks]
|
|
175
|
-
}
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
logger.info({ normalizedName }, 'Project saved to memory')
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Phase 18: Load knowledge packs based on detected tech stack
|
|
182
|
-
let packResult: PackLoadResult | null = null
|
|
183
|
-
try {
|
|
184
|
-
const initFile = fileURLToPath(import.meta.url)
|
|
185
|
-
const initDir = dirname(initFile)
|
|
186
|
-
const PACKAGE_ROOT = resolve(initDir, '..', '..', '..', '..')
|
|
187
|
-
const dataDir = path.join(process.env.CLAUDE_BRAIN_HOME || path.join(process.env.HOME || '~', '.claude-brain'), 'data')
|
|
188
|
-
|
|
189
|
-
// Default packs config
|
|
190
|
-
const packsConfig = {
|
|
191
|
-
enabled: true,
|
|
192
|
-
packsDir: 'packs',
|
|
193
|
-
alwaysLoadCore: true,
|
|
194
|
-
alwaysLoadMeta: true,
|
|
195
|
-
communityConfidenceMultiplier: 0.8,
|
|
196
|
-
personalBoost: 1.2,
|
|
197
|
-
projectBoost: 1.15
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (packsConfig.enabled) {
|
|
201
|
-
const memory = getMemoryService()
|
|
202
|
-
const packManager = new PackManager(logger, packsConfig, PACKAGE_ROOT, dataDir)
|
|
203
|
-
const packLoader = new PackLoader(logger, memory, packManager, packsConfig)
|
|
204
|
-
packResult = await packLoader.loadPacksForProject(normalizedName, analysis.techStack)
|
|
205
|
-
logger.info({ packResult }, 'Knowledge packs loaded')
|
|
206
|
-
}
|
|
207
|
-
} catch (packError) {
|
|
208
|
-
logger.warn({ error: packError }, 'Pack loading failed (non-blocking)')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Build response
|
|
212
|
-
const parts: string[] = []
|
|
213
|
-
parts.push(`## ${projectExists ? '🔄 Project Updated' : '✅ Project Initialized'}: ${normalizedName}\n`)
|
|
214
|
-
|
|
215
|
-
parts.push(`### Analysis Results\n`)
|
|
216
|
-
parts.push(`**Source:** \`${project_path}\`\n`)
|
|
217
|
-
|
|
218
|
-
if (analysis.description) {
|
|
219
|
-
parts.push(`**Description:** ${analysis.description}\n`)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
parts.push(`\n### Tech Stack`)
|
|
223
|
-
parts.push(`- **Languages:** ${analysis.languages.join(', ') || 'Not detected'}`)
|
|
224
|
-
parts.push(`- **Frameworks:** ${analysis.frameworks.join(', ') || 'Not detected'}`)
|
|
225
|
-
parts.push(`- **Package Manager:** ${analysis.packageManager || 'Not detected'}`)
|
|
226
|
-
|
|
227
|
-
parts.push(`\n### Project Features`)
|
|
228
|
-
parts.push(`- TypeScript: ${analysis.hasTypeScript ? '✅' : '❌'}`)
|
|
229
|
-
parts.push(`- Tests: ${analysis.hasTests ? '✅' : '❌'}`)
|
|
230
|
-
parts.push(`- Linting: ${analysis.hasLinting ? '✅' : '❌'}`)
|
|
231
|
-
parts.push(`- Formatting: ${analysis.hasFormatting ? '✅' : '❌'}`)
|
|
232
|
-
|
|
233
|
-
if (analysis.keyFiles.length > 0) {
|
|
234
|
-
parts.push(`\n### Key Files Detected`)
|
|
235
|
-
analysis.keyFiles.slice(0, 10).forEach(f => parts.push(`- \`${f}\``))
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (Object.keys(analysis.scripts).length > 0) {
|
|
239
|
-
parts.push(`\n### Available Scripts`)
|
|
240
|
-
Object.entries(analysis.scripts).slice(0, 8).forEach(([name, cmd]) => {
|
|
241
|
-
parts.push(`- \`${name}\`: ${cmd.slice(0, 50)}${cmd.length > 50 ? '...' : ''}`)
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (analysis.conventions.length > 0) {
|
|
246
|
-
parts.push(`\n### Detected Conventions`)
|
|
247
|
-
analysis.conventions.forEach(c => parts.push(`- ${c}`))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
parts.push(`\n### Files Created/Updated`)
|
|
251
|
-
parts.push(`- \`${normalizedName}/context.md\` - Project overview`)
|
|
252
|
-
parts.push(`- \`${normalizedName}/standards.md\` - Coding standards`)
|
|
253
|
-
parts.push(`- \`${normalizedName}/progress.md\` - Progress tracking`)
|
|
254
|
-
parts.push(`- \`${normalizedName}/decisions.md\` - Decision log`)
|
|
255
|
-
|
|
256
|
-
if (save_to_memory) {
|
|
257
|
-
parts.push(`\n### Memory`)
|
|
258
|
-
parts.push(`Project context saved to semantic memory for future recall.`)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (packResult && packResult.packsLoaded > 0) {
|
|
262
|
-
parts.push(`\n### Knowledge Packs`)
|
|
263
|
-
parts.push(`Loaded **${packResult.packsLoaded}** pack(s) with **${packResult.entriesLoaded}** entries:`)
|
|
264
|
-
for (const detail of packResult.packDetails) {
|
|
265
|
-
parts.push(`- **${detail.name}** (${detail.entriesLoaded} entries)`)
|
|
266
|
-
}
|
|
267
|
-
if (packResult.skipped.length > 0) {
|
|
268
|
-
parts.push(`\nSkipped: ${packResult.skipped.map(s => `${s.packId} (${s.reason})`).join(', ')}`)
|
|
269
|
-
}
|
|
270
|
-
} else if (packResult && packResult.skipped.length > 0) {
|
|
271
|
-
parts.push(`\n### Knowledge Packs`)
|
|
272
|
-
parts.push(`All packs already loaded (${packResult.skipped.length} skipped).`)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
parts.push(`\n### Next Steps`)
|
|
276
|
-
parts.push(`1. Use \`get_project_context("${normalizedName}")\` to view full context`)
|
|
277
|
-
parts.push(`2. Use \`remember_decision\` to save architectural decisions`)
|
|
278
|
-
parts.push(`3. Use \`recall_similar\` to search past decisions`)
|
|
279
|
-
|
|
280
|
-
logger.info({
|
|
281
|
-
project: normalizedName,
|
|
282
|
-
techStack: analysis.techStack,
|
|
283
|
-
isNew: !projectExists
|
|
284
|
-
}, 'Project initialized successfully')
|
|
285
|
-
|
|
286
|
-
return ResponseFormatter.text(parts.join('\n'))
|
|
287
|
-
|
|
288
|
-
} catch (error) {
|
|
289
|
-
ErrorHandler.logError(logger, error, { tool: 'init_project', args })
|
|
290
|
-
|
|
291
|
-
if (error instanceof McpError) {
|
|
292
|
-
throw error
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Provide detailed error message
|
|
296
|
-
if (error instanceof Error) {
|
|
297
|
-
let errorMsg = `Failed to initialize project: ${error.message}\n\n`
|
|
298
|
-
|
|
299
|
-
// Add specific troubleshooting steps based on error type
|
|
300
|
-
if (error.message.includes('EACCES') || error.message.includes('permission')) {
|
|
301
|
-
errorMsg += `**Permission Error**\n` +
|
|
302
|
-
`- Check that you have read/write permissions for the project directory\n` +
|
|
303
|
-
`- Ensure the Obsidian vault path is writable\n`
|
|
304
|
-
} else if (error.message.includes('ENOENT')) {
|
|
305
|
-
errorMsg += `**File Not Found**\n` +
|
|
306
|
-
`- Verify the project_path exists\n` +
|
|
307
|
-
`- Check that the path is absolute, not relative\n`
|
|
308
|
-
} else if (error.message.includes('parse') || error.message.includes('JSON')) {
|
|
309
|
-
errorMsg += `**Parse Error**\n` +
|
|
310
|
-
`- package.json or other config files may be malformed\n` +
|
|
311
|
-
`- Check for syntax errors in configuration files\n`
|
|
312
|
-
} else {
|
|
313
|
-
errorMsg += `**Troubleshooting:**\n` +
|
|
314
|
-
`- Ensure the directory exists and is accessible\n` +
|
|
315
|
-
`- Check file permissions in both project and vault directories\n` +
|
|
316
|
-
`- Verify config files (package.json, etc.) are valid\n`
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
errorMsg += `\n**Error details:** ${error.stack || error.message}`
|
|
320
|
-
|
|
321
|
-
throw new McpError(ErrorCode.InternalError, errorMsg)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
throw new McpError(
|
|
325
|
-
ErrorCode.InternalError,
|
|
326
|
-
`Failed to initialize project: Unknown error occurred`
|
|
327
|
-
)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Analyze project directory
|
|
333
|
-
*/
|
|
334
|
-
async function analyzeProject(projectPath: string, logger: Logger): Promise<ProjectAnalysis> {
|
|
335
|
-
const analysis: ProjectAnalysis = {
|
|
336
|
-
name: path.basename(projectPath),
|
|
337
|
-
description: '',
|
|
338
|
-
techStack: [],
|
|
339
|
-
frameworks: [],
|
|
340
|
-
languages: [],
|
|
341
|
-
packageManager: null,
|
|
342
|
-
structure: [],
|
|
343
|
-
keyFiles: [],
|
|
344
|
-
scripts: {},
|
|
345
|
-
dependencies: [],
|
|
346
|
-
devDependencies: [],
|
|
347
|
-
hasTests: false,
|
|
348
|
-
hasTypeScript: false,
|
|
349
|
-
hasLinting: false,
|
|
350
|
-
hasFormatting: false,
|
|
351
|
-
gitIgnorePatterns: [],
|
|
352
|
-
conventions: []
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Read directory structure (top level + 1 deep)
|
|
356
|
-
try {
|
|
357
|
-
const entries = await fs.readdir(projectPath, { withFileTypes: true })
|
|
358
|
-
|
|
359
|
-
for (const entry of entries) {
|
|
360
|
-
if (entry.name.startsWith('.') && entry.name !== '.eslintrc.json' && entry.name !== '.prettierrc') {
|
|
361
|
-
continue
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (entry.isDirectory()) {
|
|
365
|
-
analysis.structure.push(`${entry.name}/`)
|
|
366
|
-
} else {
|
|
367
|
-
analysis.structure.push(entry.name)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {
|
|
371
|
-
logger.warn({ error }, 'Failed to read directory structure')
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Check for package.json (Node.js/JavaScript projects)
|
|
375
|
-
await analyzePackageJson(projectPath, analysis, logger)
|
|
376
|
-
|
|
377
|
-
// Check for other config files
|
|
378
|
-
await analyzeConfigFiles(projectPath, analysis, logger)
|
|
379
|
-
|
|
380
|
-
// Check for Python projects
|
|
381
|
-
await analyzePythonProject(projectPath, analysis, logger)
|
|
382
|
-
|
|
383
|
-
// Check for Go projects
|
|
384
|
-
await analyzeGoProject(projectPath, analysis, logger)
|
|
385
|
-
|
|
386
|
-
// Check for Rust projects
|
|
387
|
-
await analyzeRustProject(projectPath, analysis, logger)
|
|
388
|
-
|
|
389
|
-
// Detect test directories
|
|
390
|
-
await detectTests(projectPath, analysis, logger)
|
|
391
|
-
|
|
392
|
-
// Read README for description
|
|
393
|
-
await readReadme(projectPath, analysis, logger)
|
|
394
|
-
|
|
395
|
-
// Read .gitignore
|
|
396
|
-
await readGitignore(projectPath, analysis, logger)
|
|
397
|
-
|
|
398
|
-
// Detect conventions from structure
|
|
399
|
-
detectConventions(analysis)
|
|
400
|
-
|
|
401
|
-
// Build final tech stack
|
|
402
|
-
analysis.techStack = [...new Set([...analysis.languages, ...analysis.frameworks])]
|
|
403
|
-
|
|
404
|
-
return analysis
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async function analyzePackageJson(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
408
|
-
try {
|
|
409
|
-
const pkgPath = path.join(projectPath, 'package.json')
|
|
410
|
-
const content = await fs.readFile(pkgPath, 'utf-8')
|
|
411
|
-
const pkg = JSON.parse(content)
|
|
412
|
-
|
|
413
|
-
analysis.name = pkg.name || analysis.name
|
|
414
|
-
analysis.description = pkg.description || ''
|
|
415
|
-
analysis.scripts = pkg.scripts || {}
|
|
416
|
-
|
|
417
|
-
// Detect package manager
|
|
418
|
-
if (await fileExists(path.join(projectPath, 'bun.lockb'))) {
|
|
419
|
-
analysis.packageManager = 'bun'
|
|
420
|
-
} else if (await fileExists(path.join(projectPath, 'pnpm-lock.yaml'))) {
|
|
421
|
-
analysis.packageManager = 'pnpm'
|
|
422
|
-
} else if (await fileExists(path.join(projectPath, 'yarn.lock'))) {
|
|
423
|
-
analysis.packageManager = 'yarn'
|
|
424
|
-
} else if (await fileExists(path.join(projectPath, 'package-lock.json'))) {
|
|
425
|
-
analysis.packageManager = 'npm'
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Languages
|
|
429
|
-
analysis.languages.push('javascript')
|
|
430
|
-
|
|
431
|
-
// Analyze dependencies
|
|
432
|
-
const allDeps = {
|
|
433
|
-
...pkg.dependencies,
|
|
434
|
-
...pkg.devDependencies
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
analysis.dependencies = Object.keys(pkg.dependencies || {})
|
|
438
|
-
analysis.devDependencies = Object.keys(pkg.devDependencies || {})
|
|
439
|
-
|
|
440
|
-
// Detect frameworks
|
|
441
|
-
if (allDeps['react']) {
|
|
442
|
-
analysis.frameworks.push('react')
|
|
443
|
-
if (allDeps['next']) analysis.frameworks.push('next.js')
|
|
444
|
-
if (allDeps['gatsby']) analysis.frameworks.push('gatsby')
|
|
445
|
-
if (allDeps['remix']) analysis.frameworks.push('remix')
|
|
446
|
-
}
|
|
447
|
-
if (allDeps['vue']) analysis.frameworks.push('vue')
|
|
448
|
-
if (allDeps['@angular/core']) analysis.frameworks.push('angular')
|
|
449
|
-
if (allDeps['svelte']) analysis.frameworks.push('svelte')
|
|
450
|
-
if (allDeps['express']) analysis.frameworks.push('express')
|
|
451
|
-
if (allDeps['fastify']) analysis.frameworks.push('fastify')
|
|
452
|
-
if (allDeps['hono']) analysis.frameworks.push('hono')
|
|
453
|
-
if (allDeps['elysia']) analysis.frameworks.push('elysia')
|
|
454
|
-
if (allDeps['nestjs'] || allDeps['@nestjs/core']) analysis.frameworks.push('nestjs')
|
|
455
|
-
if (allDeps['electron']) analysis.frameworks.push('electron')
|
|
456
|
-
if (allDeps['tauri']) analysis.frameworks.push('tauri')
|
|
457
|
-
|
|
458
|
-
// Detect TypeScript
|
|
459
|
-
if (allDeps['typescript'] || await fileExists(path.join(projectPath, 'tsconfig.json'))) {
|
|
460
|
-
analysis.hasTypeScript = true
|
|
461
|
-
analysis.languages.push('typescript')
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Detect testing
|
|
465
|
-
if (allDeps['jest'] || allDeps['vitest'] || allDeps['mocha'] || allDeps['ava']) {
|
|
466
|
-
analysis.hasTests = true
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Detect linting
|
|
470
|
-
if (allDeps['eslint'] || allDeps['biome'] || allDeps['@biomejs/biome']) {
|
|
471
|
-
analysis.hasLinting = true
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// Detect formatting
|
|
475
|
-
if (allDeps['prettier'] || allDeps['biome'] || allDeps['@biomejs/biome']) {
|
|
476
|
-
analysis.hasFormatting = true
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Key files
|
|
480
|
-
analysis.keyFiles.push('package.json')
|
|
481
|
-
|
|
482
|
-
} catch (error) {
|
|
483
|
-
// Not a Node.js project
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
async function analyzeConfigFiles(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
488
|
-
const configFiles = [
|
|
489
|
-
'tsconfig.json',
|
|
490
|
-
'.eslintrc.json',
|
|
491
|
-
'.eslintrc.js',
|
|
492
|
-
'.prettierrc',
|
|
493
|
-
'prettier.config.js',
|
|
494
|
-
'vite.config.ts',
|
|
495
|
-
'vite.config.js',
|
|
496
|
-
'webpack.config.js',
|
|
497
|
-
'rollup.config.js',
|
|
498
|
-
'tailwind.config.js',
|
|
499
|
-
'tailwind.config.ts',
|
|
500
|
-
'postcss.config.js',
|
|
501
|
-
'next.config.js',
|
|
502
|
-
'next.config.ts',
|
|
503
|
-
'nuxt.config.ts',
|
|
504
|
-
'astro.config.mjs',
|
|
505
|
-
'svelte.config.js',
|
|
506
|
-
'biome.json'
|
|
507
|
-
]
|
|
508
|
-
|
|
509
|
-
for (const file of configFiles) {
|
|
510
|
-
if (await fileExists(path.join(projectPath, file))) {
|
|
511
|
-
analysis.keyFiles.push(file)
|
|
512
|
-
|
|
513
|
-
// Detect specific tools
|
|
514
|
-
if (file.includes('tailwind')) analysis.frameworks.push('tailwindcss')
|
|
515
|
-
if (file.includes('vite')) analysis.frameworks.push('vite')
|
|
516
|
-
if (file.includes('webpack')) analysis.frameworks.push('webpack')
|
|
517
|
-
if (file.includes('astro')) analysis.frameworks.push('astro')
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
async function analyzePythonProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
523
|
-
// Check for Python files
|
|
524
|
-
if (await fileExists(path.join(projectPath, 'requirements.txt')) ||
|
|
525
|
-
await fileExists(path.join(projectPath, 'pyproject.toml')) ||
|
|
526
|
-
await fileExists(path.join(projectPath, 'setup.py'))) {
|
|
527
|
-
|
|
528
|
-
analysis.languages.push('python')
|
|
529
|
-
|
|
530
|
-
if (await fileExists(path.join(projectPath, 'requirements.txt'))) {
|
|
531
|
-
analysis.keyFiles.push('requirements.txt')
|
|
532
|
-
analysis.packageManager = 'pip'
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (await fileExists(path.join(projectPath, 'pyproject.toml'))) {
|
|
536
|
-
analysis.keyFiles.push('pyproject.toml')
|
|
537
|
-
analysis.packageManager = 'poetry'
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Check for frameworks
|
|
541
|
-
try {
|
|
542
|
-
const reqPath = path.join(projectPath, 'requirements.txt')
|
|
543
|
-
if (await fileExists(reqPath)) {
|
|
544
|
-
const content = await fs.readFile(reqPath, 'utf-8')
|
|
545
|
-
if (content.includes('django')) analysis.frameworks.push('django')
|
|
546
|
-
if (content.includes('flask')) analysis.frameworks.push('flask')
|
|
547
|
-
if (content.includes('fastapi')) analysis.frameworks.push('fastapi')
|
|
548
|
-
if (content.includes('pytest')) analysis.hasTests = true
|
|
549
|
-
}
|
|
550
|
-
} catch {}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function analyzeGoProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
555
|
-
if (await fileExists(path.join(projectPath, 'go.mod'))) {
|
|
556
|
-
analysis.languages.push('go')
|
|
557
|
-
analysis.keyFiles.push('go.mod')
|
|
558
|
-
analysis.packageManager = 'go modules'
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async function analyzeRustProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
563
|
-
if (await fileExists(path.join(projectPath, 'Cargo.toml'))) {
|
|
564
|
-
analysis.languages.push('rust')
|
|
565
|
-
analysis.keyFiles.push('Cargo.toml')
|
|
566
|
-
analysis.packageManager = 'cargo'
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
async function detectTests(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
571
|
-
const testDirs = ['test', 'tests', '__tests__', 'spec', 'specs']
|
|
572
|
-
|
|
573
|
-
for (const dir of testDirs) {
|
|
574
|
-
if (await fileExists(path.join(projectPath, dir))) {
|
|
575
|
-
analysis.hasTests = true
|
|
576
|
-
analysis.keyFiles.push(`${dir}/`)
|
|
577
|
-
break
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
async function readReadme(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
583
|
-
const readmeFiles = ['README.md', 'readme.md', 'Readme.md', 'README.txt', 'README']
|
|
584
|
-
|
|
585
|
-
for (const file of readmeFiles) {
|
|
586
|
-
try {
|
|
587
|
-
const content = await fs.readFile(path.join(projectPath, file), 'utf-8')
|
|
588
|
-
analysis.keyFiles.push(file)
|
|
589
|
-
|
|
590
|
-
// Extract first paragraph as description if not set
|
|
591
|
-
if (!analysis.description) {
|
|
592
|
-
const lines = content.split('\n')
|
|
593
|
-
for (const line of lines) {
|
|
594
|
-
const trimmed = line.trim()
|
|
595
|
-
// Skip headers and empty lines
|
|
596
|
-
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('!') && trimmed.length > 20) {
|
|
597
|
-
analysis.description = trimmed.slice(0, 200)
|
|
598
|
-
break
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
break
|
|
603
|
-
} catch {}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
async function readGitignore(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
608
|
-
try {
|
|
609
|
-
const content = await fs.readFile(path.join(projectPath, '.gitignore'), 'utf-8')
|
|
610
|
-
analysis.gitIgnorePatterns = content
|
|
611
|
-
.split('\n')
|
|
612
|
-
.filter(line => line.trim() && !line.startsWith('#'))
|
|
613
|
-
.slice(0, 20)
|
|
614
|
-
} catch {}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function detectConventions(analysis: ProjectAnalysis) {
|
|
618
|
-
// Detect conventions based on structure and config
|
|
619
|
-
|
|
620
|
-
if (analysis.structure.includes('src/')) {
|
|
621
|
-
analysis.conventions.push('Source code in src/ directory')
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (analysis.structure.includes('lib/')) {
|
|
625
|
-
analysis.conventions.push('Library code in lib/ directory')
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (analysis.structure.includes('components/') || analysis.structure.some(s => s.includes('components'))) {
|
|
629
|
-
analysis.conventions.push('Component-based architecture')
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (analysis.hasTypeScript) {
|
|
633
|
-
analysis.conventions.push('TypeScript for type safety')
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (analysis.hasLinting) {
|
|
637
|
-
analysis.conventions.push('Code linting enforced')
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (analysis.hasFormatting) {
|
|
641
|
-
analysis.conventions.push('Code formatting enforced')
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
if (analysis.hasTests) {
|
|
645
|
-
analysis.conventions.push('Test coverage expected')
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (analysis.frameworks.includes('tailwindcss')) {
|
|
649
|
-
analysis.conventions.push('Tailwind CSS for styling')
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
function detectTags(analysis: ProjectAnalysis): string[] {
|
|
654
|
-
const tags: string[] = []
|
|
655
|
-
|
|
656
|
-
if (analysis.frameworks.some(f => ['react', 'vue', 'angular', 'svelte'].includes(f))) {
|
|
657
|
-
tags.push('frontend')
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (analysis.frameworks.some(f => ['express', 'fastify', 'nestjs', 'hono', 'elysia'].includes(f))) {
|
|
661
|
-
tags.push('backend')
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (analysis.frameworks.some(f => ['next.js', 'nuxt', 'remix'].includes(f))) {
|
|
665
|
-
tags.push('fullstack')
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
if (analysis.frameworks.includes('electron') || analysis.frameworks.includes('tauri')) {
|
|
669
|
-
tags.push('desktop')
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return tags
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function generateContextContent(analysis: ProjectAnalysis): string {
|
|
676
|
-
const parts: string[] = []
|
|
677
|
-
|
|
678
|
-
parts.push(`# ${analysis.name}\n`)
|
|
679
|
-
|
|
680
|
-
parts.push(`## Overview`)
|
|
681
|
-
parts.push(analysis.description || '*Add project description here*')
|
|
682
|
-
parts.push('')
|
|
683
|
-
|
|
684
|
-
parts.push(`## Tech Stack`)
|
|
685
|
-
if (analysis.languages.length > 0) {
|
|
686
|
-
parts.push(`**Languages:** ${analysis.languages.join(', ')}`)
|
|
687
|
-
}
|
|
688
|
-
if (analysis.frameworks.length > 0) {
|
|
689
|
-
parts.push(`**Frameworks:** ${analysis.frameworks.join(', ')}`)
|
|
690
|
-
}
|
|
691
|
-
if (analysis.packageManager) {
|
|
692
|
-
parts.push(`**Package Manager:** ${analysis.packageManager}`)
|
|
693
|
-
}
|
|
694
|
-
parts.push('')
|
|
695
|
-
|
|
696
|
-
parts.push(`## Architecture`)
|
|
697
|
-
parts.push(`### Project Structure`)
|
|
698
|
-
parts.push('```')
|
|
699
|
-
analysis.structure.slice(0, 15).forEach(s => parts.push(s))
|
|
700
|
-
parts.push('```')
|
|
701
|
-
parts.push('')
|
|
702
|
-
|
|
703
|
-
if (analysis.keyFiles.length > 0) {
|
|
704
|
-
parts.push(`### Key Files`)
|
|
705
|
-
analysis.keyFiles.forEach(f => parts.push(`- \`${f}\``))
|
|
706
|
-
parts.push('')
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
parts.push(`## Key Decisions`)
|
|
710
|
-
parts.push(`*Use \`remember_decision\` to add architectural decisions here*`)
|
|
711
|
-
|
|
712
|
-
return parts.join('\n')
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function generateStandardsContent(analysis: ProjectAnalysis): string {
|
|
716
|
-
const parts: string[] = []
|
|
717
|
-
|
|
718
|
-
parts.push(`# Coding Standards: ${analysis.name}\n`)
|
|
719
|
-
|
|
720
|
-
parts.push(`## General Principles`)
|
|
721
|
-
analysis.conventions.forEach(c => parts.push(`- ${c}`))
|
|
722
|
-
if (analysis.conventions.length === 0) {
|
|
723
|
-
parts.push('*Add general coding principles*')
|
|
724
|
-
}
|
|
725
|
-
parts.push('')
|
|
726
|
-
|
|
727
|
-
parts.push(`## Language-Specific Standards`)
|
|
728
|
-
for (const lang of analysis.languages) {
|
|
729
|
-
parts.push(`### ${lang.charAt(0).toUpperCase() + lang.slice(1)}`)
|
|
730
|
-
parts.push(`*Add ${lang}-specific standards*`)
|
|
731
|
-
parts.push('')
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (analysis.frameworks.length > 0) {
|
|
735
|
-
parts.push(`## Framework-Specific Patterns`)
|
|
736
|
-
for (const fw of analysis.frameworks) {
|
|
737
|
-
parts.push(`### ${fw}`)
|
|
738
|
-
parts.push(`*Add ${fw}-specific patterns and conventions*`)
|
|
739
|
-
parts.push('')
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
parts.push(`## Common Anti-Patterns to Avoid`)
|
|
744
|
-
parts.push(`*Document patterns to avoid*`)
|
|
745
|
-
|
|
746
|
-
return parts.join('\n')
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
750
|
-
try {
|
|
751
|
-
await fs.access(filePath)
|
|
752
|
-
return true
|
|
753
|
-
} catch {
|
|
754
|
-
return false
|
|
755
|
-
}
|
|
756
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Init Project Handler
|
|
3
|
+
* Analyzes an existing codebase and creates project in vault
|
|
4
|
+
* Similar to `claude init` - reads files to extract project info
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Logger } from 'pino'
|
|
8
|
+
import type { ToolResponse } from '@/tools/types'
|
|
9
|
+
import { getVaultService, getMemoryService, isServicesInitialized } from '@/server/services'
|
|
10
|
+
import { ResponseFormatter } from '@/server/utils/response-formatter'
|
|
11
|
+
import { ErrorHandler } from '@/server/utils/error-handler'
|
|
12
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'
|
|
13
|
+
import { PackManager, PackLoader, type PackLoadResult } from '@/packs/index'
|
|
14
|
+
import fs from 'fs/promises'
|
|
15
|
+
import path from 'path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import { dirname, resolve } from 'node:path'
|
|
18
|
+
|
|
19
|
+
interface InitProjectInput {
|
|
20
|
+
project_path?: string
|
|
21
|
+
project_name?: string
|
|
22
|
+
save_to_memory?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ProjectAnalysis {
|
|
26
|
+
name: string
|
|
27
|
+
description: string
|
|
28
|
+
techStack: string[]
|
|
29
|
+
frameworks: string[]
|
|
30
|
+
languages: string[]
|
|
31
|
+
packageManager: string | null
|
|
32
|
+
structure: string[]
|
|
33
|
+
keyFiles: string[]
|
|
34
|
+
scripts: Record<string, string>
|
|
35
|
+
dependencies: string[]
|
|
36
|
+
devDependencies: string[]
|
|
37
|
+
hasTests: boolean
|
|
38
|
+
hasTypeScript: boolean
|
|
39
|
+
hasLinting: boolean
|
|
40
|
+
hasFormatting: boolean
|
|
41
|
+
gitIgnorePatterns: string[]
|
|
42
|
+
conventions: string[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function handleInitProject(
|
|
46
|
+
args: unknown,
|
|
47
|
+
logger: Logger
|
|
48
|
+
): Promise<ToolResponse> {
|
|
49
|
+
try {
|
|
50
|
+
const input = args as InitProjectInput
|
|
51
|
+
const {
|
|
52
|
+
project_path = process.cwd(),
|
|
53
|
+
project_name,
|
|
54
|
+
save_to_memory = true
|
|
55
|
+
} = input
|
|
56
|
+
|
|
57
|
+
logger.info({ project_path, project_name }, 'Initializing project analysis')
|
|
58
|
+
|
|
59
|
+
if (!isServicesInitialized()) {
|
|
60
|
+
throw new McpError(
|
|
61
|
+
ErrorCode.InternalError,
|
|
62
|
+
'Services not initialized'
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Verify path exists
|
|
67
|
+
try {
|
|
68
|
+
const stats = await fs.stat(project_path)
|
|
69
|
+
if (!stats.isDirectory()) {
|
|
70
|
+
throw new McpError(
|
|
71
|
+
ErrorCode.InvalidParams,
|
|
72
|
+
`Path is not a directory: ${project_path}`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
77
|
+
throw new McpError(
|
|
78
|
+
ErrorCode.InvalidParams,
|
|
79
|
+
`Directory not found: ${project_path}`
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
throw error
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Analyze the project
|
|
86
|
+
const analysis = await analyzeProject(project_path, logger)
|
|
87
|
+
|
|
88
|
+
// Use provided name or detected name
|
|
89
|
+
const finalName = project_name || analysis.name
|
|
90
|
+
const normalizedName = finalName
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replace(/\s+/g, '-')
|
|
93
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
94
|
+
|
|
95
|
+
if (!normalizedName || normalizedName.length < 2) {
|
|
96
|
+
throw new McpError(
|
|
97
|
+
ErrorCode.InvalidParams,
|
|
98
|
+
'Could not determine project name. Please provide project_name parameter.'
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const vault = getVaultService()
|
|
103
|
+
|
|
104
|
+
// Check if project already exists
|
|
105
|
+
const existingProjects = await vault.listProjects()
|
|
106
|
+
const projectExists = existingProjects.includes(normalizedName)
|
|
107
|
+
|
|
108
|
+
if (!projectExists) {
|
|
109
|
+
// Create new project
|
|
110
|
+
await vault.createProject(normalizedName)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update project files with analysis
|
|
114
|
+
const projectPaths = vault.getProjectPaths(normalizedName)
|
|
115
|
+
|
|
116
|
+
// Update context.md
|
|
117
|
+
const contextContent = generateContextContent(analysis)
|
|
118
|
+
await vault.writer.writeMarkdownFile(
|
|
119
|
+
projectPaths.context,
|
|
120
|
+
{
|
|
121
|
+
type: 'project-context',
|
|
122
|
+
project: normalizedName,
|
|
123
|
+
status: 'active',
|
|
124
|
+
created: new Date().toISOString().split('T')[0],
|
|
125
|
+
updated: new Date().toISOString().split('T')[0],
|
|
126
|
+
tech_stack: analysis.techStack,
|
|
127
|
+
languages: analysis.languages,
|
|
128
|
+
frameworks: analysis.frameworks,
|
|
129
|
+
tags: detectTags(analysis),
|
|
130
|
+
description: analysis.description,
|
|
131
|
+
analyzed_from: project_path
|
|
132
|
+
},
|
|
133
|
+
contextContent,
|
|
134
|
+
{ createBackup: projectExists }
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// Update standards.md
|
|
138
|
+
const standardsContent = generateStandardsContent(analysis)
|
|
139
|
+
await vault.writer.writeMarkdownFile(
|
|
140
|
+
projectPaths.standards,
|
|
141
|
+
{
|
|
142
|
+
type: 'coding-standards',
|
|
143
|
+
project: normalizedName,
|
|
144
|
+
languages: analysis.languages,
|
|
145
|
+
frameworks: analysis.frameworks,
|
|
146
|
+
last_updated: new Date().toISOString().split('T')[0]
|
|
147
|
+
},
|
|
148
|
+
standardsContent,
|
|
149
|
+
{ createBackup: projectExists }
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// Save to memory for semantic search
|
|
153
|
+
if (save_to_memory) {
|
|
154
|
+
const memory = getMemoryService()
|
|
155
|
+
|
|
156
|
+
// Store project overview as a decision/context
|
|
157
|
+
const projectSummary = `
|
|
158
|
+
Project: ${normalizedName}
|
|
159
|
+
Description: ${analysis.description}
|
|
160
|
+
Tech Stack: ${analysis.techStack.join(', ')}
|
|
161
|
+
Languages: ${analysis.languages.join(', ')}
|
|
162
|
+
Frameworks: ${analysis.frameworks.join(', ')}
|
|
163
|
+
Key Files: ${analysis.keyFiles.join(', ')}
|
|
164
|
+
Has Tests: ${analysis.hasTests}
|
|
165
|
+
Has TypeScript: ${analysis.hasTypeScript}
|
|
166
|
+
`.trim()
|
|
167
|
+
|
|
168
|
+
await memory.rememberDecision(
|
|
169
|
+
normalizedName,
|
|
170
|
+
'Project initialization and setup',
|
|
171
|
+
`Initialized ${normalizedName} with Claude Brain`,
|
|
172
|
+
projectSummary,
|
|
173
|
+
{
|
|
174
|
+
tags: ['project-init', 'setup', ...analysis.languages, ...analysis.frameworks]
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
logger.info({ normalizedName }, 'Project saved to memory')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Phase 18: Load knowledge packs based on detected tech stack
|
|
182
|
+
let packResult: PackLoadResult | null = null
|
|
183
|
+
try {
|
|
184
|
+
const initFile = fileURLToPath(import.meta.url)
|
|
185
|
+
const initDir = dirname(initFile)
|
|
186
|
+
const PACKAGE_ROOT = resolve(initDir, '..', '..', '..', '..')
|
|
187
|
+
const dataDir = path.join(process.env.CLAUDE_BRAIN_HOME || path.join(process.env.HOME || '~', '.claude-brain'), 'data')
|
|
188
|
+
|
|
189
|
+
// Default packs config
|
|
190
|
+
const packsConfig = {
|
|
191
|
+
enabled: true,
|
|
192
|
+
packsDir: 'packs',
|
|
193
|
+
alwaysLoadCore: true,
|
|
194
|
+
alwaysLoadMeta: true,
|
|
195
|
+
communityConfidenceMultiplier: 0.8,
|
|
196
|
+
personalBoost: 1.2,
|
|
197
|
+
projectBoost: 1.15
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (packsConfig.enabled) {
|
|
201
|
+
const memory = getMemoryService()
|
|
202
|
+
const packManager = new PackManager(logger, packsConfig, PACKAGE_ROOT, dataDir)
|
|
203
|
+
const packLoader = new PackLoader(logger, memory, packManager, packsConfig)
|
|
204
|
+
packResult = await packLoader.loadPacksForProject(normalizedName, analysis.techStack)
|
|
205
|
+
logger.info({ packResult }, 'Knowledge packs loaded')
|
|
206
|
+
}
|
|
207
|
+
} catch (packError) {
|
|
208
|
+
logger.warn({ error: packError }, 'Pack loading failed (non-blocking)')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Build response
|
|
212
|
+
const parts: string[] = []
|
|
213
|
+
parts.push(`## ${projectExists ? '🔄 Project Updated' : '✅ Project Initialized'}: ${normalizedName}\n`)
|
|
214
|
+
|
|
215
|
+
parts.push(`### Analysis Results\n`)
|
|
216
|
+
parts.push(`**Source:** \`${project_path}\`\n`)
|
|
217
|
+
|
|
218
|
+
if (analysis.description) {
|
|
219
|
+
parts.push(`**Description:** ${analysis.description}\n`)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
parts.push(`\n### Tech Stack`)
|
|
223
|
+
parts.push(`- **Languages:** ${analysis.languages.join(', ') || 'Not detected'}`)
|
|
224
|
+
parts.push(`- **Frameworks:** ${analysis.frameworks.join(', ') || 'Not detected'}`)
|
|
225
|
+
parts.push(`- **Package Manager:** ${analysis.packageManager || 'Not detected'}`)
|
|
226
|
+
|
|
227
|
+
parts.push(`\n### Project Features`)
|
|
228
|
+
parts.push(`- TypeScript: ${analysis.hasTypeScript ? '✅' : '❌'}`)
|
|
229
|
+
parts.push(`- Tests: ${analysis.hasTests ? '✅' : '❌'}`)
|
|
230
|
+
parts.push(`- Linting: ${analysis.hasLinting ? '✅' : '❌'}`)
|
|
231
|
+
parts.push(`- Formatting: ${analysis.hasFormatting ? '✅' : '❌'}`)
|
|
232
|
+
|
|
233
|
+
if (analysis.keyFiles.length > 0) {
|
|
234
|
+
parts.push(`\n### Key Files Detected`)
|
|
235
|
+
analysis.keyFiles.slice(0, 10).forEach(f => parts.push(`- \`${f}\``))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (Object.keys(analysis.scripts).length > 0) {
|
|
239
|
+
parts.push(`\n### Available Scripts`)
|
|
240
|
+
Object.entries(analysis.scripts).slice(0, 8).forEach(([name, cmd]) => {
|
|
241
|
+
parts.push(`- \`${name}\`: ${cmd.slice(0, 50)}${cmd.length > 50 ? '...' : ''}`)
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (analysis.conventions.length > 0) {
|
|
246
|
+
parts.push(`\n### Detected Conventions`)
|
|
247
|
+
analysis.conventions.forEach(c => parts.push(`- ${c}`))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
parts.push(`\n### Files Created/Updated`)
|
|
251
|
+
parts.push(`- \`${normalizedName}/context.md\` - Project overview`)
|
|
252
|
+
parts.push(`- \`${normalizedName}/standards.md\` - Coding standards`)
|
|
253
|
+
parts.push(`- \`${normalizedName}/progress.md\` - Progress tracking`)
|
|
254
|
+
parts.push(`- \`${normalizedName}/decisions.md\` - Decision log`)
|
|
255
|
+
|
|
256
|
+
if (save_to_memory) {
|
|
257
|
+
parts.push(`\n### Memory`)
|
|
258
|
+
parts.push(`Project context saved to semantic memory for future recall.`)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (packResult && packResult.packsLoaded > 0) {
|
|
262
|
+
parts.push(`\n### Knowledge Packs`)
|
|
263
|
+
parts.push(`Loaded **${packResult.packsLoaded}** pack(s) with **${packResult.entriesLoaded}** entries:`)
|
|
264
|
+
for (const detail of packResult.packDetails) {
|
|
265
|
+
parts.push(`- **${detail.name}** (${detail.entriesLoaded} entries)`)
|
|
266
|
+
}
|
|
267
|
+
if (packResult.skipped.length > 0) {
|
|
268
|
+
parts.push(`\nSkipped: ${packResult.skipped.map(s => `${s.packId} (${s.reason})`).join(', ')}`)
|
|
269
|
+
}
|
|
270
|
+
} else if (packResult && packResult.skipped.length > 0) {
|
|
271
|
+
parts.push(`\n### Knowledge Packs`)
|
|
272
|
+
parts.push(`All packs already loaded (${packResult.skipped.length} skipped).`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
parts.push(`\n### Next Steps`)
|
|
276
|
+
parts.push(`1. Use \`get_project_context("${normalizedName}")\` to view full context`)
|
|
277
|
+
parts.push(`2. Use \`remember_decision\` to save architectural decisions`)
|
|
278
|
+
parts.push(`3. Use \`recall_similar\` to search past decisions`)
|
|
279
|
+
|
|
280
|
+
logger.info({
|
|
281
|
+
project: normalizedName,
|
|
282
|
+
techStack: analysis.techStack,
|
|
283
|
+
isNew: !projectExists
|
|
284
|
+
}, 'Project initialized successfully')
|
|
285
|
+
|
|
286
|
+
return ResponseFormatter.text(parts.join('\n'))
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
ErrorHandler.logError(logger, error, { tool: 'init_project', args })
|
|
290
|
+
|
|
291
|
+
if (error instanceof McpError) {
|
|
292
|
+
throw error
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Provide detailed error message
|
|
296
|
+
if (error instanceof Error) {
|
|
297
|
+
let errorMsg = `Failed to initialize project: ${error.message}\n\n`
|
|
298
|
+
|
|
299
|
+
// Add specific troubleshooting steps based on error type
|
|
300
|
+
if (error.message.includes('EACCES') || error.message.includes('permission')) {
|
|
301
|
+
errorMsg += `**Permission Error**\n` +
|
|
302
|
+
`- Check that you have read/write permissions for the project directory\n` +
|
|
303
|
+
`- Ensure the Obsidian vault path is writable\n`
|
|
304
|
+
} else if (error.message.includes('ENOENT')) {
|
|
305
|
+
errorMsg += `**File Not Found**\n` +
|
|
306
|
+
`- Verify the project_path exists\n` +
|
|
307
|
+
`- Check that the path is absolute, not relative\n`
|
|
308
|
+
} else if (error.message.includes('parse') || error.message.includes('JSON')) {
|
|
309
|
+
errorMsg += `**Parse Error**\n` +
|
|
310
|
+
`- package.json or other config files may be malformed\n` +
|
|
311
|
+
`- Check for syntax errors in configuration files\n`
|
|
312
|
+
} else {
|
|
313
|
+
errorMsg += `**Troubleshooting:**\n` +
|
|
314
|
+
`- Ensure the directory exists and is accessible\n` +
|
|
315
|
+
`- Check file permissions in both project and vault directories\n` +
|
|
316
|
+
`- Verify config files (package.json, etc.) are valid\n`
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
errorMsg += `\n**Error details:** ${error.stack || error.message}`
|
|
320
|
+
|
|
321
|
+
throw new McpError(ErrorCode.InternalError, errorMsg)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
throw new McpError(
|
|
325
|
+
ErrorCode.InternalError,
|
|
326
|
+
`Failed to initialize project: Unknown error occurred`
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Analyze project directory
|
|
333
|
+
*/
|
|
334
|
+
async function analyzeProject(projectPath: string, logger: Logger): Promise<ProjectAnalysis> {
|
|
335
|
+
const analysis: ProjectAnalysis = {
|
|
336
|
+
name: path.basename(projectPath),
|
|
337
|
+
description: '',
|
|
338
|
+
techStack: [],
|
|
339
|
+
frameworks: [],
|
|
340
|
+
languages: [],
|
|
341
|
+
packageManager: null,
|
|
342
|
+
structure: [],
|
|
343
|
+
keyFiles: [],
|
|
344
|
+
scripts: {},
|
|
345
|
+
dependencies: [],
|
|
346
|
+
devDependencies: [],
|
|
347
|
+
hasTests: false,
|
|
348
|
+
hasTypeScript: false,
|
|
349
|
+
hasLinting: false,
|
|
350
|
+
hasFormatting: false,
|
|
351
|
+
gitIgnorePatterns: [],
|
|
352
|
+
conventions: []
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Read directory structure (top level + 1 deep)
|
|
356
|
+
try {
|
|
357
|
+
const entries = await fs.readdir(projectPath, { withFileTypes: true })
|
|
358
|
+
|
|
359
|
+
for (const entry of entries) {
|
|
360
|
+
if (entry.name.startsWith('.') && entry.name !== '.eslintrc.json' && entry.name !== '.prettierrc') {
|
|
361
|
+
continue
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (entry.isDirectory()) {
|
|
365
|
+
analysis.structure.push(`${entry.name}/`)
|
|
366
|
+
} else {
|
|
367
|
+
analysis.structure.push(entry.name)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
logger.warn({ error }, 'Failed to read directory structure')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check for package.json (Node.js/JavaScript projects)
|
|
375
|
+
await analyzePackageJson(projectPath, analysis, logger)
|
|
376
|
+
|
|
377
|
+
// Check for other config files
|
|
378
|
+
await analyzeConfigFiles(projectPath, analysis, logger)
|
|
379
|
+
|
|
380
|
+
// Check for Python projects
|
|
381
|
+
await analyzePythonProject(projectPath, analysis, logger)
|
|
382
|
+
|
|
383
|
+
// Check for Go projects
|
|
384
|
+
await analyzeGoProject(projectPath, analysis, logger)
|
|
385
|
+
|
|
386
|
+
// Check for Rust projects
|
|
387
|
+
await analyzeRustProject(projectPath, analysis, logger)
|
|
388
|
+
|
|
389
|
+
// Detect test directories
|
|
390
|
+
await detectTests(projectPath, analysis, logger)
|
|
391
|
+
|
|
392
|
+
// Read README for description
|
|
393
|
+
await readReadme(projectPath, analysis, logger)
|
|
394
|
+
|
|
395
|
+
// Read .gitignore
|
|
396
|
+
await readGitignore(projectPath, analysis, logger)
|
|
397
|
+
|
|
398
|
+
// Detect conventions from structure
|
|
399
|
+
detectConventions(analysis)
|
|
400
|
+
|
|
401
|
+
// Build final tech stack
|
|
402
|
+
analysis.techStack = [...new Set([...analysis.languages, ...analysis.frameworks])]
|
|
403
|
+
|
|
404
|
+
return analysis
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function analyzePackageJson(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
408
|
+
try {
|
|
409
|
+
const pkgPath = path.join(projectPath, 'package.json')
|
|
410
|
+
const content = await fs.readFile(pkgPath, 'utf-8')
|
|
411
|
+
const pkg = JSON.parse(content)
|
|
412
|
+
|
|
413
|
+
analysis.name = pkg.name || analysis.name
|
|
414
|
+
analysis.description = pkg.description || ''
|
|
415
|
+
analysis.scripts = pkg.scripts || {}
|
|
416
|
+
|
|
417
|
+
// Detect package manager
|
|
418
|
+
if (await fileExists(path.join(projectPath, 'bun.lockb'))) {
|
|
419
|
+
analysis.packageManager = 'bun'
|
|
420
|
+
} else if (await fileExists(path.join(projectPath, 'pnpm-lock.yaml'))) {
|
|
421
|
+
analysis.packageManager = 'pnpm'
|
|
422
|
+
} else if (await fileExists(path.join(projectPath, 'yarn.lock'))) {
|
|
423
|
+
analysis.packageManager = 'yarn'
|
|
424
|
+
} else if (await fileExists(path.join(projectPath, 'package-lock.json'))) {
|
|
425
|
+
analysis.packageManager = 'npm'
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Languages
|
|
429
|
+
analysis.languages.push('javascript')
|
|
430
|
+
|
|
431
|
+
// Analyze dependencies
|
|
432
|
+
const allDeps = {
|
|
433
|
+
...pkg.dependencies,
|
|
434
|
+
...pkg.devDependencies
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
analysis.dependencies = Object.keys(pkg.dependencies || {})
|
|
438
|
+
analysis.devDependencies = Object.keys(pkg.devDependencies || {})
|
|
439
|
+
|
|
440
|
+
// Detect frameworks
|
|
441
|
+
if (allDeps['react']) {
|
|
442
|
+
analysis.frameworks.push('react')
|
|
443
|
+
if (allDeps['next']) analysis.frameworks.push('next.js')
|
|
444
|
+
if (allDeps['gatsby']) analysis.frameworks.push('gatsby')
|
|
445
|
+
if (allDeps['remix']) analysis.frameworks.push('remix')
|
|
446
|
+
}
|
|
447
|
+
if (allDeps['vue']) analysis.frameworks.push('vue')
|
|
448
|
+
if (allDeps['@angular/core']) analysis.frameworks.push('angular')
|
|
449
|
+
if (allDeps['svelte']) analysis.frameworks.push('svelte')
|
|
450
|
+
if (allDeps['express']) analysis.frameworks.push('express')
|
|
451
|
+
if (allDeps['fastify']) analysis.frameworks.push('fastify')
|
|
452
|
+
if (allDeps['hono']) analysis.frameworks.push('hono')
|
|
453
|
+
if (allDeps['elysia']) analysis.frameworks.push('elysia')
|
|
454
|
+
if (allDeps['nestjs'] || allDeps['@nestjs/core']) analysis.frameworks.push('nestjs')
|
|
455
|
+
if (allDeps['electron']) analysis.frameworks.push('electron')
|
|
456
|
+
if (allDeps['tauri']) analysis.frameworks.push('tauri')
|
|
457
|
+
|
|
458
|
+
// Detect TypeScript
|
|
459
|
+
if (allDeps['typescript'] || await fileExists(path.join(projectPath, 'tsconfig.json'))) {
|
|
460
|
+
analysis.hasTypeScript = true
|
|
461
|
+
analysis.languages.push('typescript')
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Detect testing
|
|
465
|
+
if (allDeps['jest'] || allDeps['vitest'] || allDeps['mocha'] || allDeps['ava']) {
|
|
466
|
+
analysis.hasTests = true
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Detect linting
|
|
470
|
+
if (allDeps['eslint'] || allDeps['biome'] || allDeps['@biomejs/biome']) {
|
|
471
|
+
analysis.hasLinting = true
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Detect formatting
|
|
475
|
+
if (allDeps['prettier'] || allDeps['biome'] || allDeps['@biomejs/biome']) {
|
|
476
|
+
analysis.hasFormatting = true
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Key files
|
|
480
|
+
analysis.keyFiles.push('package.json')
|
|
481
|
+
|
|
482
|
+
} catch (error) {
|
|
483
|
+
// Not a Node.js project
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function analyzeConfigFiles(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
488
|
+
const configFiles = [
|
|
489
|
+
'tsconfig.json',
|
|
490
|
+
'.eslintrc.json',
|
|
491
|
+
'.eslintrc.js',
|
|
492
|
+
'.prettierrc',
|
|
493
|
+
'prettier.config.js',
|
|
494
|
+
'vite.config.ts',
|
|
495
|
+
'vite.config.js',
|
|
496
|
+
'webpack.config.js',
|
|
497
|
+
'rollup.config.js',
|
|
498
|
+
'tailwind.config.js',
|
|
499
|
+
'tailwind.config.ts',
|
|
500
|
+
'postcss.config.js',
|
|
501
|
+
'next.config.js',
|
|
502
|
+
'next.config.ts',
|
|
503
|
+
'nuxt.config.ts',
|
|
504
|
+
'astro.config.mjs',
|
|
505
|
+
'svelte.config.js',
|
|
506
|
+
'biome.json'
|
|
507
|
+
]
|
|
508
|
+
|
|
509
|
+
for (const file of configFiles) {
|
|
510
|
+
if (await fileExists(path.join(projectPath, file))) {
|
|
511
|
+
analysis.keyFiles.push(file)
|
|
512
|
+
|
|
513
|
+
// Detect specific tools
|
|
514
|
+
if (file.includes('tailwind')) analysis.frameworks.push('tailwindcss')
|
|
515
|
+
if (file.includes('vite')) analysis.frameworks.push('vite')
|
|
516
|
+
if (file.includes('webpack')) analysis.frameworks.push('webpack')
|
|
517
|
+
if (file.includes('astro')) analysis.frameworks.push('astro')
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function analyzePythonProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
523
|
+
// Check for Python files
|
|
524
|
+
if (await fileExists(path.join(projectPath, 'requirements.txt')) ||
|
|
525
|
+
await fileExists(path.join(projectPath, 'pyproject.toml')) ||
|
|
526
|
+
await fileExists(path.join(projectPath, 'setup.py'))) {
|
|
527
|
+
|
|
528
|
+
analysis.languages.push('python')
|
|
529
|
+
|
|
530
|
+
if (await fileExists(path.join(projectPath, 'requirements.txt'))) {
|
|
531
|
+
analysis.keyFiles.push('requirements.txt')
|
|
532
|
+
analysis.packageManager = 'pip'
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (await fileExists(path.join(projectPath, 'pyproject.toml'))) {
|
|
536
|
+
analysis.keyFiles.push('pyproject.toml')
|
|
537
|
+
analysis.packageManager = 'poetry'
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Check for frameworks
|
|
541
|
+
try {
|
|
542
|
+
const reqPath = path.join(projectPath, 'requirements.txt')
|
|
543
|
+
if (await fileExists(reqPath)) {
|
|
544
|
+
const content = await fs.readFile(reqPath, 'utf-8')
|
|
545
|
+
if (content.includes('django')) analysis.frameworks.push('django')
|
|
546
|
+
if (content.includes('flask')) analysis.frameworks.push('flask')
|
|
547
|
+
if (content.includes('fastapi')) analysis.frameworks.push('fastapi')
|
|
548
|
+
if (content.includes('pytest')) analysis.hasTests = true
|
|
549
|
+
}
|
|
550
|
+
} catch {}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function analyzeGoProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
555
|
+
if (await fileExists(path.join(projectPath, 'go.mod'))) {
|
|
556
|
+
analysis.languages.push('go')
|
|
557
|
+
analysis.keyFiles.push('go.mod')
|
|
558
|
+
analysis.packageManager = 'go modules'
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function analyzeRustProject(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
563
|
+
if (await fileExists(path.join(projectPath, 'Cargo.toml'))) {
|
|
564
|
+
analysis.languages.push('rust')
|
|
565
|
+
analysis.keyFiles.push('Cargo.toml')
|
|
566
|
+
analysis.packageManager = 'cargo'
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function detectTests(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
571
|
+
const testDirs = ['test', 'tests', '__tests__', 'spec', 'specs']
|
|
572
|
+
|
|
573
|
+
for (const dir of testDirs) {
|
|
574
|
+
if (await fileExists(path.join(projectPath, dir))) {
|
|
575
|
+
analysis.hasTests = true
|
|
576
|
+
analysis.keyFiles.push(`${dir}/`)
|
|
577
|
+
break
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async function readReadme(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
583
|
+
const readmeFiles = ['README.md', 'readme.md', 'Readme.md', 'README.txt', 'README']
|
|
584
|
+
|
|
585
|
+
for (const file of readmeFiles) {
|
|
586
|
+
try {
|
|
587
|
+
const content = await fs.readFile(path.join(projectPath, file), 'utf-8')
|
|
588
|
+
analysis.keyFiles.push(file)
|
|
589
|
+
|
|
590
|
+
// Extract first paragraph as description if not set
|
|
591
|
+
if (!analysis.description) {
|
|
592
|
+
const lines = content.split('\n')
|
|
593
|
+
for (const line of lines) {
|
|
594
|
+
const trimmed = line.trim()
|
|
595
|
+
// Skip headers and empty lines
|
|
596
|
+
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('!') && trimmed.length > 20) {
|
|
597
|
+
analysis.description = trimmed.slice(0, 200)
|
|
598
|
+
break
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
break
|
|
603
|
+
} catch {}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function readGitignore(projectPath: string, analysis: ProjectAnalysis, _logger: Logger) {
|
|
608
|
+
try {
|
|
609
|
+
const content = await fs.readFile(path.join(projectPath, '.gitignore'), 'utf-8')
|
|
610
|
+
analysis.gitIgnorePatterns = content
|
|
611
|
+
.split('\n')
|
|
612
|
+
.filter(line => line.trim() && !line.startsWith('#'))
|
|
613
|
+
.slice(0, 20)
|
|
614
|
+
} catch {}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function detectConventions(analysis: ProjectAnalysis) {
|
|
618
|
+
// Detect conventions based on structure and config
|
|
619
|
+
|
|
620
|
+
if (analysis.structure.includes('src/')) {
|
|
621
|
+
analysis.conventions.push('Source code in src/ directory')
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (analysis.structure.includes('lib/')) {
|
|
625
|
+
analysis.conventions.push('Library code in lib/ directory')
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (analysis.structure.includes('components/') || analysis.structure.some(s => s.includes('components'))) {
|
|
629
|
+
analysis.conventions.push('Component-based architecture')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (analysis.hasTypeScript) {
|
|
633
|
+
analysis.conventions.push('TypeScript for type safety')
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (analysis.hasLinting) {
|
|
637
|
+
analysis.conventions.push('Code linting enforced')
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (analysis.hasFormatting) {
|
|
641
|
+
analysis.conventions.push('Code formatting enforced')
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (analysis.hasTests) {
|
|
645
|
+
analysis.conventions.push('Test coverage expected')
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (analysis.frameworks.includes('tailwindcss')) {
|
|
649
|
+
analysis.conventions.push('Tailwind CSS for styling')
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function detectTags(analysis: ProjectAnalysis): string[] {
|
|
654
|
+
const tags: string[] = []
|
|
655
|
+
|
|
656
|
+
if (analysis.frameworks.some(f => ['react', 'vue', 'angular', 'svelte'].includes(f))) {
|
|
657
|
+
tags.push('frontend')
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (analysis.frameworks.some(f => ['express', 'fastify', 'nestjs', 'hono', 'elysia'].includes(f))) {
|
|
661
|
+
tags.push('backend')
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (analysis.frameworks.some(f => ['next.js', 'nuxt', 'remix'].includes(f))) {
|
|
665
|
+
tags.push('fullstack')
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (analysis.frameworks.includes('electron') || analysis.frameworks.includes('tauri')) {
|
|
669
|
+
tags.push('desktop')
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return tags
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function generateContextContent(analysis: ProjectAnalysis): string {
|
|
676
|
+
const parts: string[] = []
|
|
677
|
+
|
|
678
|
+
parts.push(`# ${analysis.name}\n`)
|
|
679
|
+
|
|
680
|
+
parts.push(`## Overview`)
|
|
681
|
+
parts.push(analysis.description || '*Add project description here*')
|
|
682
|
+
parts.push('')
|
|
683
|
+
|
|
684
|
+
parts.push(`## Tech Stack`)
|
|
685
|
+
if (analysis.languages.length > 0) {
|
|
686
|
+
parts.push(`**Languages:** ${analysis.languages.join(', ')}`)
|
|
687
|
+
}
|
|
688
|
+
if (analysis.frameworks.length > 0) {
|
|
689
|
+
parts.push(`**Frameworks:** ${analysis.frameworks.join(', ')}`)
|
|
690
|
+
}
|
|
691
|
+
if (analysis.packageManager) {
|
|
692
|
+
parts.push(`**Package Manager:** ${analysis.packageManager}`)
|
|
693
|
+
}
|
|
694
|
+
parts.push('')
|
|
695
|
+
|
|
696
|
+
parts.push(`## Architecture`)
|
|
697
|
+
parts.push(`### Project Structure`)
|
|
698
|
+
parts.push('```')
|
|
699
|
+
analysis.structure.slice(0, 15).forEach(s => parts.push(s))
|
|
700
|
+
parts.push('```')
|
|
701
|
+
parts.push('')
|
|
702
|
+
|
|
703
|
+
if (analysis.keyFiles.length > 0) {
|
|
704
|
+
parts.push(`### Key Files`)
|
|
705
|
+
analysis.keyFiles.forEach(f => parts.push(`- \`${f}\``))
|
|
706
|
+
parts.push('')
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
parts.push(`## Key Decisions`)
|
|
710
|
+
parts.push(`*Use \`remember_decision\` to add architectural decisions here*`)
|
|
711
|
+
|
|
712
|
+
return parts.join('\n')
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function generateStandardsContent(analysis: ProjectAnalysis): string {
|
|
716
|
+
const parts: string[] = []
|
|
717
|
+
|
|
718
|
+
parts.push(`# Coding Standards: ${analysis.name}\n`)
|
|
719
|
+
|
|
720
|
+
parts.push(`## General Principles`)
|
|
721
|
+
analysis.conventions.forEach(c => parts.push(`- ${c}`))
|
|
722
|
+
if (analysis.conventions.length === 0) {
|
|
723
|
+
parts.push('*Add general coding principles*')
|
|
724
|
+
}
|
|
725
|
+
parts.push('')
|
|
726
|
+
|
|
727
|
+
parts.push(`## Language-Specific Standards`)
|
|
728
|
+
for (const lang of analysis.languages) {
|
|
729
|
+
parts.push(`### ${lang.charAt(0).toUpperCase() + lang.slice(1)}`)
|
|
730
|
+
parts.push(`*Add ${lang}-specific standards*`)
|
|
731
|
+
parts.push('')
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (analysis.frameworks.length > 0) {
|
|
735
|
+
parts.push(`## Framework-Specific Patterns`)
|
|
736
|
+
for (const fw of analysis.frameworks) {
|
|
737
|
+
parts.push(`### ${fw}`)
|
|
738
|
+
parts.push(`*Add ${fw}-specific patterns and conventions*`)
|
|
739
|
+
parts.push('')
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
parts.push(`## Common Anti-Patterns to Avoid`)
|
|
744
|
+
parts.push(`*Document patterns to avoid*`)
|
|
745
|
+
|
|
746
|
+
return parts.join('\n')
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function fileExists(filePath: string): Promise<boolean> {
|
|
750
|
+
try {
|
|
751
|
+
await fs.access(filePath)
|
|
752
|
+
return true
|
|
753
|
+
} catch {
|
|
754
|
+
return false
|
|
755
|
+
}
|
|
756
|
+
}
|