#
tokens: 47314/50000 20/473 files (page 5/7)
lines: off (toggle) GitHub
raw markdown copy
This is page 5 of 7. Use http://codebase.md/push-based/angular-toolkit-mcp?page={x} to view the full context.

# Directory Structure

```
├── .aiignore
├── .cursor
│   ├── flows
│   │   ├── component-refactoring
│   │   │   ├── 01-review-component.mdc
│   │   │   ├── 02-refactor-component.mdc
│   │   │   ├── 03-validate-component.mdc
│   │   │   └── angular-20.md
│   │   ├── ds-refactoring-flow
│   │   │   ├── 01-find-violations.mdc
│   │   │   ├── 01b-find-all-violations.mdc
│   │   │   ├── 02-plan-refactoring.mdc
│   │   │   ├── 02b-plan-refactoring-for-all-violations.mdc
│   │   │   ├── 03-fix-violations.mdc
│   │   │   ├── 03-non-viable-cases.mdc
│   │   │   ├── 04-validate-changes.mdc
│   │   │   ├── 05-prepare-report.mdc
│   │   │   └── clean-global-styles.mdc
│   │   └── README.md
│   └── mcp.json.example
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── assets
│   ├── entain-logo.png
│   └── entain.png
├── CONTRIBUTING.MD
├── docs
│   ├── architecture-internal-design.md
│   ├── component-refactoring-flow.md
│   ├── contracts.md
│   ├── ds-refactoring-flow.md
│   ├── getting-started.md
│   ├── README.md
│   ├── tools.md
│   └── writing-custom-tools.md
├── eslint.config.mjs
├── jest.config.ts
├── jest.preset.mjs
├── LICENSE
├── nx.json
├── package-lock.json
├── package.json
├── packages
│   ├── .gitkeep
│   ├── angular-mcp
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── assets
│   │   │   │   └── .gitkeep
│   │   │   └── main.ts
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── vitest.config.mts
│   │   └── webpack.config.cjs
│   ├── angular-mcp-server
│   │   ├── eslint.config.mjs
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── index.ts
│   │   │   └── lib
│   │   │       ├── angular-mcp-server.ts
│   │   │       ├── prompts
│   │   │       │   └── prompt-registry.ts
│   │   │       ├── tools
│   │   │       │   ├── ds
│   │   │       │   │   ├── component
│   │   │       │   │   │   ├── get-deprecated-css-classes.tool.ts
│   │   │       │   │   │   ├── get-ds-component-data.tool.ts
│   │   │       │   │   │   ├── list-ds-components.tool.ts
│   │   │       │   │   │   └── utils
│   │   │       │   │   │       ├── deprecated-css-helpers.ts
│   │   │       │   │   │       ├── doc-helpers.ts
│   │   │       │   │   │       ├── metadata-helpers.ts
│   │   │       │   │   │       └── paths-helpers.ts
│   │   │       │   │   ├── component-contract
│   │   │       │   │   │   ├── builder
│   │   │       │   │   │   │   ├── build-component-contract.tool.ts
│   │   │       │   │   │   │   ├── models
│   │   │       │   │   │   │   │   ├── schema.ts
│   │   │       │   │   │   │   │   └── types.ts
│   │   │       │   │   │   │   ├── spec
│   │   │       │   │   │   │   │   ├── css-match.spec.ts
│   │   │       │   │   │   │   │   ├── dom-slots.extractor.spec.ts
│   │   │       │   │   │   │   │   ├── element-helpers.spec.ts
│   │   │       │   │   │   │   │   ├── inline-styles.collector.spec.ts
│   │   │       │   │   │   │   │   ├── meta.generator.spec.ts
│   │   │       │   │   │   │   │   ├── public-api.extractor.spec.ts
│   │   │       │   │   │   │   │   ├── styles.collector.spec.ts
│   │   │       │   │   │   │   │   └── typescript-analyzer.spec.ts
│   │   │       │   │   │   │   └── utils
│   │   │       │   │   │   │       ├── build-contract.ts
│   │   │       │   │   │   │       ├── css-match.ts
│   │   │       │   │   │   │       ├── dom-slots.extractor.ts
│   │   │       │   │   │   │       ├── element-helpers.ts
│   │   │       │   │   │   │       ├── inline-styles.collector.ts
│   │   │       │   │   │   │       ├── meta.generator.ts
│   │   │       │   │   │   │       ├── public-api.extractor.ts
│   │   │       │   │   │   │       ├── styles.collector.ts
│   │   │       │   │   │   │       └── typescript-analyzer.ts
│   │   │       │   │   │   ├── diff
│   │   │       │   │   │   │   ├── diff-component-contract.tool.ts
│   │   │       │   │   │   │   ├── models
│   │   │       │   │   │   │   │   └── schema.ts
│   │   │       │   │   │   │   ├── spec
│   │   │       │   │   │   │   │   ├── diff-utils.spec.ts
│   │   │       │   │   │   │   │   └── dom-path-utils.spec.ts
│   │   │       │   │   │   │   └── utils
│   │   │       │   │   │   │       ├── diff-utils.ts
│   │   │       │   │   │   │       └── dom-path-utils.ts
│   │   │       │   │   │   ├── index.ts
│   │   │       │   │   │   ├── list
│   │   │       │   │   │   │   ├── list-component-contracts.tool.ts
│   │   │       │   │   │   │   ├── models
│   │   │       │   │   │   │   │   ├── schema.ts
│   │   │       │   │   │   │   │   └── types.ts
│   │   │       │   │   │   │   ├── spec
│   │   │       │   │   │   │   │   └── contract-list-utils.spec.ts
│   │   │       │   │   │   │   └── utils
│   │   │       │   │   │   │       └── contract-list-utils.ts
│   │   │       │   │   │   └── shared
│   │   │       │   │   │       ├── models
│   │   │       │   │   │       │   └── types.ts
│   │   │       │   │   │       ├── spec
│   │   │       │   │   │       │   └── contract-file-ops.spec.ts
│   │   │       │   │   │       └── utils
│   │   │       │   │   │           └── contract-file-ops.ts
│   │   │       │   │   ├── component-usage-graph
│   │   │       │   │   │   ├── build-component-usage-graph.tool.ts
│   │   │       │   │   │   ├── index.ts
│   │   │       │   │   │   ├── models
│   │   │       │   │   │   │   ├── config.ts
│   │   │       │   │   │   │   ├── schema.ts
│   │   │       │   │   │   │   └── types.ts
│   │   │       │   │   │   └── utils
│   │   │       │   │   │       ├── angular-parser.ts
│   │   │       │   │   │       ├── component-helpers.ts
│   │   │       │   │   │       ├── component-usage-graph-builder.ts
│   │   │       │   │   │       ├── path-resolver.ts
│   │   │       │   │   │       └── unified-ast-analyzer.ts
│   │   │       │   │   ├── ds.tools.ts
│   │   │       │   │   ├── project
│   │   │       │   │   │   ├── get-project-dependencies.tool.ts
│   │   │       │   │   │   ├── report-deprecated-css.tool.ts
│   │   │       │   │   │   └── utils
│   │   │       │   │   │       ├── dependencies-helpers.ts
│   │   │       │   │   │       └── styles-report-helpers.ts
│   │   │       │   │   ├── report-violations
│   │   │       │   │   │   ├── index.ts
│   │   │       │   │   │   ├── models
│   │   │       │   │   │   │   ├── schema.ts
│   │   │       │   │   │   │   └── types.ts
│   │   │       │   │   │   ├── report-all-violations.tool.ts
│   │   │       │   │   │   └── report-violations.tool.ts
│   │   │       │   │   ├── shared
│   │   │       │   │   │   ├── index.ts
│   │   │       │   │   │   ├── models
│   │   │       │   │   │   │   ├── input-schemas.model.ts
│   │   │       │   │   │   │   └── schema-helpers.ts
│   │   │       │   │   │   ├── utils
│   │   │       │   │   │   │   ├── component-validation.ts
│   │   │       │   │   │   │   ├── cross-platform-path.ts
│   │   │       │   │   │   │   ├── handler-helpers.ts
│   │   │       │   │   │   │   ├── output.utils.ts
│   │   │       │   │   │   │   └── regex-helpers.ts
│   │   │       │   │   │   └── violation-analysis
│   │   │       │   │   │       ├── base-analyzer.ts
│   │   │       │   │   │       ├── coverage-analyzer.ts
│   │   │       │   │   │       ├── formatters.ts
│   │   │       │   │   │       ├── index.ts
│   │   │       │   │   │       └── types.ts
│   │   │       │   │   └── tools.ts
│   │   │       │   ├── schema.ts
│   │   │       │   ├── tools.ts
│   │   │       │   ├── types.ts
│   │   │       │   └── utils.ts
│   │   │       └── validation
│   │   │           ├── angular-mcp-server-options.schema.ts
│   │   │           ├── ds-components-file-loader.validation.ts
│   │   │           ├── ds-components-file.validation.ts
│   │   │           ├── ds-components.schema.ts
│   │   │           └── file-existence.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.lib.json
│   │   ├── tsconfig.tsbuildinfo
│   │   └── vitest.config.mts
│   ├── minimal-repo
│   │   └── packages
│   │       ├── application
│   │       │   ├── angular.json
│   │       │   ├── code-pushup.config.ts
│   │       │   ├── src
│   │       │   │   ├── app
│   │       │   │   │   ├── app.component.ts
│   │       │   │   │   ├── app.config.ts
│   │       │   │   │   ├── app.routes.ts
│   │       │   │   │   ├── components
│   │       │   │   │   │   ├── refactoring-tests
│   │       │   │   │   │   │   ├── bad-alert-tooltip-input.component.ts
│   │       │   │   │   │   │   ├── bad-alert.component.ts
│   │       │   │   │   │   │   ├── bad-button-dropdown.component.ts
│   │       │   │   │   │   │   ├── bad-document.component.ts
│   │       │   │   │   │   │   ├── bad-global-this.component.ts
│   │       │   │   │   │   │   ├── bad-mixed-external-assets.component.css
│   │       │   │   │   │   │   ├── bad-mixed-external-assets.component.html
│   │       │   │   │   │   │   ├── bad-mixed-external-assets.component.ts
│   │       │   │   │   │   │   ├── bad-mixed-not-standalone.component.ts
│   │       │   │   │   │   │   ├── bad-mixed.component.ts
│   │       │   │   │   │   │   ├── bad-mixed.module.ts
│   │       │   │   │   │   │   ├── bad-modal-progress.component.ts
│   │       │   │   │   │   │   ├── bad-this-window-document.component.ts
│   │       │   │   │   │   │   ├── bad-window.component.ts
│   │       │   │   │   │   │   ├── complex-components
│   │       │   │   │   │   │   │   ├── first-case
│   │       │   │   │   │   │   │   │   ├── dashboard-demo.component.html
│   │       │   │   │   │   │   │   │   ├── dashboard-demo.component.scss
│   │       │   │   │   │   │   │   │   ├── dashboard-demo.component.ts
│   │       │   │   │   │   │   │   │   ├── dashboard-header.component.html
│   │       │   │   │   │   │   │   │   ├── dashboard-header.component.scss
│   │       │   │   │   │   │   │   │   └── dashboard-header.component.ts
│   │       │   │   │   │   │   │   ├── second-case
│   │       │   │   │   │   │   │   │   ├── complex-badge-widget.component.scss
│   │       │   │   │   │   │   │   │   ├── complex-badge-widget.component.ts
│   │       │   │   │   │   │   │   │   └── complex-widget-demo.component.ts
│   │       │   │   │   │   │   │   └── third-case
│   │       │   │   │   │   │   │       ├── product-card.component.scss
│   │       │   │   │   │   │   │       ├── product-card.component.ts
│   │       │   │   │   │   │   │       └── product-showcase.component.ts
│   │       │   │   │   │   │   ├── group-1
│   │       │   │   │   │   │   │   ├── bad-mixed-1.component.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-1.module.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-1.component.css
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-1.component.html
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-1.component.ts
│   │       │   │   │   │   │   │   └── bad-mixed-not-standalone-1.component.ts
│   │       │   │   │   │   │   ├── group-2
│   │       │   │   │   │   │   │   ├── bad-mixed-2.component.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-2.module.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-2.component.css
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-2.component.html
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-2.component.ts
│   │       │   │   │   │   │   │   └── bad-mixed-not-standalone-2.component.ts
│   │       │   │   │   │   │   ├── group-3
│   │       │   │   │   │   │   │   ├── bad-mixed-3.component.spec.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-3.component.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-3.module.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-3.component.css
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-3.component.html
│   │       │   │   │   │   │   │   ├── bad-mixed-external-assets-3.component.ts
│   │       │   │   │   │   │   │   ├── bad-mixed-not-standalone-3.component.ts
│   │       │   │   │   │   │   │   └── lazy-loader-3.component.ts
│   │       │   │   │   │   │   └── group-4
│   │       │   │   │   │   │       ├── multi-violation-test.component.html
│   │       │   │   │   │   │       ├── multi-violation-test.component.scss
│   │       │   │   │   │   │       └── multi-violation-test.component.ts
│   │       │   │   │   │   └── validation-tests
│   │       │   │   │   │       ├── circular-dependency.component.ts
│   │       │   │   │   │       ├── external-files-missing.component.ts
│   │       │   │   │   │       ├── invalid-lifecycle.component.ts
│   │       │   │   │   │       ├── invalid-pipe-usage.component.ts
│   │       │   │   │   │       ├── invalid-template-syntax.component.ts
│   │       │   │   │   │       ├── missing-imports.component.ts
│   │       │   │   │   │       ├── missing-method.component.ts
│   │       │   │   │   │       ├── README.md
│   │       │   │   │   │       ├── standalone-module-conflict.component.ts
│   │       │   │   │   │       ├── standalone-module-conflict.module.ts
│   │       │   │   │   │       ├── template-reference-error.component.ts
│   │       │   │   │   │       ├── type-mismatch.component.ts
│   │       │   │   │   │       ├── valid.component.ts
│   │       │   │   │   │       ├── wrong-decorator-usage.component.ts
│   │       │   │   │   │       └── wrong-property-binding.component.ts
│   │       │   │   │   └── styles
│   │       │   │   │       ├── bad-global-styles.scss
│   │       │   │   │       ├── base
│   │       │   │   │       │   ├── _reset.scss
│   │       │   │   │       │   └── base.scss
│   │       │   │   │       ├── components
│   │       │   │   │       │   └── components.scss
│   │       │   │   │       ├── extended-deprecated-styles.scss
│   │       │   │   │       ├── layout
│   │       │   │   │       │   └── layout.scss
│   │       │   │   │       ├── new-styles-1.scss
│   │       │   │   │       ├── new-styles-10.scss
│   │       │   │   │       ├── new-styles-2.scss
│   │       │   │   │       ├── new-styles-3.scss
│   │       │   │   │       ├── new-styles-4.scss
│   │       │   │   │       ├── new-styles-5.scss
│   │       │   │   │       ├── new-styles-6.scss
│   │       │   │   │       ├── new-styles-7.scss
│   │       │   │   │       ├── new-styles-8.scss
│   │       │   │   │       ├── new-styles-9.scss
│   │       │   │   │       ├── themes
│   │       │   │   │       │   └── themes.scss
│   │       │   │   │       └── utilities
│   │       │   │   │           └── utilities.scss
│   │       │   │   ├── index.html
│   │       │   │   ├── main.ts
│   │       │   │   └── styles.css
│   │       │   ├── tsconfig.app.json
│   │       │   ├── tsconfig.json
│   │       │   └── tsconfig.spec.json
│   │       └── design-system
│   │           ├── component-options.mjs
│   │           ├── storybook
│   │           │   └── card
│   │           │       └── card-tabs
│   │           │           └── overview.mdx
│   │           ├── storybook-host-app
│   │           │   └── src
│   │           │       └── components
│   │           │           ├── badge
│   │           │           │   ├── badge-tabs
│   │           │           │   │   ├── api.mdx
│   │           │           │   │   ├── examples.mdx
│   │           │           │   │   └── overview.mdx
│   │           │           │   ├── badge.component.mdx
│   │           │           │   └── badge.component.stories.ts
│   │           │           ├── modal
│   │           │           │   ├── demo-cdk-dialog-cmp.component.ts
│   │           │           │   ├── demo-modal-cmp.component.ts
│   │           │           │   ├── modal-tabs
│   │           │           │   │   ├── api.mdx
│   │           │           │   │   ├── examples.mdx
│   │           │           │   │   └── overview.mdx
│   │           │           │   ├── modal.component.mdx
│   │           │           │   └── modal.component.stories.ts
│   │           │           └── segmented-control
│   │           │               ├── segmented-control-tabs
│   │           │               │   ├── api.mdx
│   │           │               │   ├── examples.mdx
│   │           │               │   └── overview.mdx
│   │           │               ├── segmented-control.component.mdx
│   │           │               └── segmented-control.component.stories.ts
│   │           └── ui
│   │               ├── badge
│   │               │   ├── package.json
│   │               │   ├── project.json
│   │               │   └── src
│   │               │       └── badge.component.ts
│   │               ├── modal
│   │               │   ├── package.json
│   │               │   ├── project.json
│   │               │   └── src
│   │               │       ├── modal-content.component.ts
│   │               │       ├── modal-header
│   │               │       │   └── modal-header.component.ts
│   │               │       ├── modal-header-drag
│   │               │       │   └── modal-header-drag.component.ts
│   │               │       └── modal.component.ts
│   │               ├── rx-host-listener
│   │               │   ├── package.json
│   │               │   ├── project.json
│   │               │   └── src
│   │               │       └── rx-host-listener.ts
│   │               └── segmented-control
│   │                   ├── package.json
│   │                   ├── project.json
│   │                   └── src
│   │                       ├── segmented-control.component.html
│   │                       ├── segmented-control.component.ts
│   │                       ├── segmented-control.token.ts
│   │                       └── segmented-option.component.ts
│   └── shared
│       ├── angular-ast-utils
│       │   ├── .spec.swcrc
│       │   ├── ai
│       │   │   ├── API.md
│       │   │   ├── EXAMPLES.md
│       │   │   └── FUNCTIONS.md
│       │   ├── docs
│       │   │   └── angular-component-tree.md
│       │   ├── eslint.config.mjs
│       │   ├── jest.config.ts
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── index.ts
│       │   │   └── lib
│       │   │       ├── constants.ts
│       │   │       ├── decorator-config.visitor.inline-styles.spec.ts
│       │   │       ├── decorator-config.visitor.spec.ts
│       │   │       ├── decorator-config.visitor.ts
│       │   │       ├── parse-component.ts
│       │   │       ├── schema.ts
│       │   │       ├── styles
│       │   │       │   └── utils.ts
│       │   │       ├── template
│       │   │       │   ├── noop-tmpl-visitor.ts
│       │   │       │   ├── template.walk.ts
│       │   │       │   ├── utils.spec.ts
│       │   │       │   ├── utils.ts
│       │   │       │   └── utils.unit.test.ts
│       │   │       ├── ts.walk.ts
│       │   │       ├── types.ts
│       │   │       └── utils.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   ├── tsconfig.spec.json
│       │   └── vitest.config.mts
│       ├── DEPENDENCIES.md
│       ├── ds-component-coverage
│       │   ├── .spec.swcrc
│       │   ├── ai
│       │   │   ├── API.md
│       │   │   ├── EXAMPLES.md
│       │   │   └── FUNCTIONS.md
│       │   ├── docs
│       │   │   ├── examples
│       │   │   │   ├── report.json
│       │   │   │   └── report.md
│       │   │   ├── images
│       │   │   │   └── report-overview.png
│       │   │   └── README.md
│       │   ├── jest.config.ts
│       │   ├── mocks
│       │   │   └── fixtures
│       │   │       └── e2e
│       │   │           ├── asset-location
│       │   │           │   ├── code-pushup.config.ts
│       │   │           │   ├── inl-styl-inl-tmpl
│       │   │           │   │   └── inl-styl-inl-tmpl.component.ts
│       │   │           │   ├── inl-styl-url-tmpl
│       │   │           │   │   ├── inl-styl-url-tmpl.component.html
│       │   │           │   │   └── inl-styl-url-tmpl.component.ts
│       │   │           │   ├── multi-url-styl-inl-tmpl
│       │   │           │   │   ├── multi-url-styl-inl-tmpl-1.component.css
│       │   │           │   │   ├── multi-url-styl-inl-tmpl-2.component.css
│       │   │           │   │   └── multi-url-styl-inl-tmpl.component.ts
│       │   │           │   ├── url-styl-inl-tmpl
│       │   │           │   │   ├── url-styl-inl-tmpl.component.css
│       │   │           │   │   └── url-styl-inl-tmpl.component.ts
│       │   │           │   ├── url-styl-single-inl-tmpl
│       │   │           │   │   ├── url-styl-inl-tmpl.component.ts
│       │   │           │   │   └── url-styl-single-inl-tmpl.component.css
│       │   │           │   └── url-styl-url-tmpl
│       │   │           │       ├── inl-styl-url-tmpl.component.css
│       │   │           │       ├── inl-styl-url-tmpl.component.html
│       │   │           │       └── inl-styl-url-tmpl.component.ts
│       │   │           ├── demo
│       │   │           │   ├── code-pushup.config.ts
│       │   │           │   ├── prompt.md
│       │   │           │   └── src
│       │   │           │       ├── bad-button-dropdown.component.ts
│       │   │           │       ├── bad-modal-progress.component.ts
│       │   │           │       ├── mixed-external-assets.component.css
│       │   │           │       ├── mixed-external-assets.component.html
│       │   │           │       ├── mixed-external-assets.component.ts
│       │   │           │       └── sub-folder-1
│       │   │           │           ├── bad-alert.component.ts
│       │   │           │           ├── button.component.ts
│       │   │           │           └── sub-folder-2
│       │   │           │               ├── bad-alert-tooltip-input.component.ts
│       │   │           │               └── bad-mixed.component.ts
│       │   │           ├── line-number
│       │   │           │   ├── code-pushup.config.ts
│       │   │           │   ├── inl-styl-single.component.ts
│       │   │           │   ├── inl-styl-span.component.ts
│       │   │           │   ├── inl-tmpl-single.component.ts
│       │   │           │   ├── inl-tmpl-span.component.ts
│       │   │           │   ├── url-style
│       │   │           │   │   ├── url-styl-single.component.css
│       │   │           │   │   ├── url-styl-single.component.ts
│       │   │           │   │   ├── url-styl-span.component.css
│       │   │           │   │   └── url-styl-span.component.ts
│       │   │           │   └── url-tmpl
│       │   │           │       ├── url-tmpl-single.component.html
│       │   │           │       ├── url-tmpl-single.component.ts
│       │   │           │       ├── url-tmpl-span.component.html
│       │   │           │       └── url-tmpl-span.component.ts
│       │   │           ├── style-format
│       │   │           │   ├── code-pushup.config.ts
│       │   │           │   ├── inl-css.component.ts
│       │   │           │   ├── inl-scss.component.ts
│       │   │           │   ├── styles.css
│       │   │           │   ├── styles.scss
│       │   │           │   ├── url-css.component.ts
│       │   │           │   └── url-scss.component.ts
│       │   │           └── template-syntax
│       │   │               ├── class-attribute.component.ts
│       │   │               ├── class-binding.component.ts
│       │   │               ├── code-pushup.config.ts
│       │   │               └── ng-class-binding.component.ts
│       │   ├── package.json
│       │   ├── src
│       │   │   ├── core.config.ts
│       │   │   ├── index.ts
│       │   │   └── lib
│       │   │       ├── constants.ts
│       │   │       ├── ds-component-coverage.plugin.ts
│       │   │       ├── runner
│       │   │       │   ├── audits
│       │   │       │   │   └── ds-coverage
│       │   │       │   │       ├── class-definition.utils.ts
│       │   │       │   │       ├── class-definition.visitor.ts
│       │   │       │   │       ├── class-definition.visitor.unit.test.ts
│       │   │       │   │       ├── class-usage.utils.ts
│       │   │       │   │       ├── class-usage.visitor.spec.ts
│       │   │       │   │       ├── class-usage.visitor.ts
│       │   │       │   │       ├── constants.ts
│       │   │       │   │       ├── ds-coverage.audit.ts
│       │   │       │   │       ├── schema.ts
│       │   │       │   │       └── utils.ts
│       │   │       │   ├── create-runner.ts
│       │   │       │   └── schema.ts
│       │   │       └── utils.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   ├── tsconfig.spec.json
│       │   └── vitest.config.mts
│       ├── LLMS.md
│       ├── models
│       │   ├── ai
│       │   │   ├── API.md
│       │   │   ├── EXAMPLES.md
│       │   │   └── FUNCTIONS.md
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── index.ts
│       │   │   └── lib
│       │   │       ├── cli.ts
│       │   │       ├── diagnostics.ts
│       │   │       └── mcp.ts
│       │   ├── tsconfig.json
│       │   └── tsconfig.lib.json
│       ├── styles-ast-utils
│       │   ├── .spec.swcrc
│       │   ├── ai
│       │   │   ├── API.md
│       │   │   ├── EXAMPLES.md
│       │   │   └── FUNCTIONS.md
│       │   ├── jest.config.ts
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── index.ts
│       │   │   └── lib
│       │   │       ├── postcss-safe-parser.d.ts
│       │   │       ├── styles-ast-utils.spec.ts
│       │   │       ├── styles-ast-utils.ts
│       │   │       ├── stylesheet.parse.ts
│       │   │       ├── stylesheet.parse.unit.test.ts
│       │   │       ├── stylesheet.visitor.ts
│       │   │       ├── stylesheet.walk.ts
│       │   │       ├── types.ts
│       │   │       ├── utils.ts
│       │   │       └── utils.unit.test.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   ├── tsconfig.spec.json
│       │   └── vitest.config.mts
│       ├── typescript-ast-utils
│       │   ├── .spec.swcrc
│       │   ├── ai
│       │   │   ├── API.md
│       │   │   ├── EXAMPLES.md
│       │   │   └── FUNCTIONS.md
│       │   ├── jest.config.ts
│       │   ├── package.json
│       │   ├── README.md
│       │   ├── src
│       │   │   ├── index.ts
│       │   │   └── lib
│       │   │       ├── constants.ts
│       │   │       └── utils.ts
│       │   ├── tsconfig.json
│       │   ├── tsconfig.lib.json
│       │   ├── tsconfig.spec.json
│       │   └── vitest.config.mts
│       └── utils
│           ├── .spec.swcrc
│           ├── ai
│           │   ├── API.md
│           │   ├── EXAMPLES.md
│           │   └── FUNCTIONS.md
│           ├── package.json
│           ├── README.md
│           ├── src
│           │   ├── index.ts
│           │   └── lib
│           │       ├── execute-process.ts
│           │       ├── execute-process.unit.test.ts
│           │       ├── file
│           │       │   ├── default-export-loader.spec.ts
│           │       │   ├── default-export-loader.ts
│           │       │   ├── file.resolver.ts
│           │       │   └── find-in-file.ts
│           │       ├── format-command-log.integration.test.ts
│           │       ├── format-command-log.ts
│           │       ├── logging.ts
│           │       └── utils.ts
│           ├── tsconfig.json
│           ├── tsconfig.lib.json
│           ├── tsconfig.spec.json
│           ├── vite.config.ts
│           └── vitest.config.mts
├── README.md
├── testing
│   ├── setup
│   │   ├── eslint.config.mjs
│   │   ├── eslint.next.config.mjs
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── index.d.ts
│   │   │   ├── index.mjs
│   │   │   └── memfs.constants.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.lib.json
│   │   ├── tsconfig.spec.json
│   │   ├── vitest.config.mts
│   │   └── vitest.integration.config.mts
│   ├── utils
│   │   ├── eslint.config.mjs
│   │   ├── eslint.next.config.mjs
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── index.ts
│   │   │   └── lib
│   │   │       ├── constants.ts
│   │   │       ├── e2e-setup.ts
│   │   │       ├── execute-process-helper.mock.ts
│   │   │       ├── execute-process.mock.mjs
│   │   │       ├── os-agnostic-paths.ts
│   │   │       ├── os-agnostic-paths.unit.test.ts
│   │   │       ├── source-file-from.code.ts
│   │   │       └── string.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.lib.json
│   │   ├── tsconfig.spec.json
│   │   ├── vite.config.ts
│   │   ├── vitest.config.mts
│   │   └── vitest.integration.config.mts
│   └── vitest-setup
│       ├── eslint.config.mjs
│       ├── eslint.next.config.mjs
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── index.ts
│       │   └── lib
│       │       ├── configuration.ts
│       │       └── fs-memfs.setup-file.ts
│       ├── tsconfig.json
│       ├── tsconfig.lib.json
│       ├── tsconfig.spec.json
│       ├── vite.config.ts
│       ├── vitest.config.mts
│       └── vitest.integration.config.mts
├── tools
│   ├── nx-advanced-profile.bin.js
│   ├── nx-advanced-profile.js
│   ├── nx-advanced-profile.postinstall.js
│   └── perf_hooks.patch.js
├── tsconfig.base.json
├── tsconfig.json
└── vitest.workspace.ts
```

