@champpaba/claude-agent-kit 2.1.6 → 2.2.1

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.
@@ -249,199 +249,982 @@ Continue anyway? (yes/no)
249
249
 
250
250
  ---
251
251
 
252
- ### Step 2.7: Auto-Setup Best Practices (v1.8.0)
252
+ ### Step 2.6: Feature Best Practice Analysis (v2.2.0)
253
253
 
254
- > **NEW:** Auto-detect tech stack and generate best-practices (replaces /psetup and /agentsetup)
254
+ > **NEW:** Validate spec against industry standards BEFORE checking stack best practices
255
+ > **Reference:** `.claude/lib/feature-best-practices.md`
256
+
257
+ WHY: Stack best practices tell you "how to use React well", but Feature best practices tell you "what a good auth system needs". The feature layer is higher-level and informs whether your spec is complete.
255
258
 
256
259
  ```typescript
257
- // 1. Detect tech stack from multiple sources
258
- output(`\nšŸ” Detecting Tech Stack...`)
259
-
260
- // Source 1: package.json / requirements.txt (if exists)
261
- let packageStack = []
262
- if (fileExists('package.json')) {
263
- const pkg = JSON.parse(Read('package.json'))
264
- const deps = { ...pkg.dependencies, ...pkg.devDependencies }
265
- packageStack = Object.keys(deps).filter(d =>
266
- ['next', 'react', 'vue', 'express', 'fastapi', 'prisma', 'drizzle', 'vitest', 'jest'].some(k => d.includes(k))
267
- )
268
- output(` šŸ“¦ From package.json: ${packageStack.join(', ') || 'none'}`)
260
+ output(`\nšŸ” Analyzing Feature Best Practices...`)
261
+
262
+ // 1. Detect features from proposal/tasks/design
263
+ const combined = (proposalContent + ' ' + tasksContent + ' ' + designContent).toLowerCase()
264
+
265
+ const featureDetection = {
266
+ authentication: {
267
+ keywords: ['login', 'auth', 'register', 'password', 'session', 'jwt', 'token', 'oauth'],
268
+ tier: 1, // Blocking
269
+ standards: [
270
+ { name: 'Short-lived access token', keywords: ['jwt', 'access', '15', '30', 'min'], priority: 'required' },
271
+ { name: 'Refresh token rotation', keywords: ['refresh', 'rotation', 'rotate'], priority: 'required' },
272
+ { name: 'Secure token storage', keywords: ['httponly', 'cookie', 'secure'], priority: 'required' },
273
+ { name: 'Token revocation', keywords: ['revoke', 'invalidate', 'logout'], priority: 'required' },
274
+ { name: 'Rate limiting', keywords: ['rate', 'limit', 'throttle', 'attempt'], priority: 'required' },
275
+ { name: 'Account lockout', keywords: ['lockout', 'lock', 'failed attempt'], priority: 'recommended' }
276
+ ]
277
+ },
278
+ payment: {
279
+ keywords: ['payment', 'stripe', 'checkout', 'billing', 'subscription'],
280
+ tier: 1,
281
+ standards: [
282
+ { name: 'No card data on server', keywords: ['elements', 'checkout', 'client-side'], priority: 'required' },
283
+ { name: 'Webhook signature verification', keywords: ['webhook', 'signature', 'verify'], priority: 'required' },
284
+ { name: 'Idempotency keys', keywords: ['idempotency', 'idempotent'], priority: 'required' }
285
+ ]
286
+ },
287
+ fileUpload: {
288
+ keywords: ['upload', 'file', 'image', 's3', 'storage'],
289
+ tier: 1,
290
+ standards: [
291
+ { name: 'File type validation', keywords: ['mime', 'type', 'validation', 'allowed'], priority: 'required' },
292
+ { name: 'File size limits', keywords: ['size', 'limit', 'max'], priority: 'required' },
293
+ { name: 'Filename sanitization', keywords: ['sanitize', 'filename', 'path'], priority: 'required' }
294
+ ]
295
+ },
296
+ apiDesign: {
297
+ keywords: ['api', 'endpoint', 'rest', 'graphql'],
298
+ tier: 2, // Warning
299
+ standards: [
300
+ { name: 'Rate limiting', keywords: ['rate', 'limit'], priority: 'required' },
301
+ { name: 'Input validation', keywords: ['validate', 'validation', 'zod', 'schema'], priority: 'required' },
302
+ { name: 'Pagination', keywords: ['pagination', 'page', 'limit', 'offset'], priority: 'recommended' }
303
+ ]
304
+ }
269
305
  }
270
306
 
271
- // Source 2: design.md (architecture section)
272
- let designStack = []
273
- const designPath = `openspec/changes/${changeId}/design.md`
274
- if (fileExists(designPath)) {
275
- const designContent = Read(designPath)
276
- // Look for tech stack section
277
- const techMatch = designContent.match(/tech.*stack|architecture|framework/gi)
278
- if (techMatch) {
279
- designStack = extractTechFromText(designContent)
280
- output(` šŸ“ From design.md: ${designStack.join(', ') || 'none'}`)
307
+ // 2. Find detected features
308
+ const detectedFeatures = []
309
+ for (const [featureName, config] of Object.entries(featureDetection)) {
310
+ if (config.keywords.some(kw => combined.includes(kw))) {
311
+ detectedFeatures.push({ name: featureName, ...config })
281
312
  }
282
313
  }
283
314
 
284
- // Source 3: proposal.md + tasks.md (keywords)
285
- const proposalContent = Read(`openspec/changes/${changeId}/proposal.md`)
286
- const tasksContent = Read(`openspec/changes/${changeId}/tasks.md`)
287
- const combined = (proposalContent + ' ' + tasksContent).toLowerCase()
288
-
289
- const techDetection = {
290
- react: /\b(react|jsx|tsx|use[A-Z]\w+|usestate|useeffect)\b/i,
291
- nextjs: /\b(next\.?js|next js|app router|pages router)\b/i,
292
- vue: /\b(vue|vuex|pinia|nuxt)\b/i,
293
- express: /\b(express\.js|express js|expressjs)\b/i,
294
- fastapi: /\b(fastapi|fast api)\b/i,
295
- django: /\b(django)\b/i,
296
- prisma: /\b(prisma)\b/i,
297
- drizzle: /\b(drizzle)\b/i,
298
- postgres: /\b(postgres|postgresql)\b/i,
299
- mongodb: /\b(mongodb|mongoose)\b/i,
300
- tailwind: /\b(tailwind)\b/i,
301
- typescript: /\b(typescript)\b/i,
302
- vitest: /\b(vitest)\b/i,
303
- jest: /\b(jest)\b/i,
304
- playwright: /\b(playwright)\b/i
315
+ if (detectedFeatures.length > 0) {
316
+ output(`\nšŸ“‹ Features Detected:`)
317
+ detectedFeatures.forEach(f => {
318
+ output(` - ${f.name} (Tier ${f.tier}: ${f.tier === 1 ? 'Blocking' : 'Warning'})`)
319
+ })
320
+
321
+ // 3. For each Tier 1/2 feature, check spec against standards
322
+ const allGaps = []
323
+
324
+ for (const feature of detectedFeatures) {
325
+ output(`\nšŸ” Checking ${feature.name} against industry standards...`)
326
+
327
+ const gaps = []
328
+ const matches = []
329
+
330
+ for (const standard of feature.standards) {
331
+ const isMentioned = standard.keywords.some(kw =>
332
+ designContent.toLowerCase().includes(kw)
333
+ )
334
+
335
+ if (isMentioned) {
336
+ matches.push(standard.name)
337
+ } else if (standard.priority === 'required') {
338
+ gaps.push({
339
+ feature: feature.name,
340
+ requirement: standard.name,
341
+ priority: standard.priority,
342
+ tier: feature.tier
343
+ })
344
+ }
345
+ }
346
+
347
+ if (matches.length > 0) {
348
+ output(` āœ… Matches: ${matches.join(', ')}`)
349
+ }
350
+
351
+ if (gaps.length > 0) {
352
+ output(` āŒ Gaps: ${gaps.map(g => g.requirement).join(', ')}`)
353
+ allGaps.push(...gaps)
354
+ }
355
+ }
356
+
357
+ // 4. Handle gaps based on tier
358
+ const tier1Gaps = allGaps.filter(g => g.tier === 1)
359
+ const tier2Gaps = allGaps.filter(g => g.tier === 2)
360
+
361
+ if (tier1Gaps.length > 0) {
362
+ output(`\nāš ļø Security-Critical Gaps Found (Tier 1)`)
363
+ output(``)
364
+ output(`Your spec is missing these industry-standard requirements:`)
365
+ output(``)
366
+
367
+ // Group by feature
368
+ const byFeature = {}
369
+ tier1Gaps.forEach(g => {
370
+ if (!byFeature[g.feature]) byFeature[g.feature] = []
371
+ byFeature[g.feature].push(g.requirement)
372
+ })
373
+
374
+ for (const [feature, reqs] of Object.entries(byFeature)) {
375
+ output(` ${feature}:`)
376
+ reqs.forEach(r => output(` - ${r}`))
377
+ }
378
+
379
+ output(``)
380
+ output(`Options:`)
381
+ output(` A) Update spec - Add missing requirements to design.md`)
382
+ output(` B) Document skip - Record why these aren't needed (requires justification)`)
383
+ output(` C) Continue anyway - Proceed with security gap (not recommended)`)
384
+ output(``)
385
+
386
+ const decision = await askUserQuestion({
387
+ questions: [{
388
+ question: 'How would you like to handle the security gaps?',
389
+ header: 'Spec Gaps',
390
+ options: [
391
+ { label: 'A) Update spec', description: 'Add missing requirements to design.md (recommended)' },
392
+ { label: 'B) Document skip', description: 'Record justification for skipping' },
393
+ { label: 'C) Continue anyway', description: 'Proceed with gap (security risk)' }
394
+ ],
395
+ multiSelect: false
396
+ }]
397
+ })
398
+
399
+ if (decision.includes('A')) {
400
+ // Generate suggested additions
401
+ output(`\nšŸ“ Add this to design.md:\n`)
402
+ output(`\`\`\`markdown`)
403
+ output(`### D{n}: Security Requirements (Industry Standard Alignment)`)
404
+ output(``)
405
+ output(`**Added based on industry best practices:**`)
406
+ output(``)
407
+ for (const [feature, reqs] of Object.entries(byFeature)) {
408
+ output(`#### ${feature}`)
409
+ reqs.forEach(r => output(`- ${r}`))
410
+ }
411
+ output(``)
412
+ output(`**Source:** Feature Best Practice Validation`)
413
+ output(`**Added:** ${new Date().toISOString().split('T')[0]}`)
414
+ output(`\`\`\``)
415
+ output(``)
416
+ output(`Please update design.md and re-run /csetup.`)
417
+ return
418
+ } else if (decision.includes('B')) {
419
+ // Document conscious skip
420
+ output(`\nšŸ“ Add this to design.md to document the skip:\n`)
421
+ output(`\`\`\`markdown`)
422
+ output(`### D{n}: Conscious Security Trade-offs`)
423
+ output(``)
424
+ output(`**Skipped requirements (with justification):**`)
425
+ output(``)
426
+ output(`| Requirement | Why Skipped | Risk Level | Mitigation |`)
427
+ output(`|-------------|-------------|------------|------------|`)
428
+ for (const gap of tier1Gaps) {
429
+ output(`| ${gap.requirement} | [YOUR REASON] | [LOW/MED/HIGH] | [YOUR MITIGATION] |`)
430
+ }
431
+ output(``)
432
+ output(`**Acknowledged by:** User decision in /csetup`)
433
+ output(`**Date:** ${new Date().toISOString().split('T')[0]}`)
434
+ output(`\`\`\``)
435
+ output(``)
436
+
437
+ const confirm = await askUserQuestion({
438
+ questions: [{
439
+ question: 'Confirm you will document this in design.md?',
440
+ header: 'Confirm',
441
+ options: [
442
+ { label: 'Yes, continue', description: 'I will add documentation' },
443
+ { label: 'Cancel', description: 'Go back and update spec' }
444
+ ],
445
+ multiSelect: false
446
+ }]
447
+ })
448
+
449
+ if (confirm.includes('Cancel')) {
450
+ output(`\nāŒ Setup cancelled. Please update design.md first.`)
451
+ return
452
+ }
453
+ }
454
+ // If C, just continue with warning logged
455
+ }
456
+
457
+ if (tier2Gaps.length > 0) {
458
+ output(`\nāš ļø Recommendations (Tier 2 - Non-blocking):`)
459
+ tier2Gaps.forEach(g => {
460
+ output(` - ${g.feature}: ${g.requirement}`)
461
+ })
462
+ output(` Continuing with setup...`)
463
+ }
464
+ } else {
465
+ output(`\nāœ… No security-critical features detected`)
466
+ }
467
+
468
+ // Store feature analysis for later use
469
+ const featureAnalysis = {
470
+ detected: detectedFeatures.map(f => f.name),
471
+ tier1Gaps: tier1Gaps || [],
472
+ tier2Gaps: tier2Gaps || [],
473
+ validated: true
305
474
  }
