#
tokens: 49027/50000 15/825 files (page 15/43)
lines: off (toggle) GitHub
raw markdown copy
This is page 15 of 43. Use http://codebase.md/taurgis/sfcc-dev-mcp?lines=false&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

--------------------------------------------------------------------------------
/docs/dw_campaign/DiscountPlan.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.campaign

# Class DiscountPlan

## Inheritance Hierarchy

- Object
  - dw.campaign.DiscountPlan

## Description

DiscountPlan represents a set of Discounts. Instances of the class are returned by the PromotionMgr when requesting applicable discounts for a specified set of promotions and a line item container (see PromotionMgr.getDiscounts(LineItemCtnr, PromotionPlan)). DiscountPlan provides methods to access the discounts contained in the plan, add additional discounts to the plan (future) or remove discounts from the plan.

## Properties

### approachingOrderDiscounts

**Type:** Collection (Read Only)

Get the collection of order discounts that the LineItemCtnr "almost"
 qualifies for based on the merchandise total in the cart. Nearness is
 controlled by thresholds configured at the promotion level.
 
 The collection of returned approaching discounts is ordered by the
 condition threshold of the promotion (e.g. "spend $100 and get 10% off"
 discount is returned before "spend $150 and get 15% off" discount.) A
 discount is not returned if it would be prevented from applying (because
 of compatibility rules) by an order discount already in the DiscountPlan
 or an approaching order discount with a lower condition threshold.

### bonusDiscounts

**Type:** Collection (Read Only)

All bonus discounts contained in the discount plan.

### lineItemCtnr

**Type:** LineItemCtnr (Read Only)

Returns line item container associated with discount plan. 
 A discount plan is created from and associated with a line item container.
 This method returns the line item container associated with this discount plan.

### orderDiscounts

**Type:** Collection (Read Only)

The percentage and amount order discounts contained in the
 discount plan.

## Constructor Summary

## Method Summary

### getApproachingOrderDiscounts

**Signature:** `getApproachingOrderDiscounts() : Collection`

Get the collection of order discounts that the LineItemCtnr "almost" qualifies for based on the merchandise total in the cart.

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment) : Collection`

Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment.

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment, shippingMethod : ShippingMethod) : Collection`

Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment.

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment, shippingMethods : Collection) : Collection`

Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment.

### getBonusDiscounts

**Signature:** `getBonusDiscounts() : Collection`

Returns all bonus discounts contained in the discount plan.

### getLineItemCtnr

**Signature:** `getLineItemCtnr() : LineItemCtnr`

Returns line item container associated with discount plan.

### getOrderDiscounts

**Signature:** `getOrderDiscounts() : Collection`

Returns the percentage and amount order discounts contained in the discount plan.

### getProductDiscounts

**Signature:** `getProductDiscounts(productLineItem : ProductLineItem) : Collection`

Returns the percentage, amount and fix price discounts associated with the specified product line item.

### getProductShippingDiscounts

**Signature:** `getProductShippingDiscounts(productLineItem : ProductLineItem) : Collection`

Returns the product-shipping discounts associated with the specified product line item.

### getShippingDiscounts

**Signature:** `getShippingDiscounts(shipment : Shipment) : Collection`

Returns the percentage, amount and fix price discounts associated with the specified shipment.

### removeDiscount

**Signature:** `removeDiscount(discount : Discount) : void`

Removes the specified discount from the discount plan.

## Method Detail

## Method Details

### getApproachingOrderDiscounts

**Signature:** `getApproachingOrderDiscounts() : Collection`

**Description:** Get the collection of order discounts that the LineItemCtnr "almost" qualifies for based on the merchandise total in the cart. Nearness is controlled by thresholds configured at the promotion level. The collection of returned approaching discounts is ordered by the condition threshold of the promotion (e.g. "spend $100 and get 10% off" discount is returned before "spend $150 and get 15% off" discount.) A discount is not returned if it would be prevented from applying (because of compatibility rules) by an order discount already in the DiscountPlan or an approaching order discount with a lower condition threshold.

**Returns:**

Collection of approaching order discounts ordered by the condition threshold of the promotion ascending.

---

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment) : Collection`

**Description:** Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment. Nearness is controlled by thresholds configured at the promotion level. The collection of returned approaching discounts is ordered by the condition threshold of the promotion (e.g. "spend $100 and get free standard shipping" discount is returned before "spend $150 and get free standard shipping" discount.) A discount is not returned if it would be prevented from applying (because of compatibility rules) by a shipping discount already in the DiscountPlan or an approaching shipping discount with a lower condition threshold.

**Parameters:**

- `shipment`: The shipment to calculate approaching discounts for.

**Returns:**

Collection of approaching shipping discounts ordered by the condition threshold of the promotion ascending.

---

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment, shippingMethod : ShippingMethod) : Collection`

**Description:** Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment. Here "almost" is controlled by thresholds configured at the promotion level. This method only returns discounts for shipping promotions which apply to the passed shipping method. The collection of returned approaching discounts is ordered by the condition threshold of the promotion (e.g. "spend $100 and get free standard shipping" discount is returned before "spend $150 and get free standard shipping" discount.) A discount is not returned if it would be prevented from applying (because of compatibility rules) by a shipping discount already in the DiscountPlan or an approaching shipping discount with a lower condition threshold.

**Parameters:**

- `shipment`: The shipment to calculate approaching discounts for.
- `shippingMethod`: The shipping method to filter by.

**Returns:**

Collection of approaching shipping discounts ordered by the condition threshold of the promotion, ascending.

---

### getApproachingShippingDiscounts

**Signature:** `getApproachingShippingDiscounts(shipment : Shipment, shippingMethods : Collection) : Collection`

**Description:** Get the collection of shipping discounts that the passed shipment "almost" qualifies for based on the merchandise total in the shipment. Here "almost" is controlled by thresholds configured at the promotion level. This method only returns discounts for shipping promotions which apply to one of the passed shipping methods. The collection of returned approaching discounts is ordered by the condition threshold of the promotion (e.g. "spend $100 and get free standard shipping" discount is returned before "spend $150 and get free standard shipping" discount.) A discount is not returned if it would be prevented from applying (because of compatibility rules) by a shipping discount already in the DiscountPlan or an approaching shipping discount with a lower condition threshold.

**Parameters:**

- `shipment`: The shipment to calculate approaching discounts for.
- `shippingMethods`: The shipping methods to filter by.

**Returns:**

Collection of approaching shipping discounts ordered by the condition threshold of the promotion ascending.

---

### getBonusDiscounts

**Signature:** `getBonusDiscounts() : Collection`

**Description:** Returns all bonus discounts contained in the discount plan.

**Returns:**

All bonus discounts contained in discount plan

---

### getLineItemCtnr

**Signature:** `getLineItemCtnr() : LineItemCtnr`

**Description:** Returns line item container associated with discount plan. A discount plan is created from and associated with a line item container. This method returns the line item container associated with this discount plan.

**Returns:**

Line item container associated with plan

---

### getOrderDiscounts

**Signature:** `getOrderDiscounts() : Collection`

**Description:** Returns the percentage and amount order discounts contained in the discount plan.

**Returns:**

Order discounts contained in the discount plan

---

### getProductDiscounts

**Signature:** `getProductDiscounts(productLineItem : ProductLineItem) : Collection`

**Description:** Returns the percentage, amount and fix price discounts associated with the specified product line item. If the specified product line item is not contained in the discount plan, an empty collection is returned.

**Parameters:**

- `productLineItem`: Product line item

**Returns:**

Discounts associated with specified product line item

---

### getProductShippingDiscounts

**Signature:** `getProductShippingDiscounts(productLineItem : ProductLineItem) : Collection`

**Description:** Returns the product-shipping discounts associated with the specified product line item. If the specified product line item is not contained in the discount plan, an empty collection is returned.

**Parameters:**

- `productLineItem`: Product line item

**Returns:**

Product-shipping discounts associated with specified product line item

---

### getShippingDiscounts

**Signature:** `getShippingDiscounts(shipment : Shipment) : Collection`

**Description:** Returns the percentage, amount and fix price discounts associated with the specified shipment. If the specified shipment is not contained in the discount plan, an empty collection is returned.

**Parameters:**

- `shipment`: the shipment for which to fetch discounts.

**Returns:**

Discounts associated with specified shipment

---

### removeDiscount

**Signature:** `removeDiscount(discount : Discount) : void`

**Description:** Removes the specified discount from the discount plan. This method should only be used for very simple discount scenarios. It is not recommended to use the method in case of stacked discounts (i.e. multiple order or product discounts), or complex discount types like Buy X Get Y or Total Fixed Price, since correct re-calculation of the discount plan based on the promotion rules is not guaranteed.

**Parameters:**

- `discount`: Discount to be removed

---
```

--------------------------------------------------------------------------------
/docs/dw_customer/ProductListItem.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.customer

# Class ProductListItem

## Inheritance Hierarchy

- Object
  - dw.object.PersistentObject
  - dw.object.ExtensibleObject
    - dw.customer.ProductListItem

## Description

An item in a product list. Types of items are: An item that references a product via the product's SKU. An item that represents a gift certificate.

## Constants

### TYPE_GIFT_CERTIFICATE

**Type:** Number = 2

Constant representing a gift certificate list item type.

### TYPE_PRODUCT

**Type:** Number = 1

Constant representing a product list item type.

## Properties

### ID

**Type:** String (Read Only)

The unique system generated ID of the object.

### list

**Type:** ProductList (Read Only)

The product list that this item belongs to.

### priority

**Type:** Number

Specify the priority level for the item.  Typically the lower the
 number, the higher the priority. This can be used by the owner of the product list
 to express which items he/she likes to get purchased first.

### product

**Type:** Product

The referenced product for this item.  The reference is made
 via the product ID attribute.  This method returns null if there is
 no such product in the system or if the product exists but is not
 assigned to the site catalog.

### productID

**Type:** String (Read Only)

The ID of the product referenced by this item.
 This attribute is set when a product is assigned via setProduct().
 It is possible for the ID to reference a product that doesn't exist
 anymore.  In this case getProduct() would return null.

### productOptionModel

**Type:** ProductOptionModel

The ProductOptionModel for the product associated with this item,
 or null if there is no valid product associated with this item.

### public

**Type:** boolean

A flag, typically used to determine whether the item should display
 in a customer's view of the list (as opposed to the list owner's view).

### purchasedQuantity

**Type:** Quantity (Read Only)

The sum of the quantities of all the individual purchase records
 for this item.

### purchasedQuantityValue

**Type:** Number (Read Only)

The value part of the underlying purchased quantity object, as distinct
 from the unit.

### purchases

**Type:** Collection (Read Only)

All purchases made for this item.

### quantity

**Type:** Quantity

The quantity of the item.
 The quantity is the number of products or gift certificates
 that get shipped when purchasing this product list item.

### quantityValue

**Type:** Number

The value part of the underlying quantity object, as distinct
 from the unit.

### type

**Type:** Number (Read Only)

The type of this product list item.

## Constructor Summary

## Method Summary

### createPurchase

**Signature:** `createPurchase(quantity : Number, purchaserName : String) : ProductListItemPurchase`

Create a purchase record for this item.

### getID

**Signature:** `getID() : String`

Returns the unique system generated ID of the object.

### getList

**Signature:** `getList() : ProductList`

Returns the product list that this item belongs to.

### getPriority

**Signature:** `getPriority() : Number`

Specify the priority level for the item.

### getProduct

**Signature:** `getProduct() : Product`

Returns the referenced product for this item.

### getProductID

**Signature:** `getProductID() : String`

Returns the ID of the product referenced by this item.

### getProductOptionModel

**Signature:** `getProductOptionModel() : ProductOptionModel`

Returns the ProductOptionModel for the product associated with this item, or null if there is no valid product associated with this item.

### getPurchasedQuantity

**Signature:** `getPurchasedQuantity() : Quantity`

Returns the sum of the quantities of all the individual purchase records for this item.

### getPurchasedQuantityValue

**Signature:** `getPurchasedQuantityValue() : Number`

Returns the value part of the underlying purchased quantity object, as distinct from the unit.

### getPurchases

**Signature:** `getPurchases() : Collection`

Returns all purchases made for this item.

### getQuantity

**Signature:** `getQuantity() : Quantity`

Returns the quantity of the item.

### getQuantityValue

**Signature:** `getQuantityValue() : Number`

Returns the value part of the underlying quantity object, as distinct from the unit.

### getType

**Signature:** `getType() : Number`

Returns the type of this product list item.

### isPublic

**Signature:** `isPublic() : boolean`

A flag, typically used to determine whether the item should display in a customer's view of the list (as opposed to the list owner's view).

### setPriority

**Signature:** `setPriority(priority : Number) : void`

Specify the priority level for the item.

### setProduct

**Signature:** `setProduct(product : Product) : void`

Sets the referenced product for this item by storing the product's id.

### setProductOptionModel

**Signature:** `setProductOptionModel(productOptionModel : ProductOptionModel) : void`

Store a product option model with this object.

### setPublic

**Signature:** `setPublic(flag : boolean) : void`

Typically used to determine if the item is visible to other customers.

### setQuantity

**Signature:** `setQuantity(value : Quantity) : void`

Sets the quantity of the item.

### setQuantityValue

**Signature:** `setQuantityValue(value : Number) : void`

Set the value part of the underlying quantity object, as distinct from the unit.

## Method Detail

## Method Details

### createPurchase

**Signature:** `createPurchase(quantity : Number, purchaserName : String) : ProductListItemPurchase`

**Description:** Create a purchase record for this item.

**Parameters:**

- `quantity`: The number of items purchased.
- `purchaserName`: The name of the purchaser.

**Returns:**

the purchase record.

---

### getID

**Signature:** `getID() : String`

**Description:** Returns the unique system generated ID of the object.

**Returns:**

the ID of object.

---

### getList

**Signature:** `getList() : ProductList`

**Description:** Returns the product list that this item belongs to.

**Returns:**

the list.

---

### getPriority

**Signature:** `getPriority() : Number`

**Description:** Specify the priority level for the item. Typically the lower the number, the higher the priority. This can be used by the owner of the product list to express which items he/she likes to get purchased first.

**Returns:**

the specified priority level.

---

### getProduct

**Signature:** `getProduct() : Product`

**Description:** Returns the referenced product for this item. The reference is made via the product ID attribute. This method returns null if there is no such product in the system or if the product exists but is not assigned to the site catalog.

**Returns:**

the product referenced by this item, or null.

---

### getProductID

**Signature:** `getProductID() : String`

**Description:** Returns the ID of the product referenced by this item. This attribute is set when a product is assigned via setProduct(). It is possible for the ID to reference a product that doesn't exist anymore. In this case getProduct() would return null.

**Returns:**

the product ID, or null if none exists.

---

### getProductOptionModel

**Signature:** `getProductOptionModel() : ProductOptionModel`

**Description:** Returns the ProductOptionModel for the product associated with this item, or null if there is no valid product associated with this item.

**Returns:**

the associated ProductOptionModel or null.

---

### getPurchasedQuantity

**Signature:** `getPurchasedQuantity() : Quantity`

**Description:** Returns the sum of the quantities of all the individual purchase records for this item.

**Returns:**

the sum of the quantities of all the individual purchase records for this item.

---

### getPurchasedQuantityValue

**Signature:** `getPurchasedQuantityValue() : Number`

**Description:** Returns the value part of the underlying purchased quantity object, as distinct from the unit.

**Returns:**

the value part of the underlying purchased quantity object, as distinct from the unit.

---

### getPurchases

**Signature:** `getPurchases() : Collection`

**Description:** Returns all purchases made for this item.

**Returns:**

the collection of purchase records for this item. Returns an empty list if this item has not been purchased yet.

---

### getQuantity

**Signature:** `getQuantity() : Quantity`

**Description:** Returns the quantity of the item. The quantity is the number of products or gift certificates that get shipped when purchasing this product list item.

**Returns:**

the quantity of the item.

---

### getQuantityValue

**Signature:** `getQuantityValue() : Number`

**Description:** Returns the value part of the underlying quantity object, as distinct from the unit.

**Returns:**

the value part of the underlying quantity object, as distinct from the unit.

---

### getType

**Signature:** `getType() : Number`

**Description:** Returns the type of this product list item.

**Returns:**

a code that specifies the type of item (i.e. product or gift certificate).