# Files

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/cross-platform-path.ts:
--------------------------------------------------------------------------------

```typescript
import * as path from 'path';
import * as fs from 'fs';
import { toUnixPath } from '@code-pushup/utils';

/**
 * Enhanced path resolution with workspace root context for better debugging
 */
export function resolveCrossPlatformPathWithContext(
  basePath: string,
  relativePath: string,
  workspaceRoot?: string,
): {
  resolved: string;
  context: {
    basePath: string;
    relativePath: string;
    workspaceRoot?: string;
    isBasePathSameAsWorkspaceRoot: boolean;
    relativeToWorkspaceRoot?: string;
  };
} {
  const normalizedRelative = relativePath.replace(/\\/g, '/');
  const resolved = path.resolve(basePath, normalizedRelative);
  const unixResolved = toUnixPath(resolved);

  const context = {
    basePath: toUnixPath(basePath),
    relativePath: normalizedRelative,
    workspaceRoot: workspaceRoot ? toUnixPath(workspaceRoot) : undefined,
    isBasePathSameAsWorkspaceRoot: workspaceRoot
      ? toUnixPath(basePath) === toUnixPath(workspaceRoot)
      : false,
    relativeToWorkspaceRoot: workspaceRoot
      ? path.relative(workspaceRoot, unixResolved)
      : undefined,
  };

  return { resolved: unixResolved, context };
}

/**
 * Enhanced validation with comprehensive workspace root context
 */
export function resolveCrossPlatformPathAndValidateWithContext(
  basePath: string,
  relativePath: string,
  workspaceRoot?: string,
): string {
  const { resolved, context } = resolveCrossPlatformPathWithContext(
    basePath,
    relativePath,
    workspaceRoot,
  );

  // Convert to platform-specific for fs operations
  const fsPath = resolved.replace(/\//g, path.sep);

  if (!fs.existsSync(fsPath)) {
    let errorMessage =
      `Directory does not exist: ${relativePath}\n` +
      `Resolved to: ${resolved}\n` +
      `Base path: ${basePath}`;

    if (workspaceRoot) {
      errorMessage +=
        `\n` +
        `Workspace root: ${workspaceRoot}\n` +
        `Base path same as workspace root: ${context.isBasePathSameAsWorkspaceRoot}\n` +
        `Path relative to workspace root: ${context.relativeToWorkspaceRoot}`;

      if (!context.isBasePathSameAsWorkspaceRoot) {
        errorMessage +=
          `\n` +
          `⚠️  WARNING: Base path differs from workspace root!\n` +
          `   This might indicate a configuration issue.`;
      }
    }

    throw new Error(errorMessage);
  }

  return resolved;
}

/**
 * Resolves a relative path against a base path with cross-platform normalization.
 * Handles mixed path separators and ensures consistent Unix-style output.
 *
 * @param basePath - The base directory (usually absolute)
 * @param relativePath - The relative path to resolve
 * @returns Normalized absolute path with Unix-style separators
 */
export function resolveCrossPlatformPath(
  basePath: string,
  relativePath: string,
): string {
  return resolveCrossPlatformPathWithContext(basePath, relativePath).resolved;
}

/**
 * Resolves a relative path against a base path and validates that it exists.
 * Provides helpful error messages for debugging path issues.
 *
 * @param basePath - The base directory (usually absolute)
 * @param relativePath - The relative path to resolve
 * @returns Normalized absolute path with Unix-style separators
 * @throws Error if the resolved path does not exist
 */
export function resolveCrossPlatformPathAndValidate(
  basePath: string,
  relativePath: string,
): string {
  return resolveCrossPlatformPathAndValidateWithContext(basePath, relativePath);
}

/**
 * Legacy replacement for validateTargetPath function.
 * Enhanced version that includes workspace root context when available.
 *
 * @param cwd - Current working directory (base path)
 * @param directory - Relative directory path to validate
 * @param workspaceRoot - Optional workspace root for enhanced error context
 * @returns Normalized absolute path with Unix-style separators
 * @throws Error if the resolved path does not exist
 */
export function validateTargetPath(
  cwd: string,
  directory: string,
  workspaceRoot?: string,
): string {
  return resolveCrossPlatformPathAndValidateWithContext(
    cwd,
    directory,
    workspaceRoot,
  );
}

/**
 * Converts absolute paths to relative paths based on a workspace root.
 * Handles file paths with line/column annotations (e.g., "file.html@44:9")
 *
 * @param absolutePath - The absolute path to normalize
 * @param workspaceRoot - The workspace root to make paths relative to
 * @returns The normalized relative path
 */
export function normalizeAbsolutePathToRelative(
  absolutePath: string,
  workspaceRoot: string,
): string {
  // Handle paths with line/column annotations (e.g., "file.html@44:9")
  const [filePath, annotation] = absolutePath.split('@');

  // Convert to Unix-style paths for consistent processing
  const normalizedFilePath = toUnixPath(filePath);
  const normalizedWorkspaceRoot = toUnixPath(workspaceRoot);

  // If the path is already relative or doesn't start with workspace root, return as-is
  if (
    !path.isAbsolute(normalizedFilePath) ||
    !normalizedFilePath.startsWith(normalizedWorkspaceRoot)
  ) {
    return absolutePath;
  }

  // Calculate relative path
  const relativePath = path.relative(
    normalizedWorkspaceRoot,
    normalizedFilePath,
  );

  // Reconstruct with annotation if it existed
  return annotation ? `${relativePath}@${annotation}` : relativePath;
}

/**
 * Recursively normalizes all absolute paths in an object to relative paths
 *
 * @param obj - The object to process
 * @param workspaceRoot - The workspace root to make paths relative to
 * @returns The object with normalized paths
 */
export function normalizePathsInObject<T>(obj: T, workspaceRoot: string): T {
  if (obj === null || obj === undefined) {
    return obj;
  }

  if (typeof obj === 'string') {
    // Check if this looks like an absolute path that starts with workspace root
    const normalizedWorkspaceRoot = toUnixPath(workspaceRoot);
    const normalizedObj = toUnixPath(obj);

    if (
      path.isAbsolute(normalizedObj) &&
      normalizedObj.startsWith(normalizedWorkspaceRoot)
    ) {
      return normalizeAbsolutePathToRelative(obj, workspaceRoot) as T;
    }
    return obj;
  }

  if (Array.isArray(obj)) {
    return obj.map((item) => normalizePathsInObject(item, workspaceRoot)) as T;
  }

  if (typeof obj === 'object') {
    const result = {} as T;
    for (const [key, value] of Object.entries(obj)) {
      (result as any)[key] = normalizePathsInObject(value, workspaceRoot);
    }
    return result;
  }

  return obj;
}

```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/first-case/dashboard-header.component.html:
--------------------------------------------------------------------------------

```html
<header class="dashboard-header">
  <!-- Logo and Title Section -->
  <div class="header-brand">
    <div class="brand-logo">
      <svg width="32" height="32" viewBox="0 0 32 32" class="logo-icon">
        <circle cx="16" cy="16" r="14" fill="#4F46E5"/>
        <path d="M12 16l4 4 8-8" stroke="white" stroke-width="2" fill="none"/>
      </svg>
    </div>
    <h1 class="brand-title">Dashboard Pro</h1>
    
    <!-- DsBadge Component -->
    <ds-badge 
      [size]="badgeSize() === 'large' ? 'medium' : badgeSize() === 'medium' ? 'medium' : 'xsmall'"
      [variant]="getBadgeVariant()">
      <!-- Start slot for icon -->
      <span slot="start">
        @if (!hasIconSlot()) {
          <span class="offer-badge-default-icon">🎯</span>
        }
        <ng-content select="[slot=start]" />
      </span>
      <!-- Main content -->
      <ng-content />
      @if (!hasContent()) {
        {{ defaultBadgeText() }}
      }
      <!-- End slot for dismiss button -->
      <span slot="end">
        @if (dismissible()) {
          <button 
            class="offer-badge-dismiss"
            (click)="dismissBadge()"
            aria-label="Dismiss offer">
            ×
          </button>
        }
        <ng-content select="[slot=end]" />
      </span>
    </ds-badge>
  </div>

  <!-- Search Section -->
  <div class="header-search">
    <div *ngIf="true" class="search-container" [class.search-focused]="searchFocused()">
      <svg class="search-icon" width="20" height="20" viewBox="0 0 20 20">
        <path d="M9 2a7 7 0 1 1 0 14 7 7 0 0 1 0-14zM2 9a7 7 0 1 0 14 0 7 7 0 0 0-14 0z" fill="currentColor"/>
        <path d="m13 13 4 4-1.5 1.5-4-4" fill="currentColor"/>
      </svg>
      <input 
        type="text"
        class="search-input"
        placeholder="Search dashboard..."
        [(ngModel)]="searchQuery"
        (focus)="searchFocused.set(true)"
        (blur)="searchFocused.set(false)"
        (keyup.enter)="performSearch()"
        [disabled]="searchDisabled()">
      @if (searchQuery()) {
        <button 
          class="search-clear"
          (click)="clearSearch()"
          aria-label="Clear search">
          ×
        </button>
      }
    </div>
    @if (searchSuggestions().length > 0 && searchFocused()) {
      <div class="search-suggestions">
        @for (suggestion of searchSuggestions(); track suggestion.id) {
          <button 
            class="suggestion-item"
            (click)="selectSuggestion(suggestion)">
            {{ suggestion.text }}
          </button>
        }
      </div>
    }
  </div>

  <!-- Actions Section -->
  <div class="header-actions">
    <!-- Notifications -->
    <div class="action-item notification-container">
      <button 
        class="action-button notification-button"
        [class.has-notifications]="unreadNotifications() > 0"
        (click)="toggleNotifications()"
        [attr.aria-label]="'Notifications (' + unreadNotifications() + ' unread)'">
        <svg width="24" height="24" viewBox="0 0 24 24" class="notification-icon">
          <path d="M12 2a7 7 0 0 1 7 7v4.29l1.71 1.71a1 1 0 0 1-.71 1.71H4a1 1 0 0 1-.71-1.71L5 13.29V9a7 7 0 0 1 7-7z" fill="currentColor"/>
          <path d="M10 20a2 2 0 1 0 4 0" fill="currentColor"/>
        </svg>
        @if (unreadNotifications() > 0) {
          <span class="notification-badge">{{ unreadNotifications() }}</span>
        }
      </button>
      
      @if (showNotifications()) {
        <div class="notifications-dropdown">
          <div class="dropdown-header">
            <h3>Notifications</h3>
            @if (unreadNotifications() > 0) {
              <button 
                class="mark-all-read"
                (click)="markAllNotificationsRead()">
                Mark all read
              </button>
            }
          </div>
          <div class="notifications-list">
            @for (notification of notifications(); track notification.id) {
              <div 
                class="notification-item"
                [class.notification-unread]="!notification.read"
                [class.notification-{{ notification.type }}]="true">
                <div class="notification-content">
                  <h4 class="notification-title">{{ notification.title }}</h4>
                  <p class="notification-message">{{ notification.message }}</p>
                  <span class="notification-time">{{ formatTime(notification.timestamp) }}</span>
                </div>
                <button 
                  class="notification-dismiss"
                  (click)="dismissNotification(notification.id)"
                  aria-label="Dismiss notification">
                  ×
                </button>
              </div>
            } @empty {
              <div class="no-notifications">
                <p>No notifications</p>
              </div>
            }
          </div>
        </div>
      }
    </div>

    <!-- User Menu -->
    <div class="action-item user-container">
      <button 
        class="action-button user-button"
        (click)="toggleUserMenu()"
        [attr.aria-label]="'User menu for ' + userProfile()?.name">
        @if (userProfile()?.avatar) {
          <img 
            [src]="userProfile()!.avatar" 
            [alt]="userProfile()!.name"
            class="user-avatar">
        } @else {
          <div class="user-avatar-placeholder">
            {{ getUserInitials() }}
          </div>
        }
        <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12">
          <path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none"/>
        </svg>
      </button>

      @if (showUserMenu()) {
        <div class="user-dropdown">
          <div class="user-info">
            <div class="user-details">
              <h4>{{ userProfile()?.name }}</h4>
              <p>{{ userProfile()?.email }}</p>
              <span class="user-role">{{ userProfile()?.role }}</span>
            </div>
          </div>
          <div class="user-actions">
            <button class="dropdown-item" (click)="navigateToProfile()">
              Profile Settings
            </button>
            <button class="dropdown-item" (click)="navigateToPreferences()">
              Preferences
            </button>
            <button class="dropdown-item" (click)="toggleTheme()">
              {{ darkMode() ? 'Light Mode' : 'Dark Mode' }}
            </button>
            <hr class="dropdown-divider">
            <button class="dropdown-item logout" (click)="logout()">
              Sign Out
            </button>
          </div>
        </div>
      }
    </div>
  </div>
</header> 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component/list-ds-components.tool.ts:
--------------------------------------------------------------------------------

```typescript
import { ToolSchemaOptions } from '@push-based/models';
import {
  createHandler,
  BaseHandlerOptions,
} from '../shared/utils/handler-helpers.js';
import { COMMON_ANNOTATIONS } from '../shared/models/schema-helpers.js';
import { getComponentPathsInfo } from './utils/paths-helpers.js';
import { getComponentDocPathsForName } from './utils/doc-helpers.js';
import { resolveCrossPlatformPath } from '../shared/utils/cross-platform-path.js';
import * as fs from 'fs';
import * as path from 'path';

interface DsComponentInfo {
  componentName: string;
  folderName: string;
  implementation: string[];
  documentation: string[];
  stories: string[];
  importPath: string;
}

interface ListDsComponentsOptions extends BaseHandlerOptions {
  sections?: string[];
}

export const listDsComponentsToolSchema: ToolSchemaOptions = {
  name: 'list-ds-components',
  description: `List all available Design System components in the project. Returns component names, folder structures, import paths, implementation files, documentation files, and stories files.`,
  inputSchema: {
    type: 'object',
    properties: {
      sections: {
        type: 'array',
        items: {
          type: 'string',
          enum: ['implementation', 'documentation', 'stories', 'all'],
        },
        description:
          'Sections to include in the response. Options: "implementation", "documentation", "stories", "all". Defaults to ["all"] if not specified.',
        default: ['all'],
      },
    },
    required: [],
  },
  annotations: {
    title: 'List Design System Components',
    ...COMMON_ANNOTATIONS.readOnly,
  },
};

function getAllFilesInDirectory(dirPath: string): string[] {
  const files: string[] = [];

  function walkDirectory(currentPath: string) {
    try {
      const items = fs.readdirSync(currentPath);

      for (const item of items) {
        const fullPath = path.join(currentPath, item);
        const stat = fs.statSync(fullPath);

        if (stat.isDirectory()) {
          walkDirectory(fullPath);
        } else {
          files.push(fullPath);
        }
      }
    } catch {
      return;
    }
  }

  if (fs.existsSync(dirPath)) {
    walkDirectory(dirPath);
  }

  return files;
}

function kebabCaseToPascalCase(kebabCase: string): string {
  return (
    'Ds' +
    kebabCase
      .split('-')
      .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
      .join('')
  );
}

function isValidComponentFolder(folderPath: string): boolean {
  const packageJsonPath = path.join(folderPath, 'package.json');
  const srcPath = path.join(folderPath, 'src');

  return (
    fs.existsSync(packageJsonPath) &&
    fs.existsSync(srcPath) &&
    fs.statSync(srcPath).isDirectory()
  );
}

function findStoriesFiles(componentPath: string): string[] {
  const storiesFiles: string[] = [];

  try {
    if (fs.existsSync(componentPath)) {
      const items = fs.readdirSync(componentPath);

      for (const item of items) {
        const fullPath = path.join(componentPath, item);
        const stat = fs.statSync(fullPath);

        if (stat.isFile() && item.endsWith('.stories.ts')) {
          storiesFiles.push(fullPath);
        }
      }
    }
  } catch {
    return storiesFiles;
  }

  return storiesFiles;
}