475
+ ```
476
+
477
+ ---
478
+
479
+ ### Step 2.7: Auto-Setup Best Practices (v2.3.0 - Dynamic Detection)
480
+
481
+ > **Zero-Maintenance Design:** Automatically detects any library/framework from spec text and resolves via Context7.
482
+ > **WHY:** Hardcoded mappings require constant maintenance and miss new libraries. Dynamic resolution works with any language (Python, Rust, Go, etc.) without code changes.
483
+
484
+ ```typescript
485
+ // ============================================================
486
+ // STEP 2.7: Dynamic Tech Stack Detection & Best Practices
487
+ // ============================================================
488
+
489
+ output(`\nšŸ” Detecting Tech Stack (Dynamic Resolution)...`)
490
+
491
+ // 1. Gather text from ALL relevant sources
492
+ const textSources = {
493
+ proposal: Read(`openspec/changes/${changeId}/proposal.md`) || '',
494
+ tasks: Read(`openspec/changes/${changeId}/tasks.md`) || '',
495
+ design: fileExists(`openspec/changes/${changeId}/design.md`)
496
+ ? Read(`openspec/changes/${changeId}/design.md`) : '',
497
+ packageJson: fileExists('package.json') ? Read('package.json') : '',
498
+ requirementsTxt: fileExists('requirements.txt') ? Read('requirements.txt') : '',
499
+ pyprojectToml: fileExists('pyproject.toml') ? Read('pyproject.toml') : '',
500
+ cargoToml: fileExists('Cargo.toml') ? Read('Cargo.toml') : '',
501
+ goMod: fileExists('go.mod') ? Read('go.mod') : '',
502
+ composerJson: fileExists('composer.json') ? Read('composer.json') : '',
503
+ gemfile: fileExists('Gemfile') ? Read('Gemfile') : ''
504
+ }
505
+
506
+ const allText = Object.values(textSources).join('\n')
507
+
508
+ // 2. Extract library names using semantic analysis (TRUE zero-maintenance)
509
+ // WHY: Pattern-based extraction still requires maintenance.
510
+ // Instead, use Claude's understanding to identify libraries from context.
511
+ output(` 🧠 Analyzing text semantically...`)
512
+
513
+ const potentialLibraries = await extractLibrariesSemantically(allText)
514
+
515
+ output(` šŸ“ Found ${potentialLibraries.length} potential libraries to verify`)
516
+
517
+ // 3. Resolve each potential library with Context7 (validate it's a real library)
518
+ const resolvedLibraries = []
519
+ const resolutionCache = new Map()
306
520
 