---

### isPublic

**Signature:** `isPublic() : boolean`

**Description:** A flag, typically used to determine whether the item should display in a customer's view of the list (as opposed to the list owner's view).

**Returns:**

true if the item is public.

---

### setPriority

**Signature:** `setPriority(priority : Number) : void`

**Description:** Specify the priority level for the item. Typically the lower the number, the higher the priority. This can be used by the owner of the product list to express which items he/she likes to get purchased first.

**Parameters:**

- `priority`: The new priority level.

---

### setProduct

**Signature:** `setProduct(product : Product) : void`

**Description:** Sets the referenced product for this item by storing the product's id. If null is specified, then the id is set to null.

**Deprecated:**

Use ProductList.createProductItem(Product) instead.

**Parameters:**

- `product`: The referenced product for this item.

---

### setProductOptionModel

**Signature:** `setProductOptionModel(productOptionModel : ProductOptionModel) : void`

**Description:** Store a product option model with this object. This stores a copy of the specified model, rather than an assocation to the same instance.

**Parameters:**

- `productOptionModel`: The object to store.

---

### setPublic

**Signature:** `setPublic(flag : boolean) : void`

**Description:** Typically used to determine if the item is visible to other customers.

**Parameters:**

- `flag`: If true, this product list becomes visible to other customers. If false, this product list can only be seen by the owner of the product list.

---

### setQuantity

**Signature:** `setQuantity(value : Quantity) : void`

**Description:** Sets the quantity of the item.

**Deprecated:**

Use setQuantityValue(Number) instead.

**Parameters:**

- `value`: the new quantity of the item.

---

### setQuantityValue

**Signature:** `setQuantityValue(value : Number) : void`

**Description:** Set the value part of the underlying quantity object, as distinct from the unit.

**Parameters:**

- `value`: the value to use.

---
```

--------------------------------------------------------------------------------
/docs/dw_order.hooks/PaymentHooks.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.order.hooks

# Class PaymentHooks

## Inheritance Hierarchy

- dw.order.hooks.PaymentHooks

## Description

This interface represents all script hooks that can be registered to customize the order center payment functionality. It contains the extension points (hook names), and the functions that are called by each extension point. A function must be defined inside a JavaScript source and must be exported. The script with the exported hook function must be located inside a site cartridge. Inside the site cartridge a 'package.json' file with a 'hooks' entry must exist. "hooks": "./hooks.json" The hooks entry links to a json file, relative to the 'package.json' file. This file lists all registered hooks inside the hooks property: "hooks": [ {"name": "dw.order.payment.authorize", "script": "./authorize.js"}, {"name": "dw.order.payment.validateAuthorization", "script": "./validateAuthorization.js"}, ] A hook entry has a 'name' and a 'script' property. The 'name' contains the extension point, the hook name. The 'script' contains the script relative to the hooks file, with the exported hook function.

## Constants

## Properties

## Constructor Summary

## Method Summary

### authorize

**Signature:** `authorize(order : Order, paymentDetails : OrderPaymentInstrument) : Status`

The function is called by extension point extensionPointAuthorize.

### authorizeCreditCard

**Signature:** `authorizeCreditCard(order : Order, paymentDetails : OrderPaymentInstrument, cvn : String) : Status`

The function is called by extension point extensionPointAuthorizeCreditCard.

### capture

**Signature:** `capture(invoice : Invoice) : Status`

The function is called by extension point extensionPointCapture.

### reauthorize

**Signature:** `reauthorize(order : Order) : Status`

The function is called by extension point extensionPointReauthorize.

### refund

**Signature:** `refund(invoice : Invoice) : Status`

The function is called by extension point extensionPointRefund.

### releaseAuthorization

**Signature:** `releaseAuthorization(order : Order) : Status`

The function is called by extension point extensionPointReleaseAuthorization.

### validateAuthorization

**Signature:** `validateAuthorization(order : Order) : Status`

The function is called by extension point extensionPointValidateAuthorization.

## Method Detail

## Method Details

### authorize

**Signature:** `authorize(order : Order, paymentDetails : OrderPaymentInstrument) : Status`

**Description:** The function is called by extension point extensionPointAuthorize. Custom payment authorization - modify the order as needed. Prerequisite: An order has been created using the data api or via the storefront. Return Status.OK: Corresponding payment transaction is marked as authorized (usually a custom property is used for this). Return Status.ERROR: Order is held, authorization needs to be repeated.

**Parameters:**

- `order`: the order
- `paymentDetails`: specified payment details

**Returns:**

Status.OK successful authorization. Status.ERROR authorization failed.

---

### authorizeCreditCard

**Signature:** `authorizeCreditCard(order : Order, paymentDetails : OrderPaymentInstrument, cvn : String) : Status`

**Description:** The function is called by extension point extensionPointAuthorizeCreditCard. Custom payment authorization of a credit card - modify the order as needed. Prerequisite: An order has been created using the data api or via the storefront. Return Status.OK: Corresponding payment transaction is marked as authorized (usually a custom property is used for this). Return Status.ERROR: Order is held, authorization needs to be repeated.

**Parameters:**

- `order`: the order
- `paymentDetails`: specified payment details
- `cvn`: the credit card verification number

**Returns:**

Status.OK successful authorization. Status.ERROR authorization failed.

---

### capture

**Signature:** `capture(invoice : Invoice) : Status`

**Description:** The function is called by extension point extensionPointCapture. Custom payment capture - modify the order as needed. Prerequisite: [ either ] As a result of shipping (or part-shipping) a shipping -order the warehouse updates the status of the shipping-order resulting in the creation of an unpaid debit invoice (the creation of the invoice is usually handled in ShippingOrderHooks.changeStatus(ShippingOrder, ShippingOrderWO)). [ or ] A unpaid debit invoice has been created using the data api. Context: An unpaid debit invoice is passed to the payment system for capture. The capture attempt can either succeed (complete invoice amount captured) or fail. As a result the invoice status is updated by ordercenter for further processing. See Invoice. Hook responsibility: The hook should attempt to capture the amount located in invoice.grandTotal.grossPrice. When successful, the capture hook should also update the invoice by calling Invoice.addCaptureTransaction(OrderPaymentInstrument, Money) which serves to record the capturedAmount and associate the invoice with the payment-transaction. Return Status.OK: Indicates capture succeeded: Order Center sets the Invoice status to Invoice.STATUS_PAID. Return Status.ERROR: Indicates capture failed: Order Center sets the Invoice status to Invoice.STATUS_FAILED for further processing. Post processing: When the capture hook returns with success, order center not only sets the relevant invoice status, but also sets the relevant capturedAmount values on the invoice item. Returning success means the entire invoice total has been captured, so each item within the invoice can also be marked as completely captured. Note the script implementing the hook can take responsibility for this if desired order center will not overwrite existing values, but normally the standard implementation fits. As a result each invoice item and the related order item can return a capturedAmount, useful for calculating possible refunds.

**Parameters:**

- `invoice`: the invoice

**Returns:**

Status.OK for successful capture of entire invoice amount. Status.ERROR capture failed

---

### reauthorize

**Signature:** `reauthorize(order : Order) : Status`

**Description:** The function is called by extension point extensionPointReauthorize. Custom payment authorization - modify the order as needed. Prerequisite: [ either ] Based on a selected Order, a ShippingOrder (which represents the whole or part of the order which can be shipped) is to be created ready for export to the warehouse system. [ or ] A ShippingOrder is to be directly created using the data api. Context: The related order is passed to the payment hook to check its authorization has not become invalid. Two hooks are called: a. validateAuthorization(Order) is used to check the orders authorization is still valid b. reauthorize(Order) is called if step a. returns Error Return Status.OK: Corresponding payment transaction is marked as authorized (usually a custom property is used for this). If the order had been previously authorized, the custom property values may be overwritten during reauthorization. Return Status.ERROR: Order is held, authorization needs to be repeated.

**Parameters:**

- `order`: the order

**Returns:**

Status.OK successful authorization. Status.ERROR authorization failed

---

### refund

**Signature:** `refund(invoice : Invoice) : Status`

**Description:** The function is called by extension point extensionPointRefund. Custom payment refund - modify the order as needed. Prerequisite: [ either ] Goods returned by the customer result in the creation of one or more return documents, resulting in the creation of an unpaid customer credit invoice (the creation of the invoice is usually handled in ReturnHooks.changeStatus(Return, ReturnWO)). [ or ] An unpaid customer credit invoice is created using the data api (perhaps as a result of the creation of a customer appeasement). Context: An unpaid credit invoice is passed to the payment system for refund. The refund attempt can either succeed (complete invoice amount refunded) or fail. As a result the invoice status is updated by ordercenter for further processing. See Invoice. Hook responsibility: The hook should attempt to refund the amount located in invoice.grandTotal.grossPrice. When successful, the refund hook should also update the invoice by calling Invoice.addRefundTransaction(OrderPaymentInstrument, Money) which serves to record the refundedAmount and associate the invoice with the payment-transaction. Return Status.OK: Indicates refund succeeded: Order Center sets the Invoice status to Invoice.STATUS_PAID. Return Status.ERROR: Indicates refund failed: Order Center sets the Invoice status to Invoice.STATUS_FAILED for further processing. Post processing: When the refund hook returns with success, order center not only sets the relevant invoice status, but also sets the relevant refundAmount values on the invoice item. Returning success means the entire invoice total has been refunded, so each item within the invoice can also be marked as completely refunded. Note the script implementing the hook can take responsibility for this if desired order center will not overwrite existing values, but normally the standard implementation fits. As a result each invoice item and the related order item can return a refundedAmount, useful for calculating further possible refunds.

**Parameters:**

- `invoice`: the invoice

**Returns:**

Status.OK for successful refund of entire invoice amount. Status.ERROR refund failed

---

### releaseAuthorization

**Signature:** `releaseAuthorization(order : Order) : Status`

**Description:** The function is called by extension point extensionPointReleaseAuthorization. Custom payment release authorization - modify the order as needed. Prerequisite: an authorized order is updated resulting in a need to release the remaining authorization. This happens when: - order is cancelled - order is complete after remaining order items are cancelled. Return Status.OK - successful release authorization Return Status.ERROR - failed release authorization

**Parameters:**

- `order`: the order

**Returns:**

Status.OK for successful release authorization. Status.ERROR failed release authorization

---

### validateAuthorization

**Signature:** `validateAuthorization(order : Order) : Status`

**Description:** The function is called by extension point extensionPointValidateAuthorization. Custom payment authorization - modify the order as needed. Context: This hook is called to validate whether a payment authorization exists for the order. It should usually check: - Whether the authorize or reauthorize hook was previously successfully executed for the order, e.g. by checking whether custom property has been previously set. - Whether an existing authorization has expired e.g. by comparing a timestamp set on authorization with the current time. Return Status.OK: indicates the order has a valid payment authorization. Return Status.ERROR: indicates reauthorize(Order) should be called. See reauthorize(Order) for more details.

**Parameters:**

- `order`: the order

**Returns:**

Status.OK order has a valid payment authorization. Status.ERROR order has no valid payment authorization, reauthorize(Order) should be called

---
```

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

```typescript
import React from 'react';
import { NavLink } from 'react-router-dom';
import SEO from '../components/SEO';
import BreadcrumbSchema from '../components/BreadcrumbSchema';
import StructuredData from '../components/StructuredData';
import { H1, PageSubtitle } from '../components/Typography';
import ToolFilters from '../components/ToolFilters';
import ToolCard from '../components/ToolCard';
import { tools, popularTools } from '../utils/toolsData';
import { SITE_DATES } from '../constants';

const ToolsPage: React.FC = () => {
  const [activeCategory, setActiveCategory] = React.useState('All');
  const [search, setSearch] = React.useState('');
  const [showPopularExpanded, setShowPopularExpanded] = React.useState(true);

  const filtered = tools.filter(t => {
    const inCat = activeCategory === 'All' || t.category === activeCategory;
    if (!inCat) return false;
    if (!search) return true;
    const q = search.toLowerCase();
    return (
      t.name.toLowerCase().includes(q) ||
      t.description.toLowerCase().includes(q) ||
      t.examples?.some(e => e.toLowerCase().includes(q)) ||
      t.params?.some(p => p.name.toLowerCase().includes(q)) ||
      t.tags?.some(tag => tag.toLowerCase().includes(q))
    );
  });

  const toolsStructuredData = {
    "@context": "https://schema.org",
    "@type": "TechArticle",
    "headline": "Available Tools & APIs - SFCC Development MCP Server",
    "description": "Interactive reference of SFCC Development MCP Server tools with filtering, search, examples, and quick start actions.",
    "author": {
      "@type": "Person",
      "name": "Thomas Theunen"
    },
    "publisher": {
      "@type": "Person",
      "name": "Thomas Theunen"
    },
    "datePublished": SITE_DATES.PUBLISHED,
    "dateModified": SITE_DATES.MODIFIED,
    "url": "https://sfcc-mcp-dev.rhino-inquisitor.com/tools/",
    "mainEntity": {
      "@type": "SoftwareApplication",
      "name": "SFCC Development MCP Server",
      "applicationCategory": "DeveloperApplication",
      "operatingSystem": "Node.js",
      "description": "Interactive API reference and tools catalog",
      "offers": {
        "@type": "Offer",
        "price": "0",
        "priceCurrency": "USD",
        "availability": "https://schema.org/InStock"
      }
    }
  };

  return (
    <div className="max-w-7xl mx-auto px-6 py-10">
      <SEO 
        title="Available Tools & APIs"
        description="Interactive reference of SFCC Development MCP Server tools with filtering, search, examples, and quick start actions."
        keywords="SFCC MCP tools, Commerce Cloud APIs, log analysis, system objects, cartridge generation, best practices"
        canonical="/tools/"
        ogType="article"
      />
      <BreadcrumbSchema items={[
        { name: "Home", url: "/" },
        { name: "Tools", url: "/tools/" }
      ]} />
      <StructuredData data={toolsStructuredData} />
      
      {/* Hero */}
      <div className="text-center mb-14">
        <div className="inline-flex items-center gap-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white px-4 py-2 rounded-full text-sm font-medium mb-6">
          <span>🛠️</span> Available Tools
        </div>
        <H1 id="available-tools" className="text-4xl md:text-5xl font-extrabold bg-gradient-to-r from-gray-900 via-blue-900 to-purple-900 bg-clip-text text-transparent mb-6">Interactive Tool Explorer</H1>
        <PageSubtitle className="text-lg md:text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
          36+ specialized tools. Filter by category, search prompts, copy examples, and get productive in seconds.
        </PageSubtitle>
      </div>

      {/* Quick Actions / Popular */}
      <section className="mb-16 bg-gradient-to-r from-blue-50 via-indigo-50 to-purple-50 rounded-2xl p-6 md:p-8 shadow-xl border border-blue-100">
          <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6 mb-6">
            <div>
              <h2 id="quick-actions" className="text-2xl font-bold text-gray-900 mb-1">🚀 Quick Actions</h2>
              <p className="text-sm text-gray-600">Most common starting points – copy and ask your AI now.</p>
            </div>
            <button onClick={() => setShowPopularExpanded(e=>!e)} className="text-xs font-medium text-blue-700 hover:text-blue-900 bg-blue-50 border border-blue-200 px-3 py-1.5 rounded-lg transition">
              {showPopularExpanded ? 'Collapse' : 'Expand'}
            </button>
          </div>
          {showPopularExpanded && (
            <div className="grid sm:grid-cols-2 lg:grid-cols-2 gap-4">
              {popularTools.map(tool => (
                <div key={tool.id} className="relative rounded-xl border border-gray-200 bg-white/90 p-4 shadow-sm group">
                  <div className="flex items-center justify-between mb-2">
                    <p className="font-mono text-xs font-semibold text-gray-800">{tool.name}</p>
                    <button
                      onClick={() => {
                        const ex = tool.examples?.[0];
                        if (ex) navigator.clipboard.writeText(ex);
                      }}
                      className="text-[10px] bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-0.5 rounded transition"
                    >Copy</button>
                  </div>
                  <p className="text-[11px] text-gray-600 line-clamp-2 group-hover:line-clamp-none transition-all">{tool.description}</p>
                  {tool.examples && tool.examples.length > 0 && (
                    <p className="mt-2 text-[11px] text-gray-500 italic">{tool.examples[0]}</p>
                  )}
                  <a href={`#${tool.id}`} className="absolute inset-0" aria-label={`Jump to ${tool.name}`}></a>
                </div>
              ))}
              {/* Added Hook Reference Quick Action */}
              <div className="relative rounded-xl border border-amber-300 bg-gradient-to-br from-amber-50 to-orange-50 p-4 shadow-sm group">
                <div className="flex items-center justify-between mb-2">
                  <p className="font-mono text-xs font-semibold text-amber-700">get_hook_reference</p>
                  <button
                    onClick={() => navigator.clipboard.writeText('Ask: "List available SCAPI hook extension points for product search"')}
                    className="text-[10px] bg-amber-100 hover:bg-amber-200 text-amber-700 px-2 py-0.5 rounded transition"
                  >Copy</button>
                </div>
                <p className="text-[11px] text-amber-800 line-clamp-2 group-hover:line-clamp-none transition-all">
                  Discover all available OCAPI or SCAPI hook extension points to select the correct customization surface.
                </p>
                <p className="mt-2 text-[11px] text-amber-600 italic">Ask: "List available SCAPI hook extension points for product search"</p>
                <a href="#get_hook_reference" className="absolute inset-0" aria-label="Jump to get_hook_reference"></a>
              </div>
            </div>
          )}
      </section>

      {/* Filters */}
      <div className="mb-10">
        <ToolFilters activeCategory={activeCategory} setActiveCategory={setActiveCategory} search={search} setSearch={setSearch} />
      </div>

      {/* Legend */}
      <div className="flex flex-wrap gap-3 items-center text-[11px] text-gray-600 mb-6">
        <span className="flex items-center gap-1"><span className="bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full font-semibold">Full</span> Requires credentials</span>
        <span className="flex items-center gap-1"><span className="bg-gradient-to-r from-blue-600 to-purple-600 text-white px-2 py-0.5 rounded-full font-semibold">Docs + Full</span> Both modes</span>
      </div>

      {/* Results */}
      <div className="space-y-14">
        {filtered.length === 0 && (
          <div className="text-center py-16 border border-dashed border-gray-300 rounded-xl">
            <p className="text-sm text-gray-600 mb-4">No tools match that search.</p>
            <p className="text-xs text-gray-500">Tip: Try a simpler keyword like <strong>log</strong>, <strong>class</strong>, or <strong>version</strong>.</p>
          </div>
        )}
        {filtered.length > 0 && (
          <div className="grid md:grid-cols-2 xl:grid-cols-2 gap-5">
            {filtered.map(tool => <ToolCard key={tool.id} tool={tool} />)}
          </div>
        )}
      </div>

      {/* Getting Started Hint */}
      <div className="mt-20 bg-blue-50 border border-blue-200 rounded-xl p-6">
        <h3 className="text-sm font-semibold text-blue-800 mb-2">💡 Mode Recommendation</h3>
  <p className="text-xs text-blue-800 leading-relaxed">Explore freely in Documentation Mode first. Add <code className="font-mono bg-blue-100 px-1 py-0.5 rounded">--dw-json</code> later to unlock log analysis, system & custom object exploration, job log insights, and code version management without changing any other configuration.</p>
      </div>

      {/* Next Steps */}
      <section className="mt-24 mb-12 text-center" aria-labelledby="next-steps">
        <h2 id="next-steps" className="text-2xl md:text-3xl font-bold text-gray-900 mb-4">🔗 Next Steps</h2>
        <p className="text-sm md:text-base text-gray-600 max-w-2xl mx-auto mb-8">Move from raw tool surface into practical flows or reinforce secure patterns before enabling full-mode capabilities.</p>
        <div className="flex flex-col sm:flex-row gap-4 justify-center mb-10">
          <NavLink
            to="/examples/"
            className="group bg-gradient-to-r from-blue-600 to-purple-600 text-white px-8 py-4 rounded-xl font-semibold text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 no-underline hover:no-underline focus:no-underline"
          >
            Examples & Use Cases
            <span className="ml-2 group-hover:translate-x-1 inline-block transition-transform">→</span>
          </NavLink>
          <NavLink
            to="/security/"
            className="border-2 border-gray-300 text-gray-700 px-8 py-4 rounded-xl font-semibold text-lg hover:border-blue-500 hover:text-blue-600 transition-all duration-300 no-underline hover:no-underline focus:no-underline"
          >
            Security Guidance
          </NavLink>
        </div>
        <div className="grid md:grid-cols-2 gap-6 max-w-4xl mx-auto mb-4 text-left">
          <div className="rounded-xl border border-gray-200 bg-white p-5">
            <h3 className="font-semibold text-sm mb-2">Why start with examples?</h3>
            <p className="text-xs text-gray-600">Shortens prompt iteration by showing multi-step sequences that combine docs lookup, log inspection, and object introspection.</p>
          </div>
          <div className="rounded-xl border border-blue-200 bg-blue-50 p-5">
            <h3 className="font-semibold text-sm mb-2 text-blue-800">Why review security now?</h3>
            <p className="text-xs text-blue-700">Prevents leaking credentials, encourages safe log handling, and sets a baseline for principle-of-least-privilege tokens before automation.</p>
          </div>
        </div>
      </section>
    </div>
  );
};