export const listDsComponentsHandler = createHandler<
  ListDsComponentsOptions,
  DsComponentInfo[]
>(
  listDsComponentsToolSchema.name,
  async ({ sections = ['all'] }, { cwd, uiRoot, storybookDocsRoot }) => {
    try {
      if (!uiRoot || typeof uiRoot !== 'string') {
        throw new Error('uiRoot must be provided and be a string path.');
      }

      const componentsBasePath = resolveCrossPlatformPath(cwd, uiRoot);

      if (!fs.existsSync(componentsBasePath)) {
        throw new Error(`Components directory not found: ${uiRoot}`);
      }

      const entries = fs.readdirSync(componentsBasePath, {
        withFileTypes: true,
      });
      const componentFolders = entries
        .filter((entry) => entry.isDirectory())
        .map((entry) => entry.name)
        .filter((folderName) => {
          const folderPath = path.join(componentsBasePath, folderName);
          return isValidComponentFolder(folderPath);
        });

      const components: DsComponentInfo[] = [];

      const includeAll = sections.includes('all');
      const includeImplementation =
        includeAll || sections.includes('implementation');
      const includeDocumentation =
        includeAll || sections.includes('documentation');
      const includeStories = includeAll || sections.includes('stories');

      for (const folderName of componentFolders) {
        try {
          const componentName = kebabCaseToPascalCase(folderName);

          const pathsInfo = getComponentPathsInfo(componentName, uiRoot, cwd);

          let implementationFiles: string[] = [];
          if (includeImplementation) {
            const srcFiles = getAllFilesInDirectory(pathsInfo.srcPath);
            implementationFiles = srcFiles.map((file) => `file://${file}`);
          }

          const documentationFiles: string[] = [];

          let storiesFilePaths: string[] = [];
          if (storybookDocsRoot) {
            const docsBasePath = resolveCrossPlatformPath(
              cwd,
              storybookDocsRoot,
            );

            if (includeDocumentation) {
              const docPaths = getComponentDocPathsForName(
                docsBasePath,
                componentName,
              );

              if (fs.existsSync(docPaths.paths.api)) {
                documentationFiles.push(`file://${docPaths.paths.api}`);
              }
              if (fs.existsSync(docPaths.paths.overview)) {
                documentationFiles.push(`file://${docPaths.paths.overview}`);
              }
            }

            if (includeStories) {
              const storiesComponentFolderPath = path.join(
                docsBasePath,
                folderName,
              );
              const storiesFiles = findStoriesFiles(storiesComponentFolderPath);
              storiesFilePaths = storiesFiles.map((file) => `file://${file}`);
            }
          }

          components.push({
            componentName,
            folderName,
            implementation: implementationFiles,
            documentation: documentationFiles,
            stories: storiesFilePaths,
            importPath: pathsInfo.importPath,
          });
        } catch (ctx) {
          console.warn(
            `Warning: Skipped component '${folderName}': ${(ctx as Error).message}`,
          );
        }
      }

      return components;
    } catch (ctx) {
      throw new Error(`Error listing DS components: ${(ctx as Error).message}`);
    }
  },
  (components) => {
    const response = {
      totalComponents: components?.length || 0,
      components: components || [],
    };

    return [JSON.stringify(response, null, 2)];
  },
);

export const listDsComponentsTools = [
  {
    schema: listDsComponentsToolSchema,
    handler: listDsComponentsHandler,
  },
];

```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/formatters.ts:
--------------------------------------------------------------------------------

```typescript
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { buildText } from '../utils/output.utils.js';
import {
  BaseViolationResult,
  BaseViolationAudit,
  BaseViolationIssue,
  FileGroups,
  PathCache,
} from './types.js';

// Performance-optimized path cache
const pathCache: PathCache = {};

/**
 * Filters audits to only include those with violations (score < 1)
 */
export function filterFailedAudits(
  result: BaseViolationResult,
): BaseViolationAudit[] {
  return result.audits.filter(({ score }) => score < 1);
}

/**
 * Creates standard "no violations found" content
 */
export function createNoViolationsContent(): CallToolResult['content'] {
  return [
    buildText(
      '✅ No violations found! All files are compliant with the design system.',
    ),
  ];
}

/**
 * Extracts all issues from failed audits
 */
export function extractIssuesFromAudits(
  audits: BaseViolationAudit[],
): BaseViolationIssue[] {
  return audits.flatMap(({ details }) => details?.issues ?? []);
}

/**
 * Checks if a violation result has any failures
 */
export function hasViolations(result: BaseViolationResult): boolean {
  return filterFailedAudits(result).length > 0;
}

/**
 * Performance-optimized file path normalization with caching
 */
export function normalizeFilePath(filePath: string, directory: string): string {
  const cacheKey = `${filePath}::${directory}`;

  if (pathCache[cacheKey]) {
    return pathCache[cacheKey];
  }

  // Normalize both paths to use consistent separators
  const normalizedFilePath = filePath.replace(/\\/g, '/');
  const normalizedDirectory = directory.replace(/\\/g, '/');

  // Remove leading './' from directory if present for comparison
  const cleanDirectory = normalizedDirectory.startsWith('./')
    ? normalizedDirectory.slice(2)
    : normalizedDirectory;

  let normalized: string;

  // The file path from the coverage plugin is absolute, but we need to extract the relative part
  // Look for the directory pattern in the file path and extract everything after it
  const directoryPattern = cleanDirectory;
  const directoryIndex = normalizedFilePath.indexOf(directoryPattern);

  if (directoryIndex !== -1) {
    // Found the directory in the path, extract the part after it
    const afterDirectoryIndex = directoryIndex + directoryPattern.length;
    const afterDirectory = normalizedFilePath.slice(afterDirectoryIndex);

    // Remove leading slash if present
    normalized = afterDirectory.startsWith('/')
      ? afterDirectory.slice(1)
      : afterDirectory;
  } else {
    // Fallback: try with directory prefix approach
    const directoryPrefix = normalizedDirectory.endsWith('/')
      ? normalizedDirectory
      : normalizedDirectory + '/';
    normalized = normalizedFilePath.startsWith(directoryPrefix)
      ? normalizedFilePath.slice(directoryPrefix.length)
      : normalizedFilePath;
  }

  pathCache[cacheKey] = normalized;
  return normalized;
}

/**
 * Performance-optimized message normalization with caching
 */
export function normalizeMessage(message: string, directory: string): string {
  const cacheKey = `msg::${message}::${directory}`;

  if (pathCache[cacheKey]) {
    return pathCache[cacheKey];
  }

  const directoryPrefix = directory.endsWith('/') ? directory : directory + '/';
  const normalized = message.includes(directoryPrefix)
    ? message.replace(directoryPrefix, '')
    : message;

  pathCache[cacheKey] = normalized;
  return normalized;
}

/**
 * Groups violation issues by file name - consolidated from multiple modules
 * Performance optimized with Set for duplicate checking and cached normalizations
 */
export function groupIssuesByFile(
  issues: BaseViolationIssue[],
  directory: string,
): FileGroups {
  const fileGroups: FileGroups = {};
  const processedFiles = new Set<string>(); // O(1) lookup instead of includes()

  for (const { message, source } of issues) {
    if (!source?.file) continue;

    const fileName = normalizeFilePath(source.file, directory);
    const lineNumber = source.position?.startLine || 0;

    if (!fileGroups[fileName]) {
      fileGroups[fileName] = {
        message: normalizeMessage(message, directory),
        lines: [],
      };
      processedFiles.add(fileName);
    }

    fileGroups[fileName].lines.push(lineNumber);
  }

  return fileGroups;
}

/**
 * Extracts unique file paths from violation issues - performance optimized
 */
export function extractUniqueFilePaths(
  issues: BaseViolationIssue[],
  directory: string,
): string[] {
  const filePathSet = new Set<string>(); // Eliminate O(n) includes() calls

  for (const { source } of issues) {
    if (source?.file) {
      filePathSet.add(normalizeFilePath(source.file, directory));
    }
  }

  return Array.from(filePathSet);
}

/**
 * Clears the path cache - useful for testing or memory management
 */
export function clearPathCache(): void {
  Object.keys(pathCache).forEach((key) => delete pathCache[key]);
}

/**
 * Unified formatter for violations - supports both file and folder grouping with minimal output
 */
export function formatViolations(
  result: BaseViolationResult,
  directory: string,
  options: {
    groupBy: 'file' | 'folder';
  } = { groupBy: 'file' },
): CallToolResult['content'] {
  const failedAudits = filterFailedAudits(result);

  if (failedAudits.length === 0) {
    return [buildText('No violations found.')];
  }

  const allIssues = extractIssuesFromAudits(failedAudits);
  const content: CallToolResult['content'] = [];

  if (options.groupBy === 'file') {
    // Group by individual files - minimal format
    const fileGroups = groupIssuesByFile(allIssues, directory);

    for (const [fileName, { message, lines }] of Object.entries(fileGroups)) {
      const sortedLines = lines.sort((a, b) => a - b);
      const lineInfo =
        sortedLines.length > 1
          ? `lines ${sortedLines.join(', ')}`
          : `line ${sortedLines[0]}`;

      content.push(buildText(`${fileName} (${lineInfo}): ${message}`));
    }
  } else {
    // Group by folders - minimal format
    const folderGroups: Record<
      string,
      { violations: number; files: Set<string> }
    > = {};

    for (const { source } of allIssues) {
      if (!source?.file) continue;

      const normalizedPath = normalizeFilePath(source.file, directory);
      const folderPath = normalizedPath.includes('/')
        ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/'))
        : '.';

      if (!folderGroups[folderPath]) {
        folderGroups[folderPath] = { violations: 0, files: new Set() };
      }

      folderGroups[folderPath].violations++;
      folderGroups[folderPath].files.add(normalizedPath);
    }

    // Sort folders for consistent output
    for (const [folder, { violations, files }] of Object.entries(
      folderGroups,
    ).sort()) {
      const displayPath = folder === '.' ? directory : `${directory}/${folder}`;
      content.push(
        buildText(
          `${displayPath}: ${violations} violations in ${files.size} files`,
        ),
      );
    }
  }

  return content;
}

```

--------------------------------------------------------------------------------
/packages/shared/angular-ast-utils/src/lib/decorator-config.visitor.ts:
--------------------------------------------------------------------------------

```typescript
import * as path from 'node:path';
import * as ts from 'typescript';

import { ParsedComponent } from './types.js';
import { parseStylesheet } from '@push-based/styles-ast-utils';
import { resolveFile } from '@push-based/utils';
import {
  visitAngularDecoratorProperties,
  visitAngularDecorators,
} from './ts.walk.js';
import {
  assetFromPropertyArrayInitializer,
  assetFromPropertyValueInitializer,
} from './utils.js';
import {
  isComponentDecorator,
  removeQuotes,
} from '@push-based/typescript-ast-utils';
import type {
  ParsedTemplate,
  ParseTemplateOptions,
} from '@angular/compiler' with { 'resolution-mode': 'import' };

const DEBUG = false;
const debug = ({
  step,
  title,
  info,
}: {
  step: string;
  title: string;
  info: string | null | undefined;
}) =>
  DEBUG &&
  console.log(
    `─── 📌 [${step}]: ${title} ${info ? '-' + info : ''}──────────────────────────────`,
  );

export async function classDecoratorVisitor({
  sourceFile,
}: {
  sourceFile: ts.SourceFile;
}) {
  // @TODO: rethink module resolution
  const { parseTemplate } = await import('@angular/compiler');
  const components: ParsedComponent[] = [];
  let activeComponent: ParsedComponent | null = null;
  let currentClassName: string | null = null;

  const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
    // ─── 📌 ENTER: Class Declaration ──────────────────────────────
    if (ts.isClassDeclaration(node)) {
      currentClassName = node.name?.text ?? 'Unknown'; // Capture class name immediately
      debug({
        step: 'ENTER',
        title: 'ClassDeclaration',
        info: `class ${currentClassName}`,
      });

      activeComponent = {
        startLine: ts.getLineAndCharacterOfPosition(
          sourceFile,
          node.getStart(sourceFile),
        ).line,
        className: currentClassName,
        fileName: sourceFile.fileName,
      } as ParsedComponent;

      visitAngularDecorators(node, (decorator: ts.Decorator) => {
        debug({
          step: 'ENTER',
          title: 'ClassDecorators',
          info: 'of ' + currentClassName,
        });
        if (isComponentDecorator(decorator)) {
          debug({
            step: 'ENTER',
            title: 'ClassDecorator',
            info: '@Component',
          });
          visitAngularDecoratorProperties(
            decorator,
            (prop: ts.PropertyAssignment) => {
              if (
                !ts.isPropertyAssignment(prop) ||
                !ts.isIdentifier(prop.name)
              ) {
                return;
              }

              const propName = prop.name.escapedText as string;
              const getPropValue = getPropValueFactory(parseTemplate);
              const propValue = getPropValue(
                prop,
                sourceFile,
                currentClassName ?? 'undefined-class',
              );
              if (activeComponent) {
                (activeComponent as ParsedComponent)[propName] =
                  propValue as unknown as string;

                debug({
                  step: 'Update',
                  title: 'ParsedComponent',
                  info: `add ${propName}`,
                });
              }
            },
          );

          if (activeComponent) {
            debug({
              step: 'PUSH',
              title: 'ParsedComponent',
              info: `add ${activeComponent.className}`,
            });
            components.push(activeComponent);
            activeComponent = null;
          }
        }
      });

      debug({
        step: 'EXIT',
        title: 'ClassDeclaration',
        info: `class ${currentClassName}`,
      });
      currentClassName = null;
    }
    return node;
  };

  visitor.components = components;
  return visitor;
}

function getPropValueFactory(
  parseTemplate: (
    template: string,
    templateUrl: string,
    options?: ParseTemplateOptions,
  ) => ParsedTemplate,
) {
  return (
    prop: ts.PropertyAssignment,
    sourceFile: ts.SourceFile,
    currentClassName: string,
  ): string | unknown => {
    let propName = '';
    if (ts.isIdentifier(prop.name)) {
      propName = prop.name.escapedText as string;
    } else {
      throw new Error('Property name is not an identifier');
    }
    switch (propName) {
      case 'templateUrl':
      case 'template':
        return assetFromPropertyValueInitializer({
          prop,
          sourceFile,
          textParser: async (text: string) => {
            const filePath =
              propName === 'templateUrl'
                ? path.join(path.dirname(sourceFile.fileName), text)
                : sourceFile.fileName;
            const content =
              propName === 'templateUrl' ? await resolveFile(filePath) : text;

            debug({
              step: 'RESOLVE',
              title: 'Template',
              info: `${currentClassName}; file ${filePath}`,
            });

            return parseTemplate(content, filePath, {
              preserveWhitespaces: true,
              preserveLineEndings: true,
              // preserveSignificantWhitespace: true,
            });
          },
        });
      case 'styleUrl':
        return assetFromPropertyValueInitializer({
          prop,
          sourceFile,
          textParser: async (text: string) => {
            const filePath = path.join(path.dirname(sourceFile.fileName), text);
            const content = await resolveFile(filePath);

            debug({
              step: 'RESOLVE',
              title: 'styleUrl',
              info: `${currentClassName}; file ${filePath}`,
            });
            return parseStylesheet(content, filePath);
          },
        });
      case 'styles':
        if (ts.isArrayLiteralExpression(prop.initializer)) {
          return assetFromPropertyArrayInitializer(
            prop,
            sourceFile,
            async (text: string) => {
              debug({
                step: 'RESOLVE',
                title: 'Styles',
                info: `${currentClassName}; inline-array`,
              });
              return parseStylesheet(text, sourceFile.fileName);
            },
          );
        }

        return [
          assetFromPropertyValueInitializer({
            prop,
            sourceFile,
            textParser: async (text: string) => {
              debug({
                step: 'RESOLVE',
                title: 'Styles',
                info: `${currentClassName}; inline-single`,
              });
              return parseStylesheet(text, sourceFile.fileName);
            },
          }),
        ];
      case 'styleUrls':
        return assetFromPropertyArrayInitializer(
          prop,
          sourceFile,
          async (text: string) => {
            const filePath = path.join(path.dirname(sourceFile.fileName), text);
            const content = await resolveFile(filePath);

            debug({
              step: 'RESOLVE',
              title: 'styleUrls',
              info: `${currentClassName}; file ${filePath}`,
            });
            return parseStylesheet(content, filePath);
          },
        );
      default:
        // @TODO: Implement all of the decorator props
        return removeQuotes(prop.initializer, sourceFile);
    }
  };
}

```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/first-case/dashboard-header.component.ts:
--------------------------------------------------------------------------------

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  ViewEncapsulation,
  computed,
  input,
  output,
  signal,
  booleanAttribute,
  OnInit,
  OnDestroy,
  inject,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DsBadge, DsBadgeVariant } from '@frontend/ui/badge';
import { Subject, interval, takeUntil } from 'rxjs';

export const OFFER_BADGE_TYPES = ['limited', 'premium', 'new', 'hot', 'sale'] as const;
export type OfferBadgeType = (typeof OFFER_BADGE_TYPES)[number];

export const OFFER_BADGE_SIZES = ['small', 'medium', 'large'] as const;
export type OfferBadgeSize = (typeof OFFER_BADGE_SIZES)[number];

export interface NotificationItem {
  id: string;
  title: string;
  message: string;
  timestamp: Date;
  read: boolean;
  type: 'info' | 'warning' | 'error' | 'success';
}

export interface UserProfile {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  role: string;
  lastLogin: Date;
}

@Component({
  selector: 'app-dashboard-header',
  standalone: true,
  imports: [CommonModule, FormsModule, DsBadge],
  templateUrl: './dashboard-header.component.html',
  styleUrls: ['./dashboard-header.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DashboardHeaderComponent implements OnInit, OnDestroy {
  // Offer Badge Inputs
  badgeType = input<OfferBadgeType>('premium');
  badgeSize = input<OfferBadgeSize>('medium');
  animated = input(true, { transform: booleanAttribute });
  pulsing = input(false, { transform: booleanAttribute });
  dismissible = input(true, { transform: booleanAttribute });
  
  // General Inputs
  searchDisabled = input(false, { transform: booleanAttribute });
  darkMode = input(false, { transform: booleanAttribute });
  userProfile = input<UserProfile | null>(null);
  
  // Outputs
  searchPerformed = output<string>();
  badgeDismissed = output<void>();
  notificationClicked = output<NotificationItem>();
  userActionClicked = output<string>();
  themeToggled = output<boolean>();

  // Internal State
  searchQuery = signal('');
  searchFocused = signal(false);
  showNotifications = signal(false);
  showUserMenu = signal(false);
  notifications = signal<NotificationItem[]>([]);
  searchSuggestions = signal<{id: string, text: string}[]>([]);
  
  private destroy$ = new Subject<void>();
  private elementRef = inject(ElementRef);

  // Computed values
  unreadNotifications = computed(() => 
    this.notifications().filter(n => !n.read).length
  );

  defaultBadgeText = computed(() => {
    const typeMap: Record<OfferBadgeType, string> = {
      'limited': 'Limited Time',
      'premium': 'Premium',
      'new': 'New Feature',
      'hot': 'Hot Deal',
      'sale': 'On Sale'
    };
    return typeMap[this.badgeType()];
  });

  getBadgeVariant = computed((): DsBadgeVariant => {
    const variantMap: Record<OfferBadgeType, DsBadgeVariant> = {
      'premium': 'purple-strong',
      'limited': 'red-strong',
      'new': 'green-strong',
      'hot': 'orange-strong',
      'sale': 'blue-strong'
    };
    return variantMap[this.badgeType()];
  });

  ngOnInit() {
    this.initializeMockData();
    this.setupAutoRefresh();
    this.setupClickOutsideHandlers();
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  private initializeMockData() {
    // Mock notifications
    this.notifications.set([
      {
        id: '1',
        title: 'System Update',
        message: 'New dashboard features are now available',
        timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
        read: false,
        type: 'info'
      },
      {
        id: '2',
        title: 'Payment Processed',
        message: 'Your monthly subscription has been renewed',
        timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
        read: true,
        type: 'success'
      },
      {
        id: '3',
        title: 'Storage Warning',
        message: 'You are running low on storage space',
        timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago
        read: false,
        type: 'warning'
      }
    ]);

    // Mock search suggestions
    this.searchSuggestions.set([
      { id: '1', text: 'Analytics Dashboard' },
      { id: '2', text: 'User Management' },
      { id: '3', text: 'Settings & Preferences' },
      { id: '4', text: 'Reports & Export' }
    ]);
  }

  private setupAutoRefresh() {
    // Refresh notifications every 30 seconds
    interval(30000)
      .pipe(takeUntil(this.destroy$))
      .subscribe(() => {
        // In a real app, this would fetch new notifications
        console.log('Refreshing notifications...');
      });
  }

  private setupClickOutsideHandlers() {
    document.addEventListener('click', this.handleClickOutside.bind(this));
  }

  private handleClickOutside(event: Event) {
    const target = event.target as HTMLElement;
    if (!this.elementRef.nativeElement.contains(target)) {
      this.showNotifications.set(false);
      this.showUserMenu.set(false);
      this.searchFocused.set(false);
    }
  }

  // Badge Methods

  dismissBadge() {
    this.badgeDismissed.emit();
  }

  hasIconSlot(): boolean {
    return !!this.elementRef.nativeElement.querySelector('[slot=start]');
  }

  hasContent(): boolean {
    const textContent = this.elementRef.nativeElement
      .querySelector('.ds-badge-text')
      ?.textContent?.trim();
    return !!textContent;
  }

  // Search Methods
  performSearch() {
    if (this.searchQuery().trim()) {
      this.searchPerformed.emit(this.searchQuery());
    }
  }

  clearSearch() {
    this.searchQuery.set('');
  }

  selectSuggestion(suggestion: {id: string, text: string}) {
    this.searchQuery.set(suggestion.text);
    this.searchFocused.set(false);
    this.performSearch();
  }

  // Notification Methods
  toggleNotifications() {
    this.showNotifications.update(show => !show);
    this.showUserMenu.set(false);
  }

  markAllNotificationsRead() {
    this.notifications.update(notifications =>
      notifications.map(n => ({ ...n, read: true }))
    );
  }

  dismissNotification(id: string) {
    this.notifications.update(notifications =>
      notifications.filter(n => n.id !== id)
    );
  }

  formatTime(timestamp: Date): string {
    const now = new Date();
    const diff = now.getTime() - timestamp.getTime();
    const minutes = Math.floor(diff / (1000 * 60));
    const hours = Math.floor(diff / (1000 * 60 * 60));
    const days = Math.floor(diff / (1000 * 60 * 60 * 24));

    if (minutes < 1) return 'Just now';
    if (minutes < 60) return `${minutes}m ago`;
    if (hours < 24) return `${hours}h ago`;
    return `${days}d ago`;
  }

  // User Menu Methods
  toggleUserMenu() {
    this.showUserMenu.update(show => !show);
    this.showNotifications.set(false);
  }

  getUserInitials(): string {
    const name = this.userProfile()?.name || 'User';
    return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
  }

  navigateToProfile() {
    this.userActionClicked.emit('profile');
    this.showUserMenu.set(false);
  }

  navigateToPreferences() {
    this.userActionClicked.emit('preferences');
    this.showUserMenu.set(false);
  }

  toggleTheme() {
    const newTheme = !this.darkMode();
    this.themeToggled.emit(newTheme);
    this.showUserMenu.set(false);
  }

  logout() {
    this.userActionClicked.emit('logout');
    this.showUserMenu.set(false);
  }
} 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/design-system/ui/segmented-control/src/segmented-control.component.ts:
--------------------------------------------------------------------------------

```typescript
import { FocusMonitor } from '@angular/cdk/a11y';
import { NgTemplateOutlet } from '@angular/common';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  NgZone,
  OnDestroy,
  Renderer2,
  ViewEncapsulation,
  WritableSignal,
  booleanAttribute,
  contentChildren,
  effect,
  inject,
  input,
  model,
  signal,
  untracked,
  viewChild,
  viewChildren,
} from '@angular/core';

