#
tokens: 48718/50000 5/825 files (page 43/61)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 43 of 61. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .DS_Store
├── .github
│   ├── dependabot.yml
│   ├── instructions
│   │   ├── mcp-node-tests.instructions.md
│   │   └── mcp-yml-tests.instructions.md
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── documentation.yml
│   │   ├── feature_request.yml
│   │   └── question.yml
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bug_fix.md
│   │   ├── documentation.md
│   │   └── new_tool.md
│   ├── pull_request_template.md
│   └── workflows
│       ├── ci.yml
│       ├── deploy-pages.yml
│       ├── publish.yml
│       └── update-docs.yml
├── .gitignore
├── .husky
│   └── pre-commit
├── aegis.config.docs-only.json
├── aegis.config.json
├── aegis.config.with-dw.json
├── AGENTS.md
├── ai-instructions
│   ├── claude-desktop
│   │   └── claude_custom_instructions.md
│   ├── cursor
│   │   └── .cursor
│   │       └── rules
│   │           ├── debugging-workflows.mdc
│   │           ├── hooks-development.mdc
│   │           ├── isml-templates.mdc
│   │           ├── job-framework.mdc
│   │           ├── performance-optimization.mdc
│   │           ├── scapi-endpoints.mdc
│   │           ├── security-patterns.mdc
│   │           ├── sfcc-development.mdc
│   │           ├── sfra-controllers.mdc
│   │           ├── sfra-models.mdc
│   │           ├── system-objects.mdc
│   │           └── testing-patterns.mdc
│   └── github-copilot
│       └── copilot-instructions.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│   ├── best-practices
│   │   ├── cartridge_creation.md
│   │   ├── isml_templates.md
│   │   ├── job_framework.md
│   │   ├── localserviceregistry.md
│   │   ├── ocapi_hooks.md
│   │   ├── performance.md
│   │   ├── scapi_custom_endpoint.md
│   │   ├── scapi_hooks.md
│   │   ├── security.md
│   │   ├── sfra_client_side_js.md
│   │   ├── sfra_controllers.md
│   │   ├── sfra_models.md
│   │   └── sfra_scss.md
│   ├── dw_campaign
│   │   ├── ABTest.md
│   │   ├── ABTestMgr.md
│   │   ├── ABTestSegment.md
│   │   ├── AmountDiscount.md
│   │   ├── ApproachingDiscount.md
│   │   ├── BonusChoiceDiscount.md
│   │   ├── BonusDiscount.md
│   │   ├── Campaign.md
│   │   ├── CampaignMgr.md
│   │   ├── CampaignStatusCodes.md
│   │   ├── Coupon.md
│   │   ├── CouponMgr.md
│   │   ├── CouponRedemption.md
│   │   ├── CouponStatusCodes.md
│   │   ├── Discount.md
│   │   ├── DiscountPlan.md
│   │   ├── FixedPriceDiscount.md
│   │   ├── FixedPriceShippingDiscount.md
│   │   ├── FreeDiscount.md
│   │   ├── FreeShippingDiscount.md
│   │   ├── PercentageDiscount.md
│   │   ├── PercentageOptionDiscount.md
│   │   ├── PriceBookPriceDiscount.md
│   │   ├── Promotion.md
│   │   ├── PromotionMgr.md
│   │   ├── PromotionPlan.md
│   │   ├── SlotContent.md
│   │   ├── SourceCodeGroup.md
│   │   ├── SourceCodeInfo.md
│   │   ├── SourceCodeStatusCodes.md
│   │   └── TotalFixedPriceDiscount.md
│   ├── dw_catalog
│   │   ├── Catalog.md
│   │   ├── CatalogMgr.md
│   │   ├── Category.md
│   │   ├── CategoryAssignment.md
│   │   ├── CategoryLink.md
│   │   ├── PriceBook.md
│   │   ├── PriceBookMgr.md
│   │   ├── Product.md
│   │   ├── ProductActiveData.md
│   │   ├── ProductAttributeModel.md
│   │   ├── ProductAvailabilityLevels.md
│   │   ├── ProductAvailabilityModel.md
│   │   ├── ProductInventoryList.md
│   │   ├── ProductInventoryMgr.md
│   │   ├── ProductInventoryRecord.md
│   │   ├── ProductLink.md
│   │   ├── ProductMgr.md
│   │   ├── ProductOption.md
│   │   ├── ProductOptionModel.md
│   │   ├── ProductOptionValue.md
│   │   ├── ProductPriceInfo.md
│   │   ├── ProductPriceModel.md
│   │   ├── ProductPriceTable.md
│   │   ├── ProductSearchHit.md
│   │   ├── ProductSearchModel.md
│   │   ├── ProductSearchRefinementDefinition.md
│   │   ├── ProductSearchRefinements.md
│   │   ├── ProductSearchRefinementValue.md
│   │   ├── ProductVariationAttribute.md
│   │   ├── ProductVariationAttributeValue.md
│   │   ├── ProductVariationModel.md
│   │   ├── Recommendation.md
│   │   ├── SearchModel.md
│   │   ├── SearchRefinementDefinition.md
│   │   ├── SearchRefinements.md
│   │   ├── SearchRefinementValue.md
│   │   ├── SortingOption.md
│   │   ├── SortingRule.md
│   │   ├── Store.md
│   │   ├── StoreGroup.md
│   │   ├── StoreInventoryFilter.md
│   │   ├── StoreInventoryFilterValue.md
│   │   ├── StoreMgr.md
│   │   ├── Variant.md
│   │   └── VariationGroup.md
│   ├── dw_content
│   │   ├── Content.md
│   │   ├── ContentMgr.md
│   │   ├── ContentSearchModel.md
│   │   ├── ContentSearchRefinementDefinition.md
│   │   ├── ContentSearchRefinements.md
│   │   ├── ContentSearchRefinementValue.md
│   │   ├── Folder.md
│   │   ├── Library.md
│   │   ├── MarkupText.md
│   │   └── MediaFile.md
│   ├── dw_crypto
│   │   ├── CertificateRef.md
│   │   ├── CertificateUtils.md
│   │   ├── Cipher.md
│   │   ├── Encoding.md
│   │   ├── JWE.md
│   │   ├── JWEHeader.md
│   │   ├── JWS.md
│   │   ├── JWSHeader.md
│   │   ├── KeyRef.md
│   │   ├── Mac.md
│   │   ├── MessageDigest.md
│   │   ├── SecureRandom.md
│   │   ├── Signature.md
│   │   ├── WeakCipher.md
│   │   ├── WeakMac.md
│   │   ├── WeakMessageDigest.md
│   │   ├── WeakSignature.md
│   │   └── X509Certificate.md
│   ├── dw_customer
│   │   ├── AddressBook.md
│   │   ├── AgentUserMgr.md
│   │   ├── AgentUserStatusCodes.md
│   │   ├── AuthenticationStatus.md
│   │   ├── Credentials.md
│   │   ├── Customer.md
│   │   ├── CustomerActiveData.md
│   │   ├── CustomerAddress.md
│   │   ├── CustomerCDPData.md
│   │   ├── CustomerContextMgr.md
│   │   ├── CustomerGroup.md
│   │   ├── CustomerList.md
│   │   ├── CustomerMgr.md
│   │   ├── CustomerPasswordConstraints.md
│   │   ├── CustomerPaymentInstrument.md
│   │   ├── CustomerStatusCodes.md
│   │   ├── EncryptedObject.md
│   │   ├── ExternalProfile.md
│   │   ├── OrderHistory.md
│   │   ├── ProductList.md
│   │   ├── ProductListItem.md
│   │   ├── ProductListItemPurchase.md
│   │   ├── ProductListMgr.md
│   │   ├── ProductListRegistrant.md
│   │   ├── Profile.md
│   │   └── Wallet.md
│   ├── dw_extensions.applepay
│   │   ├── ApplePayHookResult.md
│   │   └── ApplePayHooks.md
│   ├── dw_extensions.facebook
│   │   ├── FacebookFeedHooks.md
│   │   └── FacebookProduct.md
│   ├── dw_extensions.paymentrequest
│   │   ├── PaymentRequestHookResult.md
│   │   └── PaymentRequestHooks.md
│   ├── dw_extensions.payments
│   │   ├── SalesforceBancontactPaymentDetails.md
│   │   ├── SalesforceCardPaymentDetails.md
│   │   ├── SalesforceEpsPaymentDetails.md
│   │   ├── SalesforceIdealPaymentDetails.md
│   │   ├── SalesforceKlarnaPaymentDetails.md
│   │   ├── SalesforcePaymentDetails.md
│   │   ├── SalesforcePaymentIntent.md
│   │   ├── SalesforcePaymentMethod.md
│   │   ├── SalesforcePaymentRequest.md
│   │   ├── SalesforcePaymentsHooks.md
│   │   ├── SalesforcePaymentsMgr.md
│   │   ├── SalesforcePaymentsSiteConfiguration.md
│   │   ├── SalesforcePayPalOrder.md
│   │   ├── SalesforcePayPalOrderAddress.md
│   │   ├── SalesforcePayPalOrderPayer.md
│   │   ├── SalesforcePayPalPaymentDetails.md
│   │   ├── SalesforceSepaDebitPaymentDetails.md
│   │   └── SalesforceVenmoPaymentDetails.md
│   ├── dw_extensions.pinterest
│   │   ├── PinterestAvailability.md
│   │   ├── PinterestFeedHooks.md
│   │   ├── PinterestOrder.md
│   │   ├── PinterestOrderHooks.md
│   │   └── PinterestProduct.md
│   ├── dw_io
│   │   ├── CSVStreamReader.md
│   │   ├── CSVStreamWriter.md
│   │   ├── File.md
│   │   ├── FileReader.md
│   │   ├── FileWriter.md
│   │   ├── InputStream.md
│   │   ├── OutputStream.md
│   │   ├── PrintWriter.md
│   │   ├── RandomAccessFileReader.md
│   │   ├── Reader.md
│   │   ├── StringWriter.md
│   │   ├── Writer.md
│   │   ├── XMLIndentingStreamWriter.md
│   │   ├── XMLStreamConstants.md
│   │   ├── XMLStreamReader.md
│   │   └── XMLStreamWriter.md
│   ├── dw_job
│   │   ├── JobExecution.md
│   │   └── JobStepExecution.md
│   ├── dw_net
│   │   ├── FTPClient.md
│   │   ├── FTPFileInfo.md
│   │   ├── HTTPClient.md
│   │   ├── HTTPRequestPart.md
│   │   ├── Mail.md
│   │   ├── SFTPClient.md
│   │   ├── SFTPFileInfo.md
│   │   ├── WebDAVClient.md
│   │   └── WebDAVFileInfo.md
│   ├── dw_object
│   │   ├── ActiveData.md
│   │   ├── CustomAttributes.md
│   │   ├── CustomObject.md
│   │   ├── CustomObjectMgr.md
│   │   ├── Extensible.md
│   │   ├── ExtensibleObject.md
│   │   ├── Note.md
│   │   ├── ObjectAttributeDefinition.md
│   │   ├── ObjectAttributeGroup.md
│   │   ├── ObjectAttributeValueDefinition.md
│   │   ├── ObjectTypeDefinition.md
│   │   ├── PersistentObject.md
│   │   ├── SimpleExtensible.md
│   │   └── SystemObjectMgr.md
│   ├── dw_order
│   │   ├── AbstractItem.md
│   │   ├── AbstractItemCtnr.md
│   │   ├── Appeasement.md
│   │   ├── AppeasementItem.md
│   │   ├── Basket.md
│   │   ├── BasketMgr.md
│   │   ├── BonusDiscountLineItem.md
│   │   ├── CouponLineItem.md
│   │   ├── CreateAgentBasketLimitExceededException.md
│   │   ├── CreateBasketFromOrderException.md
│   │   ├── CreateCouponLineItemException.md
│   │   ├── CreateOrderException.md
│   │   ├── CreateTemporaryBasketLimitExceededException.md
│   │   ├── GiftCertificate.md
│   │   ├── GiftCertificateLineItem.md
│   │   ├── GiftCertificateMgr.md
│   │   ├── GiftCertificateStatusCodes.md
│   │   ├── Invoice.md
│   │   ├── InvoiceItem.md
│   │   ├── LineItem.md
│   │   ├── LineItemCtnr.md
│   │   ├── Order.md
│   │   ├── OrderAddress.md
│   │   ├── OrderItem.md
│   │   ├── OrderMgr.md
│   │   ├── OrderPaymentInstrument.md
│   │   ├── OrderProcessStatusCodes.md
│   │   ├── PaymentCard.md
│   │   ├── PaymentInstrument.md
│   │   ├── PaymentMethod.md
│   │   ├── PaymentMgr.md
│   │   ├── PaymentProcessor.md
│   │   ├── PaymentStatusCodes.md
│   │   ├── PaymentTransaction.md
│   │   ├── PriceAdjustment.md
│   │   ├── PriceAdjustmentLimitTypes.md
│   │   ├── ProductLineItem.md
│   │   ├── ProductShippingCost.md
│   │   ├── ProductShippingLineItem.md
│   │   ├── ProductShippingModel.md
│   │   ├── Return.md
│   │   ├── ReturnCase.md
│   │   ├── ReturnCaseItem.md
│   │   ├── ReturnItem.md
│   │   ├── Shipment.md
│   │   ├── ShipmentShippingCost.md
│   │   ├── ShipmentShippingModel.md
│   │   ├── ShippingLineItem.md
│   │   ├── ShippingLocation.md
│   │   ├── ShippingMethod.md
│   │   ├── ShippingMgr.md
│   │   ├── ShippingOrder.md
│   │   ├── ShippingOrderItem.md
│   │   ├── SumItem.md
│   │   ├── TaxGroup.md
│   │   ├── TaxItem.md
│   │   ├── TaxMgr.md
│   │   ├── TrackingInfo.md
│   │   └── TrackingRef.md
│   ├── dw_order.hooks
│   │   ├── CalculateHooks.md
│   │   ├── OrderHooks.md
│   │   ├── PaymentHooks.md
│   │   ├── ReturnHooks.md
│   │   └── ShippingOrderHooks.md
│   ├── dw_rpc
│   │   ├── SOAPUtil.md
│   │   ├── Stub.md
│   │   └── WebReference.md
│   ├── dw_suggest
│   │   ├── BrandSuggestions.md
│   │   ├── CategorySuggestions.md
│   │   ├── ContentSuggestions.md
│   │   ├── CustomSuggestions.md
│   │   ├── ProductSuggestions.md
│   │   ├── SearchPhraseSuggestions.md
│   │   ├── SuggestedCategory.md
│   │   ├── SuggestedContent.md
│   │   ├── SuggestedPhrase.md
│   │   ├── SuggestedProduct.md
│   │   ├── SuggestedTerm.md
│   │   ├── SuggestedTerms.md
│   │   ├── Suggestions.md
│   │   └── SuggestModel.md
│   ├── dw_svc
│   │   ├── FTPService.md
│   │   ├── FTPServiceDefinition.md
│   │   ├── HTTPFormService.md
│   │   ├── HTTPFormServiceDefinition.md
│   │   ├── HTTPService.md
│   │   ├── HTTPServiceDefinition.md
│   │   ├── LocalServiceRegistry.md
│   │   ├── Result.md
│   │   ├── Service.md
│   │   ├── ServiceCallback.md
│   │   ├── ServiceConfig.md
│   │   ├── ServiceCredential.md
│   │   ├── ServiceDefinition.md
│   │   ├── ServiceProfile.md
│   │   ├── ServiceRegistry.md
│   │   ├── SOAPService.md
│   │   └── SOAPServiceDefinition.md
│   ├── dw_system
│   │   ├── AgentUserStatusCodes.md
│   │   ├── Cache.md
│   │   ├── CacheMgr.md
│   │   ├── HookMgr.md
│   │   ├── InternalObject.md
│   │   ├── JobProcessMonitor.md
│   │   ├── Log.md
│   │   ├── Logger.md
│   │   ├── LogNDC.md
│   │   ├── OrganizationPreferences.md
│   │   ├── Pipeline.md
│   │   ├── PipelineDictionary.md
│   │   ├── RemoteInclude.md
│   │   ├── Request.md
│   │   ├── RequestHooks.md
│   │   ├── Response.md
│   │   ├── RESTErrorResponse.md
│   │   ├── RESTResponseMgr.md
│   │   ├── RESTSuccessResponse.md
│   │   ├── SearchStatus.md
│   │   ├── Session.md
│   │   ├── Site.md
│   │   ├── SitePreferences.md
│   │   ├── Status.md
│   │   ├── StatusItem.md
│   │   ├── System.md
│   │   └── Transaction.md
│   ├── dw_util
│   │   ├── ArrayList.md
│   │   ├── Assert.md
│   │   ├── BigInteger.md
│   │   ├── Bytes.md
│   │   ├── Calendar.md
│   │   ├── Collection.md
│   │   ├── Currency.md
│   │   ├── DateUtils.md
│   │   ├── Decimal.md
│   │   ├── FilteringCollection.md
│   │   ├── Geolocation.md
│   │   ├── HashMap.md
│   │   ├── HashSet.md
│   │   ├── Iterator.md
│   │   ├── LinkedHashMap.md
│   │   ├── LinkedHashSet.md
│   │   ├── List.md
│   │   ├── Locale.md
│   │   ├── Map.md
│   │   ├── MapEntry.md
│   │   ├── MappingKey.md
│   │   ├── MappingMgr.md
│   │   ├── PropertyComparator.md
│   │   ├── SecureEncoder.md
│   │   ├── SecureFilter.md
│   │   ├── SeekableIterator.md
│   │   ├── Set.md
│   │   ├── SortedMap.md
│   │   ├── SortedSet.md
│   │   ├── StringUtils.md
│   │   ├── Template.md
│   │   └── UUIDUtils.md
│   ├── dw_value
│   │   ├── EnumValue.md
│   │   ├── MimeEncodedText.md
│   │   ├── Money.md
│   │   └── Quantity.md
│   ├── dw_web
│   │   ├── ClickStream.md
│   │   ├── ClickStreamEntry.md
│   │   ├── Cookie.md
│   │   ├── Cookies.md
│   │   ├── CSRFProtection.md
│   │   ├── Form.md
│   │   ├── FormAction.md
│   │   ├── FormElement.md
│   │   ├── FormElementValidationResult.md
│   │   ├── FormField.md
│   │   ├── FormFieldOption.md
│   │   ├── FormFieldOptions.md
│   │   ├── FormGroup.md
│   │   ├── FormList.md
│   │   ├── FormListItem.md
│   │   ├── Forms.md
│   │   ├── HttpParameter.md
│   │   ├── HttpParameterMap.md
│   │   ├── LoopIterator.md
│   │   ├── PageMetaData.md
│   │   ├── PageMetaTag.md
│   │   ├── PagingModel.md
│   │   ├── Resource.md
│   │   ├── URL.md
│   │   ├── URLAction.md
│   │   ├── URLParameter.md
│   │   ├── URLRedirect.md
│   │   ├── URLRedirectMgr.md
│   │   └── URLUtils.md
│   ├── sfra
│   │   ├── account.md
│   │   ├── address.md
│   │   ├── billing.md
│   │   ├── cart.md
│   │   ├── categories.md
│   │   ├── content.md
│   │   ├── locale.md
│   │   ├── order.md
│   │   ├── payment.md
│   │   ├── price-default.md
│   │   ├── price-range.md
│   │   ├── price-tiered.md
│   │   ├── product-bundle.md
│   │   ├── product-full.md
│   │   ├── product-line-items.md
│   │   ├── product-search.md
│   │   ├── product-tile.md
│   │   ├── querystring.md
│   │   ├── render.md
│   │   ├── request.md
│   │   ├── response.md
│   │   ├── server.md
│   │   ├── shipping.md
│   │   ├── store.md
│   │   ├── stores.md
│   │   └── totals.md
│   └── TopLevel
│       ├── APIException.md
│       ├── arguments.md
│       ├── Array.md
│       ├── ArrayBuffer.md
│       ├── BigInt.md
│       ├── Boolean.md
│       ├── ConversionError.md
│       ├── DataView.md
│       ├── Date.md
│       ├── Error.md
│       ├── ES6Iterator.md
│       ├── EvalError.md
│       ├── Fault.md
│       ├── Float32Array.md
│       ├── Float64Array.md
│       ├── Function.md
│       ├── Generator.md
│       ├── global.md
│       ├── Int16Array.md
│       ├── Int32Array.md
│       ├── Int8Array.md
│       ├── InternalError.md
│       ├── IOError.md
│       ├── Iterable.md
│       ├── Iterator.md
│       ├── JSON.md
│       ├── Map.md
│       ├── Math.md
│       ├── Module.md
│       ├── Namespace.md
│       ├── Number.md
│       ├── Object.md
│       ├── QName.md
│       ├── RangeError.md
│       ├── ReferenceError.md
│       ├── RegExp.md
│       ├── Set.md
│       ├── StopIteration.md
│       ├── String.md
│       ├── Symbol.md
│       ├── SyntaxError.md
│       ├── SystemError.md
│       ├── TypeError.md
│       ├── Uint16Array.md
│       ├── Uint32Array.md
│       ├── Uint8Array.md
│       ├── Uint8ClampedArray.md
│       ├── URIError.md
│       ├── WeakMap.md
│       ├── WeakSet.md
│       ├── XML.md
│       ├── XMLList.md
│       └── XMLStreamError.md
├── docs-site
│   ├── .gitignore
│   ├── App.tsx
│   ├── components
│   │   ├── Badge.tsx
│   │   ├── BreadcrumbSchema.tsx
│   │   ├── CodeBlock.tsx
│   │   ├── Collapsible.tsx
│   │   ├── ConfigBuilder.tsx
│   │   ├── ConfigHero.tsx
│   │   ├── ConfigModeTabs.tsx
│   │   ├── icons.tsx
│   │   ├── Layout.tsx
│   │   ├── LightCodeContainer.tsx
│   │   ├── NewcomerCTA.tsx
│   │   ├── NextStepsStrip.tsx
│   │   ├── OnThisPage.tsx
│   │   ├── Search.tsx
│   │   ├── SEO.tsx
│   │   ├── Sidebar.tsx
│   │   ├── StructuredData.tsx
│   │   ├── ToolCard.tsx
│   │   ├── ToolFilters.tsx
│   │   ├── Typography.tsx
│   │   └── VersionBadge.tsx
│   ├── constants.tsx
│   ├── index.html
│   ├── main.tsx
│   ├── metadata.json
│   ├── package-lock.json
│   ├── package.json
│   ├── pages
│   │   ├── AIInterfacesPage.tsx
│   │   ├── ConfigurationPage.tsx
│   │   ├── DevelopmentPage.tsx
│   │   ├── ExamplesPage.tsx
│   │   ├── FeaturesPage.tsx
│   │   ├── HomePage.tsx
│   │   ├── SecurityPage.tsx
│   │   ├── ToolsPage.tsx
│   │   └── TroubleshootingPage.tsx
│   ├── postcss.config.js
│   ├── public
│   │   ├── .well-known
│   │   │   └── security.txt
│   │   ├── 404.html
│   │   ├── android-chrome-192x192.png
│   │   ├── android-chrome-512x512.png
│   │   ├── apple-touch-icon.png
│   │   ├── explain-product-pricing-methods-no-mcp.png
│   │   ├── explain-product-pricing-methods.png
│   │   ├── favicon-16x16.png
│   │   ├── favicon-32x32.png
│   │   ├── favicon.ico
│   │   ├── llms.txt
│   │   ├── robots.txt
│   │   ├── site.webmanifest
│   │   └── sitemap.xml
│   ├── README.md
│   ├── scripts
│   │   ├── generate-search-index.js
│   │   ├── generate-sitemap.js
│   │   └── search-dev.js
│   ├── src
│   │   └── styles
│   │       ├── input.css
│   │       └── prism-theme.css
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   ├── types.ts
│   ├── utils
│   │   ├── search.ts
│   │   └── toolsData.ts
│   └── vite.config.ts
├── eslint.config.js
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── convert-docs.js
├── SECURITY.md
├── server.json
├── src
│   ├── clients
│   │   ├── base
│   │   │   ├── http-client.ts
│   │   │   ├── oauth-token.ts
│   │   │   └── ocapi-auth-client.ts
│   │   ├── best-practices-client.ts
│   │   ├── cartridge-generation-client.ts
│   │   ├── docs
│   │   │   ├── class-content-parser.ts
│   │   │   ├── class-name-resolver.ts
│   │   │   ├── documentation-scanner.ts
│   │   │   ├── index.ts
│   │   │   └── referenced-types-extractor.ts
│   │   ├── docs-client.ts
│   │   ├── log-client.ts
│   │   ├── logs
│   │   │   ├── index.ts
│   │   │   ├── log-analyzer.ts
│   │   │   ├── log-client.ts
│   │   │   ├── log-constants.ts
│   │   │   ├── log-file-discovery.ts
│   │   │   ├── log-file-reader.ts
│   │   │   ├── log-formatter.ts
│   │   │   ├── log-processor.ts
│   │   │   ├── log-types.ts
│   │   │   └── webdav-client-manager.ts
│   │   ├── ocapi
│   │   │   ├── code-versions-client.ts
│   │   │   ├── site-preferences-client.ts
│   │   │   └── system-objects-client.ts
│   │   ├── ocapi-client.ts
│   │   └── sfra-client.ts
│   ├── config
│   │   ├── configuration-factory.ts
│   │   └── dw-json-loader.ts
│   ├── core
│   │   ├── handlers
│   │   │   ├── abstract-log-tool-handler.ts
│   │   │   ├── base-handler.ts
│   │   │   ├── best-practices-handler.ts
│   │   │   ├── cartridge-handler.ts
│   │   │   ├── client-factory.ts
│   │   │   ├── code-version-handler.ts
│   │   │   ├── docs-handler.ts
│   │   │   ├── job-log-handler.ts
│   │   │   ├── job-log-tool-config.ts
│   │   │   ├── log-handler.ts
│   │   │   ├── log-tool-config.ts
│   │   │   ├── sfra-handler.ts
│   │   │   ├── system-object-handler.ts
│   │   │   └── validation-helpers.ts
│   │   ├── server.ts
│   │   └── tool-definitions.ts
│   ├── index.ts
│   ├── main.ts
│   ├── services
│   │   ├── file-system-service.ts
│   │   ├── index.ts
│   │   └── path-service.ts
│   ├── tool-configs
│   │   ├── best-practices-tool-config.ts
│   │   ├── cartridge-tool-config.ts
│   │   ├── code-version-tool-config.ts
│   │   ├── docs-tool-config.ts
│   │   ├── job-log-tool-config.ts
│   │   ├── log-tool-config.ts
│   │   ├── sfra-tool-config.ts
│   │   └── system-object-tool-config.ts
│   ├── types
│   │   └── types.ts
│   └── utils
│       ├── cache.ts
│       ├── job-log-tool-config.ts
│       ├── job-log-utils.ts
│       ├── log-cache.ts
│       ├── log-tool-config.ts
│       ├── log-tool-constants.ts
│       ├── log-tool-utils.ts
│       ├── logger.ts
│       ├── ocapi-url-builder.ts
│       ├── path-resolver.ts
│       ├── query-builder.ts
│       ├── utils.ts
│       └── validator.ts
├── tests
│   ├── __mocks__
│   │   ├── docs-client.ts
│   │   ├── src
│   │   │   └── clients
│   │   │       └── base
│   │   │           └── http-client.js
│   │   └── webdav.js
│   ├── base-handler.test.ts
│   ├── base-http-client.test.ts
│   ├── best-practices-handler.test.ts
│   ├── cache.test.ts
│   ├── cartridge-handler.test.ts
│   ├── class-content-parser.test.ts
│   ├── class-name-resolver.test.ts
│   ├── client-factory.test.ts
│   ├── code-version-handler.test.ts
│   ├── code-versions-client.test.ts
│   ├── config.test.ts
│   ├── configuration-factory.test.ts
│   ├── docs-handler.test.ts
│   ├── documentation-scanner.test.ts
│   ├── file-system-service.test.ts
│   ├── job-log-handler.test.ts
│   ├── job-log-utils.test.ts
│   ├── log-client.test.ts
│   ├── log-handler.test.ts
│   ├── log-processor.test.ts
│   ├── logger.test.ts
│   ├── mcp
│   │   ├── AGENTS.md
│   │   ├── node
│   │   │   ├── activate-code-version-advanced.full-mode.programmatic.test.js
│   │   │   ├── code-versions.full-mode.programmatic.test.js
│   │   │   ├── generate-cartridge-structure.docs-only.programmatic.test.js
│   │   │   ├── get-available-best-practice-guides.docs-only.programmatic.test.js
│   │   │   ├── get-available-sfra-documents.programmatic.test.js
│   │   │   ├── get-best-practice-guide.docs-only.programmatic.test.js
│   │   │   ├── get-hook-reference.docs-only.programmatic.test.js
│   │   │   ├── get-job-execution-summary.full-mode.programmatic.test.js
│   │   │   ├── get-job-log-entries.full-mode.programmatic.test.js
│   │   │   ├── get-latest-debug.full-mode.programmatic.test.js
│   │   │   ├── get-latest-error.full-mode.programmatic.test.js
│   │   │   ├── get-latest-info.full-mode.programmatic.test.js
│   │   │   ├── get-latest-job-log-files.full-mode.programmatic.test.js
│   │   │   ├── get-latest-warn.full-mode.programmatic.test.js
│   │   │   ├── get-log-file-contents.full-mode.programmatic.test.js
│   │   │   ├── get-sfcc-class-documentation.docs-only.programmatic.test.js
│   │   │   ├── get-sfcc-class-info.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-categories.docs-only.programmatic.test.js
│   │   │   ├── get-sfra-document.programmatic.test.js
│   │   │   ├── get-sfra-documents-by-category.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definition.full-mode.programmatic.test.js
│   │   │   ├── get-system-object-definitions.docs-only.programmatic.test.js
│   │   │   ├── get-system-object-definitions.full-mode.programmatic.test.js
│   │   │   ├── list-log-files.full-mode.programmatic.test.js
│   │   │   ├── list-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-best-practices.docs-only.programmatic.test.js
│   │   │   ├── search-custom-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs-by-name.full-mode.programmatic.test.js
│   │   │   ├── search-job-logs.full-mode.programmatic.test.js
│   │   │   ├── search-logs.full-mode.programmatic.test.js
│   │   │   ├── search-sfcc-classes.docs-only.programmatic.test.js
│   │   │   ├── search-sfcc-methods.docs-only.programmatic.test.js
│   │   │   ├── search-sfra-documentation.docs-only.programmatic.test.js
│   │   │   ├── search-site-preferences.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-definitions.full-mode.programmatic.test.js
│   │   │   ├── search-system-object-attribute-groups.full-mode.programmatic.test.js
│   │   │   ├── summarize-logs.full-mode.programmatic.test.js
│   │   │   ├── tools.docs-only.programmatic.test.js
│   │   │   └── tools.full-mode.programmatic.test.js
│   │   ├── README.md
│   │   ├── test-fixtures
│   │   │   └── dw.json
│   │   └── yaml
│   │       ├── activate-code-version.docs-only.test.mcp.yml
│   │       ├── activate-code-version.full-mode.test.mcp.yml
│   │       ├── get_latest_error.test.mcp.yml
│   │       ├── get-available-best-practice-guides.docs-only.test.mcp.yml
│   │       ├── get-available-best-practice-guides.full-mode.test.mcp.yml
│   │       ├── get-available-sfra-documents.docs-only.test.mcp.yml
│   │       ├── get-available-sfra-documents.full-mode.test.mcp.yml
│   │       ├── get-best-practice-guide.docs-only.test.mcp.yml
│   │       ├── get-best-practice-guide.full-mode.test.mcp.yml
│   │       ├── get-code-versions.docs-only.test.mcp.yml
│   │       ├── get-code-versions.full-mode.test.mcp.yml
│   │       ├── get-hook-reference.docs-only.test.mcp.yml
│   │       ├── get-hook-reference.full-mode.test.mcp.yml
│   │       ├── get-job-execution-summary.full-mode.test.mcp.yml
│   │       ├── get-job-log-entries.full-mode.test.mcp.yml
│   │       ├── get-latest-debug.full-mode.test.mcp.yml
│   │       ├── get-latest-error.full-mode.test.mcp.yml
│   │       ├── get-latest-info.full-mode.test.mcp.yml
│   │       ├── get-latest-job-log-files.full-mode.test.mcp.yml
│   │       ├── get-latest-warn.full-mode.test.mcp.yml
│   │       ├── get-log-file-contents.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-documentation.full-mode.test.mcp.yml
│   │       ├── get-sfcc-class-info.docs-only.test.mcp.yml
│   │       ├── get-sfcc-class-info.full-mode.test.mcp.yml
│   │       ├── get-sfra-categories.docs-only.test.mcp.yml
│   │       ├── get-sfra-categories.full-mode.test.mcp.yml
│   │       ├── get-sfra-document.docs-only.test.mcp.yml
│   │       ├── get-sfra-document.full-mode.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.docs-only.test.mcp.yml
│   │       ├── get-sfra-documents-by-category.full-mode.test.mcp.yml
│   │       ├── get-system-object-definition.docs-only.test.mcp.yml
│   │       ├── get-system-object-definition.full-mode.test.mcp.yml
│   │       ├── get-system-object-definitions.docs-only.test.mcp.yml
│   │       ├── get-system-object-definitions.full-mode.test.mcp.yml
│   │       ├── list-log-files.full-mode.test.mcp.yml
│   │       ├── list-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── list-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-best-practices.docs-only.test.mcp.yml
│   │       ├── search-best-practices.full-mode.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-custom-object-attribute-definitions.test.mcp.yml
│   │       ├── search-job-logs-by-name.full-mode.test.mcp.yml
│   │       ├── search-job-logs.full-mode.test.mcp.yml
│   │       ├── search-logs.full-mode.test.mcp.yml
│   │       ├── search-sfcc-classes.docs-only.test.mcp.yml
│   │       ├── search-sfcc-classes.full-mode.test.mcp.yml
│   │       ├── search-sfcc-methods.docs-only.test.mcp.yml
│   │       ├── search-sfcc-methods.full-mode.test.mcp.yml
│   │       ├── search-sfra-documentation.docs-only.test.mcp.yml
│   │       ├── search-sfra-documentation.full-mode.test.mcp.yml
│   │       ├── search-site-preferences.docs-only.test.mcp.yml
│   │       ├── search-site-preferences.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-definitions.full-mode.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.docs-only.test.mcp.yml
│   │       ├── search-system-object-attribute-groups.full-mode.test.mcp.yml
│   │       ├── summarize-logs.full-mode.test.mcp.yml
│   │       ├── tools.docs-only.test.mcp.yml
│   │       └── tools.full-mode.test.mcp.yml
│   ├── oauth-token.test.ts
│   ├── ocapi-auth-client.test.ts
│   ├── ocapi-client.test.ts
│   ├── path-service.test.ts
│   ├── query-builder.test.ts
│   ├── referenced-types-extractor.test.ts
│   ├── servers
│   │   ├── sfcc-mock-server
│   │   │   ├── mock-data
│   │   │   │   └── ocapi
│   │   │   │       ├── code-versions.json
│   │   │   │       ├── custom-object-attributes-customapi.json
│   │   │   │       ├── custom-object-attributes-globalsettings.json
│   │   │   │       ├── custom-object-attributes-versionhistory.json
│   │   │   │       ├── site-preferences-ccv.json
│   │   │   │       ├── site-preferences-fastforward.json
│   │   │   │       ├── site-preferences-sfra.json
│   │   │   │       ├── site-preferences-storefront.json
│   │   │   │       ├── site-preferences-system.json
│   │   │   │       ├── system-object-attribute-groups-campaign.json
│   │   │   │       ├── system-object-attribute-groups-category.json
│   │   │   │       ├── system-object-attribute-groups-order.json
│   │   │   │       ├── system-object-attribute-groups-product.json
│   │   │   │       ├── system-object-attribute-groups-sitepreferences.json
│   │   │   │       ├── system-object-attributes-customeraddress.json
│   │   │   │       ├── system-object-attributes-product-expanded.json
│   │   │   │       ├── system-object-attributes-product.json
│   │   │   │       ├── system-object-definition-category.json
│   │   │   │       ├── system-object-definition-customer.json
│   │   │   │       ├── system-object-definition-customeraddress.json
│   │   │   │       ├── system-object-definition-order.json
│   │   │   │       ├── system-object-definition-product.json
│   │   │   │       ├── system-object-definitions-old.json
│   │   │   │       └── system-object-definitions.json
│   │   │   ├── package-lock.json
│   │   │   ├── package.json
│   │   │   ├── README.md
│   │   │   ├── scripts
│   │   │   │   └── setup-logs.js
│   │   │   ├── server.js
│   │   │   └── src
│   │   │       ├── app.js
│   │   │       ├── config
│   │   │       │   └── server-config.js
│   │   │       ├── middleware
│   │   │       │   ├── auth.js
│   │   │       │   ├── cors.js
│   │   │       │   └── logging.js
│   │   │       ├── routes
│   │   │       │   ├── ocapi
│   │   │       │   │   ├── code-versions-handler.js
│   │   │       │   │   ├── oauth-handler.js
│   │   │       │   │   ├── ocapi-error-utils.js
│   │   │       │   │   ├── ocapi-utils.js
│   │   │       │   │   ├── site-preferences-handler.js
│   │   │       │   │   └── system-objects-handler.js
│   │   │       │   ├── ocapi.js
│   │   │       │   └── webdav.js
│   │   │       └── utils
│   │   │           ├── mock-data-loader.js
│   │   │           └── webdav-xml.js
│   │   └── sfcc-mock-server-manager.ts
│   ├── sfcc-mock-server.test.ts
│   ├── site-preferences-client.test.ts
│   ├── system-objects-client.test.ts
│   ├── utils.test.ts
│   ├── validation-helpers.test.ts
│   └── validator.test.ts
├── tsconfig.json
└── tsconfig.test.json
```

# Files

--------------------------------------------------------------------------------
/tests/mcp/node/generate-cartridge-structure.docs-only.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Programmatic tests for generate_cartridge_structure tool
  3 |  * 
  4 |  * These tests provide comprehensive verification of cartridge generation functionality,
  5 |  * including directory structure validation, file content verification, cleanup operations,
  6 |  * and edge case handling. All tests use system temp directory for safe isolation.
  7 |  * 
  8 |  * Response format discovered via aegis query:
  9 |  * - Success: { content: [{ type: "text", text: "{\"success\": true, \"message\": \"...\", \"createdFiles\": [...], \"createdDirectories\": [...], \"skippedFiles\": []}" }] }
 10 |  * - Error: { content: [{ type: "text", text: "Error: ..." }], isError: true }
 11 |  */
 12 | 
 13 | import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
 14 | import { strict as assert } from 'node:assert';
 15 | import { connect } from 'mcp-aegis';
 16 | import { promises as fs } from 'node:fs';
 17 | import { join } from 'node:path';
 18 | import { tmpdir } from 'node:os';
 19 | import { randomBytes } from 'node:crypto';
 20 | 
 21 | describe('generate_cartridge_structure Programmatic Tests', () => {
 22 |   let client;
 23 |   let testDirectories = []; // Track directories for cleanup
 24 | 
 25 |   before(async () => {
 26 |     client = await connect('./aegis.config.docs-only.json');
 27 |   });
 28 | 
 29 |   after(async () => {
 30 |     if (client?.connected) {
 31 |       await client.disconnect();
 32 |     }
 33 |   });
 34 | 
 35 |   beforeEach(() => {
 36 |     // CRITICAL: Clear all buffers to prevent test interference
 37 |     client.clearAllBuffers(); // Recommended - comprehensive protection
 38 |   });
 39 | 
 40 |   afterEach(async () => {
 41 |     // Clean up all test directories
 42 |     for (const dir of testDirectories) {
 43 |       try {
 44 |         await fs.rm(dir, { recursive: true, force: true });
 45 |       } catch (error) {
 46 |         // Ignore cleanup errors - directory might not exist
 47 |         console.warn(`Cleanup warning for ${dir}:`, error.message);
 48 |       }
 49 |     }
 50 |     testDirectories = [];
 51 |   });
 52 | 
 53 |   /**
 54 |    * Create a unique temporary directory for testing
 55 |    * @returns {string} Absolute path to temporary directory
 56 |    */
 57 |   function createTempTestDir() {
 58 |     const uniqueId = randomBytes(8).toString('hex');
 59 |     const testDir = join(tmpdir(), `mcp-cartridge-test-${uniqueId}`);
 60 |     testDirectories.push(testDir);
 61 |     return testDir;
 62 |   }
 63 | 
 64 |   /**
 65 |    * Check if a file exists and is readable
 66 |    * @param {string} filePath - Path to check
 67 |    * @returns {Promise<boolean>} True if file exists and is readable
 68 |    */
 69 |   async function fileExists(filePath) {
 70 |     try {
 71 |       await fs.access(filePath, fs.constants.F_OK | fs.constants.R_OK);
 72 |       return true;
 73 |     } catch {
 74 |       return false;
 75 |     }
 76 |   }
 77 | 
 78 |   /**
 79 |    * Check if a directory exists and is readable
 80 |    * @param {string} dirPath - Path to check
 81 |    * @returns {Promise<boolean>} True if directory exists and is readable
 82 |    */
 83 |   async function directoryExists(dirPath) {
 84 |     try {
 85 |       const stat = await fs.stat(dirPath);
 86 |       return stat.isDirectory();
 87 |     } catch {
 88 |       return false;
 89 |     }
 90 |   }
 91 | 
 92 |   /**
 93 |    * Read and parse JSON response from tool execution
 94 |    * @param {object} result - MCP tool result
 95 |    * @returns {object} Parsed response object
 96 |    */
 97 |   function parseToolResponse(result) {
 98 |     assert.equal(result.isError, false, 'Tool should execute successfully');
 99 |     assert.ok(result.content, 'Result should have content');
100 |     assert.equal(result.content.length, 1, 'Should have exactly one content item');
101 |     assert.equal(result.content[0].type, 'text', 'Content should be text type');
102 |     
103 |     const responseText = result.content[0].text;
104 |     return JSON.parse(responseText);
105 |   }
106 | 
107 |   describe('Protocol Compliance', () => {
108 |     test('should be properly connected to MCP server', async () => {
109 |       assert.ok(client.connected, 'Client should be connected');
110 |     });
111 | 
112 |     test('should have generate_cartridge_structure tool available', async () => {
113 |       const tools = await client.listTools();
114 |       const cartridgeTool = tools.find(tool => tool.name === 'generate_cartridge_structure');
115 |       
116 |       assert.ok(cartridgeTool, 'generate_cartridge_structure tool should be available');
117 |       assert.equal(cartridgeTool.name, 'generate_cartridge_structure');
118 |       assert.ok(cartridgeTool.description, 'Tool should have description');
119 |       assert.ok(cartridgeTool.inputSchema, 'Tool should have input schema');
120 |       assert.equal(cartridgeTool.inputSchema.type, 'object');
121 |       assert.ok(cartridgeTool.inputSchema.properties.cartridgeName, 'Tool should have cartridgeName parameter');
122 |       assert.ok(cartridgeTool.inputSchema.required.includes('cartridgeName'), 'cartridgeName should be required');
123 |     });
124 |   });
125 | 
126 |   describe('Full Project Setup Generation', () => {
127 |     test('should create complete project structure with all required files', async () => {
128 |       const testDir = createTempTestDir();
129 |       const cartridgeName = 'test_full_project';
130 | 
131 |       const result = await client.callTool('generate_cartridge_structure', {
132 |         cartridgeName,
133 |         targetPath: testDir,
134 |         fullProjectSetup: true
135 |       });
136 | 
137 |       const response = parseToolResponse(result);
138 |       
139 |       // Verify response structure
140 |       assert.equal(response.success, true, 'Operation should be successful');
141 |       assert.ok(response.message.includes('Successfully created full project setup'), 'Message should indicate full project setup');
142 |       assert.ok(response.message.includes(cartridgeName), 'Message should include cartridge name');
143 |       assert.ok(response.message.includes(testDir), 'Message should include target path');
144 |       assert.ok(Array.isArray(response.createdFiles), 'Should have createdFiles array');
145 |       assert.ok(Array.isArray(response.createdDirectories), 'Should have createdDirectories array');
146 |       assert.ok(Array.isArray(response.skippedFiles), 'Should have skippedFiles array');
147 | 
148 |       // Verify essential project files were created
149 |       const expectedProjectFiles = [
150 |         'package.json',
151 |         'dw.json',
152 |         'webpack.config.js',
153 |         '.eslintrc.json',
154 |         '.stylelintrc.json',
155 |         '.eslintignore',
156 |         '.gitignore'
157 |       ];
158 | 
159 |       for (const file of expectedProjectFiles) {
160 |         const filePath = join(testDir, file);
161 |         assert.ok(await fileExists(filePath), `Project file ${file} should exist`);
162 |         assert.ok(response.createdFiles.some(f => f.endsWith(file)), `${file} should be in createdFiles list`);
163 |       }
164 | 
165 |       // Verify cartridge-specific files
166 |       const cartridgeDir = join(testDir, 'cartridges', cartridgeName);
167 |       const expectedCartridgeFiles = [
168 |         '.project',
169 |         join('cartridge', `${cartridgeName}.properties`)
170 |       ];
171 | 
172 |       for (const file of expectedCartridgeFiles) {
173 |         const filePath = join(cartridgeDir, file);
174 |         assert.ok(await fileExists(filePath), `Cartridge file ${file} should exist`);
175 |       }
176 | 
177 |       // Verify directory structure
178 |       const expectedDirectories = [
179 |         testDir,
180 |         join(testDir, 'cartridges'),
181 |         cartridgeDir,
182 |         join(cartridgeDir, 'cartridge'),
183 |         join(cartridgeDir, 'cartridge', 'controllers'),
184 |         join(cartridgeDir, 'cartridge', 'models'),
185 |         join(cartridgeDir, 'cartridge', 'templates'),
186 |         join(cartridgeDir, 'cartridge', 'templates', 'default'),
187 |         join(cartridgeDir, 'cartridge', 'templates', 'resources'),
188 |         join(cartridgeDir, 'cartridge', 'client'),
189 |         join(cartridgeDir, 'cartridge', 'client', 'default'),
190 |         join(cartridgeDir, 'cartridge', 'client', 'default', 'js'),
191 |         join(cartridgeDir, 'cartridge', 'client', 'default', 'scss')
192 |       ];
193 | 
194 |       for (const dir of expectedDirectories) {
195 |         assert.ok(await directoryExists(dir), `Directory ${dir} should exist`);
196 |       }
197 | 
198 |       // Verify file contents - check package.json contains cartridge name
199 |       const packageJsonPath = join(testDir, 'package.json');
200 |       const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
201 |       const packageJson = JSON.parse(packageJsonContent);
202 |       assert.ok(packageJson.name.includes(cartridgeName), 'package.json should reference cartridge name');
203 |     });
204 | 
205 |     test('should create cartridge with default fullProjectSetup when not specified', async () => {
206 |       const testDir = createTempTestDir();
207 |       const cartridgeName = 'test_default_full';
208 | 
209 |       const result = await client.callTool('generate_cartridge_structure', {
210 |         cartridgeName,
211 |         targetPath: testDir
212 |         // fullProjectSetup not specified - should default to true
213 |       });
214 | 
215 |       const response = parseToolResponse(result);
216 |       
217 |       assert.equal(response.success, true, 'Operation should be successful');
218 |       assert.ok(response.message.includes('full project setup'), 'Should create full project setup by default');
219 | 
220 |       // Verify project files were created (indicates full project setup)
221 |       const packageJsonPath = join(testDir, 'package.json');
222 |       assert.ok(await fileExists(packageJsonPath), 'package.json should exist with default full project setup');
223 |     });
224 |   });
225 | 
226 |   describe('Cartridge-Only Generation', () => {
227 |     test('should create cartridge structure without project files when fullProjectSetup is false', async () => {
228 |       const testDir = createTempTestDir();
229 |       const cartridgeName = 'test_cartridge_only';
230 | 
231 |       const result = await client.callTool('generate_cartridge_structure', {
232 |         cartridgeName,
233 |         targetPath: testDir,
234 |         fullProjectSetup: false
235 |       });
236 | 
237 |       const response = parseToolResponse(result);
238 |       
239 |       // Verify response structure
240 |       assert.equal(response.success, true, 'Operation should be successful');
241 |       assert.ok(response.message.includes('Successfully created cartridge'), 'Message should indicate cartridge creation');
242 |       assert.ok(response.message.includes('existing project'), 'Message should indicate existing project context');
243 |       assert.ok(response.message.includes(cartridgeName), 'Message should include cartridge name');
244 | 
245 |       // Verify cartridge files were created
246 |       const cartridgeDir = join(testDir, 'cartridges', cartridgeName);
247 |       const expectedCartridgeFiles = [
248 |         join(cartridgeDir, '.project'),
249 |         join(cartridgeDir, 'cartridge', `${cartridgeName}.properties`)
250 |       ];
251 | 
252 |       for (const filePath of expectedCartridgeFiles) {
253 |         assert.ok(await fileExists(filePath), `Cartridge file ${filePath} should exist`);
254 |       }
255 | 
256 |       // Verify project files were NOT created
257 |       const projectFiles = ['package.json', 'dw.json', 'webpack.config.js'];
258 |       for (const file of projectFiles) {
259 |         const filePath = join(testDir, file);
260 |         assert.ok(!(await fileExists(filePath)), `Project file ${file} should NOT exist in cartridge-only mode`);
261 |       }
262 | 
263 |       // Verify cartridge directory structure exists
264 |       const expectedDirs = [
265 |         join(testDir, 'cartridges'),
266 |         cartridgeDir,
267 |         join(cartridgeDir, 'cartridge'),
268 |         join(cartridgeDir, 'cartridge', 'controllers'),
269 |         join(cartridgeDir, 'cartridge', 'models'),
270 |         join(cartridgeDir, 'cartridge', 'templates')
271 |       ];
272 | 
273 |       for (const dir of expectedDirs) {
274 |         assert.ok(await directoryExists(dir), `Cartridge directory ${dir} should exist`);
275 |       }
276 |     });
277 |   });
278 | 
279 |   describe('Target Path Handling', () => {
280 |     test('should create cartridge in current working directory when targetPath not specified', async () => {
281 |       const cartridgeName = 'test_cwd_cartridge';
282 | 
283 |       const result = await client.callTool('generate_cartridge_structure', {
284 |         cartridgeName
285 |         // targetPath not specified - should use current working directory
286 |       });
287 | 
288 |       const response = parseToolResponse(result);
289 |       
290 |       assert.equal(response.success, true, 'Operation should be successful');
291 |       assert.ok(Array.isArray(response.createdFiles), 'Should have createdFiles array');
292 |       assert.ok(response.createdFiles.length > 0, 'Should have created files');
293 | 
294 |       // Clean up files created in current directory
295 |       const cwd = process.cwd();
296 |       const createdPaths = [...response.createdFiles, ...response.createdDirectories];
297 |       
298 |       for (const path of createdPaths) {
299 |         try {
300 |           if (path.startsWith(cwd)) {
301 |             const relativePath = path.substring(cwd.length + 1);
302 |             if (relativePath.startsWith('cartridges/') || ['package.json', 'dw.json', 'webpack.config.js', '.eslintrc.json', '.stylelintrc.json', '.eslintignore', '.gitignore'].includes(relativePath)) {
303 |               await fs.rm(path, { recursive: true, force: true });
304 |             }
305 |           }
306 |         } catch {
307 |           // Ignore cleanup errors
308 |         }
309 |       }
310 |     });
311 | 
312 |     test('should handle absolute target paths correctly', async () => {
313 |       const testDir = createTempTestDir();
314 |       const cartridgeName = 'test_absolute_path';
315 | 
316 |       const result = await client.callTool('generate_cartridge_structure', {
317 |         cartridgeName,
318 |         targetPath: testDir,
319 |         fullProjectSetup: false
320 |       });
321 | 
322 |       const response = parseToolResponse(result);
323 |       
324 |       assert.equal(response.success, true, 'Operation should be successful');
325 |       
326 |       // Verify files were created in specified absolute path
327 |       const cartridgeDir = join(testDir, 'cartridges', cartridgeName);
328 |       assert.ok(await directoryExists(cartridgeDir), 'Cartridge should be created in specified absolute path');
329 |       
330 |       // Verify all created paths are under the specified target directory
331 |       for (const createdPath of response.createdFiles) {
332 |         assert.ok(createdPath.startsWith(testDir), `Created file ${createdPath} should be under target directory ${testDir}`);
333 |       }
334 |       
335 |       for (const createdDir of response.createdDirectories) {
336 |         assert.ok(createdDir.startsWith(testDir) || createdDir === testDir, `Created directory ${createdDir} should be under target directory ${testDir}`);
337 |       }
338 |     });
339 |   });
340 | 
341 |   describe('Cartridge Name Validation', () => {
342 |     test('should reject empty cartridge name', async () => {
343 |       const testDir = createTempTestDir();
344 | 
345 |       const result = await client.callTool('generate_cartridge_structure', {
346 |         cartridgeName: '',
347 |         targetPath: testDir
348 |       });
349 | 
350 |       assert.equal(result.isError, true, 'Should return error for empty cartridge name');
351 |       assert.ok(result.content[0].text.includes('Error'), 'Error message should be present');
352 |       assert.ok(result.content[0].text.includes('valid identifier'), 'Should mention valid identifier requirement');
353 |     });
354 | 
355 |     test('should reject missing cartridge name', async () => {
356 |       const testDir = createTempTestDir();
357 | 
358 |       const result = await client.callTool('generate_cartridge_structure', {
359 |         targetPath: testDir
360 |         // cartridgeName missing
361 |       });
362 | 
363 |       assert.equal(result.isError, true, 'Should return error for missing cartridge name');
364 |       assert.ok(result.content[0].text.includes('Error'), 'Error message should be present');
365 |     });
366 | 
367 |     test('should accept valid cartridge names with different formats', async () => {
368 |       const testDir = createTempTestDir();
369 |       const validNames = [
370 |         'simple_cartridge',
371 |         'plugin-example',
372 |         'my_plugin_123',
373 |         'cart123',
374 |         'a_b_c'
375 |       ];
376 | 
377 |       for (const cartridgeName of validNames) {
378 |         const subTestDir = join(testDir, cartridgeName + '_test');
379 |         testDirectories.push(subTestDir);
380 | 
381 |         const result = await client.callTool('generate_cartridge_structure', {
382 |           cartridgeName,
383 |           targetPath: subTestDir,
384 |           fullProjectSetup: false
385 |         });
386 | 
387 |         const response = parseToolResponse(result);
388 |         assert.equal(response.success, true, `Cartridge name "${cartridgeName}" should be valid`);
389 |         assert.ok(response.message.includes(cartridgeName), `Message should include cartridge name "${cartridgeName}"`);
390 |       }
391 |     });
392 |   });
393 | 
394 |   describe('File System Integration', () => {
395 |     test('should handle directory creation permissions correctly', async () => {
396 |       const testDir = createTempTestDir();
397 |       const cartridgeName = 'test_permissions';
398 | 
399 |       const result = await client.callTool('generate_cartridge_structure', {
400 |         cartridgeName,
401 |         targetPath: testDir,
402 |         fullProjectSetup: false
403 |       });
404 | 
405 |       const response = parseToolResponse(result);
406 |       assert.equal(response.success, true, 'Should handle directory creation successfully');
407 | 
408 |       // Verify created directories have appropriate permissions (readable and writable)
409 |       const cartridgeDir = join(testDir, 'cartridges', cartridgeName);
410 |       const stat = await fs.stat(cartridgeDir);
411 |       
412 |       // Check that directory is readable and writable by owner
413 |       const mode = stat.mode;
414 |       const ownerRead = (mode & 0o400) !== 0;
415 |       const ownerWrite = (mode & 0o200) !== 0;
416 |       const ownerExecute = (mode & 0o100) !== 0;
417 |       
418 |       assert.ok(ownerRead, 'Directory should be readable by owner');
419 |       assert.ok(ownerWrite, 'Directory should be writable by owner');
420 |       assert.ok(ownerExecute, 'Directory should be executable by owner');
421 |     });
422 | 
423 |     test('should create files with correct content structure', async () => {
424 |       const testDir = createTempTestDir();
425 |       const cartridgeName = 'test_file_content';
426 | 
427 |       const result = await client.callTool('generate_cartridge_structure', {
428 |         cartridgeName,
429 |         targetPath: testDir,
430 |         fullProjectSetup: true
431 |       });
432 | 
433 |       const response = parseToolResponse(result);
434 |       assert.equal(response.success, true, 'Should create files successfully');
435 | 
436 |       // Check package.json structure
437 |       const packageJsonPath = join(testDir, 'package.json');
438 |       const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
439 |       const packageJson = JSON.parse(packageJsonContent);
440 |       
441 |       assert.ok(packageJson.name, 'package.json should have name field');
442 |       assert.ok(packageJson.version, 'package.json should have version field');
443 |       assert.ok(packageJson.scripts, 'package.json should have scripts');
444 |       assert.ok(packageJson.devDependencies, 'package.json should have devDependencies');
445 | 
446 |       // Check cartridge properties file
447 |       const propertiesPath = join(testDir, 'cartridges', cartridgeName, 'cartridge', `${cartridgeName}.properties`);
448 |       const propertiesContent = await fs.readFile(propertiesPath, 'utf-8');
449 |       
450 |       assert.ok(propertiesContent.includes('demandware.cartridges'), 'Properties file should contain demandware.cartridges');
451 |       assert.ok(propertiesContent.includes(cartridgeName), 'Properties file should reference cartridge name');
452 | 
453 |       // Check .project file (Eclipse project file)
454 |       const projectPath = join(testDir, 'cartridges', cartridgeName, '.project');
455 |       const projectContent = await fs.readFile(projectPath, 'utf-8');
456 |       
457 |       assert.ok(projectContent.includes('<name>'), '.project file should contain project name tag');
458 |       assert.ok(projectContent.includes(cartridgeName), '.project file should reference cartridge name');
459 |     });
460 | 
461 |     test('should handle nested directory creation correctly', async () => {
462 |       const testDir = createTempTestDir();
463 |       const cartridgeName = 'test_nested_dirs';
464 | 
465 |       const result = await client.callTool('generate_cartridge_structure', {
466 |         cartridgeName,
467 |         targetPath: testDir,
468 |         fullProjectSetup: false
469 |       });
470 | 
471 |       const response = parseToolResponse(result);
472 |       assert.equal(response.success, true, 'Should create nested directories successfully');
473 | 
474 |       // Verify deeply nested directories were created
475 |       const deepPath = join(testDir, 'cartridges', cartridgeName, 'cartridge', 'client', 'default', 'scss');
476 |       assert.ok(await directoryExists(deepPath), 'Deep nested directory structure should be created');
477 | 
478 |       // Verify specific key directories in the structure exist
479 |       const keyDirectories = [
480 |         join(testDir, 'cartridges', cartridgeName),
481 |         join(testDir, 'cartridges', cartridgeName, 'cartridge'),
482 |         join(testDir, 'cartridges', cartridgeName, 'cartridge', 'client'),
483 |         join(testDir, 'cartridges', cartridgeName, 'cartridge', 'client', 'default'),
484 |         join(testDir, 'cartridges', cartridgeName, 'cartridge', 'client', 'default', 'scss')
485 |       ];
486 |       
487 |       for (const dir of keyDirectories) {
488 |         assert.ok(await directoryExists(dir), `Key directory ${dir} should exist`);
489 |       }
490 |     });
491 |   });
492 | 
493 |   describe('Edge Cases and Error Handling', () => {
494 |     test('should handle special characters in target path', async () => {
495 |       const specialDir = join(tmpdir(), 'mcp-test-special chars & symbols');
496 |       testDirectories.push(specialDir);
497 |       const cartridgeName = 'test_special_path';
498 | 
499 |       const result = await client.callTool('generate_cartridge_structure', {
500 |         cartridgeName,
501 |         targetPath: specialDir,
502 |         fullProjectSetup: false
503 |       });
504 | 
505 |       const response = parseToolResponse(result);
506 |       assert.equal(response.success, true, 'Should handle special characters in path');
507 |       
508 |       const cartridgeDir = join(specialDir, 'cartridges', cartridgeName);
509 |       assert.ok(await directoryExists(cartridgeDir), 'Should create cartridge in path with special characters');
510 |     });
511 | 
512 |     test('should report created files and directories accurately', async () => {
513 |       const testDir = createTempTestDir();
514 |       const cartridgeName = 'test_file_reporting';
515 | 
516 |       const result = await client.callTool('generate_cartridge_structure', {
517 |         cartridgeName,
518 |         targetPath: testDir,
519 |         fullProjectSetup: true
520 |       });
521 | 
522 |       const response = parseToolResponse(result);
523 |       assert.equal(response.success, true, 'Operation should be successful');
524 | 
525 |       // Verify all reported files actually exist
526 |       for (const filePath of response.createdFiles) {
527 |         assert.ok(await fileExists(filePath), `Reported file ${filePath} should actually exist`);
528 |       }
529 | 
530 |       // Verify all reported directories actually exist
531 |       for (const dirPath of response.createdDirectories) {
532 |         assert.ok(await directoryExists(dirPath), `Reported directory ${dirPath} should actually exist`);
533 |       }
534 | 
535 |       // Verify files list is comprehensive - check for essential files
536 |       const essentialFiles = ['package.json', 'dw.json', '.gitignore'];
537 |       for (const file of essentialFiles) {
538 |         assert.ok(response.createdFiles.some(f => f.endsWith(file)), `Essential file ${file} should be in createdFiles list`);
539 |       }
540 | 
541 |       // Verify no duplicate entries in created files/directories
542 |       const uniqueFiles = new Set(response.createdFiles);
543 |       const uniqueDirs = new Set(response.createdDirectories);
544 |       
545 |       assert.equal(response.createdFiles.length, uniqueFiles.size, 'createdFiles should not contain duplicates');
546 |       assert.equal(response.createdDirectories.length, uniqueDirs.size, 'createdDirectories should not contain duplicates');
547 |     });
548 | 
549 |     test('should handle very long cartridge names appropriately', async () => {
550 |       const testDir = createTempTestDir();
551 |       const longCartridgeName = 'very_long_cartridge_name_that_exceeds_normal_limits_but_should_still_work_properly_for_testing_purposes';
552 | 
553 |       const result = await client.callTool('generate_cartridge_structure', {
554 |         cartridgeName: longCartridgeName,
555 |         targetPath: testDir,
556 |         fullProjectSetup: false
557 |       });
558 | 
559 |       // The tool should either succeed or provide a clear error message about name length
560 |       if (result.isError) {
561 |         assert.ok(result.content[0].text.includes('Error'), 'Should provide clear error for long names');
562 |       } else {
563 |         const response = parseToolResponse(result);
564 |         assert.equal(response.success, true, 'Should handle long cartridge names if accepted');
565 |         
566 |         const cartridgeDir = join(testDir, 'cartridges', longCartridgeName);
567 |         assert.ok(await directoryExists(cartridgeDir), 'Should create directory with long name');
568 |       }
569 |     });
570 |   });
571 | 
572 |   describe('Response Format Consistency', () => {
573 |     test('should always return consistent JSON response structure', async () => {
574 |       const testDir = createTempTestDir();
575 |       const cartridgeName = 'test_response_format';
576 | 
577 |       const result = await client.callTool('generate_cartridge_structure', {
578 |         cartridgeName,
579 |         targetPath: testDir,
580 |         fullProjectSetup: false
581 |       });
582 | 
583 |       const response = parseToolResponse(result);
584 |       
585 |       // Verify required response fields
586 |       assert.ok(typeof response.success === 'boolean', 'Response should have boolean success field');
587 |       assert.ok(typeof response.message === 'string', 'Response should have string message field');
588 |       assert.ok(Array.isArray(response.createdFiles), 'Response should have createdFiles array');
589 |       assert.ok(Array.isArray(response.createdDirectories), 'Response should have createdDirectories array');
590 |       assert.ok(Array.isArray(response.skippedFiles), 'Response should have skippedFiles array');
591 | 
592 |       // Verify field content types
593 |       for (const file of response.createdFiles) {
594 |         assert.ok(typeof file === 'string', 'All createdFiles entries should be strings');
595 |         assert.ok(file.length > 0, 'All createdFiles entries should be non-empty');
596 |       }
597 | 
598 |       for (const dir of response.createdDirectories) {
599 |         assert.ok(typeof dir === 'string', 'All createdDirectories entries should be strings');
600 |         assert.ok(dir.length > 0, 'All createdDirectories entries should be non-empty');
601 |       }
602 | 
603 |       for (const skipped of response.skippedFiles) {
604 |         assert.ok(typeof skipped === 'string', 'All skippedFiles entries should be strings');
605 |       }
606 |     });
607 | 
608 |     test('should provide meaningful success messages', async () => {
609 |       const testDir = createTempTestDir();
610 |       const cartridgeName = 'test_success_message';
611 | 
612 |       // Test full project setup message
613 |       const fullResult = await client.callTool('generate_cartridge_structure', {
614 |         cartridgeName: cartridgeName + '_full',
615 |         targetPath: testDir + '_full',
616 |         fullProjectSetup: true
617 |       });
618 | 
619 |       const fullResponse = parseToolResponse(fullResult);
620 |       assert.ok(fullResponse.message.includes('full project setup'), 'Full project message should mention full project setup');
621 |       assert.ok(fullResponse.message.includes(cartridgeName + '_full'), 'Message should include cartridge name');
622 | 
623 |       // Test cartridge-only message
624 |       const cartridgeResult = await client.callTool('generate_cartridge_structure', {
625 |         cartridgeName: cartridgeName + '_only',
626 |         targetPath: testDir + '_only',
627 |         fullProjectSetup: false
628 |       });
629 | 
630 |       const cartridgeResponse = parseToolResponse(cartridgeResult);
631 |       assert.ok(cartridgeResponse.message.includes('existing project'), 'Cartridge-only message should mention existing project');
632 |       assert.ok(cartridgeResponse.message.includes(cartridgeName + '_only'), 'Message should include cartridge name');
633 | 
634 |       // Add these additional test directories to cleanup list
635 |       testDirectories.push(testDir + '_full', testDir + '_only');
636 |     });
637 |   });
638 | });
639 | 
```