export default ToolsPage;

```

--------------------------------------------------------------------------------
/docs/dw_order/GiftCertificate.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.order

# Class GiftCertificate

## Inheritance Hierarchy

- Object
  - dw.object.PersistentObject
  - dw.object.ExtensibleObject
    - dw.order.GiftCertificate

## Description

Represents a Gift Certificate that can be used to purchase products.

## Constants

### STATUS_ISSUED

**Type:** Number = 1

Represents a status of 'issued', which indicates that the Gift Certificate has been created and that it can be used to purchase products.

### STATUS_PARTIALLY_REDEEMED

**Type:** Number = 2

Represents a status of 'partially redeemed', which indicates that the Gift Certificate has been used to purchase products, but that there is still a balance on the gift certificate.

### STATUS_PENDING

**Type:** Number = 0

Represents a status of 'pending', which indicates that the Gift Certificate has been created but that it cannot be used yet.

### STATUS_REDEEMED

**Type:** Number = 3

Represents a status of 'redeemed', which indicates that the Gift Certificate has been used and no longer contains a balance.

## Properties

### amount

**Type:** Money (Read Only)

The original amount on the gift certificate.

### balance

**Type:** Money (Read Only)

The balance on the gift certificate.

### description

**Type:** String

The description string.

### enabled

**Type:** boolean

Returns true if the Gift Certificate is enabled, false otherwise.

### giftCertificateCode

**Type:** String (Read Only)

The code of the gift certificate. This redemption code is send to
 gift certificate recipient.

### ID

**Type:** String (Read Only)

The code of the gift certificate. This redemption code is send to
 gift certificate recipient.

### maskedGiftCertificateCode

**Type:** String (Read Only)

The masked gift certificate code with
 all but the last 4 characters replaced with a '*' character.

### merchantID

**Type:** String (Read Only)

The merchant ID of the gift certificate.

### message

**Type:** String

The message to include in the email of the person receiving
 the gift certificate.

### orderNo

**Type:** String

The order number

### recipientEmail

**Type:** String

The email address of the person receiving
 the gift certificate.

### recipientName

**Type:** String

The name of the person receiving
 the gift certificate.

### senderName

**Type:** String

The name of the person or organization that
 sent the gift certificate or null if undefined.

### status

**Type:** Number

The status where the possible values are
 STATUS_PENDING, STATUS_ISSUED, STATUS_PARTIALLY_REDEEMED
 or STATUS_REDEEMED.

## Constructor Summary

## Method Summary

### getAmount

**Signature:** `getAmount() : Money`

Returns the original amount on the gift certificate.

### getBalance

**Signature:** `getBalance() : Money`

Returns the balance on the gift certificate.

### getDescription

**Signature:** `getDescription() : String`

Returns the description string.

### getGiftCertificateCode

**Signature:** `getGiftCertificateCode() : String`

Returns the code of the gift certificate.

### getID

**Signature:** `getID() : String`

Returns the code of the gift certificate.

### getMaskedGiftCertificateCode

**Signature:** `getMaskedGiftCertificateCode() : String`

Returns the masked gift certificate code with all but the last 4 characters replaced with a '*' character.

### getMaskedGiftCertificateCode

**Signature:** `getMaskedGiftCertificateCode(ignore : Number) : String`

Returns the masked gift certificate code with all but the specified number of characters replaced with a '*' character.

### getMerchantID

**Signature:** `getMerchantID() : String`

Returns the merchant ID of the gift certificate.

### getMessage

**Signature:** `getMessage() : String`

Returns the message to include in the email of the person receiving the gift certificate.

### getOrderNo

**Signature:** `getOrderNo() : String`

Returns the order number

### getRecipientEmail

**Signature:** `getRecipientEmail() : String`

Returns the email address of the person receiving the gift certificate.

### getRecipientName

**Signature:** `getRecipientName() : String`

Returns the name of the person receiving the gift certificate.

### getSenderName

**Signature:** `getSenderName() : String`

Returns the name of the person or organization that sent the gift certificate or null if undefined.

### getStatus

**Signature:** `getStatus() : Number`

Returns the status where the possible values are STATUS_PENDING, STATUS_ISSUED, STATUS_PARTIALLY_REDEEMED or STATUS_REDEEMED.

### isEnabled

**Signature:** `isEnabled() : boolean`

Returns true if the Gift Certificate is enabled, false otherwise.

### setDescription

**Signature:** `setDescription(description : String) : void`

An optional description that you can use to categorize the gift certificate.

### setEnabled

**Signature:** `setEnabled(enabled : boolean) : void`

Controls if the Gift Certificate is enabled.

### setMessage

**Signature:** `setMessage(message : String) : void`

Sets the message to include in the email of the person receiving the gift certificate.

### setOrderNo

**Signature:** `setOrderNo(orderNo : String) : void`

Sets the order number

### setRecipientEmail

**Signature:** `setRecipientEmail(recipientEmail : String) : void`

Sets the email address of the person receiving the gift certificate.

### setRecipientName

**Signature:** `setRecipientName(recipient : String) : void`

Sets the name of the person receiving the gift certificate.

### setSenderName

**Signature:** `setSenderName(sender : String) : void`

Sets the name of the person or organization that sent the gift certificate.

### setStatus

**Signature:** `setStatus(status : Number) : void`

Sets the status of the gift certificate.

## Method Detail

## Method Details

### getAmount

**Signature:** `getAmount() : Money`

**Description:** Returns the original amount on the gift certificate.

**Returns:**

the original amount on the gift certificate.

---

### getBalance

**Signature:** `getBalance() : Money`

**Description:** Returns the balance on the gift certificate.

**Returns:**

the balance on the gift certificate.

---

### getDescription

**Signature:** `getDescription() : String`

**Description:** Returns the description string.

**Returns:**

the description.

---

### getGiftCertificateCode

**Signature:** `getGiftCertificateCode() : String`

**Description:** Returns the code of the gift certificate. This redemption code is send to gift certificate recipient.

**Returns:**

the code of the gift certificate.

---

### getID

**Signature:** `getID() : String`

**Description:** Returns the code of the gift certificate. This redemption code is send to gift certificate recipient.

**Deprecated:**

Use getGiftCertificateCode()

**Returns:**

the code of the gift certificate.

---

### getMaskedGiftCertificateCode

**Signature:** `getMaskedGiftCertificateCode() : String`

**Description:** Returns the masked gift certificate code with all but the last 4 characters replaced with a '*' character.

**Returns:**

the masked gift certificate code.

---

### getMaskedGiftCertificateCode

**Signature:** `getMaskedGiftCertificateCode(ignore : Number) : String`

**Description:** Returns the masked gift certificate code with all but the specified number of characters replaced with a '*' character.

**Parameters:**

- `ignore`: the number of characters to leave unmasked.

**Returns:**

the masked gift certificate code.

**Throws:**

IllegalArgumentException - if ignore is negative.

---

### getMerchantID

**Signature:** `getMerchantID() : String`

**Description:** Returns the merchant ID of the gift certificate.

**Returns:**

the merchant ID of the gift certificate.

---

### getMessage

**Signature:** `getMessage() : String`

**Description:** Returns the message to include in the email of the person receiving the gift certificate.

**Returns:**

the message to include in the email of the person receiving the gift certificate.

---

### getOrderNo

**Signature:** `getOrderNo() : String`

**Description:** Returns the order number

**Returns:**

the order number

---

### getRecipientEmail

**Signature:** `getRecipientEmail() : String`

**Description:** Returns the email address of the person receiving the gift certificate.

**Returns:**

the email address of the person receiving the gift certificate.

---

### getRecipientName

**Signature:** `getRecipientName() : String`

**Description:** Returns the name of the person receiving the gift certificate.

**Returns:**

the name of the person receiving the gift certificate.

---

### getSenderName

**Signature:** `getSenderName() : String`

**Description:** Returns the name of the person or organization that sent the gift certificate or null if undefined.

**Returns:**

the name of the person or organization that sent the gift certificate or null if undefined.

---

### getStatus

**Signature:** `getStatus() : Number`

**Description:** Returns the status where the possible values are STATUS_PENDING, STATUS_ISSUED, STATUS_PARTIALLY_REDEEMED or STATUS_REDEEMED.

**Returns:**

the status.

---

### isEnabled

**Signature:** `isEnabled() : boolean`

**Description:** Returns true if the Gift Certificate is enabled, false otherwise.

**Returns:**

true if the Gift Certificate is enabled, false otherwise.

---

### setDescription

**Signature:** `setDescription(description : String) : void`

**Description:** An optional description that you can use to categorize the gift certificate.

**Parameters:**

- `description`: additional description.

---

### setEnabled

**Signature:** `setEnabled(enabled : boolean) : void`

**Description:** Controls if the Gift Certificate is enabled.

**Parameters:**

- `enabled`: if true, enables the Gift Certificate.

---

### setMessage

**Signature:** `setMessage(message : String) : void`

**Description:** Sets the message to include in the email of the person receiving the gift certificate.

**Parameters:**

- `message`: the message to include in the email of the person receiving the gift certificate.

---

### setOrderNo

**Signature:** `setOrderNo(orderNo : String) : void`

**Description:** Sets the order number

**Parameters:**

- `orderNo`: the order number to be set

---

### setRecipientEmail

**Signature:** `setRecipientEmail(recipientEmail : String) : void`

**Description:** Sets the email address of the person receiving the gift certificate.

**Parameters:**

- `recipientEmail`: the email address of the person receiving the gift certificate.

---

### setRecipientName

**Signature:** `setRecipientName(recipient : String) : void`

**Description:** Sets the name of the person receiving the gift certificate.

**Parameters:**

- `recipient`: the name of the person receiving the gift certificate.

---

### setSenderName

**Signature:** `setSenderName(sender : String) : void`

**Description:** Sets the name of the person or organization that sent the gift certificate.

**Parameters:**

- `sender`: the name of the person or organization that sent the gift certificate.

---

### setStatus

**Signature:** `setStatus(status : Number) : void`

**Description:** Sets the status of the gift certificate. Possible values are: STATUS_ISSUED, STATUS_PENDING, STATUS_PARTIALLY_REDEEMED and STATUS_REDEEMED.

**Parameters:**

- `status`: Gift certificate status

---
```

--------------------------------------------------------------------------------
/src/clients/docs-client.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * SFCC Documentation Client (Refactored)
 *
 * This module provides functionality to query and retrieve SFCC class documentation
 * from converted Markdown files. It orchestrates specialized modules to handle
 * different aspects of documentation processing.
 *
 * Responsibilities:
 * - Orchestrating specialized modules
 * - Managing initialization and caching
 * - Providing public API for documentation access
 * - Applying filters and search functionality
 */

import { PathResolver } from '../utils/path-resolver.js';
import { CacheManager } from '../utils/cache.js';
import { Logger } from '../utils/logger.js';
import { DocumentationScanner, SFCCClassInfo } from './docs/documentation-scanner.js';
import { ClassContentParser, SFCCClassDetails, SFCCMethod } from './docs/class-content-parser.js';
import { ClassNameResolver } from './docs/class-name-resolver.js';
import { ReferencedTypesExtractor } from './docs/referenced-types-extractor.js';