import { SEGMENTED_CONTROL_OPTIONS_TOKEN } from './segmented-control.token';
import { DsSegmentedOption } from './segmented-option.component';

@Component({
  selector: 'ds-segmented-control',
  templateUrl: './segmented-control.component.html',
  host: {
    class: `ds-segmented-control`,
  },
  imports: [NgTemplateOutlet],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DsSegmentedControl implements AfterViewInit, OnDestroy {
  private controlOptions = inject(SEGMENTED_CONTROL_OPTIONS_TOKEN);
  private renderer = inject(Renderer2);
  private ngZone = inject(NgZone);
  private focusMonitor = inject(FocusMonitor);

  readonly activeOption = model('');
  readonly fullWidth = input(this.controlOptions.fullWidth, {
    transform: booleanAttribute,
  });
  readonly roleType = input<'radiogroup' | 'tablist'>('tablist');
  readonly twoLineTruncation = input(false, { transform: booleanAttribute });
  readonly inverse = model<boolean>(false);

  protected readonly scContainer =
    viewChild.required<ElementRef<HTMLDivElement>>('scContainer');
  protected readonly segmentedOptions = contentChildren(DsSegmentedOption);
  protected readonly tabLabels =
    viewChildren<ElementRef<HTMLDivElement>>('tabOption');

  protected isReady = signal(false);

  readonly selectedOption: WritableSignal<DsSegmentedOption | null> =
    signal<DsSegmentedOption | null>(this.segmentedOptions()[0] ?? null);
  readonly focusedOption: WritableSignal<DsSegmentedOption | null> =
    signal<DsSegmentedOption | null>(null);
  readonly focusVisibleOption: WritableSignal<DsSegmentedOption | null> =
    signal<DsSegmentedOption | null>(null);
  private readonly resizeObserver = new ResizeObserver((entries) => {
    for (const entry of entries) {
      this._setIndicatorCSSVars(entry.target as HTMLDivElement);
    }
  });

  constructor() {
    effect(() => {
      const isReady = this.isReady();
      const options = this.segmentedOptions();
      const activeOption = this.activeOption();

      // activate the option that comes from the input
      untracked(() => {
        if (isReady) {
          const selectedOption = this.selectedOption();
          if (selectedOption) {
            this._setHighlightWidthAndXPost(selectedOption);
          }

          if (options.length === 0) {
            throw new Error('Please provide segmented options!');
          }
          this.selectOption(activeOption);
        }
      });
    });

    effect(() => {
      const isReady = this.isReady();

      untracked(() => {
        if (isReady) {
          const selectedOption = this.selectedOption();
          if (selectedOption) {
            this._setHighlightWidthAndXPost(selectedOption);
          }
        }
      });
    });
  }

  ngAfterViewInit(): void {
    this.tabLabels().forEach((option, index) => {
      this.focusMonitor
        .monitor(option.nativeElement, true)
        .subscribe((focusOrigin) => {
          const isFocused =
            focusOrigin === 'keyboard' || focusOrigin === 'program';
          if (isFocused) {
            this.focusedOption.set(this.segmentedOptions()[index] ?? null);
            this.focusVisibleOption.set(this.segmentedOptions()[index] ?? null);
          }
        });
    });

    // we don't want to show the initial animation, but only the subsequent ones
    this.ngZone.runOutsideAngular(() =>
      setTimeout(() => this.isReady.set(true)),
    );
    this.selectOption(this.activeOption());
  }

  /**
   * The method which will update the `selected` signal in `ds-segment-option` based on the selected name.
   * @param name Name of the selected option
   * @param event
   */
  selectOption(name: string, event?: Event): void {
    if (
      ((event &&
        this.activeOption() === name &&
        this.selectedOption()?.name() === name) ||
        name === undefined) &&
      this.activeOption() != null
    ) {
      return; // do nothing if the same option is clicked again
    }

    const option = this.segmentedOptions().find((x) => x.name() === name);
    if (option) {
      this.selectedOption.set(option);

      if (this.isReady()) {
        this._setHighlightWidthAndXPost(option);
      }
      this.activeOption.set(name);
    } else {
      // if no option can be found, we select the first one by default
      this.selectFirstOption();
    }
  }

  /**
   * Select first segment option. This is useful when the activeOption is not provided.
   * @private
   */
  private selectFirstOption() {
    const options = this.segmentedOptions();

    if (options.length === 0) {
      return;
    }

    const firstOption = options[0] ?? null;

    this.selectedOption.set(firstOption);
    this.activeOption.set(firstOption?.name() ?? '');
  }

  /**
   * Will get the active segment position in order to show the indicator on the background.
   * @private
   */
  private _setHighlightWidthAndXPost(option: DsSegmentedOption) {
    for (const item of this.tabLabels()) {
      this.resizeObserver.unobserve(item.nativeElement);
    }

    const element = this.tabLabels().find(
      (item) => item.nativeElement.id === `ds-segment-item-${option.name()}`,
    );
    if (element) {
      this._setIndicatorCSSVars(element.nativeElement);
      this.resizeObserver.observe(element.nativeElement);
    }
  }

  /**
   * Will set the active element indicator related css variables
   * @private
   */
  private _setIndicatorCSSVars(element: HTMLDivElement) {
    const { offsetWidth, offsetLeft } = element;
    // We update the DOM directly, so we don't have to go through Angular Change Detection
    this.renderer.setProperty(
      this.scContainer().nativeElement,
      'style',
      `--ds-sc-highlight-width: ${offsetWidth}px; --ds-sc-highlight-x-pos: ${offsetLeft}px`,
    );
  }

  onKeydown(event: KeyboardEvent) {
    const { key } = event;
    const options = this.segmentedOptions();
    const currentIndex = options.findIndex((option) => option.focused());
    let newIndex: number | undefined;

    if (key === 'ArrowRight') {
      newIndex = (currentIndex + 1) % options.length;
    } else if (key === 'ArrowLeft') {
      newIndex = (currentIndex - 1 + options.length) % options.length;
    } else if (
      (key === ' ' || key === 'Enter') &&
      currentIndex !== -1 &&
      options[currentIndex]
    ) {
      this.selectOption(options[currentIndex].name(), event);
    }

    if (newIndex !== undefined) {
      event.preventDefault();
      const newOption = options[newIndex];
      if (newOption) {
        this.focusOption(newOption, newIndex);
      }
    }
  }

  private focusOption(option: DsSegmentedOption, index: number) {
    const focusOption = this.tabLabels()[index];
    if (focusOption) {
      this.focusedOption.set(option);
      this.focusVisibleOption.set(option);
      this.focusMonitor.focusVia(focusOption.nativeElement, 'keyboard');
    }
  }

  ngOnDestroy() {
    if (this.tabLabels()) {
      this.tabLabels().forEach((option) =>
        this.focusMonitor.stopMonitoring(option),
      );
    }

    this.resizeObserver.disconnect();
  }
}

```

--------------------------------------------------------------------------------
/packages/shared/styles-ast-utils/ai/EXAMPLES.md:
--------------------------------------------------------------------------------

```markdown
# Examples

## 1 — Basic stylesheet parsing

> Parse CSS content and access the AST structure.

```ts
import { parseStylesheet } from '@push-based/styles-ast-utils';

const cssContent = `
.btn {
  color: red;
  background: blue;
}

.card {
  padding: 1rem;
}
`;

const result = parseStylesheet(cssContent, 'styles.css');
const root = result.root;

console.log(`Parsed ${root.nodes.length} top-level nodes`); // → 'Parsed 2 top-level nodes'
console.log(root.nodes[0].type); // → 'rule'
console.log(root.nodes[0].selector); // → '.btn'
```

---

## 2 — Using the visitor pattern

> Traverse CSS AST using the visitor pattern to collect information.

```ts
import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';

const cssContent = `
/* Main styles */
.btn {
  color: red;
  font-size: 14px;
}

@media (max-width: 768px) {
  .btn {
    font-size: 12px;
  }
}
`;

const result = parseStylesheet(cssContent, 'styles.css');
const selectors: string[] = [];
const properties: string[] = [];
const mediaQueries: string[] = [];

const visitor = {
  visitRule: (rule) => {
    selectors.push(rule.selector);
  },
  visitDecl: (decl) => {
    properties.push(`${decl.prop}: ${decl.value}`);
  },
  visitAtRule: (atRule) => {
    if (atRule.name === 'media') {
      mediaQueries.push(atRule.params);
    }
  },
  visitComment: (comment) => {
    console.log(`Found comment: ${comment.text}`);
  },
};

visitEachChild(result.root, visitor);

console.log('Selectors:', selectors);
// → ['Selectors:', ['.btn', '.btn']]

console.log('Properties:', properties);
// → ['Properties:', ['color: red', 'font-size: 14px', 'font-size: 12px']]

console.log('Media queries:', mediaQueries);
// → ['Media queries:', ['(max-width: 768px)']]
```

---

## 3 — Converting AST nodes to source locations

> Convert CSS rules to linkable source locations for error reporting.

```ts
import {
  parseStylesheet,
  styleAstRuleToSource,
} from '@push-based/styles-ast-utils';
import { Rule } from 'postcss';

const cssContent = `
.header {
  background: linear-gradient(to right, #ff0000, #00ff00);
  padding: 2rem;
}

.footer {
  margin-top: auto;
}
`;

const result = parseStylesheet(cssContent, 'components/layout.css');
const rules = result.root.nodes.filter(
  (node) => node.type === 'rule'
) as Rule[];

rules.forEach((rule, index) => {
  const source = styleAstRuleToSource(rule);
  console.log(`Rule ${index + 1}:`, {
    selector: rule.selector,
    location: `${source.file}:${source.position.startLine}:${source.position.startColumn}`,
    span: `${source.position.startLine}-${source.position.endLine}`,
  });
});

// Output:
// Rule 1: {
//   selector: '.header',
//   location: 'components/layout.css:2:1',
//   span: '2-4'
// }
// Rule 2: {
//   selector: '.footer',
//   location: 'components/layout.css:6:1',
//   span: '6-8'
// }
```

---

## 4 — Handling inline styles with line offset

> Parse inline styles and adjust line numbers for accurate source mapping.

```ts
import {
  parseStylesheet,
  styleAstRuleToSource,
} from '@push-based/styles-ast-utils';
import { Rule } from 'postcss';

// Simulate inline styles starting at line 15 in a component file
const inlineStyles = `.component-btn { color: blue; }`;
const componentFilePath = 'src/app/button.component.ts';
const styleStartLine = 15; // 0-indexed line where styles begin

const result = parseStylesheet(inlineStyles, componentFilePath);
const rule = result.root.nodes[0] as Rule;

// Convert with line offset
const source = styleAstRuleToSource(rule, styleStartLine);

console.log('Inline style location:', {
  file: source.file,
  line: source.position.startLine, // → 16 (adjusted for file position)
  selector: rule.selector, // → '.component-btn'
});
```

---

## 5 — Recursive node traversal

> Use recursive traversal to process nested CSS structures.

```ts
import {
  parseStylesheet,
  visitEachStyleNode,
} from '@push-based/styles-ast-utils';

const cssContent = `
.container {
  display: flex;
  
  .item {
    flex: 1;
    
    &:hover {
      opacity: 0.8;
    }
  }
}

@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
}
`;

const result = parseStylesheet(cssContent, 'nested-styles.scss');
let depth = 0;

const visitor = {
  visitRule: (rule) => {
    console.log(`${'  '.repeat(depth)}Rule: ${rule.selector}`);
    depth++;
    if (rule.nodes) {
      visitEachStyleNode(rule.nodes, visitor);
    }
    depth--;
  },
  visitAtRule: (atRule) => {
    console.log(`${'  '.repeat(depth)}@${atRule.name}: ${atRule.params}`);
    depth++;
    if (atRule.nodes) {
      visitEachStyleNode(atRule.nodes, visitor);
    }
    depth--;
  },
  visitDecl: (decl) => {
    console.log(`${'  '.repeat(depth)}${decl.prop}: ${decl.value}`);
  },
};

visitEachStyleNode(result.root.nodes, visitor);

// Output shows nested structure:
// Rule: .container
//   display: flex
//   Rule: .item
//     flex: 1
//     Rule: &:hover
//       opacity: 0.8
// @supports: (display: grid)
//   Rule: .grid-container
//     display: grid
//     grid-template-columns: repeat(3, 1fr)
```

---

## 6 — Safe parsing with malformed CSS

> Handle malformed CSS gracefully using the safe parser.

```ts
import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';

// Malformed CSS with missing closing braces and invalid syntax
const malformedCss = `
.btn {
  color: red
  background: blue;
  /* missing closing brace */

.card 
  padding: 1rem;
  margin: invalid-value;
}

/* unclosed comment
.footer {
  text-align: center;
`;

const result = parseStylesheet(malformedCss, 'malformed.css');
const issues: string[] = [];

const visitor = {
  visitRule: (rule) => {
    console.log(`Successfully parsed rule: ${rule.selector}`);
  },
  visitDecl: (decl) => {
    if (!decl.value || decl.value.includes('invalid')) {
      issues.push(`Invalid declaration: ${decl.prop}: ${decl.value}`);
    }
  },
};

visitEachChild(result.root, visitor);

console.log(`Parsed ${result.root.nodes.length} nodes despite malformed CSS`);
console.log('Issues found:', issues);

// The safe parser recovers from errors and continues parsing
// Output:
// Successfully parsed rule: .btn
// Successfully parsed rule: .card
// Successfully parsed rule: .footer
// Parsed 3 nodes despite malformed CSS
// Issues found: ['Invalid declaration: margin: invalid-value']
```

---

## 7 — Collecting CSS class names

> Extract all CSS class selectors from a stylesheet.

```ts
import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';

const cssContent = `
.btn, .button {
  padding: 0.5rem 1rem;
}

.btn-primary {
  background: blue;
}

.card .header {
  font-weight: bold;
}

#main .sidebar .nav-item {
  list-style: none;
}

[data-theme="dark"] .btn {
  color: white;
}
`;

const result = parseStylesheet(cssContent, 'components.css');
const classNames = new Set<string>();

const visitor = {
  visitRule: (rule) => {
    // Extract class names from selectors using regex
    const matches = rule.selector.match(/\.([a-zA-Z0-9_-]+)/g);
    if (matches) {
      matches.forEach((match) => {
        classNames.add(match.substring(1)); // Remove the dot
      });
    }
  },
};

visitEachChild(result.root, visitor);

console.log('Found CSS classes:', Array.from(classNames).sort());
// → ['Found CSS classes:', ['btn', 'btn-primary', 'button', 'card', 'header', 'nav-item', 'sidebar']]
```

These examples demonstrate the comprehensive CSS parsing and analysis capabilities of the `@push-based/styles-ast-utils` library for various stylesheet processing scenarios.

```

--------------------------------------------------------------------------------
/packages/shared/ds-component-coverage/src/lib/runner/audits/ds-coverage/class-usage.visitor.ts:
--------------------------------------------------------------------------------

```typescript
import type {
  ASTWithSource,
  TmplAstBoundAttribute,
  TmplAstBoundEvent,
  TmplAstBoundText,
  TmplAstContent,
  TmplAstDeferredBlock,
  TmplAstDeferredBlockError,
  TmplAstDeferredBlockLoading,
  TmplAstDeferredBlockPlaceholder,
  TmplAstDeferredTrigger,
  TmplAstElement,
  TmplAstForLoopBlock,
  TmplAstForLoopBlockEmpty,
  TmplAstIcu,
  TmplAstIfBlock,
  TmplAstIfBlockBranch,
  TmplAstLetDeclaration,
  TmplAstReference,
  TmplAstSwitchBlock,
  TmplAstSwitchBlockCase,
  TmplAstTemplate,
  TmplAstText,
  TmplAstTextAttribute,
  TmplAstUnknownBlock,
  TmplAstVariable,
  TmplAstVisitor,
} from '@angular/compiler' with { 'resolution-mode': 'import' };
import { Issue } from '@code-pushup/models';
import { DiagnosticsAware } from '@push-based/models';

import {
  tmplAstElementToSource,
  parseClassNames,
  extractClassNamesFromNgClassAST,
} from '@push-based/angular-ast-utils';

import {
  EXTERNAL_ASSET_ICON,
  INLINE_ASSET_ICON,
  TEMPLATE_ASSET_ICON,
} from './constants.js';

import { ComponentReplacement } from './schema.js';

function generateClassUsageMessage({
  element,
  className,
  attribute,
  componentName = 'a DS component',
  docsUrl,
}: {
  element: TmplAstElement;
  className: string;
  attribute: string;
} & Pick<ComponentReplacement, 'docsUrl' | 'componentName'>): string {
  const elementName = element.name;
  const isInline = element.sourceSpan.start.file.url.match(/\.ts$/) != null;
  const iconString = `${
    isInline ? INLINE_ASSET_ICON : EXTERNAL_ASSET_ICON
  }${TEMPLATE_ASSET_ICON} `;
  const docsLink = docsUrl
    ? ` <a href="${docsUrl}" target="_blank">Learn more</a>.`
    : '';
  return `${iconString} Element <code>${elementName}</code> in attribute <code>${attribute}</code> uses deprecated class <code>${className}</code>. Use <code>${componentName}</code> instead.${docsLink}`;
}

export class ClassUsageVisitor
  implements TmplAstVisitor<void>, DiagnosticsAware
{
  private issues: Issue[] = [];
  private currentElement: TmplAstElement | null = null;

  constructor(
    private readonly componentReplacement: ComponentReplacement,
    private readonly startLine = 0,
  ) {}

  getIssues(): Issue[] {
    return this.issues;
  }

  clear(): void {
    this.issues = [];
  }

  visitElement(element: TmplAstElement): void {
    this.currentElement = element;

    element.attributes.forEach((attr) => attr.visit(this)); // Check `class="..."`
    element.inputs.forEach((input) => input.visit(this)); // Check `[class.foo]`, `[ngClass]`

    element.children.forEach((child) => child.visit(this));

    this.currentElement = null;
  }

  visitTextAttribute(attribute: TmplAstTextAttribute): void {
    const { deprecatedCssClasses, ...compRepl } = this.componentReplacement;
    if (attribute.name === 'class' && this.currentElement) {
      const classNames = parseClassNames(attribute.value);
      const deprecatedClassesFound = classNames.filter((cn) =>
        deprecatedCssClasses.includes(cn),
      );

      if (deprecatedClassesFound.length > 0) {
        const isInline =
          attribute.sourceSpan.start.file.url.match(/\.ts$/) != null;
        const startLine = isInline ? this.startLine : 0;

        this.issues.push({
          severity: 'error',
          message: generateClassUsageMessage({
            ...compRepl,
            element: this.currentElement,
            className: deprecatedClassesFound.join(', '),
            attribute: `${attribute.name}`,
          }),
          source: tmplAstElementToSource(this.currentElement, startLine),
        });
      }
    }
  }

  visitBoundAttribute(attribute: TmplAstBoundAttribute): void {
    if (!this.currentElement) return;

    const { deprecatedCssClasses, ...compRepl } = this.componentReplacement;

    // Check `[class.foo]`
    // BindingType.Class === 2
    if (attribute.type === 2 && deprecatedCssClasses.includes(attribute.name)) {
      this.issues.push({
        severity: 'error', // @TODO if we consider transformations this needs to be dynamic
        message: generateClassUsageMessage({
          element: this.currentElement,
          className: attribute.name,
          attribute: '[class.*]',
          componentName: this.componentReplacement.componentName,
          docsUrl: this.componentReplacement.docsUrl,
        }),
        source: tmplAstElementToSource(this.currentElement, this.startLine),
      });
    }

    // Handle class="..." with interpolation and [ngClass]
    if (attribute.name === 'class' || attribute.name === 'ngClass') {
      const value: ASTWithSource = attribute.value as ASTWithSource;

      // Use AST-based parsing for both [class] and [ngClass] to avoid false positives
      // For simple string literals, the AST parsing will still work correctly
      const foundClassNames = extractClassNamesFromNgClassAST(
        value.ast,
        deprecatedCssClasses,
      );

      // Create single issue for all found deprecated classes
      if (foundClassNames.length > 0 && this.currentElement) {
        this.issues.push({
          severity: 'error', // @TODO if we consider transformations this needs to be dynamic
          message: generateClassUsageMessage({
            ...compRepl,
            element: this.currentElement,
            className: foundClassNames.join(', '),
            attribute:
              attribute.name === 'ngClass'
                ? `[${attribute.name}]`
                : attribute.name,
          }),
          source: tmplAstElementToSource(this.currentElement, this.startLine),
        });
      }
    }
  }

  visitTemplate(template: TmplAstTemplate): void {
    template.children.forEach((child) => child.visit(this));
  }

  visitContent(content: TmplAstContent): void {
    content.children.forEach((child) => child.visit(this));
  }

  visitForLoopBlock(block: TmplAstForLoopBlock): void {
    block.children.forEach((child) => child.visit(this));
    block.empty?.visit(this);
  }

  visitForLoopBlockEmpty(block: TmplAstForLoopBlockEmpty): void {
    block.children.forEach((child) => child.visit(this));
  }

  visitIfBlock(block: TmplAstIfBlock): void {
    block.branches.forEach((branch) => branch.visit(this));
  }

  visitIfBlockBranch(block: TmplAstIfBlockBranch): void {
    block.children.forEach((child) => child.visit(this));
  }

  visitSwitchBlock(block: TmplAstSwitchBlock): void {
    block.cases.forEach((caseBlock) => caseBlock.visit(this));
  }

  visitSwitchBlockCase(block: TmplAstSwitchBlockCase): void {
    block.children.forEach((child) => child.visit(this));
  }

  visitDeferredBlock(deferred: TmplAstDeferredBlock): void {
    deferred.visitAll(this);
  }

  visitDeferredBlockError(block: TmplAstDeferredBlockError): void {
    block.children.forEach((child) => child.visit(this));
  }

  visitDeferredBlockLoading(block: TmplAstDeferredBlockLoading): void {
    block.children.forEach((child) => child.visit(this));
  }

  visitDeferredBlockPlaceholder(block: TmplAstDeferredBlockPlaceholder): void {
    block.children.forEach((child) => child.visit(this));
  }

  // -- No-op Methods --
  /* eslint-disable @typescript-eslint/no-empty-function */
  visitVariable(_variable: TmplAstVariable): void {}

  visitReference(_reference: TmplAstReference): void {}

  visitText(_text: TmplAstText): void {}

  visitBoundText(_text: TmplAstBoundText): void {}

  visitIcu(_icu: TmplAstIcu): void {}

  visitBoundEvent(_event: TmplAstBoundEvent): void {}

  visitUnknownBlock(_block: TmplAstUnknownBlock): void {}

  visitDeferredTrigger(_trigger: TmplAstDeferredTrigger): void {}

  visitLetDeclaration(_decl: TmplAstLetDeclaration): void {}
  /* eslint-enable @typescript-eslint/no-empty-function */
}

```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/design-system/storybook-host-app/src/components/modal/demo-cdk-dialog-cmp.component.ts:
--------------------------------------------------------------------------------

```typescript
import {
  DIALOG_DATA,
  Dialog,
  DialogModule,
  DialogRef,
} from '@angular/cdk/dialog';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  Inject,
  Renderer2,
  ViewChild,
  booleanAttribute,
  inject,
  input,
} from '@angular/core';

