#
tokens: 49652/50000 16/473 files (page 6/10)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 6 of 10. Use http://codebase.md/push-based/angular-toolkit-mcp?lines=true&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/minimal-repo/packages/application/src/app/styles/bad-global-styles.scss:
--------------------------------------------------------------------------------

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

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

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

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

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

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

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

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

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

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

```scss
  1 | // Extended and more meaningful classes
  2 | .pill-with-badge {
  3 |   color: red;
  4 |   border: 1px solid #ccc;
  5 |   padding: 5px 10px;
  6 |   border-radius: 15px;
  7 |   display: inline-block;
  8 | }
  9 | 
 10 | .pill-with-badge-v2 {
 11 |   color: blue;
 12 |   border: 2px solid #aaa;
 13 |   padding: 6px 12px;
 14 |   border-radius: 20px;
 15 |   display: inline-block;
 16 | }
 17 | 
 18 | .sports-pill {
 19 |   color: green;
 20 |   background-color: #f0f0f0;
 21 |   padding: 8px 16px;
 22 |   border-radius: 25px;
 23 |   display: inline-block;
 24 | }
 25 | 
 26 | .offer-badge {
 27 |   color: yellow;
 28 |   background-color: #333;
 29 |   padding: 4px 8px;
 30 |   border-radius: 10px;
 31 |   display: inline-block;
 32 | }
 33 | 
 34 | .tab-nav {
 35 |   color: orange;
 36 |   background-color: #fff;
 37 |   padding: 10px;
 38 |   border-bottom: 2px solid #ddd;
 39 | }
 40 | 
 41 | .nav-tabs {
 42 |   color: purple;
 43 |   background-color: #eee;
 44 |   padding: 10px;
 45 |   border-bottom: 2px solid #ccc;
 46 | }
 47 | 
 48 | .tab-nav-item {
 49 |   color: pink;
 50 |   padding: 10px 15px;
 51 |   border-radius: 5px;
 52 |   display: inline-block;
 53 |   cursor: pointer;
 54 | }
 55 | 
 56 | .btn {
 57 |   color: brown;
 58 |   background-color: #f5f5f5;
 59 |   padding: 10px 20px;
 60 |   border: none;
 61 |   border-radius: 5px;
 62 |   cursor: pointer;
 63 | }
 64 | 
 65 | .btn-primary {
 66 |   color: cyan;
 67 |   background-color: #007bff;
 68 |   padding: 10px 20px;
 69 |   border: none;
 70 |   border-radius: 5px;
 71 |   cursor: pointer;
 72 | }
 73 | 
 74 | .legacy-button {
 75 |   color: magenta;
 76 |   background-color: #f8f9fa;
 77 |   padding: 10px 20px;
 78 |   border: 1px solid #ccc;
 79 |   border-radius: 5px;
 80 |   cursor: pointer;
 81 | }
 82 | 
 83 | .modal {
 84 |   color: lime;
 85 |   background-color: #fff;
 86 |   padding: 20px;
 87 |   border-radius: 10px;
 88 |   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 89 | }
 90 | 
 91 | .card {
 92 |   color: olive;
 93 |   background-color: #f8f9fa;
 94 |   padding: 15px;
 95 |   border-radius: 5px;
 96 |   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 97 | }
 98 | 
 99 | .loading {
100 |   color: teal;
101 |   font-size: 16px;
102 |   display: flex;
103 |   align-items: center;
104 |   justify-content: center;
105 | }
106 | 
107 | .loading-v2 {
108 |   color: navy;
109 |   font-size: 18px;
110 |   display: flex;
111 |   align-items: center;
112 |   justify-content: center;
113 | }
114 | 
115 | .loading-v3 {
116 |   color: maroon;
117 |   font-size: 20px;
118 |   display: flex;
119 |   align-items: center;
120 |   justify-content: center;
121 | }
122 | 
123 | .collapsible-container {
124 |   color: silver;
125 |   background-color: #f0f0f0;
126 |   padding: 10px;
127 |   border-radius: 5px;
128 |   overflow: hidden;
129 | }
130 | 
131 | .divider {
132 |   color: gray;
133 |   border-top: 1px solid #ccc;
134 |   margin: 10px 0;
135 | }
136 | 
137 | .count {
138 |   color: gold;
139 |   background-color: #333;
140 |   padding: 5px 10px;
141 |   border-radius: 50%;
142 |   display: inline-block;
143 | }
144 | 
145 | .badge-circle {
146 |   color: coral;
147 |   background-color: #f0f0f0;
148 |   padding: 5px 10px;
149 |   border-radius: 50%;
150 |   display: inline-block;
151 | }
152 | 
153 | .custom-control-checkbox {
154 |   color: khaki;
155 |   display: flex;
156 |   align-items: center;
157 | }
158 | 
159 | .custom-control-radio {
160 |   color: lavender;
161 |   display: flex;
162 |   align-items: center;
163 | }
164 | 
165 | .form-control-tabs-segmented-v2 {
166 |   color: salmon;
167 |   background-color: #fff;
168 |   padding: 10px;
169 |   border-radius: 5px;
170 |   display: flex;
171 |   justify-content: space-between;
172 | }
173 | 
174 | .form-control-tabs-segmented-flex {
175 |   color: sienna;
176 |   background-color: #f8f9fa;
177 |   padding: 10px;
178 |   border-radius: 5px;
179 |   display: flex;
180 |   justify-content: space-between;
181 | }
182 | 
183 | .form-control-tabs-segmented-v2-dark {
184 |   color: tan;
185 |   background-color: #333;
186 |   padding: 10px;
187 |   border-radius: 5px;
188 |   display: flex;
189 |   justify-content: space-between;
190 | }
191 | 
192 | .form-control-tabs-segmented-v3 {
193 |   color: turquoise;
194 |   background-color: #fff;
195 |   padding: 10px;
196 |   border-radius: 5px;
197 |   display: flex;
198 |   justify-content: space-between;
199 | }
200 | 
201 | .form-control-tabs-segmented-v4 {
202 |   color: violet;
203 |   background-color: #f8f9fa;
204 |   padding: 10px;
205 |   border-radius: 5px;
206 |   display: flex;
207 |   justify-content: space-between;
208 | }
209 | 
210 | .form-control-tabs-segmented {
211 |   color: wheat;
212 |   background-color: #fff;
213 |   padding: 10px;
214 |   border-radius: 5px;
215 |   display: flex;
216 |   justify-content: space-between;
217 | }
218 | 
219 | .custom-control-switcher {
220 |   color: azure;
221 |   display: flex;
222 |   align-items: center;
223 | }
224 | 
225 | // 50 more random classes
226 | .random-class-1 {
227 |   background-color: #f0f0f0;
228 | }
229 | .random-class-2 {
230 |   background-color: #e0e0e0;
231 | }
232 | .random-class-3 {
233 |   background-color: #d0d0d0;
234 | }
235 | .random-class-4 {
236 |   background-color: #c0c0c0;
237 | }
238 | .random-class-5 {
239 |   background-color: #b0b0b0;
240 | }
241 | .random-class-6 {
242 |   background-color: #a0a0a0;
243 | }
244 | .random-class-7 {
245 |   background-color: #909090;
246 | }
247 | .random-class-8 {
248 |   background-color: #808080;
249 | }
250 | .random-class-9 {
251 |   background-color: #707070;
252 | }
253 | .random-class-10 {
254 |   background-color: #606060;
255 | }
256 | .random-class-11 {
257 |   background-color: #505050;
258 | }
259 | .random-class-12 {
260 |   background-color: #404040;
261 | }
262 | .random-class-13 {
263 |   background-color: #303030;
264 | }
265 | .random-class-14 {
266 |   background-color: #202020;
267 | }
268 | .random-class-15 {
269 |   background-color: #101010;
270 | }
271 | .random-class-16 {
272 |   background-color: #f8f8f8;
273 | }
274 | .random-class-17 {
275 |   background-color: #e8e8e8;
276 | }
277 | .random-class-18 {
278 |   background-color: #d8d8d8;
279 | }
280 | .random-class-19 {
281 |   background-color: #c8c8c8;
282 | }
283 | .random-class-20 {
284 |   background-color: #b8b8b8;
285 | }
286 | .random-class-21 {
287 |   background-color: #a8a8a8;
288 | }
289 | .random-class-22 {
290 |   background-color: #989898;
291 | }
292 | .random-class-23 {
293 |   background-color: #888888;
294 | }
295 | .random-class-24 {
296 |   background-color: #787878;
297 | }
298 | .random-class-25 {
299 |   background-color: #686868;
300 | }
301 | .random-class-26 {
302 |   background-color: #585858;
303 | }
304 | .random-class-27 {
305 |   background-color: #484848;
306 | }
307 | .random-class-28 {
308 |   background-color: #383838;
309 | }
310 | .random-class-29 {
311 |   background-color: #282828;
312 | }
313 | .random-class-30 {
314 |   background-color: #181818;
315 | }
316 | .random-class-31 {
317 |   background-color: #080808;
318 | }
319 | .random-class-32 {
320 |   background-color: #fefefe;
321 | }
322 | .random-class-33 {
323 |   background-color: #ededed;
324 | }
325 | .random-class-34 {
326 |   background-color: #dcdcdc;
327 | }
328 | .random-class-35 {
329 |   background-color: #cbcbcb;
330 | }
331 | .random-class-36 {
332 |   background-color: #bababa;
333 | }
334 | .random-class-37 {
335 |   background-color: #a9a9a9;
336 | }
337 | .random-class-38 {
338 |   background-color: #989898;
339 | }
340 | .random-class-39 {
341 |   background-color: #878787;
342 | }
343 | .random-class-40 {
344 |   background-color: #767676;
345 | }
346 | .random-class-41 {
347 |   background-color: #656565;
348 | }
349 | .random-class-42 {
350 |   background-color: #545454;
351 | }
352 | .random-class-43 {
353 |   background-color: #434343;
354 | }
355 | .random-class-44 {
356 |   background-color: #323232;
357 | }
358 | .random-class-45 {
359 |   background-color: #212121;
360 | }
361 | .random-class-46 {
362 |   background-color: #101010;
363 | }
364 | .random-class-47 {
365 |   background-color: #f7f7f7;
366 | }
367 | .random-class-48 {
368 |   background-color: #e6e6e6;
369 | }
370 | .random-class-49 {
371 |   background-color: #d5d5d5;
372 | }
373 | .random-class-50 {
374 |   background-color: #c4c4c4;
375 | }
376 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component/get-ds-component-data.tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolSchemaOptions } from '@push-based/models';
  2 | import {
  3 |   createHandler,
  4 |   BaseHandlerOptions,
  5 | } from '../shared/utils/handler-helpers.js';
  6 | import { COMMON_ANNOTATIONS } from '../shared/models/schema-helpers.js';
  7 | import { getComponentPathsInfo } from './utils/paths-helpers.js';
  8 | import { getComponentDocPathsForName } from './utils/doc-helpers.js';
  9 | import {
 10 |   validateComponentName,
 11 |   componentNameToKebabCase,
 12 | } from '../shared/utils/component-validation.js';
 13 | import { resolveCrossPlatformPath } from '../shared/utils/cross-platform-path.js';
 14 | import * as fs from 'fs';
 15 | import * as path from 'path';
 16 | 
 17 | interface DsComponentDataOptions extends BaseHandlerOptions {
 18 |   componentName: string;
 19 |   sections?: string[];
 20 | }
 21 | 
 22 | interface DsComponentData {
 23 |   componentName: string;
 24 |   implementation: string[];
 25 |   documentation: string[];
 26 |   stories: string[];
 27 |   importPath: string;
 28 | }
 29 | 
 30 | export const getDsComponentDataToolSchema: ToolSchemaOptions = {
 31 |   name: 'get-ds-component-data',
 32 |   description: `Return comprehensive data for a DS component including implementation files, documentation files, stories files, and import path.`,
 33 |   inputSchema: {
 34 |     type: 'object',
 35 |     properties: {
 36 |       componentName: {
 37 |         type: 'string',
 38 |         description:
 39 |           'The class name of the component to get data for (e.g., DsBadge)',
 40 |       },
 41 |       sections: {
 42 |         type: 'array',
 43 |         items: {
 44 |           type: 'string',
 45 |           enum: ['implementation', 'documentation', 'stories', 'all'],
 46 |         },
 47 |         description:
 48 |           'Sections to include in the response. Options: "implementation", "documentation", "stories", "all". Defaults to ["all"] if not specified.',
 49 |         default: ['all'],
 50 |       },
 51 |     },
 52 |     required: ['componentName'],
 53 |   },
 54 |   annotations: {
 55 |     title: 'Get Design System Component Data',
 56 |     ...COMMON_ANNOTATIONS.readOnly,
 57 |   },
 58 | };
 59 | 
 60 | function getAllFilesInDirectory(dirPath: string): string[] {
 61 |   const files: string[] = [];
 62 | 
 63 |   function walkDirectory(currentPath: string) {
 64 |     try {
 65 |       const items = fs.readdirSync(currentPath);
 66 | 
 67 |       for (const item of items) {
 68 |         const fullPath = path.join(currentPath, item);
 69 |         const stat = fs.statSync(fullPath);
 70 | 
 71 |         if (stat.isDirectory()) {
 72 |           walkDirectory(fullPath);
 73 |         } else {
 74 |           files.push(fullPath);
 75 |         }
 76 |       }
 77 |     } catch {
 78 |       return;
 79 |     }
 80 |   }
 81 | 
 82 |   if (fs.existsSync(dirPath)) {
 83 |     walkDirectory(dirPath);
 84 |   }
 85 | 
 86 |   return files;
 87 | }
 88 | 
 89 | function findStoriesFiles(componentPath: string): string[] {
 90 |   const storiesFiles: string[] = [];
 91 | 
 92 |   try {
 93 |     if (fs.existsSync(componentPath)) {
 94 |       const items = fs.readdirSync(componentPath);
 95 | 
 96 |       for (const item of items) {
 97 |         const fullPath = path.join(componentPath, item);
 98 |         const stat = fs.statSync(fullPath);
 99 | 
100 |         if (stat.isFile() && item.endsWith('.stories.ts')) {
101 |           storiesFiles.push(fullPath);
102 |         }
103 |       }
104 |     }
105 |   } catch {
106 |     return storiesFiles;
107 |   }
108 | 
109 |   return storiesFiles;
110 | }
111 | 
112 | export const getDsComponentDataHandler = createHandler<
113 |   DsComponentDataOptions,
114 |   DsComponentData
115 | >(
116 |   getDsComponentDataToolSchema.name,
117 |   async (
118 |     { componentName, sections = ['all'] },
119 |     { cwd, uiRoot, storybookDocsRoot },
120 |   ) => {
121 |     try {
122 |       validateComponentName(componentName);
123 | 
124 |       const includeAll = sections.includes('all');
125 |       const includeImplementation =
126 |         includeAll || sections.includes('implementation');
127 |       const includeDocumentation =
128 |         includeAll || sections.includes('documentation');
129 |       const includeStories = includeAll || sections.includes('stories');
130 | 
131 |       const pathsInfo = getComponentPathsInfo(componentName, uiRoot, cwd);
132 | 
133 |       let implementationFiles: string[] = [];
134 |       if (includeImplementation) {
135 |         const srcFiles = getAllFilesInDirectory(pathsInfo.srcPath);
136 |         implementationFiles = srcFiles.map((file) => `file://${file}`);
137 |       }
138 | 
139 |       const documentationFiles: string[] = [];
140 | 
141 |       let storiesFilePaths: string[] = [];
142 |       if (storybookDocsRoot) {
143 |         const docsBasePath = resolveCrossPlatformPath(cwd, storybookDocsRoot);
144 | 
145 |         if (includeDocumentation) {
146 |           const docPaths = getComponentDocPathsForName(
147 |             docsBasePath,
148 |             componentName,
149 |           );
150 | 
151 |           if (fs.existsSync(docPaths.paths.api)) {
152 |             documentationFiles.push(`file://${docPaths.paths.api}`);
153 |           }
154 |           if (fs.existsSync(docPaths.paths.overview)) {
155 |             documentationFiles.push(`file://${docPaths.paths.overview}`);
156 |           }
157 |         }
158 | 
159 |         if (includeStories) {
160 |           const componentFolderName = componentNameToKebabCase(componentName);
161 |           const storiesComponentFolderPath = path.join(
162 |             docsBasePath,
163 |             componentFolderName,
164 |           );
165 |           const storiesFiles = findStoriesFiles(storiesComponentFolderPath);
166 |           storiesFilePaths = storiesFiles.map((file) => `file://${file}`);
167 |         }
168 |       }
169 | 
170 |       return {
171 |         componentName,
172 |         implementation: implementationFiles,
173 |         documentation: documentationFiles,
174 |         stories: storiesFilePaths,
175 |         importPath: pathsInfo.importPath,
176 |       };
177 |     } catch (ctx) {
178 |       throw new Error(
179 |         `Error retrieving component data: ${(ctx as Error).message}`,
180 |       );
181 |     }
182 |   },
183 |   (result) => {
184 |     const messages: string[] = [];
185 | 
186 |     if (result.implementation && result.implementation.length > 0) {
187 |       messages.push('Implementation');
188 |       messages.push('');
189 |       result.implementation.forEach((file: string) => {
190 |         messages.push(file);
191 |       });
192 |       messages.push('');
193 |     }
194 | 
195 |     if (result.documentation && result.documentation.length > 0) {
196 |       messages.push('Documentation');
197 |       messages.push('');
198 |       result.documentation.forEach((file: string) => {
199 |         messages.push(file);
200 |       });
201 |       messages.push('');
202 |     }
203 | 
204 |     if (result.stories && result.stories.length > 0) {
205 |       messages.push('Stories');
206 |       messages.push('');
207 |       result.stories.forEach((file: string) => {
208 |         messages.push(file);
209 |       });
210 |       messages.push('');
211 |     }
212 | 
213 |     if (result.importPath) {
214 |       messages.push('Import path');
215 |       messages.push('');
216 |       messages.push(result.importPath);
217 |     }
218 | 
219 |     return messages;
220 |   },
221 | );
222 | 
223 | export const getDsComponentDataTools = [
224 |   {
225 |     schema: getDsComponentDataToolSchema,
226 |     handler: getDsComponentDataHandler,
227 |   },
228 | ];
229 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/project/utils/dependencies-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as fs from 'fs';
  2 | import * as path from 'path';
  3 | import process from 'node:process';
  4 | import { getComponentPathsInfo } from '../../component/utils/paths-helpers.js';
  5 | import { resolveCrossPlatformPathAndValidateWithContext } from '../../shared/utils/cross-platform-path.js';
  6 | 
  7 | // Type definitions
  8 | export interface PackageJsonPeerDependencies {
  9 |   [packageName: string]: string;
 10 | }
 11 | 
 12 | export interface PackageJson {
 13 |   name?: string;
 14 |   version?: string;
 15 |   peerDependencies?: PackageJsonPeerDependencies;
 16 |   dependencies?: Record<string, string>;
 17 |   devDependencies?: Record<string, string>;
 18 |   [key: string]: unknown;
 19 | }
 20 | 
 21 | export interface ProjectAnalysisResult {
 22 |   packageJsonFound: true;
 23 |   packageJsonPath: string;
 24 |   projectJsonPath: string | null;
 25 |   isPublishable: boolean;
 26 |   peerDependencies: PackageJsonPeerDependencies;
 27 |   peerDependencyMissing: boolean;
 28 |   importPath?: string;
 29 |   message?: string;
 30 |   suggestedChange?: {
 31 |     importPath: string;
 32 |     message: string;
 33 |   };
 34 | }
 35 | 
 36 | export interface ProjectAnalysisNotFoundResult {
 37 |   packageJsonFound: false;
 38 |   searchedPath: string;
 39 |   message: string;
 40 | }
 41 | 
 42 | export type ProjectAnalysisResponse =
 43 |   | ProjectAnalysisResult
 44 |   | ProjectAnalysisNotFoundResult;
 45 | 
 46 | export interface ComponentMetadata {
 47 |   importPath?: string;
 48 |   [key: string]: unknown;
 49 | }
 50 | 
 51 | /**
 52 |  * Finds package.json by traversing up the directory tree
 53 |  */
 54 | export function findPackageJson(startPath: string): string | null {
 55 |   let currentPath = path.resolve(startPath);
 56 |   const rootPath = path.parse(currentPath).root;
 57 | 
 58 |   while (currentPath !== rootPath) {
 59 |     const packageJsonPath = path.join(currentPath, 'package.json');
 60 | 
 61 |     if (fs.existsSync(packageJsonPath)) {
 62 |       return packageJsonPath;
 63 |     }
 64 | 
 65 |     currentPath = path.dirname(currentPath);
 66 |   }
 67 | 
 68 |   return null;
 69 | }
 70 | 
 71 | /**
 72 |  * Finds project.json in the same directory as package.json or nearby
 73 |  */
 74 | export function findProjectJson(packageJsonDir: string): string | null {
 75 |   const projectJsonPath = path.join(packageJsonDir, 'project.json');
 76 | 
 77 |   if (fs.existsSync(projectJsonPath)) {
 78 |     return projectJsonPath;
 79 |   }
 80 | 
 81 |   return null;
 82 | }
 83 | 
 84 | /**
 85 |  * Reads and parses package.json file
 86 |  */
 87 | export function readPackageJson(packageJsonPath: string): PackageJson {
 88 |   try {
 89 |     const content = fs.readFileSync(packageJsonPath, 'utf-8');
 90 |     return JSON.parse(content) as PackageJson;
 91 |   } catch (ctx) {
 92 |     throw new Error(
 93 |       `Failed to read or parse package.json at ${packageJsonPath}: ${
 94 |         (ctx as Error).message
 95 |       }`,
 96 |     );
 97 |   }
 98 | }
 99 | 