// Re-export types for backward compatibility
export { SFCCClassInfo, SFCCMethod, SFCCClassDetails };
export type { SFCCProperty, SFCCConstant } from './docs/class-content-parser.js';

export interface ClassDetailsFilterOptions {
  includeDescription?: boolean;
  includeConstants?: boolean;
  includeProperties?: boolean;
  includeMethods?: boolean;
  includeInheritance?: boolean;
  search?: string;
}

export class SFCCDocumentationClient {
  private docsPath: string;
  private classCache: Map<string, SFCCClassInfo> = new Map();
  private cacheManager: CacheManager;
  private initialized = false;
  private logger: Logger;
  private documentationScanner: DocumentationScanner;
  private classContentParser: ClassContentParser;

  constructor() {
    this.docsPath = PathResolver.getDocsPath();
    this.cacheManager = new CacheManager();
    this.logger = Logger.getChildLogger('DocsClient');
    this.documentationScanner = new DocumentationScanner();
    this.classContentParser = new ClassContentParser();
  }

  /**
   * Initialize the documentation client by scanning all available classes
   */
  async initialize(): Promise<void> {
    if (this.initialized) {
      return;
    }

    try {
      this.classCache = await this.documentationScanner.scanDocumentation(this.docsPath);
      this.initialized = true;
    } catch (error) {
      throw new Error(`Failed to initialize SFCC documentation: ${error}`);
    }
  }

  /**
   * Get a list of all available SFCC classes
   */
  async getAvailableClasses(): Promise<string[]> {
    await this.initialize();
    return Array.from(this.classCache.keys())
      .sort()
      .map(className => ClassNameResolver.toOfficialFormat(className));
  }

  /**
   * Search for classes by name (partial matching)
   */
  async searchClasses(query: string): Promise<string[]> {
    await this.initialize();

    // Check cache first
    const cacheKey = `search:classes:${query.toLowerCase()}`;
    const cachedResult = this.cacheManager.getSearchResults(cacheKey);
    if (cachedResult) {
      return cachedResult;
    }

    const lowercaseQuery = query.toLowerCase();
    const results = Array.from(this.classCache.keys())
      .filter(className => className.toLowerCase().includes(lowercaseQuery))
      .sort()
      .map(className => ClassNameResolver.toOfficialFormat(className));

    // Cache the results
    this.cacheManager.setSearchResults(cacheKey, results);
    return results;
  }

  /**
   * Get the raw documentation content for a class
   */
  async getClassDocumentation(className: string): Promise<string | null> {
    await this.initialize();

    // Check cache first
    const normalizedClassName = ClassNameResolver.normalizeClassName(className);
    const cacheKey = `content:${normalizedClassName}`;
    const cachedContent = this.cacheManager.getFileContent(cacheKey);
    if (cachedContent !== undefined) {
      return cachedContent || null;
    }

    // Resolve class name with fallback logic
    const resolved = ClassNameResolver.resolveClassName(normalizedClassName, this.classCache);
    const content = resolved ? resolved.info.content : null;

    // Cache the result (including null results to avoid repeated lookups)
    this.cacheManager.setFileContent(cacheKey, content ?? '');

    return content;
  }

  /**
   * Parse class documentation and extract structured information
   */
  async getClassDetails(className: string): Promise<SFCCClassDetails | null> {
    // Check cache first
    const cacheKey = `details:${className}`;
    const cachedDetails = this.cacheManager.getClassDetails(cacheKey);
    if (cachedDetails !== undefined) {
      return cachedDetails;
    }

    const content = await this.getClassDocumentation(className);
    if (!content) {
      // Cache null results to avoid repeated parsing attempts
      this.cacheManager.setClassDetails(cacheKey, null);
      return null;
    }

    const details = this.classContentParser.parseClassContent(content);

    // Cache the parsed details
    this.cacheManager.setClassDetails(cacheKey, details);

    return details;
  }

  /**
   * Get class details with optional expansion of referenced types and filtering
   */
  async getClassDetailsExpanded(
    className: string,
    expand: boolean = false,
    filterOptions?: ClassDetailsFilterOptions,
  ): Promise<SFCCClassDetails & { referencedTypes?: SFCCClassDetails[] } | null> {
    // Set default filter options
    const filters = {
      includeDescription: filterOptions?.includeDescription ?? true,
      includeConstants: filterOptions?.includeConstants ?? true,
      includeProperties: filterOptions?.includeProperties ?? true,
      includeMethods: filterOptions?.includeMethods ?? true,
      includeInheritance: filterOptions?.includeInheritance ?? true,
      search: filterOptions?.search,
    };

    // Check cache first for expanded details with filter options
    const cacheKey = `details-expanded:${className}:${expand}:${JSON.stringify(filters)}`;
    const cachedResult = this.cacheManager.getClassDetails(cacheKey);
    if (cachedResult !== undefined) {
      return cachedResult;
    }

    const classDetails = await this.getClassDetails(className);
    if (!classDetails) {
      this.cacheManager.setClassDetails(cacheKey, null);
      return null;
    }

    // Apply filtering and search to the class details
    const filteredDetails = this.applyFiltersAndSearch(classDetails, filters);

    if (!expand) {
      this.cacheManager.setClassDetails(cacheKey, filteredDetails);
      return filteredDetails;
    }

    // Get the raw content to extract referenced types
    const content = await this.getClassDocumentation(className);
    if (!content) {
      this.cacheManager.setClassDetails(cacheKey, filteredDetails);
      return filteredDetails;
    }

    const referencedTypeNames = ReferencedTypesExtractor.extractFilteredReferencedTypes(content, className);
    const referencedTypes: SFCCClassDetails[] = [];

    // Get details for each referenced type
    for (const typeName of referencedTypeNames) {
      try {
        const typeDetails = await this.getClassDetails(typeName);
        if (typeDetails) {
          referencedTypes.push(typeDetails);
        }
      } catch {
        // Silently skip types that can't be found
        this.logger.warn(`Could not find details for referenced type: ${typeName}`);
      }
    }

    const result = {
      ...filteredDetails,
      referencedTypes: referencedTypes.length > 0 ? referencedTypes : undefined,
    };

    // Cache the result
    this.cacheManager.setClassDetails(cacheKey, result);

    return result;
  }

  /**
   * Apply filters and search to class details
   */
  private applyFiltersAndSearch(
    classDetails: SFCCClassDetails,
    filters: {
      includeDescription: boolean;
      includeConstants: boolean;
      includeProperties: boolean;
      includeMethods: boolean;
      includeInheritance: boolean;
      search?: string;
    },
  ): SFCCClassDetails {
    const result: SFCCClassDetails = {
      className: classDetails.className,
      packageName: classDetails.packageName,
      description: filters.includeDescription ? classDetails.description : '',
      constants: [],
      properties: [],
      methods: [],
      inheritance: filters.includeInheritance ? classDetails.inheritance : undefined,
      constructorInfo: classDetails.constructorInfo,
    };

    // Apply search filter to constants
    if (filters.includeConstants) {
      result.constants = filters.search
        ? classDetails.constants.filter(constant =>
          this.matchesSearch(constant.name, constant.description, filters.search!),
        )
        : classDetails.constants;
    }

    // Apply search filter to properties
    if (filters.includeProperties) {
      result.properties = filters.search
        ? classDetails.properties.filter(property =>
          this.matchesSearch(property.name, property.description, filters.search!),
        )
        : classDetails.properties;
    }

    // Apply search filter to methods
    if (filters.includeMethods) {
      result.methods = filters.search
        ? classDetails.methods.filter(method =>
          this.matchesSearch(method.name, method.description, filters.search!) ||
          this.matchesSearch(method.signature, '', filters.search!),
        )
        : classDetails.methods;
    }

    // Apply search filter to inheritance
    if (filters.includeInheritance && filters.search && result.inheritance) {
      result.inheritance = result.inheritance.filter(inheritanceItem =>
        this.matchesSearch(inheritanceItem, '', filters.search!),
      );
    }

    return result;
  }

  /**
   * Check if a name or description matches the search term (case-insensitive)
   */
  private matchesSearch(name: string, description: string, searchTerm: string): boolean {
    const search = searchTerm.toLowerCase();
    return (
      name.toLowerCase().includes(search) ||
      description.toLowerCase().includes(search)
    );
  }

  /**
   * Search for methods across all classes
   */
  async searchMethods(methodName: string): Promise<{ className: string; method: SFCCMethod }[]> {
    await this.initialize();

    // Check cache first
    const cacheKey = `search:methods:${methodName.toLowerCase()}`;
    const cachedResult = this.cacheManager.getMethodSearch(cacheKey);
    if (cachedResult) {
      return cachedResult;
    }

    const results: { className: string; method: SFCCMethod }[] = [];

    for (const [fullClassName] of this.classCache) {
      const details = await this.getClassDetails(fullClassName);
      const methods = details?.methods ?? [];
      for (const method of methods) {
        if (method.name.toLowerCase().includes(methodName.toLowerCase())) {
          results.push({
            className: fullClassName,
            method,
          });
        }
      }
    }

    // Cache the search results
    this.cacheManager.setMethodSearch(cacheKey, results);
    return results;
  }

  /**
   * Get cache statistics for monitoring performance
   */
  getCacheStats(): ReturnType<CacheManager['getAllStats']> {
    return this.cacheManager.getAllStats();
  }

  /**
   * Clear all caches
   */
  clearCache(): void {
    this.cacheManager.clearAll();
  }

  /**
   * Cleanup resources and destroy caches
   */
  destroy(): void {
    this.cacheManager.destroy();
  }
}

```

--------------------------------------------------------------------------------
/docs/dw_campaign/PromotionPlan.md:
--------------------------------------------------------------------------------

```markdown
## Package: dw.campaign

# Class PromotionPlan

## Inheritance Hierarchy

- Object
  - dw.campaign.PromotionPlan

## Description

PromotionPlan represents a set of Promotion instances and is used to display active or upcoming promotions on storefront pages, or to pass it to the PromotionMgr to calculate a DiscountPlan and subsequently apply discounts to a line item container. Instances of the class are returned by the PromotionMgr.getActivePromotions(), PromotionMgr.getActiveCustomerPromotions() and PromotionMgr.getUpcomingPromotions(Number). PromotionPlan provides methods to access the promotions in the plan and to remove promotions from the plan. All methods which return a collection of promotions sort them by the following ordered criteria: Exclusivity: GLOBAL exclusive promotions first, followed by CLASS exclusive promotions, and NO exclusive promotions last. Rank: sorted ascending Promotion Class: PRODUCT promotions first, followed by ORDER promotions, and SHIPPING promotions last. Discount type: Fixed price promotions first, followed by free, amount-off, percentage-off, and bonus product promotions last. Best discount: Sorted descending. For example, 30% off comes before 20% off. ID: alphanumeric ascending.

## Constants

### SORT_BY_EXCLUSIVITY

**Type:** Number = 1

Constant indicating that a collection of promotions should be sorted first by exclusivity, then rank, promotion class, etc. See class-level javadoc for details. This is the default sort order for methods that return a collection of promotions.

### SORT_BY_START_DATE

**Type:** Number = 2

Constant indicating that a collection of promotions should be sorted by start date ascending. If there is no explicit start date for a promotion the start date of its containing Campaign or AB-test is used instead. Promotions without a start date are sorted before promotions with a start date in the future and after promotions with a start date in the past. In case two promotion assignments have the same start date, they are sorted by their ID.

## Properties

### orderPromotions

**Type:** Collection (Read Only)

All order promotions contained in this plan.

### productPromotions

**Type:** Collection (Read Only)

All product promotions contained in this plan.

### promotions

**Type:** Collection (Read Only)

All promotions contained in this plan sorted by exclusivity.

### shippingPromotions

**Type:** Collection (Read Only)

All shipping promotions contained in this plan.

## Constructor Summary

## Method Summary

### getOrderPromotions

**Signature:** `getOrderPromotions() : Collection`

Returns all order promotions contained in this plan.

### getPaymentCardPromotions

**Signature:** `getPaymentCardPromotions(paymentCard : PaymentCard) : Collection`

Returns the order promotions explicitly associated to the specified discounted payment card.

### getPaymentMethodPromotions

**Signature:** `getPaymentMethodPromotions(paymentMethod : PaymentMethod) : Collection`

Returns the order promotions explicitly associated to the specified discounted payment method.

### getProductPromotions

**Signature:** `getProductPromotions() : Collection`

Returns all product promotions contained in this plan.

### getProductPromotions

**Signature:** `getProductPromotions(product : Product) : Collection`

Returns the promotions related to the specified product.

### getProductPromotionsForDiscountedProduct

**Signature:** `getProductPromotionsForDiscountedProduct(product : Product) : Collection`

Returns the product promotions for which the specified product is a discounted (and possibly also a qualifying) product.

### getProductPromotionsForQualifyingProduct

**Signature:** `getProductPromotionsForQualifyingProduct(product : Product) : Collection`

Returns the product promotions for which the specified product is a qualifying but NOT a discounted product.

### getPromotions

**Signature:** `getPromotions() : Collection`

Returns all promotions contained in this plan sorted by exclusivity.

### getPromotions

**Signature:** `getPromotions(sortOrder : Number) : Collection`

Returns all promotions contained in this plan sorted according to the specified sort order.

### getPromotions

**Signature:** `getPromotions(product : Product) : Collection`

Returns the promotions related to the specified product.

### getShippingPromotions

**Signature:** `getShippingPromotions() : Collection`

Returns all shipping promotions contained in this plan.

### getShippingPromotions

**Signature:** `getShippingPromotions(shippingMethod : ShippingMethod) : Collection`

Returns the shipping promotions related to the specified discounted shipping method, i.e.

### removePromotion

**Signature:** `removePromotion(promotion : Promotion) : void`

Remove promotion from promotion plan.

## Method Detail

## Method Details

### getOrderPromotions

**Signature:** `getOrderPromotions() : Collection`

**Description:** Returns all order promotions contained in this plan.

**Returns:**

The sorted collection of order promotions contained in the promotion plan.

---

### getPaymentCardPromotions

**Signature:** `getPaymentCardPromotions(paymentCard : PaymentCard) : Collection`

**Description:** Returns the order promotions explicitly associated to the specified discounted payment card. This method is usually used to display order promotions along with payment card choices.

**Parameters:**

- `paymentCard`: Discounted payment card

**Returns:**

The sorted collection of order promotions associated with the specified payment card.

---

### getPaymentMethodPromotions

**Signature:** `getPaymentMethodPromotions(paymentMethod : PaymentMethod) : Collection`

**Description:** Returns the order promotions explicitly associated to the specified discounted payment method. This method is usually used to display order promotions along with payment method choices.

**Parameters:**

- `paymentMethod`: Discounted payment method

**Returns:**

The sorted collection of order promotions associated with the specified payment method.

---

### getProductPromotions

**Signature:** `getProductPromotions() : Collection`

**Description:** Returns all product promotions contained in this plan.

**Returns:**

The sorted collection of product promotions contained in the promotion plan.

---

### getProductPromotions

**Signature:** `getProductPromotions(product : Product) : Collection`

**Description:** Returns the promotions related to the specified product. The method returns all promotions where the product is either a qualifying product, or a discounted product, or both. It also returns promotions where the specified product is a bonus product. This method is usually used to display product promotions on a product details page. If a master product is passed, then this method will return promotions which are relevant for the master itself or at least one of its variants.

**Parameters:**

- `product`: Product associated with promotion

**Returns:**

The sorted collection of promotions related to specified discounted product.

---

### getProductPromotionsForDiscountedProduct

**Signature:** `getProductPromotionsForDiscountedProduct(product : Product) : Collection`

**Description:** Returns the product promotions for which the specified product is a discounted (and possibly also a qualifying) product. It also returns promotions where the specified product is a bonus product. This method is usually used to display product promotions on a product details page when separate callout messages are defined depending on if the product is a qualifying or discounted product for the promotion. If a master product is passed, then this method will return promotions for which the master product itself or at least one of its product's variants is a discounted product.

**Parameters:**

- `product`: The discounted product.

**Returns:**

Product promotions related to the specified discounted product.

---

### getProductPromotionsForQualifyingProduct

**Signature:** `getProductPromotionsForQualifyingProduct(product : Product) : Collection`

