hone-ai 0.5.0 → 0.10.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/README.md +47 -2
- package/package.json +5 -2
- package/src/agent-client.integration.test.ts +57 -59
- package/src/agent-client.test.ts +27 -27
- package/src/agent-client.ts +109 -77
- package/src/agent.test.ts +16 -16
- package/src/agent.ts +103 -103
- package/src/agents-md-generator.test.ts +360 -0
- package/src/agents-md-generator.ts +900 -0
- package/src/config.test.ts +209 -224
- package/src/config.ts +84 -83
- package/src/errors.test.ts +211 -208
- package/src/errors.ts +107 -101
- package/src/index.integration.test.ts +327 -223
- package/src/index.ts +163 -100
- package/src/integration-test.ts +168 -137
- package/src/logger.test.ts +67 -67
- package/src/logger.ts +8 -8
- package/src/prd-generator.integration.test.ts +50 -50
- package/src/prd-generator.test.ts +66 -25
- package/src/prd-generator.ts +280 -194
- package/src/prds.test.ts +60 -65
- package/src/prds.ts +64 -62
- package/src/prompt.test.ts +154 -155
- package/src/prompt.ts +63 -65
- package/src/run.ts +147 -147
- package/src/status.test.ts +80 -80
- package/src/status.ts +40 -42
- package/src/task-generator.test.ts +93 -66
- package/src/task-generator.ts +125 -112
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGENTS.md generation functionality
|
|
3
|
+
* Core module for generating project documentation for AI agents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig, resolveModelForPhase, type HoneConfig, type AgentType } from './config'
|
|
7
|
+
import { readFile, writeFile, mkdir } from 'fs/promises'
|
|
8
|
+
import { join } from 'path'
|
|
9
|
+
import { existsSync } from 'fs'
|
|
10
|
+
import { AgentClient } from './agent-client'
|
|
11
|
+
import { log, logError, logVerbose, logVerboseError } from './logger'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Central constant for the agents documentation directory name
|
|
15
|
+
* This can be made configurable via config file in the future if needed
|
|
16
|
+
*/
|
|
17
|
+
export const AGENTS_DOCS_DIR = '.agents-docs'
|
|
18
|
+
|
|
19
|
+
export interface AgentsMdGeneratorOptions {
|
|
20
|
+
projectPath?: string
|
|
21
|
+
overwrite?: boolean
|
|
22
|
+
agent?: AgentType
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProjectAnalysis {
|
|
26
|
+
languages: string[]
|
|
27
|
+
buildSystems: string[]
|
|
28
|
+
testingFrameworks: string[]
|
|
29
|
+
dependencies: string[]
|
|
30
|
+
architecture: string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GenerationResult {
|
|
34
|
+
success: boolean
|
|
35
|
+
mainFilePath?: string
|
|
36
|
+
agentsDirPath?: string
|
|
37
|
+
filesCreated: string[]
|
|
38
|
+
error?: Error
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TemplateSection {
|
|
42
|
+
title: string
|
|
43
|
+
content: string
|
|
44
|
+
priority: number
|
|
45
|
+
detailFile?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract preservable content (gotchas, custom learnings) from existing AGENTS.md
|
|
50
|
+
* This function looks for custom sections that should be preserved when regenerating
|
|
51
|
+
*/
|
|
52
|
+
function extractPreservableContent(existingContent: string): string | null {
|
|
53
|
+
const lines = existingContent.split('\n')
|
|
54
|
+
const preservedSections: string[] = []
|
|
55
|
+
let currentSection: string[] = []
|
|
56
|
+
let inPreservableSection = false
|
|
57
|
+
let sectionTitle = ''
|
|
58
|
+
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (!line) continue
|
|
61
|
+
|
|
62
|
+
// Look for section headers that might contain gotchas/learnings
|
|
63
|
+
if (line.startsWith('## ') || line.startsWith('# ')) {
|
|
64
|
+
// Save previous section if it was preservable
|
|
65
|
+
if (inPreservableSection && currentSection.length > 0) {
|
|
66
|
+
preservedSections.push(`## ${sectionTitle}\n\n${currentSection.join('\n').trim()}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if this is a section we want to preserve
|
|
70
|
+
const title = line.replace(/^#+\s*/, '').toLowerCase()
|
|
71
|
+
sectionTitle = line.replace(/^#+\s*/, '')
|
|
72
|
+
inPreservableSection =
|
|
73
|
+
title.includes('gotcha') ||
|
|
74
|
+
title.includes('learning') ||
|
|
75
|
+
title.includes('note') ||
|
|
76
|
+
title.includes('warning') ||
|
|
77
|
+
title.includes('tip') ||
|
|
78
|
+
title.includes('custom') ||
|
|
79
|
+
title.includes('specific') ||
|
|
80
|
+
line.includes('PRESERVED CONTENT')
|
|
81
|
+
|
|
82
|
+
currentSection = []
|
|
83
|
+
} else if (inPreservableSection) {
|
|
84
|
+
currentSection.push(line)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Don't forget the last section
|
|
89
|
+
if (inPreservableSection && currentSection.length > 0) {
|
|
90
|
+
preservedSections.push(`## ${sectionTitle}\n\n${currentSection.join('\n').trim()}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return preservedSections.length > 0 ? preservedSections.join('\n\n') : null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Analyze project structure and gather context for AGENTS.md generation
|
|
98
|
+
*/
|
|
99
|
+
async function analyzeProject(projectPath: string): Promise<ProjectAnalysis> {
|
|
100
|
+
const analysis: ProjectAnalysis = {
|
|
101
|
+
languages: [],
|
|
102
|
+
buildSystems: [],
|
|
103
|
+
testingFrameworks: [],
|
|
104
|
+
dependencies: [],
|
|
105
|
+
architecture: [],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Package.json analysis for Node.js projects
|
|
110
|
+
const pkgPath = join(projectPath, 'package.json')
|
|
111
|
+
if (existsSync(pkgPath)) {
|
|
112
|
+
try {
|
|
113
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))
|
|
114
|
+
|
|
115
|
+
// Detect languages based on dependencies
|
|
116
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
117
|
+
if (allDeps.typescript || existsSync(join(projectPath, 'tsconfig.json'))) {
|
|
118
|
+
analysis.languages.push('TypeScript')
|
|
119
|
+
} else {
|
|
120
|
+
analysis.languages.push('JavaScript')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Detect frameworks and build systems
|
|
124
|
+
if (allDeps.react) analysis.dependencies.push('React')
|
|
125
|
+
if (allDeps.next) analysis.dependencies.push('Next.js')
|
|
126
|
+
if (allDeps.vue) analysis.dependencies.push('Vue.js')
|
|
127
|
+
if (allDeps.express) analysis.dependencies.push('Express')
|
|
128
|
+
if (allDeps.fastify) analysis.dependencies.push('Fastify')
|
|
129
|
+
if (allDeps['commander'] || allDeps['commander.js'])
|
|
130
|
+
analysis.dependencies.push('Commander.js')
|
|
131
|
+
|
|
132
|
+
// Build systems
|
|
133
|
+
if (pkg.scripts?.build) analysis.buildSystems.push('npm scripts')
|
|
134
|
+
if (existsSync(join(projectPath, 'webpack.config.js')))
|
|
135
|
+
analysis.buildSystems.push('Webpack')
|
|
136
|
+
if (
|
|
137
|
+
existsSync(join(projectPath, 'vite.config.ts')) ||
|
|
138
|
+
existsSync(join(projectPath, 'vite.config.js'))
|
|
139
|
+
) {
|
|
140
|
+
analysis.buildSystems.push('Vite')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Testing frameworks
|
|
144
|
+
if (allDeps.jest) analysis.testingFrameworks.push('Jest')
|
|
145
|
+
if (allDeps.vitest) analysis.testingFrameworks.push('Vitest')
|
|
146
|
+
if (allDeps.mocha) analysis.testingFrameworks.push('Mocha')
|
|
147
|
+
if (allDeps.bun) {
|
|
148
|
+
analysis.testingFrameworks.push('Bun Test')
|
|
149
|
+
analysis.buildSystems.push('Bun')
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logVerbose(`Could not parse package.json: ${error}`)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Python project detection
|
|
157
|
+
if (
|
|
158
|
+
existsSync(join(projectPath, 'requirements.txt')) ||
|
|
159
|
+
existsSync(join(projectPath, 'pyproject.toml')) ||
|
|
160
|
+
existsSync(join(projectPath, 'setup.py'))
|
|
161
|
+
) {
|
|
162
|
+
analysis.languages.push('Python')
|
|
163
|
+
|
|
164
|
+
if (existsSync(join(projectPath, 'pyproject.toml'))) {
|
|
165
|
+
analysis.buildSystems.push('Poetry/setuptools')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Java project detection
|
|
170
|
+
if (existsSync(join(projectPath, 'pom.xml'))) {
|
|
171
|
+
analysis.languages.push('Java')
|
|
172
|
+
analysis.buildSystems.push('Maven')
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
existsSync(join(projectPath, 'build.gradle')) ||
|
|
176
|
+
existsSync(join(projectPath, 'build.gradle.kts'))
|
|
177
|
+
) {
|
|
178
|
+
analysis.languages.push('Java/Kotlin')
|
|
179
|
+
analysis.buildSystems.push('Gradle')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Go project detection
|
|
183
|
+
if (existsSync(join(projectPath, 'go.mod'))) {
|
|
184
|
+
analysis.languages.push('Go')
|
|
185
|
+
analysis.buildSystems.push('Go modules')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Rust project detection
|
|
189
|
+
if (existsSync(join(projectPath, 'Cargo.toml'))) {
|
|
190
|
+
analysis.languages.push('Rust')
|
|
191
|
+
analysis.buildSystems.push('Cargo')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Architecture patterns
|
|
195
|
+
if (existsSync(join(projectPath, 'src'))) {
|
|
196
|
+
analysis.architecture.push('src/ directory structure')
|
|
197
|
+
}
|
|
198
|
+
if (existsSync(join(projectPath, 'docker-compose.yml'))) {
|
|
199
|
+
analysis.architecture.push('Docker Compose')
|
|
200
|
+
}
|
|
201
|
+
if (existsSync(join(projectPath, 'Dockerfile'))) {
|
|
202
|
+
analysis.architecture.push('Docker containerization')
|
|
203
|
+
}
|
|
204
|
+
if (existsSync(join(projectPath, '.github/workflows'))) {
|
|
205
|
+
analysis.architecture.push('GitHub Actions CI/CD')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
logVerbose(`[AgentsMd] Project analysis complete: ${JSON.stringify(analysis, null, 2)}`)
|
|
209
|
+
return analysis
|
|
210
|
+
} catch (error) {
|
|
211
|
+
logVerboseError(
|
|
212
|
+
`[AgentsMd] Error analyzing project: ${error instanceof Error ? error.message : error}`
|
|
213
|
+
)
|
|
214
|
+
return analysis // Return partial analysis on error
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Discovery prompts for analyzing different aspects of the project
|
|
220
|
+
*/
|
|
221
|
+
const DISCOVERY_PROMPTS = {
|
|
222
|
+
languages: `Analyze this project's codebase to identify the primary programming languages used and their purposes.
|
|
223
|
+
|
|
224
|
+
IMPORTANT LANGUAGE DETECTION RULES:
|
|
225
|
+
- Look for source code files (.js, .ts, .py, .java, .go, .rs, .php, .rb, etc.)
|
|
226
|
+
- Check package.json, requirements.txt, go.mod, Cargo.toml, pom.xml, build.gradle for dependencies
|
|
227
|
+
- Identify language-specific configuration files (tsconfig.json, .eslintrc, setup.py, etc.)
|
|
228
|
+
- For TypeScript projects, note if it's primarily TypeScript or mixed JS/TS
|
|
229
|
+
- For frontend projects, distinguish between client-side and server-side languages
|
|
230
|
+
|
|
231
|
+
CRITICAL: Your response MUST start directly with the structured format below. NO preambles like "Based on my analysis..." or "Here's what I found..." - start IMMEDIATELY with "PRIMARY LANGUAGES:".
|
|
232
|
+
|
|
233
|
+
PRIMARY LANGUAGES: [language 1, language 2, ...]
|
|
234
|
+
USAGE CONTEXT: [brief explanation of how each language is used in the project]`,
|
|
235
|
+
|
|
236
|
+
buildSystems: `Analyze this project to identify build systems, package managers, and compilation/bundling tools.
|
|
237
|
+
|
|
238
|
+
BUILD SYSTEM DETECTION RULES:
|
|
239
|
+
- npm/yarn/pnpm: Look for package.json, package-lock.json, yarn.lock, pnpm-lock.yaml
|
|
240
|
+
- Maven: Look for pom.xml, maven-wrapper files
|
|
241
|
+
- Gradle: Look for build.gradle, gradlew files
|
|
242
|
+
- Go modules: Look for go.mod, go.sum
|
|
243
|
+
- Cargo: Look for Cargo.toml, Cargo.lock
|
|
244
|
+
- Webpack: Look for webpack.config.js, webpack configurations
|
|
245
|
+
- Vite: Look for vite.config.ts/js
|
|
246
|
+
- Parcel: Look for .parcelrc, parcel configurations
|
|
247
|
+
- Build scripts in package.json (build, bundle, compile commands)
|
|
248
|
+
- Docker: Look for Dockerfile, docker-compose.yml
|
|
249
|
+
- Make: Look for Makefile
|
|
250
|
+
- Custom build scripts in various languages
|
|
251
|
+
|
|
252
|
+
CRITICAL: Your response MUST start directly with the structured format below. NO preambles like "Based on my analysis..." or "Here's what I found..." - start IMMEDIATELY with "BUILD SYSTEMS:".
|
|
253
|
+
|
|
254
|
+
BUILD SYSTEMS: [system 1, system 2, ...]
|
|
255
|
+
BUILD COMMANDS: [key build commands developers should know]
|
|
256
|
+
BUNDLING: [bundling tools if applicable]`,
|
|
257
|
+
|
|
258
|
+
testing: `Identify testing frameworks, test organization patterns, and testing strategies used in this project.
|
|
259
|
+
|
|
260
|
+
TESTING FRAMEWORK DETECTION:
|
|
261
|
+
- JavaScript/TypeScript: Jest, Vitest, Mocha, Cypress, Playwright, Testing Library
|
|
262
|
+
- Python: pytest, unittest, nose, tox
|
|
263
|
+
- Java: JUnit, TestNG, Mockito, Spring Test
|
|
264
|
+
- Go: built-in testing, Testify, Ginkgo
|
|
265
|
+
- Rust: built-in testing, proptest, criterion
|
|
266
|
+
- Ruby: RSpec, minitest
|
|
267
|
+
- PHP: PHPUnit, Pest
|
|
268
|
+
|
|
269
|
+
Look for:
|
|
270
|
+
- Test files (*.test.*, *.spec.*, *_test.*, test_*.py)
|
|
271
|
+
- Test directories (/test, /tests, /__tests__)
|
|
272
|
+
- Configuration files (jest.config.js, vitest.config.ts, pytest.ini)
|
|
273
|
+
- CI/CD test configurations
|
|
274
|
+
- Mock/stub patterns
|
|
275
|
+
- E2E testing setup
|
|
276
|
+
|
|
277
|
+
CRITICAL: Your response MUST start directly with the structured format below. NO preambles like "Based on my analysis..." or "Here's what I found..." - start IMMEDIATELY with "TESTING FRAMEWORKS:".
|
|
278
|
+
|
|
279
|
+
TESTING FRAMEWORKS: [framework 1, framework 2, ...]
|
|
280
|
+
TEST COMMANDS: [how to run tests]
|
|
281
|
+
TEST ORGANIZATION: [how tests are structured and organized]
|
|
282
|
+
E2E TESTING: [end-to-end testing approach if present]`,
|
|
283
|
+
|
|
284
|
+
architecture: `Analyze the project's architectural patterns, directory structure, and design decisions.
|
|
285
|
+
|
|
286
|
+
ARCHITECTURE ANALYSIS AREAS:
|
|
287
|
+
- Directory/folder structure and organization
|
|
288
|
+
- Design patterns (MVC, MVP, MVVM, layered architecture, microservices, etc.)
|
|
289
|
+
- Code organization (modules, packages, namespaces)
|
|
290
|
+
- Database integration patterns
|
|
291
|
+
- API design patterns (REST, GraphQL, RPC)
|
|
292
|
+
- Configuration management
|
|
293
|
+
- Dependency injection patterns
|
|
294
|
+
- Error handling patterns
|
|
295
|
+
- Logging and monitoring
|
|
296
|
+
- Security patterns
|
|
297
|
+
- Performance considerations
|
|
298
|
+
|
|
299
|
+
Examine:
|
|
300
|
+
- Source code organization in src/, lib/, app/ directories
|
|
301
|
+
- Configuration files and their patterns
|
|
302
|
+
- Database schema or ORM usage
|
|
303
|
+
- API endpoint definitions
|
|
304
|
+
- Middleware/interceptor patterns
|
|
305
|
+
- Shared utilities and common code
|
|
306
|
+
|
|
307
|
+
CRITICAL: Your response MUST start directly with the structured format below. NO preambles like "Based on my analysis..." or "Here's what I found..." - start IMMEDIATELY with "ARCHITECTURE PATTERN:".
|
|
308
|
+
|
|
309
|
+
ARCHITECTURE PATTERN: [primary architectural pattern]
|
|
310
|
+
DIRECTORY STRUCTURE: [key organizational principles]
|
|
311
|
+
DESIGN PATTERNS: [notable design patterns in use]
|
|
312
|
+
DATABASE: [data layer architecture if applicable]
|
|
313
|
+
API DESIGN: [API architectural patterns if applicable]`,
|
|
314
|
+
|
|
315
|
+
deployment: `Analyze deployment strategies, infrastructure patterns, and operational considerations for this project.
|
|
316
|
+
|
|
317
|
+
DEPLOYMENT ANALYSIS:
|
|
318
|
+
- Containerization (Docker, Podman)
|
|
319
|
+
- Container orchestration (Kubernetes, Docker Swarm, Docker Compose)
|
|
320
|
+
- Cloud platforms (AWS, GCP, Azure, Vercel, Netlify, Railway)
|
|
321
|
+
- CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI)
|
|
322
|
+
- Infrastructure as Code (Terraform, CloudFormation, Pulumi)
|
|
323
|
+
- Serverless deployment (Lambda, Cloud Functions, Vercel Functions)
|
|
324
|
+
- Static site deployment
|
|
325
|
+
- Database deployment and migrations
|
|
326
|
+
- Environment configuration management
|
|
327
|
+
- Monitoring and logging setup
|
|
328
|
+
|
|
329
|
+
Look for:
|
|
330
|
+
- Dockerfile, docker-compose.yml
|
|
331
|
+
- .github/workflows/, .gitlab-ci.yml, Jenkinsfile
|
|
332
|
+
- Cloud provider configuration files
|
|
333
|
+
- Deployment scripts
|
|
334
|
+
- Environment variable configurations (.env patterns)
|
|
335
|
+
- Database migration files
|
|
336
|
+
- Package.json deploy scripts
|
|
337
|
+
|
|
338
|
+
CRITICAL: Your response MUST start directly with the structured format below. NO preambles like "Based on my analysis..." or "Here's what I found..." - start IMMEDIATELY with "DEPLOYMENT STRATEGY:".
|
|
339
|
+
|
|
340
|
+
DEPLOYMENT STRATEGY: [primary deployment approach]
|
|
341
|
+
CONTAINERIZATION: [Docker/container usage]
|
|
342
|
+
CI/CD: [continuous integration/deployment setup]
|
|
343
|
+
HOSTING: [where the application is designed to be hosted]
|
|
344
|
+
ENVIRONMENT MANAGEMENT: [how environments are configured]`,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Execute a discovery prompt against the project using agent
|
|
349
|
+
*/
|
|
350
|
+
async function executeDiscoveryPrompt(
|
|
351
|
+
projectPath: string,
|
|
352
|
+
promptKey: keyof typeof DISCOVERY_PROMPTS,
|
|
353
|
+
config: HoneConfig,
|
|
354
|
+
agent?: AgentType
|
|
355
|
+
): Promise<string> {
|
|
356
|
+
const resolvedAgent = agent || config.defaultAgent
|
|
357
|
+
const model = resolveModelForPhase(config, 'agentsMd', resolvedAgent) // Use agentsMd phase model with resolved agent
|
|
358
|
+
const client = new AgentClient({
|
|
359
|
+
agent: resolvedAgent,
|
|
360
|
+
model,
|
|
361
|
+
workingDir: projectPath,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
logVerbose(`[AgentsMd] Executing ${promptKey} discovery prompt`)
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await client.messages.create({
|
|
368
|
+
max_tokens: 2000,
|
|
369
|
+
messages: [
|
|
370
|
+
{
|
|
371
|
+
role: 'user',
|
|
372
|
+
content: 'Analyze the project and provide the requested analysis.',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
system: DISCOVERY_PROMPTS[promptKey],
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
const content = response.content[0]
|
|
379
|
+
const result = content && content.type === 'text' ? content.text.trim() : ''
|
|
380
|
+
|
|
381
|
+
logVerbose(`[AgentsMd] Completed ${promptKey} discovery: ${result.substring(0, 100)}...`)
|
|
382
|
+
return result
|
|
383
|
+
} catch (error) {
|
|
384
|
+
logVerboseError(
|
|
385
|
+
`[AgentsMd] Failed ${promptKey} discovery: ${error instanceof Error ? error.message : error}`
|
|
386
|
+
)
|
|
387
|
+
return `Error analyzing ${promptKey}: ${error instanceof Error ? error.message : error}`
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Execute parallel agent-based project scanning
|
|
393
|
+
*/
|
|
394
|
+
async function executeParallelScanning(
|
|
395
|
+
projectPath: string,
|
|
396
|
+
config: HoneConfig,
|
|
397
|
+
agent?: AgentType
|
|
398
|
+
): Promise<Record<keyof typeof DISCOVERY_PROMPTS, string>> {
|
|
399
|
+
const promptKeys = Object.keys(DISCOVERY_PROMPTS) as (keyof typeof DISCOVERY_PROMPTS)[]
|
|
400
|
+
|
|
401
|
+
logVerbose(`[AgentsMd] Starting parallel scanning with ${promptKeys.length} discovery prompts`)
|
|
402
|
+
|
|
403
|
+
// Execute all discovery prompts in parallel to stay within 90-second limit
|
|
404
|
+
const results = await Promise.all(
|
|
405
|
+
promptKeys.map(async key => ({
|
|
406
|
+
key,
|
|
407
|
+
result: await executeDiscoveryPrompt(projectPath, key, config, agent),
|
|
408
|
+
}))
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
// Convert results array to object
|
|
412
|
+
const scanResults = Object.fromEntries(results.map(({ key, result }) => [key, result])) as Record<
|
|
413
|
+
keyof typeof DISCOVERY_PROMPTS,
|
|
414
|
+
string
|
|
415
|
+
>
|
|
416
|
+
|
|
417
|
+
logVerbose(`[AgentsMd] Parallel scanning completed with results for: ${promptKeys.join(', ')}`)
|
|
418
|
+
return scanResults
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Create adaptive template sections based on discovered tech stack
|
|
423
|
+
*/
|
|
424
|
+
function createTemplateSections(
|
|
425
|
+
scanResults: Record<keyof typeof DISCOVERY_PROMPTS, string>,
|
|
426
|
+
analysis: ProjectAnalysis
|
|
427
|
+
): TemplateSection[] {
|
|
428
|
+
const sections: TemplateSection[] = []
|
|
429
|
+
|
|
430
|
+
// Build content using agent discovery results with static analysis fallback
|
|
431
|
+
const getContentWithFallback = (agentResult: string | undefined, fallbackData: string[]) => {
|
|
432
|
+
if (
|
|
433
|
+
agentResult &&
|
|
434
|
+
!agentResult.includes('failed to analyze') &&
|
|
435
|
+
!agentResult.includes('Error:')
|
|
436
|
+
) {
|
|
437
|
+
return agentResult
|
|
438
|
+
}
|
|
439
|
+
return fallbackData.length > 0
|
|
440
|
+
? `Static analysis detected: ${fallbackData.join(', ')}`
|
|
441
|
+
: 'Not available.'
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// High priority sections (always include)
|
|
445
|
+
sections.push({
|
|
446
|
+
title: 'Project Overview',
|
|
447
|
+
content: getContentWithFallback(scanResults.languages || '', analysis.languages),
|
|
448
|
+
priority: 1,
|
|
449
|
+
detailFile: 'languages.md',
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
sections.push({
|
|
453
|
+
title: 'Build System',
|
|
454
|
+
content: getContentWithFallback(scanResults.buildSystems || '', analysis.buildSystems),
|
|
455
|
+
priority: 2,
|
|
456
|
+
detailFile: 'build.md',
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// Medium priority sections (include if they have significant content)
|
|
460
|
+
const testingContent = getContentWithFallback(
|
|
461
|
+
scanResults.testing || '',
|
|
462
|
+
analysis.testingFrameworks
|
|
463
|
+
)
|
|
464
|
+
if (testingContent && !testingContent.includes('Not available')) {
|
|
465
|
+
sections.push({
|
|
466
|
+
title: 'Testing Framework',
|
|
467
|
+
content: testingContent,
|
|
468
|
+
priority: 3,
|
|
469
|
+
detailFile: 'testing.md',
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const architectureContent = getContentWithFallback(
|
|
474
|
+
scanResults.architecture || '',
|
|
475
|
+
analysis.architecture
|
|
476
|
+
)
|
|
477
|
+
if (architectureContent && !architectureContent.includes('Not available')) {
|
|
478
|
+
sections.push({
|
|
479
|
+
title: 'Architecture',
|
|
480
|
+
content: architectureContent,
|
|
481
|
+
priority: 4,
|
|
482
|
+
detailFile: 'architecture.md',
|
|
483
|
+
})
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Lower priority sections (include if space allows)
|
|
487
|
+
const deploymentContent = scanResults.deployment || ''
|
|
488
|
+
if (deploymentContent && !deploymentContent.includes('not available')) {
|
|
489
|
+
sections.push({
|
|
490
|
+
title: 'Deployment',
|
|
491
|
+
content: deploymentContent,
|
|
492
|
+
priority: 5,
|
|
493
|
+
detailFile: 'deployment.md',
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Sort by priority
|
|
498
|
+
return sections.sort((a, b) => a.priority - b.priority)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Count lines in text content
|
|
503
|
+
*/
|
|
504
|
+
function countLines(text: string): number {
|
|
505
|
+
return text.split('\n').length
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Generate compact AGENTS.md content that fits within 100-line limit
|
|
510
|
+
*/
|
|
511
|
+
function generateCompactContent(sections: TemplateSection[], useAgentsDir: boolean): string {
|
|
512
|
+
const header = `# AGENTS.md
|
|
513
|
+
|
|
514
|
+
Learnings and patterns for future agents working on this project.
|
|
515
|
+
`
|
|
516
|
+
|
|
517
|
+
if (!useAgentsDir) {
|
|
518
|
+
// Full content in main file
|
|
519
|
+
const fullSections = sections
|
|
520
|
+
.map(
|
|
521
|
+
section => `## ${section.title}
|
|
522
|
+
|
|
523
|
+
${section.content}
|
|
524
|
+
`
|
|
525
|
+
)
|
|
526
|
+
.join('\n')
|
|
527
|
+
|
|
528
|
+
return (
|
|
529
|
+
header +
|
|
530
|
+
'\n' +
|
|
531
|
+
fullSections +
|
|
532
|
+
`
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
*This AGENTS.md was generated using agent-based project discovery.*
|
|
536
|
+
`
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Compact version with references to ${AGENTS_DOCS_DIR}/ files
|
|
541
|
+
const compactSections = sections
|
|
542
|
+
.map(
|
|
543
|
+
section => `## ${section.title}
|
|
544
|
+
|
|
545
|
+
${getFirstSentence(section.content)}
|
|
546
|
+
|
|
547
|
+
See [@${AGENTS_DOCS_DIR}/${section.detailFile}](${AGENTS_DOCS_DIR}/${section.detailFile}) for detailed information.
|
|
548
|
+
`
|
|
549
|
+
)
|
|
550
|
+
.join('\n')
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
header +
|
|
554
|
+
'\n' +
|
|
555
|
+
compactSections +
|
|
556
|
+
`
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
*This AGENTS.md was generated using agent-based project discovery.*
|
|
560
|
+
*Detailed information is available in the ${AGENTS_DOCS_DIR}/ directory.*
|
|
561
|
+
`
|
|
562
|
+
)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Extract concise, informative summary from agent-generated content
|
|
567
|
+
* Skips unhelpful preambles like "Based on my analysis..."
|
|
568
|
+
*/
|
|
569
|
+
function getFirstSentence(content: string): string {
|
|
570
|
+
if (!content) return 'Information not available.'
|
|
571
|
+
|
|
572
|
+
// Skip common unhelpful agent preambles - comprehensive list of patterns
|
|
573
|
+
const skipPatterns = [
|
|
574
|
+
/^Based on (?:my |the )?(?:comprehensive |detailed |thorough )?(?:analysis|exploration|examination|review|investigation).*?[:,]\s*/gi,
|
|
575
|
+
/^(?:Here's|Here is).*?(?:analysis|overview|summary|breakdown).*?[:,]\s*/gi,
|
|
576
|
+
/^I(?:'ve|'ll| have| will).*?(?:analyze|explore|examine|review).*?[:,]\s*/gi,
|
|
577
|
+
/^(?:Looking|Examining|Reviewing|Analyzing) (?:at )?(?:the |this )?(?:project|codebase|code).*?[:,]\s*/gi,
|
|
578
|
+
/^After (?:analyzing|examining|reviewing|exploring).*?[:,]\s*/gi,
|
|
579
|
+
/^Let me (?:analyze|explore|examine|review).*?[:,]\s*/gi,
|
|
580
|
+
/^Upon (?:analysis|examination|review|exploration).*?[:,]\s*/gi,
|
|
581
|
+
/^(?:The |This )?(?:analysis|exploration|examination) (?:shows|reveals|indicates).*?[:,]\s*/gi,
|
|
582
|
+
]
|
|
583
|
+
|
|
584
|
+
let cleanContent = content.trim()
|
|
585
|
+
|
|
586
|
+
// Remove matching preamble patterns (including following whitespace/newlines)
|
|
587
|
+
for (const pattern of skipPatterns) {
|
|
588
|
+
cleanContent = cleanContent.replace(pattern, '').trim()
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Look for structured information markers (uppercase patterns)
|
|
592
|
+
const lines = cleanContent.split('\n').filter(line => line.trim())
|
|
593
|
+
|
|
594
|
+
// Try to find lines with structured info like "**KEY**: value" or "KEY: value"
|
|
595
|
+
for (const line of lines) {
|
|
596
|
+
const trimmed = line.trim()
|
|
597
|
+
// Match **UPPERCASE**: value or UPPERCASE: value patterns
|
|
598
|
+
if (trimmed.match(/^\*\*[A-Z][A-Z\s_-]+\*\*\s*:|^[A-Z][A-Z\s_-]+:/)) {
|
|
599
|
+
if (trimmed.length <= 150) {
|
|
600
|
+
return trimmed
|
|
601
|
+
}
|
|
602
|
+
// If too long, extract just the key and first part of value
|
|
603
|
+
const colonIndex = trimmed.indexOf(':')
|
|
604
|
+
if (colonIndex > 0) {
|
|
605
|
+
const key = trimmed.substring(0, colonIndex + 1)
|
|
606
|
+
const value = trimmed.substring(colonIndex + 1).trim()
|
|
607
|
+
const shortValue = value.split(/[,;]/)[0]?.trim() || value.substring(0, 80)
|
|
608
|
+
return `${key} ${shortValue}`
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Try to get the first meaningful line that isn't a preamble
|
|
614
|
+
for (const line of lines) {
|
|
615
|
+
const trimmed = line.trim()
|
|
616
|
+
// Skip lines that look like preambles
|
|
617
|
+
if (
|
|
618
|
+
trimmed.toLowerCase().startsWith('based on') ||
|
|
619
|
+
trimmed.toLowerCase().startsWith("here's") ||
|
|
620
|
+
trimmed.toLowerCase().startsWith('here is') ||
|
|
621
|
+
trimmed.toLowerCase().startsWith('i ') ||
|
|
622
|
+
trimmed.toLowerCase().startsWith("i'") ||
|
|
623
|
+
trimmed.toLowerCase().startsWith('the analysis') ||
|
|
624
|
+
trimmed.toLowerCase().startsWith('looking at') ||
|
|
625
|
+
trimmed.toLowerCase().startsWith('after ')
|
|
626
|
+
) {
|
|
627
|
+
continue
|
|
628
|
+
}
|
|
629
|
+
if (trimmed.length > 0 && trimmed.length <= 150) {
|
|
630
|
+
return trimmed
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// If first line is acceptable, use it
|
|
635
|
+
const firstLine = lines[0]?.trim() ?? ''
|
|
636
|
+
if (firstLine.length > 0 && firstLine.length <= 150) {
|
|
637
|
+
return firstLine
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// If first line is too long, try to get first sentence
|
|
641
|
+
const sentences = cleanContent.split(/[.!?]+/)
|
|
642
|
+
if (sentences.length > 0 && sentences[0]?.trim().length && sentences[0].trim().length <= 150) {
|
|
643
|
+
return sentences[0].trim() + '.'
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Fallback: truncate to reasonable length
|
|
647
|
+
return cleanContent.substring(0, 150).trim() + '...'
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Generate AGENTS.md content based on agent-based discovery
|
|
652
|
+
*/
|
|
653
|
+
async function generateContent(
|
|
654
|
+
projectPath: string,
|
|
655
|
+
analysis: ProjectAnalysis,
|
|
656
|
+
config: HoneConfig,
|
|
657
|
+
agent?: AgentType
|
|
658
|
+
): Promise<{ mainContent: string; detailSections?: TemplateSection[]; useAgentsDir: boolean }> {
|
|
659
|
+
log('\nPhase 2: Agent Discovery')
|
|
660
|
+
log('-'.repeat(80))
|
|
661
|
+
|
|
662
|
+
process.stdout.write('Executing agent-based project discovery... ')
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
// Execute parallel agent scanning for comprehensive project analysis
|
|
666
|
+
const scanResults = await executeParallelScanning(projectPath, config, agent)
|
|
667
|
+
process.stdout.write('✓\n')
|
|
668
|
+
|
|
669
|
+
logVerbose(
|
|
670
|
+
`[AgentsMd] Discovery completed with ${Object.keys(scanResults).length} analysis areas`
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
log('\nPhase 3: Content Generation')
|
|
674
|
+
log('-'.repeat(80))
|
|
675
|
+
|
|
676
|
+
// Create adaptive template sections based on discovered tech stack
|
|
677
|
+
const sections = createTemplateSections(scanResults, analysis)
|
|
678
|
+
|
|
679
|
+
// Generate initial content to check line count
|
|
680
|
+
const fullContent = generateCompactContent(sections, false)
|
|
681
|
+
const lineCount = countLines(fullContent)
|
|
682
|
+
|
|
683
|
+
logVerbose(`[AgentsMd] Generated content has ${lineCount} lines (limit: 100)`)
|
|
684
|
+
|
|
685
|
+
// Decide whether to use ${AGENTS_DOCS_DIR}/ subdirectory based on content length and complexity
|
|
686
|
+
const useAgentsDir = lineCount > 100 || sections.length > 5
|
|
687
|
+
|
|
688
|
+
if (useAgentsDir) {
|
|
689
|
+
if (lineCount > 100) {
|
|
690
|
+
log(
|
|
691
|
+
`Content exceeds 100-line limit. Creating ${AGENTS_DOCS_DIR}/ subdirectory for detailed information.`
|
|
692
|
+
)
|
|
693
|
+
} else {
|
|
694
|
+
log(
|
|
695
|
+
`Project has complex structure. Creating ${AGENTS_DOCS_DIR}/ subdirectory for better organization.`
|
|
696
|
+
)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
logVerbose(`[AgentsMd] Using ${AGENTS_DOCS_DIR}/ directory: ${useAgentsDir}`)
|
|
701
|
+
|
|
702
|
+
const mainContent = generateCompactContent(sections, useAgentsDir)
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
mainContent,
|
|
706
|
+
detailSections: useAgentsDir ? sections : undefined,
|
|
707
|
+
useAgentsDir,
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
process.stdout.write('✗\n')
|
|
711
|
+
throw error
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Main function to generate AGENTS.md documentation
|
|
717
|
+
*/
|
|
718
|
+
export async function generateAgentsMd(
|
|
719
|
+
options: AgentsMdGeneratorOptions = {}
|
|
720
|
+
): Promise<GenerationResult> {
|
|
721
|
+
const projectPath = options.projectPath || process.cwd()
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
log('Phase 1: Project Analysis')
|
|
725
|
+
log('-'.repeat(80))
|
|
726
|
+
|
|
727
|
+
process.stdout.write('Analyzing project structure... ')
|
|
728
|
+
const analysis = await analyzeProject(projectPath)
|
|
729
|
+
process.stdout.write('✓\n')
|
|
730
|
+
|
|
731
|
+
process.stdout.write('Loading configuration... ')
|
|
732
|
+
const config = await loadConfig()
|
|
733
|
+
process.stdout.write('✓\n')
|
|
734
|
+
|
|
735
|
+
logVerbose(
|
|
736
|
+
`[AgentsMd] Found ${analysis.languages.length} languages, ${analysis.buildSystems.length} build systems, ${analysis.testingFrameworks.length} testing frameworks`
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
// Check if AGENTS.md already exists and handle accordingly
|
|
740
|
+
const agentsPath = join(projectPath, 'AGENTS.md')
|
|
741
|
+
const existingAgentsDirPath = join(projectPath, AGENTS_DOCS_DIR)
|
|
742
|
+
|
|
743
|
+
if (existsSync(agentsPath)) {
|
|
744
|
+
if (!options.overwrite) {
|
|
745
|
+
log('\n• AGENTS.md already exists. Use --overwrite to replace it.')
|
|
746
|
+
return {
|
|
747
|
+
success: false,
|
|
748
|
+
filesCreated: [],
|
|
749
|
+
error: new Error('AGENTS.md already exists'),
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
log('• AGENTS.md exists. Overwriting with new content.')
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Also check for existing ${AGENTS_DOCS_DIR}/ directory and inform user
|
|
757
|
+
if (existsSync(existingAgentsDirPath)) {
|
|
758
|
+
if (!options.overwrite) {
|
|
759
|
+
logVerbose(
|
|
760
|
+
`[AgentsMd] ${AGENTS_DOCS_DIR}/ directory already exists. Detail files will be skipped unless --overwrite is used.`
|
|
761
|
+
)
|
|
762
|
+
} else {
|
|
763
|
+
logVerbose(
|
|
764
|
+
`[AgentsMd] ${AGENTS_DOCS_DIR}/ directory exists. Detail files will be overwritten.`
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const { mainContent, detailSections, useAgentsDir } = await generateContent(
|
|
770
|
+
projectPath,
|
|
771
|
+
analysis,
|
|
772
|
+
config,
|
|
773
|
+
options.agent
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
log('\nPhase 4: File Generation')
|
|
777
|
+
log('-'.repeat(80))
|
|
778
|
+
|
|
779
|
+
process.stdout.write('Writing AGENTS.md file... ')
|
|
780
|
+
|
|
781
|
+
// Preserve existing gotchas/learnings if overwriting
|
|
782
|
+
let finalContent = mainContent
|
|
783
|
+
if (options.overwrite && existsSync(agentsPath)) {
|
|
784
|
+
try {
|
|
785
|
+
const existingContent = await readFile(agentsPath, 'utf-8')
|
|
786
|
+
const preservedContent = extractPreservableContent(existingContent)
|
|
787
|
+
if (preservedContent) {
|
|
788
|
+
finalContent = `${mainContent}\n\n<!-- PRESERVED CONTENT FROM PREVIOUS VERSION -->\n${preservedContent}`
|
|
789
|
+
logVerbose('[AgentsMd] Preserved existing gotchas/learnings from previous AGENTS.md')
|
|
790
|
+
}
|
|
791
|
+
} catch (error) {
|
|
792
|
+
logVerboseError(`[AgentsMd] Could not preserve existing content: ${error}`)
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
await writeFile(agentsPath, finalContent, 'utf-8')
|
|
797
|
+
process.stdout.write('✓\n')
|
|
798
|
+
|
|
799
|
+
const filesCreated = [agentsPath]
|
|
800
|
+
|
|
801
|
+
// Create ${AGENTS_DOCS_DIR}/ directory and detail files if needed
|
|
802
|
+
let agentsDirPath: string | undefined
|
|
803
|
+
let detailFilesCreated = 0
|
|
804
|
+
|
|
805
|
+
if (useAgentsDir && detailSections) {
|
|
806
|
+
agentsDirPath = join(projectPath, AGENTS_DOCS_DIR)
|
|
807
|
+
|
|
808
|
+
// Handle existing ${AGENTS_DOCS_DIR}/ directory
|
|
809
|
+
if (existsSync(agentsDirPath)) {
|
|
810
|
+
if (!options.overwrite) {
|
|
811
|
+
log(
|
|
812
|
+
`• ${AGENTS_DOCS_DIR}/ directory already exists. Use --overwrite to replace existing detail files.`
|
|
813
|
+
)
|
|
814
|
+
} else {
|
|
815
|
+
log(`• ${AGENTS_DOCS_DIR}/ directory exists. Overwriting existing detail files.`)
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
await mkdir(agentsDirPath, { recursive: true })
|
|
819
|
+
logVerbose(`[AgentsMd] Created ${AGENTS_DOCS_DIR}/ directory for detailed information`)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
process.stdout.write(`Creating ${detailSections.length} detail files... `)
|
|
823
|
+
|
|
824
|
+
// Write detail files
|
|
825
|
+
for (const section of detailSections) {
|
|
826
|
+
if (section.detailFile) {
|
|
827
|
+
const detailPath = join(agentsDirPath, section.detailFile)
|
|
828
|
+
|
|
829
|
+
// Check if detail file already exists
|
|
830
|
+
if (existsSync(detailPath) && !options.overwrite) {
|
|
831
|
+
logVerbose(`[AgentsMd] Skipping existing detail file: ${section.detailFile}`)
|
|
832
|
+
continue
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
try {
|
|
836
|
+
const detailContent = `# ${section.title}
|
|
837
|
+
|
|
838
|
+
${section.content}
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
*This file is part of the AGENTS.md documentation system.*
|
|
843
|
+
`
|
|
844
|
+
await writeFile(detailPath, detailContent, 'utf-8')
|
|
845
|
+
filesCreated.push(detailPath)
|
|
846
|
+
detailFilesCreated++
|
|
847
|
+
logVerbose(
|
|
848
|
+
`[AgentsMd] ${existsSync(detailPath) && options.overwrite ? 'Updated' : 'Created'} detail file: ${section.detailFile}`
|
|
849
|
+
)
|
|
850
|
+
} catch (fileError) {
|
|
851
|
+
logVerboseError(
|
|
852
|
+
`[AgentsMd] Failed to write detail file ${section.detailFile}: ${fileError instanceof Error ? fileError.message : fileError}`
|
|
853
|
+
)
|
|
854
|
+
// Continue with other files rather than failing completely
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
process.stdout.write('✓\n')
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Success message with details
|
|
863
|
+
log('')
|
|
864
|
+
if (useAgentsDir && detailSections) {
|
|
865
|
+
log(`✓ Generated AGENTS.md with ${detailSections.length} sections`)
|
|
866
|
+
log(`✓ Created ${detailFilesCreated} detail files in ${AGENTS_DOCS_DIR}/`)
|
|
867
|
+
} else {
|
|
868
|
+
log(
|
|
869
|
+
`✓ Generated AGENTS.md with ${analysis.languages.length + analysis.buildSystems.length + analysis.testingFrameworks.length} detected components`
|
|
870
|
+
)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Next steps guidance
|
|
874
|
+
log('')
|
|
875
|
+
log('Next steps:')
|
|
876
|
+
log(' 1. Review the generated AGENTS.md for accuracy')
|
|
877
|
+
if (useAgentsDir) {
|
|
878
|
+
log(` 2. Check detailed information in the ${AGENTS_DOCS_DIR}/ directory`)
|
|
879
|
+
}
|
|
880
|
+
log(' 3. Edit and customize the documentation as needed')
|
|
881
|
+
log(' 4. Commit the changes to your repository')
|
|
882
|
+
|
|
883
|
+
return {
|
|
884
|
+
success: true,
|
|
885
|
+
mainFilePath: agentsPath,
|
|
886
|
+
agentsDirPath,
|
|
887
|
+
filesCreated,
|
|
888
|
+
}
|
|
889
|
+
} catch (error) {
|
|
890
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
891
|
+
logError('\n✗ Failed to generate AGENTS.md')
|
|
892
|
+
logError(`Error: ${err.message}`)
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
success: false,
|
|
896
|
+
filesCreated: [],
|
|
897
|
+
error: err,
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|