import { DemoCloseIconComponent } from '@design-system/storybook-demo-cmp-lib';
import { DsButton } from '@frontend/ui/button';
import { DsButtonIcon } from '@frontend/ui/button-icon';
import {
  DsModal,
  DsModalContent,
  DsModalHeader,
  DsModalHeaderDrag,
  DsModalHeaderVariant,
  DsModalVariant,
} from '@frontend/ui/modal';

@Component({
  selector: 'ds-demo-cdk-dialog-cmp',
  imports: [
    DialogModule,
    DsButton,
    DsModalHeader,
    DsButtonIcon,
    DemoCloseIconComponent,
    DsModal,
    DsModalContent,
    DsModalHeaderDrag,
  ],
  standalone: true,
  template: `
    <ds-modal
      [inverse]="data.inverse"
      [bottomSheet]="data.bottomSheet"
      [variant]="data.variant"
    >
      <ds-modal-header [variant]="data.headerVariant">
        <ds-modal-header-drag #dragHandle />
        <button slot="end" ds-button-icon size="small" (click)="close()">
          <ds-demo-close-icon />
        </button>
      </ds-modal-header>
      <!-- eslint-disable-next-line @angular-eslint/template/no-inline-styles -->
      <div style="height: 400px; width: 400px; overflow: auto">
        <ds-modal-content>
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam,
          ducimus, sequi! Ab consequatur earum expedita fugit illo illum in
          maiores nihil nostrum officiis ratione repellendus temporibus, vel!
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam,
          ducimus, sequi! Ab consequatur earum expedita fugit illo illum in
          maiores nihil nostrum officiis ratione repellendus temporibus, vel! Lo
          rem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam,
          ducimus, sequi! Ab consequatur earum expedita fugit illo illum in
          maiores nihil nostrum officiis ratione repellendus temporibus, vel!
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam,
          ducimus, sequi! Ab consequatur earum expedita fugit illo illum in
          maiores nihil nostrum officiis ratione repellendus temporibus, vel!
          Lorem ipsum Lorem ipsum dolor sit amet, consectetur adipisicing elit.
          Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit illo
          illum in maiores nihil nostrum officiis ratione repellendus
          temporibus, vel! dolor sit amet, consectetur adipisicing elit.
          Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit illo
          illum in maiores nihil nostrum officiis ratione repellendus
          temporibus, vel! Lorem ipsum dolor sit amet, consectetur adipisicing
          elit. Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit
          illo illum in maiores nihil nostrum officiis ratione repellendus
          temporibus, vel! Lorem ipsum dolor sit amet, consectetur adipisicing
          elit. Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit
          illo illum in maiores nihil nostrum officiis ratione repellendus
          temporibus, vel!
          <br />
          <br />
          <b>Lorem ipsum dolor sit amet</b>, consectetur adipisicing elit.
          Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit illo
          illum in maiores nihil nostrum officiis ratione repellendus
          temporibus, vel!
          <br />
          <br />
          <div class="footer-buttons">
            <button
              ds-button
              [inverse]="data.inverse"
              kind="secondary"
              variant="outline"
              (click)="close()"
            >
              Outline Button
            </button>
            <button
              ds-button
              [inverse]="data.inverse"
              kind="primary"
              variant="filled"
              (click)="close()"
            >
              Filled Button
            </button>
          </div>
        </ds-modal-content>
      </div>
    </ds-modal>
  `,
  styles: [
    `
      ds-modal {
        width: 400px;
        min-height: 300px;
      }

      /* Bottom Sheet styles */
      :host-context(.ds-bottom-sheet-panel) ds-modal {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        margin-left: auto;
        margin-right: auto;
      }

      .footer-buttons {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 10px;
      }
    `,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DemoCdkModalCmp implements AfterViewInit {
  @ViewChild('dragHandle', { static: true, read: ElementRef })
  dragHandle!: ElementRef<HTMLElement>;
  @ViewChild(DsModal, { static: true, read: ElementRef })
  modalElementRef!: ElementRef<HTMLElement>;

  private renderer = inject(Renderer2);
  private isDragging = false;
  private startX = 0;
  private startY = 0;
  private initialLeft = 0;
  private initialTop = 0;
  private moveListener?: () => void;
  private upListener?: () => void;

  constructor(
    private dialogRef: DialogRef,
    @Inject(DIALOG_DATA)
    public data: { headerVariant: string; inverse: boolean; variant: string },
  ) {}

  ngAfterViewInit() {
    if (this.dragHandle) {
      this.renderer.listen(
        this.dragHandle.nativeElement,
        'mousedown',
        (event: MouseEvent) => this.startDrag(event),
      );
    }
  }

  startDrag(event: MouseEvent) {
    event.preventDefault();
    this.isDragging = true;

    const dialogEl = this.modalElementRef.nativeElement;

    const rect = dialogEl.getBoundingClientRect();
    this.startX = event.clientX;
    this.startY = event.clientY;
    this.initialLeft = rect.left;
    this.initialTop = rect.top;

    this.moveListener = this.renderer.listen('document', 'mousemove', (e) =>
      this.onDragMove(e, dialogEl),
    );
    this.upListener = this.renderer.listen('document', 'mouseup', () =>
      this.endDrag(),
    );
  }

  private onDragMove(event: MouseEvent, dialogEl: HTMLElement) {
    if (!this.isDragging) return;

    const deltaX = event.clientX - this.startX;
    const deltaY = event.clientY - this.startY;

    const left = this.initialLeft + deltaX;
    const top = this.initialTop + deltaY;

    // Apply updated position
    this.renderer.setStyle(dialogEl, 'position', 'fixed');
    this.renderer.setStyle(dialogEl, 'left', `${left}px`);
    this.renderer.setStyle(dialogEl, 'top', `${top}px`);
    this.renderer.setStyle(dialogEl, 'margin', `0`);
  }

  private endDrag() {
    this.isDragging = false;
    this.moveListener?.();
    this.upListener?.();
  }

  close() {
    this.dialogRef.close();
  }
}

@Component({
  selector: 'ds-demo-cdk-dialog-container',
  imports: [DialogModule, DsButton],
  standalone: true,
  template: `
    <button ds-button (click)="openDialog()">Open with CDK Dialog</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DemoCdkModalContainer {
  dialog = inject(Dialog);

  headerVariant = input<DsModalHeaderVariant>();
  inverse = input(false, { transform: booleanAttribute });
  variant = input<DsModalVariant>();
  bottomSheetInput = input(false, { transform: booleanAttribute });

  openDialog() {
    const isBottomSheet = this.bottomSheetInput();
    this.dialog.open(DemoCdkModalCmp, {
      panelClass: isBottomSheet ? 'ds-bottom-sheet-panel' : 'ds-dialog-panel',
      data: {
        headerVariant: this.headerVariant(),
        inverse: this.inverse(),
        variant: this.variant(),
        bottomSheet: isBottomSheet,
      },
    });
  }
}

```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-usage-graph/utils/component-usage-graph-builder.ts:
--------------------------------------------------------------------------------

```typescript
import * as path from 'path';
import { toUnixPath } from '@code-pushup/utils';
import { findAllFiles } from '@push-based/utils';
import {
  BuildComponentUsageGraphOptions,
  ComponentUsageGraphResult,
  FileInfo,
} from '../models/types.js';
import {
  DEPENDENCY_ANALYSIS_CONFIG,
  clearComponentImportRegexCache,
} from '../models/config.js';
import {
  analyzeFileWithUnifiedOptimization,
  extractComponentImportsUnified,
} from './unified-ast-analyzer.js';
import { resolveCrossPlatformPathAndValidateWithContext } from '../../shared/utils/cross-platform-path.js';

const BATCH_SIZE = 50; // Process files in batches of 50

export async function buildComponentUsageGraph(
  options: BuildComponentUsageGraphOptions,
): Promise<ComponentUsageGraphResult> {
  const startTime = performance.now();

  const targetPath = resolveCrossPlatformPathAndValidateWithContext(
    options.cwd,
    options.directory,
    options.workspaceRoot,
  );

  const files: Record<string, FileInfo> = {};

  // Phase 1: Directory scanning
  const scanStartTime = performance.now();
  const allFiles = await scanDirectoryWithUtils(targetPath);
  const scanTime = performance.now() - scanStartTime;

  // Phase 2: File analysis with unified AST parsing
  const analysisStartTime = performance.now();
  await processFilesInParallel(allFiles, targetPath, files);
  const analysisTime = performance.now() - analysisStartTime;

  // Phase 3: Reverse dependency analysis
  const reverseDepsStartTime = performance.now();
  await addReverseDependenciesOptimized(files, targetPath);
  const reverseDepsTime = performance.now() - reverseDepsStartTime;

  const totalTime = performance.now() - startTime;

  // Log performance metrics
  console.log(`🚀 Unified AST Analysis Performance:`);
  console.log(
    `  📁 Directory scan: ${scanTime.toFixed(2)}ms (${((scanTime / totalTime) * 100).toFixed(1)}%)`,
  );
  console.log(
    `  🔍 File analysis: ${analysisTime.toFixed(2)}ms (${((analysisTime / totalTime) * 100).toFixed(1)}%)`,
  );
  console.log(
    `  🔗 Reverse deps: ${reverseDepsTime.toFixed(2)}ms (${((reverseDepsTime / totalTime) * 100).toFixed(1)}%)`,
  );
  console.log(
    `  ⚡ Total time: ${totalTime.toFixed(2)}ms for ${Object.keys(files).length} files`,
  );
  console.log(
    `  📊 Avg per file: ${(totalTime / Object.keys(files).length).toFixed(2)}ms`,
  );

  return files;
}

async function scanDirectoryWithUtils(dirPath: string): Promise<string[]> {
  const files: string[] = [];
  const { fileExtensions } = DEPENDENCY_ANALYSIS_CONFIG;

  try {
    // Use findAllFiles async generator for better memory efficiency
    for await (const file of findAllFiles(dirPath, (filePath) => {
      const ext = path.extname(filePath);
      return fileExtensions.includes(ext as any);
    })) {
      files.push(toUnixPath(file));
    }
  } catch (ctx) {
    throw new Error(
      `Failed to scan directory ${dirPath}: ${(ctx as Error).message}`,
    );
  }

  return files;
}

async function processFilesInParallel(
  allFiles: string[],
  targetPath: string,
  files: Record<string, FileInfo>,
): Promise<void> {
  // Single pass with slice batching (no extra helpers)
  for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
    const batch = allFiles.slice(i, i + BATCH_SIZE);
    const batchStartTime = performance.now();

    const results = await Promise.all(
      batch.map(async (filePath) => {
        try {
          const relativePath = toUnixPath(path.relative(targetPath, filePath));
          const fileInfo = await analyzeFileWithUnifiedOptimization(
            filePath,
            targetPath,
          );
          return { relativePath, fileInfo } as const;
        } catch (ctx) {
          throw new Error(
            `Failed to analyze file ${filePath}: ${(ctx as Error).message}`,
          );
        }
      }),
    );

    for (const result of results) {
      if (result) {
        files[result.relativePath] = result.fileInfo;
      }
    }

    const batchTime = performance.now() - batchStartTime;
    console.log(
      `  📦 Batch ${Math.ceil((i + batch.length) / BATCH_SIZE)}: ${batch.length} files in ${batchTime.toFixed(2)}ms (${(batchTime / batch.length).toFixed(2)}ms/file)`,
    );
  }
}

async function addReverseDependenciesOptimized(
  files: Record<string, FileInfo>,
  basePath: string,
): Promise<void> {
  // Build component name to file path mapping
  const componentMap = new Map<string, string>();
  const componentNames: string[] = [];

  for (const [filePath, fileInfo] of Object.entries(files)) {
    if (fileInfo.componentName) {
      componentMap.set(fileInfo.componentName, filePath);
      componentNames.push(fileInfo.componentName);
    }
  }

  if (componentNames.length === 0) {
    console.log(`  ℹ️  No components found for reverse dependency analysis`);
    return; // No components to analyze
  }

  console.log(
    `  🔍 Analyzing reverse dependencies for ${componentNames.length} components`,
  );

  // Process files in parallel batches for reverse dependency analysis
  const fileEntries = Object.entries(files);
  const batches = createBatches(fileEntries, BATCH_SIZE);
  let processedBatches = 0;

  for (const batch of batches) {
    const batchStartTime = performance.now();
    const promises = batch.map(
      async ([filePath, fileInfo]: [string, FileInfo]) => {
        const fullPath = path.resolve(basePath, filePath);

        try {
          // Only analyze TypeScript/JavaScript files for component imports
          if (
            fileInfo.type === 'typescript' ||
            fileInfo.type === 'javascript'
          ) {
            // Use unified component import extraction instead of separate file read + AST parsing
            const foundComponents = await extractComponentImportsUnified(
              fullPath,
              componentNames,
            );

            // Collect all reverse dependencies for this file
            const reverseDependencies = [];
            for (const componentName of foundComponents) {
              const componentFilePath = componentMap.get(componentName);
              if (componentFilePath && componentFilePath !== filePath) {
                reverseDependencies.push({
                  componentFilePath,
                  dependency: {
                    path: toUnixPath(filePath),
                    type: 'reverse-dependency' as const,
                    resolved: true,
                    resolvedPath: toUnixPath(filePath),
                    componentName: componentName,
                    sourceFile: filePath,
                  },
                });
              }
            }

            return reverseDependencies;
          }
        } catch (ctx) {
          throw new Error(
            `Failed to analyze reverse dependencies for ${filePath}: ${(ctx as Error).message}`,
          );
        }

        return [];
      },
    );

    const results = await Promise.all(promises);
    const batchTime = performance.now() - batchStartTime;
    processedBatches++;

    // Apply reverse dependencies
    let dependenciesAdded = 0;
    for (const result of results) {
      if (Array.isArray(result)) {
        for (const dependency of result) {
          files[dependency.componentFilePath].dependencies.push(
            dependency.dependency,
          );
          dependenciesAdded++;
        }
      }
    }

    console.log(
      `  🔗 Reverse deps batch ${processedBatches}: ${batch.length} files, ${dependenciesAdded} dependencies in ${batchTime.toFixed(2)}ms`,
    );
  }
}

// Compact batch helper reused by reverse-dependency phase
function createBatches<T>(items: T[], batchSize: number): T[][] {
  const batches: T[][] = [];
  for (let i = 0; i < items.length; i += batchSize) {
    batches.push(items.slice(i, i + batchSize));
  }
  return batches;
}

export function clearAnalysisCache(): void {
  clearComponentImportRegexCache();
}

```

--------------------------------------------------------------------------------
/packages/shared/ds-component-coverage/src/lib/runner/audits/ds-coverage/class-definition.visitor.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, expect, it } from 'vitest';
import { createClassDefinitionVisitor } from './class-definition.visitor';
import postcss from 'postcss';
import { visitEachChild } from '@push-based/styles-ast-utils';

describe('ClassDefinitionVisitor', () => {
  let cssAstVisitor: ReturnType<typeof createClassDefinitionVisitor>;

  it('should find deprecated class in CSS selector', () => {
    const styles = `
                /* This comment is here */
                .btn {
                  color: red;
                }
             `;

    cssAstVisitor = createClassDefinitionVisitor({
      deprecatedCssClasses: ['btn'],
      componentName: 'DsButton',
      docsUrl: 'docs.example.com/DsButton',
    });

    const ast = postcss.parse(styles, { from: 'styles.css' });
    visitEachChild(ast, cssAstVisitor);

    expect(cssAstVisitor.getIssues()).toHaveLength(1);
    const message = cssAstVisitor.getIssues()[0].message;
    expect(message).toContain('btn');
    expect(message).toContain('DsButton');
    expect(cssAstVisitor.getIssues()[0]).toEqual(
      expect.objectContaining({
        severity: 'error',
        source: expect.objectContaining({
          file: 'styles.css',
          position: expect.any(Object),
        }),
      }),
    );
  });

  it('should not find class when it is not deprecated', () => {
    const styles = `
                .safe-class {
                  color: red;
                }

                #btn-1 {
                  color: green;
                }

                button {
                  color: blue;
                }
             `;

    cssAstVisitor = createClassDefinitionVisitor({
      deprecatedCssClasses: ['btn'],
      componentName: 'DsButton',
    });

    const ast = postcss.parse(styles, { from: 'styles.css' });
    visitEachChild(ast, cssAstVisitor);

    expect(cssAstVisitor.getIssues()).toHaveLength(0);
  });

  it('should find deprecated class in complex selector', () => {
    const styles = `
                div > button.btn {
                  color: blue;
                }
             `;

    cssAstVisitor = createClassDefinitionVisitor({
      deprecatedCssClasses: ['btn'],
      componentName: 'DsButton',
    });

    const ast = postcss.parse(styles, { from: 'styles.css' });
    visitEachChild(ast, cssAstVisitor);

    expect(cssAstVisitor.getIssues()).toHaveLength(1);
    const message = cssAstVisitor.getIssues()[0].message;
    expect(message).toContain('btn');
    expect(message).toContain('DsButton');
    expect(cssAstVisitor.getIssues()[0]).toEqual(
      expect.objectContaining({
        severity: 'error',
        source: expect.objectContaining({
          file: 'styles.css',
          position: expect.any(Object),
        }),
      }),
    );
  });

  describe('deduplication', () => {
    it('should deduplicate multiple deprecated classes in same selector', () => {
      const styles = `
                  .btn.btn-primary {
                    color: red;
                  }
               `;

      cssAstVisitor = createClassDefinitionVisitor({
        deprecatedCssClasses: ['btn', 'btn-primary'],
        componentName: 'DsButton',
        docsUrl: 'docs.example.com/DsButton',
      });

      const ast = postcss.parse(styles, { from: 'styles.css' });
      visitEachChild(ast, cssAstVisitor);

      expect(cssAstVisitor.getIssues()).toHaveLength(1);
      const message = cssAstVisitor.getIssues()[0].message;
      expect(message).toContain('btn, btn-primary');
      expect(message).toContain('DsButton');
      expect(cssAstVisitor.getIssues()[0]).toEqual(
        expect.objectContaining({
          severity: 'error',
          source: expect.objectContaining({
            file: 'styles.css',
            position: expect.any(Object),
          }),
        }),
      );
    });

    it('should deduplicate multiple deprecated classes in comma-separated selectors', () => {
      const styles = `
                  .btn, .btn-primary {
                    color: red;
                  }
               `;

      cssAstVisitor = createClassDefinitionVisitor({
        deprecatedCssClasses: ['btn', 'btn-primary'],
        componentName: 'DsButton',
        docsUrl: 'docs.example.com/DsButton',
      });

      const ast = postcss.parse(styles, { from: 'styles.css' });
      visitEachChild(ast, cssAstVisitor);

      expect(cssAstVisitor.getIssues()).toHaveLength(1);
      const message = cssAstVisitor.getIssues()[0].message;
      expect(message).toContain('btn, btn-primary');
      expect(message).toContain('DsButton');
      expect(cssAstVisitor.getIssues()[0]).toEqual(
        expect.objectContaining({
          severity: 'error',
          source: expect.objectContaining({
            file: 'styles.css',
            position: expect.any(Object),
          }),
        }),
      );
    });

    it('should deduplicate three deprecated classes in same selector', () => {
      const styles = `
                  .btn.btn-primary.btn-large {
                    color: red;
                  }
               `;

      cssAstVisitor = createClassDefinitionVisitor({
        deprecatedCssClasses: ['btn', 'btn-primary', 'btn-large'],
        componentName: 'DsButton',
        docsUrl: 'docs.example.com/DsButton',
      });

      const ast = postcss.parse(styles, { from: 'styles.css' });
      visitEachChild(ast, cssAstVisitor);

      expect(cssAstVisitor.getIssues()).toHaveLength(1);
      const message = cssAstVisitor.getIssues()[0].message;
      expect(message).toContain('btn, btn-primary, btn-large');
      expect(message).toContain('DsButton');
      expect(cssAstVisitor.getIssues()[0]).toEqual(
        expect.objectContaining({
          severity: 'error',
          source: expect.objectContaining({
            file: 'styles.css',
            position: expect.any(Object),
          }),
        }),
      );
    });

    it('should still create single issue for single deprecated class', () => {
      const styles = `
                  .btn {
                    color: red;
                  }
               `;

      cssAstVisitor = createClassDefinitionVisitor({
        deprecatedCssClasses: ['btn', 'btn-primary'],
        componentName: 'DsButton',
        docsUrl: 'docs.example.com/DsButton',
      });

      const ast = postcss.parse(styles, { from: 'styles.css' });
      visitEachChild(ast, cssAstVisitor);

      expect(cssAstVisitor.getIssues()).toHaveLength(1);
      const message = cssAstVisitor.getIssues()[0].message;
      expect(message).toContain('btn');
      expect(message).not.toContain(',');
      expect(message).toContain('DsButton');
      expect(cssAstVisitor.getIssues()[0]).toEqual(
        expect.objectContaining({
          severity: 'error',
          source: expect.objectContaining({
            file: 'styles.css',
            position: expect.any(Object),
          }),
        }),
      );
    });

    it('should handle mixed deprecated and non-deprecated classes', () => {
      const styles = `
                  .btn.safe-class.btn-primary {
                    color: red;
                  }
               `;

      cssAstVisitor = createClassDefinitionVisitor({
        deprecatedCssClasses: ['btn', 'btn-primary'],
        componentName: 'DsButton',
        docsUrl: 'docs.example.com/DsButton',
      });

      const ast = postcss.parse(styles, { from: 'styles.css' });
      visitEachChild(ast, cssAstVisitor);

      expect(cssAstVisitor.getIssues()).toHaveLength(1);
      const message = cssAstVisitor.getIssues()[0].message;
      expect(message).toContain('btn, btn-primary');
      expect(message).not.toContain('safe-class');
      expect(message).toContain('DsButton');
      expect(cssAstVisitor.getIssues()[0]).toEqual(
        expect.objectContaining({
          severity: 'error',
          source: expect.objectContaining({
            file: 'styles.css',
            position: expect.any(Object),
          }),
        }),
      );
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/dom-slots.extractor.ts:
--------------------------------------------------------------------------------

```typescript
import {
  visitComponentTemplate,
  visitEachTmplChild,
  NoopTmplVisitor,
  parseClassNames,
  ParsedComponent,
} from '@push-based/angular-ast-utils';
import {
  extractBindings,
  extractAttributes,
  extractEvents,
} from './element-helpers.js';
import type {
  TmplAstNode,
  TmplAstElement,
  TmplAstForLoopBlock,
  TmplAstIfBlock,
  TmplAstSwitchBlock,
  TmplAstDeferredBlock,
  TmplAstContent,
  TmplAstTemplate,
} from '@angular/compiler' with { 'resolution-mode': 'import' };
import type {
  Slots,
  DomStructure,
  StructuralDirectiveContext,
} from '../../shared/models/types.js';

/**
 * Extract both content projection slots (ng-content) and DOM structure
 * in a single pass over the component template for better performance
 */
export async function extractSlotsAndDom(
  parsedComponent: ParsedComponent,
): Promise<{ slots: Slots; dom: DomStructure }> {
  const slots: Slots = {};
  const dom: DomStructure = {};

  await visitComponentTemplate(
    parsedComponent,
    {},
    async (_, templateAsset) => {
      const parsedTemplate = await templateAsset.parse();
      const visitor = new DomAndSlotExtractionVisitor(slots, dom);
      visitEachTmplChild(parsedTemplate.nodes as TmplAstNode[], visitor);
      return [];
    },
  );

  return { slots, dom };
}

/**
 * Combined visitor to extract ng-content slots and build DOM structure in a single pass
 */
class DomAndSlotExtractionVisitor extends NoopTmplVisitor {
  private slotCounter = 0;
  private pathStack: string[] = [];

  /**
   * Stack of active structural directive contexts so that nested elements inherit
   * the directive information of all parent control-flow blocks.
   */
  private directiveStack: StructuralDirectiveContext[] = [];

  constructor(
    private slots: Slots,
    private dom: DomStructure,
  ) {
    super();
  }

  override visitElement(element: TmplAstElement): void {
    // skip explicit handling of <ng-content> here – it is visited by visitContent

    const selectorKey = this.generateSelectorKey(element);
    const parentKey =
      this.pathStack.length > 0 ? this.pathStack.join(' > ') : null;

    this.dom[selectorKey] = {
      tag: element.name,
      parent: parentKey,
      children: [],
      bindings: extractBindings(element),
      attributes: extractAttributes(element),
      events: extractEvents(element),
      structural:
        this.directiveStack.length > 0
          ? // spread to detach reference
            [...this.directiveStack]
          : undefined,
    };

    if (parentKey && this.dom[parentKey]) {
      this.dom[parentKey].children.push(selectorKey);
    }

    // Push only the current element's selector to the stack
    const currentSelector = this.generateCurrentElementSelector(element);
    this.pathStack.push(currentSelector);
    visitEachTmplChild(element.children as TmplAstNode[], this);
    this.pathStack.pop();
  }

  private visitBlockWithChildren(block: { children?: TmplAstNode[] }): void {
    if (block.children) {
      visitEachTmplChild(block.children as TmplAstNode[], this);
    }
  }

  override visitForLoopBlock(block: TmplAstForLoopBlock): void {
    const ctx: StructuralDirectiveContext = {
      kind: 'for',
      expression: (block as any).expression?.source ?? undefined,
      alias: (block as any).item?.name ?? undefined,
    };
    this.directiveStack.push(ctx);
    this.visitBlockWithChildren(block);
    block.empty?.visit(this);
    this.directiveStack.pop();
  }

  override visitIfBlock(block: TmplAstIfBlock): void {
    const outerCtx: StructuralDirectiveContext = { kind: 'if' };
    this.directiveStack.push(outerCtx);
    block.branches.forEach((branch) => branch.visit(this));
    this.directiveStack.pop();
  }

  override visitSwitchBlock(block: TmplAstSwitchBlock): void {
    const ctx: StructuralDirectiveContext = {
      kind: 'switch',
      expression: (block as any).expression?.source ?? undefined,
    };
    this.directiveStack.push(ctx);
    block.cases.forEach((caseBlock) => caseBlock.visit(this));
    this.directiveStack.pop();
  }

  override visitDeferredBlock(deferred: TmplAstDeferredBlock): void {
    const ctx: StructuralDirectiveContext = { kind: 'defer' };
    this.directiveStack.push(ctx);
    deferred.visitAll(this);
    this.directiveStack.pop();
  }

  /**
   * Handle <ng-content> projection points represented in the Angular template AST as TmplAstContent.
   * Recognises default, attribute-selector ([slot=foo]) and legacy slot=foo syntaxes.
   */
  override visitContent(content: TmplAstContent): void {
    const selectValue = content.selector ?? '';
    const slotName = selectValue ? this.parseSlotName(selectValue) : 'default';

    this.slots[slotName] = {
      selector: selectValue
        ? `ng-content[select="${selectValue}"]`
        : 'ng-content',
    };
  }

  private parseSlotName(selectValue: string): string {
    // Matches [slot=foo], [slot='foo'], [slot="foo"], slot=foo  (case-insensitive)
    const match = selectValue.match(
      /(?:^\[?)\s*slot\s*=\s*['"]?([^'" \]\]]+)['"]?\]?$/i,
    );
    if (match) {
      return match[1];
    }

    if (selectValue.startsWith('.')) {
      return selectValue.substring(1);
    }

    return selectValue || `slot-${this.slotCounter++}`;
  }

  private generateSelectorKey(element: TmplAstElement): string {
    const currentSelector = this.generateCurrentElementSelector(element);

    return this.pathStack.length > 0
      ? `${this.pathStack.join(' > ')} > ${currentSelector}`
      : currentSelector;
  }

  private generateCurrentElementSelector(element: TmplAstElement): string {
    const classes = this.extractClasses(element);
    const id = element.attributes.find((attr) => attr.name === 'id')?.value;

    let selector = element.name;
    if (id) selector += `#${id}`;
    if (classes.length > 0) selector += '.' + classes.join('.');

    return selector;
  }

  private extractClasses(element: TmplAstElement): string[] {
    const out = new Set<string>();

    const classAttr = element.attributes.find((attr) => attr.name === 'class');
    if (classAttr) {
      parseClassNames(classAttr.value).forEach((cls) => out.add(cls));
    }

    element.inputs
      .filter((input) => input.name.startsWith('class.'))
      .forEach((input) => out.add(input.name.substring(6)));

    return [...out];
  }

  /** Legacy structural directives on <ng-template> (e.g., *ngIf, *ngFor). */
  override visitTemplate(template: TmplAstTemplate): void {
    const dir = template.templateAttrs?.find?.((a: any) =>
      a.name?.startsWith?.('ng'),
    );

    if (!dir) {
      // Just traverse children when no structural directive is present
      this.visitBlockWithChildren(template);
      return;
    }

    const map: Record<string, StructuralDirectiveContext['kind']> = {
      ngIf: 'if',
      ngForOf: 'for',
      ngSwitch: 'switch',
      ngSwitchCase: 'switchCase',
      ngSwitchDefault: 'switchDefault',
    } as const;

    const kind = map[dir.name as keyof typeof map];

    // If the directive is not one we're interested in, skip recording
    if (!kind) {
      this.visitBlockWithChildren(template);
      return;
    }

    const ctx: StructuralDirectiveContext = {
      kind,
      expression: (dir as any).value as string | undefined,
    };

    this.directiveStack.push(ctx);
    this.visitBlockWithChildren(template);
    this.directiveStack.pop();
  }
}

/**
 * Attach simple "just traverse children" visitors dynamically to
 * avoid eight nearly identical class methods above.
 */
const SIMPLE_VISIT_METHODS = [
  'visitForLoopBlockEmpty',
  'visitIfBlockBranch',
  'visitSwitchBlockCase',
  'visitDeferredBlockError',
  'visitDeferredBlockLoading',
  'visitDeferredBlockPlaceholder',
  // 'visitTemplate',
] as const;

SIMPLE_VISIT_METHODS.forEach((method) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (DomAndSlotExtractionVisitor.prototype as any)[method] = function (
    this: DomAndSlotExtractionVisitor,
    block: { children?: TmplAstNode[] },
  ): void {
    if (block.children) {
      visitEachTmplChild(block.children as TmplAstNode[], this);
    }
  };
});

```

--------------------------------------------------------------------------------
/packages/shared/utils/ai/FUNCTIONS.md:
--------------------------------------------------------------------------------

```markdown
# Public API — Quick Reference

| Symbol                 | Kind     | Summary                                                        |
| ---------------------- | -------- | -------------------------------------------------------------- |
| `ProcessResult`        | type     | Process execution result with stdout, stderr, code, and timing |
| `ProcessError`         | class    | Error class for process execution failures                     |
| `ProcessConfig`        | type     | Configuration object for process execution                     |
| `ProcessObserver`      | type     | Observer interface for process events                          |
| `LinePosition`         | type     | Position information for text matches within a line            |
| `SourcePosition`       | type     | Position information with line and column details              |
| `SourceLocation`       | type     | File location with position information                        |
| `accessContent`        | function | Generator function to iterate over file content lines          |
| `calcDuration`         | function | Calculate duration between performance timestamps              |
| `executeProcess`       | function | Execute a child process with observer pattern                  |
| `fileResolverCache`    | constant | Map cache for file resolution operations                       |
| `findAllFiles`         | function | Async generator to find files matching a predicate             |
| `findFilesWithPattern` | function | Find TypeScript files containing a search pattern              |
| `findInFile`           | function | Find pattern matches within a specific file                    |
| `formatCommandLog`     | function | Format command strings with ANSI colors and directory context  |
| `getLineHits`          | function | Get all pattern matches within a text line                     |
| `isExcludedDirectory`  | function | Check if a directory should be excluded from searches          |
| `isVerbose`            | function | Check if verbose logging is enabled via environment variable   |
| `loadDefaultExport`    | function | Dynamically import ES modules and extract default export       |
| `objectToCliArgs`      | function | Convert object properties to command-line arguments            |
| `resolveFile`          | function | Read file content directly without caching                     |
| `resolveFileCached`    | function | Read file content with caching for performance                 |

## Types

### `ProcessResult`

```ts
type ProcessResult = {
  stdout: string;
  stderr: string;
  code: number | null;
  date: string;
  duration: number;
};
```

Represents the result of a process execution with output streams, exit code, and timing information.

### `ProcessConfig`

```ts
type ProcessConfig = Omit<
  SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>,
  'stdio'
> & {
  command: string;
  args?: string[];
  observer?: ProcessObserver;
  ignoreExitCode?: boolean;
};
```

Configuration object for process execution, extending Node.js spawn options.

### `ProcessObserver`

```ts
type ProcessObserver = {
  onStdout?: (stdout: string, sourceProcess?: ChildProcess) => void;
  onStderr?: (stderr: string, sourceProcess?: ChildProcess) => void;
  onError?: (error: ProcessError) => void;
  onComplete?: () => void;
};
```

Observer interface for handling process events during execution.

### `LinePosition`

```ts
type LinePosition = {
  startColumn: number;
  endColumn?: number;
};
```

Position information for text matches within a single line.

### `SourcePosition`

```ts
type SourcePosition = {
  startLine: number;
  endLine?: number;
} & LinePosition;
```

Extended position information including line numbers.

### `SourceLocation`

```ts
type SourceLocation = {
  file: string;
  position: SourcePosition;
};
```

Complete location information with file path and position details.

## Classes

### `ProcessError extends Error`

Error class for process execution failures, containing additional process result information.

**Properties:**

- `code: number | null` - Process exit code
- `stderr: string` - Process error output
- `stdout: string` - Process standard output

## Functions

### `executeProcess(cfg: ProcessConfig): Promise<ProcessResult>`

Executes a child process with comprehensive error handling and observer pattern support.

**Parameters:**

- `cfg` - Process configuration object

**Returns:** Promise resolving to process result

### `findFilesWithPattern(baseDir: string, searchPattern: string): Promise<string[]>`

Searches for TypeScript files containing the specified pattern.

**Parameters:**

- `baseDir` - Directory to search (absolute or resolved by caller)
- `searchPattern` - Pattern to match in file contents

**Returns:** Promise resolving to array of file paths

### `findAllFiles(baseDir: string, predicate?: (file: string) => boolean): AsyncGenerator<string>`

Async generator that finds all files matching a predicate function.

**Parameters:**

- `baseDir` - Base directory to search
- `predicate` - Optional file filter function (defaults to `.ts` files)

**Returns:** Async generator yielding file paths

### `findInFile(file: string, searchPattern: string, bail?: boolean): Promise<SourceLocation[]>`

Finds all occurrences of a pattern within a specific file.

**Parameters:**

- `file` - File path to search
- `searchPattern` - Pattern to find
- `bail` - Optional flag to stop after first match

**Returns:** Promise resolving to array of source locations

### `resolveFileCached(filePath: string): Promise<string>`

Resolves file content with caching to avoid reading the same file multiple times.

**Parameters:**

- `filePath` - Path to the file to read

**Returns:** Promise resolving to file content

### `resolveFile(filePath: string): Promise<string>`

Resolves file content directly without caching.

**Parameters:**

- `filePath` - Path to the file to read

**Returns:** Promise resolving to file content

### `formatCommandLog(command: string, args?: string[], cwd?: string): string`

Formats a command string with ANSI colors and optional directory context.

**Parameters:**

- `command` - Command to execute
- `args` - Command arguments (optional)
- `cwd` - Current working directory (optional)

**Returns:** ANSI-colored formatted command string

### `objectToCliArgs<T extends object>(params?: CliArgsObject<T>): string[]`

Converts an object with different value types into command-line arguments array.

**Parameters:**

- `params` - Object with CLI parameters

**Returns:** Array of formatted CLI arguments

### `calcDuration(start: number, stop?: number): number`

Calculates duration between performance timestamps.

**Parameters:**

- `start` - Start timestamp from `performance.now()`
- `stop` - Optional end timestamp (defaults to current time)

**Returns:** Duration in milliseconds

### `getLineHits(content: string, pattern: string, bail?: boolean): LinePosition[]`

Gets all pattern matches within a text line.

**Parameters:**

- `content` - Text content to search
- `pattern` - Pattern to find
- `bail` - Optional flag to stop after first match

**Returns:** Array of line positions

### `accessContent(content: string): Generator<string>`

Generator function to iterate over file content lines.

**Parameters:**

- `content` - File content string

**Returns:** Generator yielding individual lines

### `isExcludedDirectory(fileName: string): boolean`

Checks if a directory should be excluded from file searches.

**Parameters:**

- `fileName` - Directory name to check

**Returns:** `true` if directory should be excluded

### `isVerbose(): boolean`

Checks if verbose logging is enabled via the `NG_MCP_VERBOSE` environment variable.

**Returns:** `true` if verbose logging is enabled

### `loadDefaultExport<T = unknown>(filePath: string): Promise<T>`

Dynamically imports an ES Module and extracts the default export. Uses proper file URL conversion for cross-platform compatibility.

**Parameters:**

- `filePath` - Absolute path to the ES module file to import

**Returns:** Promise resolving to the default export from the module

**Throws:** Error if the module cannot be loaded or has no default export

**Example:**

```typescript
const config = await loadDefaultExport('/path/to/config.js');
const data = await loadDefaultExport<MyDataType>('/path/to/data.mjs');
```

## Constants

### `fileResolverCache: Map<string, Promise<string>>`

Map cache used by `resolveFileCached` to store file reading promises and avoid duplicate file operations.

```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/third-case/product-card.component.ts:
--------------------------------------------------------------------------------

```typescript
import {
  ChangeDetectionStrategy,
  Component,
  ViewEncapsulation,
  computed,
  input,
  output,
  signal,
  booleanAttribute,
  OnInit,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DsBadge } from '@frontend/ui/badge';

export interface Product {
  id: string;
  name: string;
  price: number;
  originalPrice?: number;
  category: string;
  rating: number;
  reviewCount: number;
  inStock: boolean;
  imageUrl: string;
  tags: string[];
}

export const PRODUCT_BADGE_TYPES = ['sale', 'new', 'bestseller', 'limited'] as const;
export type ProductBadgeType = (typeof PRODUCT_BADGE_TYPES)[number];

@Component({
  selector: 'app-product-card',
  standalone: true,
  imports: [CommonModule, FormsModule, DsBadge],
  template: `
    <div class="product-card" [class.product-card-selected]="selected()">
      <!-- Product Image with Badge Overlay -->
      <div class="product-image-container">
        <img 
          [src]="product().imageUrl" 
          [alt]="product().name"
          class="product-image"
          (error)="onImageError($event)">
        
        <!-- DsBadge Implementation -->
        @if (showBadge()) {
          <div class="badge-overlay">
            <ds-badge 
              [size]="compact() ? 'xsmall' : 'medium'"
              [variant]="getBadgeVariant()">
              
              <!-- Icon slot (start) -->
              <span slot="start">
                @switch (badgeType()) {
                  @case ('sale') {
                    🔥
                  }
                  @case ('new') {
                    ✨
                  }
                  @case ('bestseller') {
                    ⭐
                  }
                  @case ('limited') {
                    ⏰
                  }
                }
              </span>
              
              <!-- Main badge text -->
              {{ getBadgeText() }}
              
              <!-- Optional percentage for sale badges (end slot) -->
              @if (badgeType() === 'sale' && product().originalPrice) {
                <span slot="end">
                  -{{ getSalePercentage() }}%
                </span>
              }
            </ds-badge>
          </div>
        }
        
        <!-- Stock status indicator -->
        @if (!product().inStock) {
          <div class="stock-overlay">
            <span class="stock-badge">Out of Stock</span>
          </div>
        }
      </div>

      <!-- Product Content -->
      <div class="product-content">
        <div class="product-header">
          <h3 class="product-name">{{ product().name }}</h3>
          <button 
            class="favorite-button"
            [class.favorite-active]="favorited()"
            (click)="toggleFavorite()"
            [attr.aria-label]="favorited() ? 'Remove from favorites' : 'Add to favorites'">
            <svg width="20" height="20" viewBox="0 0 20 20">
              <path d="M10 15l-5.5 3 1-6L1 7.5l6-.5L10 1l3 6 6 .5-4.5 4.5 1 6z" 
                    [attr.fill]="favorited() ? '#ef4444' : 'none'"
                    [attr.stroke]="favorited() ? '#ef4444' : 'currentColor'"
                    stroke-width="1.5"/>
            </svg>
          </button>
        </div>
        
        <div class="product-category">{{ product().category }}</div>
        
        <!-- Price section with conditional styling -->
        <div class="product-pricing">
          @if (product().originalPrice && product().originalPrice > product().price) {
            <span class="original-price">\${{ product().originalPrice.toFixed(2) }}</span>
          }
          <span class="current-price">\${{ product().price.toFixed(2) }}</span>
        </div>
        
        <!-- Rating and reviews -->
        <div class="product-rating">
          <div class="rating-stars">
            @for (star of getStarArray(); track $index) {
              <span class="star" [class.star-filled]="star">★</span>
            }
          </div>
          <span class="rating-text">{{ product().rating.toFixed(1) }} ({{ product().reviewCount }})</span>
        </div>
        
        <!-- Product tags -->
        @if (product().tags.length > 0) {
          <div class="product-tags">
            @for (tag of product().tags.slice(0, 3); track tag) {
              <span class="product-tag">{{ tag }}</span>
            }
            @if (product().tags.length > 3) {
              <span class="tag-more">+{{ product().tags.length - 3 }} more</span>
            }
          </div>
        }
      </div>

      <!-- Product Actions -->
      <div class="product-actions">
        <button 
          class="action-button add-to-cart"
          [disabled]="!product().inStock"
          (click)="addToCart()"
          [attr.aria-label]="'Add ' + product().name + ' to cart'">
          @if (product().inStock) {
            Add to Cart
          } @else {
            Notify When Available
          }
        </button>
        
        <button 
          class="action-button quick-view"
          (click)="quickView()"
          [attr.aria-label]="'Quick view ' + product().name">
          Quick View
        </button>
      </div>
    </div>
  `,
  styleUrls: ['./product-card.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCardComponent implements OnInit {
  // Product data
  product = input.required<Product>();
  
  // Badge configuration
  badgeType = input<ProductBadgeType>('sale');
  showBadge = input(true, { transform: booleanAttribute });
  animated = input(true, { transform: booleanAttribute });
  compact = input(false, { transform: booleanAttribute });
  
  // Card state
  selected = input(false, { transform: booleanAttribute });
  favorited = signal(false);
  
  // Outputs
  productSelected = output<Product>();
  favoriteToggled = output<{product: Product, favorited: boolean}>();
  addToCartClicked = output<Product>();
  quickViewClicked = output<Product>();

  ngOnInit() {
    // Initialize favorited state from localStorage or API
    const savedFavorites = localStorage.getItem('favoriteProducts');
    if (savedFavorites) {
      const favorites = JSON.parse(savedFavorites) as string[];
      this.favorited.set(favorites.includes(this.product().id));
    }
  }

  getBadgeText(): string {
    const typeMap: Record<ProductBadgeType, string> = {
      'sale': 'Sale',
      'new': 'New',
      'bestseller': 'Best Seller',
      'limited': 'Limited Time'
    };
    return typeMap[this.badgeType()];
  }

  getBadgeVariant() {
    const variantMap: Record<ProductBadgeType, string> = {
      'sale': 'red-strong',
      'new': 'green-strong',
      'bestseller': 'yellow-strong',
      'limited': 'purple-strong'
    };
    return variantMap[this.badgeType()];
  }

  getBadgeAriaLabel(): string {
    const text = this.getBadgeText();
    if (this.badgeType() === 'sale' && this.product().originalPrice) {
      return `${text} badge: ${this.getSalePercentage()}% off`;
    }
    return `${text} badge`;
  }

  getSalePercentage(): number {
    const original = this.product().originalPrice;
    const current = this.product().price;
    if (!original || original <= current) return 0;
    return Math.round(((original - current) / original) * 100);
  }

  getStarArray(): boolean[] {
    const rating = this.product().rating;
    const stars: boolean[] = [];
    for (let i = 1; i <= 5; i++) {
      stars.push(i <= rating);
    }
    return stars;
  }

  toggleFavorite() {
    const newFavorited = !this.favorited();
    this.favorited.set(newFavorited);
    
    // Update localStorage
    const savedFavorites = localStorage.getItem('favoriteProducts');
    const favorites = savedFavorites ? JSON.parse(savedFavorites) as string[] : [];
    
    if (newFavorited) {
      if (!favorites.includes(this.product().id)) {
        favorites.push(this.product().id);
      }
    } else {
      const index = favorites.indexOf(this.product().id);
      if (index > -1) {
        favorites.splice(index, 1);
      }
    }
    
    localStorage.setItem('favoriteProducts', JSON.stringify(favorites));
    this.favoriteToggled.emit({product: this.product(), favorited: newFavorited});
  }

  addToCart() {
    if (this.product().inStock) {
      this.addToCartClicked.emit(this.product());
    }
  }

  quickView() {
    this.quickViewClicked.emit(this.product());
  }

  onImageError(event: Event) {
    const img = event.target as HTMLImageElement;
    img.src = 'https://via.placeholder.com/300x200?text=No+Image';
  }
} 
```

--------------------------------------------------------------------------------
/packages/shared/angular-ast-utils/src/lib/template/utils.ts:
--------------------------------------------------------------------------------

```typescript
import type {
  AST,
  ASTWithSource,
  ParsedTemplate,
  ParseSourceSpan,
} from '@angular/compiler' with { 'resolution-mode': 'import' };

import { Issue } from '@code-pushup/models';
import { Asset, ParsedComponent } from '../types.js';

/**
 * Convert a TmplAstElement to an Issue source object and adjust its position based on startLine.
 * It creates a "linkable" source object for the issue.
 * By default, the source location is 0 indexed, so we add 1 to the startLine to make it work in file links.
 *
 * @param element The element to convert.
 * @param startLine The baseline number to adjust positions.
 */
export function tmplAstElementToSource(
  {
    startSourceSpan,
    sourceSpan,
    endSourceSpan,
  }: {
    sourceSpan: ParseSourceSpan;
    startSourceSpan: ParseSourceSpan;
    endSourceSpan: ParseSourceSpan | null;
  },
  startLine = 0,
): Issue['source'] {
  const offset = startLine; // TS Ast is 0 indexed so is work in 0 based index out of the box
  return {
    file: sourceSpan.start.file.url,
    position: {
      startLine: (startSourceSpan?.start.line ?? 0) + offset + 1,
      ...(startSourceSpan?.start.col && {
        startColumn: startSourceSpan?.start.col,
      }),
      ...(endSourceSpan?.end.line !== undefined && {
        endLine: endSourceSpan?.end.line + offset + 1,
      }),
      ...(endSourceSpan?.end.col && {
        endColumn: endSourceSpan?.end.col,
      }),
    },
  };
}

export function parseClassNames(classString: string): string[] {
  return classString.trim().split(/\s+/).filter(Boolean);
}

export async function visitComponentTemplate<T>(
  component: ParsedComponent,
  visitorArgument: T,
  getIssues: (
    tokenReplacement: T,
    asset: Asset<ParsedTemplate>,
  ) => Promise<Issue[]>,
): Promise<Issue[]> {
  const { templateUrl, template } = component;

  if (templateUrl == null && template == null) {
    return [];
  }
  const componentTemplate = templateUrl ?? template;

  return getIssues(visitorArgument, componentTemplate);
}

/**
 * AST-based ngClass parser that properly detects class usage in Angular expressions
 * Handles arrays, objects, and ternary expressions to find actual class usage
 */
export function extractClassNamesFromNgClassAST(
  ast: AST,
  targetClassNames: string[],
): string[] {
  const foundClasses: string[] = [];
  const targetSet = new Set(targetClassNames);

  function visitAST(node: AST): void {
    if (!node) return;

    // Use duck typing instead of instanceof for better compatibility
    const nodeType = node.constructor.name;

    // Handle array literals: ['class1', 'class2', variable]
    if (nodeType === 'LiteralArray' && 'expressions' in node) {
      const arrayNode = node as any;
      arrayNode.expressions.forEach((expr: any) => {
        if (
          expr.constructor.name === 'LiteralPrimitive' &&
          typeof expr.value === 'string'
        ) {
          const classNames = parseClassNames(expr.value);
          classNames.forEach((className: string) => {
            if (targetSet.has(className)) {
              foundClasses.push(className);
            }
          });
        }
        visitAST(expr);
      });
    }
    // Handle object literals: { 'class1': true, 'class2': condition }
    else if (nodeType === 'LiteralMap' && 'keys' in node && 'values' in node) {
      const mapNode = node as any;
      mapNode.keys.forEach((key: any, index: number) => {
        // Handle the key structure: { key: "className", quoted: true }
        if (key && typeof key.key === 'string') {
          const classNames = parseClassNames(key.key);
          classNames.forEach((className: string) => {
            if (targetSet.has(className)) {
              foundClasses.push(className);
            }
          });
        }
        // Visit the value expression but don't extract classes from it
        // (e.g., in { 'card': option?.logo?.toLowerCase() === 'card' })
        // we don't want to extract 'card' from the comparison
        visitAST(mapNode.values[index]);
      });
    }
    // Handle string literals: 'class1 class2'
    else if (
      nodeType === 'LiteralPrimitive' &&
      'value' in node &&
      typeof (node as any).value === 'string'
    ) {
      const primitiveNode = node as any;
      const classNames = parseClassNames(primitiveNode.value);
      classNames.forEach((className: string) => {
        if (targetSet.has(className)) {
          foundClasses.push(className);
        }
      });
    }
    // Handle interpolation: "static {{ dynamic }} static"
    else if (
      nodeType === 'Interpolation' &&
      'strings' in node &&
      'expressions' in node
    ) {
      const interpolationNode = node as any;
      // Extract class names from static string parts only
      // Don't process the expressions to avoid false positives
      interpolationNode.strings.forEach((str: string) => {
        if (str && str.trim()) {
          const classNames = parseClassNames(str);
          classNames.forEach((className: string) => {
            if (targetSet.has(className)) {
              foundClasses.push(className);
            }
          });
        }
      });
      // Note: We intentionally don't visit the expressions to avoid false positives
      // from dynamic expressions like {{ someCondition ? 'card' : 'other' }}
    }
    // Handle ternary expressions: condition ? 'class1' : 'class2'
    else if (
      nodeType === 'Conditional' &&
      'trueExp' in node &&
      'falseExp' in node
    ) {
      const conditionalNode = node as any;
      // Don't visit the condition (to avoid false positives from comparisons)
      visitAST(conditionalNode.trueExp);
      visitAST(conditionalNode.falseExp);
    }
    // Handle binary expressions (avoid extracting from comparisons)
    else if (nodeType === 'Binary') {
      // For binary expressions like comparisons, we generally don't want to extract
      // class names from them to avoid false positives like 'card' in "option?.logo === 'card'"
      return;
    }
    // Handle property access: object.property
    else if (
      (nodeType === 'PropertyRead' || nodeType === 'SafePropertyRead') &&
      'receiver' in node
    ) {
      const propertyNode = node as any;
      visitAST(propertyNode.receiver);
      // Don't extract from property names
    }
    // Handle keyed access: object[key]
    else if (
      (nodeType === 'KeyedRead' || nodeType === 'SafeKeyedRead') &&
      'receiver' in node &&
      'key' in node
    ) {
      const keyedNode = node as any;
      visitAST(keyedNode.receiver);
      visitAST(keyedNode.key);
    }
    // Handle function calls: func(args)
    else if (
      (nodeType === 'Call' || nodeType === 'SafeCall') &&
      'receiver' in node &&
      'args' in node
    ) {
      const callNode = node as any;
      visitAST(callNode.receiver);
      callNode.args.forEach((arg: any) => visitAST(arg));
    }
    // Handle prefix not: !expression
    else if (nodeType === 'PrefixNot' && 'expression' in node) {
      const prefixNode = node as any;
      visitAST(prefixNode.expression);
    } else {
      const anyNode = node as any;
      if (anyNode.expressions && Array.isArray(anyNode.expressions)) {
        anyNode.expressions.forEach((expr: any) => visitAST(expr));
      }
      if (anyNode.receiver) {
        visitAST(anyNode.receiver);
      }
      if (anyNode.args && Array.isArray(anyNode.args)) {
        anyNode.args.forEach((arg: any) => visitAST(arg));
      }
      if (anyNode.left) {
        visitAST(anyNode.left);
      }
      if (anyNode.right) {
        visitAST(anyNode.right);
      }
    }
  }

  visitAST(ast);
  return Array.from(new Set(foundClasses));
}

export function ngClassContainsClass(
  astWithSource: ASTWithSource,
  className: string,
): boolean {
  const foundClasses = extractClassNamesFromNgClassAST(astWithSource.ast, [
    className,
  ]);
  return foundClasses.includes(className);
}

/**
 * Check if a class name exists in an ngClass expression string
 * This is a simplified regex-based implementation for backward compatibility
 * For more accurate AST-based parsing, use extractClassNamesFromNgClassAST directly
 *
 * @param source The ngClass expression source string
 * @param className The class name to search for
 * @returns true if the class name is found in the expression
 */
export function ngClassesIncludeClassName(
  source: string,
  className: string,
): boolean {
  const escaped = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const boundary = '[\\w$-]';
  const regex = new RegExp(`(?<!${boundary})${escaped}(?!${boundary})`);

  return regex.test(source);
}

```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/first-case/dashboard-header.component.scss:
--------------------------------------------------------------------------------

```scss
// Dashboard Header Component Styles
.dashboard-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1.5rem;
  background: #ffffff;
  border-bottom: 1px solid #e5e7eb;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
  position: relative;
  z-index: 100;
  min-height: 4rem;
}

// Header Brand Section
.header-brand {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-shrink: 0;
}

.brand-logo {
  display: flex;
  align-items: center;
  justify-content: center;
}

.logo-icon {
  width: 2rem;
  height: 2rem;
  border-radius: 0.375rem;
}

.brand-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: #1f2937;
  margin: 0;
  line-height: 1.2;
}

// DsBadge Component Styling
.offer-badge-default-icon {
  font-size: 1em;
  line-height: 1;
}

.offer-badge-dismiss {
  background: none;
  border: none;
  color: currentColor;
  cursor: pointer;
  padding: 0;
  margin-left: 0.25rem;
  font-size: 1.125rem;
  line-height: 1;
  opacity: 0.7;
  transition: opacity 0.2s ease;
  
  &:hover {
    opacity: 1;
  }
}

// Header Search Section
.header-search {
  flex: 1;
  max-width: 32rem;
  margin: 0 2rem;
  position: relative;
}

.search-container {
  position: relative;
  display: flex;
  align-items: center;
  background: #f9fafb;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  padding: 0 0.75rem;
  transition: all 0.2s ease;
  
  &.search-focused {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
    background: white;
  }
}

.search-icon {
  color: #6b7280;
  flex-shrink: 0;
  margin-right: 0.5rem;
}

.search-input {
  flex: 1;
  border: none;
  background: transparent;
  padding: 0.75rem 0;
  font-size: 0.875rem;
  color: #1f2937;
  outline: none;
  
  &::placeholder {
    color: #9ca3af;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

.search-clear {
  background: none;
  border: none;
  color: #6b7280;
  cursor: pointer;
  padding: 0.25rem;
  margin-left: 0.5rem;
  font-size: 1.125rem;
  line-height: 1;
  border-radius: 0.25rem;
  transition: all 0.2s ease;
  
  &:hover {
    background: #f3f4f6;
    color: #374151;
  }
}

.search-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: white;
  border: 1px solid #d1d5db;
  border-top: none;
  border-radius: 0 0 0.5rem 0.5rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  z-index: 50;
  max-height: 12rem;
  overflow-y: auto;
}

.suggestion-item {
  display: block;
  width: 100%;
  padding: 0.75rem;
  border: none;
  background: none;
  text-align: left;
  font-size: 0.875rem;
  color: #374151;
  cursor: pointer;
  transition: background-color 0.2s ease;
  
  &:hover {
    background: #f9fafb;
  }
  
  &:not(:last-child) {
    border-bottom: 1px solid #f3f4f6;
  }
}

// Header Actions Section
.header-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.action-item {
  position: relative;
}

.action-button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  border: none;
  background: none;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: all 0.2s ease;
  color: #6b7280;
  
  &:hover {
    background: #f3f4f6;
    color: #374151;
  }
}

// Notifications
.notification-button {
  position: relative;
  
  &.has-notifications {
    color: #3b82f6;
  }
}

.notification-icon {
  width: 1.5rem;
  height: 1.5rem;
}

.notification-badge {
  position: absolute;
  top: -0.25rem;
  right: -0.25rem;
  background: #ef4444;
  color: white;
  font-size: 0.625rem;
  font-weight: 600;
  padding: 0.125rem 0.375rem;
  border-radius: 9999px;
  min-width: 1.125rem;
  text-align: center;
  line-height: 1;
}

.notifications-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  width: 20rem;
  background: white;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
  z-index: 50;
  margin-top: 0.5rem;
  max-height: 24rem;
  overflow: hidden;
}

.dropdown-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
  border-bottom: 1px solid #f3f4f6;
  
  h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: #1f2937;
  }
}

.mark-all-read {
  background: none;
  border: none;
  color: #3b82f6;
  font-size: 0.875rem;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
  border-radius: 0.25rem;
  transition: background-color 0.2s ease;
  
  &:hover {
    background: #f3f4f6;
  }
}

.notifications-list {
  max-height: 18rem;
  overflow-y: auto;
}

.notification-item {
  display: flex;
  align-items: flex-start;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #f9fafb;
  transition: background-color 0.2s ease;
  
  &:hover {
    background: #f9fafb;
  }
  
  &.notification-unread {
    background: #eff6ff;
    border-left: 3px solid #3b82f6;
  }
  
  &.notification-error {
    border-left-color: #ef4444;
  }
  
  &.notification-warning {
    border-left-color: #f59e0b;
  }
  
  &.notification-success {
    border-left-color: #10b981;
  }
}

.notification-content {
  flex: 1;
  margin-right: 0.5rem;
}

.notification-title {
  margin: 0 0 0.25rem 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: #1f2937;
  line-height: 1.25;
}

.notification-message {
  margin: 0 0 0.5rem 0;
  font-size: 0.75rem;
  color: #6b7280;
  line-height: 1.4;
}

.notification-time {
  font-size: 0.625rem;
  color: #9ca3af;
}

.notification-dismiss {
  background: none;
  border: none;
  color: #9ca3af;
  cursor: pointer;
  padding: 0.25rem;
  font-size: 1rem;
  line-height: 1;
  border-radius: 0.25rem;
  transition: all 0.2s ease;
  
  &:hover {
    background: #f3f4f6;
    color: #6b7280;
  }
}

.no-notifications {
  padding: 2rem 1rem;
  text-align: center;
  color: #9ca3af;
  font-size: 0.875rem;
  
  p {
    margin: 0;
  }
}

// User Menu
.user-button {
  padding: 0.375rem 0.75rem;
  gap: 0.5rem;
}

.user-avatar {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  object-fit: cover;
}

.user-avatar-placeholder {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: #3b82f6;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 600;
}

.dropdown-arrow {
  color: #9ca3af;
  transition: transform 0.2s ease;
}

.user-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  width: 16rem;
  background: white;
  border: 1px solid #d1d5db;
  border-radius: 0.5rem;
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
  z-index: 50;
  margin-top: 0.5rem;
  overflow: hidden;
}

.user-info {
  padding: 1rem;
  border-bottom: 1px solid #f3f4f6;
}

.user-details {
  h4 {
    margin: 0 0 0.25rem 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: #1f2937;
  }
  
  p {
    margin: 0 0 0.5rem 0;
    font-size: 0.75rem;
    color: #6b7280;
  }
}

.user-role {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  background: #f3f4f6;
  color: #6b7280;
  font-size: 0.625rem;
  font-weight: 500;
  border-radius: 9999px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.user-actions {
  padding: 0.5rem 0;
}

.dropdown-item {
  display: block;
  width: 100%;
  padding: 0.5rem 1rem;
  border: none;
  background: none;
  text-align: left;
  font-size: 0.875rem;
  color: #374151;
  cursor: pointer;
  transition: background-color 0.2s ease;
  
  &:hover {
    background: #f9fafb;
  }
  
  &.logout {
    color: #ef4444;
    
    &:hover {
      background: #fef2f2;
    }
  }
}

.dropdown-divider {
  margin: 0.5rem 0;
  border: none;
  border-top: 1px solid #f3f4f6;
}



// Dark mode support
@media (prefers-color-scheme: dark) {
  .dashboard-header {
    background: #1f2937;
    border-bottom-color: #374151;
  }
  
  .brand-title {
    color: #f9fafb;
  }
  
  .search-container {
    background: #374151;
    border-color: #4b5563;
    
    &.search-focused {
      background: #4b5563;
      border-color: #3b82f6;
    }
  }
  
  .search-input {
    color: #f9fafb;
    
    &::placeholder {
      color: #9ca3af;
    }
  }
  
  .search-suggestions,
  .notifications-dropdown,
  .user-dropdown {
    background: #374151;
    border-color: #4b5563;
  }
  
  .dropdown-header h3,
  .notification-title,
  .user-details h4 {
    color: #f9fafb;
  }
  
  .dropdown-item {
    color: #d1d5db;
    
    &:hover {
      background: #4b5563;
    }
  }
}

// Responsive design
@media (max-width: 768px) {
  .dashboard-header {
    padding: 0.5rem 1rem;
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  
  .header-search {
    order: 3;
    flex-basis: 100%;
    margin: 0;
    max-width: none;
  }
  
  .brand-title {
    font-size: 1rem;
  }
  
  .notifications-dropdown,
  .user-dropdown {
    width: 16rem;
    right: -4rem;
  }
} 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/styles/extended-deprecated-styles.scss:
--------------------------------------------------------------------------------

```scss
// Extended stylesheet with nested and combined selectors for deprecated classes
// This file demonstrates various ways deprecated classes might be used in real applications

// Nested selectors for badge-related deprecated classes
.container {
  .pill-with-badge {
    color: red;
    border: 1px solid #ccc;
    padding: 5px 10px;
    border-radius: 15px;
    display: inline-block;

    &:hover {
      background-color: #f0f0f0;
    }

    &.active {
      background-color: #e0e0e0;
    }
  }

  .pill-with-badge-v2 {
    color: blue;
    border: 2px solid #aaa;
    padding: 6px 12px;
    border-radius: 20px;
    display: inline-block;

    &.large {
      padding: 8px 16px;
    }
  }

  .sports-pill {
    color: green;
    background-color: #f0f0f0;
    padding: 8px 16px;
    border-radius: 25px;
    display: inline-block;

    &.highlighted {
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
  }

  .offer-badge {
    color: yellow;
    background-color: #333;
    padding: 4px 8px;
    border-radius: 10px;
    display: inline-block;

    &.urgent {
      animation: pulse 1s infinite;
    }
  }
}

// Combined selectors for navigation deprecated classes
.navigation-wrapper {
  .tab-nav,
  .nav-tabs {
    padding: 10px;
    border-bottom: 2px solid #ddd;

    .tab-nav-item {
      color: pink;
      padding: 10px 15px;
      border-radius: 5px;
      display: inline-block;
      cursor: pointer;

      &:hover {
        background-color: #f5f5f5;
      }

      &.active {
        background-color: #007bff;
        color: white;
      }
    }
  }

  .tab-nav {
    color: orange;
    background-color: #fff;
  }

  .nav-tabs {
    color: purple;
    background-color: #eee;
  }
}

// Nested button selectors
.button-group {
  .btn {
    color: brown;
    background-color: #f5f5f5;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;

    &:hover {
      background-color: #e0e0e0;
    }

    &:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
  }

  .btn-primary {
    color: cyan;
    background-color: #007bff;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;

    &:hover {
      background-color: #0056b3;
    }

    &.loading {
      position: relative;

      &::after {
        content: '';
        position: absolute;
        width: 16px;
        height: 16px;
        border: 2px solid transparent;
        border-top: 2px solid currentColor;
        border-radius: 50%;
        animation: spin 1s linear infinite;
      }
    }
  }

  .legacy-button {
    color: magenta;
    background-color: #f8f9fa;
    padding: 10px 20px;
    border: 1px solid #ccc;
    border-radius: 5px;
    cursor: pointer;

    &.deprecated-style {
      border-style: dashed;
    }
  }
}

// Modal and card combinations
.content-area {
  .modal {
    color: lime;
    background-color: #fff;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);

    .card {
      color: olive;
      background-color: #f8f9fa;
      padding: 15px;
      border-radius: 5px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      margin-bottom: 15px;

      &:last-child {
        margin-bottom: 0;
      }
    }

    &.with-loading {
      .loading,
      .loading-v2,
      .loading-v3 {
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 20px 0;
      }

      .loading {
        color: teal;
        font-size: 16px;
      }

      .loading-v2 {
        color: navy;
        font-size: 18px;
      }

      .loading-v3 {
        color: maroon;
        font-size: 20px;
      }
    }
  }
}

// Complex nested form controls
.form-section {
  .form-control-tabs-segmented,
  .form-control-tabs-segmented-v2,
  .form-control-tabs-segmented-v3,
  .form-control-tabs-segmented-v4,
  .form-control-tabs-segmented-flex,
  .form-control-tabs-segmented-v2-dark {
    padding: 10px;
    border-radius: 5px;
    display: flex;
    justify-content: space-between;
    margin-bottom: 15px;

    &.with-custom-controls {
      .custom-control-checkbox,
      .custom-control-radio,
      .custom-control-switcher {
        display: flex;
        align-items: center;
        margin-right: 15px;

        &:last-child {
          margin-right: 0;
        }
      }
    }
  }

  .form-control-tabs-segmented {
    color: wheat;
    background-color: #fff;
  }

  .form-control-tabs-segmented-v2 {
    color: salmon;
    background-color: #fff;
  }

  .form-control-tabs-segmented-v3 {
    color: turquoise;
    background-color: #fff;
  }

  .form-control-tabs-segmented-v4 {
    color: violet;
    background-color: #f8f9fa;
  }

  .form-control-tabs-segmented-flex {
    color: sienna;
    background-color: #f8f9fa;
  }

  .form-control-tabs-segmented-v2-dark {
    color: tan;
    background-color: #333;
  }
}

// Utility classes with nested selectors
.utility-section {
  .collapsible-container {
    color: silver;
    background-color: #f0f0f0;
    padding: 10px;
    border-radius: 5px;
    overflow: hidden;

    &.expanded {
      max-height: none;
    }

    &.collapsed {
      max-height: 50px;
    }

    .divider {
      color: gray;
      border-top: 1px solid #ccc;
      margin: 10px 0;

      &.thick {
        border-top-width: 2px;
      }
    }
  }

  .count,
  .badge-circle {
    padding: 5px 10px;
    border-radius: 50%;
    display: inline-block;

    &.small {
      padding: 3px 6px;
      font-size: 12px;
    }

    &.large {
      padding: 8px 16px;
      font-size: 18px;
    }
  }

  .count {
    color: gold;
    background-color: #333;
  }

  .badge-circle {
    color: coral;
    background-color: #f0f0f0;
  }
}

// Random classes with various combinations
.random-section {
  @for $i from 1 through 50 {
    .random-class-#{$i} {
      background-color: lighten(#000, $i * 2%);

      &:hover {
        background-color: lighten(#000, ($i * 2% + 10%));
      }

      &.active {
        background-color: darken(#000, $i * 1%);
      }

      // Nested combinations
      .pill-with-badge,
      .offer-badge {
        margin: 5px;

        &.inline {
          display: inline-block;
        }
      }

      .btn,
      .btn-primary,
      .legacy-button {
        margin-right: 10px;

        &:last-child {
          margin-right: 0;
        }
      }
    }
  }
}

// Complex multi-level nesting
.complex-layout {
  .header {
    .nav-tabs {
      .tab-nav-item {
        .pill-with-badge {
          font-size: 12px;

          &.notification {
            .count {
              position: absolute;
              top: -5px;
              right: -5px;
            }
          }
        }
      }
    }
  }

  .main-content {
    .modal {
      .card {
        .form-control-tabs-segmented {
          .custom-control-checkbox {
            .loading {
              margin-left: 10px;
            }
          }
        }
      }
    }
  }

  .sidebar {
    .collapsible-container {
      .sports-pill,
      .offer-badge {
        display: block;
        margin-bottom: 5px;

        &:hover {
          .badge-circle {
            transform: scale(1.1);
          }
        }
      }
    }
  }
}

// Media query combinations
@media (max-width: 768px) {
  .mobile-specific {
    .pill-with-badge,
    .pill-with-badge-v2,
    .sports-pill,
    .offer-badge {
      display: block;
      width: 100%;
      text-align: center;
      margin-bottom: 10px;
    }

    .btn,
    .btn-primary,
    .legacy-button {
      width: 100%;
      margin-bottom: 10px;
    }

    .form-control-tabs-segmented,
    .form-control-tabs-segmented-v2,
    .form-control-tabs-segmented-v3,
    .form-control-tabs-segmented-v4 {
      flex-direction: column;

      .custom-control-checkbox,
      .custom-control-radio,
      .custom-control-switcher {
        margin-bottom: 10px;
      }
    }
  }
}

// Keyframe animations for deprecated classes
@keyframes pulse {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

// Pseudo-element combinations
.enhanced-deprecated {
  .pill-with-badge::before,
  .sports-pill::before,
  .offer-badge::before {
    content: '⚠️';
    margin-right: 5px;
  }

  .btn::after,
  .btn-primary::after,
  .legacy-button::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 2px;
    background: linear-gradient(90deg, transparent, currentColor, transparent);
    opacity: 0;
    transition: opacity 0.3s;
  }

  .btn:hover::after,
  .btn-primary:hover::after,
  .legacy-button:hover::after {
    opacity: 1;
  }
}

```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/angular-mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
import { PROMPTS, PROMPTS_IMPL } from './prompts/prompt-registry.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import {
  CallToolRequest,
  CallToolRequestSchema,
  GetPromptRequestSchema,
  GetPromptResult,
  ListPromptsRequestSchema,
  ListPromptsResult,
  ListResourcesRequestSchema,
  ListResourcesResult,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { TOOLS } from './tools/tools.js';
import { toolNotFound } from './tools/utils.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import {
  AngularMcpServerOptionsSchema,
  AngularMcpServerOptions,
} from './validation/angular-mcp-server-options.schema.js';
import { validateAngularMcpServerFilesExist } from './validation/file-existence.js';
import { validateDeprecatedCssClassesFile } from './validation/ds-components-file.validation.js';

export class AngularMcpServerWrapper {
  private readonly mcpServer: McpServer;
  private readonly workspaceRoot: string;
  private readonly storybookDocsRoot?: string;
  private readonly deprecatedCssClassesPath?: string;
  private readonly uiRoot: string;

  /**
   * Private constructor - use AngularMcpServerWrapper.create() instead.
   * Config is already validated when this constructor is called.
   */
  private constructor(config: AngularMcpServerOptions) {
    // Config is already validated, no need to validate again
    const { workspaceRoot, ds } = config;

    this.workspaceRoot = workspaceRoot;
    this.storybookDocsRoot = ds.storybookDocsRoot;
    this.deprecatedCssClassesPath = ds.deprecatedCssClassesPath;
    this.uiRoot = ds.uiRoot;

    this.mcpServer = new McpServer({
      name: 'Angular MCP',
      version: '0.0.0',
    });

    this.mcpServer.server.registerCapabilities({
      prompts: {},
      tools: {},
      resources: {},
    });
    this.registerPrompts();
    this.registerTools();
    this.registerResources();
  }

  /**
   * Creates and validates an AngularMcpServerWrapper instance.
   * This is the recommended way to create an instance as it performs all necessary validations.
   *
   * @param config - The Angular MCP server configuration options
   * @returns A Promise that resolves to a fully configured AngularMcpServerWrapper instance
   * @throws {Error} If configuration validation fails or required files don't exist
   */
  static async create(
    config: AngularMcpServerOptions,
  ): Promise<AngularMcpServerWrapper> {
    // Validate config using the Zod schema - only once here
    const validatedConfig = AngularMcpServerOptionsSchema.parse(config);

    // Validate file existence (optional keys are checked only when provided)
    validateAngularMcpServerFilesExist(validatedConfig);

    // Load and validate deprecatedCssClassesPath content only if provided
    if (validatedConfig.ds.deprecatedCssClassesPath) {
      await validateDeprecatedCssClassesFile(validatedConfig);
    }

    return new AngularMcpServerWrapper(validatedConfig);
  }

  getMcpServer(): McpServer {
    return this.mcpServer;
  }

  private registerResources() {
    this.mcpServer.server.setRequestHandler(
      ListResourcesRequestSchema,
      async (): Promise<ListResourcesResult> => {
        const resources = [];

        // Try to read the llms.txt file from the package root (optional)
        try {
          const filePath = path.resolve(__dirname, '../../llms.txt');

          // Only attempt to read if file exists
          if (fs.existsSync(filePath)) {
            console.log('Reading llms.txt from:', filePath);
            const content = fs.readFileSync(filePath, 'utf-8');
            const lines = content.split('\n');

            let currentSection = '';

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

              // Skip empty lines and comments that don't start with #
              if (!line || (line.startsWith('#') && !line.includes(':'))) {
                continue;
              }

              // Update section if line starts with #
              if (line.startsWith('# ')) {
                currentSection = line.substring(2).replace(':', '').trim();
                continue;
              }

              // Parse markdown links: [name](url)
              const linkMatch = line.match(/- \[(.*?)\]\((.*?)\):(.*)/);
              if (linkMatch) {
                const [, name, uri, description = ''] = linkMatch;
                resources.push({
                  uri,
                  name: name.trim(),
                  type: currentSection.toLowerCase(),
                  content: description.trim() || name.trim(),
                });
                continue;
              }

              // Parse simple links: - [name](url)
              const simpleLinkMatch = line.match(/- \[(.*?)\]\((.*?)\)/);
              if (simpleLinkMatch) {
                const [, name, uri] = simpleLinkMatch;
                resources.push({
                  uri,
                  name: name.trim(),
                  type: currentSection.toLowerCase(),
                  content: name.trim(),
                });
              }
            }
          } else {
            console.log('llms.txt not found at:', filePath, '(skipping)');
          }
        } catch (ctx: unknown) {
          if (ctx instanceof Error) {
            console.error('Error reading llms.txt (non-fatal):', ctx.message);
          }
        }

        // Scan available design system components to add them as discoverable resources
        try {
          if (this.storybookDocsRoot) {
            const dsUiPath = path.resolve(
              process.cwd(),
              this.storybookDocsRoot,
            );
            if (fs.existsSync(dsUiPath)) {
              const componentFolders = fs
                .readdirSync(dsUiPath, { withFileTypes: true })
                .filter((dirent) => dirent.isDirectory())
                .map((dirent) => dirent.name);

              for (const folder of componentFolders) {
                // Convert kebab-case to PascalCase with 'Ds' prefix
                const componentName =
                  'Ds' +
                  folder
                    .split('-')
                    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
                    .join('');

                resources.push({
                  uri: `ds-component://${folder}`,
                  name: componentName,
                  type: 'design-system-component',
                  content: `Design System component: ${componentName}`,
                });
              }
            }
          }
        } catch (ctx: unknown) {
          if (ctx instanceof Error) {
            console.error(
              'Error scanning DS components (non-fatal):',
              ctx.message,
            );
          }
        }

        return {
          resources,
        };
      },
    );
  }

  private registerPrompts() {
    this.mcpServer.server.setRequestHandler(
      ListPromptsRequestSchema,
      async (): Promise<ListPromptsResult> => {
        return {
          prompts: Object.values(PROMPTS),
        };
      },
    );

    this.mcpServer.server.setRequestHandler(
      GetPromptRequestSchema,
      async (request): Promise<GetPromptResult> => {
        const prompt = PROMPTS[request.params.name];
        if (!prompt) {
          throw new Error(`Prompt not found: ${request.params.name}`);
        }

        const promptResult = PROMPTS_IMPL[request.params.name];
        // Register all prompts
        if (promptResult && promptResult.text) {
          return {
            messages: [
              {
                role: 'user',
                content: {
                  type: 'text',
                  text: promptResult.text(request.params.arguments ?? {}),
                },
              },
            ],
          };
        }
        throw new Error('Prompt implementation not found');
      },
    );
  }

  private registerTools() {
    this.mcpServer.server.setRequestHandler(
      ListToolsRequestSchema,
      async () => {
        return {
          tools: TOOLS.map(({ schema }) => schema),
        };
      },
    );

    this.mcpServer.server.setRequestHandler(
      CallToolRequestSchema,
      async (request: CallToolRequest) => {
        const tool = TOOLS.find(
          ({ schema }) => request.params.name === schema.name,
        );

        if (tool?.schema && tool.schema.name === request.params.name) {
          return await tool.handler({
            ...request,
            params: {
              ...request.params,
              arguments: {
                ...request.params.arguments,
                storybookDocsRoot: this.storybookDocsRoot,
                deprecatedCssClassesPath: this.deprecatedCssClassesPath,
                uiRoot: this.uiRoot,
                cwd: this.workspaceRoot,
                workspaceRoot: this.workspaceRoot,
              },
            },
          });
        }

        return {
          content: [toolNotFound(request)],
          isError: false,
        };
      },
    );
  }
}

```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-usage-graph/utils/unified-ast-analyzer.ts:
--------------------------------------------------------------------------------

```typescript
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import { toUnixPath } from '@code-pushup/utils';

import {
  DependencyInfo,
  FileInfo,
  ComponentMetadata,
  FileExtension,
} from '../models/types.js';
import {
  DEPENDENCY_ANALYSIS_CONFIG,
  REGEX_PATTERNS,
  getCombinedComponentImportRegex,
} from '../models/config.js';
import { isExternal, resolveDependencyPath } from './path-resolver.js';

const DEP_REGEX_TABLE: Array<[RegExp, DependencyInfo['type']]> = [
  [REGEX_PATTERNS.ES6_IMPORT, 'import'],
  [REGEX_PATTERNS.COMMONJS_REQUIRE, 'require'],
  [REGEX_PATTERNS.DYNAMIC_IMPORT, 'dynamic-import'],
];

const STYLE_REGEX_TABLE: Array<
  [RegExp, DependencyInfo['type'], ((p: string) => boolean)?]
> = [
  [REGEX_PATTERNS.CSS_IMPORT, 'css-import'],
  [
    REGEX_PATTERNS.CSS_URL,
    'asset',
    (u) => !u.startsWith('http') && !u.startsWith('data:'),
  ],
];

export interface UnifiedAnalysisResult {
  dependencies: DependencyInfo[];
  componentMetadata?: ComponentMetadata;
  importedComponentNames: string[];
  isAngularComponent: boolean;
}

export async function analyzeFileWithUnifiedAST(
  filePath: string,
  basePath: string,
  componentNamesForReverseDeps?: string[],
): Promise<UnifiedAnalysisResult> {
  const content = await fs.promises.readFile(filePath, 'utf-8');

  try {
    return analyzeContentWithUnifiedAST(
      content,
      filePath,
      basePath,
      componentNamesForReverseDeps,
    );
  } catch {
    return analyzeContentWithRegexFallback(
      content,
      filePath,
      basePath,
      componentNamesForReverseDeps,
    );
  }
}

function analyzeContentWithUnifiedAST(
  content: string,
  filePath: string,
  basePath: string,
  componentNamesForReverseDeps?: string[],
): UnifiedAnalysisResult {
  const sourceFile = ts.createSourceFile(
    filePath,
    content,
    ts.ScriptTarget.Latest,
    true,
  );

  const result: UnifiedAnalysisResult = {
    dependencies: [],
    importedComponentNames: [],
    isAngularComponent: false,
  };

  const componentNameSet = componentNamesForReverseDeps
    ? new Set(componentNamesForReverseDeps)
    : new Set<string>();
  let componentClassName: string | undefined;

  const visit = (node: ts.Node): void => {
    if (ts.isImportDeclaration(node) && node.moduleSpecifier) {
      if (ts.isStringLiteral(node.moduleSpecifier)) {
        const importPath = node.moduleSpecifier.text;
        result.dependencies.push(
          createDependencyInfo(importPath, 'import', filePath, basePath),
        );

        if (
          componentNamesForReverseDeps &&
          componentNamesForReverseDeps.length > 0 &&
          node.importClause
        ) {
          extractComponentImportsFromImportNode(
            node,
            componentNameSet,
            result.importedComponentNames,
          );
        }
      }
    } else if (ts.isCallExpression(node)) {
      if (
        ts.isIdentifier(node.expression) &&
        node.expression.text === 'require' &&
        node.arguments.length === 1 &&
        ts.isStringLiteral(node.arguments[0])
      ) {
        const importPath = node.arguments[0].text;
        result.dependencies.push(
          createDependencyInfo(importPath, 'require', filePath, basePath),
        );
      } else if (
        node.expression.kind === ts.SyntaxKind.ImportKeyword &&
        node.arguments.length === 1 &&
        ts.isStringLiteral(node.arguments[0])
      ) {
        const importPath = node.arguments[0].text;
        result.dependencies.push(
          createDependencyInfo(
            importPath,
            'dynamic-import',
            filePath,
            basePath,
          ),
        );
      }
    } else if (ts.isClassDeclaration(node) && node.name) {
      const hasComponentDecorator = ts
        .getDecorators?.(node as ts.HasDecorators)
        ?.some((decorator) => {
          if (ts.isCallExpression(decorator.expression)) {
            return (
              ts.isIdentifier(decorator.expression.expression) &&
              decorator.expression.expression.text === 'Component'
            );
          }
          return (
            ts.isIdentifier(decorator.expression) &&
            decorator.expression.text === 'Component'
          );
        });

      if (hasComponentDecorator) {
        result.isAngularComponent = true;
        componentClassName = node.name.text;
      }
    }

    ts.forEachChild(node, visit);
  };

  visit(sourceFile);

  if (result.isAngularComponent && componentClassName) {
    result.componentMetadata = {
      className: componentClassName,
    };
  }

  return result;
}

function extractComponentImportsFromImportNode(
  importNode: ts.ImportDeclaration,
  componentNameSet: Set<string>,
  foundComponents: string[],
): void {
  const importClause = importNode.importClause;
  if (!importClause) return;

  if (
    importClause.namedBindings &&
    ts.isNamedImports(importClause.namedBindings)
  ) {
    for (const element of importClause.namedBindings.elements) {
      const importName = element.name.text;
      if (componentNameSet.has(importName)) {
        foundComponents.push(importName);
      }
    }
  }

  if (importClause.name) {
    const importName = importClause.name.text;
    if (componentNameSet.has(importName)) {
      foundComponents.push(importName);
    }
  }
}

function analyzeContentWithRegexFallback(
  content: string,
  filePath: string,
  basePath: string,
  componentNamesForReverseDeps?: string[],
): UnifiedAnalysisResult {
  const result: UnifiedAnalysisResult = {
    dependencies: [],
    importedComponentNames: [],
    isAngularComponent: false,
  };

  DEP_REGEX_TABLE.forEach(([regex, type]) => {
    regex.lastIndex = 0;
    let match: RegExpExecArray | null;
    while ((match = regex.exec(content))) {
      const importPath = match[1] || match[2];
      result.dependencies.push(
        createDependencyInfo(importPath, type, filePath, basePath),
      );
    }
  });

  result.isAngularComponent =
    REGEX_PATTERNS.ANGULAR_COMPONENT_DECORATOR.test(content);

  if (result.isAngularComponent) {
    const classMatch = content.match(/export\s+class\s+(\w+)/);
    if (classMatch) {
      result.componentMetadata = {
        className: classMatch[1],
      };
    }
  }

  if (componentNamesForReverseDeps && componentNamesForReverseDeps.length > 0) {
    const combinedImportRegex = getCombinedComponentImportRegex(
      componentNamesForReverseDeps,
    );
    const matches = Array.from(content.matchAll(combinedImportRegex));
    result.importedComponentNames = matches
      .map((match) => match[1])
      .filter(Boolean);
  }

  return result;
}

/**
 * Enhanced version of analyzeFileOptimized that uses unified AST analysis
 */
export async function analyzeFileWithUnifiedOptimization(
  filePath: string,
  basePath: string,
): Promise<FileInfo> {
  const stats = await fs.promises.stat(filePath);
  const ext = path.extname(filePath);

  const { stylesExtensions, scriptExtensions } = DEPENDENCY_ANALYSIS_CONFIG;

  let dependencies: DependencyInfo[] = [];
  let isAngularComponent = false;
  let componentName: string | undefined;

  if (scriptExtensions.includes(ext as any)) {
    const unifiedResult = await analyzeFileWithUnifiedAST(filePath, basePath);
    dependencies = unifiedResult.dependencies;
    isAngularComponent = unifiedResult.isAngularComponent;
    componentName = unifiedResult.componentMetadata?.className;
  } else if (stylesExtensions.includes(ext as any)) {
    dependencies = parseStyleDependencies(
      await fs.promises.readFile(filePath, 'utf-8'),
      filePath,
      basePath,
    );
  }

  return {
    type:
      DEPENDENCY_ANALYSIS_CONFIG.fileTypeMap[ext as FileExtension] || 'unknown',
    size: stats.size,
    dependencies,
    lastModified: stats.mtime.getTime(),
    isAngularComponent,
    componentName,
  };
}

export async function extractComponentImportsUnified(
  filePath: string,
  componentNames: string[],
): Promise<string[]> {
  if (componentNames.length === 0) {
    return [];
  }

  try {
    const content = await fs.promises.readFile(filePath, 'utf-8');
    const result = analyzeContentWithUnifiedAST(
      content,
      filePath,
      '',
      componentNames,
    );
    return Array.from(new Set(result.importedComponentNames));
  } catch {
    return [];
  }
}

function createDependencyInfo(
  importPath: string,
  type: DependencyInfo['type'],
  filePath: string,
  basePath: string,
): DependencyInfo {
  if (isExternal(importPath)) {
    return {
      path: importPath,
      type: 'external',
      resolved: false,
    };
  }

  const resolvedPath = resolveDependencyPath(importPath, filePath, basePath);

  return {
    path: importPath,
    type,
    resolved: resolvedPath !== null,
    resolvedPath: resolvedPath ? toUnixPath(resolvedPath) : undefined,
  };
}

function parseStyleDependencies(
  content: string,
  filePath: string,
  basePath: string,
): DependencyInfo[] {
  const dependencies: DependencyInfo[] = [];

  STYLE_REGEX_TABLE.forEach(([regex, type, filter]) => {
    regex.lastIndex = 0;
    let match: RegExpExecArray | null;
    while ((match = regex.exec(content))) {
      const importPath = match[1] || match[2];
      if (!filter || filter(importPath)) {
        dependencies.push(
          createDependencyInfo(importPath, type, filePath, basePath),
        );
      }
    }
  });

  return dependencies;
}

```
Page 5/7FirstPrevNextLast