100 | /**
101 |  * Checks if the library has peer dependencies (indicating it's buildable/publishable)
102 |  */
103 | export function hasPeerDependencies(packageJson: PackageJson): boolean {
104 |   return (
105 |     packageJson.peerDependencies !== undefined &&
106 |     typeof packageJson.peerDependencies === 'object' &&
107 |     Object.keys(packageJson.peerDependencies).length > 0
108 |   );
109 | }
110 | 
111 | /**
112 |  * Gets import path from component metadata
113 |  */
114 | export async function getComponentImportPath(
115 |   componentName: string,
116 |   cwd: string,
117 |   uiRoot: string,
118 | ): Promise<string | null> {
119 |   try {
120 |     const pathsInfo = getComponentPathsInfo(componentName, uiRoot, cwd);
121 |     return pathsInfo.importPath || null;
122 |   } catch {
123 |     return null;
124 |   }
125 | }
126 | 
127 | /**
128 |  * Builds the base project analysis result from package.json and project.json
129 |  */
130 | export function buildProjectAnalysisResult(
131 |   cwd: string,
132 |   packageJsonPath: string,
133 |   packageJson: PackageJson,
134 | ): ProjectAnalysisResult {
135 |   const packageJsonDir = path.dirname(packageJsonPath);
136 |   const hasPackagePeerDeps = hasPeerDependencies(packageJson);
137 |   const projectJsonPath = findProjectJson(packageJsonDir);
138 | 
139 |   return {
140 |     packageJsonFound: true,
141 |     packageJsonPath: path.relative(cwd, packageJsonPath),
142 |     projectJsonPath: projectJsonPath
143 |       ? path.relative(cwd, projectJsonPath)
144 |       : null,
145 |     isPublishable: hasPackagePeerDeps,
146 |     peerDependencies: packageJson.peerDependencies || {},
147 |     peerDependencyMissing: false,
148 |   };
149 | }
150 | 
151 | /**
152 |  * Handles peer dependencies analysis and component import path validation
153 |  */
154 | export async function handlePeerDependenciesAnalysis(
155 |   result: ProjectAnalysisResult,
156 |   componentName?: string,
157 |   cwd?: string,
158 |   uiRoot?: string,
159 | ): Promise<void> {
160 |   if (!result.isPublishable) {
161 |     result.message =
162 |       'Library has no peer dependencies - appears to be a normal library';
163 |     return;
164 |   }
165 | 
166 |   if (!componentName) {
167 |     result.message =
168 |       'Library has peer dependencies (publishable/buildable). Provide componentName to validate import path.';
169 |     return;
170 |   }
171 | 
172 |   if (!cwd) {
173 |     result.message = 'CWD is required for component import path validation.';
174 |     return;
175 |   }
176 | 
177 |   if (!uiRoot) {
178 |     result.message =
179 |       'UI root is required for component import path validation.';
180 |     return;
181 |   }
182 | 
183 |   // Try to get import path for the component
184 |   const importPath = await getComponentImportPath(componentName, cwd, uiRoot);
185 | 
186 |   if (!importPath || importPath.trim() === '') {
187 |     result.peerDependencyMissing = true;
188 |     result.suggestedChange = {
189 |       importPath: '*',
190 |       message:
191 |         'Component import path is missing or empty. This is required for publishable libraries.',
192 |     };
193 |   } else {
194 |     result.importPath = importPath;
195 |     result.message =
196 |       'Component import path found - library appears properly configured';
197 |   }
198 | }
199 | 
200 | /**
201 |  * Analyzes project dependencies and determines if library is buildable/publishable
202 |  */
203 | export async function analyzeProjectDependencies(
204 |   cwd: string,
205 |   directory: string,
206 |   componentName?: string,
207 |   workspaceRoot?: string,
208 |   uiRoot?: string,
209 | ): Promise<ProjectAnalysisResponse> {
210 |   // Parameter validation
211 |   if (!directory || typeof directory !== 'string') {
212 |     throw new Error('Directory parameter is required and must be a string');
213 |   }
214 | 
215 |   // Set working directory
216 |   process.chdir(cwd);
217 | 
218 |   // Validate target path exists
219 |   const targetPath = resolveCrossPlatformPathAndValidateWithContext(
220 |     cwd,
221 |     directory,
222 |     workspaceRoot,
223 |   );
224 | 
225 |   // Find package.json
226 |   const packageJsonPath = findPackageJson(targetPath);
227 | 
228 |   if (!packageJsonPath) {
229 |     return {
230 |       packageJsonFound: false,
231 |       searchedPath: targetPath,
232 |       message: 'No package.json found in the directory tree',
233 |     };
234 |   }
235 | 
236 |   // Read and parse package.json
237 |   const packageJson = readPackageJson(packageJsonPath);
238 | 
239 |   // Build base result
240 |   const result = buildProjectAnalysisResult(cwd, packageJsonPath, packageJson);
241 | 
242 |   // Handle peer dependencies analysis
243 |   await handlePeerDependenciesAnalysis(result, componentName, cwd, uiRoot);
244 | 
245 |   return result;
246 | }
247 | 
```

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

```typescript
  1 | import * as path from 'path';
  2 | import * as fs from 'fs';
  3 | import { toUnixPath } from '@code-pushup/utils';
  4 | 
  5 | /**
  6 |  * Enhanced path resolution with workspace root context for better debugging
  7 |  */
  8 | export function resolveCrossPlatformPathWithContext(
  9 |   basePath: string,
 10 |   relativePath: string,
 11 |   workspaceRoot?: string,
 12 | ): {
 13 |   resolved: string;
 14 |   context: {
 15 |     basePath: string;
 16 |     relativePath: string;
 17 |     workspaceRoot?: string;
 18 |     isBasePathSameAsWorkspaceRoot: boolean;
 19 |     relativeToWorkspaceRoot?: string;
 20 |   };
 21 | } {
 22 |   const normalizedRelative = relativePath.replace(/\\/g, '/');
 23 |   const resolved = path.resolve(basePath, normalizedRelative);
 24 |   const unixResolved = toUnixPath(resolved);
 25 | 
 26 |   const context = {
 27 |     basePath: toUnixPath(basePath),
 28 |     relativePath: normalizedRelative,
 29 |     workspaceRoot: workspaceRoot ? toUnixPath(workspaceRoot) : undefined,
 30 |     isBasePathSameAsWorkspaceRoot: workspaceRoot
 31 |       ? toUnixPath(basePath) === toUnixPath(workspaceRoot)
 32 |       : false,
 33 |     relativeToWorkspaceRoot: workspaceRoot
 34 |       ? path.relative(workspaceRoot, unixResolved)
 35 |       : undefined,
 36 |   };
 37 | 
 38 |   return { resolved: unixResolved, context };
 39 | }
 40 | 
 41 | /**
 42 |  * Enhanced validation with comprehensive workspace root context
 43 |  */
 44 | export function resolveCrossPlatformPathAndValidateWithContext(
 45 |   basePath: string,
 46 |   relativePath: string,
 47 |   workspaceRoot?: string,
 48 | ): string {
 49 |   const { resolved, context } = resolveCrossPlatformPathWithContext(
 50 |     basePath,
 51 |     relativePath,
 52 |     workspaceRoot,
 53 |   );
 54 | 
 55 |   // Convert to platform-specific for fs operations
 56 |   const fsPath = resolved.replace(/\//g, path.sep);
 57 | 
 58 |   if (!fs.existsSync(fsPath)) {
 59 |     let errorMessage =
 60 |       `Directory does not exist: ${relativePath}\n` +
 61 |       `Resolved to: ${resolved}\n` +
 62 |       `Base path: ${basePath}`;
 63 | 
 64 |     if (workspaceRoot) {
 65 |       errorMessage +=
 66 |         `\n` +
 67 |         `Workspace root: ${workspaceRoot}\n` +
 68 |         `Base path same as workspace root: ${context.isBasePathSameAsWorkspaceRoot}\n` +
 69 |         `Path relative to workspace root: ${context.relativeToWorkspaceRoot}`;
 70 | 
 71 |       if (!context.isBasePathSameAsWorkspaceRoot) {
 72 |         errorMessage +=
 73 |           `\n` +
 74 |           `⚠️  WARNING: Base path differs from workspace root!\n` +
 75 |           `   This might indicate a configuration issue.`;
 76 |       }
 77 |     }
 78 | 
 79 |     throw new Error(errorMessage);
 80 |   }
 81 | 
 82 |   return resolved;
 83 | }
 84 | 
 85 | /**
 86 |  * Resolves a relative path against a base path with cross-platform normalization.
 87 |  * Handles mixed path separators and ensures consistent Unix-style output.
 88 |  *
 89 |  * @param basePath - The base directory (usually absolute)
 90 |  * @param relativePath - The relative path to resolve
 91 |  * @returns Normalized absolute path with Unix-style separators
 92 |  */
 93 | export function resolveCrossPlatformPath(
 94 |   basePath: string,
 95 |   relativePath: string,
 96 | ): string {
 97 |   return resolveCrossPlatformPathWithContext(basePath, relativePath).resolved;
 98 | }
 99 | 
100 | /**
101 |  * Resolves a relative path against a base path and validates that it exists.
102 |  * Provides helpful error messages for debugging path issues.
103 |  *
104 |  * @param basePath - The base directory (usually absolute)
105 |  * @param relativePath - The relative path to resolve
106 |  * @returns Normalized absolute path with Unix-style separators
107 |  * @throws Error if the resolved path does not exist
108 |  */
109 | export function resolveCrossPlatformPathAndValidate(
110 |   basePath: string,
111 |   relativePath: string,
112 | ): string {
113 |   return resolveCrossPlatformPathAndValidateWithContext(basePath, relativePath);
114 | }
115 | 
116 | /**
117 |  * Legacy replacement for validateTargetPath function.
118 |  * Enhanced version that includes workspace root context when available.
119 |  *
120 |  * @param cwd - Current working directory (base path)
121 |  * @param directory - Relative directory path to validate
122 |  * @param workspaceRoot - Optional workspace root for enhanced error context
123 |  * @returns Normalized absolute path with Unix-style separators
124 |  * @throws Error if the resolved path does not exist
125 |  */
126 | export function validateTargetPath(
127 |   cwd: string,
128 |   directory: string,
129 |   workspaceRoot?: string,
130 | ): string {
131 |   return resolveCrossPlatformPathAndValidateWithContext(
132 |     cwd,
133 |     directory,
134 |     workspaceRoot,
135 |   );
136 | }
137 | 
138 | /**
139 |  * Converts absolute paths to relative paths based on a workspace root.
140 |  * Handles file paths with line/column annotations (e.g., "file.html@44:9")
141 |  *
142 |  * @param absolutePath - The absolute path to normalize
143 |  * @param workspaceRoot - The workspace root to make paths relative to
144 |  * @returns The normalized relative path
145 |  */
146 | export function normalizeAbsolutePathToRelative(
147 |   absolutePath: string,
148 |   workspaceRoot: string,
149 | ): string {
150 |   // Handle paths with line/column annotations (e.g., "file.html@44:9")
151 |   const [filePath, annotation] = absolutePath.split('@');
152 | 
153 |   // Convert to Unix-style paths for consistent processing
154 |   const normalizedFilePath = toUnixPath(filePath);
155 |   const normalizedWorkspaceRoot = toUnixPath(workspaceRoot);
156 | 
157 |   // If the path is already relative or doesn't start with workspace root, return as-is
158 |   if (
159 |     !path.isAbsolute(normalizedFilePath) ||
160 |     !normalizedFilePath.startsWith(normalizedWorkspaceRoot)
161 |   ) {
162 |     return absolutePath;
163 |   }
164 | 
165 |   // Calculate relative path
166 |   const relativePath = path.relative(
167 |     normalizedWorkspaceRoot,
168 |     normalizedFilePath,
169 |   );
170 | 
171 |   // Reconstruct with annotation if it existed
172 |   return annotation ? `${relativePath}@${annotation}` : relativePath;
173 | }
174 | 
175 | /**
176 |  * Recursively normalizes all absolute paths in an object to relative paths
177 |  *
178 |  * @param obj - The object to process
179 |  * @param workspaceRoot - The workspace root to make paths relative to
180 |  * @returns The object with normalized paths
181 |  */
182 | export function normalizePathsInObject<T>(obj: T, workspaceRoot: string): T {
183 |   if (obj === null || obj === undefined) {
184 |     return obj;
185 |   }
186 | 
187 |   if (typeof obj === 'string') {
188 |     // Check if this looks like an absolute path that starts with workspace root
189 |     const normalizedWorkspaceRoot = toUnixPath(workspaceRoot);
190 |     const normalizedObj = toUnixPath(obj);
191 | 
192 |     if (
193 |       path.isAbsolute(normalizedObj) &&
194 |       normalizedObj.startsWith(normalizedWorkspaceRoot)
195 |     ) {
196 |       return normalizeAbsolutePathToRelative(obj, workspaceRoot) as T;
197 |     }
198 |     return obj;
199 |   }
200 | 
201 |   if (Array.isArray(obj)) {
202 |     return obj.map((item) => normalizePathsInObject(item, workspaceRoot)) as T;
203 |   }
204 | 
205 |   if (typeof obj === 'object') {
206 |     const result = {} as T;
207 |     for (const [key, value] of Object.entries(obj)) {
208 |       (result as any)[key] = normalizePathsInObject(value, workspaceRoot);
209 |     }
210 |     return result;
211 |   }
212 | 
213 |   return obj;
214 | }
215 | 
```

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

```html
  1 | <header class="dashboard-header">
  2 |   <!-- Logo and Title Section -->
  3 |   <div class="header-brand">
  4 |     <div class="brand-logo">
  5 |       <svg width="32" height="32" viewBox="0 0 32 32" class="logo-icon">
  6 |         <circle cx="16" cy="16" r="14" fill="#4F46E5"/>
  7 |         <path d="M12 16l4 4 8-8" stroke="white" stroke-width="2" fill="none"/>
  8 |       </svg>
  9 |     </div>
 10 |     <h1 class="brand-title">Dashboard Pro</h1>
 11 |     
 12 |     <!-- DsBadge Component -->
 13 |     <ds-badge 
 14 |       [size]="badgeSize() === 'large' ? 'medium' : badgeSize() === 'medium' ? 'medium' : 'xsmall'"
 15 |       [variant]="getBadgeVariant()">
 16 |       <!-- Start slot for icon -->
 17 |       <span slot="start">
 18 |         @if (!hasIconSlot()) {
 19 |           <span class="offer-badge-default-icon">🎯</span>
 20 |         }
 21 |         <ng-content select="[slot=start]" />
 22 |       </span>
 23 |       <!-- Main content -->
 24 |       <ng-content />
 25 |       @if (!hasContent()) {
 26 |         {{ defaultBadgeText() }}
 27 |       }
 28 |       <!-- End slot for dismiss button -->
 29 |       <span slot="end">
 30 |         @if (dismissible()) {
 31 |           <button 
 32 |             class="offer-badge-dismiss"
 33 |             (click)="dismissBadge()"
 34 |             aria-label="Dismiss offer">
 35 |             ×
 36 |           </button>
 37 |         }
 38 |         <ng-content select="[slot=end]" />
 39 |       </span>
 40 |     </ds-badge>
 41 |   </div>
 42 | 
 43 |   <!-- Search Section -->
 44 |   <div class="header-search">
 45 |     <div *ngIf="true" class="search-container" [class.search-focused]="searchFocused()">
 46 |       <svg class="search-icon" width="20" height="20" viewBox="0 0 20 20">
 47 |         <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"/>
 48 |         <path d="m13 13 4 4-1.5 1.5-4-4" fill="currentColor"/>
 49 |       </svg>
 50 |       <input 
 51 |         type="text"
 52 |         class="search-input"
 53 |         placeholder="Search dashboard..."
 54 |         [(ngModel)]="searchQuery"
 55 |         (focus)="searchFocused.set(true)"
 56 |         (blur)="searchFocused.set(false)"
 57 |         (keyup.enter)="performSearch()"
 58 |         [disabled]="searchDisabled()">
 59 |       @if (searchQuery()) {
 60 |         <button 
 61 |           class="search-clear"
 62 |           (click)="clearSearch()"
 63 |           aria-label="Clear search">
 64 |           ×
 65 |         </button>
 66 |       }
 67 |     </div>
 68 |     @if (searchSuggestions().length > 0 && searchFocused()) {
 69 |       <div class="search-suggestions">
 70 |         @for (suggestion of searchSuggestions(); track suggestion.id) {
 71 |           <button 
 72 |             class="suggestion-item"
 73 |             (click)="selectSuggestion(suggestion)">
 74 |             {{ suggestion.text }}
 75 |           </button>
 76 |         }
 77 |       </div>
 78 |     }
 79 |   </div>
 80 | 
 81 |   <!-- Actions Section -->
 82 |   <div class="header-actions">
 83 |     <!-- Notifications -->
 84 |     <div class="action-item notification-container">
 85 |       <button 
 86 |         class="action-button notification-button"
 87 |         [class.has-notifications]="unreadNotifications() > 0"
 88 |         (click)="toggleNotifications()"
 89 |         [attr.aria-label]="'Notifications (' + unreadNotifications() + ' unread)'">
 90 |         <svg width="24" height="24" viewBox="0 0 24 24" class="notification-icon">
 91 |           <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"/>
 92 |           <path d="M10 20a2 2 0 1 0 4 0" fill="currentColor"/>
 93 |         </svg>
 94 |         @if (unreadNotifications() > 0) {
 95 |           <span class="notification-badge">{{ unreadNotifications() }}</span>
 96 |         }
 97 |       </button>
 98 |       
 99 |       @if (showNotifications()) {
100 |         <div class="notifications-dropdown">
101 |           <div class="dropdown-header">
102 |             <h3>Notifications</h3>
103 |             @if (unreadNotifications() > 0) {
104 |               <button 
105 |                 class="mark-all-read"
106 |                 (click)="markAllNotificationsRead()">
107 |                 Mark all read
108 |               </button>
109 |             }
110 |           </div>
111 |           <div class="notifications-list">
112 |             @for (notification of notifications(); track notification.id) {
113 |               <div 
114 |                 class="notification-item"
115 |                 [class.notification-unread]="!notification.read"
116 |                 [class.notification-{{ notification.type }}]="true">
117 |                 <div class="notification-content">
118 |                   <h4 class="notification-title">{{ notification.title }}</h4>
119 |                   <p class="notification-message">{{ notification.message }}</p>
120 |                   <span class="notification-time">{{ formatTime(notification.timestamp) }}</span>
121 |                 </div>
122 |                 <button 
123 |                   class="notification-dismiss"
124 |                   (click)="dismissNotification(notification.id)"
125 |                   aria-label="Dismiss notification">
126 |                   ×
127 |                 </button>
128 |               </div>
129 |             } @empty {
130 |               <div class="no-notifications">
131 |                 <p>No notifications</p>
132 |               </div>
133 |             }
134 |           </div>
135 |         </div>
136 |       }
137 |     </div>
138 | 
139 |     <!-- User Menu -->
140 |     <div class="action-item user-container">
141 |       <button 
142 |         class="action-button user-button"
143 |         (click)="toggleUserMenu()"
144 |         [attr.aria-label]="'User menu for ' + userProfile()?.name">
145 |         @if (userProfile()?.avatar) {
146 |           <img 
147 |             [src]="userProfile()!.avatar" 
148 |             [alt]="userProfile()!.name"
149 |             class="user-avatar">
150 |         } @else {
151 |           <div class="user-avatar-placeholder">
152 |             {{ getUserInitials() }}
153 |           </div>
154 |         }
155 |         <svg class="dropdown-arrow" width="12" height="12" viewBox="0 0 12 12">
156 |           <path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none"/>
157 |         </svg>
158 |       </button>
159 | 
160 |       @if (showUserMenu()) {
161 |         <div class="user-dropdown">
162 |           <div class="user-info">
163 |             <div class="user-details">
164 |               <h4>{{ userProfile()?.name }}</h4>
165 |               <p>{{ userProfile()?.email }}</p>
166 |               <span class="user-role">{{ userProfile()?.role }}</span>
167 |             </div>
168 |           </div>
169 |           <div class="user-actions">
170 |             <button class="dropdown-item" (click)="navigateToProfile()">
171 |               Profile Settings
172 |             </button>
173 |             <button class="dropdown-item" (click)="navigateToPreferences()">
174 |               Preferences
175 |             </button>
176 |             <button class="dropdown-item" (click)="toggleTheme()">
177 |               {{ darkMode() ? 'Light Mode' : 'Dark Mode' }}
178 |             </button>
179 |             <hr class="dropdown-divider">
180 |             <button class="dropdown-item logout" (click)="logout()">
181 |               Sign Out
182 |             </button>
183 |           </div>
184 |         </div>
185 |       }
186 |     </div>
187 |   </div>
188 | </header> 
```

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

```typescript
  1 | import { ToolSchemaOptions } from '@push-based/models';
  2 | import {
  3 |   createHandler,
  4 |   BaseHandlerOptions,
  5 | } from '../shared/utils/handler-helpers.js';
  6 | import { COMMON_ANNOTATIONS } from '../shared/models/schema-helpers.js';
  7 | import { getComponentPathsInfo } from './utils/paths-helpers.js';
  8 | import { getComponentDocPathsForName } from './utils/doc-helpers.js';
  9 | import { resolveCrossPlatformPath } from '../shared/utils/cross-platform-path.js';
 10 | import * as fs from 'fs';
 11 | import * as path from 'path';
 12 | 
 13 | interface DsComponentInfo {
 14 |   componentName: string;
 15 |   folderName: string;
 16 |   implementation: string[];
 17 |   documentation: string[];
 18 |   stories: string[];
 19 |   importPath: string;
 20 | }
 21 | 
 22 | interface ListDsComponentsOptions extends BaseHandlerOptions {
 23 |   sections?: string[];
 24 | }
 25 | 
 26 | export const listDsComponentsToolSchema: ToolSchemaOptions = {
 27 |   name: 'list-ds-components',
 28 |   description: `List all available Design System components in the project. Returns component names, folder structures, import paths, implementation files, documentation files, and stories files.`,
 29 |   inputSchema: {
 30 |     type: 'object',
 31 |     properties: {
 32 |       sections: {
 33 |         type: 'array',
 34 |         items: {
 35 |           type: 'string',
 36 |           enum: ['implementation', 'documentation', 'stories', 'all'],
 37 |         },
 38 |         description:
 39 |           'Sections to include in the response. Options: "implementation", "documentation", "stories", "all". Defaults to ["all"] if not specified.',
 40 |         default: ['all'],
 41 |       },
 42 |     },
 43 |     required: [],
 44 |   },
 45 |   annotations: {
 46 |     title: 'List Design System Components',
 47 |     ...COMMON_ANNOTATIONS.readOnly,
 48 |   },
 49 | };
 50 | 
 51 | function getAllFilesInDirectory(dirPath: string): string[] {
 52 |   const files: string[] = [];
 53 | 
 54 |   function walkDirectory(currentPath: string) {
 55 |     try {
 56 |       const items = fs.readdirSync(currentPath);
 57 | 
 58 |       for (const item of items) {
 59 |         const fullPath = path.join(currentPath, item);
 60 |         const stat = fs.statSync(fullPath);
 61 | 
 62 |         if (stat.isDirectory()) {
 63 |           walkDirectory(fullPath);
 64 |         } else {
 65 |           files.push(fullPath);
 66 |         }
 67 |       }
 68 |     } catch {
 69 |       return;
 70 |     }
 71 |   }
 72 | 
 73 |   if (fs.existsSync(dirPath)) {
 74 |     walkDirectory(dirPath);
 75 |   }
 76 | 
 77 |   return files;
 78 | }
 79 | 
 80 | function kebabCaseToPascalCase(kebabCase: string): string {
 81 |   return (
 82 |     'Ds' +
 83 |     kebabCase
 84 |       .split('-')
 85 |       .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
 86 |       .join('')
 87 |   );
 88 | }
 89 | 
 90 | function isValidComponentFolder(folderPath: string): boolean {
 91 |   const packageJsonPath = path.join(folderPath, 'package.json');
 92 |   const srcPath = path.join(folderPath, 'src');
 93 | 
 94 |   return (
 95 |     fs.existsSync(packageJsonPath) &&
 96 |     fs.existsSync(srcPath) &&
 97 |     fs.statSync(srcPath).isDirectory()
 98 |   );
 99 | }
100 | 
101 | function findStoriesFiles(componentPath: string): string[] {
102 |   const storiesFiles: string[] = [];
103 | 
104 |   try {
105 |     if (fs.existsSync(componentPath)) {
106 |       const items = fs.readdirSync(componentPath);
107 | 
108 |       for (const item of items) {
109 |         const fullPath = path.join(componentPath, item);
110 |         const stat = fs.statSync(fullPath);
111 | 
112 |         if (stat.isFile() && item.endsWith('.stories.ts')) {
113 |           storiesFiles.push(fullPath);
114 |         }
115 |       }
116 |     }
117 |   } catch {
118 |     return storiesFiles;
119 |   }
120 | 
121 |   return storiesFiles;
122 | }
123 | 
124 | export const listDsComponentsHandler = createHandler<
125 |   ListDsComponentsOptions,
126 |   DsComponentInfo[]
127 | >(
128 |   listDsComponentsToolSchema.name,
129 |   async ({ sections = ['all'] }, { cwd, uiRoot, storybookDocsRoot }) => {
130 |     try {
131 |       if (!uiRoot || typeof uiRoot !== 'string') {
132 |         throw new Error('uiRoot must be provided and be a string path.');
133 |       }
134 | 
135 |       const componentsBasePath = resolveCrossPlatformPath(cwd, uiRoot);
136 | 
137 |       if (!fs.existsSync(componentsBasePath)) {
138 |         throw new Error(`Components directory not found: ${uiRoot}`);
139 |       }
140 | 
141 |       const entries = fs.readdirSync(componentsBasePath, {
142 |         withFileTypes: true,
143 |       });
144 |       const componentFolders = entries
145 |         .filter((entry) => entry.isDirectory())
146 |         .map((entry) => entry.name)
147 |         .filter((folderName) => {
148 |           const folderPath = path.join(componentsBasePath, folderName);
149 |           return isValidComponentFolder(folderPath);
150 |         });
151 | 
152 |       const components: DsComponentInfo[] = [];
153 | 
154 |       const includeAll = sections.includes('all');
155 |       const includeImplementation =
156 |         includeAll || sections.includes('implementation');
157 |       const includeDocumentation =
158 |         includeAll || sections.includes('documentation');
159 |       const includeStories = includeAll || sections.includes('stories');
160 | 
161 |       for (const folderName of componentFolders) {
162 |         try {
163 |           const componentName = kebabCaseToPascalCase(folderName);
164 | 
165 |           const pathsInfo = getComponentPathsInfo(componentName, uiRoot, cwd);
166 | 
167 |           let implementationFiles: string[] = [];
168 |           if (includeImplementation) {
169 |             const srcFiles = getAllFilesInDirectory(pathsInfo.srcPath);
170 |             implementationFiles = srcFiles.map((file) => `file://${file}`);
171 |           }
172 | 
173 |           const documentationFiles: string[] = [];
174 | 
175 |           let storiesFilePaths: string[] = [];
176 |           if (storybookDocsRoot) {
177 |             const docsBasePath = resolveCrossPlatformPath(
178 |               cwd,
179 |               storybookDocsRoot,
180 |             );
181 | 
182 |             if (includeDocumentation) {
183 |               const docPaths = getComponentDocPathsForName(
184 |                 docsBasePath,
185 |                 componentName,
186 |               );
187 | 
188 |               if (fs.existsSync(docPaths.paths.api)) {
189 |                 documentationFiles.push(`file://${docPaths.paths.api}`);
190 |               }
191 |               if (fs.existsSync(docPaths.paths.overview)) {
192 |                 documentationFiles.push(`file://${docPaths.paths.overview}`);
193 |               }
194 |             }
195 | 
196 |             if (includeStories) {
197 |               const storiesComponentFolderPath = path.join(
198 |                 docsBasePath,
199 |                 folderName,
200 |               );
201 |               const storiesFiles = findStoriesFiles(storiesComponentFolderPath);
202 |               storiesFilePaths = storiesFiles.map((file) => `file://${file}`);
203 |             }
204 |           }
205 | 
206 |           components.push({
207 |             componentName,
208 |             folderName,
209 |             implementation: implementationFiles,
210 |             documentation: documentationFiles,
211 |             stories: storiesFilePaths,
212 |             importPath: pathsInfo.importPath,
213 |           });
214 |         } catch (ctx) {
215 |           console.warn(
216 |             `Warning: Skipped component '${folderName}': ${(ctx as Error).message}`,
217 |           );
218 |         }
219 |       }
220 | 
221 |       return components;
222 |     } catch (ctx) {
223 |       throw new Error(`Error listing DS components: ${(ctx as Error).message}`);
224 |     }
225 |   },
226 |   (components) => {
227 |     const response = {
228 |       totalComponents: components?.length || 0,
229 |       components: components || [],
230 |     };
231 | 
232 |     return [JSON.stringify(response, null, 2)];
233 |   },
234 | );
235 | 
236 | export const listDsComponentsTools = [
237 |   {
238 |     schema: listDsComponentsToolSchema,
239 |     handler: listDsComponentsHandler,
240 |   },
241 | ];
242 | 
```

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

```typescript
  1 | import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
  2 | import { buildText } from '../utils/output.utils.js';
  3 | import {
  4 |   BaseViolationResult,
  5 |   BaseViolationAudit,
  6 |   BaseViolationIssue,
  7 |   FileGroups,
  8 |   PathCache,
  9 | } from './types.js';
 10 | 
 11 | // Performance-optimized path cache
 12 | const pathCache: PathCache = {};
 13 | 
 14 | /**
 15 |  * Filters audits to only include those with violations (score < 1)
 16 |  */
 17 | export function filterFailedAudits(
 18 |   result: BaseViolationResult,
 19 | ): BaseViolationAudit[] {
 20 |   return result.audits.filter(({ score }) => score < 1);
 21 | }
 22 | 
 23 | /**
 24 |  * Creates standard "no violations found" content
 25 |  */
 26 | export function createNoViolationsContent(): CallToolResult['content'] {
 27 |   return [
 28 |     buildText(
 29 |       '✅ No violations found! All files are compliant with the design system.',
 30 |     ),
 31 |   ];
 32 | }
 33 | 
 34 | /**
 35 |  * Extracts all issues from failed audits
 36 |  */
 37 | export function extractIssuesFromAudits(
 38 |   audits: BaseViolationAudit[],
 39 | ): BaseViolationIssue[] {
 40 |   return audits.flatMap(({ details }) => details?.issues ?? []);
 41 | }
 42 | 
 43 | /**
 44 |  * Checks if a violation result has any failures
 45 |  */
 46 | export function hasViolations(result: BaseViolationResult): boolean {
 47 |   return filterFailedAudits(result).length > 0;
 48 | }
 49 | 
 50 | /**
 51 |  * Performance-optimized file path normalization with caching
 52 |  */
 53 | export function normalizeFilePath(filePath: string, directory: string): string {
 54 |   const cacheKey = `${filePath}::${directory}`;
 55 | 
 56 |   if (pathCache[cacheKey]) {
 57 |     return pathCache[cacheKey];
 58 |   }
 59 | 
 60 |   // Normalize both paths to use consistent separators
 61 |   const normalizedFilePath = filePath.replace(/\\/g, '/');
 62 |   const normalizedDirectory = directory.replace(/\\/g, '/');
 63 | 
 64 |   // Remove leading './' from directory if present for comparison
 65 |   const cleanDirectory = normalizedDirectory.startsWith('./')
 66 |     ? normalizedDirectory.slice(2)
 67 |     : normalizedDirectory;
 68 | 
 69 |   let normalized: string;
 70 | 
 71 |   // The file path from the coverage plugin is absolute, but we need to extract the relative part
 72 |   // Look for the directory pattern in the file path and extract everything after it
 73 |   const directoryPattern = cleanDirectory;
 74 |   const directoryIndex = normalizedFilePath.indexOf(directoryPattern);
 75 | 
 76 |   if (directoryIndex !== -1) {
 77 |     // Found the directory in the path, extract the part after it
 78 |     const afterDirectoryIndex = directoryIndex + directoryPattern.length;
 79 |     const afterDirectory = normalizedFilePath.slice(afterDirectoryIndex);
 80 | 
 81 |     // Remove leading slash if present
 82 |     normalized = afterDirectory.startsWith('/')
 83 |       ? afterDirectory.slice(1)
 84 |       : afterDirectory;
 85 |   } else {
 86 |     // Fallback: try with directory prefix approach
 87 |     const directoryPrefix = normalizedDirectory.endsWith('/')
 88 |       ? normalizedDirectory
 89 |       : normalizedDirectory + '/';
 90 |     normalized = normalizedFilePath.startsWith(directoryPrefix)
 91 |       ? normalizedFilePath.slice(directoryPrefix.length)
 92 |       : normalizedFilePath;
 93 |   }
 94 | 
 95 |   pathCache[cacheKey] = normalized;
 96 |   return normalized;
 97 | }
 98 | 
 99 | /**
100 |  * Performance-optimized message normalization with caching
101 |  */
102 | export function normalizeMessage(message: string, directory: string): string {
103 |   const cacheKey = `msg::${message}::${directory}`;
104 | 
105 |   if (pathCache[cacheKey]) {
106 |     return pathCache[cacheKey];
107 |   }
108 | 
109 |   const directoryPrefix = directory.endsWith('/') ? directory : directory + '/';
110 |   const normalized = message.includes(directoryPrefix)
111 |     ? message.replace(directoryPrefix, '')
112 |     : message;
113 | 
114 |   pathCache[cacheKey] = normalized;
115 |   return normalized;
116 | }
117 | 
118 | /**
119 |  * Groups violation issues by file name - consolidated from multiple modules
120 |  * Performance optimized with Set for duplicate checking and cached normalizations
121 |  */
122 | export function groupIssuesByFile(
123 |   issues: BaseViolationIssue[],
124 |   directory: string,
125 | ): FileGroups {
126 |   const fileGroups: FileGroups = {};
127 |   const processedFiles = new Set<string>(); // O(1) lookup instead of includes()
128 | 
129 |   for (const { message, source } of issues) {
130 |     if (!source?.file) continue;
131 | 
132 |     const fileName = normalizeFilePath(source.file, directory);
133 |     const lineNumber = source.position?.startLine || 0;
134 | 
135 |     if (!fileGroups[fileName]) {
136 |       fileGroups[fileName] = {
137 |         message: normalizeMessage(message, directory),
138 |         lines: [],
139 |       };
140 |       processedFiles.add(fileName);
141 |     }
142 | 
143 |     fileGroups[fileName].lines.push(lineNumber);
144 |   }
145 | 
146 |   return fileGroups;
147 | }
148 | 
149 | /**
150 |  * Extracts unique file paths from violation issues - performance optimized
151 |  */
152 | export function extractUniqueFilePaths(
153 |   issues: BaseViolationIssue[],
154 |   directory: string,
155 | ): string[] {
156 |   const filePathSet = new Set<string>(); // Eliminate O(n) includes() calls
157 | 
158 |   for (const { source } of issues) {
159 |     if (source?.file) {
160 |       filePathSet.add(normalizeFilePath(source.file, directory));
161 |     }
162 |   }
163 | 
164 |   return Array.from(filePathSet);
165 | }
166 | 
167 | /**
168 |  * Clears the path cache - useful for testing or memory management
169 |  */
170 | export function clearPathCache(): void {
171 |   Object.keys(pathCache).forEach((key) => delete pathCache[key]);
172 | }
173 | 
174 | /**
175 |  * Unified formatter for violations - supports both file and folder grouping with minimal output
176 |  */
177 | export function formatViolations(
178 |   result: BaseViolationResult,
179 |   directory: string,
180 |   options: {
181 |     groupBy: 'file' | 'folder';
182 |   } = { groupBy: 'file' },
183 | ): CallToolResult['content'] {
184 |   const failedAudits = filterFailedAudits(result);
185 | 
186 |   if (failedAudits.length === 0) {
187 |     return [buildText('No violations found.')];
188 |   }
189 | 
190 |   const allIssues = extractIssuesFromAudits(failedAudits);
191 |   const content: CallToolResult['content'] = [];
192 | 
193 |   if (options.groupBy === 'file') {
194 |     // Group by individual files - minimal format
195 |     const fileGroups = groupIssuesByFile(allIssues, directory);
196 | 
197 |     for (const [fileName, { message, lines }] of Object.entries(fileGroups)) {
198 |       const sortedLines = lines.sort((a, b) => a - b);
199 |       const lineInfo =
200 |         sortedLines.length > 1
201 |           ? `lines ${sortedLines.join(', ')}`
202 |           : `line ${sortedLines[0]}`;
203 | 
204 |       content.push(buildText(`${fileName} (${lineInfo}): ${message}`));
205 |     }
206 |   } else {
207 |     // Group by folders - minimal format
208 |     const folderGroups: Record<
209 |       string,
210 |       { violations: number; files: Set<string> }
211 |     > = {};
212 | 
213 |     for (const { source } of allIssues) {
214 |       if (!source?.file) continue;
215 | 
216 |       const normalizedPath = normalizeFilePath(source.file, directory);
217 |       const folderPath = normalizedPath.includes('/')
218 |         ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/'))
219 |         : '.';
220 | 
221 |       if (!folderGroups[folderPath]) {
222 |         folderGroups[folderPath] = { violations: 0, files: new Set() };
223 |       }
224 | 
225 |       folderGroups[folderPath].violations++;
226 |       folderGroups[folderPath].files.add(normalizedPath);
227 |     }
228 | 
229 |     // Sort folders for consistent output
230 |     for (const [folder, { violations, files }] of Object.entries(
231 |       folderGroups,
232 |     ).sort()) {
233 |       const displayPath = folder === '.' ? directory : `${directory}/${folder}`;
234 |       content.push(
235 |         buildText(
236 |           `${displayPath}: ${violations} violations in ${files.size} files`,
237 |         ),
238 |       );
239 |     }
240 |   }
241 | 
242 |   return content;
243 | }
244 | 
```

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

```typescript
  1 | import * as path from 'node:path';
  2 | import * as ts from 'typescript';
  3 | 
  4 | import { ParsedComponent } from './types.js';
  5 | import { parseStylesheet } from '@push-based/styles-ast-utils';
  6 | import { resolveFile } from '@push-based/utils';
  7 | import {
  8 |   visitAngularDecoratorProperties,
  9 |   visitAngularDecorators,
 10 | } from './ts.walk.js';
 11 | import {
 12 |   assetFromPropertyArrayInitializer,
 13 |   assetFromPropertyValueInitializer,
 14 | } from './utils.js';
 15 | import {
 16 |   isComponentDecorator,
 17 |   removeQuotes,
 18 | } from '@push-based/typescript-ast-utils';
 19 | import type {
 20 |   ParsedTemplate,
 21 |   ParseTemplateOptions,
 22 | } from '@angular/compiler' with { 'resolution-mode': 'import' };
 23 | 
 24 | const DEBUG = false;
 25 | const debug = ({
 26 |   step,
 27 |   title,
 28 |   info,
 29 | }: {
 30 |   step: string;
 31 |   title: string;
 32 |   info: string | null | undefined;
 33 | }) =>
 34 |   DEBUG &&
 35 |   console.log(
 36 |     `─── 📌 [${step}]: ${title} ${info ? '-' + info : ''}──────────────────────────────`,
 37 |   );
 38 | 
 39 | export async function classDecoratorVisitor({
 40 |   sourceFile,
 41 | }: {
 42 |   sourceFile: ts.SourceFile;
 43 | }) {
 44 |   // @TODO: rethink module resolution
 45 |   const { parseTemplate } = await import('@angular/compiler');
 46 |   const components: ParsedComponent[] = [];
 47 |   let activeComponent: ParsedComponent | null = null;
 48 |   let currentClassName: string | null = null;
 49 | 
 50 |   const visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
 51 |     // ─── 📌 ENTER: Class Declaration ──────────────────────────────
 52 |     if (ts.isClassDeclaration(node)) {
 53 |       currentClassName = node.name?.text ?? 'Unknown'; // Capture class name immediately
 54 |       debug({
 55 |         step: 'ENTER',
 56 |         title: 'ClassDeclaration',
 57 |         info: `class ${currentClassName}`,
 58 |       });
 59 | 
 60 |       activeComponent = {
 61 |         startLine: ts.getLineAndCharacterOfPosition(
 62 |           sourceFile,
 63 |           node.getStart(sourceFile),
 64 |         ).line,
 65 |         className: currentClassName,
 66 |         fileName: sourceFile.fileName,
 67 |       } as ParsedComponent;
 68 | 
 69 |       visitAngularDecorators(node, (decorator: ts.Decorator) => {
 70 |         debug({
 71 |           step: 'ENTER',
 72 |           title: 'ClassDecorators',
 73 |           info: 'of ' + currentClassName,
 74 |         });
 75 |         if (isComponentDecorator(decorator)) {
 76 |           debug({
 77 |             step: 'ENTER',
 78 |             title: 'ClassDecorator',
 79 |             info: '@Component',
 80 |           });
 81 |           visitAngularDecoratorProperties(
 82 |             decorator,
 83 |             (prop: ts.PropertyAssignment) => {
 84 |               if (
 85 |                 !ts.isPropertyAssignment(prop) ||
 86 |                 !ts.isIdentifier(prop.name)
 87 |               ) {
 88 |                 return;
 89 |               }
 90 | 
 91 |               const propName = prop.name.escapedText as string;
 92 |               const getPropValue = getPropValueFactory(parseTemplate);
 93 |               const propValue = getPropValue(
 94 |                 prop,
 95 |                 sourceFile,
 96 |                 currentClassName ?? 'undefined-class',
 97 |               );
 98 |               if (activeComponent) {
 99 |                 (activeComponent as ParsedComponent)[propName] =
100 |                   propValue as unknown as string;
101 | 
102 |                 debug({
103 |                   step: 'Update',
104 |                   title: 'ParsedComponent',
105 |                   info: `add ${propName}`,
106 |                 });
107 |               }
108 |             },
109 |           );
110 | 
111 |           if (activeComponent) {
112 |             debug({
113 |               step: 'PUSH',
114 |               title: 'ParsedComponent',
115 |               info: `add ${activeComponent.className}`,
116 |             });
117 |             components.push(activeComponent);
118 |             activeComponent = null;
119 |           }
120 |         }
121 |       });
122 | 
123 |       debug({
124 |         step: 'EXIT',
125 |         title: 'ClassDeclaration',
126 |         info: `class ${currentClassName}`,
127 |       });
128 |       currentClassName = null;
129 |     }
130 |     return node;
131 |   };
132 | 
133 |   visitor.components = components;
134 |   return visitor;
135 | }
136 | 
137 | function getPropValueFactory(
138 |   parseTemplate: (
139 |     template: string,
140 |     templateUrl: string,
141 |     options?: ParseTemplateOptions,
142 |   ) => ParsedTemplate,
143 | ) {
144 |   return (
145 |     prop: ts.PropertyAssignment,
146 |     sourceFile: ts.SourceFile,
147 |     currentClassName: string,
148 |   ): string | unknown => {
149 |     let propName = '';
150 |     if (ts.isIdentifier(prop.name)) {
151 |       propName = prop.name.escapedText as string;
152 |     } else {
153 |       throw new Error('Property name is not an identifier');
154 |     }
155 |     switch (propName) {
156 |       case 'templateUrl':
157 |       case 'template':
158 |         return assetFromPropertyValueInitializer({
159 |           prop,
160 |           sourceFile,
161 |           textParser: async (text: string) => {
162 |             const filePath =
163 |               propName === 'templateUrl'
164 |                 ? path.join(path.dirname(sourceFile.fileName), text)
165 |                 : sourceFile.fileName;
166 |             const content =
167 |               propName === 'templateUrl' ? await resolveFile(filePath) : text;
168 | 
169 |             debug({
170 |               step: 'RESOLVE',
171 |               title: 'Template',
172 |               info: `${currentClassName}; file ${filePath}`,
173 |             });
174 | 
175 |             return parseTemplate(content, filePath, {
176 |               preserveWhitespaces: true,
177 |               preserveLineEndings: true,
178 |               // preserveSignificantWhitespace: true,
179 |             });
180 |           },
181 |         });
182 |       case 'styleUrl':
183 |         return assetFromPropertyValueInitializer({
184 |           prop,
185 |           sourceFile,
186 |           textParser: async (text: string) => {
187 |             const filePath = path.join(path.dirname(sourceFile.fileName), text);
188 |             const content = await resolveFile(filePath);
189 | 
190 |             debug({
191 |               step: 'RESOLVE',
192 |               title: 'styleUrl',
193 |               info: `${currentClassName}; file ${filePath}`,
194 |             });
195 |             return parseStylesheet(content, filePath);
196 |           },
197 |         });
198 |       case 'styles':
199 |         if (ts.isArrayLiteralExpression(prop.initializer)) {
200 |           return assetFromPropertyArrayInitializer(
201 |             prop,
202 |             sourceFile,
203 |             async (text: string) => {
204 |               debug({
205 |                 step: 'RESOLVE',
206 |                 title: 'Styles',
207 |                 info: `${currentClassName}; inline-array`,
208 |               });
209 |               return parseStylesheet(text, sourceFile.fileName);
210 |             },
211 |           );
212 |         }
213 | 
214 |         return [
215 |           assetFromPropertyValueInitializer({
216 |             prop,
217 |             sourceFile,
218 |             textParser: async (text: string) => {
219 |               debug({
220 |                 step: 'RESOLVE',
221 |                 title: 'Styles',
222 |                 info: `${currentClassName}; inline-single`,
223 |               });
224 |               return parseStylesheet(text, sourceFile.fileName);
225 |             },
226 |           }),
227 |         ];
228 |       case 'styleUrls':
229 |         return assetFromPropertyArrayInitializer(
230 |           prop,
231 |           sourceFile,
232 |           async (text: string) => {
233 |             const filePath = path.join(path.dirname(sourceFile.fileName), text);
234 |             const content = await resolveFile(filePath);
235 | 
236 |             debug({
237 |               step: 'RESOLVE',
238 |               title: 'styleUrls',
239 |               info: `${currentClassName}; file ${filePath}`,
240 |             });
241 |             return parseStylesheet(content, filePath);
242 |           },
243 |         );
244 |       default:
245 |         // @TODO: Implement all of the decorator props
246 |         return removeQuotes(prop.initializer, sourceFile);
247 |     }
248 |   };
249 | }
250 | 
```

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

```typescript
  1 | import {
  2 |   ChangeDetectionStrategy,
  3 |   Component,
  4 |   ElementRef,
  5 |   ViewEncapsulation,
  6 |   computed,
  7 |   input,
  8 |   output,
  9 |   signal,
 10 |   booleanAttribute,
 11 |   OnInit,
 12 |   OnDestroy,
 13 |   inject,
 14 | } from '@angular/core';
 15 | import { CommonModule } from '@angular/common';
 16 | import { FormsModule } from '@angular/forms';
 17 | import { DsBadge, DsBadgeVariant } from '@frontend/ui/badge';
 18 | import { Subject, interval, takeUntil } from 'rxjs';
 19 | 
 20 | export const OFFER_BADGE_TYPES = ['limited', 'premium', 'new', 'hot', 'sale'] as const;
 21 | export type OfferBadgeType = (typeof OFFER_BADGE_TYPES)[number];
 22 | 
 23 | export const OFFER_BADGE_SIZES = ['small', 'medium', 'large'] as const;
 24 | export type OfferBadgeSize = (typeof OFFER_BADGE_SIZES)[number];
 25 | 
 26 | export interface NotificationItem {
 27 |   id: string;
 28 |   title: string;
 29 |   message: string;
 30 |   timestamp: Date;
 31 |   read: boolean;
 32 |   type: 'info' | 'warning' | 'error' | 'success';
 33 | }
 34 | 
 35 | export interface UserProfile {
 36 |   id: string;
 37 |   name: string;
 38 |   email: string;
 39 |   avatar?: string;
 40 |   role: string;
 41 |   lastLogin: Date;
 42 | }
 43 | 
 44 | @Component({
 45 |   selector: 'app-dashboard-header',
 46 |   standalone: true,
 47 |   imports: [CommonModule, FormsModule, DsBadge],
 48 |   templateUrl: './dashboard-header.component.html',
 49 |   styleUrls: ['./dashboard-header.component.scss'],
 50 |   encapsulation: ViewEncapsulation.None,
 51 |   changeDetection: ChangeDetectionStrategy.OnPush,
 52 | })
 53 | export class DashboardHeaderComponent implements OnInit, OnDestroy {
 54 |   // Offer Badge Inputs
 55 |   badgeType = input<OfferBadgeType>('premium');
 56 |   badgeSize = input<OfferBadgeSize>('medium');
 57 |   animated = input(true, { transform: booleanAttribute });
 58 |   pulsing = input(false, { transform: booleanAttribute });
 59 |   dismissible = input(true, { transform: booleanAttribute });
 60 |   
 61 |   // General Inputs
 62 |   searchDisabled = input(false, { transform: booleanAttribute });
 63 |   darkMode = input(false, { transform: booleanAttribute });
 64 |   userProfile = input<UserProfile | null>(null);
 65 |   
 66 |   // Outputs
 67 |   searchPerformed = output<string>();
 68 |   badgeDismissed = output<void>();
 69 |   notificationClicked = output<NotificationItem>();
 70 |   userActionClicked = output<string>();
 71 |   themeToggled = output<boolean>();
 72 | 
 73 |   // Internal State
 74 |   searchQuery = signal('');
 75 |   searchFocused = signal(false);
 76 |   showNotifications = signal(false);
 77 |   showUserMenu = signal(false);
 78 |   notifications = signal<NotificationItem[]>([]);
 79 |   searchSuggestions = signal<{id: string, text: string}[]>([]);
 80 |   
 81 |   private destroy$ = new Subject<void>();
 82 |   private elementRef = inject(ElementRef);
 83 | 
 84 |   // Computed values
 85 |   unreadNotifications = computed(() => 
 86 |     this.notifications().filter(n => !n.read).length
 87 |   );
 88 | 
 89 |   defaultBadgeText = computed(() => {
 90 |     const typeMap: Record<OfferBadgeType, string> = {
 91 |       'limited': 'Limited Time',
 92 |       'premium': 'Premium',
 93 |       'new': 'New Feature',
 94 |       'hot': 'Hot Deal',
 95 |       'sale': 'On Sale'
 96 |     };
 97 |     return typeMap[this.badgeType()];
 98 |   });
 99 | 
100 |   getBadgeVariant = computed((): DsBadgeVariant => {
101 |     const variantMap: Record<OfferBadgeType, DsBadgeVariant> = {
102 |       'premium': 'purple-strong',
103 |       'limited': 'red-strong',
104 |       'new': 'green-strong',
105 |       'hot': 'orange-strong',
106 |       'sale': 'blue-strong'
107 |     };
108 |     return variantMap[this.badgeType()];
109 |   });
110 | 
111 |   ngOnInit() {
112 |     this.initializeMockData();
113 |     this.setupAutoRefresh();
114 |     this.setupClickOutsideHandlers();
115 |   }
116 | 
117 |   ngOnDestroy() {
118 |     this.destroy$.next();
119 |     this.destroy$.complete();
120 |   }
121 | 
122 |   private initializeMockData() {
123 |     // Mock notifications
124 |     this.notifications.set([
125 |       {
126 |         id: '1',
127 |         title: 'System Update',
128 |         message: 'New dashboard features are now available',
129 |         timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
130 |         read: false,
131 |         type: 'info'
132 |       },
133 |       {
134 |         id: '2',
135 |         title: 'Payment Processed',
136 |         message: 'Your monthly subscription has been renewed',
137 |         timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
138 |         read: true,
139 |         type: 'success'
140 |       },
141 |       {
142 |         id: '3',
143 |         title: 'Storage Warning',
144 |         message: 'You are running low on storage space',
145 |         timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago
146 |         read: false,
147 |         type: 'warning'
148 |       }
149 |     ]);
150 | 
151 |     // Mock search suggestions
152 |     this.searchSuggestions.set([
153 |       { id: '1', text: 'Analytics Dashboard' },
154 |       { id: '2', text: 'User Management' },
155 |       { id: '3', text: 'Settings & Preferences' },
156 |       { id: '4', text: 'Reports & Export' }
157 |     ]);
158 |   }
159 | 
160 |   private setupAutoRefresh() {
161 |     // Refresh notifications every 30 seconds
162 |     interval(30000)
163 |       .pipe(takeUntil(this.destroy$))
164 |       .subscribe(() => {
165 |         // In a real app, this would fetch new notifications
166 |         console.log('Refreshing notifications...');
167 |       });
168 |   }
169 | 
170 |   private setupClickOutsideHandlers() {
171 |     document.addEventListener('click', this.handleClickOutside.bind(this));
172 |   }
173 | 
174 |   private handleClickOutside(event: Event) {
175 |     const target = event.target as HTMLElement;
176 |     if (!this.elementRef.nativeElement.contains(target)) {
177 |       this.showNotifications.set(false);
178 |       this.showUserMenu.set(false);
179 |       this.searchFocused.set(false);
180 |     }
181 |   }
182 | 
183 |   // Badge Methods
184 | 
185 |   dismissBadge() {
186 |     this.badgeDismissed.emit();
187 |   }
188 | 
189 |   hasIconSlot(): boolean {
190 |     return !!this.elementRef.nativeElement.querySelector('[slot=start]');
191 |   }
192 | 
193 |   hasContent(): boolean {
194 |     const textContent = this.elementRef.nativeElement
195 |       .querySelector('.ds-badge-text')
196 |       ?.textContent?.trim();
197 |     return !!textContent;
198 |   }
199 | 
200 |   // Search Methods
201 |   performSearch() {
202 |     if (this.searchQuery().trim()) {
203 |       this.searchPerformed.emit(this.searchQuery());
204 |     }
205 |   }
206 | 
207 |   clearSearch() {
208 |     this.searchQuery.set('');
209 |   }
210 | 
211 |   selectSuggestion(suggestion: {id: string, text: string}) {
212 |     this.searchQuery.set(suggestion.text);
213 |     this.searchFocused.set(false);
214 |     this.performSearch();
215 |   }
216 | 
217 |   // Notification Methods
218 |   toggleNotifications() {
219 |     this.showNotifications.update(show => !show);
220 |     this.showUserMenu.set(false);
221 |   }
222 | 
223 |   markAllNotificationsRead() {
224 |     this.notifications.update(notifications =>
225 |       notifications.map(n => ({ ...n, read: true }))
226 |     );
227 |   }
228 | 
229 |   dismissNotification(id: string) {
230 |     this.notifications.update(notifications =>
231 |       notifications.filter(n => n.id !== id)
232 |     );
233 |   }
234 | 
235 |   formatTime(timestamp: Date): string {
236 |     const now = new Date();
237 |     const diff = now.getTime() - timestamp.getTime();
238 |     const minutes = Math.floor(diff / (1000 * 60));
239 |     const hours = Math.floor(diff / (1000 * 60 * 60));
240 |     const days = Math.floor(diff / (1000 * 60 * 60 * 24));
241 | 
242 |     if (minutes < 1) return 'Just now';
243 |     if (minutes < 60) return `${minutes}m ago`;
244 |     if (hours < 24) return `${hours}h ago`;
245 |     return `${days}d ago`;
246 |   }
247 | 
248 |   // User Menu Methods
249 |   toggleUserMenu() {
250 |     this.showUserMenu.update(show => !show);
251 |     this.showNotifications.set(false);
252 |   }
253 | 
254 |   getUserInitials(): string {
255 |     const name = this.userProfile()?.name || 'User';
256 |     return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
257 |   }
258 | 
259 |   navigateToProfile() {
260 |     this.userActionClicked.emit('profile');
261 |     this.showUserMenu.set(false);
262 |   }
263 | 
264 |   navigateToPreferences() {
265 |     this.userActionClicked.emit('preferences');
266 |     this.showUserMenu.set(false);
267 |   }
268 | 
269 |   toggleTheme() {
270 |     const newTheme = !this.darkMode();
271 |     this.themeToggled.emit(newTheme);
272 |     this.showUserMenu.set(false);
273 |   }
274 | 
275 |   logout() {
276 |     this.userActionClicked.emit('logout');
277 |     this.showUserMenu.set(false);
278 |   }
279 | } 
```

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

```typescript
  1 | import { FocusMonitor } from '@angular/cdk/a11y';
  2 | import { NgTemplateOutlet } from '@angular/common';
  3 | import {
  4 |   AfterViewInit,
  5 |   ChangeDetectionStrategy,
  6 |   Component,
  7 |   ElementRef,
  8 |   NgZone,
  9 |   OnDestroy,
 10 |   Renderer2,
 11 |   ViewEncapsulation,
 12 |   WritableSignal,
 13 |   booleanAttribute,
 14 |   contentChildren,
 15 |   effect,
 16 |   inject,
 17 |   input,
 18 |   model,
 19 |   signal,
 20 |   untracked,
 21 |   viewChild,
 22 |   viewChildren,
 23 | } from '@angular/core';
 24 | 
 25 | import { SEGMENTED_CONTROL_OPTIONS_TOKEN } from './segmented-control.token';
 26 | import { DsSegmentedOption } from './segmented-option.component';
 27 | 
 28 | @Component({
 29 |   selector: 'ds-segmented-control',
 30 |   templateUrl: './segmented-control.component.html',
 31 |   host: {
 32 |     class: `ds-segmented-control`,
 33 |   },
 34 |   imports: [NgTemplateOutlet],
 35 |   encapsulation: ViewEncapsulation.None,
 36 |   changeDetection: ChangeDetectionStrategy.OnPush,
 37 | })
 38 | export class DsSegmentedControl implements AfterViewInit, OnDestroy {
 39 |   private controlOptions = inject(SEGMENTED_CONTROL_OPTIONS_TOKEN);
 40 |   private renderer = inject(Renderer2);
 41 |   private ngZone = inject(NgZone);
 42 |   private focusMonitor = inject(FocusMonitor);
 43 | 
 44 |   readonly activeOption = model('');
 45 |   readonly fullWidth = input(this.controlOptions.fullWidth, {
 46 |     transform: booleanAttribute,
 47 |   });
 48 |   readonly roleType = input<'radiogroup' | 'tablist'>('tablist');
 49 |   readonly twoLineTruncation = input(false, { transform: booleanAttribute });
 50 |   readonly inverse = model<boolean>(false);
 51 | 
 52 |   protected readonly scContainer =
 53 |     viewChild.required<ElementRef<HTMLDivElement>>('scContainer');
 54 |   protected readonly segmentedOptions = contentChildren(DsSegmentedOption);
 55 |   protected readonly tabLabels =
 56 |     viewChildren<ElementRef<HTMLDivElement>>('tabOption');
 57 | 
 58 |   protected isReady = signal(false);
 59 | 
 60 |   readonly selectedOption: WritableSignal<DsSegmentedOption | null> =
 61 |     signal<DsSegmentedOption | null>(this.segmentedOptions()[0] ?? null);
 62 |   readonly focusedOption: WritableSignal<DsSegmentedOption | null> =
 63 |     signal<DsSegmentedOption | null>(null);
 64 |   readonly focusVisibleOption: WritableSignal<DsSegmentedOption | null> =
 65 |     signal<DsSegmentedOption | null>(null);
 66 |   private readonly resizeObserver = new ResizeObserver((entries) => {
 67 |     for (const entry of entries) {
 68 |       this._setIndicatorCSSVars(entry.target as HTMLDivElement);
 69 |     }
 70 |   });
 71 | 
 72 |   constructor() {
 73 |     effect(() => {
 74 |       const isReady = this.isReady();
 75 |       const options = this.segmentedOptions();
 76 |       const activeOption = this.activeOption();
 77 | 
 78 |       // activate the option that comes from the input
 79 |       untracked(() => {
 80 |         if (isReady) {
 81 |           const selectedOption = this.selectedOption();
 82 |           if (selectedOption) {
 83 |             this._setHighlightWidthAndXPost(selectedOption);
 84 |           }
 85 | 
 86 |           if (options.length === 0) {
 87 |             throw new Error('Please provide segmented options!');
 88 |           }
 89 |           this.selectOption(activeOption);
 90 |         }
 91 |       });
 92 |     });
 93 | 
 94 |     effect(() => {
 95 |       const isReady = this.isReady();
 96 | 
 97 |       untracked(() => {
 98 |         if (isReady) {
 99 |           const selectedOption = this.selectedOption();
100 |           if (selectedOption) {
101 |             this._setHighlightWidthAndXPost(selectedOption);
102 |           }
103 |         }
104 |       });
105 |     });
106 |   }
107 | 
108 |   ngAfterViewInit(): void {
109 |     this.tabLabels().forEach((option, index) => {
110 |       this.focusMonitor
111 |         .monitor(option.nativeElement, true)
112 |         .subscribe((focusOrigin) => {
113 |           const isFocused =
114 |             focusOrigin === 'keyboard' || focusOrigin === 'program';
115 |           if (isFocused) {
116 |             this.focusedOption.set(this.segmentedOptions()[index] ?? null);
117 |             this.focusVisibleOption.set(this.segmentedOptions()[index] ?? null);
118 |           }
119 |         });
120 |     });
121 | 
122 |     // we don't want to show the initial animation, but only the subsequent ones
123 |     this.ngZone.runOutsideAngular(() =>
124 |       setTimeout(() => this.isReady.set(true)),
125 |     );
126 |     this.selectOption(this.activeOption());
127 |   }
128 | 
129 |   /**
130 |    * The method which will update the `selected` signal in `ds-segment-option` based on the selected name.
131 |    * @param name Name of the selected option
132 |    * @param event
133 |    */
134 |   selectOption(name: string, event?: Event): void {
135 |     if (
136 |       ((event &&
137 |         this.activeOption() === name &&
138 |         this.selectedOption()?.name() === name) ||
139 |         name === undefined) &&
140 |       this.activeOption() != null
141 |     ) {
142 |       return; // do nothing if the same option is clicked again
143 |     }
144 | 
145 |     const option = this.segmentedOptions().find((x) => x.name() === name);
146 |     if (option) {
147 |       this.selectedOption.set(option);
148 | 
149 |       if (this.isReady()) {
150 |         this._setHighlightWidthAndXPost(option);
151 |       }
152 |       this.activeOption.set(name);
153 |     } else {
154 |       // if no option can be found, we select the first one by default
155 |       this.selectFirstOption();
156 |     }
157 |   }
158 | 
159 |   /**
160 |    * Select first segment option. This is useful when the activeOption is not provided.
161 |    * @private
162 |    */
163 |   private selectFirstOption() {
164 |     const options = this.segmentedOptions();
165 | 
166 |     if (options.length === 0) {
167 |       return;
168 |     }
169 | 
170 |     const firstOption = options[0] ?? null;
171 | 
172 |     this.selectedOption.set(firstOption);
173 |     this.activeOption.set(firstOption?.name() ?? '');
174 |   }
175 | 
176 |   /**
177 |    * Will get the active segment position in order to show the indicator on the background.
178 |    * @private
179 |    */
180 |   private _setHighlightWidthAndXPost(option: DsSegmentedOption) {
181 |     for (const item of this.tabLabels()) {
182 |       this.resizeObserver.unobserve(item.nativeElement);
183 |     }
184 | 
185 |     const element = this.tabLabels().find(
186 |       (item) => item.nativeElement.id === `ds-segment-item-${option.name()}`,
187 |     );
188 |     if (element) {
189 |       this._setIndicatorCSSVars(element.nativeElement);
190 |       this.resizeObserver.observe(element.nativeElement);
191 |     }
192 |   }
193 | 
194 |   /**
195 |    * Will set the active element indicator related css variables
196 |    * @private
197 |    */
198 |   private _setIndicatorCSSVars(element: HTMLDivElement) {
199 |     const { offsetWidth, offsetLeft } = element;
200 |     // We update the DOM directly, so we don't have to go through Angular Change Detection
201 |     this.renderer.setProperty(
202 |       this.scContainer().nativeElement,
203 |       'style',
204 |       `--ds-sc-highlight-width: ${offsetWidth}px; --ds-sc-highlight-x-pos: ${offsetLeft}px`,
205 |     );
206 |   }
207 | 
208 |   onKeydown(event: KeyboardEvent) {
209 |     const { key } = event;
210 |     const options = this.segmentedOptions();
211 |     const currentIndex = options.findIndex((option) => option.focused());
212 |     let newIndex: number | undefined;
213 | 
214 |     if (key === 'ArrowRight') {
215 |       newIndex = (currentIndex + 1) % options.length;
216 |     } else if (key === 'ArrowLeft') {
217 |       newIndex = (currentIndex - 1 + options.length) % options.length;
218 |     } else if (
219 |       (key === ' ' || key === 'Enter') &&
220 |       currentIndex !== -1 &&
221 |       options[currentIndex]
222 |     ) {
223 |       this.selectOption(options[currentIndex].name(), event);
224 |     }
225 | 
226 |     if (newIndex !== undefined) {
227 |       event.preventDefault();
228 |       const newOption = options[newIndex];
229 |       if (newOption) {
230 |         this.focusOption(newOption, newIndex);
231 |       }
232 |     }
233 |   }
234 | 
235 |   private focusOption(option: DsSegmentedOption, index: number) {
236 |     const focusOption = this.tabLabels()[index];
237 |     if (focusOption) {
238 |       this.focusedOption.set(option);
239 |       this.focusVisibleOption.set(option);
240 |       this.focusMonitor.focusVia(focusOption.nativeElement, 'keyboard');
241 |     }
242 |   }
243 | 
244 |   ngOnDestroy() {
245 |     if (this.tabLabels()) {
246 |       this.tabLabels().forEach((option) =>
247 |         this.focusMonitor.stopMonitoring(option),
248 |       );
249 |     }
250 | 
251 |     this.resizeObserver.disconnect();
252 |   }
253 | }
254 | 
```

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

```markdown
  1 | # Examples
  2 | 
  3 | ## 1 — Basic stylesheet parsing
  4 | 
  5 | > Parse CSS content and access the AST structure.
  6 | 
  7 | ```ts
  8 | import { parseStylesheet } from '@push-based/styles-ast-utils';
  9 | 
 10 | const cssContent = `
 11 | .btn {
 12 |   color: red;
 13 |   background: blue;
 14 | }
 15 | 
 16 | .card {
 17 |   padding: 1rem;
 18 | }
 19 | `;
 20 | 
 21 | const result = parseStylesheet(cssContent, 'styles.css');
 22 | const root = result.root;
 23 | 
 24 | console.log(`Parsed ${root.nodes.length} top-level nodes`); // → 'Parsed 2 top-level nodes'
 25 | console.log(root.nodes[0].type); // → 'rule'
 26 | console.log(root.nodes[0].selector); // → '.btn'
 27 | ```
 28 | 
 29 | ---
 30 | 
 31 | ## 2 — Using the visitor pattern
 32 | 
 33 | > Traverse CSS AST using the visitor pattern to collect information.
 34 | 
 35 | ```ts
 36 | import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';
 37 | 
 38 | const cssContent = `
 39 | /* Main styles */
 40 | .btn {
 41 |   color: red;
 42 |   font-size: 14px;
 43 | }
 44 | 
 45 | @media (max-width: 768px) {
 46 |   .btn {
 47 |     font-size: 12px;
 48 |   }
 49 | }
 50 | `;
 51 | 
 52 | const result = parseStylesheet(cssContent, 'styles.css');
 53 | const selectors: string[] = [];
 54 | const properties: string[] = [];
 55 | const mediaQueries: string[] = [];
 56 | 
 57 | const visitor = {
 58 |   visitRule: (rule) => {
 59 |     selectors.push(rule.selector);
 60 |   },
 61 |   visitDecl: (decl) => {
 62 |     properties.push(`${decl.prop}: ${decl.value}`);
 63 |   },
 64 |   visitAtRule: (atRule) => {
 65 |     if (atRule.name === 'media') {
 66 |       mediaQueries.push(atRule.params);
 67 |     }
 68 |   },
 69 |   visitComment: (comment) => {
 70 |     console.log(`Found comment: ${comment.text}`);
 71 |   },
 72 | };
 73 | 
 74 | visitEachChild(result.root, visitor);
 75 | 
 76 | console.log('Selectors:', selectors);
 77 | // → ['Selectors:', ['.btn', '.btn']]
 78 | 
 79 | console.log('Properties:', properties);
 80 | // → ['Properties:', ['color: red', 'font-size: 14px', 'font-size: 12px']]
 81 | 
 82 | console.log('Media queries:', mediaQueries);
 83 | // → ['Media queries:', ['(max-width: 768px)']]
 84 | ```
 85 | 
 86 | ---
 87 | 
 88 | ## 3 — Converting AST nodes to source locations
 89 | 
 90 | > Convert CSS rules to linkable source locations for error reporting.
 91 | 
 92 | ```ts
 93 | import {
 94 |   parseStylesheet,
 95 |   styleAstRuleToSource,
 96 | } from '@push-based/styles-ast-utils';
 97 | import { Rule } from 'postcss';
 98 | 
 99 | const cssContent = `