**Description:** Returns the product promotions for which the specified product is a qualifying but NOT a discounted product. This method is usually used to display product promotions on a product details page when separate callout messages are defined depending on if the product is a qualifying or discounted product for the promotion. If a master product is passed, then this method will return promotions for which the master product itself or at least one of its product's variants is a qualifying product.

**Parameters:**

- `product`: The qualifying product.

**Returns:**

Product promotions related to the specified qualifying product.

---

### getPromotions

**Signature:** `getPromotions() : Collection`

**Description:** Returns all promotions contained in this plan sorted by exclusivity.

**Returns:**

The sorted collection of promotions contained in the promotion plan.

---

### getPromotions

**Signature:** `getPromotions(sortOrder : Number) : Collection`

**Description:** Returns all promotions contained in this plan sorted according to the specified sort order. If the passed sort order is invalid, then the returned promotions will be sorted by exclusivity.

**Parameters:**

- `sortOrder`: the sort order to use. Must be SORT_BY_EXCLUSIVITY or SORT_BY_START_DATE. If an invalid value is passed, SORT_BY_EXCLUSIVITY is used.

**Returns:**

The sorted collection of promotions contained in the promotion plan.

---

### getPromotions

**Signature:** `getPromotions(product : Product) : Collection`

**Description:** Returns the promotions related to the specified product. The method returns all promotions where the product is either a qualifying product, or a discounted product, or both. It also returns promotions where the specified product is a bonus product. This method is usually used to display product promotions on a product details page.

**Deprecated:**

Use getProductPromotions(Product)

**Parameters:**

- `product`: Product associated with promotion

**Returns:**

The sorted collection of promotions related to the specified discounted product.

---

### getShippingPromotions

**Signature:** `getShippingPromotions() : Collection`

**Description:** Returns all shipping promotions contained in this plan.

**Returns:**

The sorted collection of shipping promotions contained in promotion plan.

---

### getShippingPromotions

**Signature:** `getShippingPromotions(shippingMethod : ShippingMethod) : Collection`

**Description:** Returns the shipping promotions related to the specified discounted shipping method, i.e. the returned promotions apply a discount on the specified shipping method. This method is usually used to display shipping promotions along with shipping methods.

**Parameters:**

- `shippingMethod`: Discounted shipping method

**Returns:**

The sorted collection of shipping promotions with specified method as discounted method.

---

### removePromotion

**Signature:** `removePromotion(promotion : Promotion) : void`

**Description:** Remove promotion from promotion plan. Please note that this is the only way to remove promotions from the plan, i.e. removing promotions from the collections returned by methods such as getProductPromotions() has no effect on the promotion plan.

**Parameters:**

- `promotion`: Promotion to remove from promotion plan

---
```

--------------------------------------------------------------------------------
/tests/mcp/node/get-available-sfra-documents.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
import { test, describe, before, after, beforeEach } from 'node:test';
import { strict as assert } from 'node:assert';
import { connect } from 'mcp-aegis';

/**
 * Programmatic tests for the MCP tool: get_available_sfra_documents
 * Mirrors YAML tests but adds deeper JSON parsing & resilience checks.
 *
 * Validates:
 * 1. Tool is registered
 * 2. Basic successful invocation returns non-empty array JSON string
 * 3. JSON parse yields objects with required keys (id, name, category, filename)
 * 4. Category coverage (expects at least: core, product, order, customer, pricing, store, other)
 * 5. Extraneous argument tolerance (should ignore unexpected params)
 * 6. Negative invalid method error path via raw JSON-RPC call
 */

describe('get_available_sfra_documents (programmatic)', () => {
  let client;
  const CONFIG = './aegis.config.docs-only.json';

  before(async () => {
    client = await connect(CONFIG);
  });

  after(async () => {
    if (client?.connected) await client.disconnect();
  });

  beforeEach(() => {
    client.clearAllBuffers(); // Recommended - comprehensive protection
  });

  const TOOL_NAME = 'get_available_sfra_documents';
  const REQUIRED_CATEGORIES = ['core', 'product', 'order', 'customer', 'pricing', 'store', 'other'];
  // Actual response objects do NOT include an explicit 'id' field; we validate name/category/filename
  const REQUIRED_KEYS = ['name', 'category', 'filename'];

  test('tool should be registered', async () => {
    const tools = await client.listTools();
    const names = tools.map(t => t.name);
    assert.ok(names.includes(TOOL_NAME), 'Tool not found in listTools');
  });

  async function invoke(extraArgs = {}) {
    const result = await client.callTool(TOOL_NAME, { ...extraArgs });
    assert.equal(result.isError, false, 'Tool invocation should not be error');
    assert.ok(result.content && Array.isArray(result.content) && result.content.length > 0, 'Content array expected');
    const textBlocks = result.content.filter(c => c.type === 'text');
    assert.ok(textBlocks.length > 0, 'At least one text block expected');
    const raw = textBlocks.map(t => t.text).join('\n');
    return { raw, result };
  }

  function safeParseDocuments(raw) {
    let docs;
    try {
      docs = JSON.parse(raw);
  } catch {
      // Attempt to extract JSON array via regex fallback if wrapping noise present
      const match = raw.match(/\[[\s\S]*\]/);
      assert.ok(match, 'Could not locate JSON array in content');
      docs = JSON.parse(match[0]);
    }
    assert.ok(Array.isArray(docs), 'Parsed docs should be array');
    assert.ok(docs.length >= 15, 'Expect at least 15 SFRA documents');
    return docs;
  }

  test('basic invocation returns valid JSON array with required keys', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    // Validate keys & reasonable string values
    for (const d of docs.slice(0, 25)) { // sample first 25 to keep runtime low
      for (const key of REQUIRED_KEYS) {
        assert.ok(Object.prototype.hasOwnProperty.call(d, key), `Missing key ${key}`);
        assert.equal(typeof d[key], 'string', `${key} should be string`);
        assert.ok(d[key].length > 0, `${key} should be non-empty`);
      }
    }
  });

  test('category coverage present', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const categories = new Set(docs.map(d => d.category));
    for (const c of REQUIRED_CATEGORIES) {
      assert.ok(categories.has(c), `Expected category ${c}`);
    }
  });

  test('extraneous argument is ignored', async () => {
    const { raw } = await invoke({ unused: 'value' });
    const docs = safeParseDocuments(raw);
    assert.ok(docs.length >= 15, 'At least 15 docs expected with extraneous argument');
  });

  test('multiple product-* occurrences and filename pattern sanity', async () => {
    const { raw } = await invoke();
    assert.match(raw, /(product-).*(product-).*(product-)/s, 'Expect at least three product- occurrences');
  assert.match(raw, /"filename"\s*:\s*"[a-z0-9-]+\.md"/, 'Expect filename .md entries');
  });

  test('negative path: invalid method should return JSON-RPC error', async () => {
    // Raw JSON-RPC send with invalid method name returns error response (not throw)
    const response = await client.sendMessage({
      jsonrpc: '2.0',
      id: 'bad-method-1',
      method: 'tools/call_WRONG',
      params: { name: TOOL_NAME, arguments: {} }
    });
    assert.ok(response.error, 'Expected JSON-RPC error object');
    assert.match(response.error.message || '', /method/i, 'Error message should mention method');
  });

  test('argument validation error path (null arguments object)', async () => {
    let caught = null;
    try {
      await client.callTool(TOOL_NAME, null);
    } catch (err) {
      caught = err;
    }
    assert.ok(caught, 'Expected callTool to reject with invalid_type');
    assert.match(caught.message, /invalid_type|Expected object/i, 'Error should mention invalid type');
  });

  test('filenames unique and kebab-case', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const filenames = docs.map(d => d.filename);
    const fileSet = new Set(filenames);
    assert.equal(fileSet.size, filenames.length, 'Filenames must be unique');
    for (const f of filenames.slice(0, 50)) {
      assert.match(f, /^[a-z0-9-]+\.md$/, 'filename should be kebab-case .md');
    }
  });

  test('filename semantic correlation with name (first token appears in name)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    for (const d of docs.slice(0, 40)) {
      assert.match(d.filename, /^[a-z0-9-]+\.md$/, 'filename must be kebab-case .md');
      const firstSegment = d.filename.replace(/\.md$/, '').split('-')[0];
      assert.ok(d.name.toLowerCase().includes(firstSegment), 'name should contain filename first segment');
    }
  });

  test('category distribution sanity (no category monopolizes >70%)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const counts = docs.reduce((acc, d) => { acc[d.category] = (acc[d.category]||0)+1; return acc; }, {});
    const total = docs.length;
    for (const [cat, cnt] of Object.entries(counts)) {
      assert.ok(cnt / total <= 0.7, `Category ${cat} dominates with ${(cnt/total*100).toFixed(1)}%`);
    }
  });

  test('deterministic ordering (two calls produce identical first 10 IDs)', async () => {
    const first = safeParseDocuments((await invoke()).raw).slice(0,10).map(d=>d.id);
    const second = safeParseDocuments((await invoke()).raw).slice(0,10).map(d=>d.id);
    assert.deepEqual(second, first, 'First 10 IDs should be stable across calls');
  });

  test('sample documents contain expected semantic keywords', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const textBlob = JSON.stringify(docs.slice(0, 30)).toLowerCase();
    const expected = ['server', 'request', 'response', 'product', 'price', 'cart'];
    for (const token of expected) {
      assert.ok(textBlob.includes(token), `Expected token ${token} in first 30 docs blob`);
    }
  });

  test('no empty or placeholder names', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    for (const d of docs) {
      assert.ok(d.name.trim().length > 2, 'name should be >2 chars');
      assert.ok(!/^(todo|tbd|placeholder)$/i.test(d.name), 'name should not be placeholder');
    }
  });

  test('round-trip JSON parse reproducibility (stringify->parse equality for first 5)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw).slice(0,5);
    const roundTrip = JSON.parse(JSON.stringify(docs));
    assert.deepEqual(roundTrip, docs, 'Round trip serialization must preserve first 5 docs');
  });

  test('at least one document per required category and each category has <= 50% of docs', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const counts = docs.reduce((acc, d) => { acc[d.category] = (acc[d.category]||0)+1; return acc; }, {});
    const total = docs.length;
    for (const c of REQUIRED_CATEGORIES) {
      assert.ok(counts[c] > 0, `Category ${c} missing`);
      assert.ok((counts[c] / total) <= 0.5, `Category ${c} exceeds 50% threshold`);
    }
  });

  test('filename/id pairing uniqueness (no duplicate filename)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const filenames = docs.map(d => d.filename);
    const fileSet = new Set(filenames);
    assert.equal(fileSet.size, filenames.length, 'Filenames must be unique');
  });

  test('sorted by name alpha? (non-fatal heuristic) warn if not mostly sorted', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw).slice(0,30);
    const names = docs.map(d=>d.name.toLowerCase());
    const sorted = [...names].sort();
    let outOfOrder = 0;
    names.forEach((n,i)=>{ if(n!==sorted[i]) outOfOrder++; });
    if (outOfOrder > Math.ceil(names.length * 0.4)) {
      console.warn(`Heuristic: more than 40% (${outOfOrder}/${names.length}) of first 30 names out of alpha order`);
    }
  });

  // Removed category/id correlation test (no id field in response)

  // Removed id/filename prefix correlation (id not present)

  test('document count stable across two invocations (delta <= 2)', async () => {
    const firstLen = safeParseDocuments((await invoke()).raw).length;
    const secondLen = safeParseDocuments((await invoke()).raw).length;
    const delta = Math.abs(firstLen - secondLen);
    assert.ok(delta <= 2, `Doc count changed unexpectedly by ${delta}`);
  });

  test('category histogram logged for diagnostic insight (non-asserting)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const counts = docs.reduce((acc, d) => { acc[d.category] = (acc[d.category]||0)+1; return acc; }, {});
    console.info('SFRA docs category histogram:', counts);
  });

  test('ensure presence of canonical core documents', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const names = docs.map(d=>d.name.toLowerCase());
    const required = ['server', 'request', 'response', 'querystring', 'render'];
    for (const r of required) {
      assert.ok(names.some(n=>n.includes(r)), `Missing canonical core doc: ${r}`);
    }
  });

  test('ensure presence of key product/order/customer domain docs', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    const blob = JSON.stringify(docs).toLowerCase();
    const expected = ['product', 'cart', 'shipping', 'billing', 'account', 'customer', 'price'];
    for (const token of expected) {
      assert.ok(blob.includes(token), `Missing expected domain token: ${token}`);
    }
  });

  test('no obvious placeholder filenames', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    for (const d of docs) {
      assert.ok(!/(placeholder|temp|dummy)/i.test(d.filename), 'Filename should not look placeholder');
    }
  });

  test('filename extension enforcement (.md only)', async () => {
    const { raw } = await invoke();
    const docs = safeParseDocuments(raw);
    for (const d of docs) {
      assert.ok(d.filename.endsWith('.md'), 'All filenames must end with .md');
    }
  });
});

```

--------------------------------------------------------------------------------
/tests/validation-helpers.test.ts:
--------------------------------------------------------------------------------