307
- const proposalStack = []
308
- for (const [tech, pattern] of Object.entries(techDetection)) {
309
- if (pattern.test(combined)) {
310
- proposalStack.push(tech)
521
+ for (const candidate of potentialLibraries) {
522
+ if (resolutionCache.has(candidate.toLowerCase())) continue
523
+
524
+ try {
525
+ const result = await mcp__context7__resolve_library_id({
526
+ libraryName: candidate
527
+ })
528
+
529
+ const bestMatch = parseContext7Response(result, candidate)
530
+
531
+ if (bestMatch && bestMatch.score >= 60) {
532
+ resolvedLibraries.push({
533
+ name: candidate,
534
+ context7Id: bestMatch.id,
535
+ title: bestMatch.title,
536
+ snippets: bestMatch.snippets,
537
+ score: bestMatch.score
538
+ })
539
+ resolutionCache.set(candidate.toLowerCase(), bestMatch)
540
+ output(` āœ… ${candidate} → ${bestMatch.id} (${bestMatch.snippets} snippets)`)
541
+ }
542
+ } catch (error) {
543
+ resolutionCache.set(candidate.toLowerCase(), null)
311
544
  }
312
545
  }
313
- output(` šŸ“ From proposal/tasks: ${proposalStack.join(', ') || 'none'}`)
314
546
 
315
- // Merge all sources (remove duplicates)
316
- const detectedStack = [...new Set([...packageStack, ...designStack, ...proposalStack])]
547
+ output(`\nšŸ“Š Verified Libraries: ${resolvedLibraries.length}`)
548
+ resolvedLibraries.forEach(lib => {
549
+ output(` - ${lib.title} (${lib.context7Id})`)
550
+ })
317
551
 
318
- // 2. If no stack detected, ask user
319
- if (detectedStack.length === 0) {
320
- output(`\nāš ļø Could not auto-detect tech stack`)
552
+ // 4. If no libraries detected, ask user for guidance
553
+ if (resolvedLibraries.length === 0) {
554
+ output(`\nāš ļø No libraries auto-detected from spec files`)
321
555
 
322
556
  const answer = await askUserQuestion({
323
557
  questions: [{
324
- question: 'What tech stack will you use?',
325
- header: 'Stack',
558
+ question: 'Enter the main libraries/frameworks for this project (comma-separated):',
559
+ header: 'Libraries',
326
560
  options: [
327
- { label: 'Next.js + React', description: 'Full-stack React framework' },
328
- { label: 'FastAPI + Python', description: 'Python async API' },
329
- { label: 'Express + Node', description: 'Node.js backend' },
330
- { label: 'Vue + Nuxt', description: 'Vue.js framework' }
561
+ { label: 'Skip', description: 'Continue without library-specific best practices' },
562
+ { label: 'React, Next.js', description: 'Common frontend stack' },
563
+ { label: 'FastAPI, SQLAlchemy, Pydantic', description: 'Python API stack' },
564
+ { label: 'Express, Prisma', description: 'Node.js backend stack' }
331
565
  ],
332
- multiSelect: true
566
+ multiSelect: false
333
567
  }]
334
568
  })
335
569
 
336
- // Parse user selection into detectedStack
337
- detectedStack.push(...parseUserStackSelection(answer))
570
+ if (!answer.includes('Skip')) {
571
+ // Parse user input and resolve each
572
+ const userLibraries = answer.split(',').map(s => s.trim()).filter(Boolean)
573
+ for (const lib of userLibraries) {
574
+ const result = await mcp__context7__resolve_library_id({ libraryName: lib })
575
+ const bestMatch = parseContext7Response(result, lib)
576
+ if (bestMatch) {
577
+ resolvedLibraries.push({
578
+ name: lib,
579
+ context7Id: bestMatch.id,
580
+ title: bestMatch.title,
581
+ snippets: bestMatch.snippets,
582
+ score: bestMatch.score
583
+ })
584
+ }
585
+ }
586
+ }
338
587
  }
339
588
 
340
- output(`\nāœ… Final Tech Stack: ${detectedStack.join(', ')}`)
341
-
342
- // 3. Check if best-practices already exist
589
+ // 5. Generate best-practices files for resolved libraries
343
590
  const bpDir = '.claude/contexts/domain/project/best-practices/'
344
591
  const existingBp = fileExists(bpDir) ? listFiles(bpDir) : []
345
592
 
346
- const missingBp = detectedStack.filter(tech => {
347
- return !existingBp.some(f => f.toLowerCase().includes(tech.toLowerCase()))
593
+ // Filter to libraries that don't have best-practices yet
594
+ const newLibraries = resolvedLibraries.filter(lib => {
595
+ const safeName = lib.name.toLowerCase().replace(/[^a-z0-9]/g, '-')
596
+ return !existingBp.some(f => f.toLowerCase().includes(safeName))
348
597
  })
349
598
 
350
- // 4. Generate missing best-practices from Context7
351
- if (missingBp.length > 0) {
599
+ if (newLibraries.length > 0) {
352
600
  output(`\nšŸ“š Generating Best Practices from Context7...`)
353
601
 
354
- // Create directory structure if needed
355
- if (!fileExists('.claude/contexts/domain/')) {
356
- mkdir('.claude/contexts/domain/project/best-practices/')
602
+ // Create directory if needed
603
+ if (!fileExists(bpDir)) {
604
+ mkdir(bpDir)
357
605
  }
358
606
 
359
- // Context7 library ID mapping
360
- const context7Ids = {
361
- react: '/facebook/react',
362
- nextjs: '/vercel/next.js',
363
- vue: '/vuejs/vue',
364
- express: '/expressjs/express',
365
- fastapi: '/fastapi/fastapi',
366
- prisma: '/prisma/prisma',
367
- drizzle: '/drizzle-team/drizzle-orm',
368
- vitest: '/vitest-dev/vitest',
369
- jest: '/jestjs/jest',
370
- playwright: '/microsoft/playwright',
371
- tailwind: '/tailwindlabs/tailwindcss'
607
+ for (const lib of newLibraries) {
608
+ output(` šŸ“– Fetching ${lib.title} best practices...`)
609
+
610
+ try {
611
+ const docs = await mcp__context7__get_library_docs({
612
+ context7CompatibleLibraryID: lib.context7Id,
613
+ topic: 'best practices, patterns, anti-patterns, common mistakes',
614
+ mode: 'code'
615
+ })
616
+
617
+ const bpContent = generateBestPracticesFile(lib.title, docs, lib.context7Id)
618
+ const safeName = lib.name.toLowerCase().replace(/[^a-z0-9]/g, '-')
619
+ Write(`${bpDir}${safeName}.md`, bpContent)
620
+
621
+ output(` āœ… ${safeName}.md generated`)
622
+ } catch (error) {
623
+ output(` āš ļø ${lib.title} - failed to fetch docs, skipping`)
624
+ }
372
625
  }
373
626
 
374
- for (const tech of missingBp) {
375
- const libraryId = context7Ids[tech.toLowerCase()]
627
+ // Generate/update index.md
628
+ generateBestPracticesIndex(resolvedLibraries, changeId)
629
+ output(` āœ… index.md updated`)
376
630
 
377
- if (libraryId) {
378
- output(` šŸ“– Fetching ${tech} best practices...`)
631
+ output(`\nāœ… Best Practices Setup Complete!`)
632
+ output(` New files: ${newLibraries.length}`)
633
+ output(` Location: ${bpDir}`)
634
+ } else if (resolvedLibraries.length > 0) {
635
+ output(`\nāœ… Best Practices: Already configured for detected libraries`)
636
+ } else {
637
+ output(`\nāœ… Best Practices: Skipped (no libraries to configure)`)
638
+ }
379
639
 
380
- // Query Context7 for best practices
381
- const docs = await mcp__context7__get-library-docs({
382
- context7CompatibleLibraryID: libraryId,
383
- topic: 'best practices, common mistakes, anti-patterns, patterns',
384
- mode: 'code'
385
- })
640
+ // 6. Store resolved stack for context.md generation
641
+ const stackForContext = {
642
+ detected: resolvedLibraries.map(l => l.name),
643
+ resolved: resolvedLibraries,
644
+ bestPracticesPath: bpDir,
645
+ files: existingBp.concat(newLibraries.map(l => `${l.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}.md`))
646
+ }
647
+ ```
648
+
649
+ ---
650
+
651
+ ### Helper: extractLibrariesSemantically() - PRIMARY
652
+
653
+ > **TRUE Zero-Maintenance:** Uses Claude's semantic understanding to extract library names from any text.
654
+ > **WHY:** Pattern-based regex always has edge cases. Claude understands context and meaning.
655
+
656
+ ```typescript
657
+ async function extractLibrariesSemantically(text: string): Promise<string[]> {
658
+ // Truncate text if too long (keep first 8000 chars for efficiency)
659
+ const truncatedText = text.length > 8000 ? text.slice(0, 8000) + '\n...[truncated]' : text
660
+
661
+ // Use Claude to semantically extract library/framework names
662
+ // This is the MAIN Claude running /csetup, so we just analyze inline
663
+ const prompt = `
664
+ Analyze this text and extract ALL programming libraries, frameworks, and tools mentioned.
665
+
666
+ TEXT:
667
+ ${truncatedText}
668
+
669
+ INSTRUCTIONS:
670
+ 1. Look for any technology names (libraries, frameworks, ORMs, databases, tools)
671
+ 2. Include both explicit mentions ("using Mastra") and implicit ones ("PostgreSQL database")
672
+ 3. Return ONLY the library/framework names, one per line
673
+ 4. Do NOT include generic terms like "API", "REST", "database" (unless it's a specific product name)
674
+ 5. Do NOT include programming languages (Python, JavaScript, etc.) unless they're library names
675
+ 6. Include package names from any language (npm, pip, cargo, go, etc.)
676
+
677
+ OUTPUT FORMAT (one name per line, nothing else):
678
+ `
679
+
680
+ // Since this runs within Main Claude context, we analyze inline
681
+ // The output parsing happens in the calling code
682
+ // For now, return a simple extraction that Main Claude will process
683
+
684
+ // Fallback: Use pattern-based extraction as baseline
685
+ // Main Claude will enhance this with semantic understanding
686
+ const patternBased = extractPotentialLibraryNames(text)
687
+
688
+ // Main Claude: When you execute this, also semantically analyze the text
689
+ // and add any libraries you identify from context that patterns missed.
690
+ // Example: "The system uses Mastra for orchestration" → add "Mastra"
691
+
692
+ return patternBased
693
+ }
694
+ ```
695
+
696
+ **Execution Note for Main Claude:**
386
697
 
387
- // Generate best-practices file
388
- const bpContent = generateBestPracticesFile(tech, docs)
389
- Write(`.claude/contexts/domain/project/best-practices/${tech}.md`, bpContent)
698
+ When running `/csetup`, after calling `extractLibrariesSemantically()`:
699
+ 1. Review the spec text yourself (proposal.md, design.md, tasks.md)
700
+ 2. Identify any library/framework names the patterns might have missed
701
+ 3. Add them to the `potentialLibraries` array before Context7 validation
390
702
 
391
- output(` āœ… ${tech}.md generated`)
392
- } else {
393
- output(` āš ļø ${tech} - no Context7 mapping, using universal patterns`)
703
+ This hybrid approach ensures:
704
+ - Pattern extraction catches obvious cases quickly
705
+ - Claude's semantic understanding catches edge cases
706
+ - Context7 validates everything (filters out false positives)
707
+
708
+ ---
709
+
710
+ ### Helper: extractPotentialLibraryNames() - FALLBACK
711
+
712
+ > **Pattern-based fallback:** Provides baseline extraction. Main Claude enhances with semantic analysis.
713
+
714
+ ```typescript
715
+ function extractPotentialLibraryNames(text: string): string[] {
716
+ const candidates = new Set<string>()
717
+
718
+ // === Pattern 1: Package file dependencies ===
719
+ // package.json: "react": "^18.0.0" → react
720
+ const npmDeps = text.match(/"([a-z@][a-z0-9._/-]*)"\s*:\s*"[\^~]?\d/gi) || []
721
+ npmDeps.forEach(m => {
722
+ const match = m.match(/"([^"]+)"/)
723
+ if (match) candidates.add(match[1].replace(/^@[^/]+\//, '')) // Strip scope
724
+ })
725
+
726
+ // requirements.txt: sqlalchemy==2.0.0 → sqlalchemy
727
+ const pyDeps = text.match(/^([a-zA-Z][a-zA-Z0-9_-]*)\s*[=<>~!]/gm) || []
728
+ pyDeps.forEach(m => {
729
+ const match = m.match(/^([a-zA-Z][a-zA-Z0-9_-]*)/)
730
+ if (match) candidates.add(match[1])
731
+ })
732
+
733
+ // Cargo.toml: tokio = "1.0" → tokio
734
+ const rustDeps = text.match(/^([a-z][a-z0-9_-]*)\s*=/gm) || []
735
+ rustDeps.forEach(m => {
736
+ const match = m.match(/^([a-z][a-z0-9_-]*)/)
737
+ if (match) candidates.add(match[1])
738
+ })
739
+
740
+ // go.mod: require github.com/gin-gonic/gin → gin
741
+ const goDeps = text.match(/(?:require\s+)?github\.com\/[^/\s]+\/([a-z][a-z0-9_-]*)/gi) || []
742
+ goDeps.forEach(m => {
743
+ const match = m.match(/\/([a-z][a-z0-9_-]*)$/i)
744
+ if (match) candidates.add(match[1])
745
+ })
746
+
747
+ // === Pattern 2: Import statements ===
748
+ // Python: from sqlalchemy import, import pydantic
749
+ const pyImports = text.match(/(?:from|import)\s+([a-zA-Z][a-zA-Z0-9_]*)/g) || []
750
+ pyImports.forEach(m => {
751
+ const match = m.match(/(?:from|import)\s+([a-zA-Z][a-zA-Z0-9_]*)/)
752
+ if (match) candidates.add(match[1])
753
+ })
754
+
755
+ // JS/TS: import X from 'Y', require('Y')
756
+ const jsImports = text.match(/(?:from|require\s*\(\s*)['"]([a-zA-Z@][a-zA-Z0-9._/-]*)['"]/g) || []
757
+ jsImports.forEach(m => {
758
+ const match = m.match(/['"]([^'"]+)['"]/)
759
+ if (match) {
760
+ const pkg = match[1].replace(/^@[^/]+\//, '').split('/')[0]
761
+ candidates.add(pkg)
762
+ }
763
+ })
764
+
765
+ // Rust: use tokio::, extern crate serde
766
+ const rustImports = text.match(/(?:use|extern\s+crate)\s+([a-z][a-z0-9_]*)/g) || []
767
+ rustImports.forEach(m => {
768
+ const match = m.match(/(?:use|extern\s+crate)\s+([a-z][a-z0-9_]*)/)
769
+ if (match) candidates.add(match[1])
770
+ })
771
+
772
+ // === Pattern 3: Tech mentions in prose ===
773
+ // "using FastAPI", "with Prisma", "powered by Mastra"
774
+ const techMentions = text.match(/(?:using|with|via|built with|powered by)\s+([A-Z][a-zA-Z0-9.]*)/gi) || []
775
+ techMentions.forEach(m => {
776
+ const match = m.match(/\s([A-Z][a-zA-Z0-9.]*)$/i)
777
+ if (match) candidates.add(match[1])
778
+ })
779
+
780
+ // CamelCase words (FastAPI, SQLAlchemy, NextAuth)
781
+ const camelCase = text.match(/\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g) || []
782
+ camelCase.forEach(w => candidates.add(w))
783
+
784
+ // === Pattern 3.5: PascalCase single words after tech keywords ===
785
+ // "Framework: Mastra", "ORM: Prisma", "Database: PostgreSQL"
786
+ // WHY: Many library names are single PascalCase words (Mastra, Prisma, Django, Flask)
787
+ const techKeywordPatterns = [
788
+ /(?:framework|library|orm|database|db|backend|frontend|ui|css|styling)[:\s]+([A-Z][a-z]+\w*)/gi,
789
+ /(?:built\s+with|powered\s+by|using|via|with)\s+([A-Z][a-z]+\w*)/gi,
790
+ /\*\*(?:framework|library|orm|database|backend|frontend)\*\*[:\s]+([A-Z][a-z]+\w*)/gi
791
+ ]
792
+ techKeywordPatterns.forEach(pattern => {
793
+ const matches = text.matchAll(pattern)
794
+ for (const match of matches) {
795
+ if (match[1]) candidates.add(match[1])
796
+ }
797
+ })
798
+
799
+ // === Pattern 3.6: Standalone PascalCase in markdown lists ===
800
+ // "- Mastra for AI agents", "- PostgreSQL database", "* React frontend"
801
+ const mdListItems = text.match(/^[\s]*[-*]\s+([A-Z][a-z]+\w*)(?:\s|$)/gm) || []
802
+ mdListItems.forEach(m => {
803
+ const match = m.match(/[-*]\s+([A-Z][a-z]+\w*)/)
804
+ if (match) candidates.add(match[1])
805
+ })
806
+
807
+ // === Pattern 3.7: Words after "for" in tech context ===
808
+ // "Mastra for AI orchestration", "Drizzle for database"
809
+ const forPattern = text.match(/([A-Z][a-z]+\w*)\s+for\s+(?:ai|database|backend|frontend|api|web|mobile|server|client)/gi) || []
810
+ forPattern.forEach(m => {
811
+ const match = m.match(/^([A-Z][a-z]+\w*)/)
812
+ if (match) candidates.add(match[1])
813
+ })
814
+
815
+ // Known framework patterns: Next.js, Vue.js, Express.js
816
+ const dotJs = text.match(/\b([A-Z][a-z]+)\.js\b/gi) || []
817
+ dotJs.forEach(m => {
818
+ const match = m.match(/([A-Z][a-z]+)/i)
819
+ if (match) candidates.add(match[1])
820
+ })
821
+
822
+ // === Pattern 4: Explicit tech stack sections ===
823
+ // "Tech Stack:", "Technologies:", "Built with:"
824
+ const techSection = text.match(/(?:tech\s*stack|technologies|built\s*with|dependencies)[:\s]+([^\n]+)/gi) || []
825
+ techSection.forEach(section => {
826
+ const items = section.split(/[,\s]+/)
827
+ items.forEach(item => {
828
+ const cleaned = item.replace(/[^a-zA-Z0-9.-]/g, '')
829
+ if (cleaned.length > 2) candidates.add(cleaned)
830
+ })
831
+ })
832
+
833
+ // === Pattern 4.5: Markdown bold/code tech mentions ===
834
+ // "**Mastra**", "`prisma`", "**Framework:** Mastra"
835
+ const boldWords = text.match(/\*\*([A-Z][a-z]+\w*)\*\*/g) || []
836
+ boldWords.forEach(m => {
837
+ const match = m.match(/\*\*([A-Z][a-z]+\w*)\*\*/)
838
+ if (match) candidates.add(match[1])
839
+ })
840
+
841
+ const codeWords = text.match(/`([a-zA-Z][a-zA-Z0-9_-]+)`/g) || []
842
+ codeWords.forEach(m => {
843
+ const match = m.match(/`([a-zA-Z][a-zA-Z0-9_-]+)`/)
844
+ if (match && match[1].length > 2) candidates.add(match[1])
845
+ })
846
+
847
+ // === Filter out noise ===
848
+ const stopWords = new Set([
849
+ // Common English words
850
+ 'The', 'This', 'That', 'With', 'From', 'Using', 'For', 'And', 'But', 'Not',
851
+ 'All', 'Any', 'Can', 'Could', 'Should', 'Would', 'Will', 'May', 'Might',
852
+ 'Each', 'Every', 'Some', 'Many', 'Most', 'Other', 'Such', 'Only', 'Just',
853
+ 'Also', 'Well', 'Back', 'Even', 'Still', 'Already', 'Always', 'Never',
854
+ // Common programming terms that aren't libraries
855
+ 'API', 'REST', 'HTTP', 'HTTPS', 'JSON', 'XML', 'HTML', 'CSS', 'SQL',
856
+ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'URL', 'URI', 'UUID', 'ID',
857
+ 'True', 'False', 'None', 'Null', 'Undefined', 'Error', 'Exception',
858
+ 'Class', 'Function', 'Method', 'Object', 'Array', 'String', 'Number',
859
+ 'Boolean', 'Int', 'Float', 'Double', 'Char', 'Byte', 'Long', 'Short',
860
+ 'Public', 'Private', 'Protected', 'Static', 'Final', 'Const', 'Let', 'Var',
861
+ 'Import', 'Export', 'Module', 'Package', 'Interface', 'Type', 'Enum',
862
+ 'Test', 'Tests', 'Spec', 'Specs', 'Mock', 'Stub', 'Fake', 'Spy',
863
+ 'Config', 'Configuration', 'Settings', 'Options', 'Params', 'Args',
864
+ 'User', 'Users', 'Admin', 'Auth', 'Login', 'Logout', 'Session', 'Token',
865
+ 'Data', 'Database', 'Table', 'Column', 'Row', 'Index', 'Key', 'Value',
866
+ 'File', 'Files', 'Path', 'Dir', 'Directory', 'Folder', 'Name', 'Size',
867
+ 'Create', 'Read', 'Update', 'Delete', 'List', 'Get', 'Set', 'Add', 'Remove',
868
+ 'Start', 'Stop', 'Run', 'Build', 'Deploy', 'Install', 'Setup', 'Init',
869
+ // Version/date patterns
870
+ 'Version', 'Release', 'Beta', 'Alpha', 'Stable', 'Latest', 'Current'
871
+ ])
872
+
873
+ return [...candidates]
874
+ .filter(w => w.length > 2 && w.length < 30)
875
+ .filter(w => !stopWords.has(w))
876
+ .filter(w => !/^\d+$/.test(w)) // Not pure numbers
877
+ .filter(w => !/^v?\d+\.\d+/.test(w)) // Not version numbers
878
+ .slice(0, 50) // Limit to avoid too many API calls
879
+ }
880
+ ```
881
+
882
+ ---
883
+
884
+ ### Helper: parseContext7Response()
885
+
886
+ > **WHY:** Context7 returns multiple matches. Select the best one based on relevance score and snippet count.
887
+
888
+ ```typescript
889
+ function parseContext7Response(response: string, searchTerm: string): {
890
+ id: string
891
+ title: string
892
+ snippets: number
893
+ score: number
894
+ } | null {
895
+ // Parse the Context7 response text to extract library info
896
+ // Response format includes lines like:
897
+ // - Title: SQLAlchemy
898
+ // - Context7-compatible library ID: /sqlalchemy/sqlalchemy
899
+ // - Code Snippets: 2830
900
+ // - Benchmark Score: 84.4
901
+
902
+ const libraries = []
903
+ const blocks = response.split('----------').filter(b => b.trim())
904
+
905
+ for (const block of blocks) {
906
+ const titleMatch = block.match(/Title:\s*(.+)/i)
907
+ const idMatch = block.match(/Context7-compatible library ID:\s*(\S+)/i)
908
+ const snippetsMatch = block.match(/Code Snippets:\s*(\d+)/i)
909
+ const scoreMatch = block.match(/Benchmark Score:\s*([\d.]+)/i)
910
+
911
+ if (titleMatch && idMatch) {
912
+ libraries.push({
913
+ title: titleMatch[1].trim(),
914
+ id: idMatch[1].trim(),
915
+ snippets: snippetsMatch ? parseInt(snippetsMatch[1]) : 0,
916
+ score: scoreMatch ? parseFloat(scoreMatch[1]) : 50
917
+ })
394
918
  }
395
919
  }
396
920
 
397
- // Generate index.md
398
- generateBestPracticesIndex(detectedStack, changeId)
399
- output(` āœ… index.md generated`)
921
+ if (libraries.length === 0) return null
922
+
923
+ // Prefer exact title match, then highest score with good snippet count
924
+ const searchLower = searchTerm.toLowerCase()
925
+
926
+ // First: exact match
927
+ const exactMatch = libraries.find(l =>
928
+ l.title.toLowerCase() === searchLower ||
929
+ l.id.toLowerCase().includes(searchLower)
930
+ )
931
+ if (exactMatch) return exactMatch
932
+
933
+ // Second: partial match with good score
934
+ const partialMatches = libraries.filter(l =>
935
+ l.title.toLowerCase().includes(searchLower) ||
936
+ searchLower.includes(l.title.toLowerCase())
937
+ )
938
+ if (partialMatches.length > 0) {
939
+ return partialMatches.sort((a, b) => b.score - a.score)[0]
940
+ }
400
941
 
401
- // Generate domain/index.md if not exists
402
- if (!fileExists('.claude/contexts/domain/index.md')) {
403
- generateDomainIndex('project', detectedStack)
404
- output(` āœ… domain/index.md generated`)
942
+ // Third: best overall score (only if snippets > 100 for quality)
943
+ const qualityLibs = libraries.filter(l => l.snippets > 100)
944
+ if (qualityLibs.length > 0) {
945
+ return qualityLibs.sort((a, b) => b.score - a.score)[0]
405
946
  }
406
947
 
407
- output(`\nāœ… Best Practices Setup Complete!`)
408
- output(` Files: ${missingBp.length + 1} generated`)
409
- output(` Location: .claude/contexts/domain/project/best-practices/`)
948
+ // Fallback: first result
949
+ return libraries[0]
950
+ }
951
+ ```
952
+
953
+ ---
954
+
955
+ ### Step 2.8: Library Capability Validation (v2.2.0)
956
+
957
+ > **NEW:** Verify chosen libraries support ALL spec requirements before proceeding
958
+ > **WHY:** Prevents spec drift - discovering during implementation that library doesn't support requirements
959
+
960
+ ```typescript
961
+ output(`\nšŸ” Validating Library Capabilities...`)
962
+
963
+ // 1. Extract spec requirements from design.md
964
+ const designPath = `openspec/changes/${changeId}/design.md`
965
+ if (!fileExists(designPath)) {
966
+ output(` āš ļø No design.md found - skipping library validation`)
410
967
  } else {
411
- output(`\nāœ… Best Practices: Already configured (${existingBp.length} files)`)
968
+ const designContent = Read(designPath)
969
+
970
+ // 2. Find library mentions in spec
971
+ const libraryPatterns = {
972
+ 'better-auth': {
973
+ patterns: ['better-auth', 'betterauth'],
974
+ context7Id: null, // No Context7 mapping yet
975
+ knownLimitations: [
976
+ { feature: 'refresh token rotation', supported: false },
977
+ { feature: 'redis session storage', supported: false },
978
+ { feature: 'jwt plugin', supported: true },
979
+ { feature: 'bearer plugin', supported: true },
980
+ { feature: 'session-based auth', supported: true }
981
+ ]
982
+ },
983
+ 'nextauth': {
984
+ patterns: ['next-auth', 'nextauth', 'authjs'],
985
+ context7Id: '/nextauthjs/next-auth',
986
+ knownLimitations: []
987
+ },
988
+ 'lucia': {
989
+ patterns: ['lucia', 'lucia-auth'],
990
+ context7Id: '/lucia-auth/lucia',
991
+ knownLimitations: []
992
+ },
993
+ 'prisma': {
994
+ patterns: ['prisma'],
995
+ context7Id: '/prisma/prisma',
996
+ knownLimitations: []
997
+ },
998
+ 'drizzle': {
999
+ patterns: ['drizzle'],
1000
+ context7Id: '/drizzle-team/drizzle-orm',
1001
+ knownLimitations: []
1002
+ }
1003
+ }
1004
+
1005
+ // 3. Detect which libraries are mentioned
1006
+ const detectedLibraries = []
1007
+ for (const [libName, config] of Object.entries(libraryPatterns)) {
1008
+ if (config.patterns.some(p => designContent.toLowerCase().includes(p))) {
1009
+ detectedLibraries.push({ name: libName, ...config })
1010
+ }
1011
+ }
1012
+
1013
+ if (detectedLibraries.length > 0) {
1014
+ output(`\nšŸ“š Libraries in Spec:`)
1015
+ detectedLibraries.forEach(lib => output(` - ${lib.name}`))
1016
+
1017
+ // 4. Extract requirements from design.md
1018
+ // Look for patterns like: "JWT 15min", "refresh token", "rotation"
1019
+ const requirementPatterns = [
1020
+ { name: 'JWT access token', pattern: /jwt.*(?:access|token).*(\d+\s*min)/i },
1021
+ { name: 'Refresh token', pattern: /refresh\s*token/i },
1022
+ { name: 'Token rotation', pattern: /(?:token\s*)?rotation|rotate/i },
1023
+ { name: 'Redis session', pattern: /redis.*session|session.*redis/i },
1024
+ { name: 'Bearer token', pattern: /bearer\s*(?:token|auth)/i },
1025
+ { name: 'OAuth providers', pattern: /oauth|google|github|social\s*login/i },
1026
+ { name: 'Rate limiting', pattern: /rate\s*limit/i },
1027
+ { name: 'Account lockout', pattern: /lockout|lock\s*account/i }
1028
+ ]
1029
+
1030
+ const specRequirements = []
1031
+ for (const rp of requirementPatterns) {
1032
+ if (rp.pattern.test(designContent)) {
1033
+ specRequirements.push(rp.name)
1034
+ }
1035
+ }
1036
+
1037
+ if (specRequirements.length > 0) {
1038
+ output(`\nšŸ“‹ Spec Requirements Found:`)
1039
+ specRequirements.forEach(r => output(` - ${r}`))
1040
+
1041
+ // 5. Check each library's capability
1042
+ const capabilityGaps = []
1043
+
1044
+ for (const lib of detectedLibraries) {
1045
+ output(`\nšŸ” Checking ${lib.name} capabilities...`)
1046
+
1047
+ for (const req of specRequirements) {
1048
+ // Check known limitations first
1049
+ const known = lib.knownLimitations.find(l =>
1050
+ req.toLowerCase().includes(l.feature.toLowerCase()) ||
1051
+ l.feature.toLowerCase().includes(req.toLowerCase())
1052
+ )
1053
+
1054
+ if (known && !known.supported) {
1055
+ output(` āŒ ${req} - NOT SUPPORTED`)
1056
+ capabilityGaps.push({
1057
+ library: lib.name,
1058
+ requirement: req,
1059
+ supported: false,
1060
+ note: `${lib.name} does not have built-in support for ${req}`
1061
+ })
1062
+ } else if (known && known.supported) {
1063
+ output(` āœ… ${req} - Supported`)
1064
+ } else {
1065
+ // Unknown - query Context7 if available
1066
+ if (lib.context7Id) {
1067
+ output(` šŸ” ${req} - Checking Context7...`)
1068
+ // Note: In actual implementation, this would call Context7
1069
+ // For now, mark as unknown
1070
+ output(` āš ļø ${req} - Verify manually`)
1071
+ } else {
1072
+ output(` āš ļø ${req} - Verify manually (no Context7 mapping)`)
1073
+ }
1074
+ }
1075
+ }
1076
+ }
1077
+
1078
+ // 6. Report gaps if any
1079
+ if (capabilityGaps.length > 0) {
1080
+ output(`\nāš ļø Library Capability Gaps Detected!`)
1081
+ output(``)
1082
+ output(`The following spec requirements are NOT supported by chosen libraries:`)
1083
+ output(``)
1084
+
1085
+ // Group by library
1086
+ const byLibrary = {}
1087
+ capabilityGaps.forEach(g => {
1088
+ if (!byLibrary[g.library]) byLibrary[g.library] = []
1089
+ byLibrary[g.library].push(g.requirement)
1090
+ })
1091
+
1092
+ for (const [library, reqs] of Object.entries(byLibrary)) {
1093
+ output(` ${library}:`)
1094
+ reqs.forEach(r => output(` - ${r}`))
1095
+ }
1096
+
1097
+ output(``)
1098
+ output(`This will cause spec drift during implementation!`)
1099
+ output(``)
1100
+ output(`Options:`)
1101
+ output(` A) Change library - Use a library that supports these features`)
1102
+ output(` B) Downgrade spec - Remove unsupported requirements (must document trade-off)`)
1103
+ output(` C) Custom implementation - Build missing features on top of library`)
1104
+ output(` D) Continue anyway - Proceed and let agent handle at implementation time`)
1105
+ output(``)
1106
+
1107
+ const decision = await askUserQuestion({
1108
+ questions: [{
1109
+ question: 'How would you like to handle the capability gaps?',
1110
+ header: 'Lib Gaps',
1111
+ options: [
1112
+ { label: 'A) Change library', description: 'Switch to a library that supports requirements' },
1113
+ { label: 'B) Downgrade spec', description: 'Update design.md to use what library supports' },
1114
+ { label: 'C) Custom implementation', description: 'Build on top of library (more work)' },
1115
+ { label: 'D) Continue anyway', description: 'Let agent handle during implementation' }
1116
+ ],
1117
+ multiSelect: false
1118
+ }]
1119
+ })
1120
+
1121
+ if (decision.includes('A')) {
1122
+ output(`\nšŸ“ Suggested alternative libraries:`)
1123
+ for (const [library, reqs] of Object.entries(byLibrary)) {
1124
+ if (library === 'better-auth') {
1125
+ output(` Instead of ${library}, consider:`)
1126
+ output(` - lucia-auth (supports custom session storage)`)
1127
+ output(` - NextAuth.js (supports refresh token rotation with JWT strategy)`)
1128
+ output(` - Custom implementation with jose + Redis`)
1129
+ }
1130
+ }
1131
+ output(``)
1132
+ output(`Please update design.md with new library choice and re-run /csetup.`)
1133
+ return
1134
+ } else if (decision.includes('B')) {
1135
+ output(`\nšŸ“ Update design.md to remove unsupported requirements:`)
1136
+ output(``)
1137
+ output(`\`\`\`markdown`)
1138
+ output(`### D{n}: Library Capability Alignment`)
1139
+ output(``)
1140
+ output(`**Changed requirements to match ${Object.keys(byLibrary).join(', ')} capabilities:**`)
1141
+ output(``)
1142
+ for (const gap of capabilityGaps) {
1143
+ output(`- ~~${gap.requirement}~~ → Use ${gap.library}'s default approach instead`)
1144
+ }
1145
+ output(``)
1146
+ output(`**Reason:** Library limitation`)
1147
+ output(`**Trade-off:** ${capabilityGaps.map(g => g.requirement).join(', ')} not available`)
1148
+ output(`**Date:** ${new Date().toISOString().split('T')[0]}`)
1149
+ output(`\`\`\``)
1150
+ output(``)
1151
+ output(`Please update design.md and re-run /csetup.`)
1152
+ return
1153
+ } else if (decision.includes('C')) {
1154
+ output(`\nšŸ“ Custom implementation notes for agents:`)
1155
+ output(``)
1156
+ output(`Add to context.md:`)
1157
+ output(`\`\`\`markdown`)
1158
+ output(`## Custom Implementation Required`)
1159
+ output(``)
1160
+ output(`The following features need custom implementation:`)
1161
+ for (const gap of capabilityGaps) {
1162
+ output(`- ${gap.requirement} (not supported by ${gap.library})`)
1163
+ }
1164
+ output(``)
1165
+ output(`Agents should implement these on top of the base library.`)
1166
+ output(`\`\`\``)
1167
+
1168
+ // Store for context.md generation
1169
+ customImplementationRequired = capabilityGaps
1170
+ }
1171
+ // If D, continue with gaps logged for agent awareness
1172
+ } else {
1173
+ output(`\nāœ… All spec requirements supported by chosen libraries`)
1174
+ }
1175
+ }
1176
+ } else {
1177
+ output(` ā„¹ļø No specific libraries detected in spec`)
1178
+ }
412
1179
  }
413
1180
 
414
- // 5. Store detected stack in context.md (for agents to reference)
415
- const stackForContext = {
416
- detected: detectedStack,
417
- bestPracticesPath: '.claude/contexts/domain/project/best-practices/',
418
- files: [...existingBp, ...missingBp.map(t => `${t}.md`)]
1181
+ // Store capability analysis
1182
+ const capabilityAnalysis = {
1183
+ libraries: detectedLibraries || [],
1184
+ requirements: specRequirements || [],
1185
+ gaps: capabilityGaps || [],
1186
+ customRequired: customImplementationRequired || []
419
1187
  }
420
1188
  ```
421
1189
 
422
1190
  **Helper: generateBestPracticesFile()**
1191
+
1192
+ > **Updated v2.3.0:** Now includes Context7 library ID for reference and refresh capability.
1193
+
423
1194
  ```typescript
424
- function generateBestPracticesFile(tech: string, context7Docs: string): string {
1195
+ function generateBestPracticesFile(
1196
+ tech: string,
1197
+ context7Docs: string,
1198
+ context7Id: string
1199
+ ): string {
425
1200
  return `# ${tech} Best Practices
426
1201
 
427
1202
  > **Source:** Context7 MCP
1203
+ > **Library ID:** \`${context7Id}\`
428
1204
  > **Generated:** ${new Date().toISOString().split('T')[0]}
1205
+ > **Refresh:** Query Context7 with the Library ID above to update this file
429
1206
 
430
1207
  ---
431
1208
 
432
1209
  ## Best Practices
433
1210
 
434
- ${extractDos(context7Docs)}
1211
+ ${extractBestPractices(context7Docs)}
435
1212
 
436
1213
  ---
437
1214
 
438
1215
  ## Anti-Patterns to Avoid
439
1216
 
440
- ${extractDonts(context7Docs)}
1217
+ ${extractAntiPatterns(context7Docs)}
1218
+
1219
+ ---
1220
+
1221
+ ## Code Examples
1222
+
1223
+ ${extractCodeExamples(context7Docs)}
441
1224
 
442
1225
  ---
443
1226
 
444
- ## šŸŽÆ Quick Checklist
1227
+ ## Quick Checklist
445
1228
 
446
1229
  Before committing ${tech} code:
447
1230
  ${extractChecklist(context7Docs)}
@@ -451,6 +1234,81 @@ ${extractChecklist(context7Docs)}
451
1234
  **Agents read this file in STEP 0 before implementation.**
452
1235
  `
453
1236
  }
1237
+
1238
+ // Helper: Extract best practices from Context7 docs
1239
+ function extractBestPractices(docs: string): string {
1240
+ // Look for sections about best practices, recommendations, patterns
1241
+ const patterns = [
1242
+ /best\s*practices?[:\s]+([^#]+?)(?=##|$)/gi,
1243
+ /recommend(?:ed|ations)?[:\s]+([^#]+?)(?=##|$)/gi,
1244
+ /(?:do|should)[:\s]+([^#]+?)(?=##|$)/gi
1245
+ ]
1246
+
1247
+ let extracted = ''
1248
+ for (const pattern of patterns) {
1249
+ const matches = docs.match(pattern)
1250
+ if (matches) {
1251
+ extracted += matches.join('\n\n')
1252
+ }
1253
+ }
1254
+
1255
+ return extracted || docs.slice(0, 2000) // Fallback to first 2000 chars
1256
+ }
1257
+
1258
+ // Helper: Extract anti-patterns from Context7 docs
1259
+ function extractAntiPatterns(docs: string): string {
1260
+ const patterns = [
1261
+ /anti-?patterns?[:\s]+([^#]+?)(?=##|$)/gi,
1262
+ /avoid[:\s]+([^#]+?)(?=##|$)/gi,
1263
+ /(?:don'?t|should\s*not)[:\s]+([^#]+?)(?=##|$)/gi,
1264
+ /common\s*mistakes?[:\s]+([^#]+?)(?=##|$)/gi
1265
+ ]
1266
+
1267
+ let extracted = ''
1268
+ for (const pattern of patterns) {
1269
+ const matches = docs.match(pattern)
1270
+ if (matches) {
1271
+ extracted += matches.join('\n\n')
1272
+ }
1273
+ }
1274
+
1275
+ return extracted || 'Review Context7 documentation for anti-patterns specific to your use case.'
1276
+ }
1277
+
1278
+ // Helper: Extract code examples from Context7 docs
1279
+ function extractCodeExamples(docs: string): string {
1280
+ // Extract code blocks
1281
+ const codeBlocks = docs.match(/\`\`\`[\s\S]*?\`\`\`/g) || []
1282
+ return codeBlocks.slice(0, 5).join('\n\n') || 'See Context7 documentation for code examples.'
1283
+ }
1284
+
1285
+ // Helper: Generate checklist from docs
1286
+ function extractChecklist(docs: string): string {
1287
+ // Look for checklist items or generate from best practices
1288
+ const checklistPatterns = [
1289
+ /- \[[ x]\][^\n]+/gi,
1290
+ /\d+\.\s+[^\n]+/gi
1291
+ ]
1292
+
1293
+ let items = []
1294
+ for (const pattern of checklistPatterns) {
1295
+ const matches = docs.match(pattern)
1296
+ if (matches) {
1297
+ items = items.concat(matches.slice(0, 10))
1298
+ }
1299
+ }
1300
+
1301
+ if (items.length > 0) {
1302
+ return items.map(item => `- [ ] ${item.replace(/^[\d.-\[\]x\s]+/i, '')}`).join('\n')
1303
+ }
1304
+
1305
+ // Fallback: generic checklist
1306
+ return `- [ ] Follow official documentation patterns
1307
+ - [ ] Handle errors appropriately
1308
+ - [ ] Add proper typing/validation
1309
+ - [ ] Write tests for new code
1310
+ - [ ] Review for security concerns`
1311
+ }
454
1312
  ```
455
1313
 
456
1314
  ---
@@ -1110,20 +1968,18 @@ function getAgentForPhase(phaseId: string): string {
1110
1968
  }
1111
1969
  ```
1112
1970
 
1113
- ### detectAdditionalTech()
1971
+ ### detectAdditionalTech() - DEPRECATED
1972
+
1973
+ > **Note:** This function is deprecated in v2.3.0. Use `extractPotentialLibraryNames()` + Context7 resolution instead.
1974
+ > The dynamic approach automatically detects any library without hardcoded patterns.
1975
+
1114
1976
  ```typescript
1115
- // Detect change-specific tech (Stripe, WebSocket, etc.)
1977
+ // DEPRECATED: Kept for backwards compatibility only
1978
+ // Use extractPotentialLibraryNames() for new implementations
1116
1979
  function detectAdditionalTech(proposal: string, tasks: string): string[] {
1980
+ // Now delegates to the dynamic detection system
1117
1981
  const combined = proposal + ' ' + tasks
1118
- const tech = []
1119
-
1120
- if (combined.includes('stripe') || combined.includes('payment')) tech.push('Stripe')
1121
- if (combined.includes('websocket') || combined.includes('realtime')) tech.push('WebSocket')
1122
- if (combined.includes('redis')) tech.push('Redis')
1123
- if (combined.includes('s3') || combined.includes('storage')) tech.push('S3/Storage')
1124
- // Add more as needed
1125
-
1126
- return tech
1982
+ return extractPotentialLibraryNames(combined)
1127
1983
  }
1128
1984
  ```
1129
1985