100 | .header {
101 |   background: linear-gradient(to right, #ff0000, #00ff00);
102 |   padding: 2rem;
103 | }
104 | 
105 | .footer {
106 |   margin-top: auto;
107 | }
108 | `;
109 | 
110 | const result = parseStylesheet(cssContent, 'components/layout.css');
111 | const rules = result.root.nodes.filter(
112 |   (node) => node.type === 'rule'
113 | ) as Rule[];
114 | 
115 | rules.forEach((rule, index) => {
116 |   const source = styleAstRuleToSource(rule);
117 |   console.log(`Rule ${index + 1}:`, {
118 |     selector: rule.selector,
119 |     location: `${source.file}:${source.position.startLine}:${source.position.startColumn}`,
120 |     span: `${source.position.startLine}-${source.position.endLine}`,
121 |   });
122 | });
123 | 
124 | // Output:
125 | // Rule 1: {
126 | //   selector: '.header',
127 | //   location: 'components/layout.css:2:1',
128 | //   span: '2-4'
129 | // }
130 | // Rule 2: {
131 | //   selector: '.footer',
132 | //   location: 'components/layout.css:6:1',
133 | //   span: '6-8'
134 | // }
135 | ```
136 | 
137 | ---
138 | 
139 | ## 4 — Handling inline styles with line offset
140 | 
141 | > Parse inline styles and adjust line numbers for accurate source mapping.
142 | 
143 | ```ts
144 | import {
145 |   parseStylesheet,
146 |   styleAstRuleToSource,
147 | } from '@push-based/styles-ast-utils';
148 | import { Rule } from 'postcss';
149 | 
150 | // Simulate inline styles starting at line 15 in a component file
151 | const inlineStyles = `.component-btn { color: blue; }`;
152 | const componentFilePath = 'src/app/button.component.ts';
153 | const styleStartLine = 15; // 0-indexed line where styles begin
154 | 
155 | const result = parseStylesheet(inlineStyles, componentFilePath);
156 | const rule = result.root.nodes[0] as Rule;
157 | 
158 | // Convert with line offset
159 | const source = styleAstRuleToSource(rule, styleStartLine);
160 | 
161 | console.log('Inline style location:', {
162 |   file: source.file,
163 |   line: source.position.startLine, // → 16 (adjusted for file position)
164 |   selector: rule.selector, // → '.component-btn'
165 | });
166 | ```
167 | 
168 | ---
169 | 
170 | ## 5 — Recursive node traversal
171 | 
172 | > Use recursive traversal to process nested CSS structures.
173 | 
174 | ```ts
175 | import {
176 |   parseStylesheet,
177 |   visitEachStyleNode,
178 | } from '@push-based/styles-ast-utils';
179 | 
180 | const cssContent = `
181 | .container {
182 |   display: flex;
183 |   
184 |   .item {
185 |     flex: 1;
186 |     
187 |     &:hover {
188 |       opacity: 0.8;
189 |     }
190 |   }
191 | }
192 | 
193 | @supports (display: grid) {
194 |   .grid-container {
195 |     display: grid;
196 |     grid-template-columns: repeat(3, 1fr);
197 |   }
198 | }
199 | `;
200 | 
201 | const result = parseStylesheet(cssContent, 'nested-styles.scss');
202 | let depth = 0;
203 | 
204 | const visitor = {
205 |   visitRule: (rule) => {
206 |     console.log(`${'  '.repeat(depth)}Rule: ${rule.selector}`);
207 |     depth++;
208 |     if (rule.nodes) {
209 |       visitEachStyleNode(rule.nodes, visitor);
210 |     }
211 |     depth--;
212 |   },
213 |   visitAtRule: (atRule) => {
214 |     console.log(`${'  '.repeat(depth)}@${atRule.name}: ${atRule.params}`);
215 |     depth++;
216 |     if (atRule.nodes) {
217 |       visitEachStyleNode(atRule.nodes, visitor);
218 |     }
219 |     depth--;
220 |   },
221 |   visitDecl: (decl) => {
222 |     console.log(`${'  '.repeat(depth)}${decl.prop}: ${decl.value}`);
223 |   },
224 | };
225 | 
226 | visitEachStyleNode(result.root.nodes, visitor);
227 | 
228 | // Output shows nested structure:
229 | // Rule: .container
230 | //   display: flex
231 | //   Rule: .item
232 | //     flex: 1
233 | //     Rule: &:hover
234 | //       opacity: 0.8
235 | // @supports: (display: grid)
236 | //   Rule: .grid-container
237 | //     display: grid
238 | //     grid-template-columns: repeat(3, 1fr)
239 | ```
240 | 
241 | ---
242 | 
243 | ## 6 — Safe parsing with malformed CSS
244 | 
245 | > Handle malformed CSS gracefully using the safe parser.
246 | 
247 | ```ts
248 | import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';
249 | 
250 | // Malformed CSS with missing closing braces and invalid syntax
251 | const malformedCss = `
252 | .btn {
253 |   color: red
254 |   background: blue;
255 |   /* missing closing brace */
256 | 
257 | .card 
258 |   padding: 1rem;
259 |   margin: invalid-value;
260 | }
261 | 
262 | /* unclosed comment
263 | .footer {
264 |   text-align: center;
265 | `;
266 | 
267 | const result = parseStylesheet(malformedCss, 'malformed.css');
268 | const issues: string[] = [];
269 | 
270 | const visitor = {
271 |   visitRule: (rule) => {
272 |     console.log(`Successfully parsed rule: ${rule.selector}`);
273 |   },
274 |   visitDecl: (decl) => {
275 |     if (!decl.value || decl.value.includes('invalid')) {
276 |       issues.push(`Invalid declaration: ${decl.prop}: ${decl.value}`);
277 |     }
278 |   },
279 | };
280 | 
281 | visitEachChild(result.root, visitor);
282 | 
283 | console.log(`Parsed ${result.root.nodes.length} nodes despite malformed CSS`);
284 | console.log('Issues found:', issues);
285 | 
286 | // The safe parser recovers from errors and continues parsing
287 | // Output:
288 | // Successfully parsed rule: .btn
289 | // Successfully parsed rule: .card
290 | // Successfully parsed rule: .footer
291 | // Parsed 3 nodes despite malformed CSS
292 | // Issues found: ['Invalid declaration: margin: invalid-value']
293 | ```
294 | 
295 | ---
296 | 
297 | ## 7 — Collecting CSS class names
298 | 
299 | > Extract all CSS class selectors from a stylesheet.
300 | 
301 | ```ts
302 | import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils';
303 | 
304 | const cssContent = `
305 | .btn, .button {
306 |   padding: 0.5rem 1rem;
307 | }
308 | 
309 | .btn-primary {
310 |   background: blue;
311 | }
312 | 
313 | .card .header {
314 |   font-weight: bold;
315 | }
316 | 
317 | #main .sidebar .nav-item {
318 |   list-style: none;
319 | }
320 | 
321 | [data-theme="dark"] .btn {
322 |   color: white;
323 | }
324 | `;
325 | 
326 | const result = parseStylesheet(cssContent, 'components.css');
327 | const classNames = new Set<string>();
328 | 
329 | const visitor = {
330 |   visitRule: (rule) => {
331 |     // Extract class names from selectors using regex
332 |     const matches = rule.selector.match(/\.([a-zA-Z0-9_-]+)/g);
333 |     if (matches) {
334 |       matches.forEach((match) => {
335 |         classNames.add(match.substring(1)); // Remove the dot
336 |       });
337 |     }
338 |   },
339 | };
340 | 
341 | visitEachChild(result.root, visitor);
342 | 
343 | console.log('Found CSS classes:', Array.from(classNames).sort());
344 | // → ['Found CSS classes:', ['btn', 'btn-primary', 'button', 'card', 'header', 'nav-item', 'sidebar']]
345 | ```
346 | 
347 | These examples demonstrate the comprehensive CSS parsing and analysis capabilities of the `@push-based/styles-ast-utils` library for various stylesheet processing scenarios.
348 | 
```
Page 6/10FirstPrevNextLast