```typescript
import { ValidationHelpers, ValidationRule, CommonValidations } from '../src/core/handlers/validation-helpers.js';
import { HandlerError } from '../src/core/handlers/base-handler.js';

describe('ValidationHelpers', () => {
  describe('validateArguments', () => {
    it('should pass when all required fields are present and valid', () => {
      const args = {
        stringField: 'value1',
        numberField: 42,
        booleanField: true,
        objectField: { prop: 'value' },
      };

      const rules: ValidationRule[] = [
        { field: 'stringField', required: true, type: 'string' },
        { field: 'numberField', required: true, type: 'number' },
        { field: 'booleanField', required: true, type: 'boolean' },
        { field: 'objectField', required: true, type: 'object' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should throw HandlerError when required field is missing', () => {
      const args = {
        field1: 'value1',
      };

      const rules: ValidationRule[] = [
        { field: 'field1', required: true, type: 'string' },
        { field: 'field2', required: true, type: 'string' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow(HandlerError);

      try {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      } catch (error) {
        expect(error).toBeInstanceOf(HandlerError);
        expect((error as HandlerError).message).toBe('field2 is required');
        expect((error as HandlerError).toolName).toBe('test_tool');
        expect((error as HandlerError).code).toBe('MISSING_ARGUMENT');
      }
    });

    it('should throw when required field is null', () => {
      const args = {
        field1: 'value1',
        field2: null,
      };

      const rules: ValidationRule[] = [
        { field: 'field1', required: true, type: 'string' },
        { field: 'field2', required: true, type: 'string' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow('field2 is required');
    });

    it('should throw when required field is empty string', () => {
      const args = {
        field1: 'value1',
        field2: '',
      };

      const rules: ValidationRule[] = [
        { field: 'field1', required: true, type: 'string' },
        { field: 'field2', required: true, type: 'string' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow('field2 is required');
    });

    it('should throw when field type is incorrect', () => {
      const args = {
        stringField: 123, // Should be string
      };

      const rules: ValidationRule[] = [
        { field: 'stringField', required: true, type: 'string' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow(HandlerError);

      try {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      } catch (error) {
        expect(error).toBeInstanceOf(HandlerError);
        expect((error as HandlerError).message).toBe('stringField must be of type string');
        expect((error as HandlerError).code).toBe('INVALID_TYPE');
      }
    });

    it('should pass when optional field is missing', () => {
      const args = {
        required: 'value',
      };

      const rules: ValidationRule[] = [
        { field: 'required', required: true, type: 'string' },
        { field: 'optional', required: false, type: 'string' },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should validate custom validator function', () => {
      const args = {
        email: 'invalid-email',
      };

      const rules: ValidationRule[] = [
        {
          field: 'email',
          required: true,
          type: 'string',
          validator: (value: string) => value.includes('@'),
          errorMessage: 'email must be a valid email address',
        },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow('email must be a valid email address');
    });

    it('should pass custom validator when valid', () => {
      const args = {
        email: '[email protected]',
      };

      const rules: ValidationRule[] = [
        {
          field: 'email',
          required: true,
          type: 'string',
          validator: (value: string) => value.includes('@'),
          errorMessage: 'email must be a valid email address',
        },
      ];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });
  });

  describe('requireStrings', () => {
    it('should pass when all required string fields are present', () => {
      const args = {
        field1: 'value1',
        field2: 'value2',
      };

      expect(() => {
        ValidationHelpers.requireStrings(args, ['field1', 'field2'], 'test_tool');
      }).not.toThrow();
    });

    it('should throw when required string field is missing', () => {
      const args = {
        field1: 'value1',
      };

      expect(() => {
        ValidationHelpers.requireStrings(args, ['field1', 'field2'], 'test_tool');
      }).toThrow('field2 is required for test_tool');
    });

    it('should pass when no fields are required', () => {
      const args = { field1: 'value1' };

      expect(() => {
        ValidationHelpers.requireStrings(args, [], 'test_tool');
      }).not.toThrow();
    });
  });

  describe('type validation', () => {
    it('should validate string type correctly', () => {
      const args = { field: 'string value' };
      const rules: ValidationRule[] = [{ field: 'field', type: 'string' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should validate number type correctly', () => {
      const args = { field: 42 };
      const rules: ValidationRule[] = [{ field: 'field', type: 'number' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should validate boolean type correctly', () => {
      const args = { field: true };
      const rules: ValidationRule[] = [{ field: 'field', type: 'boolean' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should validate object type correctly', () => {
      const args = { field: { prop: 'value' } };
      const rules: ValidationRule[] = [{ field: 'field', type: 'object' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should validate array type correctly', () => {
      const args = { field: ['item1', 'item2'] };
      const rules: ValidationRule[] = [{ field: 'field', type: 'array' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).not.toThrow();
    });

    it('should reject array when object expected', () => {
      const args = { field: ['item1', 'item2'] };
      const rules: ValidationRule[] = [{ field: 'field', type: 'object' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow('field must be of type object');
    });

    it('should reject null when object expected', () => {
      const args = { field: null };
      const rules: ValidationRule[] = [{ field: 'field', required: true, type: 'object' }];

      expect(() => {
        ValidationHelpers.validateArguments(args, rules, 'test_tool');
      }).toThrow('field is required');
    });
  });

  describe('CommonValidations', () => {
    describe('Generic validation methods', () => {
      it('should support generic requiredString validation', () => {
        const rules = CommonValidations.requiredString('customField');
        expect(rules).toEqual([{
          field: 'customField',
          required: true,
          type: 'string',
          validator: expect.any(Function),
          errorMessage: 'customField must be a non-empty string',
        }]);
      });

      it('should support custom error messages in requiredString', () => {
        const rules = CommonValidations.requiredString('customField', 'Custom error message');
        expect(rules[0].errorMessage).toBe('Custom error message');
      });

      it('should validate with generic requiredString', () => {
        const args = { customField: 'valid-value' };
        const rules = CommonValidations.requiredString('customField');

        expect(() => {
          ValidationHelpers.validateArguments(args, rules, 'test_tool');
        }).not.toThrow();
      });

      it('should fail with empty string in requiredString', () => {
        const args = { customField: '' };
        const rules = CommonValidations.requiredString('customField');

        expect(() => {
          ValidationHelpers.validateArguments(args, rules, 'test_tool');
        }).toThrow('customField must be a non-empty string');
      });

      it('should support generic requiredField validation', () => {
        const rules = CommonValidations.requiredField(
          'myField',
          'number',
          (value: number) => value > 0,
          'myField must be positive',
        );
        expect(rules).toEqual([{
          field: 'myField',
          required: true,
          type: 'number',
          validator: expect.any(Function),
          errorMessage: 'myField must be positive',
        }]);
      });

      it('should support optionalField validation', () => {
        const rules = CommonValidations.optionalField(
          'optionalField',
          'boolean',
          (value: boolean) => typeof value === 'boolean',
          'optionalField must be boolean',
        );
        expect(rules[0].required).toBe(false);
      });

      it('should validate cartridge name with custom pattern', () => {
        const rules = CommonValidations.requiredField(
          'cartridgeName',
          'string',
          (value: string) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value),
          'cartridgeName must be a valid identifier (letters, numbers, underscore, hyphen)',
        );

        // Test valid cartridge name
        const validArgs = { cartridgeName: 'plugin_example' };
        expect(() => {
          ValidationHelpers.validateArguments(validArgs, rules, 'test_tool');
        }).not.toThrow();

        // Test invalid cartridge name
        const invalidArgs = { cartridgeName: '123invalid' };
        expect(() => {
          ValidationHelpers.validateArguments(invalidArgs, rules, 'test_tool');
        }).toThrow('cartridgeName must be a valid identifier');
      });

      it('should validate className pattern', () => {
        const rules = CommonValidations.requiredString('className');

        // Test valid className
        const validArgs = { className: 'Product' };
        expect(() => {
          ValidationHelpers.validateArguments(validArgs, rules, 'test_tool');
        }).not.toThrow();

        // Test empty className
        const invalidArgs = { className: '' };
        expect(() => {
          ValidationHelpers.validateArguments(invalidArgs, rules, 'test_tool');
        }).toThrow('className must be a non-empty string');
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get-sfra-categories.docs-only.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
description: "Comprehensive tests for get_sfra_categories tool in docs-only mode"

tests:
  # Basic functionality tests
  - it: "should return all SFRA category information with proper structure"
    request:
      jsonrpc: "2.0" 
      id: "test-1"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-1"
        result:
          content:
            - type: "text"
              text: "match:type:string"
          isError: false
      stderr: "toBeEmpty"

  - it: "should return JSON array of category objects"
    request:
      jsonrpc: "2.0"
      id: "test-2"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-2"
        result:
          content:
            - type: "text"
              text: "match:regex:^\\[[\\s\\S]*\\]$"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include all expected category fields"
    request:
      jsonrpc: "2.0"
      id: "test-3"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-3"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*category[\\s\\S]*count[\\s\\S]*description[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Specific category validation tests
  - it: "should include core category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-4"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-4"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"core\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Core SFRA classes[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include customer category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-5"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-5"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"customer\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Customer account[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include order category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-6"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-6"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"order\"[\\s\\S]*\"count\":[\\s]*6[\\s\\S]*Order, cart, billing[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include product category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-7"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-7"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"product\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Product-related[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include pricing category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-8"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-8"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"pricing\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Pricing and discount[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include store category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-9"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-9"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"store\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Store and location[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include other category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-10"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-10"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"other\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Other models and utilities[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Count validation tests
  - it: "should return exactly 7 categories"
    request:
      jsonrpc: "2.0"
      id: "test-11"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-11"
        result:
          content:
            - type: "text"
              text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have core with count 5"
    request:
      jsonrpc: "2.0"
      id: "test-12"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-12"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 5"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have customer with count 2"
    request:
      jsonrpc: "2.0"
      id: "test-13"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-13"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 2"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have order with count 6"
    request:
      jsonrpc: "2.0"
      id: "test-14"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-14"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 6"
          isError: false
      stderr: "toBeEmpty"

  # JSON structure validation tests
  - it: "should return valid JSON structure with proper formatting"
    request:
      jsonrpc: "2.0"
      id: "test-15"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-15"
        result:
          content:
            - type: "text"
              text: "match:regex:^\\[[\\s\\S]*\\{[\\s\\S]*\"category\"[\\s\\S]*\"count\"[\\s\\S]*\"description\"[\\s\\S]*\\}[\\s\\S]*\\]$"
          isError: false
      stderr: "toBeEmpty"

  # Error handling and edge case tests
  - it: "should handle empty parameters gracefully"
    request:
      jsonrpc: "2.0"
      id: "test-16"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-16"
        result:
          content:
            - type: "text"
              text: "match:contains:core"
          isError: false
      stderr: "toBeEmpty"

  - it: "should ignore invalid parameters and return normal result"
    request:
      jsonrpc: "2.0"
      id: "test-17"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments:
          invalid: "param"
          another: "value"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-17"
        result:
          content:
            - type: "text"
              text: "match:contains:\"category\": \"core\""
          isError: false
      stderr: "toBeEmpty"

  # Performance tests
  - it: "should respond quickly for metadata operation"
    request:
      jsonrpc: "2.0"
      id: "test-18"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-18"
        result:
          content:
            - type: "text"
              text: "match:type:string"
          isError: false
      performance:
        maxResponseTime: "500ms"
      stderr: "toBeEmpty"

  # Content validation patterns
  - it: "should have consistent category naming patterns"
    request:
      jsonrpc: "2.0"
      id: "test-19"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-19"
        result:
          content:
            - type: "text"
              text: "match:regex:\"category\":[\\s]*\"[a-z]+\""
          isError: false
      stderr: "toBeEmpty"

  - it: "should have numeric count values"
    request:
      jsonrpc: "2.0"
      id: "test-20"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-20"
        result:
          content:
            - type: "text"
              text: "match:regex:\"count\":[\\s]*[0-9]+"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have descriptive text for all categories"
    request:
      jsonrpc: "2.0"
      id: "test-21"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-21"
        result:
          content:
            - type: "text"
              text: "match:regex:\"description\":[\\s]*\"[A-Z][\\s\\S]*\""
          isError: false
      stderr: "toBeEmpty"

  # Category completeness test - adjusted order to match actual response order
  - it: "should include all expected SFRA category names in order"
    request:
      jsonrpc: "2.0"
      id: "test-22"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-22"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*core[\\s\\S]*customer[\\s\\S]*order[\\s\\S]*other[\\s\\S]*pricing[\\s\\S]*product[\\s\\S]*store[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Robustness tests
  - it: "should return consistent results across multiple calls"
    request:
      jsonrpc: "2.0"
      id: "test-23"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-23"
        result:
          content:
            - type: "text"
              text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
          isError: false
      stderr: "toBeEmpty"

```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get-sfra-categories.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
description: "Comprehensive tests for get_sfra_categories tool in full-mode mode"