--------------------------------------------------------------------------------
/tests/mcp/node/search-system-object-attribute-definitions.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { test, describe, before, after, beforeEach } from 'node:test';
  2 | import { strict as assert } from 'node:assert';
  3 | import { connect } from 'mcp-aegis';
  4 | 
  5 | describe('search_system_object_attribute_definitions - Full Mode Programmatic Tests', () => {
  6 |   let client;
  7 | 
  8 |   before(async () => {
  9 |     client = await connect('./aegis.config.with-dw.json');
 10 |   });
 11 | 
 12 |   after(async () => {
 13 |     if (client?.connected) {
 14 |       await client.disconnect();
 15 |     }
 16 |   });
 17 | 
 18 |   beforeEach(() => {
 19 |     // Critical: Clear stderr to prevent test interference
 20 |     client.clearStderr();
 21 |   });
 22 | 
 23 |   // Helper functions for common validations
 24 |   function assertValidMCPResponse(result) {
 25 |     assert.ok(result.content, 'Should have content');
 26 |     assert.ok(Array.isArray(result.content), 'Content should be array');
 27 |     assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
 28 |   }
 29 | 
 30 |   function assertValidSearchResponse(result) {
 31 |     assertValidMCPResponse(result);
 32 |     assert.equal(result.isError, false, 'Should not be error');
 33 |     
 34 |     const responseText = result.content[0].text;
 35 |     const responseData = JSON.parse(responseText);
 36 |     
 37 |     assert.ok(responseData.hits !== undefined, 'Should have hits array');
 38 |     assert.ok(Array.isArray(responseData.hits), 'Hits should be array');
 39 |     assert.ok(typeof responseData.total === 'number', 'Should have total count');
 40 |     
 41 |     return responseData;
 42 |   }
 43 | 
 44 |   function assertValidAttributeDefinition(attribute) {
 45 |     assert.ok(typeof attribute.id === 'string', 'Attribute should have id');
 46 |     assert.ok(typeof attribute._type === 'string', 'Should have _type');
 47 |     assert.equal(attribute._type, 'object_attribute_definition', 'Should be object_attribute_definition type');
 48 |     assert.ok(typeof attribute._resource_state === 'string', 'Should have _resource_state');
 49 |     assert.ok(typeof attribute.link === 'string', 'Should have link');
 50 |     
 51 |     // Note: OCAPI search results only return basic info (id, link, _type, _resource_state)
 52 |     // Detailed fields like display_name, value_type, mandatory are only available 
 53 |     // when fetching individual attributes via the link
 54 |   }
 55 | 
 56 |   describe('Protocol Compliance and Tool Availability', () => {
 57 |     test('should be connected to MCP server', async () => {
 58 |       assert.ok(client.connected, 'Client should be connected');
 59 |     });
 60 | 
 61 |     test('should have search_system_object_attribute_definitions tool available', async () => {
 62 |       const tools = await client.listTools();
 63 |       const toolNames = tools.map(tool => tool.name);
 64 |       assert.ok(toolNames.includes('search_system_object_attribute_definitions'), 
 65 |         'Tool should be available in full mode');
 66 |     });
 67 | 
 68 |     test('should have proper tool schema definition', async () => {
 69 |       const tools = await client.listTools();
 70 |       const tool = tools.find(t => t.name === 'search_system_object_attribute_definitions');
 71 |       
 72 |       assert.ok(tool, 'Tool should exist');
 73 |       assert.ok(tool.description, 'Tool should have description');
 74 |       assert.ok(tool.inputSchema, 'Tool should have input schema');
 75 |       assert.equal(tool.inputSchema.type, 'object', 'Schema should be object type');
 76 |       
 77 |       // Validate required parameters
 78 |       const required = tool.inputSchema.required || [];
 79 |       assert.ok(required.includes('objectType'), 'objectType should be required');
 80 |       assert.ok(required.includes('searchRequest'), 'searchRequest should be required');
 81 |     });
 82 |   });
 83 | 
 84 |   describe('Dynamic Test Case Generation', () => {
 85 |     test('should generate and validate test cases for known object types', async () => {
 86 |       const knownObjectTypes = ['Product', 'Customer', 'Order', 'Category', 'Site'];
 87 |       
 88 |       for (const objectType of knownObjectTypes) {
 89 |         // Test with match_all_query for each object type
 90 |         const result = await client.callTool('search_system_object_attribute_definitions', {
 91 |           objectType: objectType,
 92 |           searchRequest: {
 93 |             query: { match_all_query: {} },
 94 |             count: 5
 95 |           }
 96 |         });
 97 |         
 98 |         const responseData = assertValidSearchResponse(result);
 99 |         
100 |         // Validate each attribute in the response
101 |         responseData.hits.forEach(attribute => {
102 |           assertValidAttributeDefinition(attribute);
103 |           
104 |           // Business logic validation - verify attribute IDs are reasonable
105 |           assert.ok(attribute.id.length > 0, 'Attribute ID should not be empty');
106 |           assert.ok(!/\s/.test(attribute.id), 'Attribute ID should not contain spaces');
107 |           
108 |           // Note: Detailed validation of Product-specific fields would require
109 |           // fetching individual attributes via their links, which is beyond
110 |           // the scope of search functionality testing
111 |         });
112 |         
113 |         assert.ok(responseData.total >= 0, `${objectType} should have non-negative attribute count`);
114 |       }
115 |     });
116 | 
117 |     test('should validate query type combinations dynamically', async () => {
118 |       const queryTypes = [
119 |         { match_all_query: {} },
120 |         { 
121 |           text_query: {
122 |             fields: ['id', 'display_name', 'description'],
123 |             search_phrase: 'name'
124 |           }
125 |         },
126 |         {
127 |           term_query: {
128 |             fields: ['value_type'],
129 |             operator: 'is',
130 |             values: ['string']
131 |           }
132 |         }
133 |       ];
134 |       
135 |       for (const query of queryTypes) {
136 |         const result = await client.callTool('search_system_object_attribute_definitions', {
137 |           objectType: 'Product',
138 |           searchRequest: { query, count: 3 }
139 |         });
140 |         
141 |         const responseData = assertValidSearchResponse(result);
142 |         
143 |         // Validate that query type affects results appropriately
144 |         if (query.text_query && query.text_query.search_phrase === 'name') {
145 |           // Should return attributes related to 'name'
146 |           // Note: Not asserting as it depends on actual data and fuzzy matching
147 |           responseData.hits.some(attr => 
148 |             attr.id.toLowerCase().includes('name') ||
149 |             Object.values(attr.display_name).some(name => 
150 |               name.toLowerCase().includes('name')
151 |             )
152 |           );
153 |         }
154 |         
155 |         if (query.term_query && query.term_query.values.includes('string')) {
156 |           // Note: OCAPI search results don't include value_type in basic response
157 |           // This would require fetching individual attributes to validate
158 |           // For search testing, we focus on verifying the search request/response structure
159 |           assert.ok(responseData.hits.length >= 0, 'Should return non-negative results');
160 |         }
161 |       }
162 |     });
163 |   });
164 | 
165 |   describe('Complex Query Combinations', () => {
166 |     test('should handle bool_query with multiple clauses', async () => {
167 |       const complexQuery = {
168 |         bool_query: {
169 |           must: [
170 |             {
171 |               term_query: {
172 |                 fields: ['mandatory'],
173 |                 operator: 'is',
174 |                 values: ['true']
175 |               }
176 |             },
177 |             {
178 |               term_query: {
179 |                 fields: ['searchable'],
180 |                 operator: 'is', 
181 |                 values: ['true']
182 |               }
183 |             }
184 |           ],
185 |           should: [
186 |             {
187 |               text_query: {
188 |                 fields: ['id'],
189 |                 search_phrase: 'custom'
190 |               }
191 |             }
192 |           ],
193 |           must_not: [
194 |             {
195 |               term_query: {
196 |                 fields: ['system'],
197 |                 operator: 'is',
198 |                 values: ['true']
199 |               }
200 |             }
201 |           ]
202 |         }
203 |       };
204 | 
205 |       const result = await client.callTool('search_system_object_attribute_definitions', {
206 |         objectType: 'Product',
207 |         searchRequest: {
208 |           query: complexQuery,
209 |           count: 10
210 |         }
211 |       });
212 | 
213 |       const responseData = assertValidSearchResponse(result);
214 |       
215 |       // Validate that results match the complex query criteria
216 |       responseData.hits.forEach(attr => {
217 |         // Note: OCAPI search results only return basic attribute info (id, link, _type)
218 |         // Detailed validation of mandatory, searchable, system flags would require
219 |         // fetching individual attributes via their links. For search API testing,
220 |         // we focus on validating the search request/response structure and pagination.
221 |         assertValidAttributeDefinition(attr);
222 |         assert.ok(attr.id.length > 0, 'Attribute should have valid ID');
223 |       });
224 |     });
225 | 
226 |     test('should handle nested bool_query structures', async () => {
227 |       const nestedQuery = {
228 |         bool_query: {
229 |           must: [
230 |             {
231 |               bool_query: {
232 |                 should: [
233 |                   {
234 |                     term_query: {
235 |                       fields: ['value_type'],
236 |                       operator: 'one_of',
237 |                       values: ['string', 'text']
238 |                     }
239 |                   },
240 |                   {
241 |                     term_query: {
242 |                       fields: ['value_type'],
243 |                       operator: 'is',
244 |                       values: ['enum-of-string']
245 |                     }
246 |                   }
247 |                 ]
248 |               }
249 |             }
250 |           ]
251 |         }
252 |       };
253 | 
254 |       const result = await client.callTool('search_system_object_attribute_definitions', {
255 |         objectType: 'Customer',
256 |         searchRequest: {
257 |           query: nestedQuery,
258 |           count: 5,
259 |           sorts: [
260 |             { field: 'id', sort_order: 'asc' }
261 |           ]
262 |         }
263 |       });
264 | 
265 |       const responseData = assertValidSearchResponse(result);
266 |       
267 |       // Validate nested query results
268 |       responseData.hits.forEach(attr => {
269 |         // Note: OCAPI search results don't include value_type in basic response
270 |         // For search API testing, we validate the structure and response format
271 |         assertValidAttributeDefinition(attr);
272 |         assert.ok(attr.id.length > 0, 'Should have valid attribute ID');
273 |       });
274 | 
275 |       // Validate sorting is applied
276 |       if (responseData.hits.length > 1) {
277 |         for (let i = 1; i < responseData.hits.length; i++) {
278 |           assert.ok(responseData.hits[i].id >= responseData.hits[i-1].id, 
279 |             'Results should be sorted by id in ascending order');
280 |         }
281 |       }
282 |     });
283 |   });
284 | 
285 |   describe('Pagination and Large Dataset Handling', () => {
286 |     test('should handle pagination correctly across multiple requests', async () => {
287 |       const pageSize = 5;
288 |       const maxPages = 3;
289 |       const allResults = [];
290 |       let totalCount = 0;
291 | 
292 |       for (let page = 0; page < maxPages; page++) {
293 |         const result = await client.callTool('search_system_object_attribute_definitions', {
294 |           objectType: 'Product',
295 |           searchRequest: {
296 |             query: { match_all_query: {} },
297 |             count: pageSize,
298 |             start: page * pageSize,
299 |             sorts: [{ field: 'id', sort_order: 'asc' }]
300 |           }
301 |         });
302 | 
303 |         const responseData = assertValidSearchResponse(result);
304 |         
305 |         if (page === 0) {
306 |           totalCount = responseData.total;
307 |         } else {
308 |           assert.equal(responseData.total, totalCount, 
309 |             'Total count should be consistent across pages');
310 |         }
311 | 
312 |         // Validate page results
313 |         assert.ok(responseData.hits.length <= pageSize, 
314 |           'Page should not exceed requested size');
315 |         
316 |         // Check for duplicates across pages
317 |         responseData.hits.forEach(attr => {
318 |           const isDuplicate = allResults.some(existing => existing.id === attr.id);
319 |           assert.equal(isDuplicate, false, `Attribute ${attr.id} should not appear in multiple pages`);
320 |           allResults.push(attr);
321 |         });
322 | 
323 |         // Break if we've reached the end
324 |         if (responseData.hits.length < pageSize || 
325 |             allResults.length >= responseData.total) {
326 |           break;
327 |         }
328 |       }
329 | 
330 |       // Validate overall pagination consistency
331 |       assert.ok(allResults.length > 0, 'Should have retrieved some results');
332 |       
333 |       // Validate sorting consistency across pages
334 |       if (allResults.length > 1) {
335 |         for (let i = 1; i < allResults.length; i++) {
336 |           assert.ok(allResults[i].id >= allResults[i-1].id, 
337 |             'Results should maintain sort order across pages');
338 |         }
339 |       }
340 |     });
341 | 
342 |     test('should handle large count requests appropriately', async () => {
343 |       const largeCount = 200;
344 |       
345 |       const result = await client.callTool('search_system_object_attribute_definitions', {
346 |         objectType: 'Product',
347 |         searchRequest: {
348 |           query: { match_all_query: {} },
349 |           count: largeCount
350 |         }
351 |       });
352 | 
353 |       const responseData = assertValidSearchResponse(result);
354 |       
355 |       // Validate that server handles large requests (may return fewer than requested)
356 |       assert.ok(responseData.hits.length <= largeCount, 
357 |         'Should not return more than requested');
358 |       assert.ok(responseData.hits.length >= 0, 
359 |         'Should return non-negative number of results');
360 |       
361 |       // Validate that all returned results are valid
362 |       responseData.hits.forEach((attr, index) => {
363 |         try {
364 |           assertValidAttributeDefinition(attr);
365 |         } catch (error) {
366 |           throw new Error(`Invalid attribute at index ${index}: ${error.message}`);
367 |         }
368 |       });
369 |     });
370 |   });
371 | 
372 |   describe('Cross-Field Validation and Business Logic', () => {
373 |     test('should validate attribute relationships and constraints', async () => {
374 |       const result = await client.callTool('search_system_object_attribute_definitions', {
375 |         objectType: 'Product',
376 |         searchRequest: {
377 |           query: { match_all_query: {} },
378 |           count: 50
379 |         }
380 |       });
381 | 
382 |       const responseData = assertValidSearchResponse(result);
383 |       
384 |       // Business logic validations - based on OCAPI search response structure
385 |       responseData.hits.forEach(attr => {
386 |         assertValidAttributeDefinition(attr);
387 |         
388 |         // Validate basic structure returned by OCAPI search
389 |         assert.ok(attr.id.length > 0, 'Attribute ID should not be empty');
390 |         assert.ok(attr.link.includes('/attribute_definitions/'), 'Link should be valid');
391 |         assert.ok(attr._resource_state.length > 0, 'Should have resource state');
392 |         
393 |         // Note: Detailed attribute properties (value_type, mandatory, searchable, system, 
394 |         // display_name) are not included in search results. They would need to be 
395 |         // fetched individually via the attribute's link for detailed validation.
396 |       });
397 |     });
398 | 
399 |     test('should validate search result consistency across different sort orders', async () => {
400 |       const sortFields = ['id']; // Only test 'id' since it's available in OCAPI search results
401 |       const sortOrders = ['asc', 'desc'];
402 |       const resultSets = new Map();
403 | 
404 |       for (const field of sortFields) {
405 |         for (const order of sortOrders) {
406 |           const result = await client.callTool('search_system_object_attribute_definitions', {
407 |             objectType: 'Customer',
408 |             searchRequest: {
409 |               query: { match_all_query: {} },
410 |               count: 10,
411 |               sorts: [{ field, sort_order: order }]
412 |             }
413 |           });
414 | 
415 |           const responseData = assertValidSearchResponse(result);
416 |           const key = `${field}_${order}`;
417 |           resultSets.set(key, responseData);
418 | 
419 |           // Validate sort order is applied - OCAPI search results are sorted by ID
420 |           if (responseData.hits.length > 1) {
421 |             for (let i = 1; i < responseData.hits.length; i++) {
422 |               const current = responseData.hits[i];
423 |               const previous = responseData.hits[i-1];
424 |               
425 |               // For OCAPI search results, we can only sort by 'id' reliably
426 |               // since other detailed fields are not included in search response
427 |               if (field === 'id') {
428 |                 const currentValue = current.id;
429 |                 const previousValue = previous.id;
430 | 
431 |                 if (order === 'asc') {
432 |                   assert.ok(currentValue >= previousValue, 
433 |                     `${field} should be in ascending order: ${previousValue} <= ${currentValue}`);
434 |                 } else {
435 |                   assert.ok(currentValue <= previousValue, 
436 |                     `${field} should be in descending order: ${previousValue} >= ${currentValue}`);
437 |                 }
438 |               }
439 |               // Note: Other sort fields (display_name, value_type) are not available 
440 |               // in OCAPI search results, so we skip detailed validation for those
441 |             }
442 |           }
443 |         }
444 |       }
445 | 
446 |       // Validate that different sort orders return same total count
447 |       const totalCounts = Array.from(resultSets.values()).map(data => data.total);
448 |       const uniqueTotals = [...new Set(totalCounts)];
449 |       assert.equal(uniqueTotals.length, 1, 
450 |         'All sort variations should return same total count');
451 |     });
452 |   });
453 | 
454 |   describe('Error Recovery and Edge Cases', () => {
455 |     test('should handle invalid object types gracefully', async () => {
456 |       // Test non-existent object types (return empty results)
457 |       const invalidObjectTypes = ['InvalidObject', 'NonExistent'];
458 |       
459 |       for (const objectType of invalidObjectTypes) {
460 |         const result = await client.callTool('search_system_object_attribute_definitions', {
461 |           objectType: objectType,
462 |           searchRequest: {
463 |             query: { match_all_query: {} },
464 |             count: 5
465 |           }
466 |         });
467 | 
468 |         // OCAPI returns successful response with empty results for invalid object types
469 |         const responseData = assertValidSearchResponse(result);
470 |         assert.equal(responseData.total, 0, 
471 |           `Should return 0 results for invalid object type: ${objectType}`);
472 |         assert.equal(responseData.hits.length, 0, 
473 |           `Should return empty hits array for invalid object type: ${objectType}`);
474 |       }
475 | 
476 |       // Test empty object type (returns validation error)
477 |       const emptyResult = await client.callTool('search_system_object_attribute_definitions', {
478 |         objectType: '',
479 |         searchRequest: {
480 |           query: { match_all_query: {} },
481 |           count: 5
482 |         }
483 |       });
484 | 
485 |       assert.equal(emptyResult.isError, true, 'Should return error for empty object type');
486 |       const errorText = emptyResult.content[0].text;
487 |       assert.ok(errorText.includes('objectType must be a non-empty string'), 
488 |         'Error message should indicate objectType validation issue');
489 |     });
490 | 
491 |     test('should handle malformed queries gracefully', async () => {
492 |       const malformedQueries = [
493 |         { invalid_query_type: {} },
494 |         { text_query: { missing_required_fields: true } },
495 |         { term_query: { fields: [], operator: 'invalid', values: [] } },
496 |         { bool_query: { invalid_clause: [] } }
497 |       ];
498 | 
499 |       for (const query of malformedQueries) {
500 |         const result = await client.callTool('search_system_object_attribute_definitions', {
501 |           objectType: 'Product',
502 |           searchRequest: { query, count: 5 }
503 |         });
504 | 
505 |         // Should handle malformed queries gracefully
506 |         if (result.isError) {
507 |           const errorText = result.content[0].text;
508 |           assert.ok(errorText.length > 0, 'Error message should not be empty');
509 |         } else {
510 |           // If not an error, should return valid response structure
511 |           assertValidSearchResponse(result);
512 |         }
513 |       }
514 |     });
515 | 
516 |     test('should recover from network issues and continue working', async () => {
517 |       // Test normal operation
518 |       const normalResult = await client.callTool('search_system_object_attribute_definitions', {
519 |         objectType: 'Product',
520 |         searchRequest: {
521 |           query: { match_all_query: {} },
522 |           count: 3
523 |         }
524 |       });
525 | 
526 |       assertValidSearchResponse(normalResult);
527 |       
528 |       // Test with missing query field - OCAPI provides default behavior (match_all_query)
529 |       const missingQueryResult = await client.callTool('search_system_object_attribute_definitions', {
530 |         objectType: 'Product',
531 |         searchRequest: {
532 |           // Missing query field - OCAPI defaults to match_all_query
533 |           count: 3
534 |         }
535 |       });
536 | 
537 |       // OCAPI provides graceful defaults rather than errors
538 |       const missingQueryData = assertValidSearchResponse(missingQueryResult);
539 |       assert.ok(missingQueryData.total >= 0, 'Should return valid results with default query');
540 |       
541 |       // Test that service continues to work normally after edge case
542 |       const recoveryResult = await client.callTool('search_system_object_attribute_definitions', {
543 |         objectType: 'Customer',
544 |         searchRequest: {
545 |           query: { match_all_query: {} },
546 |           count: 3
547 |         }
548 |       });
549 | 
550 |       assertValidSearchResponse(recoveryResult);
551 |       assert.ok(recoveryResult.content[0].text.length > 0, 
552 |         'Service should continue working normally');
553 |     });
554 |   });
555 | 
556 |   describe('Multi-Step Workflow Validation', () => {
557 |     test('should support attribute discovery and detailed analysis workflow', async () => {
558 |       // Step 1: Discover all attributes for an object type
559 |       const discoveryResult = await client.callTool('search_system_object_attribute_definitions', {
560 |         objectType: 'Product',
561 |         searchRequest: {
562 |           query: { match_all_query: {} },
563 |           count: 10,
564 |           select: '(**)'
565 |         }
566 |       });
567 | 
568 |       const discoveryData = assertValidSearchResponse(discoveryResult);
569 |       assert.ok(discoveryData.hits.length > 0, 'Should discover some attributes');
570 | 
571 |       // Step 2: Analyze specific attribute types found in step 1
572 |       const valueTypes = [...new Set(discoveryData.hits.map(attr => attr.value_type))];
573 |       
574 |       for (const valueType of valueTypes.slice(0, 3)) { // Test first 3 types
575 |         const typeAnalysisResult = await client.callTool('search_system_object_attribute_definitions', {
576 |           objectType: 'Product',
577 |           searchRequest: {
578 |             query: {
579 |               term_query: {
580 |                 fields: ['value_type'],
581 |                 operator: 'is',
582 |                 values: [valueType]
583 |               }
584 |             },
585 |             count: 5
586 |           }
587 |         });
588 | 
589 |         const typeData = assertValidSearchResponse(typeAnalysisResult);
590 |         
591 |         // Validate that all results have the expected value type
592 |         typeData.hits.forEach(attr => {
593 |           // Note: OCAPI search results don't include value_type field
594 |           // The query filtering happens server-side, so we validate structure instead
595 |           assertValidAttributeDefinition(attr);
596 |           assert.ok(attr.id.length > 0, `Should have valid attribute ID for type search: ${valueType}`);
597 |         });
598 |       }
599 | 
600 |       // Step 3: Analyze attribute relationships (mandatory vs optional)
601 |       const mandatoryResult = await client.callTool('search_system_object_attribute_definitions', {
602 |         objectType: 'Product',
603 |         searchRequest: {
604 |           query: {
605 |             term_query: {
606 |               fields: ['mandatory'],
607 |               operator: 'is',
608 |               values: ['true']
609 |             }
610 |           },
611 |           count: 10
612 |         }
613 |       });
614 | 
615 |       const mandatoryData = assertValidSearchResponse(mandatoryResult);
616 |       
617 |       mandatoryData.hits.forEach(attr => {
618 |         // Note: OCAPI search results don't include mandatory field in basic response
619 |         // The query filtering happens server-side, so we validate the response structure
620 |         assertValidAttributeDefinition(attr);
621 |         assert.ok(attr.id.length > 0, 'Should have valid attribute ID');
622 |       });
623 | 
624 |       // Validate workflow consistency
625 |       const totalMandatory = mandatoryData.total;
626 |       assert.ok(totalMandatory >= 0, 'Should have non-negative mandatory attribute count');
627 |       assert.ok(totalMandatory <= discoveryData.total, 
628 |         'Mandatory attributes should be subset of all attributes');
629 |     });
630 | 
631 |     test('should support complex search refinement workflow', async () => {
632 |       // Step 1: Broad search
633 |       const broadResult = await client.callTool('search_system_object_attribute_definitions', {
634 |         objectType: 'Customer',
635 |         searchRequest: {
636 |           query: {
637 |             text_query: {
638 |               fields: ['id', 'display_name'],
639 |               search_phrase: 'address'
640 |             }
641 |           },
642 |           count: 20
643 |         }
644 |       });
645 | 
646 |       const broadData = assertValidSearchResponse(broadResult);
647 |       
648 |       // Step 2: Refine to only searchable address-related attributes
649 |       const refinedResult = await client.callTool('search_system_object_attribute_definitions', {
650 |         objectType: 'Customer',
651 |         searchRequest: {
652 |           query: {
653 |             bool_query: {
654 |               must: [
655 |                 {
656 |                   text_query: {
657 |                     fields: ['id', 'display_name'],
658 |                     search_phrase: 'address'
659 |                   }
660 |                 },
661 |                 {
662 |                   term_query: {
663 |                     fields: ['searchable'],
664 |                     operator: 'is',
665 |                     values: ['true']
666 |                   }
667 |                 }
668 |               ]
669 |             }
670 |           },
671 |           count: 20
672 |         }
673 |       });
674 | 
675 |       const refinedData = assertValidSearchResponse(refinedResult);
676 |       
677 |       // Validate refinement logic
678 |       assert.ok(refinedData.total <= broadData.total, 
679 |         'Refined search should return same or fewer results');
680 |       
681 |       refinedData.hits.forEach(attr => {
682 |         // Note: OCAPI search results don't include searchable field in basic response
683 |         // The query filtering happens server-side, so we validate response structure
684 |         assertValidAttributeDefinition(attr);
685 |         assert.ok(attr.id.length > 0, 'Should have valid attribute ID');
686 |         
687 |         // Should contain address-related terms - validating with expression
688 |         // Note: This validation works with attribute IDs which are available
689 |         const containsAddressTerm = attr.id.toLowerCase().includes('address');
690 |         // Expression evaluated for documentation purposes
691 |         assert.ok(typeof containsAddressTerm === 'boolean', 'Should evaluate address term check');
692 |       });
693 | 
694 |       // Step 3: Further refine to only custom (non-system) attributes
695 |       const customResult = await client.callTool('search_system_object_attribute_definitions', {
696 |         objectType: 'Customer',
697 |         searchRequest: {
698 |           query: {
699 |             bool_query: {
700 |               must: [
701 |                 {
702 |                   text_query: {
703 |                     fields: ['id', 'display_name'],
704 |                     search_phrase: 'address'
705 |                   }
706 |                 },
707 |                 {
708 |                   term_query: {
709 |                     fields: ['searchable'],
710 |                     operator: 'is',
711 |                     values: ['true']
712 |                   }
713 |                 }
714 |               ],
715 |               must_not: [
716 |                 {
717 |                   term_query: {
718 |                     fields: ['system'],
719 |                     operator: 'is',
720 |                     values: ['true']
721 |                   }
722 |                 }
723 |               ]
724 |             }
725 |           },
726 |           count: 20
727 |         }
728 |       });
729 | 
730 |       const customData = assertValidSearchResponse(customResult);
731 |       
732 |       // Validate final refinement
733 |       assert.ok(customData.total <= refinedData.total, 
734 |         'Custom search should return same or fewer results than refined search');
735 |       
736 |       customData.hits.forEach(attr => {
737 |         // Note: OCAPI search results don't include searchable/system fields in basic response
738 |         // The query filtering happens server-side, so we validate response structure
739 |         assertValidAttributeDefinition(attr);
740 |         assert.ok(attr.id.length > 0, 'Should have valid attribute ID');
741 |       });
742 |     });
743 |   });
744 | });
```

--------------------------------------------------------------------------------
/tests/mcp/node/get-best-practice-guide.docs-only.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Programmatic tests for get_best_practice_guide tool
  3 |  * 
  4 |  * These tests provide advanced verification capabilities beyond YAML pattern matching,
  5 |  * including dynamic validation, comprehensive content analysis,
  6 |  * cross-guide relationship testing, and advanced error categorization for the SFCC
  7 |  * best practice guide retrieval functionality.
  8 |  * 
  9 |  * Response format discovered via aegis query:
 10 |  * - Success: { content: [{ type: "text", text: "{\"title\":\"...\",\"description\":\"...\",\"sections\":[...],\"content\":\"...\"}" }], isError: false }
 11 |  * - Error: { content: [{ type: "text", text: "Error: guideName must be a non-empty string" }], isError: true }
 12 |  * - Invalid guide: { content: [{ type: "text", text: "null" }], isError: false }
 13 |  * - JSON structure: title, description, sections array, content markdown
 14 |  */
 15 | 
 16 | import { test, describe, before, after, beforeEach } from 'node:test';
 17 | import { strict as assert } from 'node:assert';
 18 | import { connect } from 'mcp-aegis';
 19 | 
 20 | /**
 21 |  * Content analysis utility for comprehensive guide validation
 22 |  */
 23 | class ContentAnalyzer {
 24 |   constructor() {
 25 |     this.contentPatterns = {
 26 |       security: [
 27 |         /CSRF|cross-site request forgery/i,
 28 |         /XSS|cross-site scripting/i,
 29 |         /authentication|authorization/i,
 30 |         /encryption|cryptography/i,
 31 |         /validation|sanitization/i
 32 |       ],
 33 |       performance: [
 34 |         /performance|optimization/i,
 35 |         /caching|cache/i,
 36 |         /memory|cpu/i,
 37 |         /scalability|throughput/i,
 38 |         /monitoring|metrics/i
 39 |       ],
 40 |       sfra: [
 41 |         /controller|middleware/i,
 42 |         /server\.get|server\.post/i,
 43 |         /SFRA|storefront reference architecture/i,
 44 |         /isml|template/i,
 45 |         /scss|sass/i,
 46 |         /model|view/i
 47 |       ],
 48 |       sfra_scss: [
 49 |         /@import\s+['"]~base\//i,
 50 |         /\$[a-z0-9_-]+\s*:/i,
 51 |         /@include|mixins?/i,
 52 |         /BEM|\b[a-z0-9]+__(?:[a-z0-9-]+)|\b[a-z0-9]+--[a-z0-9-]+/i,
 53 |         /prefers-reduced-motion|focus ring|wcag/i
 54 |       ],
 55 |       cartridge: [
 56 |         /cartridge path/i,
 57 |         /override mechanism/i,
 58 |         /app_storefront_base/i,
 59 |         /plugin_|app_custom_|int_|bm_/i,
 60 |         /deployment|upload/i
 61 |       ]
 62 |     };
 63 |   }
 64 | 
 65 |   analyzeGuide(guideData) {
 66 |     const analysis = {
 67 |       structuralIntegrity: this.checkStructuralIntegrity(guideData),
 68 |       contentDepth: this.assessContentDepth(guideData),
 69 |       codeExamples: this.extractCodeExamples(guideData),
 70 |       crossReferences: this.findCrossReferences(guideData),
 71 |       technicalAccuracy: this.validateTechnicalContent(guideData),
 72 |       readabilityMetrics: this.calculateReadabilityMetrics(guideData)
 73 |     };
 74 | 
 75 |     return analysis;
 76 |   }
 77 | 
 78 |   checkStructuralIntegrity(guideData) {
 79 |     const requiredFields = ['title', 'description', 'sections', 'content'];
 80 |     const missingFields = requiredFields.filter(field => !guideData[field]);
 81 |     
 82 |     return {
 83 |       isComplete: missingFields.length === 0,
 84 |       missingFields,
 85 |       sectionsCount: Array.isArray(guideData.sections) ? guideData.sections.length : 0,
 86 |       hasValidMarkdown: typeof guideData.content === 'string' && guideData.content.includes('#')
 87 |     };
 88 |   }
 89 | 
 90 |   assessContentDepth(guideData) {
 91 |     const content = guideData.content || '';
 92 |     const wordCount = content.split(/\s+/).length;
 93 |     const paragraphCount = content.split('\n\n').length;
 94 |     const headingCount = (content.match(/^#+\s/gm) || []).length;
 95 |     
 96 |     return {
 97 |       wordCount,
 98 |       paragraphCount,
 99 |       headingCount,
100 |       averageWordsPerParagraph: wordCount / Math.max(paragraphCount, 1),
101 |       depthScore: this.calculateDepthScore(wordCount, headingCount, paragraphCount)
102 |     };
103 |   }
104 | 
105 |   calculateDepthScore(wordCount, headingCount, paragraphCount) {
106 |     // Scoring based on content richness
107 |     let score = 0;
108 |     if (wordCount > 2000) score += 3;
109 |     else if (wordCount > 1000) score += 2;
110 |     else if (wordCount > 500) score += 1;
111 |     
112 |     if (headingCount > 8) score += 2;
113 |     else if (headingCount > 4) score += 1;
114 |     
115 |     if (paragraphCount > 15) score += 2;
116 |     else if (paragraphCount > 8) score += 1;
117 |     
118 |     return Math.min(score, 5); // Max score of 5
119 |   }
120 | 
121 |   extractCodeExamples(guideData) {
122 |     const content = guideData.content || '';
123 |     const codeBlocks = content.match(/```[\s\S]*?```/g) || [];
124 |     const inlineCode = content.match(/`[^`\n]+`/g) || [];
125 |     
126 |     const languages = codeBlocks.map(block => {
127 |       const match = block.match(/```(\w+)/);
128 |       return match ? match[1] : 'unknown';
129 |     });
130 | 
131 |     return {
132 |       codeBlockCount: codeBlocks.length,
133 |       inlineCodeCount: inlineCode.length,
134 |       languages: [...new Set(languages)],
135 |       hasJavaScript: languages.includes('javascript'),
136 |       hasJSON: languages.includes('json'),
137 |       hasBash: languages.includes('bash'),
138 |       hasHTML: languages.includes('html') || languages.includes('isml')
139 |     };
140 |   }
141 | 
142 |   findCrossReferences(guideData) {
143 |     const content = guideData.content || '';
144 |     const mcpReferences = (content.match(/MCP server|MCP tool|`\w+_\w+`/g) || []).length;
145 |     const sfccReferences = (content.match(/dw\.\w+|SFCC|Salesforce Commerce Cloud/g) || []).length;
146 |     const externalReferences = (content.match(/https?:\/\/[^\s)]+/g) || []).length;
147 |     
148 |     return {
149 |       mcpReferences,
150 |       sfccReferences,
151 |       externalReferences,
152 |       totalReferences: mcpReferences + sfccReferences + externalReferences,
153 |       hasIntegrationWorkflow: content.includes('MCP Integration Workflow')
154 |     };
155 |   }
156 | 
157 |   validateTechnicalContent(guideData) {
158 |     const content = guideData.content || '';
159 |     const guideName = this.inferGuideType(guideData);
160 |     const expectedPatterns = this.contentPatterns[guideName] || [];
161 |     
162 |     const matchedPatterns = expectedPatterns.filter(pattern => pattern.test(content));
163 | 
164 |     let modernPattern = /dw\.crypto\.Cipher|server\.append|server\.prepend/i;
165 |     if (guideName === 'sfra_scss') {
166 |       modernPattern = /@include|prefers-reduced-motion|mixins?|@import\s+['"]~base\//i;
167 |     }
168 |     
169 |     return {
170 |       guideName,
171 |       expectedPatterns: expectedPatterns.length,
172 |       matchedPatterns: matchedPatterns.length,
173 |       accuracyScore: expectedPatterns.length > 0 ? 
174 |         (matchedPatterns.length / expectedPatterns.length) : 1,
175 |       hasDeprecatedReferences: /WeakCipher|WeakMac|WeakMessageDigest/i.test(content),
176 |       hasModernPractices: modernPattern.test(content)
177 |     };
178 |   }
179 | 
180 |   inferGuideType(guideData) {
181 |     const title = (guideData.title || '').toLowerCase();
182 |     if (title.includes('security')) return 'security';
183 |     if (title.includes('performance')) return 'performance';
184 |     if (title.includes('scss') || title.includes('styling')) return 'sfra_scss';
185 |     if (title.includes('sfra') || title.includes('controller')) return 'sfra';
186 |     if (title.includes('cartridge')) return 'cartridge';
187 |     return 'general';
188 |   }
189 | 
190 |   calculateReadabilityMetrics(guideData) {
191 |     const content = guideData.content || '';
192 |     const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
193 |     const words = content.split(/\s+/).filter(w => w.length > 0);
194 |     
195 |     const avgWordsPerSentence = words.length / Math.max(sentences.length, 1);
196 |     const avgCharsPerWord = words.reduce((sum, word) => sum + word.length, 0) / Math.max(words.length, 1);
197 |     
198 |     return {
199 |       sentenceCount: sentences.length,
200 |       wordCount: words.length,
201 |       avgWordsPerSentence,
202 |       avgCharsPerWord,
203 |       readabilityScore: this.calculateFleschScore(avgWordsPerSentence, avgCharsPerWord)
204 |     };
205 |   }
206 | 
207 |   calculateFleschScore(avgWordsPerSentence, avgCharsPerWord) {
208 |     // Simplified Flesch Reading Ease approximation
209 |     return Math.max(0, 206.835 - (1.015 * avgWordsPerSentence) - (84.6 * (avgCharsPerWord / 4.7)));
210 |   }
211 | }
212 | 
213 | /**
214 |  * Error categorization utility for comprehensive error analysis
215 |  */
216 | class ErrorAnalyzer {
217 |   static categorizeError(errorText) {
218 |     const patterns = [
219 |       { type: 'validation', keywords: ['required', 'invalid', 'missing', 'empty string'] },
220 |       { type: 'not_found', keywords: ['not found', 'does not exist', 'null'] },
221 |       { type: 'permission', keywords: ['permission', 'unauthorized', 'forbidden'] },
222 |       { type: 'network', keywords: ['connection', 'timeout', 'unreachable'] },
223 |       { type: 'parsing', keywords: ['parse', 'syntax', 'format'] }
224 |     ];
225 | 
226 |     for (const pattern of patterns) {
227 |       if (pattern.keywords.some(keyword => 
228 |           errorText.toLowerCase().includes(keyword))) {
229 |         return pattern.type;
230 |       }
231 |     }
232 |     return 'unknown';
233 |   }
234 | 
235 |   static assessErrorQuality(errorText) {
236 |     return {
237 |       isInformative: errorText.length > 20,
238 |       hasContext: errorText.includes('guideName'),
239 |       isActionable: /must|should|required|expected/i.test(errorText),
240 |       category: this.categorizeError(errorText)
241 |     };
242 |   }
243 | }
244 | 
245 | /**
246 |  * Custom assertion helpers for MCP response validation
247 |  */
248 | function assertValidMCPResponse(result, expectError = false) {
249 |   assert.ok(result.content, 'Should have content');
250 |   assert.ok(Array.isArray(result.content), 'Content should be array');
251 |   assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
252 |   assert.equal(result.isError, expectError, `isError should be ${expectError}`);
253 | }
254 | 
255 | function assertGuideContent(result, expectedGuideName) {
256 |   assertValidMCPResponse(result);
257 |   assert.equal(result.content[0].type, 'text');
258 |   
259 |   const text = result.content[0].text;
260 |   if (text === 'null') {
261 |     // Invalid guide name returns null
262 |     return null;
263 |   }
264 |   
265 |   const guideData = JSON.parse(text);
266 |   assert.ok(guideData.title, 'Guide should have title');
267 |   assert.ok(guideData.description, 'Guide should have description');
268 |   assert.ok(Array.isArray(guideData.sections), 'Guide should have sections array');
269 |   assert.ok(guideData.content, 'Guide should have content');
270 |   
271 |   if (expectedGuideName) {
272 |     assert.ok(
273 |       guideData.title.toLowerCase().includes(expectedGuideName.replace('_', ' ')) ||
274 |       guideData.description.toLowerCase().includes(expectedGuideName.replace('_', ' ')),
275 |       `Guide should be related to ${expectedGuideName}`
276 |     );
277 |   }
278 |   
279 |   return guideData;
280 | }
281 | 
282 | function assertErrorResponse(result, expectedErrorType) {
283 |   assertValidMCPResponse(result, true);
284 |   assert.equal(result.content[0].type, 'text');
285 |   
286 |   const errorText = result.content[0].text;
287 |   assert.ok(errorText.includes('Error:'), 'Should be error message');
288 |   
289 |   const errorAnalysis = ErrorAnalyzer.assessErrorQuality(errorText);
290 |   assert.ok(errorAnalysis.isInformative, 'Error should be informative');
291 |   
292 |   if (expectedErrorType) {
293 |     assert.equal(errorAnalysis.category, expectedErrorType, 
294 |       `Error should be categorized as ${expectedErrorType}`);
295 |   }
296 |   
297 |   return errorAnalysis;
298 | }
299 | 
300 | describe('get_best_practice_guide Tool - Advanced Programmatic Tests', () => {
301 |   let client;
302 |   let contentAnalyzer;
303 | 
304 |   before(async () => {
305 |     client = await connect('./aegis.config.docs-only.json');
306 |     contentAnalyzer = new ContentAnalyzer();
307 |   });
308 | 
309 |   after(async () => {
310 |     if (client?.connected) {
311 |       await client.disconnect();
312 |     }
313 |   });
314 | 
315 |   beforeEach(() => {
316 |     // CRITICAL: Clear all buffers to prevent leaking between tests
317 |     client.clearAllBuffers(); // Recommended - comprehensive protection
318 |   });
319 | 
320 |   describe('Protocol Compliance and Basic Functionality', () => {
321 |     test('should retrieve valid cartridge creation guide', async () => {
322 |       const result = await client.callTool('get_best_practice_guide', {
323 |         guideName: 'cartridge_creation'
324 |       });
325 | 
326 |       const guideData = assertGuideContent(result, 'cartridge');
327 |       assert.ok(guideData.title.includes('Cartridge'), 'Should be cartridge guide');
328 |       assert.ok(guideData.content.includes('Core Principles'), 'Should have core principles');
329 |     });
330 | 
331 |     test('should retrieve security best practices guide', async () => {
332 |       const result = await client.callTool('get_best_practice_guide', {
333 |         guideName: 'security'
334 |       });
335 | 
336 |       const guideData = assertGuideContent(result, 'security');
337 |       assert.ok(guideData.title.toLowerCase().includes('secure') || 
338 |         guideData.title.toLowerCase().includes('security'), 'Should be security guide');
339 |       assert.ok(guideData.content.includes('CSRF') || guideData.content.includes('XSS') || 
340 |         guideData.content.includes('Security'), 'Should contain security concepts');
341 |     });
342 | 
343 |     test('should retrieve SFRA client-side JavaScript guide', async () => {
344 |       const result = await client.callTool('get_best_practice_guide', {
345 |         guideName: 'sfra_client_side_js'
346 |       });
347 | 
348 |       const guideData = assertGuideContent(result);
349 |       assert.ok(guideData.title.toLowerCase().includes('client-side javascript') ||
350 |         guideData.description.toLowerCase().includes('client-side javascript'),
351 |         'Guide should reference client-side JavaScript in title or description');
352 |       assert.ok(/ajax|assets\.js|debounce|event delegation/i.test(guideData.content),
353 |         'Guide content should include client-side patterns like AJAX or assets.js');
354 |     });
355 | 
356 |     test('should retrieve SFRA SCSS guide', async () => {
357 |       const result = await client.callTool('get_best_practice_guide', {
358 |         guideName: 'sfra_scss'
359 |       });
360 | 
361 |       const guideData = assertGuideContent(result);
362 |       assert.ok(guideData.title.toLowerCase().includes('scss') ||
363 |         guideData.description.toLowerCase().includes('scss'),
364 |         'Guide should reference SCSS in title or description');
365 |       assert.ok(/@import|_variables\.scss|mixins|sass/i.test(guideData.content),
366 |         'Guide content should highlight SCSS override patterns and mixins');
367 |     });
368 | 
369 |     test('should handle invalid guide name gracefully', async () => {
370 |       const result = await client.callTool('get_best_practice_guide', {
371 |         guideName: 'nonexistent_guide'
372 |       });
373 | 
374 |       assertValidMCPResponse(result);
375 |       assert.equal(result.content[0].text, 'null', 'Should return null for invalid guide');
376 |     });
377 | 
378 |     test('should validate required parameters', async () => {
379 |       const result = await client.callTool('get_best_practice_guide', {});
380 |       
381 |       assertErrorResponse(result, 'validation');
382 |     });
383 | 
384 |     test('should handle empty guide name', async () => {
385 |       const result = await client.callTool('get_best_practice_guide', {
386 |         guideName: ''
387 |       });
388 |       
389 |       assertErrorResponse(result, 'validation');
390 |     });
391 |   });
392 | 
393 |   describe('Comprehensive Guide Validation', () => {
394 |     const availableGuides = [
395 |       'cartridge_creation',
396 |       'security', 
397 |       'performance',
398 |       'sfra_controllers',
399 |       'sfra_models',
400 |       'sfra_client_side_js',
401 |       'sfra_scss',
402 |       'ocapi_hooks',
403 |       'scapi_hooks',
404 |       'scapi_custom_endpoint',
405 |       'isml_templates',
406 |       'job_framework',
407 |       'localserviceregistry'
408 |     ];
409 | 
410 |     test('should validate all available guides have proper structure', async () => {
411 |       const guideAnalyses = new Map();
412 | 
413 |       for (const guideName of availableGuides) {
414 |         const result = await client.callTool('get_best_practice_guide', {
415 |           guideName
416 |         });
417 | 
418 |         const guideData = assertGuideContent(result);
419 |         const analysis = contentAnalyzer.analyzeGuide(guideData);
420 |         guideAnalyses.set(guideName, analysis);
421 | 
422 |         // Structural integrity checks
423 |         assert.ok(analysis.structuralIntegrity.isComplete, 
424 |           `${guideName} should have complete structure`);
425 |         assert.ok(analysis.structuralIntegrity.sectionsCount > 0, 
426 |           `${guideName} should have sections`);
427 |         assert.ok(analysis.structuralIntegrity.hasValidMarkdown, 
428 |           `${guideName} should have valid markdown`);
429 | 
430 |         // Content depth validation
431 |         assert.ok(analysis.contentDepth.wordCount > 500, 
432 |           `${guideName} should have substantial content (>500 words)`);
433 |         assert.ok(analysis.contentDepth.depthScore >= 2, 
434 |           `${guideName} should have good content depth`);
435 |       }
436 | 
437 |       // Analyze overall guide quality
438 |       const averageDepthScore = Array.from(guideAnalyses.values())
439 |         .reduce((sum, analysis) => sum + analysis.contentDepth.depthScore, 0) / 
440 |         guideAnalyses.size;
441 |       
442 |       assert.ok(averageDepthScore >= 3, 
443 |         `Average guide depth score should be >= 3, got ${averageDepthScore}`);
444 |     });
445 | 
446 |     test('should validate technical accuracy across guide types', async () => {
447 |       const techGuides = ['security', 'sfra_controllers', 'sfra_client_side_js', 'sfra_scss', 'cartridge_creation'];
448 |       
449 |       for (const guideName of techGuides) {
450 |         const result = await client.callTool('get_best_practice_guide', {
451 |           guideName
452 |         });
453 | 
454 |         const guideData = assertGuideContent(result);
455 |         const analysis = contentAnalyzer.analyzeGuide(guideData);
456 | 
457 |         // Technical accuracy validation
458 |         assert.ok(analysis.technicalAccuracy.accuracyScore >= 0.7, 
459 |           `${guideName} should have high technical accuracy`);
460 |         
461 |         // Modern practices validation
462 |         if (guideName === 'security') {
463 |           assert.ok(analysis.technicalAccuracy.hasModernPractices, 
464 |             'Security guide should reference modern practices');
465 |           assert.ok(!analysis.technicalAccuracy.hasDeprecatedReferences ||
466 |             analysis.technicalAccuracy.hasModernPractices,
467 |             'Security guide should prefer modern over deprecated practices');
468 |         }
469 | 
470 |         // Code examples validation
471 |         if (['sfra_controllers', 'sfra_client_side_js', 'sfra_scss', 'cartridge_creation'].includes(guideName)) {
472 |           assert.ok(analysis.codeExamples.codeBlockCount > 0, 
473 |             `${guideName} should have code examples`);
474 | 
475 |           if (guideName === 'sfra_scss') {
476 |             const hasScssLanguage = analysis.codeExamples.languages.includes('scss');
477 |             const mentionsScssPatterns = /@import|\$[a-zA-Z_-]+|mixins?/i.test(guideData.content);
478 |             assert.ok(hasScssLanguage || mentionsScssPatterns,
479 |               'SFRA SCSS guide should showcase SCSS override patterns');
480 |           } else {
481 |             assert.ok(analysis.codeExamples.hasJavaScript, 
482 |               `${guideName} should have JavaScript examples`);
483 |           }
484 |         }
485 |       }
486 |     });
487 | 
488 |     test('should validate cross-references and integration patterns', async () => {
489 |       const result = await client.callTool('get_best_practice_guide', {
490 |         guideName: 'cartridge_creation'
491 |       });
492 | 
493 |       const guideData = assertGuideContent(result);
494 |       const analysis = contentAnalyzer.analyzeGuide(guideData);
495 | 
496 |       // Cross-reference validation
497 |       assert.ok(analysis.crossReferences.mcpReferences > 0, 
498 |         'Cartridge guide should reference MCP tools');
499 |       assert.ok(analysis.crossReferences.hasIntegrationWorkflow, 
500 |         'Cartridge guide should have MCP integration workflow');
501 |       assert.ok(analysis.crossReferences.sfccReferences > 0, 
502 |         'Cartridge guide should reference SFCC concepts');
503 |     });
504 |   });
505 | 
506 |   describe('Advanced Error Handling and Edge Cases', () => {
507 |     test('should provide detailed error categorization', async () => {
508 |       const errorScenarios = [
509 |         { params: {}, expectedCategory: 'validation' },
510 |         { params: { guideName: '' }, expectedCategory: 'validation' },
511 |         { params: { guideName: null }, expectedCategory: 'validation' },
512 |         { params: { guideName: 123 }, expectedCategory: 'validation' }
513 |       ];
514 | 
515 |       for (const scenario of errorScenarios) {
516 |         const result = await client.callTool('get_best_practice_guide', scenario.params);
517 |         
518 |         if (result.isError) {
519 |           const errorAnalysis = assertErrorResponse(result, scenario.expectedCategory);
520 |           assert.ok(errorAnalysis.isActionable, 
521 |             'Error message should be actionable');
522 |         } else {
523 |           // Some invalid params might return null instead of error
524 |           assert.equal(result.content[0].text, 'null', 
525 |             'Invalid params should return null if not error');
526 |         }
527 |       }
528 |     });
529 | 
530 |     test('should handle malformed requests gracefully', async () => {
531 |       const malformedScenarios = [
532 |         { guideName: 'a'.repeat(1000) }, // Very long guide name
533 |         { guideName: 'guide\nwith\nnewlines' }, // Guide name with newlines
534 |         { guideName: 'guide with spaces' }, // Guide name with spaces
535 |         { extraParam: 'unexpected', guideName: 'security' } // Extra parameters
536 |       ];
537 | 
538 |       for (const params of malformedScenarios) {
539 |         const result = await client.callTool('get_best_practice_guide', params);
540 |         
541 |         // Should either return valid response or informative error
542 |         if (result.isError) {
543 |           assertErrorResponse(result);
544 |         } else {
545 |           // If it succeeds or returns null, that's also acceptable
546 |           assertValidMCPResponse(result);
547 |         }
548 |       }
549 |     });
550 | 
551 |     test('should maintain consistency across error conditions', async () => {
552 |       const errorConditions = [
553 |         { guideName: 'invalid1' },
554 |         { guideName: 'invalid2' },
555 |         { guideName: 'nonexistent' }
556 |       ];
557 | 
558 |       const errorResponses = [];
559 |       for (const params of errorConditions) {
560 |         const result = await client.callTool('get_best_practice_guide', params);
561 |         errorResponses.push(result);
562 |       }
563 | 
564 |       // All invalid guide names should behave consistently
565 |       const responseTypes = errorResponses.map(r => 
566 |         r.isError ? 'error' : (r.content[0].text === 'null' ? 'null' : 'unexpected')
567 |       );
568 |       
569 |       const uniqueResponseTypes = [...new Set(responseTypes)];
570 |       assert.equal(uniqueResponseTypes.length, 1, 
571 |         'All invalid guide names should have consistent response type');
572 |     });
573 |   });
574 | 
575 |   describe('Multi-Step Integration Workflows', () => {
576 |     test('should support guide discovery to detailed retrieval workflow', async () => {
577 |       // Step 1: Discover available guides
578 |       const availableResult = await client.callTool('get_available_best_practice_guides', {});
579 |       assertValidMCPResponse(availableResult);
580 |       
581 |       const availableGuides = JSON.parse(availableResult.content[0].text);
582 |       assert.ok(Array.isArray(availableGuides), 'Should return guides array');
583 |       assert.ok(availableGuides.length > 0, 'Should have available guides');
584 | 
585 |       // Step 2: Retrieve detailed guide for each discovered guide
586 |       for (const guide of availableGuides.slice(0, 3)) { // Test first 3 for performance
587 |         const detailResult = await client.callTool('get_best_practice_guide', {
588 |           guideName: guide.name
589 |         });
590 | 
591 |         const guideData = assertGuideContent(detailResult);
592 |         
593 |         // Validate consistency between discovery and detailed retrieval
594 |         // Note: Descriptions may be slightly different between discovery and detailed view
595 |         assert.ok(guideData.description.toLowerCase().includes('best practices') ||
596 |           guideData.description.toLowerCase().includes('guide') ||
597 |           guide.description.toLowerCase().includes('best practices'), 
598 |           'Guide descriptions should be conceptually consistent');
599 |       }
600 |     });
601 | 
602 |     test('should support cross-guide relationship analysis', async () => {
603 |       // Get related guides
604 |       const cartridgeResult = await client.callTool('get_best_practice_guide', {
605 |         guideName: 'cartridge_creation'
606 |       });
607 |       const securityResult = await client.callTool('get_best_practice_guide', {
608 |         guideName: 'security'
609 |       });
610 |       const sfraResult = await client.callTool('get_best_practice_guide', {
611 |         guideName: 'sfra_controllers'
612 |       });
613 | 
614 |       const cartridgeGuide = assertGuideContent(cartridgeResult);
615 |       const securityGuide = assertGuideContent(securityResult);
616 |       const sfraGuide = assertGuideContent(sfraResult);
617 | 
618 |       // Analyze cross-references between guides
619 |       const cartridgeAnalysis = contentAnalyzer.analyzeGuide(cartridgeGuide);
620 |       const securityAnalysis = contentAnalyzer.analyzeGuide(securityGuide);
621 |       const sfraAnalysis = contentAnalyzer.analyzeGuide(sfraGuide);
622 | 
623 |       // Validate that guides appropriately reference each other
624 |       assert.ok(cartridgeAnalysis.crossReferences.mcpReferences > 0, 
625 |         'Cartridge guide should reference other MCP tools');
626 |       
627 |       // Security guide should have practical implementation focus
628 |       assert.ok(securityAnalysis.codeExamples.codeBlockCount > 0, 
629 |         'Security guide should have code examples');
630 |       
631 |       // SFRA guide should reference server concepts
632 |       assert.ok(sfraAnalysis.technicalAccuracy.hasModernPractices, 
633 |         'SFRA guide should reference modern practices');
634 |     });
635 | 
636 |     test('should validate comprehensive development workflow coverage', async () => {
637 |       // Simulate a complete development workflow
638 |       const workflowGuides = [
639 |         'cartridge_creation',        // Project setup
640 |         'sfra_controllers',          // Server-side implementation
641 |         'sfra_client_side_js',       // Client-side enhancements
642 |         'sfra_scss',                 // Styling and theming overrides
643 |         'security',                  // Security review
644 |         'performance'                // Optimization
645 |       ];
646 | 
647 |       const workflowAnalysis = new Map();
648 | 
649 |       for (const guideName of workflowGuides) {
650 |         const result = await client.callTool('get_best_practice_guide', {
651 |           guideName
652 |         });
653 | 
654 |         const guideData = assertGuideContent(result);
655 |         const analysis = contentAnalyzer.analyzeGuide(guideData);
656 |         workflowAnalysis.set(guideName, analysis);
657 |       }
658 | 
659 |       // Validate workflow completeness
660 |       const totalCodeExamples = Array.from(workflowAnalysis.values())
661 |         .reduce((sum, analysis) => sum + analysis.codeExamples.codeBlockCount, 0);
662 |       
663 |       assert.ok(totalCodeExamples >= 10, 
664 |         'Workflow guides should provide substantial code examples');
665 | 
666 |       const totalCrossReferences = Array.from(workflowAnalysis.values())
667 |         .reduce((sum, analysis) => sum + analysis.crossReferences.totalReferences, 0);
668 |       
669 |       assert.ok(totalCrossReferences >= 20, 
670 |         'Workflow guides should have extensive cross-references');
671 | 
672 |       // Validate that each workflow stage has appropriate depth
673 |       workflowAnalysis.forEach((analysis, guideName) => {
674 |         assert.ok(analysis.contentDepth.depthScore >= 3, 
675 |           `${guideName} should have sufficient depth for workflow stage`);
676 |       });
677 |     });
678 |   });
679 | 
680 |   describe('Content Quality and Accessibility', () => {
681 |     test('should validate readability across all guides', async () => {
682 |   const testGuides = ['cartridge_creation', 'security', 'sfra_controllers', 'sfra_client_side_js', 'sfra_scss'];
683 |       
684 |       for (const guideName of testGuides) {
685 |         const result = await client.callTool('get_best_practice_guide', {
686 |           guideName
687 |         });
688 | 
689 |         const guideData = assertGuideContent(result);
690 |         const analysis = contentAnalyzer.analyzeGuide(guideData);
691 | 
692 |         // Readability validation
693 |         assert.ok(analysis.readabilityMetrics.avgWordsPerSentence < 25, 
694 |           `${guideName} should have readable sentence length`);
695 |         assert.ok(analysis.readabilityMetrics.readabilityScore > 30, 
696 |           `${guideName} should have acceptable readability score`);
697 |         
698 |         // Content structure validation
699 |         assert.ok(analysis.contentDepth.headingCount >= 5, 
700 |           `${guideName} should have good content organization`);
701 |         assert.ok(analysis.contentDepth.averageWordsPerParagraph < 200, 
702 |           `${guideName} should have digestible paragraph sizes`);
703 |       }
704 |     });
705 | 
706 |     test('should ensure comprehensive coverage of technical topics', async () => {
707 |       const technicalGuides = {
708 |         'security': ['CSRF', 'XSS', 'authentication', 'encryption'],
709 |         'performance': ['performance', 'optimization'], // Simplified expectations
710 |         'sfra_controllers': ['server.get', 'middleware', 'ISML'],
711 |         'sfra_client_side_js': ['ajax', 'assets.js', 'debounce', 'validation'],
712 |         'sfra_scss': ['@import', '_variables.scss', 'mixins', 'scss']
713 |       };
714 | 
715 |       for (const [guideName, expectedTopics] of Object.entries(technicalGuides)) {
716 |         const result = await client.callTool('get_best_practice_guide', {
717 |           guideName
718 |         });
719 | 
720 |         const guideData = assertGuideContent(result);
721 |         const content = guideData.content.toLowerCase();
722 | 
723 |         const coveredTopics = expectedTopics.filter(topic => 
724 |           content.includes(topic.toLowerCase())
725 |         );
726 | 
727 |         const coverageRatio = coveredTopics.length / expectedTopics.length;
728 |         assert.ok(coverageRatio >= 0.7, 
729 |           `${guideName} should cover at least 70% of expected topics, covered ${coverageRatio * 100}%`);
730 |       }
731 |     });
732 |   });
733 | });
734 | 
```

--------------------------------------------------------------------------------
/.github/instructions/mcp-node-tests.instructions.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | applyTo: "**/*.programmatic.test.js"
  3 | ---
  4 | # MCP Aegis - Programmatic Testing Guide for AI Agents
  5 | 
  6 | **Target Audience**: AI coding assistants generating JavaScript/TypeScript programmatic t# Debugging and monitoring
  7 | const stderr = client.getStderr();      // Get captured stderr
  8 | client.clearStderr();                   // Clear stderr buffer (REQUIRED in beforeEach!)
  9 | 
 10 | **⚠️ CRITICAL**: Always include `client.clearStderr()` in your `beforeEach()` hook to prevent stderr from one test affecting the next test. This is a common source of test flakiness. files for Model Context Protocol servers.
 11 | 
 12 | ## Overview
 13 | 
 14 | **Programmatic Testing** provides JavaScript/TypeScript API for complex MCP server testing scenarios that require dynamic validation logic, multi-step workflows, and integration with existing test suites. **For basic functional testing, the YAML-based testing approach (see `../yaml/AGENTS.md`) is more than sufficient and recommended.**
 15 | 
 16 | ### When to Use Programmatic vs YAML Testing
 17 | 
 18 | **Use YAML Testing for:**
 19 | - ✅ Basic functional validation (tool discovery, parameter validation, response structure)
 20 | - ✅ Standard error handling scenarios
 21 | - ✅ Simple input/output verification
 22 | - ✅ CI/CD pipeline testing (more reliable across environments)
 23 | - ✅ Quick test development and maintenance
 24 | 
 25 | **Use Programmatic Testing for:**
 26 | - Complex business logic validation requiring code execution
 27 | - Multi-step workflows with state management
 28 | - Dynamic test case generation based on server configuration
 29 | - Integration with existing JavaScript/TypeScript test suites
 30 | - Advanced error recovery and resilience testing
 31 | 
 32 | ### 📚 Key Resources
 33 | - **[Programmatic Testing Documentation](https://aegis.rhino-inquisitor.com/programmatic-testing.html)** - Complete guide
 34 | - **[API Reference](https://aegis.rhino-inquisitor.com/api-reference.html)** - All methods and properties
 35 | - **[Examples Directory](../../examples/)** - Real-world programmatic test files
 36 | - **[YAML Testing Guide](../yaml/AGENTS.md)** - Recommended for basic testing scenarios
 37 | 
 38 | ## Quick Setup
 39 | 
 40 | ### 1. Installation and Initialization
 41 | ```bash
 42 | # Install in project
 43 | npm install --save-dev mcp-aegis
 44 | # OR 
 45 | npx mcp-aegis init
 46 | ```
 47 | 
 48 | ### 2. Configuration File
 49 | Always create `aegis.config.json` first:
 50 | 
 51 | ```json
 52 | {
 53 |   "name": "My MCP Server",
 54 |   "command": "node",
 55 |   "args": ["./server.js"],
 56 |   "startupTimeout": 5000,
 57 |   "env": {
 58 |     "NODE_ENV": "test"
 59 |   }
 60 | }
 61 | ```
 62 | 
 63 | ### 3. Basic Programmatic Test Structure
 64 | File naming convention: `*.programmatic.test.js`
 65 | 
 66 | ```javascript
 67 | import { test, describe, before, after, beforeEach } from 'node:test';
 68 | import { strict as assert } from 'node:assert';
 69 | import { connect } from 'mcp-aegis';
 70 | 
 71 | describe('[SERVER_NAME] Programmatic Tests', () => {
 72 |   let client;
 73 | 
 74 |   before(async () => {
 75 |     client = await connect('./aegis.config.json');
 76 |   });
 77 | 
 78 |   after(async () => {
 79 |     if (client?.connected) {
 80 |       await client.disconnect();
 81 |     }
 82 |   });
 83 | 
 84 |   beforeEach(() => {
 85 |     // CRITICAL: Clear all buffers to prevent leaking into next tests
 86 |     client.clearAllBuffers(); // Recommended - comprehensive protection
 87 |     // OR: client.clearStderr(); // Minimum - stderr only
 88 |   });
 89 | 
 90 |   test('should list available tools', async () => {
 91 |     const tools = await client.listTools();
 92 |     assert.ok(Array.isArray(tools), 'Tools should be array');
 93 |     assert.ok(tools.length > 0, 'Should have at least one tool');
 94 |   });
 95 | 
 96 |   test('should execute tool successfully', async () => {
 97 |     const result = await client.callTool('[TOOL_NAME]', { param: 'value' });
 98 |     assert.ok(result.content, 'Should return content');
 99 |     assert.equal(result.isError, false, 'Should not be error');
100 |   });
101 | });
102 | ```
103 | 
104 | ## Quick Debugging with Query Command
105 | 
106 | Before writing comprehensive programmatic tests, use the `query` command to rapidly test your server:
107 | 
108 | ```bash
109 | # List all available tools
110 | aegis query --config aegis.config.json
111 | 
112 | # Test specific tool with arguments
113 | aegis query read_file '{"path": "test.txt"}' --config aegis.config.json
114 | 
115 | # Get JSON output for inspection
116 | aegis query calculator '{"operation": "add", "a": 5, "b": 3}' --config aegis.config.json --json
117 | ```
118 | 
119 | **Benefits for programmatic testing workflow**:
120 | - **Rapid prototyping**: Verify server behavior before writing test code
121 | - **API exploration**: Discover tool signatures and response formats
122 | - **Debug assistance**: Inspect actual responses to design assertions
123 | - **Development speed**: Test changes instantly without rebuilding test suite
124 | 
125 | **Integration with programmatic tests**:
126 | ```javascript
127 | // Use query command findings to create targeted tests
128 | test('should handle file reading as discovered via query', async () => {
129 |   // Based on: aegis query read_file '{"path": "test.txt"}'
130 |   const result = await client.callTool('read_file', { path: 'test.txt' });
131 |   
132 |   // Query command showed this response structure:
133 |   assert.ok(result.content);
134 |   assert.equal(result.content[0].type, 'text');
135 |   assert.ok(result.content[0].text.includes('expected content'));
136 | });
137 | ```
138 | 
139 | ## API Reference
140 | 
141 | ### Main Entry Points
142 | ```javascript
143 | import { createClient, connect } from 'mcp-aegis';
144 | 
145 | // Option 1: Create client (not connected)
146 | const client = await createClient('./aegis.config.json');
147 | await client.connect();
148 | 
149 | // Option 2: Create and auto-connect
150 | const connectedClient = await connect('./aegis.config.json');
151 | 
152 | // Option 3: Inline configuration
153 | const client = await connect({
154 |   name: 'My Server',
155 |   command: 'node',
156 |   args: ['./server.js'],
157 |   cwd: './server-directory',
158 |   startupTimeout: 5000
159 | });
160 | ```
161 | 
162 | ### Core Methods
163 | ```javascript
164 | // Server lifecycle
165 | await client.connect();                 // Start server + MCP handshake
166 | await client.disconnect();              // Graceful shutdown
167 | const isConnected = client.connected;   // Connection status
168 | 
169 | // Tool operations
170 | const tools = await client.listTools();                    // Get available tools
171 | const result = await client.callTool(name, arguments);     // Execute tool
172 | const response = await client.sendMessage(jsonRpcMessage); // Raw JSON-RPC
173 | 
174 | // Debugging and monitoring
175 | const stderr = client.getStderr();      // Get captured stderr
176 | client.clearStderr();                   // Clear stderr buffer
177 | ```
178 | 
179 | ### Error Handling
180 | ```javascript
181 | try {
182 |   const result = await client.callTool('nonexistent_tool', {});
183 | } catch (error) {
184 |   assert.ok(error.message.includes('Failed to call tool'));
185 |   // Error details available in error object
186 | }
187 | 
188 | // Check for execution errors (not exceptions)
189 | const result = await client.callTool('tool_name', { invalid: 'param' });
190 | if (result.isError) {
191 |   assert.ok(result.content[0].text.includes('error message'));
192 | }
193 | ```
194 | 
195 | ## Critical: Preventing Test Interference
196 | 
197 | ### Buffer Leaking Prevention
198 | **The most common source of flaky programmatic tests is buffer leaking between tests.** When one test generates output (stderr, partial stdout messages) and doesn't clear it, subsequent tests may see the output from previous tests, causing unexpected failures.
199 | 
200 | #### Always Include beforeEach Hook
201 | ```javascript
202 | beforeEach(() => {
203 |   // RECOMMENDED: Clear all buffers to prevent any leaking
204 |   client.clearAllBuffers();
205 |   
206 |   // OR minimum: Clear only stderr (less comprehensive)
207 |   // client.clearStderr();
208 | });
209 | ```
210 | 
211 | #### Buffer Bleeding Sources
212 | - **Stderr buffer**: Error messages and debug output
213 | - **Stdout buffer**: Partial JSON messages from previous requests  
214 | - **Ready state**: Server readiness flag not reset
215 | - **Pending reads**: Lingering message handlers
216 | 
217 | **Best Practice**: Use `client.clearAllBuffers()` instead of just `clearStderr()` for comprehensive protection.
218 | 
219 | #### Common Anti-Patterns to Avoid
220 | ```javascript
221 | // ❌ WRONG - Missing beforeEach entirely
222 | describe('My Tests', () => {
223 |   let client;
224 |   
225 |   before(async () => {
226 |     client = await connect('./config.json');
227 |   });
228 |   
229 |   // Missing beforeEach - tests will leak buffers!
230 |   
231 |   test('first test', async () => {
232 |     const result = await client.callTool('tool', {});
233 |     // This test might generate stderr or leave stdout buffer data
234 |   });
235 |   
236 |   test('second test', async () => {
237 |     // This test might see output from first test!
238 |     assert.equal(client.getStderr(), ''); // Will fail if first test had stderr
239 |   });
240 | });
241 | 
242 | // ✅ CORRECT - Include beforeEach with clearStderr
243 | describe('My Tests', () => {
244 |   let client;
245 |   
246 |   before(async () => {
247 |     client = await connect('./config.json');
248 |   });
249 |   
250 |   beforeEach(() => {
251 |     client.clearAllBuffers(); // Prevents all buffer leaking between tests
252 |   });
253 |   
254 |   test('first test', async () => {
255 |     const result = await client.callTool('tool', {});
256 |     // Any stderr is isolated to this test
257 |   });
258 |   
259 |   test('second test', async () => {
260 |     // Clean slate - no stderr from previous tests
261 |     assert.equal(client.getStderr(), ''); // Will pass
262 |   });
263 | });
264 | ```
265 | 
266 | #### Debugging Stderr Issues
267 | If you're experiencing flaky test failures related to unexpected stderr content:
268 | 
269 | 1. **Add clearStderr() to beforeEach** - Most common fix
270 | 2. **Check test isolation** - Ensure each test starts with clean state  
271 | 3. **Debug stderr content** - Log `client.getStderr()` to see what's leaking
272 | 4. **Use afterEach cleanup** - Optional additional cleanup
273 | 
274 | ```javascript
275 | beforeEach(() => {
276 |   client.clearStderr();
277 | });
278 | 
279 | afterEach(() => {
280 |   // Optional: Debug what stderr was generated
281 |   const stderr = client.getStderr();
282 |   if (stderr) {
283 |     console.log('Test generated stderr:', stderr);
284 |   }
285 | });
286 | ```
287 | 
288 | ## Testing Patterns
289 | 
290 | ### 1. Tool Discovery and Validation
291 | ```javascript
292 | describe('Tool Discovery', () => {
293 |   test('should list all expected tools', async () => {
294 |     const tools = await client.listTools();
295 |     
296 |     assert.equal(tools.length, 4, 'Should have 4 tools');
297 |     
298 |     const toolNames = tools.map(tool => tool.name);
299 |     assert.ok(toolNames.includes('calculator'));
300 |     assert.ok(toolNames.includes('text_processor'));
301 |     assert.ok(toolNames.includes('data_validator'));
302 |   });
303 | 
304 |   test('should validate tool schemas', async () => {
305 |     const tools = await client.listTools();
306 |     
307 |     tools.forEach(tool => {
308 |       assert.ok(tool.name, 'Tool should have name');
309 |       assert.ok(tool.description, 'Tool should have description');
310 |       assert.ok(tool.inputSchema, 'Tool should have input schema');
311 |       assert.equal(tool.inputSchema.type, 'object');
312 |     });
313 |   });
314 | });
315 | ```
316 | 
317 | ### 2. Tool Execution Testing
318 | ```javascript
319 | describe('Calculator Tool', () => {
320 |   test('should perform basic arithmetic', async () => {
321 |     const result = await client.callTool('calculator', {
322 |       operation: 'add',
323 |       a: 15,
324 |       b: 27
325 |     });
326 | 
327 |     assert.equal(result.isError, false);
328 |     assert.equal(result.content.length, 1);
329 |     assert.equal(result.content[0].type, 'text');
330 |     assert.equal(result.content[0].text, 'Result: 42');
331 |   });
332 | 
333 |   test('should handle division by zero', async () => {
334 |     const result = await client.callTool('calculator', {
335 |       operation: 'divide',
336 |       a: 10,
337 |       b: 0
338 |     });
339 | 
340 |     assert.equal(result.isError, true);
341 |     assert.ok(result.content[0].text.includes('division by zero'));
342 |   });
343 | 
344 |   test('should validate required parameters', async () => {
345 |     const result = await client.callTool('calculator', {
346 |       operation: 'add'
347 |       // Missing a and b parameters
348 |     });
349 | 
350 |     assert.equal(result.isError, true);
351 |     assert.ok(result.content[0].text.includes('required'));
352 |   });
353 | });
354 | ```
355 | 
356 | ### 3. Multi-Step Workflows
357 | ```javascript
358 | describe('Multi-Step Agent Workflows', () => {
359 |   test('should support complex decision chains', async () => {
360 |     // Step 1: Search for information
361 |     const searchResult = await client.callTool('search_knowledge', {
362 |       query: 'customer support best practices'
363 |     });
364 |     assert.equal(searchResult.isError, false);
365 |     
366 |     // Step 2: Analyze findings
367 |     const analysisResult = await client.callTool('analyze_content', {
368 |       content: searchResult.content[0].text,
369 |       focus: 'actionable recommendations'
370 |     });
371 |     assert.equal(analysisResult.isError, false);
372 |     
373 |     // Step 3: Generate summary based on analysis
374 |     const summaryResult = await client.callTool('generate_summary', {
375 |       source_data: analysisResult.content[0].text,
376 |       format: 'executive_summary'
377 |     });
378 |     assert.equal(summaryResult.isError, false);
379 |     assert.ok(summaryResult.content[0].text.includes('Executive Summary'));
380 |   });
381 | 
382 |   test('should maintain context across tool calls', async () => {
383 |     // Initialize conversation context
384 |     const initResult = await client.callTool('conversation_manager', {
385 |       action: 'initialize',
386 |       user_id: 'test_user_123'
387 |     });
388 |     assert.equal(initResult.isError, false);
389 |     
390 |     const sessionId = extractSessionId(initResult.content[0].text);
391 |     
392 |     // Make context-dependent call
393 |     const contextResult = await client.callTool('conversation_manager', {
394 |       action: 'recall',
395 |       user_id: 'test_user_123',
396 |       session_id: sessionId
397 |     });
398 |     assert.equal(contextResult.isError, false);
399 |     assert.ok(contextResult.content[0].text.includes('session found'));
400 |   });
401 | });
402 | ```
403 | 
404 | ### 4. Error Recovery and Resilience
405 | ```javascript
406 | describe('Error Recovery', () => {
407 |   test('should handle failures gracefully', async () => {
408 |     // Test normal operation
409 |     const normalResult = await client.callTool('external_api_call', {
410 |       endpoint: 'users',
411 |       action: 'list'
412 |     });
413 |     assert.equal(normalResult.isError, false);
414 |     
415 |     // Test failure scenario - should not throw
416 |     const failureResult = await client.callTool('external_api_call', {
417 |       endpoint: 'invalid_endpoint',
418 |       action: 'list'
419 |     });
420 |     assert.equal(failureResult.isError, true);
421 |     assert.ok(failureResult.content[0].text.includes('not found'));
422 |     
423 |     // Test recovery - should work again
424 |     const recoveryResult = await client.callTool('external_api_call', {
425 |       endpoint: 'users',
426 |       action: 'list'
427 |     });
428 |     assert.equal(recoveryResult.isError, false);
429 |   });
430 | 
431 |   test('should handle server restart scenarios', async () => {
432 |     // Verify initial connection
433 |     const tools = await client.listTools();
434 |     assert.ok(tools.length > 0);
435 |     
436 |     // Simulate server issue by calling invalid method
437 |     try {
438 |       await client.sendMessage({
439 |         jsonrpc: '2.0',
440 |         id: 'test-1',
441 |         method: 'invalid_method',
442 |         params: {}
443 |       });
444 |     } catch (error) {
445 |       // Expected error
446 |     }
447 |     
448 |     // Verify connection still works
449 |     const toolsAfter = await client.listTools();
450 |     assert.ok(toolsAfter.length > 0);
451 |   });
452 | });
453 | ```
454 | 
455 | ### ⚠️ Critical: Avoid Concurrent Requests
456 | 
457 | **Never use `Promise.all()` or concurrent requests** with MCP Aegis's programmatic API. MCP communication uses a single stdio process with shared message handlers and buffers. Concurrent requests can cause:
458 | 
459 | - **Buffer conflicts**: Multiple requests writing to the same stdout/stderr streams
460 | - **Message handler interference**: JSON-RPC messages getting mixed or corrupted  
461 | - **Race conditions**: Responses arriving out of order or getting lost
462 | - **Unpredictable test failures**: Flaky tests that pass/fail randomly
463 | 
464 | ```javascript
465 | // ❌ NEVER DO THIS - Causes buffer/message handler conflicts
466 | const promises = tools.map(tool => client.callTool(tool.name, {}));
467 | const results = await Promise.all(promises); // WILL CAUSE ISSUES!
468 | 
469 | // ✅ ALWAYS DO THIS - Sequential requests work reliably
470 | const results = [];
471 | for (const tool of tools) {
472 |   const result = await client.callTool(tool.name, {});
473 |   results.push(result);
474 | }
475 | ```
476 | 
477 | ### 5. Sequential Request Testing
478 | ```javascript
479 | describe('Sequential Request Testing', () => {
480 |   test('should handle sequential requests correctly', async () => {
481 |     const results = [];
482 |     
483 |     // Execute requests sequentially to avoid buffer/message handler conflicts
484 |     for (let i = 0; i < 10; i++) {
485 |       const result = await client.callTool('sequential_operation', { id: i });
486 |       results.push(result);
487 |     }
488 |     
489 |     results.forEach((result, i) => {
490 |       assert.equal(result.isError, false, `Request ${i} should succeed`);
491 |       assert.ok(result.content[0].text.includes(`id: ${i}`));
492 |     });
493 |   });
494 | 
495 |   test('should handle large payload processing', async () => {
496 |     const largeText = 'x'.repeat(50000); // 50KB text
497 |     
498 |     const result = await client.callTool('text_processor', {
499 |       text: largeText,
500 |       operation: 'word_count'
501 |     });
502 |     
503 |     assert.equal(result.isError, false);
504 |     assert.ok(result.content[0].text.includes('50000'));
505 |   });
506 | });
507 | ```
508 | 
509 | ### ⚠️ Performance Testing Not Recommended
510 | 
511 | **Performance testing is not recommended in programmatic tests** due to CI environment variability. CI environments have:
512 | - Highly variable resource allocation and sharing
513 | - Unpredictable I/O performance and network latency  
514 | - JIT compilation delays and garbage collection interference
515 | - Container and process initialization overhead
516 | 
517 | **Use dedicated performance testing tools instead:**
518 | - Load testing frameworks (Artillery, k6, Apache JMeter)
519 | - APM tools (New Relic, DataDog, AppDynamics)
520 | - Custom monitoring with controlled environments
521 | - Synthetic monitoring services
522 | 
523 | **If you must include timing validation**, use extremely lenient thresholds (5+ seconds) and focus on detecting major regressions rather than precise performance requirements.
524 | 
525 | ### 6. Dynamic Validation Logic
526 | ```javascript
527 | describe('Dynamic Validation', () => {
528 |   test('should validate business rules dynamically', async () => {
529 |     const tools = await client.listTools();
530 |     
531 |     // Dynamic validation based on actual tools
532 |     const calculatorTool = tools.find(t => t.name === 'calculator');
533 |     if (calculatorTool) {
534 |       const supportedOps = calculatorTool.inputSchema.properties.operation.enum;
535 |       
536 |       for (const operation of supportedOps) {
537 |         const result = await client.callTool('calculator', {
538 |           operation,
539 |           a: 10,
540 |           b: 5
541 |         });
542 |         assert.equal(result.isError, false, `Operation ${operation} should work`);
543 |       }
544 |     }
545 |   });
546 | 
547 |   test('should generate test cases from schema', async () => {
548 |     const tools = await client.listTools();
549 |     
550 |     for (const tool of tools) {
551 |       const schema = tool.inputSchema;
552 |       const testCases = generateTestCases(schema);
553 |       
554 |       for (const testCase of testCases) {
555 |         const result = await client.callTool(tool.name, testCase.input);
556 |         
557 |         if (testCase.shouldSucceed) {
558 |           assert.equal(result.isError, false, 
559 |             `${tool.name} should succeed with ${JSON.stringify(testCase.input)}`);
560 |         } else {
561 |           assert.equal(result.isError, true,
562 |             `${tool.name} should fail with ${JSON.stringify(testCase.input)}`);
563 |         }
564 |       }
565 |     }
566 |   });
567 | });
568 | 
569 | function generateTestCases(schema) {
570 |   // Generate test cases based on JSON schema
571 |   const testCases = [];
572 |   
573 |   // Valid case with all required properties
574 |   const validCase = {};
575 |   for (const [prop, propSchema] of Object.entries(schema.properties || {})) {
576 |     if (propSchema.type === 'string') {
577 |       validCase[prop] = 'test_value';
578 |     } else if (propSchema.type === 'number') {
579 |       validCase[prop] = 42;
580 |     }
581 |   }
582 |   testCases.push({ input: validCase, shouldSucceed: true });
583 |   
584 |   // Invalid case missing required properties
585 |   if (schema.required?.length > 0) {
586 |     testCases.push({ input: {}, shouldSucceed: false });
587 |   }
588 |   
589 |   return testCases;
590 | }
591 | ```
592 | 
593 | ## Advanced Testing Scenarios
594 | 
595 | ### State Management Testing
596 | ```javascript
597 | describe('State Management', () => {
598 |   test('should maintain state across multiple calls', async () => {
599 |     // Initialize state
600 |     await client.callTool('state_manager', {
601 |       action: 'set',
602 |       key: 'test_key',
603 |       value: 'test_value'
604 |     });
605 |     
606 |     // Verify state persists
607 |     const result = await client.callTool('state_manager', {
608 |       action: 'get',
609 |       key: 'test_key'
610 |     });
611 |     
612 |     assert.equal(result.content[0].text, 'test_value');
613 |   });
614 | });
615 | ```
616 | 
617 | ### Integration Testing with External Services
618 | ```javascript
619 | describe('External Service Integration', () => {
620 |   test('should handle API dependencies', async () => {
621 |     // Mock external service responses if needed
622 |     const result = await client.callTool('api_client', {
623 |       endpoint: 'https://jsonplaceholder.typicode.com/posts/1',
624 |       method: 'GET'
625 |     });
626 |     
627 |     assert.equal(result.isError, false);
628 |     const responseData = JSON.parse(result.content[0].text);
629 |     assert.ok(responseData.id);
630 |     assert.ok(responseData.title);
631 |   });
632 | });
633 | ```
634 | 
635 | ### Custom Assertion Helpers
636 | ```javascript
637 | // Helper functions for common assertions
638 | function assertValidMCPResponse(result) {
639 |   assert.ok(result.content, 'Should have content');
640 |   assert.ok(Array.isArray(result.content), 'Content should be array');
641 |   assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
642 | }
643 | 
644 | function assertTextContent(result, expectedSubstring) {
645 |   assertValidMCPResponse(result);
646 |   assert.equal(result.content[0].type, 'text');
647 |   assert.ok(result.content[0].text.includes(expectedSubstring));
648 | }
649 | 
650 | function assertToolSchema(tool) {
651 |   assert.ok(tool.name, 'Tool should have name');
652 |   assert.ok(tool.description, 'Tool should have description');
653 |   assert.ok(tool.inputSchema, 'Tool should have schema');
654 |   assert.equal(tool.inputSchema.type, 'object');
655 | }
656 | 
657 | // Usage
658 | test('should return valid calculation result', async () => {
659 |   const result = await client.callTool('calculator', { operation: 'add', a: 2, b: 3 });
660 |   assertTextContent(result, 'Result: 5');
661 | });
662 | ```
663 | 
664 | ## Framework Integration
665 | 
666 | ### Jest Integration
667 | ```javascript
668 | // jest.config.js
669 | module.exports = {
670 |   testEnvironment: 'node',
671 |   setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
672 |   testMatch: ['**/*.programmatic.test.js']
673 | };
674 | 
675 | // test/setup.js
676 | global.mcpClient = null;
677 | 
678 | beforeAll(async () => {
679 |   const { connect } = require('mcp-aegis');
680 |   global.mcpClient = await connect('./aegis.config.json');
681 | });
682 | 
683 | afterAll(async () => {
684 |   if (global.mcpClient) {
685 |     await global.mcpClient.disconnect();
686 |   }
687 | });
688 | ```
689 | 
690 | ### Mocha Integration
691 | ```javascript
692 | // test/helpers/mcp-setup.js
693 | const { connect } = require('mcp-aegis');
694 | 
695 | let client;
696 | 
697 | exports.mochaHooks = {
698 |   beforeAll: async () => {
699 |     client = await connect('./aegis.config.json');
700 |   },
701 |   afterAll: async () => {
702 |     if (client) {
703 |       await client.disconnect();
704 |     }
705 |   }
706 | };
707 | 
708 | exports.getClient = () => client;
709 | ```
710 | 
711 | ### TypeScript Support
712 | ```typescript
713 | import { test, describe, before, after } from 'node:test';
714 | import { strict as assert } from 'node:assert';
715 | import { connect, MCPClient } from 'mcp-aegis';
716 | 
717 | describe('TypeScript MCP Tests', () => {
718 |   let client: MCPClient;
719 | 
720 |   before(async () => {
721 |     client = await connect('./aegis.config.json');
722 |   });
723 | 
724 |   after(async () => {
725 |     await client.disconnect();
726 |   });
727 | 
728 |   test('should have typed responses', async () => {
729 |     const tools = await client.listTools();
730 |     
731 |     // TypeScript provides full type safety
732 |     assert.ok(Array.isArray(tools));
733 |     tools.forEach(tool => {
734 |       assert.ok(typeof tool.name === 'string');
735 |       assert.ok(typeof tool.description === 'string');
736 |       assert.ok(typeof tool.inputSchema === 'object');
737 |     });
738 |   });
739 | });
740 | ```
741 | 
742 | ## Best Practices for AI Agents
743 | 
744 | ### 1. Test Structure Generation
745 | ```javascript
746 | // Template for generating comprehensive test suites
747 | function generateTestSuite(serverConfig, toolList) {
748 |   return `
749 | describe('${serverConfig.name} Programmatic Tests', () => {
750 |   let client;
751 | 
752 |   before(async () => {
753 |     client = await connect('${serverConfig.configPath}');
754 |   });
755 | 
756 |   after(async () => {
757 |     if (client?.connected) {
758 |       await client.disconnect();
759 |     }
760 |   });
761 | 
762 |   beforeEach(() => {
763 |     client.clearStderr();
764 |   });
765 | 
766 |   describe('Protocol Compliance', () => {
767 |     test('should complete MCP handshake', async () => {
768 |       assert.ok(client.connected, 'Client should be connected');
769 |     });
770 | 
771 |     test('should list available tools', async () => {
772 |       const tools = await client.listTools();
773 |       assert.ok(Array.isArray(tools));
774 |       assert.equal(tools.length, ${toolList.length});
775 |     });
776 |   });
777 | 
778 |   ${toolList.map(tool => generateToolTests(tool)).join('\n\n')}
779 | });`;
780 | }
781 | 
782 | function generateToolTests(tool) {
783 |   return `
784 |   describe('${tool.name} Tool', () => {
785 |     test('should execute successfully with valid parameters', async () => {
786 |       const result = await client.callTool('${tool.name}', ${JSON.stringify(tool.validParams)});
787 |       assert.equal(result.isError, false);
788 |       assertValidMCPResponse(result);
789 |     });
790 | 
791 |     test('should handle invalid parameters gracefully', async () => {
792 |       const result = await client.callTool('${tool.name}', {});
793 |       assert.equal(result.isError, true);
794 |       assertTextContent(result, 'required');
795 |     });
796 |   });`;
797 | }
798 | ```
799 | 
800 | ### 2. Configuration Management
801 | ```javascript
802 | // Detect server configuration from project structure
803 | function detectServerConfig(projectPath) {
804 |   const packageJson = readPackageJson(projectPath);
805 |   
806 |   return {
807 |     name: packageJson.name || 'MCP Server',
808 |     command: detectRuntime(packageJson.engines),
809 |     args: [detectServerFile(projectPath)],
810 |     cwd: projectPath,
811 |     startupTimeout: 5000,
812 |     env: {
813 |       NODE_ENV: 'test',
814 |       ...(packageJson.mcpConfig?.testEnv || {})
815 |     }
816 |   };
817 | }
818 | 
819 | function detectRuntime(engines = {}) {
820 |   if (engines.node) return 'node';
821 |   if (engines.python) return 'python';
822 |   return 'node'; // default
823 | }
824 | ```
825 | 
826 | ### 3. Error Pattern Recognition
827 | ```javascript
828 | // Common error patterns to test for
829 | const errorPatterns = [
830 |   { type: 'validation', keywords: ['required', 'invalid', 'missing'] },
831 |   { type: 'not_found', keywords: ['not found', 'does not exist'] },
832 |   { type: 'permission', keywords: ['permission', 'unauthorized', 'forbidden'] },
833 |   { type: 'network', keywords: ['connection', 'timeout', 'unreachable'] }
834 | ];
835 | 
836 | function categorizeError(errorText) {
837 |   for (const pattern of errorPatterns) {
838 |     if (pattern.keywords.some(keyword => 
839 |         errorText.toLowerCase().includes(keyword))) {
840 |       return pattern.type;
841 |     }
842 |   }
843 |   return 'unknown';
844 | }
845 | ```
846 | 
847 | ### 4. Built-in Functional Monitoring
848 | ```javascript
849 | // Focus on functional correctness rather than timing
850 | class FunctionalMonitor {
851 |   constructor() {
852 |     this.results = new Map();
853 |   }
854 | 
855 |   async validateTool(client, toolName, params) {
856 |     const result = await client.callTool(toolName, params);
857 |     
858 |     const validation = {
859 |       success: !result.isError,
860 |       hasContent: result.content && result.content.length > 0,
861 |       contentType: result.content?.[0]?.type,
862 |       timestamp: new Date().toISOString()
863 |     };
864 |     
865 |     if (!this.results.has(toolName)) {
866 |       this.results.set(toolName, []);
867 |     }
868 |     this.results.get(toolName).push(validation);
869 |     
870 |     return { result, validation };
871 |   }
872 | 
873 |   getReliabilityStats(toolName) {
874 |     const validations = this.results.get(toolName) || [];
875 |     const successful = validations.filter(v => v.success).length;
876 |     
877 |     return {
878 |       totalCalls: validations.length,
879 |       successRate: successful / validations.length,
880 |       failureRate: (validations.length - successful) / validations.length,
881 |       lastValidation: validations[validations.length - 1]?.timestamp
882 |     };
883 |   }
884 | }
885 | ```
886 | 
887 | ## Test Execution Commands
888 | 
889 | **CRITICAL**: Use the correct test commands for this SFCC Dev MCP project:
890 | 
891 | ```bash
892 | # Run individual MCP programmatic test (CORRECT for this project)
893 | node --test tests/mcp/node/specific-test.programmatic.test.js
894 | 
895 | # Run all MCP programmatic tests (CORRECT for this project)  
896 | npm run test:mcp:node
897 | 
898 | # Run all MCP tests (YAML + programmatic) (CORRECT for this project)
899 | npm run test:mcp:all
900 | 
901 | # ❌ WRONG: Don't use npm test with individual files for MCP tests
902 | # npm test -- tests/mcp/node/specific-test.programmatic.test.js  # This runs Jest!
903 | 
904 | # ❌ WRONG: Don't use Jest for MCP programmatic tests  
905 | # npx jest --testMatch="**/*.programmatic.test.js"  # Jest doesn't handle MCP tests
906 | 
907 | # Watch mode for development (Node.js test runner)
908 | node --test --watch tests/mcp/node/*.programmatic.test.js
909 | 
910 | # Jest is used for unit tests only (src/ directory)
911 | jest base-handler.test.ts
912 | 
913 | # Complete test suite (Jest + MCP tests)
914 | npm test
915 | ```
916 | 
917 | **Package.json Script Reference for this project:**
918 | - `npm run test:mcp:node` → `node --test tests/mcp/node/*.programmatic.test.js`
919 | - `npm run test:mcp:yaml` → Aegis YAML tests (docs-only mode)
920 | - `npm run test:mcp:yaml:full` → Aegis YAML tests (full mode)
921 | - `npm run test:mcp:all` → All MCP tests (YAML + programmatic)
922 | - `npm test` → Jest unit tests + MCP tests
923 | 
924 | ---
925 | 
926 | **Key Success Factors for Programmatic Testing:**
927 | 1. Always use proper lifecycle management (before/after hooks)
928 | 2. Clear stderr between tests for isolation
929 | 3. Use appropriate assertion libraries and helpers
930 | 4. Include comprehensive error handling tests
931 | 5. Test both success and failure scenarios
932 | 6. Implement performance monitoring for critical operations
933 | 7. Use TypeScript for better development experience
934 | 8. Integrate with existing testing frameworks and CI/CD pipelines
935 | 
```

--------------------------------------------------------------------------------
/docs-site/pages/DevelopmentPage.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import React from 'react';
  2 | import SEO from '../components/SEO';
  3 | import BreadcrumbSchema from '../components/BreadcrumbSchema';
  4 | import StructuredData from '../components/StructuredData';
  5 | import CodeBlock, { InlineCode } from '../components/CodeBlock';
  6 | import { H1, PageSubtitle, H2, H3 } from '../components/Typography';
  7 | import { SITE_DATES } from '../constants';
  8 | 
  9 | const DevelopmentPage: React.FC = () => {
 10 |     const developmentStructuredData = {
 11 |         "@context": "https://schema.org",
 12 |         "@type": "TechArticle",
 13 |         "headline": "Development Guide - SFCC Development MCP Server",
 14 |         "description": "Contributing to the SFCC Development MCP Server project. Learn the architecture, setup development environment, and contribute new features.",
 15 |         "author": {
 16 |             "@type": "Person",
 17 |             "name": "Thomas Theunen"
 18 |         },
 19 |         "publisher": {
 20 |             "@type": "Person",
 21 |             "name": "Thomas Theunen"
 22 |         },
 23 |         "datePublished": SITE_DATES.PUBLISHED,
 24 |         "dateModified": SITE_DATES.MODIFIED,
 25 |         "url": "https://sfcc-mcp-dev.rhino-inquisitor.com/development/",
 26 |         "about": [
 27 |           {
 28 |             "@type": "SoftwareApplication",
 29 |             "name": "SFCC Development MCP Server",
 30 |             "applicationCategory": "DeveloperApplication",
 31 |             "operatingSystem": "Node.js",
 32 |             "offers": {
 33 |               "@type": "Offer",
 34 |               "price": "0",
 35 |               "priceCurrency": "USD",
 36 |               "availability": "https://schema.org/InStock"
 37 |             }
 38 |           }
 39 |         ],
 40 |         "mainEntity": {
 41 |             "@type": "Guide",
 42 |             "name": "SFCC MCP Development Guide"
 43 |         }
 44 |     };
 45 | 
 46 |     return (
 47 |         <div className="max-w-6xl mx-auto px-6 py-12">
 48 |             <SEO 
 49 |                 title="Development Guide"
 50 |                 description="Contributing to the SFCC Development MCP Server project. Learn the architecture, setup development environment, and contribute new features."
 51 |                 keywords="SFCC Development MCP Server, SFCC MCP development, TypeScript MCP server, Model Context Protocol development, SFCC tools development"
 52 |                 canonical="/development/"
 53 |                 ogType="article"
 54 |             />
 55 |             <BreadcrumbSchema items={[
 56 |                 { name: "Home", url: "/" },
 57 |                 { name: "Development", url: "/development/" }
 58 |             ]} />
 59 |             <StructuredData data={developmentStructuredData} />
 60 |             
 61 |             <H1 id="development-guide">👨‍💻 Development Guide</H1>
 62 |             <PageSubtitle>Contributing to the SFCC Development MCP Server project</PageSubtitle>
 63 | 
 64 |             <H2 id="getting-started">🚀 Getting Started</H2>
 65 |             
 66 |             <H3 id="prerequisites">Prerequisites</H3>
 67 |             <ul className="list-disc pl-6 space-y-1">
 68 |                 <li><strong>Node.js</strong> 18 or higher</li>
 69 |                 <li><strong>npm</strong> 8 or higher</li>
 70 |                 <li><strong>Git</strong> for version control</li>
 71 |                 <li><strong>TypeScript</strong> knowledge recommended</li>
 72 |             </ul>
 73 | 
 74 |             <H3 id="local-development-setup">Local Development Setup</H3>
 75 |             <CodeBlock language="bash" code={`
 76 | # Clone the repository
 77 | git clone https://github.com/taurgis/sfcc-dev-mcp.git
 78 | cd sfcc-dev-mcp
 79 | 
 80 | # Install dependencies
 81 | npm install
 82 | 
 83 | # Build TypeScript
 84 | npm run build
 85 | 
 86 | # Run tests
 87 | npm test
 88 | 
 89 | # Start in development mode
 90 | npm run dev -- --dw-json /Users/username/sfcc-project/dw.json
 91 |             `} />
 92 | 
 93 |             <H2 id="project-architecture">🏗️ Project Architecture</H2>
 94 |             
 95 |             <p className="text-[11px] text-gray-500 mb-4">Surface: <strong>36+ specialized tools</strong> spanning documentation, best practices, SFRA, cartridge generation, runtime logs, job logs, system & custom objects, site preferences, and code versions.</p>
 96 |             <H3 id="directory-structure">Directory Structure</H3>
 97 |             <CodeBlock language="text" code={`
 98 | sfcc-dev-mcp/
 99 | ├── src/
100 | │   ├── main.ts                    # CLI entry point
101 | │   ├── index.ts                   # Package exports
102 | │   ├── core/                      # Core MCP server & tool definitions
103 | │   │   ├── server.ts              # MCP server (registers handlers, capability gating)
104 | │   │   ├── tool-definitions.ts    # All tool schemas grouped by category
105 | │   │   └── handlers/              # Modular tool handlers
106 | │   │       ├── base-handler.ts
107 | │   │       ├── docs-handler.ts
108 | │   │       ├── best-practices-handler.ts
109 | │   │       ├── sfra-handler.ts
110 | │   │       ├── log-handler.ts
111 | │   │       ├── job-log-handler.ts
112 | │   │       ├── system-object-handler.ts
113 | │   │       ├── code-version-handler.ts
114 | │   │       └── cartridge-handler.ts
115 | │   ├── clients/                   # API & domain clients (logic, not routing)
116 | │   │   ├── base/                  # Shared HTTP + auth
117 | │   │   │   ├── http-client.ts
118 | │   │   │   ├── ocapi-auth-client.ts
119 | │   │   │   └── oauth-token.ts
120 | │   │   ├── logs/                  # Modular log system (composition)
121 | │   │   │   ├── log-client.ts          # Orchestrator
122 | │   │   │   ├── webdav-client-manager.ts # WebDAV auth + setup
123 | │   │   │   ├── log-file-reader.ts     # Range / tail reads
124 | │   │   │   ├── log-file-discovery.ts  # Listing & filtering
125 | │   │   │   ├── log-processor.ts       # Parsing & normalization
126 | │   │   │   ├── log-analyzer.ts        # Pattern & health analysis
127 | │   │   │   ├── log-formatter.ts       # Output shaping
128 | │   │   │   ├── log-constants.ts       # Central constants/config
129 | │   │   │   └── log-types.ts           # Type definitions
130 | │   │   ├── docs/                  # Modular documentation system
131 | │   │   │   ├── documentation-scanner.ts
132 | │   │   │   ├── class-content-parser.ts
133 | │   │   │   ├── class-name-resolver.ts
134 | │   │   │   ├── referenced-types-extractor.ts
135 | │   │   │   └── index.ts
136 | │   │   ├── docs-client.ts
137 | │   │   ├── sfra-client.ts
138 | │   │   ├── best-practices-client.ts
139 | │   │   ├── cartridge-generation-client.ts
140 | │   │   ├── ocapi/
141 | │   │   │   ├── site-preferences-client.ts
142 | │   │   │   └── system-objects-client.ts
143 | │   │   ├── ocapi-client.ts
144 | │   │   ├── log-client.ts              # Backwards compat wrapper
145 | │   └── best-practices-client.ts   # (already listed above? keep once)
146 | │   ├── services/                 # Dependency injection service layer
147 | │   │   ├── file-system-service.ts
148 | │   │   ├── path-service.ts
149 | │   │   └── index.ts
150 | │   ├── tool-configs/             # Tool grouping & category configs
151 | │   │   ├── docs-tool-config.ts
152 | │   │   ├── sfra-tool-config.ts
153 | │   │   ├── best-practices-tool-config.ts
154 | │   │   ├── log-tool-config.ts
155 | │   │   ├── job-log-tool-config.ts
156 | │   │   ├── system-object-tool-config.ts
157 | │   │   ├── cartridge-tool-config.ts
158 | │   │   └── code-version-tool-config.ts
159 | │   ├── config/
160 | │   │   ├── configuration-factory.ts   # Mode & capability resolution
161 | │   │   └── dw-json-loader.ts          # Secure dw.json loading
162 | │   ├── utils/
163 | │   │   ├── cache.ts
164 | │   │   ├── logger.ts
165 | │   │   ├── path-resolver.ts
166 | │   │   ├── query-builder.ts
167 | │   │   ├── utils.ts
168 | │   │   ├── validator.ts
169 | │   └── types/
170 | │       └── types.ts
171 | ├── tests/                        # Jest + MCP YAML + programmatic tests
172 | │   ├── *.test.ts
173 | │   ├── mcp/yaml/*.mcp.yml        # Declarative tool tests
174 | │   ├── mcp/node/*.programmatic.test.js
175 | │   └── servers/webdav/           # Mock WebDAV server fixtures
176 | ├── docs/                         # SFCC & best practices markdown sources
177 | ├── docs-site/                    # React + Vite documentation site
178 | ├── scripts/                      # Conversion & build scripts
179 | └── ai-instructions/              # AI platform instruction sets
180 |             `} />
181 | 
182 |             <H3 id="configuration-system">Configuration & Capability Gating (<InlineCode>src/config/</InlineCode>)</H3>
183 |             <ul className="list-disc pl-6 space-y-1">
184 |               <li><strong>configuration-factory.ts</strong>: Determines operating mode & derives capabilities (<InlineCode>canAccessLogs</InlineCode>, <InlineCode>canAccessJobLogs</InlineCode>, <InlineCode>canAccessOCAPI</InlineCode>, <InlineCode>canAccessSitePrefs</InlineCode>).</li>
185 |               <li><strong>dw-json-loader.ts</strong>: Safe credential ingestion, prevents accidental misuse.</li>
186 |               <li><strong>Capability Gating</strong>: No credentials → docs & best practice tools only; WebDAV creds → runtime + job logs; Data API creds → system & custom objects, site preferences, code versions.</li>
187 |               <li><strong>Least Privilege</strong>: Tools requiring unavailable capabilities never registered.</li>
188 |             </ul>
189 | 
190 |             <H3 id="adding-new-tools">Adding New Tools (Updated Flow)</H3>
191 |             <ol className="list-decimal pl-6 space-y-2">
192 |               <li><strong>Define schema</strong> in correct category array inside <InlineCode>tool-definitions.ts</InlineCode>.</li>
193 |               <li><strong>Decide placement</strong> – extend existing handler or create new handler extending <InlineCode>BaseToolHandler</InlineCode>.</li>
194 |               <li><strong>Implement logic</strong> inside a client/service (keep handler thin).</li>
195 |               <li><strong>Register handler</strong> only if new category (update <InlineCode>registerHandlers()</InlineCode> in <InlineCode>server.ts</InlineCode>).</li>
196 |               <li><strong>Discover response format</strong>: Run with <InlineCode>npx aegis query [tool]</InlineCode> BEFORE writing tests; capture real JSON shape.</li>
197 |               <li><strong>Add tests</strong>: Jest unit + YAML (docs & full mode as applicable) + programmatic Node tests if complex.</li>
198 |               <li><strong>Update docs</strong>: This page + README + copilot instructions when categories/counts change.</li>
199 |             </ol>
200 | 
201 |             <H3 id="testing-strategy">Testing Strategy</H3>
202 |             <p className="text-xs text-gray-500 mb-2">Programmatic MCP tests must use Node test runner (e.g. <InlineCode>node --test tests/mcp/node/your-test.programmatic.test.js</InlineCode>) — do NOT invoke via <InlineCode>npm test -- file</InlineCode> (Jest only).</p>
203 |             <H3 id="unit-tests">Unit Tests</H3>
204 |             <CodeBlock language="bash" code={`
205 | # Run all tests
206 | npm test
207 | 
208 | # Run specific test file
209 | npm test base-http-client.test.ts
210 | 
211 | # Run with coverage
212 | npm run test:coverage
213 | 
214 | # Watch mode for development
215 | npm run test:watch
216 |             `} />
217 | 
218 |             <H3 id="available-test-scripts">Available Test Scripts</H3>
219 |             <CodeBlock language="bash" code={`
220 | # Core testing scripts from package.json
221 | npm test              # Run all tests with Jest
222 | npm run test:watch    # Run tests in watch mode
223 | npm run test:coverage # Run tests with coverage report
224 | 
225 | # Linting and code quality
226 | npm run lint          # Check code style
227 | npm run lint:fix      # Auto-fix linting issues
228 | npm run lint:check    # Check with zero warnings
229 |             `} />
230 | 
231 |             <H3 id="manual-testing">Manual Testing</H3>
232 |             <CodeBlock language="bash" code={`
233 | # Test with real SFCC instance (create your own test-dw.json)
234 | npm run dev -- --dw-json /Users/username/sfcc-project/test-dw.json --debug
235 | 
236 | # Test documentation-only mode
237 | npm run dev -- --debug
238 |             `} />
239 | 
240 |             <H2 id="documentation-updates">📚 Documentation Updates</H2>
241 |             
242 |             <H3 id="updating-sfcc-documentation">Updating SFCC Documentation</H3>
243 |             
244 |             <p><strong>1. Add/Update Markdown Files</strong> in <InlineCode>docs/</InlineCode>:</p>
245 |             <CodeBlock language="bash" code={`
246 | # Add new class documentation
247 | echo "# NewClass\\n\\nDescription..." > docs/dw_catalog/NewClass.md
248 |             `} />
249 | 
250 |             <p><strong>2. Run Documentation Conversion:</strong></p>
251 |             <CodeBlock language="bash" code={`
252 | # Convert and process documentation (requires axios and cheerio)
253 | npm run convert-docs
254 | 
255 | # Test with limited conversion
256 | npm run convert-docs:test
257 | 
258 | # Limited conversion (5 files)
259 | npm run convert-docs:limit
260 |             `} />
261 | 
262 |             <p><strong>3. Test Documentation Tools:</strong></p>
263 |             <CodeBlock language="bash" code={`
264 | # Test documentation access with your changes
265 | npm run dev -- --debug true
266 | # Then use MCP client to test get_sfcc_class_info with "NewClass"
267 |             `} />
268 | 
269 |             <H3 id="updating-github-pages">Updating Documentation Site</H3>
270 |             <p>The documentation site (<InlineCode>docs-site/</InlineCode>) is a React + Vite app. Deployment is handled by GitHub Actions after changes are pushed to the default branch.</p>
271 |             <ol className="list-decimal pl-6 space-y-1">
272 |               <li><strong>Edit Content</strong>: Modify or add pages/components under <InlineCode>docs-site/</InlineCode>.</li>
273 |               <li><strong>Local Preview</strong>:
274 |                 <CodeBlock language="bash" code={`cd docs-site
275 | npm install
276 | npm run dev  # Opens Vite dev server (default http://localhost:5173)
277 |                 `} />
278 |               </li>
279 |               <li><strong>Build (optional check)</strong>:
280 |                 <CodeBlock language="bash" code={`cd docs-site
281 | npm run build  # Generates dist/ with static assets
282 |                 `} />
283 |               </li>
284 |               <li><strong>Push Changes</strong>: CI workflow publishes the built site to GitHub Pages.</li>
285 |               <li><strong>Search Index / Sitemap</strong>: Automatically generated via build scripts (<InlineCode>generate:search-index</InlineCode>, <InlineCode>generate:sitemap</InlineCode>).</li>
286 |             </ol>
287 | 
288 |             <H2 id="coding-standards">🎯 Coding Standards</H2>
289 |             
290 |             <H3 id="typescript-guidelines">TypeScript Guidelines</H3>
291 |             <CodeBlock language="typescript" code={`
292 | // Use explicit types
293 | interface ToolParams {
294 |   readonly query: string;
295 |   readonly limit?: number;
296 | }
297 | 
298 | // Use proper error handling
299 | async function riskyOperation(): Promise<Result> {
300 |   try {
301 |     return await performOperation();
302 |   } catch (error) {
303 |     this.logger.error('Operation failed', { error: error.message });
304 |     throw new OperationError('Failed to perform operation', error);
305 |   }
306 | }
307 | 
308 | // Use meaningful names
309 | const searchProductsByCategory = (categoryId: string) => {
310 |   // Implementation
311 | };
312 |             `} />
313 | 
314 |             <H3 id="code-organization">Code Organization</H3>
315 |             <ul className="list-disc pl-6 space-y-1">
316 |                 <li><strong>Single Responsibility</strong>: Each class/function has one clear purpose</li>
317 |                 <li><strong>Dependency Injection</strong>: Use constructor injection for dependencies</li>
318 |                 <li><strong>Error Boundaries</strong>: Proper error handling at service boundaries</li>
319 |                 <li><strong>Logging</strong>: Comprehensive logging for debugging and monitoring</li>
320 |             </ul>
321 | 
322 |             <H3 id="git-workflow">Git Workflow</H3>
323 |             <CodeBlock language="bash" code={`
324 | # Create feature branch from develop
325 | git checkout develop
326 | git pull origin develop
327 | git checkout -b feature/new-tool-name
328 | 
329 | # Make atomic commits
330 | git add src/clients/my-client.ts
331 | git commit -m "feat: add my new tool implementation"
332 | 
333 | git add tests/my-client.test.ts  
334 | git commit -m "test: add unit tests for my new tool"
335 | 
336 | git add docs-site/tools.md
337 | git commit -m "docs: update tools documentation"
338 | 
339 | # Push and create PR to develop branch
340 | git push origin feature/new-tool-name
341 |             `} />
342 | 
343 |             <p><strong>Commit Message Convention:</strong></p>
344 |             <ul className="list-disc pl-6 space-y-1">
345 |                 <li><InlineCode>feat:</InlineCode> - New features</li>
346 |                 <li><InlineCode>fix:</InlineCode> - Bug fixes</li>
347 |                 <li><InlineCode>docs:</InlineCode> - Documentation updates</li>
348 |                 <li><InlineCode>test:</InlineCode> - Test additions/modifications</li>
349 |                 <li><InlineCode>refactor:</InlineCode> - Code refactoring</li>
350 |                 <li><InlineCode>chore:</InlineCode> - Build process or auxiliary tool changes</li>
351 |             </ul>
352 | 
353 |             <H2 id="testing-best-practices">🧪 Testing Best Practices</H2>
354 |             
355 |             <H3 id="test-structure">Test Structure</H3>
356 |             <CodeBlock language="typescript" code={`
357 | describe('FeatureName', () => {
358 |   let client: MyClient;
359 |   let mockHttpClient: jest.Mocked<HttpClient>;
360 |   
361 |   beforeEach(() => {
362 |     mockHttpClient = createMockHttpClient();
363 |     client = new MyClient(mockHttpClient, mockLogger);
364 |   });
365 |   
366 |   describe('methodName', () => {
367 |     it('should handle success case', async () => {
368 |       // Arrange
369 |       const input = { query: 'test' };
370 |       const mockResponse = { data: 'mock response' };
371 |       mockHttpClient.get.mockResolvedValue(mockResponse);
372 |       
373 |       // Act
374 |       const result = await client.methodName(input);
375 |       
376 |       // Assert
377 |       expect(result).toBeDefined();
378 |       expect(mockHttpClient.get).toHaveBeenCalledWith('/expected/path');
379 |     });
380 |     
381 |     it('should handle error case', async () => {
382 |       // Arrange
383 |       const input = { query: 'test' };
384 |       mockHttpClient.get.mockRejectedValue(new Error('Network error'));
385 |       
386 |       // Act & Assert
387 |       await expect(client.methodName(input)).rejects.toThrow('Network error');
388 |     });
389 |   });
390 | });
391 |             `} />
392 | 
393 |             <H3 id="mock-strategy">Mock Strategy</H3>
394 |             <CodeBlock language="typescript" code={`
395 | // Mock external dependencies using Jest
396 | const mockLogger = {
397 |   info: jest.fn(),
398 |   error: jest.fn(),
399 |   debug: jest.fn(),
400 |   warn: jest.fn()
401 | };
402 | 
403 | // Use factories for complex mocks
404 | const createMockSFCCResponse = (overrides = {}) => ({
405 |   statusCode: 200,
406 |   headers: { 'content-type': 'application/json' },
407 |   data: 'Mock response data',
408 |   ...overrides
409 | });
410 |             `} />
411 | 
412 |             <H3 id="testing-files-available">Testing Coverage Overview</H3>
413 |             <ul className="list-disc pl-6 space-y-1">
414 |               <li><strong>Unit Clients</strong>: HTTP/auth, OCAPI subclients, docs, SFRA, best practices, cartridge generation.</li>
415 |               <li><strong>Handlers</strong>: Each modular handler has focused tests (error shaping, capability filtering).</li>
416 |               <li><strong>Log System</strong>: Discovery, reader, processor, analyzer, formatter modules.</li>
417 |               <li><strong>Job Logs</strong>: Parsing & multi-level consolidation logic.</li>
418 |               <li><strong>MCP Protocol Tests</strong>: YAML declarative + programmatic (Node) in <InlineCode>tests/mcp/</InlineCode>.</li>
419 |               <li><strong>WebDAV Mock</strong>: Integration environment for log + job retrieval.</li>
420 |             </ul>
421 | 
422 |             <H3 id="handler-architecture">Handler Architecture</H3>
423 |             <ul className="list-disc pl-6 space-y-1">
424 |               <li><strong>BaseToolHandler</strong>: Central timing, error normalization, logger integration.</li>
425 |               <li><strong>Category Isolation</strong>: Each functional domain kept small & cohesive.</li>
426 |               <li><strong>Extensibility</strong>: New feature area → new handler; minimal churn to existing code.</li>
427 |               <li><strong>Testing Benefit</strong>: Handlers test orchestration; clients test domain logic.</li>
428 |             </ul>
429 | 
430 |             <H3 id="services-di">Services & Dependency Injection</H3>
431 |             <ul className="list-disc pl-6 space-y-1">
432 |               <li><strong>FileSystemService</strong> & <strong>PathService</strong>: Abstract Node APIs for test isolation.</li>
433 |               <li><strong>Client Composition</strong>: Pass services or mocks explicitly—no hidden globals.</li>
434 |               <li><strong>Deterministic Tests</strong>: Avoids brittle fs/path mocking at module level.</li>
435 |             </ul>
436 | 
437 |             <H3 id="log-architecture">Log & Job Log Architecture</H3>
438 |             <ul className="list-disc pl-6 space-y-1">
439 |               <li><strong>Reader</strong>: Range tail reads minimize bandwidth.</li>
440 |               <li><strong>Processor</strong>: Normalizes raw lines → structured entries.</li>
441 |               <li><strong>Analyzer</strong>: Pattern extraction, severity grouping, health scoring.</li>
442 |               <li><strong>Formatter</strong>: Produces human-oriented summaries for MCP output.</li>
443 |               <li><strong>Job Logs</strong>: Unified multi-level log files consolidated logically.</li>
444 |             </ul>
445 | 
446 |             <H3 id="tool-configs">Tool Config Modules</H3>
447 |             <p>Each <InlineCode>tool-configs/*.ts</InlineCode> file groups logically related tool definitions or export sets, enabling cleaner segregation and future dynamic registration strategies.</p>
448 | 
449 |             <H3 id="caching-performance">Caching & Performance</H3>
450 |             <ul className="list-disc pl-6 space-y-1">
451 |               <li><strong>cache.ts</strong>: In-memory response caching (documentation & static lookups).</li>
452 |               <li><strong>log-cache.ts</strong>: Specialized transient caching for recently tailed segments.</li>
453 |               <li><strong>Avoid Premature I/O</strong>: Lazy fetch patterns in log discovery & system objects.</li>
454 |               <li><strong>Capability Filter</strong>: Reduces surface area → fewer accidental expensive calls.</li>
455 |             </ul>
456 | 
457 |             <H2 id="release-process">🚀 Release Process</H2>
458 |             
459 |             <H3 id="version-management">Version Management</H3>
460 |             <CodeBlock language="bash" code={`
461 | # Update version
462 | npm version patch  # 1.0.0 → 1.0.1
463 | npm version minor  # 1.0.0 → 1.1.0  
464 | npm version major  # 1.0.0 → 2.0.0
465 | 
466 | # Push tags
467 | git push origin main --tags
468 |             `} />
469 | 
470 |             <H3 id="release-checklist">Release Checklist</H3>
471 |             <p><strong>1. Update Documentation</strong></p>
472 |             <ul className="list-disc pl-6 space-y-1">
473 |                 <li>README.md tool counts & feature surface (36+ phrasing)</li>
474 |                 <li><InlineCode>ai-instructions/github-copilot/copilot-instructions.md</InlineCode> architecture updates</li>
475 |                 <li><InlineCode>.github/copilot-instructions.md</InlineCode> (sync architecture + counts)</li>
476 |                 <li>Configuration & Features pages updated if capability surface changed</li>
477 |                 <li>CHANGELOG.md entry (if present)</li>
478 |             </ul>
479 | 
480 |             <p><strong>2. Testing</strong></p>
481 |             <ul className="list-disc pl-6 space-y-1">
482 |                 <li>All unit tests pass (<InlineCode>npm test</InlineCode>)</li>
483 |                 <li>Linting passes (<InlineCode>npm run lint:check</InlineCode>)</li>
484 |                 <li>Manual testing with real SFCC instance</li>
485 |                 <li>Documentation-only mode validation</li>
486 |                 <li>Build succeeds (<InlineCode>npm run build</InlineCode>)</li>
487 |             </ul>
488 | 
489 |             <p><strong>3. Build & Package</strong></p>
490 |             <ul className="list-disc pl-6 space-y-1">
491 |                 <li>TypeScript compilation successful</li>
492 |                 <li>Package size reasonable</li>
493 |                 <li>Dependencies audit clean (<InlineCode>npm audit</InlineCode>)</li>
494 |             </ul>
495 | 
496 |             <p><strong>4. Release</strong></p>
497 |             <ul className="list-disc pl-6 space-y-1">
498 |                 <li>GitHub release with changelog</li>
499 |                 <li>npm publish (automated via <InlineCode>.github/workflows/publish.yml</InlineCode>)</li>
500 |                 <li>Documentation deployment (automated)</li>
501 |             </ul>
502 | 
503 |             <H2 id="contributing-guidelines">🤝 Contributing Guidelines</H2>
504 |             
505 |             <H3 id="before-contributing">Before Contributing</H3>
506 |             <ol className="list-decimal pl-6 space-y-1">
507 |                 <li><strong>Check Existing Issues</strong>: Search for existing issues or discussions</li>
508 |                 <li><strong>Discuss Large Changes</strong>: Open an issue for significant modifications</li>
509 |                 <li><strong>Follow Conventions</strong>: Adhere to established coding and commit patterns</li>
510 |             </ol>
511 | 
512 |             <H3 id="pull-request-process">Pull Request Process</H3>
513 |             <ol className="list-decimal pl-6 space-y-1">
514 |                 <li><strong>Fork & Branch</strong>: Create feature branch from <InlineCode>develop</InlineCode></li>
515 |                 <li><strong>Implement Changes</strong>: Follow coding standards and testing requirements</li>
516 |                 <li><strong>Update Documentation</strong>: Ensure documentation reflects changes</li>
517 |                 <li><strong>Test Thoroughly</strong>: All tests must pass (<InlineCode>npm test</InlineCode>, <InlineCode>npm run lint:check</InlineCode>)</li>
518 |                 <li><strong>Submit PR</strong>: Provide clear description and link to related issues</li>
519 |             </ol>
520 | 
521 |             <H3 id="code-review">Code Review</H3>
522 |             <ul className="list-disc pl-6 space-y-1">
523 |                 <li><strong>GitHub Actions</strong>: CI pipeline must pass (see <InlineCode>.github/workflows/ci.yml</InlineCode>)</li>
524 |                 <li><strong>Code Quality</strong>: ESLint and TypeScript checks must pass</li>
525 |                 <li><strong>Test Coverage</strong>: Maintain or improve test coverage</li>
526 |                 <li><strong>Documentation</strong>: Ensure user-facing changes are documented</li>
527 |             </ul>
528 | 
529 |             <H2 id="performance-considerations">📊 Performance Considerations</H2>
530 |             
531 |             <H3 id="optimization-guidelines">Optimization Guidelines</H3>
532 |             <ul className="list-disc pl-6 space-y-1">
533 |                 <li><strong>Caching Strategy</strong>: Implement intelligent caching for API responses</li>
534 |                 <li><strong>Rate Limiting</strong>: Respect SFCC API limits and implement backoff</li>
535 |                 <li><strong>Memory Management</strong>: Monitor memory usage, especially for large datasets</li>
536 |                 <li><strong>Asynchronous Operations</strong>: Use proper async/await patterns</li>
537 |             </ul>
538 | 
539 |             <H3 id="monitoring">Monitoring</H3>
540 |             <CodeBlock language="typescript" code={`
541 | // Performance monitoring example
542 | const startTime = Date.now();
543 | try {
544 |   const result = await performOperation();
545 |   this.metrics.recordSuccess('operation_name', Date.now() - startTime);
546 |   return result;
547 | } catch (error) {
548 |   this.metrics.recordError('operation_name', Date.now() - startTime);
549 |   throw error;
550 | }
551 |             `} />
552 | 
553 |             <H2 id="security-considerations">🔒 Security Considerations</H2>
554 |             
555 |             <H3 id="credential-handling">Credential Handling</H3>
556 |             <ul className="list-disc pl-6 space-y-1">
557 |                 <li><strong>No Hardcoding</strong>: Never commit credentials to repository</li>
558 |                 <li><strong>Secure Storage</strong>: Use appropriate credential storage mechanisms</li>
559 |                 <li><strong>Minimal Permissions</strong>: Request only necessary permissions</li>
560 |                 <li><strong>Rotation Support</strong>: Design for credential rotation</li>
561 |             </ul>
562 | 
563 |             <H3 id="input-validation">Input Validation</H3>
564 |             <CodeBlock language="typescript" code={`
565 | // Validate all inputs using proper TypeScript types
566 | import { ToolResponse, ValidationError } from '../types/types.js';
567 | 
568 | interface ToolParams {
569 |   readonly query: string;
570 |   readonly limit?: number;
571 | }
572 | 
573 | function validateToolInput(input: unknown): ToolParams {
574 |   if (!input || typeof input !== 'object') {
575 |     throw new ValidationError('Input must be an object');
576 |   }
577 |   
578 |   const { query, limit } = input as any;
579 |   
580 |   if (!query || typeof query !== 'string') {
581 |     throw new ValidationError('Query is required and must be a string');
582 |   }
583 |   
584 |   if (query.length > 1000) {
585 |     throw new ValidationError('Query must be 1000 characters or less');
586 |   }
587 |   
588 |   if (limit !== undefined && (typeof limit !== 'number' || limit < 1 || limit > 100)) {
589 |     throw new ValidationError('Limit must be a number between 1 and 100');
590 |   }
591 |   
592 |   return { query, limit };
593 | }
594 |             `} />
595 | 
596 |             <H2 id="next-steps">Next Steps</H2>
597 |             <ul className="list-disc pl-6 space-y-2">
598 |                 <li>📝 <strong><a href="https://github.com/taurgis/sfcc-dev-mcp/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">Contributing Guidelines</a></strong> - Detailed contribution process</li>
599 |                 <li>🏗️ <strong><a href="https://github.com/taurgis/sfcc-dev-mcp/issues" target="_blank" rel="noopener noreferrer">Issues & Features</a></strong> - Report bugs or request features</li>
600 |                 <li>💬 <strong><a href="https://github.com/taurgis/sfcc-dev-mcp/discussions" target="_blank" rel="noopener noreferrer">Discussions</a></strong> - Community discussions and Q&A</li>
601 |                 <li>🚀 <strong><a href="https://github.com/taurgis/sfcc-dev-mcp/actions" target="_blank" rel="noopener noreferrer">GitHub Actions</a></strong> - View CI/CD pipeline status</li>
602 |             </ul>
603 |         </div>
604 |     );
605 | };
606 | 
607 | export default DevelopmentPage;
```
Page 43/61FirstPrevNextLast