tests:
  # Basic functionality tests
  - it: "should return all SFRA category information with proper structure"
    request:
      jsonrpc: "2.0" 
      id: "test-1"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-1"
        result:
          content:
            - type: "text"
              text: "match:type:string"
          isError: false
      stderr: "toBeEmpty"

  - it: "should return JSON array of category objects"
    request:
      jsonrpc: "2.0"
      id: "test-2"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-2"
        result:
          content:
            - type: "text"
              text: "match:regex:^\\[[\\s\\S]*\\]$"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include all expected category fields"
    request:
      jsonrpc: "2.0"
      id: "test-3"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-3"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*category[\\s\\S]*count[\\s\\S]*description[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Specific category validation tests
  - it: "should include core category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-4"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-4"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"core\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Core SFRA classes[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include customer category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-5"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-5"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"customer\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Customer account[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include order category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-6"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-6"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"order\"[\\s\\S]*\"count\":[\\s]*6[\\s\\S]*Order, cart, billing[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include product category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-7"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-7"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"product\"[\\s\\S]*\"count\":[\\s]*5[\\s\\S]*Product-related[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include pricing category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-8"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-8"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"pricing\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Pricing and discount[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include store category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-9"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-9"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"store\"[\\s\\S]*\"count\":[\\s]*2[\\s\\S]*Store and location[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include other category with proper information"
    request:
      jsonrpc: "2.0"
      id: "test-10"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-10"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*\"category\":[\\s]*\"other\"[\\s\\S]*\"count\":[\\s]*3[\\s\\S]*Other models and utilities[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Count validation tests
  - it: "should return exactly 7 categories"
    request:
      jsonrpc: "2.0"
      id: "test-11"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-11"
        result:
          content:
            - type: "text"
              text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have core with count 5"
    request:
      jsonrpc: "2.0"
      id: "test-12"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-12"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 5"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have customer with count 2"
    request:
      jsonrpc: "2.0"
      id: "test-13"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-13"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 2"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have order with count 6"
    request:
      jsonrpc: "2.0"
      id: "test-14"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-14"
        result:
          content:
            - type: "text"
              text: "match:contains:\"count\": 6"
          isError: false
      stderr: "toBeEmpty"

  # JSON structure validation tests
  - it: "should return valid JSON structure with proper formatting"
    request:
      jsonrpc: "2.0"
      id: "test-15"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-15"
        result:
          content:
            - type: "text"
              text: "match:regex:^\\[[\\s\\S]*\\{[\\s\\S]*\"category\"[\\s\\S]*\"count\"[\\s\\S]*\"description\"[\\s\\S]*\\}[\\s\\S]*\\]$"
          isError: false
      stderr: "toBeEmpty"

  # Error handling and edge case tests
  - it: "should handle empty parameters gracefully"
    request:
      jsonrpc: "2.0"
      id: "test-16"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-16"
        result:
          content:
            - type: "text"
              text: "match:contains:core"
          isError: false
      stderr: "toBeEmpty"

  - it: "should ignore invalid parameters and return normal result"
    request:
      jsonrpc: "2.0"
      id: "test-17"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments:
          invalid: "param"
          another: "value"
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-17"
        result:
          content:
            - type: "text"
              text: "match:contains:\"category\": \"core\""
          isError: false
      stderr: "toBeEmpty"

  # Performance tests
  - it: "should respond quickly for metadata operation"
    request:
      jsonrpc: "2.0"
      id: "test-18"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-18"
        result:
          content:
            - type: "text"
              text: "match:type:string"
          isError: false
      performance:
        maxResponseTime: "500ms"
      stderr: "toBeEmpty"

  # Content validation patterns
  - it: "should have consistent category naming patterns"
    request:
      jsonrpc: "2.0"
      id: "test-19"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-19"
        result:
          content:
            - type: "text"
              text: "match:regex:\"category\":[\\s]*\"[a-z]+\""
          isError: false
      stderr: "toBeEmpty"

  - it: "should have numeric count values"
    request:
      jsonrpc: "2.0"
      id: "test-20"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-20"
        result:
          content:
            - type: "text"
              text: "match:regex:\"count\":[\\s]*[0-9]+"
          isError: false
      stderr: "toBeEmpty"

  - it: "should have descriptive text for all categories"
    request:
      jsonrpc: "2.0"
      id: "test-21"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-21"
        result:
          content:
            - type: "text"
              text: "match:regex:\"description\":[\\s]*\"[A-Z][\\s\\S]*\""
          isError: false
      stderr: "toBeEmpty"

  # Category completeness test - adjusted order to match actual response order
  - it: "should include all expected SFRA category names in order"
    request:
      jsonrpc: "2.0"
      id: "test-22"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-22"
        result:
          content:
            - type: "text"
              text: "match:regex:[\\s\\S]*core[\\s\\S]*customer[\\s\\S]*order[\\s\\S]*other[\\s\\S]*pricing[\\s\\S]*product[\\s\\S]*store[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Robustness tests
  - it: "should return consistent results across multiple calls"
    request:
      jsonrpc: "2.0"
      id: "test-23"
      method: "tools/call"
      params:
        name: "get_sfra_categories"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "test-23"
        result:
          content:
            - type: "text"
              text: "match:regex:(?:\"category\"[\\s\\S]*?){7}"
          isError: false
      stderr: "toBeEmpty"

```

--------------------------------------------------------------------------------
/tests/mcp/yaml/tools.full-mode.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
# ==================================================================================
# SFCC MCP Server - Full Mode Comprehensive Validation Tests
# Validates full server mode with WebDAV/OCAPI credentials for complete functionality
# Focuses on tool metadata validation (NOT tool execution due to test credentials)
# 
# Quick Test Commands:
# aegis "tests/mcp/yaml/full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --verbose
# aegis "tests/mcp/yaml/full-mode.test.mcp.yml" --config "aegis.config.with-dw.json" --debug --timing
# aegis query --config "aegis.config.with-dw.json"  # List all tools
# aegis query get_latest_error '{"limit": 5}' --config "aegis.config.with-dw.json"
# ==================================================================================
description: "SFCC MCP Server - Full Mode comprehensive metadata validation and tool presence verification"

# ==================================================================================
# TOOL COUNT & AVAILABILITY VALIDATION
# ==================================================================================
tests:
  - it: "should have exactly 36 tools available in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-tools-count"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-count"
        result:
          tools: "match:arrayLength:36"
      stderr: "toBeEmpty"

  - it: "should have significantly more tools than docs-only mode"
    request:
      jsonrpc: "2.0"
      id: "full-vs-docs-count"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-vs-docs-count"
        result:
          tools: "match:arrayLength:36"  # More than docs-only 15 tools
      stderr: "toBeEmpty"

  - it: "should have proper tool structure with required fields"
    request:
      jsonrpc: "2.0"
      id: "full-tool-structure"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tool-structure"
        result:
          tools:
            match:arrayElements:
              match:partial:
                name: "match:type:string"
                description: "match:type:string"
                inputSchema: "match:type:object"
      stderr: "toBeEmpty"

  # ==================================================================================
  # TOOL METADATA QUALITY VALIDATION
  # ==================================================================================

  - it: "should have tool names following snake_case convention"
    request:
      jsonrpc: "2.0"
      id: "full-naming-convention"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-naming-convention"
        result:
          tools:
            match:arrayElements:
              match:partial:
                name: "match:regex:^[a-z][a-z0-9_]*$"
      stderr: "toBeEmpty"

  - it: "should have meaningful tool descriptions"
    request:
      jsonrpc: "2.0"
      id: "full-description-quality"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-description-quality"
        result:
          tools:
            match:arrayElements:
              match:partial:
                description: "match:regex:.{20,}"  # At least 20 characters
      stderr: "toBeEmpty"

  - it: "should have non-empty tool descriptions"
    request:
      jsonrpc: "2.0"
      id: "full-description-nonempty"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-description-nonempty"
        result:
          tools:
            match:arrayElements:
              match:partial:
                description: "match:not:regex:^\\s*$"  # Not empty or whitespace-only
      stderr: "toBeEmpty"

  - it: "should have proper inputSchema structure"
    request:
      jsonrpc: "2.0"
      id: "full-schema-structure"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-schema-structure"
        result:
          tools:
            match:arrayElements:
              match:partial:
                inputSchema:
                  type: "object"
                  properties: "match:type:object"
      stderr: "toBeEmpty"

  # ==================================================================================
  # TOOL CATEGORY VALIDATION - Documentation Tools (from docs-only mode)
  # ==================================================================================

  - it: "should have documentation tools available"
    request:
      jsonrpc: "2.0"
      id: "full-tools-list-2"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-list-2"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:get_sfcc_class_info"
      stderr: "toBeEmpty"

  - it: "should include all docs-only tools in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-includes-docs"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-includes-docs"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:search_sfra_documentation"
      stderr: "toBeEmpty"

  - it: "should have cartridge generation tools available"
    request:
      jsonrpc: "2.0"
      id: "full-tools-cartridge"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-cartridge"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:generate_cartridge_structure"
      stderr: "toBeEmpty"

  # ==================================================================================
  # FULL-MODE SPECIFIC TOOLS - WebDAV Dependent (Log Analysis)
  # ==================================================================================

  - it: "should have log analysis tools available in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-tools-logs"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-logs"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:get_latest_error"
      stderr: "toBeEmpty"

  - it: "should have WebDAV-dependent tools (log analysis)"
    request:
      jsonrpc: "2.0"
      id: "webdav-tools"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "webdav-tools"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:summarize_logs"
      stderr: "toBeEmpty"

  - it: "should have job log tools available in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-tools-jobs"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-jobs"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:get_job_log_entries"
      stderr: "toBeEmpty"

  - it: "should have log search and analysis capabilities"
    request:
      jsonrpc: "2.0"
      id: "log-search-tools"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "log-search-tools"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:search_logs"
      stderr: "toBeEmpty"

  # ==================================================================================
  # FULL-MODE SPECIFIC TOOLS - OCAPI Dependent (System Objects & Site Preferences)
  # ==================================================================================

  - it: "should have system object tools available in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-tools-system"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-system"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:get_system_object_definitions"
      stderr: "toBeEmpty"

  - it: "should have OCAPI-dependent tools (system objects)"
    request:
      jsonrpc: "2.0"
      id: "ocapi-tools"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "ocapi-tools"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:search_site_preferences"
      stderr: "toBeEmpty"

  - it: "should have site preferences search capabilities"
    request:
      jsonrpc: "2.0"
      id: "site-prefs-tools"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "site-prefs-tools"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:search_system_object_attribute_groups"
      stderr: "toBeEmpty"

  # ==================================================================================
  # FULL-MODE SPECIFIC TOOLS - Code Version Management
  # ==================================================================================

  - it: "should have code version tools available in full mode"
    request:
      jsonrpc: "2.0"
      id: "full-tools-code"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-tools-code"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:get_code_versions"
      stderr: "toBeEmpty"

  - it: "should have code version activation capabilities"
    request:
      jsonrpc: "2.0"
      id: "code-activation-tools"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "code-activation-tools"
        result:
          match:extractField: "tools.*.name"
          value: "match:arrayContains:activate_code_version"
      stderr: "toBeEmpty"

  # ==================================================================================
  # COMPREHENSIVE TOOL VALIDATION - Combined Patterns
  # ==================================================================================

  - it: "should validate all tools have consistent metadata structure"
    request:
      jsonrpc: "2.0"
      id: "full-comprehensive-validation"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "full-comprehensive-validation"
        result:
          tools:
            match:arrayElements:
              match:partial:
                name: "match:regex:^[a-z][a-z0-9_]*$"  # snake_case names
                description: "match:regex:.{10,}"       # min 10 chars
                inputSchema:
                  type: "object"
                  properties: "match:type:object"
      stderr: "toBeEmpty"

  - it: "should have tools categorized by functionality"
    request:
      jsonrpc: "2.0"
      id: "tool-categorization"
      method: "tools/list"
      params: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "tool-categorization"
        result:
          tools: "match:not:arrayLength:0"  # Not empty
      stderr: "toBeEmpty"

  # NOTE: We intentionally do NOT test tool execution in full mode because:
  # 1. The test credentials are not real SFCC instances
  # 2. Tools would fail with authentication/connection errors
  # 3. This test suite is designed to verify tool PRESENCE, not functionality
  # 4. Tool functionality testing should be done against real SFCC development instances

```

--------------------------------------------------------------------------------
/tests/mcp/yaml/get_latest_error.test.mcp.yml:
--------------------------------------------------------------------------------

```yaml
---
description: "Test get_latest_error tool in full mode"
tests:
  # Basic functionality tests
  - it: "should retrieve latest error messages with default parameters"
    request:
      jsonrpc: "2.0"
      id: "error-default"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-default"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "2000ms"

  - it: "should limit error messages when limit parameter is provided"
    request:
      jsonrpc: "2.0"
      id: "error-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 3
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-limit"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:Latest 3 error messages"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "2000ms"

  - it: "should retrieve the latest 2 error messages"
    request:
      jsonrpc: "2.0"
      id: "error-date"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 2
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-date"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:Latest 2 error messages"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "2000ms"

  # Content validation tests
  - it: "should include log file name in response"
    request:
      jsonrpc: "2.0"
      id: "error-filename"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-filename"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:regex:error-blade-[\\d]{8}-[\\d]{6}\\.log"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include ERROR level log entries"
    request:
      jsonrpc: "2.0"
      id: "error-level"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 5
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-level"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:ERROR"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include timestamps in GMT format"
    request:
      jsonrpc: "2.0"
      id: "error-timestamp"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 2
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-timestamp"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:regex:[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:[\\d]{2}:[\\d]{2}\\.[\\d]{3} GMT"
          isError: false
      stderr: "toBeEmpty"

  - it: "should separate multiple error entries with separators"
    request:
      jsonrpc: "2.0"
      id: "error-separators"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 3
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-separators"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:---"
          isError: false
      stderr: "toBeEmpty"

  # Common SFCC error patterns validation
  - it: "should contain realistic SFCC error scenarios"
    request:
      jsonrpc: "2.0"
      id: "error-patterns"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 5
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-patterns"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:regex:(?:PipelineCallServlet|SystemJobThread)"
          isError: false
      stderr: "toBeEmpty"

  - it: "should include SFCC Sites and thread information"
    request:
      jsonrpc: "2.0"
      id: "error-sites"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 3
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-sites"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:Sites-"
          isError: false
      stderr: "toBeEmpty"

  # Parameter handling tests
  - it: "should handle string limit parameter gracefully"
    request:
      jsonrpc: "2.0"
      id: "error-string-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: "5"
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-string-limit"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:Latest 5 error messages"
          isError: false
      stderr: "toBeEmpty"

  - it: "should handle large limit values"
    request:
      jsonrpc: "2.0"
      id: "error-large-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 50
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-large-limit"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:contains:Latest 50 error messages"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "3000ms"

  - it: "should handle zero limit parameter with error response"
    request:
      jsonrpc: "2.0"
      id: "error-zero-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 0
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-zero-limit"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:Invalid limit '0' for get_latest_error"
          isError: true
      stderr: "toBeEmpty"

  # Date format validation
  - it: "should handle valid YYYYMMDD date format"
    request:
      jsonrpc: "2.0"
      id: "error-valid-date"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          date: "20240101"
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-valid-date"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
          isError: false
      stderr: "toBeEmpty"

  - it: "should handle future dates gracefully"
    request:
      jsonrpc: "2.0"
      id: "error-future-date"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          date: "20251231"
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-future-date"
        result:
          content:
            match:arrayElements:
              type: "text"
              text: "match:type:string"
          isError: false
      stderr: "toBeEmpty"

  # Performance tests
  - it: "should respond quickly for small limits"
    request:
      jsonrpc: "2.0"
      id: "error-perf-small"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-perf-small"
        result:
          content: "match:type:array"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1000ms"

  - it: "should maintain performance with default parameters"
    request:
      jsonrpc: "2.0"
      id: "error-perf-default"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments: {}
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-perf-default"
        result:
          content: "match:type:array"
          isError: false
      stderr: "toBeEmpty"
    performance:
      maxResponseTime: "1500ms"

  # Edge cases and robustness

  - it: "should return consistent structure regardless of parameters"
    request:
      jsonrpc: "2.0"
      id: "error-structure"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-structure"
        result:
          match:partial:
            content: "match:type:array"
            isError: "match:type:boolean"
      stderr: "toBeEmpty"

  # Comprehensive content validation
  - it: "should include comprehensive error information with multiple patterns"
    request:
      jsonrpc: "2.0"
      id: "error-comprehensive"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 3
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-comprehensive"
        result:
          content:
            match:arrayElements:
              match:partial:
                type: "text"
                text: "match:regex:[\\s\\S]*(?:Custom cartridge error|Product import failed|Customer profile creation failed|Payment authorization failed|AWS S3 Configuration Issue)[\\s\\S]*"
          isError: false
      stderr: "toBeEmpty"

  # Additional edge cases
  - it: "should handle negative limit parameter with error response"
    request:
      jsonrpc: "2.0"
      id: "error-negative-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: -5
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-negative-limit"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:Invalid limit"
          isError: true
      stderr: "toBeEmpty"

  - it: "should handle extremely large limit parameter gracefully"
    request:
      jsonrpc: "2.0"
      id: "error-huge-limit"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          limit: 9999
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-huge-limit"
        result:
          content:
            match:arrayElements:
              match:partial:
                text: "match:contains:Invalid limit"
          isError: true
      stderr: "toBeEmpty"

  - it: "should handle invalid date format gracefully"
    request:
      jsonrpc: "2.0"
      id: "error-invalid-date"
      method: "tools/call"
      params:
        name: "get_latest_error"
        arguments:
          date: "2024-01-01"
          limit: 1
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-invalid-date"
        result:
          content: "match:type:array"
          isError: "match:type:boolean"
      stderr: "toBeEmpty"

  - it: "should handle missing arguments object gracefully"
    request:
      jsonrpc: "2.0"
      id: "error-no-args"
      method: "tools/call"
      params:
        name: "get_latest_error"
    expect:
      response:
        jsonrpc: "2.0"
        id: "error-no-args"
        result:
          content: "match:type:array"
          isError: false
      stderr: "toBeEmpty"

```

--------------------------------------------------------------------------------
/src/clients/best-practices-client.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * SFCC Best Practices Client
 *
 * Provides access to SFCC development best practices documentation including
 * cartridge creation, ISML templates, job framework, LocalServiceRegistry,
 * OCAPI hooks, SCAPI hooks, SCAPI custom endpoints, SFRA controllers, and SFRA models.
 */

import * as fs from 'fs/promises';
import * as path from 'path';
import { PathResolver } from '../utils/path-resolver.js';
import { CacheManager } from '../utils/cache.js';
import { Logger } from '../utils/logger.js';

export interface BestPracticeGuide {
  title: string;
  description: string;
  sections: string[];
  content: string;
}

/**
 * Client for accessing SFCC best practices documentation
 */
export class SFCCBestPracticesClient {
  private cache: CacheManager;
  private docsPath: string;
  private logger: Logger;

  constructor() {
    this.cache = new CacheManager();
    this.docsPath = PathResolver.getBestPracticesPath();
    this.logger = Logger.getChildLogger('BestPracticesClient');
  }

  /**
   * Get all available best practice guides
   */
  async getAvailableGuides(): Promise<Array<{name: string; title: string; description: string}>> {
    const cacheKey = 'best-practices:available-guides';
    const cached = this.cache.getSearchResults(cacheKey);
    if (cached) {return cached;}

    const guides = [
      {
        name: 'cartridge_creation',
        title: 'Cartridge Creation Best Practices',
        description: 'Instructions and best practices for creating, configuring, and deploying custom SFRA cartridges',
      },
      {
        name: 'isml_templates',
        title: 'ISML Templates Best Practices',
        description: 'Comprehensive best practices for developing ISML templates within the SFRA framework, including security, performance, and maintainability guidelines',
      },
      {
        name: 'job_framework',
        title: 'Job Framework Best Practices',
        description: 'Comprehensive guide for developing custom jobs in the SFCC Job Framework, covering both task-oriented and chunk-oriented approaches with performance optimization and debugging strategies',
      },
      {
        name: 'localserviceregistry',
        title: 'LocalServiceRegistry Best Practices',
        description: 'Comprehensive guide for creating server-to-server integrations in SFCC using dw.svc.LocalServiceRegistry, including configuration patterns, callback implementation, OAuth flows, and reusable service module patterns',
      },
      {
        name: 'ocapi_hooks',
        title: 'OCAPI Hooks Best Practices',
        description: 'Best practices for implementing OCAPI hooks in Salesforce B2C Commerce Cloud',
      },
      {
        name: 'scapi_hooks',
        title: 'SCAPI Hooks Best Practices',
        description: 'Essential best practices for implementing SCAPI hooks with AI development assistance',
      },
      {
        name: 'scapi_custom_endpoint',
        title: 'Custom SCAPI Endpoint Best Practices',
        description: 'Best practices for creating custom SCAPI endpoints in B2C Commerce Cloud',
      },
      {
        name: 'sfra_controllers',
        title: 'SFRA Controllers Best Practices',
        description: 'Best practices and code patterns for developing SFRA controllers',
      },
      {
        name: 'sfra_models',
        title: 'SFRA Models Best Practices',
        description: 'Best practices for developing SFRA models in Salesforce B2C Commerce Cloud',
      },
      {
        name: 'sfra_client_side_js',
        title: 'SFRA Client-Side JavaScript Best Practices',
        description: 'Comprehensive patterns for architecting, extending, validating, and optimizing client-side JavaScript in SFRA storefronts using jQuery',
      },
      {
        name: 'sfra_scss',
        title: 'SFRA SCSS Best Practices',
        description: 'Implementation-focused SCSS override strategies for SFRA storefronts covering cartridge overlays, theming tokens, responsive mixins, and plugin extension guardrails',
      },
      {
        name: 'performance',
        title: 'Performance and Stability Best Practices',
        description: 'Comprehensive performance optimization strategies, coding standards, and stability guidelines for SFCC development including caching, index-friendly APIs, and job development',
      },
      {
        name: 'security',
        title: 'Security Best Practices',
        description: 'Comprehensive security best practices for SFCC development covering SFRA Controllers, OCAPI/SCAPI Hooks, and Custom SCAPI Endpoints with OWASP compliance guidelines',
      },
    ];

    this.cache.setSearchResults(cacheKey, guides);
    return guides;
  }

  /**
   * Get a specific best practice guide
   */
  async getBestPracticeGuide(guideName: string): Promise<BestPracticeGuide | null> {
    const cacheKey = `best-practices:guide:${guideName}`;
    const cached = this.cache.getFileContent(cacheKey);
    if (cached) {return JSON.parse(cached);}

    try {
      // Enhanced security validation - validate guideName before path construction
      if (!guideName || typeof guideName !== 'string') {
        throw new Error('Invalid guide name: must be a non-empty string');
      }

      // Prevent null bytes and dangerous characters in the guide name itself
      if (guideName.includes('\0') || guideName.includes('\x00')) {
        throw new Error('Invalid guide name: contains null bytes');
      }

      // Prevent path traversal sequences in the guide name
      if (guideName.includes('..') || guideName.includes('/') || guideName.includes('\\')) {
        throw new Error('Invalid guide name: contains path traversal sequences');
      }

      // Only allow alphanumeric characters, underscores, and hyphens
      if (!/^[a-zA-Z0-9_-]+$/.test(guideName)) {
        throw new Error('Invalid guide name: contains invalid characters');
      }

      const filePath = path.join(this.docsPath, `${guideName}.md`);

      // Additional security validation - ensure the resolved path is within the docs directory
      const resolvedPath = path.resolve(filePath);
      const resolvedDocsPath = path.resolve(this.docsPath);

      if (!resolvedPath.startsWith(resolvedDocsPath)) {
        throw new Error('Invalid guide name: path outside allowed directory');
      }

      // Ensure the file still ends with .md after path resolution
      if (!resolvedPath.toLowerCase().endsWith('.md')) {
        throw new Error('Invalid guide name: must reference a markdown file');
      }

      const content = await fs.readFile(resolvedPath, 'utf-8');

      // Basic content validation
      if (!content.trim()) {
        throw new Error(`Empty best practice guide: ${guideName}`);
      }

      // Check for binary content
      if (content.includes('\0')) {
        throw new Error(`Invalid content in best practice guide: ${guideName}`);
      }

      const lines = content.split('\n');
      const title = lines.find(line => line.startsWith('#'))?.replace('#', '').trim() ?? guideName;

      // Extract sections (## headers)
      const sections = lines
        .filter(line => line.startsWith('##'))
        .map(line => line.replace('##', '').trim());

      // Extract description (first paragraph after title)
      const descriptionStart = lines.findIndex(line => line.startsWith('#')) + 1;
      const descriptionEnd = lines.findIndex((line, index) =>
        index > descriptionStart && (line.startsWith('#') || line.trim() === ''));
      const description = lines
        .slice(descriptionStart, descriptionEnd > -1 ? descriptionEnd : descriptionStart + 3)
        .join(' ')
        .trim();

      const guide: BestPracticeGuide = {
        title,
        description,
        sections,
        content,
      };

      this.cache.setFileContent(cacheKey, JSON.stringify(guide));
      return guide;
    } catch (error) {
      this.logger.error(`Error reading best practice guide ${guideName}:`, error);
      return null;
    }
  }

  /**
   * Search across all best practices for specific terms
   */
  async searchBestPractices(query: string): Promise<Array<{
    guide: string;
    title: string;
    matches: Array<{section: string; content: string}>;
  }>> {
    const cacheKey = `best-practices:search:${query.toLowerCase()}`;
    const cached = this.cache.getSearchResults(cacheKey);
    if (cached) {return cached;}

    const guides = await this.getAvailableGuides();
    const results = [];

    for (const guide of guides) {
      const guideContent = await this.getBestPracticeGuide(guide.name);
      if (!guideContent) {continue;}

      const matches = [];
      const lines = guideContent.content.split('\n');
      let currentSection = '';

      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];

        if (line.startsWith('##')) {
          currentSection = line.replace('##', '').trim();
        }

        if (line.toLowerCase().includes(query.toLowerCase())) {
          // Get context around the match
          const start = Math.max(0, i - 2);
          const end = Math.min(lines.length, i + 3);
          const context = lines.slice(start, end).join('\n');

          matches.push({
            section: currentSection || 'Introduction',
            content: context,
          });
        }
      }

      if (matches.length > 0) {
        results.push({
          guide: guide.name,
          title: guide.title,
          matches,
        });
      }
    }

    this.cache.setSearchResults(cacheKey, results);
    return results;
  }

  /**
   * Get hook reference tables for OCAPI/SCAPI hooks
   */
  async getHookReference(guideName: string): Promise<Array<{
    category: string;
    hooks: Array<{endpoint: string; hookPoints: string[]; signature?: string}>;
  }>> {
    if (!guideName.includes('hooks')) {return [];}

    const cacheKey = `best-practices:hook-reference:${guideName}`;
    const cached = this.cache.getSearchResults(cacheKey);
    if (cached) {return cached;}

    const guide = await this.getBestPracticeGuide(guideName);
    if (!guide) {return [];}

    const reference = [];
    const lines = guide.content.split('\n');
    let currentCategory = '';
    let inTable = false;
    let hooks: Array<{endpoint: string; hookPoints: string[]; signature?: string}> = [];

    for (const line of lines) {
      // Look for hook reference sections
      if (line.match(/^###?\s+(Shop API Hooks|Data API Hooks|Shopper.*Hooks|.*API Hooks)/i)) {
        if (currentCategory && hooks.length > 0) {
          reference.push({ category: currentCategory, hooks: [...hooks] });
        }
        currentCategory = line.replace(/^#+\s*/, '');
        hooks = [];
        inTable = false;
      }

      // Detect table headers
      if (line.includes('API Endpoint') && line.includes('Hook')) {
        inTable = true;
        continue;
      }

      // Skip separator line
      if (line.match(/^\|[\s\-|]+\|$/)) {
        continue;
      }

      // Parse table rows
      if (inTable && line.startsWith('|') && !line.includes('**')) {
        const parts = line.split('|').map(p => p.trim()).filter(p => p);
        if (parts.length >= 2) {
          const endpoint = parts[0].replace(/`/g, '');
          const hookPoints = parts[1].split(',').map(h => h.replace(/`/g, '').trim());
          const signature = parts[2] ? parts[2].replace(/`/g, '') : undefined;

          if (endpoint && hookPoints.length > 0) {
            hooks.push({ endpoint, hookPoints, signature });
          }
        }
      }

      // End table when we hit a new section
      if (inTable && line.startsWith('#')) {
        inTable = false;
      }
    }

    // Add last category
    if (currentCategory && hooks.length > 0) {
      reference.push({ category: currentCategory, hooks });
    }

    this.cache.setSearchResults(cacheKey, reference);
    return reference;
  }
}

```

--------------------------------------------------------------------------------
/tests/mcp/node/search-job-logs-by-name.full-mode.programmatic.test.js:
--------------------------------------------------------------------------------

```javascript
import { test, describe, before, after, beforeEach } from 'node:test';
import { strict as assert } from 'node:assert';
import { connect } from 'mcp-aegis';

describe('search_job_logs_by_name - Optimized Programmatic Tests', () => {
  let client;
  let discoveredJobNames = [];

  before(async () => {
    client = await connect('./aegis.config.with-dw.json');
    
    // Discover available job names for dynamic testing
    await discoverJobNames();
  });

  after(async () => {
    if (client?.connected) {
      await client.disconnect();
    }
  });

  beforeEach(() => {
    // CRITICAL: Clear all buffers to prevent leaking into next tests
    client.clearAllBuffers();
  });

  // Simplified helper functions focused on essential validation
  function assertValidMCPResponse(result) {
    assert.ok(result.content, 'Should have content');
    assert.ok(Array.isArray(result.content), 'Content should be array');
    assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
  }

  function parseResponseText(text) {
    return text.startsWith('"') && text.endsWith('"') ? JSON.parse(text) : text;
  }

  function assertSuccessResponse(result) {
    assertValidMCPResponse(result);
    assert.equal(result.isError, false, 'Should not be an error response');
    assert.equal(result.content[0].type, 'text');
  }

  function assertErrorResponse(result, expectedErrorText) {
    assertValidMCPResponse(result);
    assert.equal(result.isError, true, 'Should be an error response');
    if (expectedErrorText) {
      const text = parseResponseText(result.content[0].text);
      assert.ok(text.includes(expectedErrorText), 
        `Expected "${expectedErrorText}" in "${text}"`);
    }
  }

  function assertJobSearchResults(result) {
    assertSuccessResponse(result);
    const text = parseResponseText(result.content[0].text);
    
    if (text.includes('No job logs found')) {
      return 0; // Valid empty result
    }
    
    // Extract count and validate basic structure
    const countMatch = text.match(/Found (\d+) job logs:/);
    assert.ok(countMatch, 'Should contain job count message');
    
    const actualCount = parseInt(countMatch[1]);
    assert.ok(actualCount > 0, 'Should have positive count when jobs found');
    
    // Validate key components are present
    assert.ok(text.includes('🔧 Job:'), 'Should contain job emoji');
    assert.ok(text.includes('ID:'), 'Should contain job ID');
    assert.ok(text.includes('File:'), 'Should contain file name');
    assert.ok(text.includes('Modified:'), 'Should contain modification time');
    assert.ok(text.includes('Size:'), 'Should contain file size');
    
    return actualCount;
  }

  async function discoverJobNames() {
    try {
      const result = await client.callTool('search_job_logs_by_name', { 
        jobName: 'Import', // Use common search term to find jobs
        limit: 5
      });
      
      if (!result.isError && result.content?.[0]?.text) {
        const text = parseResponseText(result.content[0].text);
        if (!text.includes('No job logs found')) {
          // Extract job names from the response
          const jobMatches = text.match(/🔧 Job: ([A-Za-z0-9]+)/g) || [];
          discoveredJobNames = [...new Set(jobMatches.map(match => 
            match.replace('🔧 Job: ', '')))];
        }
      }
    } catch (error) {
      console.warn('Could not discover job names:', error.message);
    }
  }

  // Core functionality tests
  describe('Core Functionality', () => {
    test('should search for job logs and return structured results', async () => {
      const result = await client.callTool('search_job_logs_by_name', { 
        jobName: 'Import' 
      });
      
      const count = assertJobSearchResults(result);
      
      if (count > 0) {
        const text = parseResponseText(result.content[0].text);
        // Verify partial matching works (job names containing "Import")
        const jobMatches = text.match(/🔧 Job: ([A-Za-z0-9]+)/g) || [];
        const containsImport = jobMatches.some(match => 
          match.toLowerCase().includes('import'));
        assert.ok(containsImport, 'Should find jobs containing "Import"');
      }
    });

    test('should respect limit parameter and handle various values', async () => {
      const testCases = [
        { limit: 1, desc: 'single result' },
        { limit: 3, desc: 'multiple results' },
        { limit: 1000, desc: 'large limit' }
      ];
      
      for (const { limit, desc } of testCases) {
        const result = await client.callTool('search_job_logs_by_name', { 
          jobName: 'Import',
          limit 
        });
        
        assertSuccessResponse(result);
        const text = parseResponseText(result.content[0].text);
        
        if (!text.includes('No job logs found')) {
          const countMatch = text.match(/Found (\d+) job logs:/);
          if (countMatch) {
            const actualCount = parseInt(countMatch[1]);
            assert.ok(actualCount <= limit, 
              `${desc}: actual count ${actualCount} should not exceed limit ${limit}`);
          }
        }
      }
    });

    test('should return no results for non-existent job name', async () => {
      const result = await client.callTool('search_job_logs_by_name', { 
        jobName: 'NonExistentJobXYZ123'
      });
      
      assertSuccessResponse(result);
      const text = parseResponseText(result.content[0].text);
      assert.ok(text.includes('No job logs found'), 'Should indicate no results found');
    });
  });

  // Parameter validation tests
  describe('Parameter Validation', () => {
    test('should reject empty job name', async () => {
      const result = await client.callTool('search_job_logs_by_name', { 
        jobName: '' 
      });
      
      assertErrorResponse(result, 'jobName must be a non-empty string');
    });

    test('should reject missing jobName parameter', async () => {
      const result = await client.callTool('search_job_logs_by_name', {});
      
      assertErrorResponse(result, 'jobName must be a non-empty string');
    });

    test('should handle invalid limit parameter', async () => {
      const invalidLimits = [-1, 0, 'invalid'];
      
      for (const limit of invalidLimits) {
        const result = await client.callTool('search_job_logs_by_name', { 
          jobName: 'Import',
          limit 
        });
        
        assertErrorResponse(result); // Should be an error, don't need to check exact message
      }
    });
  });

  // Dynamic discovery tests
  describe('Dynamic Discovery Tests', () => {
    test('should handle discovered job names effectively', async () => {
      if (discoveredJobNames.length === 0) {
        console.log('⚠️ Skipping dynamic discovery test - no job names found');
        return;
      }

      // Test with discovered job names
      const testJobName = discoveredJobNames[0];
      const result = await client.callTool('search_job_logs_by_name', { 
        jobName: testJobName,
        limit: 2
      });
      
      const count = assertJobSearchResults(result);
      assert.ok(count >= 0, 'Should return valid count for discovered job name');
    });

    test('should support case-insensitive search with discovered names', async () => {
      if (discoveredJobNames.length === 0) {
        console.log('⚠️ Skipping case-insensitive test - no job names discovered');
        return;
      }
      
      const testJobName = discoveredJobNames[0];
      const variations = [
        testJobName.toLowerCase(),
        testJobName.substring(0, 3).toLowerCase()
      ];
      
      for (const variation of variations) {
        const result = await client.callTool('search_job_logs_by_name', { 
          jobName: variation
        });
        
        assertSuccessResponse(result);
        // Should find results or return no results message
        const text = parseResponseText(result.content[0].text);
        assert.ok(text.includes('Found') || text.includes('No job logs found'),
          `Should return valid response for case variation "${variation}"`);
      }
    });
  });

  // Multi-step workflow tests
  describe('Multi-Step Workflows', () => {
    test('should support workflow: discover -> search -> validate', async () => {
      // Step 1: Search with broad term to discover available jobs
      const discoveryResult = await client.callTool('search_job_logs_by_name', { 
        jobName: 'Job',
        limit: 5
      });
      
      assertSuccessResponse(discoveryResult);
      
      const discoveryText = parseResponseText(discoveryResult.content[0].text);
      if (!discoveryText.includes('No job logs found')) {
        // Step 2: Extract a specific job name for targeted search
        const jobMatch = discoveryText.match(/🔧 Job: ([A-Za-z0-9]+)/);
        
        if (jobMatch) {
          const specificJobName = jobMatch[1];
          
          // Step 3: Search for that specific job
          const specificResult = await client.callTool('search_job_logs_by_name', { 
            jobName: specificJobName,
            limit: 1
          });
          
          const specificCount = assertJobSearchResults(specificResult);
          assert.ok(specificCount >= 0, 'Specific search should return valid results');
          
          // Step 4: Validate the targeted search found the job
          if (specificCount > 0) {
            const specificText = parseResponseText(specificResult.content[0].text);
            assert.ok(specificText.includes(specificJobName),
              `Targeted search should include job name "${specificJobName}"`);
          }
        }
      }
    });

    test('should handle sequential searches consistently', async () => {
      const searchTerms = ['Import', 'Job', 'Process'];
      const results = [];
      
      // Perform sequential searches
      for (const term of searchTerms) {
        const result = await client.callTool('search_job_logs_by_name', { 
          jobName: term,
          limit: 2
        });
        
        assertSuccessResponse(result);
        results.push({ term, result });
      }
      
      // Validate all searches completed successfully
      assert.equal(results.length, searchTerms.length, 
        'All sequential searches should complete');
      
      // Each result should be valid
      for (const { term, result } of results) {
        const text = parseResponseText(result.content[0].text);
        assert.ok(text.includes('Found') || text.includes('No job logs found'),
          `Search for "${term}" should return valid response format`);
      }
    });
  });

  // Edge cases and error handling
  describe('Edge Cases', () => {
    test('should handle various edge case inputs', async () => {
      const edgeCases = [
        { jobName: 'a', desc: 'single character' },
        { jobName: 'Job-With-Hyphens', desc: 'special characters' },
        { jobName: '   Import   ', desc: 'whitespace padding' }
      ];
      
      for (const { jobName, desc } of edgeCases) {
        const result = await client.callTool('search_job_logs_by_name', { 
          jobName,
          limit: 1
        });
        
        // Should not throw errors, should return valid response
        assertValidMCPResponse(result);
        
        if (result.isError) {
          // If it's an error, should be a validation error
          const text = parseResponseText(result.content[0].text);
          assert.ok(text.includes('Error'), 
            `${desc}: Error response should contain "Error"`);
        } else {
          // If successful, should have valid format
          const text = parseResponseText(result.content[0].text);
          assert.ok(text.includes('Found') || text.includes('No job logs found'),
            `${desc}: Should return valid search response`);
        }
      }
    });
  });
});
```
Page 15/43FirstPrevNextLast