#
tokens: 49866/50000 26/473 files (page 5/10)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 5 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/components/refactoring-tests/group-3/bad-mixed-3.component.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
  2 | import { MixedStylesComponent3 } from './bad-mixed-3.component';
  3 | 
  4 | describe('MixedStylesComponent', () => {
  5 |   let component: MixedStylesComponent3;
  6 |   let fixture: ComponentFixture<MixedStylesComponent3>;
  7 | 
  8 |   beforeEach(async () => {
  9 |     await TestBed.configureTestingModule({
 10 |       imports: [MixedStylesComponent3],
 11 |     }).compileComponents();
 12 | 
 13 |     fixture = TestBed.createComponent(MixedStylesComponent3);
 14 |     component = fixture.componentInstance;
 15 |     fixture.detectChanges();
 16 |   });
 17 | 
 18 |   it('should create', () => {
 19 |     expect(component).toBeTruthy();
 20 |   });
 21 | 
 22 |   it('should render template', () => {
 23 |     fixture.detectChanges();
 24 |     expect(fixture.nativeElement).toBeTruthy();
 25 |   });
 26 | 
 27 |   describe('Template Content Tests', () => {
 28 |     beforeEach(() => {
 29 |       fixture.detectChanges();
 30 |     });
 31 | 
 32 |     it('should render DS modal component', () => {
 33 |       const dsModal = fixture.nativeElement.querySelector('ds-modal');
 34 |       expect(dsModal).toBeTruthy();
 35 |       expect(dsModal.hasAttribute('open')).toBe(true);
 36 |     });
 37 | 
 38 |     it('should render ds-modal with proper structure', () => {
 39 |       const dsModal = fixture.nativeElement.querySelector('ds-modal');
 40 |       expect(dsModal).toBeTruthy();
 41 | 
 42 |       const modalContent = dsModal.querySelector('ds-modal-content');
 43 |       expect(modalContent).toBeTruthy();
 44 |     });
 45 | 
 46 |     it('should display modal content text', () => {
 47 |       const dsModalContent = fixture.nativeElement.querySelector('ds-modal p');
 48 |       expect(dsModalContent?.textContent?.trim()).toBe('Good Modal Content');
 49 | 
 50 |       const dsModalHeader =
 51 |         fixture.nativeElement.querySelector('ds-modal h2');
 52 |       expect(dsModalHeader?.textContent?.trim()).toBe('Good Modal');
 53 |     });
 54 | 
 55 |     it('should render buttons with different implementations', () => {
 56 |       const dsButton = fixture.nativeElement.querySelector('ds-button');
 57 |       const legacyButton = fixture.nativeElement.querySelector('button.btn');
 58 | 
 59 |       expect(dsButton).toBeTruthy();
 60 |       expect(legacyButton).toBeTruthy();
 61 |     });
 62 | 
 63 |     it('should render progress bars', () => {
 64 |       const dsProgressBar =
 65 |         fixture.nativeElement.querySelector('ds-progress-bar');
 66 |       const legacyProgressBar =
 67 |         fixture.nativeElement.querySelector('div.progress-bar');
 68 | 
 69 |       expect(dsProgressBar).toBeTruthy();
 70 |       expect(legacyProgressBar).toBeTruthy();
 71 |     });
 72 | 
 73 |     it('should render dropdown components', () => {
 74 |       const dsDropdown = fixture.nativeElement.querySelector('ds-dropdown');
 75 |       const legacyDropdown =
 76 |         fixture.nativeElement.querySelector('select.dropdown');
 77 | 
 78 |       expect(dsDropdown).toBeTruthy();
 79 |       expect(legacyDropdown).toBeTruthy();
 80 |     });
 81 | 
 82 |     it('should render alert components', () => {
 83 |       const dsAlert = fixture.nativeElement.querySelector('ds-alert');
 84 |       const legacyAlert = fixture.nativeElement.querySelector('div.alert');
 85 | 
 86 |       expect(dsAlert).toBeTruthy();
 87 |       expect(legacyAlert).toBeTruthy();
 88 |     });
 89 | 
 90 |     it('should render tooltip components', () => {
 91 |       const dsTooltip = fixture.nativeElement.querySelector('ds-tooltip');
 92 |       const legacyTooltip = fixture.nativeElement.querySelector('div.tooltip');
 93 | 
 94 |       expect(dsTooltip).toBeTruthy();
 95 |       expect(legacyTooltip).toBeTruthy();
 96 |     });
 97 | 
 98 |     it('should render breadcrumb navigation', () => {
 99 |       const dsBreadcrumb = fixture.nativeElement.querySelector('ds-breadcrumb');
100 |       const legacyBreadcrumb =
101 |         fixture.nativeElement.querySelector('nav.breadcrumb');
102 | 
103 |       expect(dsBreadcrumb).toBeTruthy();
104 |       expect(legacyBreadcrumb).toBeTruthy();
105 |     });
106 | 
107 |     it('should have breadcrumb items', () => {
108 |       const breadcrumbItems =
109 |         fixture.nativeElement.querySelectorAll('ds-breadcrumb-item');
110 |       expect(breadcrumbItems.length).toBe(3);
111 |     });
112 |   });
113 | });
114 | 
```

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

```typescript
  1 | import { existsSync, readFileSync } from 'node:fs';
  2 | import { createHash } from 'node:crypto';
  3 | import { parseComponents } from '@push-based/angular-ast-utils';
  4 | import { resolveCrossPlatformPathAndValidate } from '../../../shared/index.js';
  5 | import { generateMeta } from './meta.generator.js';
  6 | import { extractPublicApi } from './public-api.extractor.js';
  7 | import { extractSlotsAndDom } from './dom-slots.extractor.js';
  8 | import { collectStylesV2 } from './styles.collector.js';
  9 | import { collectInlineStyles } from './inline-styles.collector.js';
 10 | import type { ComponentContract } from '../../shared/models/types.js';
 11 | import { relative } from 'node:path';
 12 | 
 13 | /**
 14 |  * Build a complete component contract from template and style files.
 15 |  * Template and style paths can be the same as TypeScript path for inline templates/styles.
 16 |  */
 17 | export async function buildComponentContract(
 18 |   templatePath: string,
 19 |   scssPath: string,
 20 |   cwd: string,
 21 |   typescriptPath: string,
 22 | ): Promise<ComponentContract> {
 23 |   const componentTsPath = resolveCrossPlatformPathAndValidate(
 24 |     cwd,
 25 |     typescriptPath,
 26 |   );
 27 | 
 28 |   // Validate TypeScript file exists (required)
 29 |   if (!existsSync(componentTsPath)) {
 30 |     throw new Error(`Component TypeScript file not found: ${componentTsPath}`);
 31 |   }
 32 | 
 33 |   // Resolve and validate template path
 34 |   // If it's the same as TS path, it means inline template
 35 |   const resolvedTemplatePath = resolveCrossPlatformPathAndValidate(
 36 |     cwd,
 37 |     templatePath,
 38 |   );
 39 |   const isInlineTemplate = resolvedTemplatePath === componentTsPath;
 40 | 
 41 |   if (!isInlineTemplate && !existsSync(resolvedTemplatePath)) {
 42 |     throw new Error(`Template file not found: ${resolvedTemplatePath}`);
 43 |   }
 44 | 
 45 |   // Resolve and validate style path
 46 |   // If it's the same as TS path, it means inline styles or no external styles
 47 |   const resolvedScssPath = resolveCrossPlatformPathAndValidate(cwd, scssPath);
 48 |   const isInlineOrNoStyles = resolvedScssPath === componentTsPath;
 49 | 
 50 |   if (!isInlineOrNoStyles && !existsSync(resolvedScssPath)) {
 51 |     throw new Error(`Style file not found: ${resolvedScssPath}`);
 52 |   }
 53 | 
 54 |   const sources = {
 55 |     ts: readFileSync(componentTsPath, 'utf-8'),
 56 |     scss: isInlineOrNoStyles ? '' : readFileSync(resolvedScssPath, 'utf-8'),
 57 |     template: isInlineTemplate
 58 |       ? ''
 59 |       : readFileSync(resolvedTemplatePath, 'utf-8'),
 60 |   };
 61 | 
 62 |   const [parsedComponent] = await parseComponents([componentTsPath]);
 63 |   if (!parsedComponent) {
 64 |     throw new Error(`Failed to parse component: ${componentTsPath}`);
 65 |   }
 66 | 
 67 |   const relativeTemplatePath = relative(cwd, resolvedTemplatePath);
 68 |   const relativeScssPath = relative(cwd, resolvedScssPath);
 69 | 
 70 |   const meta = generateMeta(
 71 |     relativeTemplatePath,
 72 |     parsedComponent,
 73 |     isInlineTemplate,
 74 |   );
 75 |   const publicApi = extractPublicApi(parsedComponent);
 76 |   const { slots, dom } = await extractSlotsAndDom(parsedComponent);
 77 | 
 78 |   const styleBuckets: import('../../shared/models/types.js').StyleDeclarations[] =
 79 |     [];
 80 | 
 81 |   if (!isInlineOrNoStyles) {
 82 |     const externalStyles = await collectStylesV2(resolvedScssPath, dom);
 83 |     externalStyles.sourceFile = relativeScssPath;
 84 |     styleBuckets.push(externalStyles);
 85 |   }
 86 | 
 87 |   const inlineStyles = await collectInlineStyles(parsedComponent, dom);
 88 |   styleBuckets.push(inlineStyles);
 89 | 
 90 |   const styles = styleBuckets.reduce<
 91 |     import('../../shared/models/types.js').StyleDeclarations
 92 |   >(
 93 |     (acc, bucket) => {
 94 |       acc.rules = { ...acc.rules, ...bucket.rules };
 95 |       return acc;
 96 |     },
 97 |     {
 98 |       sourceFile:
 99 |         styleBuckets.length > 0
100 |           ? styleBuckets[styleBuckets.length - 1].sourceFile
101 |           : relativeScssPath,
102 |       rules: {},
103 |     },
104 |   );
105 | 
106 |   const hash = createHash('sha256')
107 |     .update(sources.template + sources.scss + sources.ts)
108 |     .digest('hex');
109 | 
110 |   return {
111 |     meta: { ...meta, hash },
112 |     publicApi,
113 |     slots,
114 |     dom,
115 |     styles,
116 |   };
117 | }
118 | 
```

--------------------------------------------------------------------------------
/testing/utils/src/lib/os-agnostic-paths.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   type MockInstance,
  3 |   afterEach,
  4 |   beforeEach,
  5 |   describe,
  6 |   expect,
  7 |   it,
  8 |   vi,
  9 | } from 'vitest';
 10 | import { osAgnosticPath } from './os-agnostic-paths';
 11 | 
 12 | describe('osAgnosticPath', () => {
 13 |   const cwdSpy: MockInstance<[], string> = vi.spyOn(process, 'cwd');
 14 | 
 15 |   it('should forward nullish paths on Linux/macOS and Windows', () => {
 16 |     expect(osAgnosticPath(undefined)).toBeUndefined();
 17 |   });
 18 | 
 19 |   describe('Unix-based systems (Linux/macOS)', () => {
 20 |     const unixCwd = '/Users/jerry';
 21 | 
 22 |     beforeEach(() => {
 23 |       cwdSpy.mockReturnValue(unixCwd);
 24 |     });
 25 | 
 26 |     afterEach(() => {
 27 |       cwdSpy.mockReset();
 28 |     });
 29 | 
 30 |     it('should convert a path within the CWD to an OS-agnostic path on Linux/macOS', () => {
 31 |       expect(
 32 |         osAgnosticPath(`${unixCwd}/.code-pushup/.code-pushup.config.ts`),
 33 |       ).toBe('<CWD>/.code-pushup/.code-pushup.config.ts');
 34 |     });
 35 | 
 36 |     it('should return paths outside of CWD on Linux/macOS', () => {
 37 |       expect(
 38 |         osAgnosticPath(`${unixCwd}/../.code-pushup/.code-pushup.config.ts`),
 39 |       ).toBe('../.code-pushup/.code-pushup.config.ts');
 40 |     });
 41 | 
 42 |     it('should handle absolute paths correctly on Linux/macOS', () => {
 43 |       expect(osAgnosticPath('/.code-pushup/.code-pushup.config.ts')).toBe(
 44 |         '/.code-pushup/.code-pushup.config.ts',
 45 |       );
 46 |     });
 47 | 
 48 |     it('should handle paths with CWD shorthand "." correctly on Linux/macOS', () => {
 49 |       expect(osAgnosticPath('./.code-pushup/.code-pushup.config.ts')).toBe(
 50 |         './.code-pushup/.code-pushup.config.ts',
 51 |       );
 52 |     });
 53 | 
 54 |     it('should handle relative paths correctly on Linux/macOS', () => {
 55 |       expect(osAgnosticPath('../../.code-pushup/.code-pushup.config.ts')).toBe(
 56 |         '../../.code-pushup/.code-pushup.config.ts',
 57 |       );
 58 |     });
 59 | 
 60 |     it('should handle path segments correctly on Linux/macOS', () => {
 61 |       expect(osAgnosticPath('.code-pushup/.code-pushup.config.ts')).toBe(
 62 |         '.code-pushup/.code-pushup.config.ts',
 63 |       );
 64 |     });
 65 | 
 66 |     it('should NOT modify already OS-agnostic paths on Linux/macOS', () => {
 67 |       expect(osAgnosticPath('<CWD>/.code-pushup/.code-pushup.config.ts')).toBe(
 68 |         '<CWD>/.code-pushup/.code-pushup.config.ts',
 69 |       );
 70 |     });
 71 |   });
 72 | 
 73 |   describe('Windows', () => {
 74 |     const windowsCWD = String.raw`D:\\users\\jerry`;
 75 | 
 76 |     beforeEach(() => {
 77 |       cwdSpy.mockReturnValue(windowsCWD);
 78 |     });
 79 | 
 80 |     afterEach(() => {
 81 |       cwdSpy.mockReset();
 82 |     });
 83 | 
 84 |     it('should return paths outside of CWD on Windows', () => {
 85 |       expect(
 86 |         osAgnosticPath(
 87 |           `${windowsCWD}\\..\\.code-pushup\\.code-pushup.config.ts`,
 88 |         ),
 89 |       ).toBe('../.code-pushup/.code-pushup.config.ts');
 90 |     });
 91 | 
 92 |     it('should convert a path within the CWD to an OS-agnostic path on Windows', () => {
 93 |       expect(
 94 |         osAgnosticPath(`${windowsCWD}\\.code-pushup\\.code-pushup.config.ts`),
 95 |       ).toBe('<CWD>/.code-pushup/.code-pushup.config.ts');
 96 |     });
 97 | 
 98 |     it('should handle absolute paths correctly on Windows', () => {
 99 |       expect(
100 |         osAgnosticPath(String.raw`\.code-pushup\.code-pushup.config.ts`),
101 |       ).toBe('/.code-pushup/.code-pushup.config.ts');
102 |     });
103 | 
104 |     it('should handle paths with CWD shorthand "." correctly on Windows', () => {
105 |       expect(
106 |         osAgnosticPath(String.raw`.\.code-pushup\.code-pushup.config.ts`),
107 |       ).toBe('./.code-pushup/.code-pushup.config.ts');
108 |     });
109 | 
110 |     it('should handle relative paths correctly on Windows', () => {
111 |       expect(
112 |         osAgnosticPath(String.raw`..\..\.code-pushup\.code-pushup.config.ts`),
113 |       ).toBe('../../.code-pushup/.code-pushup.config.ts');
114 |     });
115 | 
116 |     it('should handle path segments correctly on Windows', () => {
117 |       expect(
118 |         osAgnosticPath(String.raw`.code-pushup\.code-pushup.config.ts`),
119 |       ).toBe('.code-pushup/.code-pushup.config.ts');
120 |     });
121 |   });
122 | });
123 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/spec/styles.collector.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /* eslint-disable prefer-const */
  2 | import { describe, it, expect, vi, beforeEach } from 'vitest';
  3 | 
  4 | // -----------------------------------------------------------------------------
  5 | // Mocks for dependencies used by styles.collector.ts
  6 | // -----------------------------------------------------------------------------
  7 | 
  8 | // fs.readFileSync mock
  9 | let readFileSyncMock: any;
 10 | vi.mock('node:fs', () => ({
 11 |   get readFileSync() {
 12 |     return readFileSyncMock;
 13 |   },
 14 | }));
 15 | 
 16 | // Initialize after mock registration
 17 | readFileSyncMock = vi.fn();
 18 | 
 19 | // style AST utilities mocks
 20 | let parseStylesheetMock: any;
 21 | let visitEachChildMock: any;
 22 | vi.mock('@push-based/styles-ast-utils', () => ({
 23 |   get parseStylesheet() {
 24 |     return parseStylesheetMock;
 25 |   },
 26 |   get visitEachChild() {
 27 |     return visitEachChildMock;
 28 |   },
 29 | }));
 30 | 
 31 | // Initialize after mock registration
 32 | parseStylesheetMock = vi.fn();
 33 | visitEachChildMock = vi.fn();
 34 | 
 35 | // selectorMatches mock
 36 | let selectorMatchesMock: any;
 37 | vi.mock('../utils/css-match.js', () => ({
 38 |   get selectorMatches() {
 39 |     return selectorMatchesMock;
 40 |   },
 41 | }));
 42 | 
 43 | // Initialize after mock registration
 44 | selectorMatchesMock = vi.fn();
 45 | 
 46 | // SUT
 47 | import { collectStylesV2 } from '../utils/styles.collector.js';
 48 | 
 49 | // -----------------------------------------------------------------------------
 50 | // Helper to fabricate Rule objects understood by collectStylesV2
 51 | // -----------------------------------------------------------------------------
 52 | function createRule(selector: string, declarations: Record<string, string>) {
 53 |   return {
 54 |     selector,
 55 |     walkDecls(callback: (decl: { prop: string; value: string }) => void) {
 56 |       Object.entries(declarations).forEach(([prop, value]) =>
 57 |         callback({ prop, value }),
 58 |       );
 59 |     },
 60 |   } as any;
 61 | }
 62 | 
 63 | function resetMocks() {
 64 |   readFileSyncMock.mockReset();
 65 |   parseStylesheetMock.mockReset();
 66 |   visitEachChildMock.mockReset();
 67 |   selectorMatchesMock.mockReset();
 68 | }
 69 | 
 70 | // -----------------------------------------------------------------------------
 71 | // Tests
 72 | // -----------------------------------------------------------------------------
 73 | 
 74 | describe('collectStylesV2', () => {
 75 |   const scssPath = '/styles.scss';
 76 | 
 77 |   beforeEach(() => {
 78 |     resetMocks();
 79 |     readFileSyncMock.mockReturnValue('dummy');
 80 |     // parseStylesheet returns root obj with type root
 81 |     parseStylesheetMock.mockReturnValue({ root: { type: 'root' } });
 82 |   });
 83 | 
 84 |   it('collects properties and matches dom elements', async () => {
 85 |     const dom = {
 86 |       div: {} as any,
 87 |     };
 88 | 
 89 |     // Provide one rule 'div { color:red }'
 90 |     visitEachChildMock.mockImplementation((_root: any, visitor: any) => {
 91 |       visitor.visitRule(createRule('div', { color: 'red' }));
 92 |     });
 93 | 
 94 |     selectorMatchesMock.mockImplementation(
 95 |       (css: string, domKey: string) => css === 'div' && domKey === 'div',
 96 |     );
 97 | 
 98 |     const styles = await collectStylesV2(scssPath, dom as any);
 99 | 
100 |     expect(styles.sourceFile).toBe(scssPath);
101 |     expect(styles.rules.div).toBeDefined();
102 |     expect(styles.rules.div.properties).toEqual({ color: 'red' });
103 |     expect(styles.rules.div.appliesTo).toEqual(['div']);
104 |   });
105 | 
106 |   it('handles multiple rules and appliesTo filtering', async () => {
107 |     const dom = {
108 |       div: {} as any,
109 |       'span.foo': {} as any,
110 |     };
111 | 
112 |     visitEachChildMock.mockImplementation((_root: any, visitor: any) => {
113 |       visitor.visitRule(createRule('div', { margin: '0' }));
114 |       visitor.visitRule(createRule('.foo', { padding: '1rem' }));
115 |     });
116 | 
117 |     selectorMatchesMock.mockImplementation((css: string, domKey: string) => {
118 |       if (css === 'div') return domKey === 'div';
119 |       if (css === '.foo') return domKey === 'span.foo';
120 |       return false;
121 |     });
122 | 
123 |     const styles = await collectStylesV2(scssPath, dom as any);
124 | 
125 |     expect(styles.rules.div.appliesTo).toEqual(['div']);
126 |     expect(styles.rules['.foo'].appliesTo).toEqual(['span.foo']);
127 |   });
128 | });
129 | 
```

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

```typescript
  1 | import { Component, signal } from '@angular/core';
  2 | import { DashboardHeaderComponent, UserProfile, NotificationItem } from '../dashboard-header.component';
  3 | 
  4 | @Component({
  5 |   selector: 'app-dashboard-demo',
  6 |   standalone: true,
  7 |   imports: [DashboardHeaderComponent],
  8 |   templateUrl: './dashboard-demo.component.html',
  9 |   styleUrls: ['./dashboard-demo.component.scss']
 10 | })
 11 | export class DashboardDemoComponent {
 12 |   darkMode = signal(false);
 13 |   eventLog = signal<string[]>([]);
 14 |   
 15 |   userProfile = signal<UserProfile>({
 16 |     id: '1',
 17 |     name: 'John Doe',
 18 |     email: '[email protected]',
 19 |     role: 'Administrator',
 20 |     lastLogin: new Date(),
 21 |     avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face'
 22 |   });
 23 | 
 24 |   private users: UserProfile[] = [
 25 |     {
 26 |       id: '1',
 27 |       name: 'John Doe',
 28 |       email: '[email protected]',
 29 |       role: 'Administrator',
 30 |       lastLogin: new Date(),
 31 |       avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=150&h=150&fit=crop&crop=face'
 32 |     },
 33 |     {
 34 |       id: '2',
 35 |       name: 'Jane Smith',
 36 |       email: '[email protected]',
 37 |       role: 'Manager',
 38 |       lastLogin: new Date(Date.now() - 1000 * 60 * 30), // 30 minutes ago
 39 |     },
 40 |     {
 41 |       id: '3',
 42 |       name: 'Mike Johnson',
 43 |       email: '[email protected]',
 44 |       role: 'Developer',
 45 |       lastLogin: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
 46 |       avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop&crop=face'
 47 |     }
 48 |   ];
 49 | 
 50 |   private currentUserIndex = 0;
 51 | 
 52 |   onSearchPerformed(query: string) {
 53 |     this.addLogEntry(`Search performed: "${query}"`);
 54 |   }
 55 | 
 56 |   onBadgeDismissed() {
 57 |     this.addLogEntry('Offer badge dismissed');
 58 |   }
 59 | 
 60 |   onNotificationClicked(notification: NotificationItem) {
 61 |     this.addLogEntry(`Notification clicked: ${notification.title}`);
 62 |   }
 63 | 
 64 |   onUserActionClicked(action: string) {
 65 |     this.addLogEntry(`User action: ${action}`);
 66 |     
 67 |     if (action === 'logout') {
 68 |       this.addLogEntry('User logged out');
 69 |       // In a real app, you would handle logout logic here
 70 |     }
 71 |   }
 72 | 
 73 |   onThemeToggled(isDark: boolean) {
 74 |     this.darkMode.set(isDark);
 75 |     this.addLogEntry(`Theme toggled to: ${isDark ? 'dark' : 'light'}`);
 76 |     
 77 |     // Apply theme to demo container
 78 |     const container = document.querySelector('.demo-container');
 79 |     if (container) {
 80 |       container.classList.toggle('dark', isDark);
 81 |     }
 82 |   }
 83 | 
 84 |   toggleTheme() {
 85 |     const newTheme = !this.darkMode();
 86 |     this.onThemeToggled(newTheme);
 87 |   }
 88 | 
 89 |   changeUser() {
 90 |     this.currentUserIndex = (this.currentUserIndex + 1) % this.users.length;
 91 |     const newUser = this.users[this.currentUserIndex];
 92 |     this.userProfile.set(newUser);
 93 |     this.addLogEntry(`User changed to: ${newUser.name}`);
 94 |   }
 95 | 
 96 |   addNotification() {
 97 |     const notifications = [
 98 |       {
 99 |         id: Date.now().toString(),
100 |         title: 'New Message',
101 |         message: 'You have received a new message from a colleague',
102 |         timestamp: new Date(),
103 |         read: false,
104 |         type: 'info' as const
105 |       },
106 |       {
107 |         id: Date.now().toString(),
108 |         title: 'Task Completed',
109 |         message: 'Your background task has finished successfully',
110 |         timestamp: new Date(),
111 |         read: false,
112 |         type: 'success' as const
113 |       },
114 |       {
115 |         id: Date.now().toString(),
116 |         title: 'Warning',
117 |         message: 'Your session will expire in 10 minutes',
118 |         timestamp: new Date(),
119 |         read: false,
120 |         type: 'warning' as const
121 |       }
122 |     ];
123 |     
124 |     const randomNotification = notifications[Math.floor(Math.random() * notifications.length)];
125 |     this.addLogEntry(`Added notification: ${randomNotification.title}`);
126 |   }
127 | 
128 |   private addLogEntry(message: string) {
129 |     const timestamp = new Date().toLocaleTimeString();
130 |     const entry = `[${timestamp}] ${message}`;
131 |     this.eventLog.update(log => [entry, ...log.slice(0, 49)]); // Keep last 50 entries
132 |   }
133 | } 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/spec/css-match.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, vi } from 'vitest';
  2 | 
  3 | vi.mock('@push-based/angular-ast-utils', () => {
  4 |   return {
  5 |     parseClassNames: (classStr: string) => classStr.trim().split(/\s+/),
  6 |     ngClassesIncludeClassName: (expr: string, className: string) =>
  7 |       expr.includes(className),
  8 |   };
  9 | });
 10 | 
 11 | import { selectorMatches } from '../utils/css-match.js';
 12 | 
 13 | import type {
 14 |   DomElement,
 15 |   Attribute,
 16 |   Binding,
 17 | } from '../../shared/models/types.js';
 18 | 
 19 | function createElement(overrides: Partial<DomElement> = {}): DomElement {
 20 |   return {
 21 |     tag: 'div',
 22 |     parent: null,
 23 |     children: [],
 24 |     attributes: [],
 25 |     bindings: [],
 26 |     events: [],
 27 |     ...overrides,
 28 |   } as DomElement;
 29 | }
 30 | 
 31 | function attr(name: string, source: string): Attribute {
 32 |   return { type: 'attribute', name, source };
 33 | }
 34 | 
 35 | function classBinding(name: string, source = ''): Binding {
 36 |   return { type: 'class', name, source } as Binding;
 37 | }
 38 | 
 39 | describe('selectorMatches', () => {
 40 |   const domKey = '';
 41 | 
 42 |   describe('class selectors', () => {
 43 |     it('matches static class attribute', () => {
 44 |       const el = createElement({
 45 |         attributes: [attr('class', 'foo bar')],
 46 |       });
 47 |       expect(selectorMatches('.foo', domKey, el)).toBe(true);
 48 |       expect(selectorMatches('.bar', domKey, el)).toBe(true);
 49 |       expect(selectorMatches('.baz', domKey, el)).toBe(false);
 50 |     });
 51 | 
 52 |     it('matches Angular [class.foo] binding', () => {
 53 |       const el = createElement({
 54 |         bindings: [classBinding('class.foo')],
 55 |       });
 56 |       expect(selectorMatches('.foo', domKey, el)).toBe(true);
 57 |     });
 58 | 
 59 |     it('matches ngClass expression', () => {
 60 |       const el = createElement({
 61 |         bindings: [classBinding('ngClass', "{ 'foo': cond }")],
 62 |       });
 63 |       expect(selectorMatches('.foo', domKey, el)).toBe(true);
 64 |     });
 65 |   });
 66 | 
 67 |   describe('id selectors', () => {
 68 |     it('matches id attribute', () => {
 69 |       const el = createElement({ attributes: [attr('id', 'my-id')] });
 70 |       expect(selectorMatches('#my-id', domKey, el)).toBe(true);
 71 |       expect(selectorMatches('#other', domKey, el)).toBe(false);
 72 |     });
 73 |   });
 74 | 
 75 |   describe('tag selectors', () => {
 76 |     it('matches element tag', () => {
 77 |       const spanEl = createElement({ tag: 'span' });
 78 |       expect(selectorMatches('span', domKey, spanEl)).toBe(true);
 79 |       expect(selectorMatches('div', domKey, spanEl)).toBe(false);
 80 |     });
 81 |   });
 82 | 
 83 |   describe('attribute selectors', () => {
 84 |     it('matches existence selector', () => {
 85 |       const el = createElement({ attributes: [attr('disabled', '')] });
 86 |       expect(selectorMatches('[disabled]', domKey, el)).toBe(true);
 87 |     });
 88 | 
 89 |     it('matches equality selector', () => {
 90 |       const el = createElement({ attributes: [attr('type', 'button')] });
 91 |       expect(selectorMatches('[type="button"]', domKey, el)).toBe(true);
 92 |       expect(selectorMatches('[type="text"]', domKey, el)).toBe(false);
 93 |     });
 94 | 
 95 |     it('matches *= selector', () => {
 96 |       const el = createElement({
 97 |         attributes: [attr('data-role', 'dialog-box')],
 98 |       });
 99 |       expect(selectorMatches('[data-role*="dialog"]', domKey, el)).toBe(true);
100 |       expect(selectorMatches('[data-role*="modal"]', domKey, el)).toBe(false);
101 |     });
102 | 
103 |     it('matches ^= and $= selectors', () => {
104 |       const el = createElement({
105 |         attributes: [attr('data-role', 'dialog-box')],
106 |       });
107 |       expect(selectorMatches('[data-role^="dialog"]', domKey, el)).toBe(true);
108 |       expect(selectorMatches('[data-role$="box"]', domKey, el)).toBe(true);
109 |     });
110 |   });
111 | 
112 |   describe('composite selectors', () => {
113 |     it('matches when any comma-separated selector matches', () => {
114 |       const el = createElement({ attributes: [attr('class', 'foo')] });
115 |       expect(selectorMatches('.foo, #bar', domKey, el)).toBe(true);
116 |       expect(selectorMatches('.baz, #bar', domKey, el)).toBe(false);
117 |     });
118 | 
119 |     it('matches last part of descendant selector', () => {
120 |       const el = createElement({ attributes: [attr('class', 'foo')] });
121 |       expect(selectorMatches('div .foo', domKey, el)).toBe(true);
122 |     });
123 |   });
124 | });
125 | 
```

--------------------------------------------------------------------------------
/packages/shared/utils/src/lib/file/default-export-loader.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, expect, it, beforeEach, afterEach } from 'vitest';
  2 | import { writeFileSync, rmSync, mkdirSync } from 'node:fs';
  3 | import { join } from 'node:path';
  4 | import { tmpdir } from 'node:os';
  5 | import { loadDefaultExport } from './default-export-loader.js';
  6 | 
  7 | describe('loadDefaultExport', () => {
  8 |   let testDir: string;
  9 | 
 10 |   beforeEach(() => {
 11 |     testDir = join(
 12 |       tmpdir(),
 13 |       `test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
 14 |     );
 15 |     mkdirSync(testDir, { recursive: true });
 16 |   });
 17 | 
 18 |   afterEach(() => {
 19 |     rmSync(testDir, { recursive: true, force: true });
 20 |   });
 21 | 
 22 |   const createFile = (name: string, content: string) => {
 23 |     const path = join(testDir, name);
 24 |     writeFileSync(path, content, 'utf-8');
 25 |     return path;
 26 |   };
 27 | 
 28 |   describe('Success Cases', () => {
 29 |     it.each([
 30 |       {
 31 |         type: 'array',
 32 |         content: '[{name: "test"}]',
 33 |         expected: [{ name: 'test' }],
 34 |       },
 35 |       {
 36 |         type: 'object',
 37 |         content: '{version: "1.0"}',
 38 |         expected: { version: '1.0' },
 39 |       },
 40 |       { type: 'string', content: '"test"', expected: 'test' },
 41 |       { type: 'null', content: 'null', expected: null },
 42 |       { type: 'boolean', content: 'false', expected: false },
 43 |       { type: 'undefined', content: 'undefined', expected: undefined },
 44 |     ])('should load $type default export', async ({ content, expected }) => {
 45 |       const path = createFile('test.mjs', `export default ${content};`);
 46 |       expect(await loadDefaultExport(path)).toEqual(expected);
 47 |     });
 48 |   });
 49 | 
 50 |   describe('Error Cases - No Default Export', () => {
 51 |     it.each([
 52 |       {
 53 |         desc: 'named exports only',
 54 |         content: 'export const a = 1; export const b = 2;',
 55 |         exports: 'a, b',
 56 |       },
 57 |       { desc: 'empty module', content: '', exports: 'none' },
 58 |       { desc: 'comments only', content: '// comment', exports: 'none' },
 59 |       {
 60 |         desc: 'function exports',
 61 |         content: 'export function fn() {}',
 62 |         exports: 'fn',
 63 |       },
 64 |     ])('should throw error for $desc', async ({ content, exports }) => {
 65 |       const path = createFile('test.mjs', content);
 66 |       await expect(loadDefaultExport(path)).rejects.toThrow(
 67 |         `No default export found in module. Expected ES Module format:\nexport default [...]\n\nAvailable exports: ${exports}`,
 68 |       );
 69 |     });
 70 |   });
 71 | 
 72 |   describe('Error Cases - File System', () => {
 73 |     it('should throw error when file does not exist', async () => {
 74 |       const path = join(testDir, 'missing.mjs');
 75 |       await expect(loadDefaultExport(path)).rejects.toThrow(
 76 |         `Failed to load module from ${path}`,
 77 |       );
 78 |     });
 79 | 
 80 |     it('should throw error when file has syntax errors', async () => {
 81 |       const path = createFile(
 82 |         'syntax.mjs',
 83 |         'export default { invalid: syntax }',
 84 |       );
 85 |       await expect(loadDefaultExport(path)).rejects.toThrow(
 86 |         `Failed to load module from ${path}`,
 87 |       );
 88 |     });
 89 |   });
 90 | 
 91 |   describe('Edge Cases', () => {
 92 |     it('should work with TypeScript generics', async () => {
 93 |       interface Config {
 94 |         name: string;
 95 |       }
 96 |       const path = createFile('typed.mjs', 'export default [{name: "test"}];');
 97 |       const result = await loadDefaultExport<Config[]>(path);
 98 |       expect(result).toEqual([{ name: 'test' }]);
 99 |     });
100 | 
101 |     it('should handle mixed exports (prefers default)', async () => {
102 |       const path = createFile(
103 |         'mixed.mjs',
104 |         'export const named = "n"; export default "d";',
105 |       );
106 |       expect(await loadDefaultExport<string>(path)).toBe('d');
107 |     });
108 | 
109 |     it('should handle complex nested structures', async () => {
110 |       const path = createFile(
111 |         'complex.mjs',
112 |         `
113 |         export default {
114 |           data: [{ name: 'test', meta: { date: new Date('2024-01-01') } }],
115 |           version: '1.0'
116 |         };
117 |       `,
118 |       );
119 |       const result = await loadDefaultExport(path);
120 |       expect(result).toMatchObject({
121 |         data: [{ name: 'test', meta: { date: expect.any(Date) } }],
122 |         version: '1.0',
123 |       });
124 |     });
125 |   });
126 | });
127 | 
```

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

```typescript
  1 | import {
  2 |   createHandler,
  3 |   BaseHandlerOptions,
  4 | } from './shared/utils/handler-helpers.js';
  5 | import { ToolSchemaOptions } from '@push-based/models';
  6 | import { dsComponentCoveragePlugin } from '@push-based/ds-component-coverage';
  7 | import { baseToolsSchema } from '../schema.js';
  8 | import { join } from 'node:path';
  9 | import {
 10 |   reportViolationsTools,
 11 |   reportAllViolationsTools,
 12 | } from './report-violations/index.js';
 13 | 
 14 | export const componentCoverageToolsSchema: ToolSchemaOptions = {
 15 |   name: 'ds_component-coverage',
 16 |   description:
 17 |     'Migration report. Search for deprecated CSS classes in a component. List available options with the tool `ds_list-options',
 18 |   inputSchema: {
 19 |     type: 'object',
 20 |     properties: {
 21 |       ...baseToolsSchema.inputSchema.properties,
 22 |       directory: {
 23 |         type: 'string',
 24 |         description:
 25 |           'The relative path the directory (starting with "./path/to/dir" avoid big folders.)  to run the task in starting from CWD. Respect the OS specifics.',
 26 |       },
 27 |       dsComponents: {
 28 |         type: 'array',
 29 |         items: {
 30 |           type: 'object',
 31 |           required: ['componentName', 'deprecatedCssClasses'],
 32 |           properties: {
 33 |             componentName: {
 34 |               type: 'string',
 35 |               description: 'The class name of the component to search for',
 36 |             },
 37 |             deprecatedCssClasses: {
 38 |               type: 'array',
 39 |               items: {
 40 |                 type: 'string',
 41 |               },
 42 |               description: 'List of deprecated CSS classes for this component',
 43 |             },
 44 |             docsUrl: {
 45 |               type: 'string',
 46 |               description: 'URL to the component documentation',
 47 |             },
 48 |           },
 49 |         },
 50 |         description: 'Array of components and their deprecated CSS classes',
 51 |       },
 52 |     },
 53 |     required: [
 54 |       ...(baseToolsSchema.inputSchema.required as string[]),
 55 |       'directory',
 56 |       'dsComponents',
 57 |     ],
 58 |   },
 59 |   annotations: {
 60 |     title: 'Design System Component Coverage',
 61 |     readOnlyHint: true,
 62 |     openWorldHint: true,
 63 |     idempotentHint: false,
 64 |   },
 65 | };
 66 | 
 67 | interface ComponentCoverageOptions extends BaseHandlerOptions {
 68 |   directory: string;
 69 |   dsComponents: Array<{
 70 |     componentName: string;
 71 |     deprecatedCssClasses: string[];
 72 |     docsUrl?: string;
 73 |   }>;
 74 | }
 75 | 
 76 | export const componentCoverageHandler = createHandler<
 77 |   ComponentCoverageOptions,
 78 |   any
 79 | >(
 80 |   componentCoverageToolsSchema.name,
 81 |   async (params, { cwd }) => {
 82 |     const { directory, dsComponents, ...pluginOptions } = params;
 83 | 
 84 |     const pluginConfig = await dsComponentCoveragePlugin({
 85 |       ...pluginOptions,
 86 |       directory: join(cwd, directory),
 87 |       dsComponents,
 88 |     });
 89 | 
 90 |     const { executePlugin } = await import('@code-pushup/core');
 91 |     const result = await executePlugin(pluginConfig as any, {
 92 |       cache: { read: false, write: false },
 93 |       persist: { outputDir: '' },
 94 |     });
 95 |     const reducedResult = {
 96 |       ...result,
 97 |       audits: result.audits.filter(({ score }) => score < 1),
 98 |     };
 99 | 
100 |     return {
101 |       directory,
102 |       reducedResult,
103 |     };
104 |   },
105 |   (result) => {
106 |     const output = [`List of missing DS components:`];
107 | 
108 |     output.push(`Result:\n\nBase directory: ${result.directory}`);
109 | 
110 |     result.reducedResult.audits.forEach(({ details, title }: any) => {
111 |       const auditOutput = [`\n${title}`];
112 |       (details?.issues ?? []).forEach(
113 |         ({ message, source }: any, index: number) => {
114 |           if (index === 0) {
115 |             auditOutput.push(message.replace(result.directory + '/', ''));
116 |           }
117 |           auditOutput.push(
118 |             `  - ${source?.file.replace(result.directory + '/', '')}#L${source?.position?.startLine}`,
119 |           );
120 |         },
121 |       );
122 |       output.push(auditOutput.join('\n'));
123 |     });
124 | 
125 |     return [output.join('\n')];
126 |   },
127 | );
128 | 
129 | export const componentCoverageTools = [
130 |   {
131 |     schema: componentCoverageToolsSchema,
132 |     handler: componentCoverageHandler,
133 |   },
134 | ];
135 | 
136 | export const dsTools = [
137 |   ...componentCoverageTools,
138 |   ...reportViolationsTools,
139 |   ...reportAllViolationsTools,
140 | ];
141 | 
```

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

```scss
  1 | // Product Card Component Styles
  2 | .product-card {
  3 |   background: white;
  4 |   border-radius: 0.75rem;
  5 |   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  6 |   overflow: hidden;
  7 |   transition: all 0.3s ease;
  8 |   position: relative;
  9 |   
 10 |   &:hover {
 11 |     transform: translateY(-2px);
 12 |     box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
 13 |   }
 14 |   
 15 |   &.product-card-selected {
 16 |     border: 2px solid #3b82f6;
 17 |     box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
 18 |   }
 19 | }
 20 | 
 21 | // Product Image Section
 22 | .product-image-container {
 23 |   position: relative;
 24 |   width: 100%;
 25 |   height: 200px;
 26 |   overflow: hidden;
 27 | }
 28 | 
 29 | .product-image {
 30 |   width: 100%;
 31 |   height: 100%;
 32 |   object-fit: cover;
 33 |   transition: transform 0.3s ease;
 34 |   
 35 |   .product-card:hover & {
 36 |     transform: scale(1.05);
 37 |   }
 38 | }
 39 | 
 40 | // Badge Overlay - Moderate complexity offer-badge
 41 | .badge-overlay {
 42 |   position: absolute;
 43 |   top: 0.75rem;
 44 |   left: 0.75rem;
 45 |   z-index: 2;
 46 | }
 47 | 
 48 | // DsBadge replaces the custom offer-badge implementation
 49 | // The badge styling is now handled by the design system component
 50 | 
 51 | // Stock Overlay
 52 | .stock-overlay {
 53 |   position: absolute;
 54 |   top: 0;
 55 |   left: 0;
 56 |   right: 0;
 57 |   bottom: 0;
 58 |   background: rgba(0, 0, 0, 0.6);
 59 |   display: flex;
 60 |   align-items: center;
 61 |   justify-content: center;
 62 |   z-index: 3;
 63 | }
 64 | 
 65 | .stock-badge {
 66 |   background: #ef4444;
 67 |   color: white;
 68 |   padding: 0.5rem 1rem;
 69 |   border-radius: 0.375rem;
 70 |   font-weight: 600;
 71 |   text-transform: uppercase;
 72 |   font-size: 0.875rem;
 73 | }
 74 | 
 75 | // Product Content
 76 | .product-content {
 77 |   padding: 1rem;
 78 | }
 79 | 
 80 | .product-header {
 81 |   display: flex;
 82 |   justify-content: space-between;
 83 |   align-items: flex-start;
 84 |   margin-bottom: 0.5rem;
 85 | }
 86 | 
 87 | .product-name {
 88 |   margin: 0;
 89 |   font-size: 1.125rem;
 90 |   font-weight: 600;
 91 |   color: #1f2937;
 92 |   line-height: 1.4;
 93 |   flex: 1;
 94 |   margin-right: 0.5rem;
 95 | }
 96 | 
 97 | .favorite-button {
 98 |   background: none;
 99 |   border: none;
100 |   cursor: pointer;
101 |   padding: 0.25rem;
102 |   border-radius: 0.25rem;
103 |   transition: all 0.2s ease;
104 |   color: #6b7280;
105 |   
106 |   &:hover {
107 |     background: #f3f4f6;
108 |     color: #ef4444;
109 |   }
110 |   
111 |   &.favorite-active {
112 |     color: #ef4444;
113 |   }
114 | }
115 | 
116 | .product-category {
117 |   font-size: 0.875rem;
118 |   color: #6b7280;
119 |   margin-bottom: 0.75rem;
120 |   text-transform: capitalize;
121 | }
122 | 
123 | .product-pricing {
124 |   display: flex;
125 |   align-items: center;
126 |   gap: 0.5rem;
127 |   margin-bottom: 0.75rem;
128 | }
129 | 
130 | .original-price {
131 |   font-size: 0.875rem;
132 |   color: #6b7280;
133 |   text-decoration: line-through;
134 | }
135 | 
136 | .current-price {
137 |   font-size: 1.125rem;
138 |   font-weight: 700;
139 |   color: #ef4444;
140 | }
141 | 
142 | .product-rating {
143 |   display: flex;
144 |   align-items: center;
145 |   gap: 0.5rem;
146 |   margin-bottom: 0.75rem;
147 | }
148 | 
149 | .rating-stars {
150 |   display: flex;
151 |   gap: 0.125rem;
152 | }
153 | 
154 | .star {
155 |   color: #d1d5db;
156 |   font-size: 1rem;
157 |   
158 |   &.star-filled {
159 |     color: #f59e0b;
160 |   }
161 | }
162 | 
163 | .rating-text {
164 |   font-size: 0.875rem;
165 |   color: #6b7280;
166 | }
167 | 
168 | .product-tags {
169 |   display: flex;
170 |   flex-wrap: wrap;
171 |   gap: 0.375rem;
172 |   margin-bottom: 1rem;
173 | }
174 | 
175 | .product-tag {
176 |   background: #f3f4f6;
177 |   color: #374151;
178 |   padding: 0.25rem 0.5rem;
179 |   border-radius: 0.25rem;
180 |   font-size: 0.75rem;
181 |   font-weight: 500;
182 | }
183 | 
184 | .tag-more {
185 |   color: #6b7280;
186 |   font-size: 0.75rem;
187 |   font-style: italic;
188 | }
189 | 
190 | // Product Actions
191 | .product-actions {
192 |   padding: 0 1rem 1rem;
193 |   display: flex;
194 |   gap: 0.5rem;
195 | }
196 | 
197 | .action-button {
198 |   flex: 1;
199 |   padding: 0.75rem;
200 |   border: none;
201 |   border-radius: 0.375rem;
202 |   font-weight: 600;
203 |   font-size: 0.875rem;
204 |   cursor: pointer;
205 |   transition: all 0.2s ease;
206 |   
207 |   &.add-to-cart {
208 |     background: #3b82f6;
209 |     color: white;
210 |     
211 |     &:hover:not(:disabled) {
212 |       background: #2563eb;
213 |     }
214 |     
215 |     &:disabled {
216 |       background: #9ca3af;
217 |       cursor: not-allowed;
218 |     }
219 |   }
220 |   
221 |   &.quick-view {
222 |     background: #f3f4f6;
223 |     color: #374151;
224 |     border: 1px solid #d1d5db;
225 |     
226 |     &:hover {
227 |       background: #e5e7eb;
228 |       border-color: #9ca3af;
229 |     }
230 |   }
231 | }
232 | 
233 | // Custom badge animations removed - DsBadge handles its own styling
234 | 
235 | // Responsive design
236 | @media (max-width: 768px) {
237 |   .product-card {
238 |     margin: 0.5rem;
239 |   }
240 |   
241 |   // DsBadge responsive styles are handled by the design system
242 |   
243 |   .product-name {
244 |     font-size: 1rem;
245 |   }
246 |   
247 |   .product-actions {
248 |     flex-direction: column;
249 |     
250 |     .action-button {
251 |       flex: none;
252 |     }
253 |   }
254 | } 
```

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

```markdown
 1 | # Public API — Quick Reference
 2 | 
 3 | | Symbol                              | Kind     | Signature                                                                     | Summary                                                |
 4 | | ----------------------------------- | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------ |
 5 | | `ANGULAR_COMPONENT_DECORATOR`       | function | `const ANGULAR_COMP...`                                                       | Constant for Angular component decorator string        |
 6 | | `AngularUnit`                       | function | `type AngularUnit = z.infer<…>`                                               | Union type for Angular unit types                      |
 7 | | `AngularUnitSchema`                 | function | `const AngularUnitSchema = z.enum…`                                           | Zod schema for Angular unit types                      |
 8 | | `Asset<T>`                          | function | `interface Asset<T>`                                                          | Typed asset wrapper                                    |
 9 | | `assetFromPropertyArrayInitializer` | function | `assetFromPropertyArrayInitializer(prop, sourceFile, textParser): Asset<T>[]` | Create assets from array property initializers         |
10 | | `assetFromPropertyValueInitializer` | function | `assetFromPropertyValueInitializer(opts): Asset<T>`                           | Create asset from property value initializer           |
11 | | `classDecoratorVisitor`             | function | `classDecoratorVisitor(opts): Visitor`                                        | Iterate class decorators in a SourceFile               |
12 | | `findAngularUnits`                  | function | `findAngularUnits(directory, unit): Promise<string[]>`                        | Find Angular unit files by type in directory           |
13 | | `ngClassesIncludeClassName`         | function | `ngClassesIncludeClassName(src, name): boolean`                               | Check if class name exists inside `[ngClass]` bindings |
14 | | `parseAngularUnit`                  | function | `parseAngularUnit(directory, unit): Promise<ParsedComponent[]>`               | Parse Angular units in a directory                     |
15 | | `parseClassNames`                   | function | `parseClassNames(str): string[]`                                              | Split string into individual CSS class names           |
16 | | `parseComponents`                   | function | `parseComponents(files): Promise<ParsedComponent[]>`                          | Parse TS/TSX components into AST descriptors           |
17 | | `ParsedComponent`                   | function | `type ParsedComponent`                                                        | Type for parsed Angular component data                 |
18 | | `SourceLink`                        | function | `type SourceLink`                                                             | Type for source file location reference                |
19 | | `tmplAstElementToSource`            | function | `tmplAstElementToSource(el): Source`                                          | Convert template AST elements to source map location   |
20 | | `visitAngularDecoratorProperties`   | function | `visitAngularDecoratorProperties(opts)`                                       | Visit properties of Angular decorators                 |
21 | | `visitAngularDecorators`            | function | `visitAngularDecorators(opts)`                                                | Traverse decorators & return matches                   |
22 | | `visitComponentStyles`              | function | `visitComponentStyles(comp, arg, cb): Promise<Issue[]>`                       | Visit component styles with callback                   |
23 | | `visitComponentTemplate`            | function | `visitComponentTemplate(comp, arg, cb)`                                       | Run visitor against a component's template             |
24 | | `visitEachTmplChild`                | function | `visitEachTmplChild(nodes, visitor)`                                          | Visit each template AST child node                     |
25 | 
```

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

```typescript
  1 | import * as path from 'path';
  2 | import { toUnixPath } from '@code-pushup/utils';
  3 | import { buildText } from '../../shared/utils/output.utils.js';
  4 | import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
  5 | import {
  6 |   DependencyGraphResult,
  7 |   DependencyInfo,
  8 |   ComponentGroup,
  9 |   FileExtension,
 10 | } from '../models/types.js';
 11 | import { DEPENDENCY_ANALYSIS_CONFIG } from '../models/config.js';
 12 | 
 13 | // Consolidated utilities
 14 | const NAME_RE =
 15 |   /^(.*?)(?:\.(?:component|directive|pipe|service|module|spec))?\.(?:ts|js|html?|s?css|less)$/i;
 16 | 
 17 | const baseName = (filePath: string): string => {
 18 |   const fileName = path.basename(filePath);
 19 |   const match = fileName.match(NAME_RE);
 20 |   return match ? match[1] : fileName;
 21 | };
 22 | 
 23 | const rel = (root: string, p: string) =>
 24 |   toUnixPath(path.isAbsolute(p) ? path.relative(root, p) : p);
 25 | 
 26 | type Index = Map<string, string[]>; // baseName → related files
 27 | 
 28 | const buildIndex = (graph: DependencyGraphResult): Index => {
 29 |   const index: Index = new Map();
 30 |   for (const fp of Object.keys(graph)) {
 31 |     const bn = baseName(fp);
 32 |     (index.get(bn) ?? index.set(bn, []).get(bn)!).push(fp);
 33 |   }
 34 |   return index;
 35 | };
 36 | 
 37 | const expandViolations = (seeds: string[], index: Index): string[] => [
 38 |   ...new Set(seeds.flatMap((s) => index.get(baseName(s)) ?? [])),
 39 | ];
 40 | 
 41 | export const assetToComponentTs = (p: string): string =>
 42 |   path.join(path.dirname(p), baseName(p) + '.component.ts');
 43 | 
 44 | export const filterGraph = (
 45 |   graph: DependencyGraphResult,
 46 |   violationFiles: string[],
 47 |   root: string,
 48 |   index: Index = buildIndex(graph),
 49 | ): DependencyGraphResult => {
 50 |   const seeds = violationFiles.flatMap((f) =>
 51 |     /\.(html?|s?css|sass|less)$/i.test(f) ? [f, assetToComponentTs(f)] : [f],
 52 |   );
 53 | 
 54 |   const bad = new Set(expandViolations(seeds, index).map((f) => rel(root, f)));
 55 |   const badNames = new Set([...bad].map(baseName));
 56 | 
 57 |   return Object.fromEntries(
 58 |     Object.entries(graph).filter(
 59 |       ([fp, info]) =>
 60 |         bad.has(fp) ||
 61 |         badNames.has(baseName(fp)) ||
 62 |         info.dependencies.some(
 63 |           (d) => d.type === 'reverse-dependency' && bad.has(d.path),
 64 |         ),
 65 |     ),
 66 |   );
 67 | };
 68 | 
 69 | const buildGroups = (
 70 |   result: DependencyGraphResult,
 71 | ): Map<string, ComponentGroup> => {
 72 |   const componentGroups = new Map<string, ComponentGroup>();
 73 | 
 74 |   for (const [filePath, fileInfo] of Object.entries(result)) {
 75 |     const bn = baseName(filePath);
 76 | 
 77 |     if (!componentGroups.has(bn)) {
 78 |       componentGroups.set(bn, {
 79 |         relatedFiles: [],
 80 |         hasReverseDeps: false,
 81 |       });
 82 |     }
 83 | 
 84 |     const group = componentGroups.get(bn);
 85 |     if (group) {
 86 |       if (fileInfo.isAngularComponent) {
 87 |         group.componentFile = [filePath, fileInfo];
 88 |         group.hasReverseDeps = fileInfo.dependencies.some(
 89 |           (dep: DependencyInfo) => dep.type === 'reverse-dependency',
 90 |         );
 91 |       } else {
 92 |         group.relatedFiles.push([filePath, fileInfo]);
 93 |       }
 94 |     }
 95 |   }
 96 | 
 97 |   return componentGroups;
 98 | };
 99 | 
100 | const getFileType = (filePath: string): string => {
101 |   const extension = path.extname(filePath).toLowerCase() as FileExtension;
102 |   return DEPENDENCY_ANALYSIS_CONFIG.fileTypeMap[extension] || 'file';
103 | };
104 | 
105 | type Mode = 'inline' | 'entity';
106 | 
107 | export const printComponents = (
108 |   graph: DependencyGraphResult,
109 |   mode: Mode = 'inline',
110 | ): string | CallToolResult['content'] => {
111 |   const groups = buildGroups(graph);
112 |   const comps = [...groups.values()].filter((g) => g.componentFile);
113 | 
114 |   if (!comps.length)
115 |     return mode === 'inline'
116 |       ? 'No Angular components found with violations.'
117 |       : [buildText('No Angular components found with violations.')];
118 | 
119 |   const toLines = (g: ComponentGroup) => {
120 |     if (!g.componentFile) return '';
121 |     const [cp, ci] = g.componentFile;
122 |     return [
123 |       `Component: ${ci.componentName ?? 'Unknown'}`,
124 |       '',
125 |       `- ${ci.type}: ${cp}`,
126 |       ...g.relatedFiles.map(([p, i]) => `- ${i.type}: ${p}`),
127 |       ...ci.dependencies
128 |         .filter((d) => d.type === 'reverse-dependency')
129 |         .map(
130 |           (d) =>
131 |             `- ${getFileType(d.resolvedPath ?? d.path)}: ${d.resolvedPath ?? d.path}`,
132 |         ),
133 |     ].join('\n');
134 |   };
135 | 
136 |   if (mode === 'inline') {
137 |     return comps.map(toLines).join('\n\n');
138 |   }
139 |   return comps.map((g) => buildText(toLines(g)));
140 | };
141 | 
```

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

```typescript
  1 | import {
  2 |   ChangeDetectionStrategy,
  3 |   Component,
  4 |   booleanAttribute,
  5 |   inject,
  6 |   input,
  7 | } from '@angular/core';
  8 | import {
  9 |   MAT_BOTTOM_SHEET_DATA,
 10 |   MatBottomSheet,
 11 |   MatBottomSheetModule,
 12 |   MatBottomSheetRef,
 13 | } from '@angular/material/bottom-sheet';
 14 | import {
 15 |   MAT_DIALOG_DATA,
 16 |   MatDialog,
 17 |   MatDialogModule,
 18 |   MatDialogRef,
 19 | } from '@angular/material/dialog';
 20 | 
 21 | import { DemoCloseIconComponent } from '@design-system/storybook-demo-cmp-lib';
 22 | import { DsButton } from '@frontend/ui/button';
 23 | import { DsButtonIcon } from '@frontend/ui/button-icon';
 24 | import {
 25 |   DsModal,
 26 |   DsModalContent,
 27 |   DsModalHeader,
 28 |   DsModalHeaderVariant,
 29 |   DsModalVariant,
 30 | } from '@frontend/ui/modal';
 31 | 
 32 | @Component({
 33 |   selector: 'ds-demo-dialog-cmp',
 34 |   imports: [
 35 |     MatDialogModule,
 36 |     DsButton,
 37 |     DsModalHeader,
 38 |     DsButtonIcon,
 39 |     DemoCloseIconComponent,
 40 |     DsModal,
 41 |     DsModalContent,
 42 |   ],
 43 |   standalone: true,
 44 |   template: `
 45 |     <ds-modal
 46 |       [inverse]="data.inverse"
 47 |       [bottomSheet]="data.bottomSheet"
 48 |       [variant]="data.variant"
 49 |     >
 50 |       <ds-modal-header [variant]="data.headerVariant">
 51 |         <div slot="start">
 52 |           <div slot="title">Hello start</div>
 53 |           <div slot="subtitle">Header subtitle</div>
 54 |         </div>
 55 | 
 56 |         <button slot="end" ds-button-icon size="small" (click)="close()">
 57 |           Close
 58 |         </button>
 59 |       </ds-modal-header>
 60 |       <ds-modal-content>
 61 |         Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam,
 62 |         ducimus, sequi! Ab consequatur earum expedita fugit illo illum in
 63 |         maiores nihil nostrum officiis ratione repellendus temporibus, vel!
 64 |         <br />
 65 |         <br />
 66 |         <b>Lorem ipsum dolor sit amet</b>, consectetur adipisicing elit.
 67 |         Aliquam, ducimus, sequi! Ab consequatur earum expedita fugit illo illum
 68 |         in maiores nihil nostrum officiis ratione repellendus temporibus, vel!
 69 |         <br />
 70 |         <br />
 71 |         <div class="footer-buttons">
 72 |           <button
 73 |             ds-button
 74 |             [inverse]="data.inverse"
 75 |             kind="secondary"
 76 |             variant="outline"
 77 |             mat-dialog-close
 78 |           >
 79 |             Outline Button
 80 |           </button>
 81 |           <button
 82 |             ds-button
 83 |             [inverse]="data.inverse"
 84 |             kind="primary"
 85 |             variant="filled"
 86 |             mat-dialog-close
 87 |           >
 88 |             Filled Button
 89 |           </button>
 90 |         </div>
 91 |       </ds-modal-content>
 92 |     </ds-modal>
 93 |   `,
 94 |   styles: `
 95 |     ds-modal {
 96 |       width: 400px;
 97 |       min-height: 300px;
 98 |       margin-left: auto;
 99 |       margin-right: auto;
100 |     }
101 | 
102 |     .footer-buttons {
103 |       display: grid;
104 |       grid-template-columns: 1fr 1fr;
105 |       gap: 10px;
106 |     }
107 |   `,
108 |   changeDetection: ChangeDetectionStrategy.OnPush,
109 | })
110 | export class DemoModalCmp {
111 |   dialogRef = inject(MatDialogRef<DemoModalCmp>, { optional: true });
112 |   bottomSheetRef = inject(MatBottomSheetRef<DemoModalCmp>, { optional: true });
113 |   dialogData = inject(MAT_DIALOG_DATA, { optional: true });
114 |   bottomSheetData = inject(MAT_BOTTOM_SHEET_DATA, { optional: true });
115 | 
116 |   data = this.dialogData ?? this.bottomSheetData ?? {}; // fallback to empty {}
117 | 
118 |   close() {
119 |     this.dialogRef?.close();
120 |     this.bottomSheetRef?.dismiss();
121 |   }
122 | }
123 | 
124 | @Component({
125 |   selector: 'ds-demo-dialog-container',
126 |   standalone: true,
127 |   imports: [MatDialogModule, MatBottomSheetModule, DsButton],
128 |   template: `
129 |     <button ds-button (click)="openDialog()">Open with Material Dialog</button>
130 |   `,
131 |   changeDetection: ChangeDetectionStrategy.OnPush,
132 | })
133 | export class DemoModalContainer {
134 |   dialog = inject(MatDialog);
135 |   bottomSheet = inject(MatBottomSheet);
136 | 
137 |   headerVariant = input<DsModalHeaderVariant>();
138 |   variant = input<DsModalVariant>();
139 |   inverse = input(false, { transform: booleanAttribute });
140 |   bottomSheetInput = input(false, { transform: booleanAttribute });
141 | 
142 |   openDialog() {
143 |     const data = {
144 |       headerVariant: this.headerVariant(),
145 |       inverse: this.inverse(),
146 |       variant: this.variant(),
147 |       bottomSheet: this.bottomSheetInput(),
148 |     };
149 | 
150 |     if (data.bottomSheet) {
151 |       this.bottomSheet.open(DemoModalCmp, {
152 |         data,
153 |         panelClass: 'ds-bottom-sheet-panel',
154 |       });
155 |     } else {
156 |       this.dialog.open(DemoModalCmp, {
157 |         data,
158 |         panelClass: 'ds-dialog-panel',
159 |       });
160 |     }
161 |   }
162 | }
163 | 
```

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

```typescript
  1 | import { dsComponentCoveragePlugin } from '@push-based/ds-component-coverage';
  2 | import * as process from 'node:process';
  3 | import { validateDsComponentsArray } from '../../../../validation/ds-components-file-loader.validation.js';
  4 | import {
  5 |   ReportCoverageParams,
  6 |   BaseViolationResult,
  7 |   FormattedCoverageResult,
  8 |   BaseViolationAudit,
  9 | } from './types.js';
 10 | import { groupIssuesByFile } from './formatters.js';
 11 | import { resolveCrossPlatformPath } from '../utils/cross-platform-path.js';
 12 | 
 13 | /**
 14 |  * Validates the input parameters for the report coverage tool
 15 |  */
 16 | export function validateReportInput(params: ReportCoverageParams): void {
 17 |   if (!params.directory || typeof params.directory !== 'string') {
 18 |     throw new Error('Directory parameter is required and must be a string');
 19 |   }
 20 | 
 21 |   try {
 22 |     validateDsComponentsArray(params.dsComponents);
 23 |   } catch (ctx) {
 24 |     throw new Error(
 25 |       `Invalid dsComponents parameter: ${(ctx as Error).message}`,
 26 |     );
 27 |   }
 28 | }
 29 | 
 30 | /**
 31 |  * Executes the coverage plugin and returns the result
 32 |  */
 33 | export async function executeCoveragePlugin(
 34 |   params: ReportCoverageParams,
 35 | ): Promise<BaseViolationResult> {
 36 |   const pluginConfig = await dsComponentCoveragePlugin({
 37 |     dsComponents: params.dsComponents,
 38 |     directory: resolveCrossPlatformPath(
 39 |       params.cwd || process.cwd(),
 40 |       params.directory,
 41 |     ),
 42 |   });
 43 | 
 44 |   const { executePlugin } = await import('@code-pushup/core');
 45 |   return (await executePlugin(pluginConfig as any, {
 46 |     cache: { read: false, write: false },
 47 |     persist: { outputDir: '' },
 48 |   })) as BaseViolationResult;
 49 | }
 50 | 
 51 | /**
 52 |  * Extracts component name from audit title - performance optimized with caching
 53 |  */
 54 | const componentNameCache = new Map<string, string>();
 55 | 
 56 | export function extractComponentName(title: string): string {
 57 |   if (componentNameCache.has(title)) {
 58 |     return componentNameCache.get(title)!;
 59 |   }
 60 | 
 61 |   const componentMatch = title.match(/Usage coverage for (\w+) component/);
 62 |   const componentName = componentMatch ? componentMatch[1] : 'Unknown';
 63 | 
 64 |   componentNameCache.set(title, componentName);
 65 |   return componentName;
 66 | }
 67 | 
 68 | /**
 69 |  * Formats the coverage result as text output - performance optimized
 70 |  */
 71 | export function formatCoverageResult(
 72 |   result: BaseViolationResult,
 73 |   params: ReportCoverageParams,
 74 | ): string {
 75 |   // Pre-filter failed audits to avoid repeated filtering
 76 |   const failedAudits = result.audits.filter(
 77 |     ({ score }: BaseViolationAudit) => score < 1,
 78 |   );
 79 | 
 80 |   if (failedAudits.length === 0) {
 81 |     return '';
 82 |   }
 83 | 
 84 |   const output: string[] = [];
 85 |   output.push(''); // Pre-allocate with expected size for better performance
 86 | 
 87 |   for (const { details, title } of failedAudits) {
 88 |     const componentName = extractComponentName(title);
 89 | 
 90 |     output.push(`- design system component: ${componentName}`);
 91 |     output.push(`- base directory: ${params.directory}`);
 92 |     output.push('');
 93 | 
 94 |     // Use shared, optimized groupIssuesByFile function
 95 |     const fileGroups = groupIssuesByFile(
 96 |       details?.issues ?? [],
 97 |       params.directory,
 98 |     );
 99 | 
100 |     // Add first message
101 |     const firstFile = Object.keys(fileGroups)[0];
102 |     if (firstFile && fileGroups[firstFile]) {
103 |       output.push(fileGroups[firstFile].message);
104 |       output.push('');
105 |     }
106 | 
107 |     // Add files and lines - optimize sorting
108 |     for (const [fileName, { lines }] of Object.entries(fileGroups)) {
109 |       output.push(`- ${fileName}`);
110 |       // Sort once and cache the result
111 |       const sortedLines =
112 |         lines.length > 1 ? lines.sort((a, b) => a - b) : lines;
113 |       output.push(` - lines: ${sortedLines.join(',')}`);
114 |     }
115 | 
116 |     output.push('');
117 |   }
118 | 
119 |   return output.join('\n');
120 | }
121 | 
122 | /**
123 |  * Main implementation function for reporting project coverage
124 |  */
125 | export async function analyzeProjectCoverage(
126 |   params: ReportCoverageParams,
127 | ): Promise<FormattedCoverageResult> {
128 |   validateReportInput(params);
129 | 
130 |   if (params.cwd) {
131 |     process.chdir(params.cwd);
132 |   }
133 | 
134 |   const result = await executeCoveragePlugin(params);
135 | 
136 |   const textOutput = formatCoverageResult(result, params);
137 | 
138 |   const formattedResult: FormattedCoverageResult = {
139 |     textOutput,
140 |   };
141 | 
142 |   if (params.returnRawData) {
143 |     formattedResult.rawData = {
144 |       rawPluginResult: result,
145 |       pluginOptions: {
146 |         directory: params.directory,
147 |         dsComponents: params.dsComponents,
148 |       },
149 |     };
150 |   }
151 | 
152 |   return formattedResult;
153 | }
154 | 
```

--------------------------------------------------------------------------------
/tools/perf_hooks.patch.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { Performance, performance } from 'node:perf_hooks';
  2 | import { basename } from 'node:path';
  3 | 
  4 | // Global array to store complete events.
  5 | const trace = [];
  6 | 
  7 | // Metadata events.
  8 | 
  9 | const processMetadata = {
 10 |   name: 'process_name', // Used to label the main process
 11 |   ph: 'M',
 12 |   pid: 0,
 13 |   tid: process.pid,
 14 |   ts: 0,
 15 |   args: { name: 'Measure Process' },
 16 | };
 17 | 
 18 | const threadMetadata = {
 19 |   name: 'thread_name', // Used to label the child processes
 20 |   ph: 'M',
 21 |   pid: 0,
 22 |   tid: process.pid,
 23 |   ts: 0,
 24 |   args: {
 25 |     name: `Child Process: ${basename(process.argv.at(0))} ${basename(
 26 |       process.argv.at(1),
 27 |     )} ${process.argv.slice(2).join(' ')}`,
 28 |   },
 29 | };
 30 | 
 31 | const originalMark = Performance.prototype.mark;
 32 | const originalMeasure = Performance.prototype.measure;
 33 | 
 34 | let correlationIdCounter = 0;
 35 | function generateCorrelationId() {
 36 |   return ++correlationIdCounter;
 37 | }
 38 | 
 39 | /**
 40 |  * Parse an error stack into an array of frames.
 41 |  */
 42 | function parseStack(stack) {
 43 |   const frames = [];
 44 |   const lines = stack.split('\n').slice(2); // Skip error message & current function.
 45 |   for (const line of lines) {
 46 |     const trimmed = line.trim();
 47 |     const regex1 = /^at\s+(.*?)\s+\((.*):(\d+):(\d+)\)$/;
 48 |     const regex2 = /^at\s+(.*):(\d+):(\d+)$/;
 49 |     let match = trimmed.match(regex1);
 50 |     if (match) {
 51 |       frames.push({
 52 |         functionName: match[1],
 53 |         file: match[2].replace(process.cwd(), ''),
 54 |         line: Number(match[3]),
 55 |         column: Number(match[4]),
 56 |       });
 57 |     } else {
 58 |       match = trimmed.match(regex2);
 59 |       if (match) {
 60 |         frames.push({
 61 |           functionName: null,
 62 |           file: match[1],
 63 |           line: Number(match[2]),
 64 |           column: Number(match[3]),
 65 |         });
 66 |       } else {
 67 |         frames.push({ raw: trimmed });
 68 |       }
 69 |     }
 70 |   }
 71 |   return frames;
 72 | }
 73 | 
 74 | Performance.prototype.mark = function (name, options) {
 75 |   const err = new Error();
 76 |   const callStack = parseStack(err.stack);
 77 |   const opt = Object.assign({}, options, {
 78 |     detail: Object.assign({}, (options && options.detail) || {}, { callStack }),
 79 |   });
 80 |   return originalMark.call(this, name, opt);
 81 | };
 82 | 
 83 | // Override measure to create complete events.
 84 | Performance.prototype.measure = function (name, start, end, options) {
 85 |   const startEntry = performance.getEntriesByName(start, 'mark')[0];
 86 |   const endEntry = performance.getEntriesByName(end, 'mark')[0];
 87 |   let event = null;
 88 |   if (startEntry && endEntry) {
 89 |     const ts = startEntry.startTime * 1000; // Convert ms to microseconds.
 90 |     const dur = (endEntry.startTime - startEntry.startTime) * 1000;
 91 | 
 92 |     // Enrich event further if needed (here keeping it minimal to match your profile).
 93 |     event = {
 94 |       name,
 95 |       cat: 'measure', // Keeping the same category as in your uploaded trace.
 96 |       ph: 'X',
 97 |       ts,
 98 |       dur,
 99 |       pid: 0,
100 |       tid: process.pid,
101 |       args: {
102 |         startDetail: startEntry.detail || {},
103 |         endDetail: endEntry.detail || {},
104 |         // Optionally: add correlation and extra labels.
105 |         uiLabel: name,
106 |       },
107 |     };
108 | 
109 |     // Push metadata events once.
110 |     if (trace.length < 1) {
111 |       trace.push(threadMetadata);
112 |       console.log(`traceEvent:JSON:${JSON.stringify(threadMetadata)}`);
113 |       trace.push(processMetadata);
114 |       console.log(`traceEvent:JSON:${JSON.stringify(processMetadata)}`);
115 |     }
116 |     trace.push(event);
117 |     console.log(`traceEvent:JSON:${JSON.stringify(event)}`);
118 | 
119 |     // console.log('Measure Event:', JSON.stringify(event));
120 |   } else {
121 |     console.warn('Missing start or end mark for measure', name);
122 |   }
123 |   return originalMeasure.call(this, name, start, end, options);
124 | };
125 | 
126 | // Return the complete Chrome Trace profile object.
127 | performance.profile = function () {
128 |   return {
129 |     metadata: {
130 |       source: 'Nx Advanced Profiling',
131 |       startTime: Date.now() / 1000,
132 |       hardwareConcurrency: 12,
133 |       dataOrigin: 'TraceEvents',
134 |       modifications: {
135 |         entriesModifications: {
136 |           hiddenEntries: [],
137 |           expandableEntries: [],
138 |         },
139 |         initialBreadcrumb: {
140 |           window: {
141 |             min: 269106047711,
142 |             max: 269107913714,
143 |             range: 1866003,
144 |           },
145 |           child: null,
146 |         },
147 |         annotations: {
148 |           entryLabels: [],
149 |           labelledTimeRanges: [],
150 |           linksBetweenEntries: [],
151 |         },
152 |       },
153 |     },
154 |     traceEvents: trace,
155 |   };
156 | };
157 | performance.trace = trace;
158 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/css-match.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   parseClassNames,
  3 |   ngClassesIncludeClassName,
  4 | } from '@push-based/angular-ast-utils';
  5 | import type {
  6 |   DomElement,
  7 |   Attribute,
  8 |   Binding,
  9 | } from '../../shared/models/types.js';
 10 | 
 11 | /**
 12 |  * Check if a CSS selector matches a DOM element with improved accuracy
 13 |  */
 14 | export function selectorMatches(
 15 |   cssSelector: string,
 16 |   domKey: string,
 17 |   element: DomElement,
 18 | ): boolean {
 19 |   const normalizedSelector = cssSelector.trim();
 20 | 
 21 |   // Handle multiple selectors separated by commas
 22 |   if (normalizedSelector.includes(',')) {
 23 |     return normalizedSelector
 24 |       .split(',')
 25 |       .some((selector) => selectorMatches(selector, domKey, element));
 26 |   }
 27 | 
 28 |   // Handle descendant selectors (space-separated)
 29 |   if (normalizedSelector.includes(' ')) {
 30 |     const parts = normalizedSelector.split(/\s+/);
 31 |     const lastPart = parts[parts.length - 1];
 32 |     // Check if the element matches the last part - simplified check as full implementation
 33 |     // would need to traverse the DOM tree to check ancestors
 34 |     return matchSelector(lastPart, element);
 35 |   }
 36 | 
 37 |   // Handle single selector
 38 |   return matchSelector(normalizedSelector, element);
 39 | }
 40 | 
 41 | /**
 42 |  * Match a single CSS selector (class, id, tag, or attribute)
 43 |  */
 44 | function matchSelector(selector: string, element: DomElement): boolean {
 45 |   // Class selector (.class-name)
 46 |   if (selector.startsWith('.')) {
 47 |     return matchAttribute('class', selector.substring(1), '=', element);
 48 |   }
 49 | 
 50 |   // ID selector (#id-name)
 51 |   if (selector.startsWith('#')) {
 52 |     return matchAttribute('id', selector.substring(1), '=', element);
 53 |   }
 54 | 
 55 |   // Attribute selector ([attr], [attr=value], [attr*=value], etc.)
 56 |   if (selector.startsWith('[') && selector.endsWith(']')) {
 57 |     const content = selector.slice(1, -1);
 58 | 
 59 |     // Simple attribute existence check [attr]
 60 |     if (!content.includes('=')) {
 61 |       return (
 62 |         element.attributes?.some((attr: Attribute) => attr.name === content) ||
 63 |         false
 64 |       );
 65 |     }
 66 | 
 67 |     // Parse attribute selector with value using non-greedy split before the operator
 68 |     // This correctly captures operators like *=, ^=, $=
 69 |     const match = content.match(/^(.+?)([*^$]?=)(.+)$/);
 70 |     if (!match) return false;
 71 | 
 72 |     const [, attrNameRaw, operator, value] = match;
 73 |     const attrName = attrNameRaw.trim();
 74 |     return matchAttribute(attrName, stripQuotes(value), operator, element);
 75 |   }
 76 | 
 77 |   // Tag selector (div, span, etc.)
 78 |   return element.tag === selector;
 79 | }
 80 | 
 81 | /**
 82 |  * Unified attribute matching with support for classes, IDs, and general attributes
 83 |  */
 84 | function matchAttribute(
 85 |   attrName: string,
 86 |   expectedValue: string,
 87 |   operator: string,
 88 |   element: DomElement,
 89 | ): boolean {
 90 |   // Special handling for class attributes
 91 |   if (attrName === 'class') {
 92 |     // Check static class attribute
 93 |     const classAttr = element.attributes?.find(
 94 |       (attr: Attribute) => attr.name === 'class',
 95 |     );
 96 |     if (classAttr) {
 97 |       const classes = parseClassNames(classAttr.source);
 98 |       if (classes.includes(expectedValue)) {
 99 |         return true;
100 |       }
101 |     }
102 | 
103 |     // Check class bindings [class.foo]
104 |     const classBindings = element.bindings?.filter(
105 |       (binding: Binding) =>
106 |         binding.type === 'class' && binding.name === `class.${expectedValue}`,
107 |     );
108 | 
109 |     // Check ngClass bindings
110 |     const ngClassBindings = element.bindings?.filter(
111 |       (binding: Binding) => binding.name === 'ngClass',
112 |     );
113 | 
114 |     for (const binding of ngClassBindings || []) {
115 |       if (ngClassesIncludeClassName(binding.source, expectedValue)) {
116 |         return true;
117 |       }
118 |     }
119 | 
120 |     return classBindings && classBindings.length > 0;
121 |   }
122 | 
123 |   // General attribute matching
124 |   const attr = element.attributes?.find(
125 |     (attr: Attribute) => attr.name === attrName,
126 |   );
127 |   if (!attr) return false;
128 | 
129 |   const attrValue = attr.source;
130 |   return OPERATORS[operator]?.(attrValue, expectedValue) || false;
131 | }
132 | 
133 | // Operator lookup table
134 | const OPERATORS: Record<
135 |   string,
136 |   (attrValue: string, expectedValue: string) => boolean
137 | > = {
138 |   '=': (attrValue, expectedValue) => attrValue === expectedValue,
139 |   '*=': (attrValue, expectedValue) => attrValue.includes(expectedValue),
140 |   '^=': (attrValue, expectedValue) => attrValue.startsWith(expectedValue),
141 |   '$=': (attrValue, expectedValue) => attrValue.endsWith(expectedValue),
142 | };
143 | 
144 | /**
145 |  * Remove surrounding quotes from a string
146 |  */
147 | function stripQuotes(str: string): string {
148 |   return str.startsWith('"') || str.startsWith("'") ? str.slice(1, -1) : str;
149 | }
150 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/shared/spec/contract-file-ops.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /* eslint-disable prefer-const */
  2 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  3 | 
  4 | let readFileMock: any;
  5 | let mkdirMock: any;
  6 | let writeFileMock: any;
  7 | let existsSyncMock: any;
  8 | let resolveCrossPlatformPathMock: any;
  9 | 
 10 | vi.mock('node:fs/promises', () => ({
 11 |   get readFile() {
 12 |     return readFileMock;
 13 |   },
 14 |   get mkdir() {
 15 |     return mkdirMock;
 16 |   },
 17 |   get writeFile() {
 18 |     return writeFileMock;
 19 |   },
 20 | }));
 21 | 
 22 | vi.mock('node:fs', () => ({
 23 |   get existsSync() {
 24 |     return existsSyncMock;
 25 |   },
 26 | }));
 27 | 
 28 | vi.mock('node:crypto', () => ({
 29 |   createHash: () => ({
 30 |     update: () => ({
 31 |       digest: () => 'deadbeefdeadbeef',
 32 |     }),
 33 |   }),
 34 | }));
 35 | 
 36 | vi.mock('../../../shared/utils/cross-platform-path.js', () => ({
 37 |   get resolveCrossPlatformPath() {
 38 |     return resolveCrossPlatformPathMock;
 39 |   },
 40 | }));
 41 | 
 42 | import {
 43 |   loadContract,
 44 |   saveContract,
 45 |   generateContractSummary,
 46 |   generateDiffFileName,
 47 | } from '../utils/contract-file-ops.js';
 48 | 
 49 | import type { ComponentContract } from '../models/types.js';
 50 | 
 51 | function fixedDate(dateStr = '2024-02-10T12:00:00Z') {
 52 |   vi.useFakeTimers();
 53 |   vi.setSystemTime(new Date(dateStr));
 54 | }
 55 | 
 56 | function restoreTime() {
 57 |   vi.useRealTimers();
 58 | }
 59 | 
 60 | const minimalContract: ComponentContract = {
 61 |   meta: {
 62 |     name: 'FooComponent',
 63 |     selector: 'app-foo',
 64 |     sourceFile: '/src/app/foo.component.ts',
 65 |     templateType: 'external',
 66 |     generatedAt: new Date().toISOString(),
 67 |     hash: 'hash',
 68 |   },
 69 |   publicApi: {
 70 |     properties: { foo: { type: 'string', isInput: true, required: true } },
 71 |     events: { done: { type: 'void' } },
 72 |     methods: {
 73 |       do: {
 74 |         name: 'do',
 75 |         parameters: [],
 76 |         returnType: 'void',
 77 |         isPublic: true,
 78 |         isStatic: false,
 79 |         isAsync: false,
 80 |       },
 81 |     },
 82 |     lifecycle: ['ngOnInit'],
 83 |     imports: [],
 84 |   },
 85 |   slots: { default: { selector: 'ng-content' } },
 86 |   dom: {
 87 |     div: {
 88 |       tag: 'div',
 89 |       parent: null,
 90 |       children: [],
 91 |       bindings: [],
 92 |       attributes: [],
 93 |       events: [],
 94 |     },
 95 |   },
 96 |   styles: {
 97 |     sourceFile: '/src/app/foo.component.scss',
 98 |     rules: { div: { appliesTo: ['div'], properties: { color: 'red' } } },
 99 |   },
100 | };
101 | 
102 | describe('contract-file-ops', () => {
103 |   beforeEach(() => {
104 |     readFileMock = vi.fn();
105 |     mkdirMock = vi.fn();
106 |     writeFileMock = vi.fn();
107 |     existsSyncMock = vi.fn();
108 |     resolveCrossPlatformPathMock = vi.fn(
109 |       (_root: string, p: string) => `${_root}/${p}`,
110 |     );
111 |   });
112 | 
113 |   afterEach(() => {
114 |     restoreTime();
115 |   });
116 | 
117 |   describe('loadContract', () => {
118 |     it('loads wrapped contract files', async () => {
119 |       const filePath = '/tmp/contract.json';
120 | 
121 |       existsSyncMock.mockReturnValue(true);
122 |       readFileMock.mockResolvedValue(
123 |         JSON.stringify({ contract: minimalContract }),
124 |       );
125 | 
126 |       const contract = await loadContract(filePath);
127 | 
128 |       expect(readFileMock).toHaveBeenCalledWith(filePath, 'utf-8');
129 |       expect(contract).toEqual(minimalContract);
130 |     });
131 | 
132 |     it('throws when file is missing', async () => {
133 |       existsSyncMock.mockReturnValue(false);
134 | 
135 |       await expect(loadContract('/missing.json')).rejects.toThrow(
136 |         'Contract file not found',
137 |       );
138 |     });
139 |   });
140 | 
141 |   describe('saveContract', () => {
142 |     it('writes contract with metadata and returns path & hash', async () => {
143 |       fixedDate();
144 | 
145 |       const workspaceRoot = '/workspace';
146 |       const templatePath = 'src/app/foo.component.html';
147 |       const scssPath = 'src/app/foo.component.scss';
148 |       const cwd = '/cwd';
149 | 
150 |       writeFileMock.mockResolvedValue(undefined);
151 |       mkdirMock.mockResolvedValue(undefined);
152 | 
153 |       const { contractFilePath, hash } = await saveContract(
154 |         minimalContract,
155 |         workspaceRoot,
156 |         templatePath,
157 |         scssPath,
158 |         cwd,
159 |       );
160 | 
161 |       // mkdir called for .cursor/tmp directory
162 |       expect(mkdirMock).toHaveBeenCalled();
163 |       expect(contractFilePath).toMatch(/foo\.component.*\.contract\.json$/i);
164 |       expect(hash.startsWith('sha256-')).toBe(true);
165 |       expect(writeFileMock).toHaveBeenCalled();
166 |     });
167 |   });
168 | 
169 |   describe('generateContractSummary', () => {
170 |     it('generates human-readable summary lines', () => {
171 |       const lines = generateContractSummary(minimalContract);
172 |       expect(lines).toEqual(
173 |         expect.arrayContaining([
174 |           expect.stringMatching(/^🎯 DOM Elements: 1/),
175 |           expect.stringMatching(/^🎨 Style Rules: 1/),
176 |           expect.stringMatching(/^📥 Properties: 1/),
177 |           expect.stringMatching(/^📤 Events: 1/),
178 |         ]),
179 |       );
180 |     });
181 |   });
182 | 
183 |   describe('generateDiffFileName', () => {
184 |     it('creates timestamped diff filename', () => {
185 |       fixedDate();
186 |       const before = '/contracts/foo.contract.json';
187 |       const after = '/contracts/bar.contract.json';
188 |       const fname = generateDiffFileName(before, after);
189 |       expect(fname).toMatch(/^diff-foo-vs-bar-.*\.json$/);
190 |     });
191 |   });
192 | });
193 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/spec/public-api.extractor.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /* eslint-disable prefer-const */
  2 | import { describe, it, expect, beforeEach, vi } from 'vitest';
  3 | 
  4 | let createProgramMock: any;
  5 | vi.mock('typescript', () => {
  6 |   return {
  7 |     get createProgram() {
  8 |       return createProgramMock;
  9 |     },
 10 |     ScriptTarget: { Latest: 99 },
 11 |     ModuleKind: { ESNext: 99 },
 12 |   };
 13 | });
 14 | 
 15 | createProgramMock = vi.fn();
 16 | 
 17 | let extractClassDeclaration: any;
 18 | let extractPublicMethods: any;
 19 | let extractLifecycleHooks: any;
 20 | let extractImports: any;
 21 | let extractInputsAndOutputs: any;
 22 | 
 23 | extractClassDeclaration = vi.fn();
 24 | extractPublicMethods = vi.fn();
 25 | extractLifecycleHooks = vi.fn();
 26 | extractImports = vi.fn();
 27 | extractInputsAndOutputs = vi.fn();
 28 | 
 29 | vi.mock('../utils/typescript-analyzer.js', () => ({
 30 |   get extractClassDeclaration() {
 31 |     return extractClassDeclaration;
 32 |   },
 33 |   get extractPublicMethods() {
 34 |     return extractPublicMethods;
 35 |   },
 36 |   get extractLifecycleHooks() {
 37 |     return extractLifecycleHooks;
 38 |   },
 39 |   get extractImports() {
 40 |     return extractImports;
 41 |   },
 42 |   get extractInputsAndOutputs() {
 43 |     return extractInputsAndOutputs;
 44 |   },
 45 | }));
 46 | 
 47 | import { extractPublicApi } from '../utils/public-api.extractor.js';
 48 | 
 49 | type ParsedComponentStub = {
 50 |   fileName: string;
 51 |   inputs?: Record<string, any>;
 52 |   outputs?: Record<string, any>;
 53 | };
 54 | 
 55 | function makeParsedComponent(
 56 |   partial: Partial<ParsedComponentStub> = {},
 57 | ): ParsedComponentStub {
 58 |   return {
 59 |     fileName: '/comp.ts',
 60 |     ...partial,
 61 |   } as ParsedComponentStub;
 62 | }
 63 | 
 64 | function resetHelperMocks() {
 65 |   extractClassDeclaration.mockReset();
 66 |   extractPublicMethods.mockReset();
 67 |   extractLifecycleHooks.mockReset();
 68 |   extractImports.mockReset();
 69 |   extractInputsAndOutputs.mockReset();
 70 |   createProgramMock.mockReset();
 71 | }
 72 | 
 73 | describe('extractPublicApi', () => {
 74 |   beforeEach(() => {
 75 |     resetHelperMocks();
 76 |   });
 77 | 
 78 |   it('maps basic inputs and outputs when no class declaration', () => {
 79 |     const parsed = makeParsedComponent({
 80 |       inputs: { foo: { type: 'string', required: true } },
 81 |       outputs: { done: { type: 'void' } },
 82 |     });
 83 | 
 84 |     extractClassDeclaration.mockReturnValue(undefined);
 85 | 
 86 |     const api = extractPublicApi(parsed as any);
 87 | 
 88 |     expect(api.properties.foo).toEqual({
 89 |       type: 'string',
 90 |       isInput: true,
 91 |       required: true,
 92 |     });
 93 |     expect(api.events.done).toEqual({ type: 'void' });
 94 |     expect(api.methods).toEqual({});
 95 |     expect(api.lifecycle).toEqual([]);
 96 |     expect(api.imports).toEqual([]);
 97 |   });
 98 | 
 99 |   it('merges TypeScript analysis results', () => {
100 |     const parsed = makeParsedComponent();
101 | 
102 |     const classDeclStub = {};
103 |     extractClassDeclaration.mockReturnValue(classDeclStub);
104 | 
105 |     createProgramMock.mockReturnValue({
106 |       getSourceFile: () => ({}),
107 |     });
108 | 
109 |     extractInputsAndOutputs.mockReturnValue({
110 |       inputs: {
111 |         bar: { type: 'number', required: false, alias: 'baz' },
112 |       },
113 |       outputs: {
114 |         submitted: { type: 'CustomEvt', alias: 'submittedAlias' },
115 |       },
116 |     });
117 | 
118 |     extractPublicMethods.mockReturnValue({
119 |       doStuff: { parameters: [], returnType: 'void' },
120 |     });
121 | 
122 |     extractLifecycleHooks.mockReturnValue(['ngOnInit']);
123 |     extractImports.mockReturnValue([
124 |       { name: 'HttpClient', path: '@angular/common/http' },
125 |     ]);
126 | 
127 |     const api = extractPublicApi(parsed as any);
128 | 
129 |     expect(api.properties.bar).toEqual(
130 |       expect.objectContaining({
131 |         type: 'number',
132 |         isInput: true,
133 |         alias: 'baz',
134 |       }),
135 |     );
136 | 
137 |     expect(api.events.submitted).toEqual(
138 |       expect.objectContaining({ type: 'CustomEvt', alias: 'submittedAlias' }),
139 |     );
140 | 
141 |     expect(api.methods).toHaveProperty('doStuff');
142 |     expect(api.lifecycle).toEqual(['ngOnInit']);
143 |     expect(api.imports[0]).toEqual({
144 |       name: 'HttpClient',
145 |       path: '@angular/common/http',
146 |     });
147 |   });
148 | 
149 |   it('coerces booleanAttribute transform to boolean type', () => {
150 |     const parsed = makeParsedComponent();
151 |     extractClassDeclaration.mockReturnValue({});
152 |     createProgramMock.mockReturnValue({ getSourceFile: () => ({}) });
153 |     extractInputsAndOutputs.mockReturnValue({
154 |       inputs: {
155 |         flag: { type: 'any', transform: 'booleanAttribute' },
156 |       },
157 |       outputs: {},
158 |     });
159 | 
160 |     const api = extractPublicApi(parsed as any);
161 |     expect(api.properties.flag.type).toBe('boolean');
162 |   });
163 | 
164 |   it('handles signal input with defaultValue and transform', () => {
165 |     const parsed = makeParsedComponent();
166 |     extractClassDeclaration.mockReturnValue({});
167 |     createProgramMock.mockReturnValue({ getSourceFile: () => ({}) });
168 |     extractInputsAndOutputs.mockReturnValue({
169 |       inputs: {
170 |         count: { type: 'number', defaultValue: 0, transform: 'identity' },
171 |       },
172 |       outputs: {},
173 |     });
174 | 
175 |     const api = extractPublicApi(parsed as any);
176 | 
177 |     const countProp = api.properties.count as any;
178 |     expect(countProp.isInput).toBe(true);
179 |     expect(countProp.defaultValue).toBe(0);
180 |     expect(countProp.transform).toBe('identity');
181 |     expect(countProp).not.toHaveProperty('alias');
182 |   });
183 | });
184 | 
```

--------------------------------------------------------------------------------
/docs/architecture-internal-design.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Angular MCP Server – Architecture & Internal Design
  2 | 
  3 | > **Audience:** Backend & tool authors who need to understand how the MCP server is wired together so they can extend it with new tools, prompts, or transports.
  4 | 
  5 | ---
  6 | 
  7 | ## 1. High-level Overview
  8 | 
  9 | The Angular MCP Server is a **Node.js** process that wraps the generic **[Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk)** and exposes Angular-specific analysis & refactoring tools.  
 10 | It follows a clean, layered design:
 11 | 
 12 | 1. **Transport & Core (MCP SDK)** – HTTP/SSE transport, request routing, JSON-schema validation.  
 13 | 2. **Server Wrapper (`AngularMcpServerWrapper`)** – registers prompts, tools, and resources; injects workspace-specific paths.  
 14 | 3. **Tools Layer (`src/lib/tools/**`)** – thin adapters delegating to shared analysis libraries.  
 15 | 4. **Shared Libraries (`packages/shared/**`)** – AST utilities, DS coverage, generic helpers.
 16 | 
 17 | ---
 18 | 
 19 | ## 2. Runtime Flow
 20 | 
 21 | ```mermaid
 22 | graph TD
 23 |     subgraph Editor / LLM Client
 24 |       A[CallTool / ListTools] -->|HTTP + SSE| B(McpServer Core)
 25 |     end
 26 | 
 27 |     B --> C{Request Router}
 28 |     C -->|tools| D[Tools Registry]
 29 |     C -->|prompts| E[Prompts Registry]
 30 |     C -->|resources| F[Resources Provider]
 31 | 
 32 |     D --> G[Individual Tool Handler]
 33 |     G --> H[Shared Libs & Workspace FS]
 34 | ```
 35 | 
 36 | 1. The client sends a **`CallTool`** request.  
 37 | 2. `McpServer` validates the request against JSON Schema.  
 38 | 3. `AngularMcpServerWrapper` routes it to the correct handler inside **Tools Registry**.  
 39 | 4. The handler performs analysis (often via shared libs) and returns structured content.  
 40 | 5. The response streams back.
 41 | 
 42 | ---
 43 | 
 44 | ## 3. Bootstrap Sequence
 45 | 
 46 | 1. **CLI Invocation** (see `.cursor/mcp.json`):
 47 |    ```bash
 48 |    node packages/angular-mcp/dist/main.js --workspaceRoot=/abs/path ...
 49 |    ```
 50 | 2. `main.ts` → `AngularMcpServerWrapper.create()`
 51 | 3. **Config Validation** (`AngularMcpServerOptionsSchema`) – checks absolute/relative paths.
 52 | 4. **File Existence Validation** – ensures Storybook docs & DS mapping files are present.
 53 | 5. **Server Setup** – registers capabilities & calls:
 54 |    - `registerPrompts()`
 55 |    - `registerTools()`
 56 |    - `registerResources()`
 57 | 6. Server starts listening.
 58 | 
 59 | ---
 60 | 
 61 | ## 4. Directory Layout (server package)
 62 | 
 63 | ```
 64 | packages/angular-mcp-server/
 65 |   src/
 66 |     lib/
 67 |       angular-mcp-server.ts   # Wrapper class (core of the server)
 68 |       tools/
 69 |         ds/                  # DS-specific tool categories
 70 |         shared/              # Internal helpers
 71 |       prompts/               # Prompt schemas & impls
 72 |       validation/            # Zod schemas & file checks
 73 |     index.ts                 # Re-export of wrapper
 74 | ```
 75 | 
 76 | ---
 77 | 
 78 | ## 5. Tools & Extension Points
 79 | 
 80 | | Extension | Where to Add | Boilerplate |
 81 | |-----------|-------------|-------------|
 82 | | **Tool** | `src/lib/tools/**` | 1. Create `my-awesome.tool.ts` exporting `ToolsConfig[]`.  <br>2. Add it to the export list in `tools/ds/tools.ts` (or another category). |
 83 | | **Prompt** | `prompts/prompt-registry.ts` | 1. Append schema to `PROMPTS`. <br>2. Provide implementation in `PROMPTS_IMPL`. |
 84 | | **Resource Provider** | `registerResources()` | Extend logic to aggregate custom docs or design-system assets. |
 85 | 
 86 | All tools share the `ToolsConfig` interface (`@push-based/models`) that bundles:
 87 | - `schema` (name, description, arguments, return type)
 88 | - `handler(request)` async function.
 89 | 
 90 | The MCP SDK auto-validates every call against the schema – no manual parsing required.
 91 | 
 92 | ---
 93 | 
 94 | ## 6. Configuration Options
 95 | 
 96 | | Option | Type | Description |
 97 | |--------|------|-------------|
 98 | | `workspaceRoot` | absolute path | Root of the Nx/Angular workspace. |
 99 | | `ds.storybookDocsRoot` | relative path | Path (from root) to Storybook MDX/Docs for DS components. |
100 | | `ds.deprecatedCssClassesPath` | relative path | JS file mapping components → deprecated CSS classes. |
101 | | `ds.uiRoot` | relative path | Folder containing raw design-system component source. |
102 | 
103 | Validation is handled via **Zod** in `angular-mcp-server-options.schema.ts`.
104 | 
105 | ---
106 | 
107 | ## 7. Shared Libraries in Play
108 | 
109 | ```
110 | models  (types & schemas)
111 | ├─ utils
112 | ├─ styles-ast-utils
113 | └─ angular-ast-utils
114 |     └─ ds-component-coverage  (top-level plugin)
115 | ```
116 | 
117 | These libraries provide AST parsing, file operations, and DS analysis. Tools import them directly; they are **framework-agnostic** and can be unit-tested in isolation.
118 | 
119 | ---
120 | 
121 | ## 8. Testing & Examples
122 | 
123 | `packages/minimal-repo/**` contains miniature Angular apps used by unit/integration tests. They are **not** part of production code but useful when debugging a new tool.
124 | 
125 | ---
126 | 
127 | ## 9. Adding a New Tool – Checklist
128 | 
129 | 1. Identify functionality and pick/create an appropriate shared library function.  
130 | 2. Generate a JSON-schema with arguments & result shape (can use Zod helper).  
131 | 3. Implement handler logic (avoid heavy FS operations in main thread; prefer async).  
132 | 4. Export via `ToolsConfig[]` and append to category list.  
133 | 5. Write unit tests.
134 | 6. Update `docs/tools.md` once published.
135 | 
136 | ---
```

--------------------------------------------------------------------------------
/packages/shared/DEPENDENCIES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Shared Libraries Dependencies
  2 | 
  3 | This document provides an AI-friendly overview of the shared libraries in the `/packages/shared` directory, their purposes, and cross-dependencies.
  4 | 
  5 | ## Library Overview
  6 | 
  7 | ### Foundation Layer (No Internal Dependencies)
  8 | 
  9 | #### `@push-based/models`
 10 | 
 11 | - **Purpose**: Core types and interfaces for CLI and MCP tooling
 12 | - **Key Exports**: CliArgsObject, ArgumentValue, ToolSchemaOptions, ToolsConfig, ToolHandlerContentResult, DiagnosticsAware
 13 | - **Dependencies**: None (foundation library)
 14 | - **Used By**: All other shared libraries
 15 | 
 16 | #### `@push-based/typescript-ast-utils`
 17 | 
 18 | - **Purpose**: TypeScript AST parsing and manipulation utilities
 19 | - **Key Exports**: isComponentDecorator, removeQuotes, getDecorators, isDecorator
 20 | - **Dependencies**: None (foundation library)
 21 | - **Used By**: angular-ast-utils
 22 | 
 23 | ### Intermediate Layer (Single Foundation Dependency)
 24 | 
 25 | #### `@push-based/utils`
 26 | 
 27 | - **Purpose**: General utility functions and file system operations
 28 | - **Key Exports**: findFilesWithPattern, resolveFile
 29 | - **Dependencies**: models
 30 | - **Used By**: angular-ast-utils, ds-component-coverage
 31 | 
 32 | #### `@push-based/styles-ast-utils`
 33 | 
 34 | - **Purpose**: CSS/SCSS AST parsing and manipulation utilities
 35 | - **Key Exports**: parseStylesheet, CssAstVisitor, visitStyleSheet, styleAstRuleToSource
 36 | - **Dependencies**: models
 37 | - **Used By**: angular-ast-utils, ds-component-coverage
 38 | 
 39 | ### Advanced Layer (Multiple Dependencies)
 40 | 
 41 | #### `@push-based/angular-ast-utils`
 42 | 
 43 | - **Purpose**: Angular component parsing and template/style analysis
 44 | - **Key Exports**: parseComponents, visitComponentTemplate, visitComponentStyles, findAngularUnits
 45 | - **Dependencies**:
 46 |   - models (types and schemas)
 47 |   - utils (file operations)
 48 |   - typescript-ast-utils (TS AST utilities)
 49 |   - styles-ast-utils (CSS AST utilities)
 50 | - **Used By**: ds-component-coverage
 51 | 
 52 | #### `@push-based/ds-component-coverage`
 53 | 
 54 | - **Purpose**: Design System component usage analysis and coverage reporting
 55 | - **Key Exports**: dsComponentCoveragePlugin, runnerFunction, getAngularDsUsageCategoryRefs
 56 | - **Dependencies**:
 57 |   - models (audit types)
 58 |   - utils (utilities)
 59 |   - styles-ast-utils (CSS analysis)
 60 |   - angular-ast-utils (component parsing)
 61 | - **Used By**: None (top-level plugin)
 62 | 
 63 | ## Dependency Graph
 64 | 
 65 | ```
 66 | @code-pushup/models (foundation)
 67 | ├── @code-pushup/utils
 68 | ├── styles-ast-utils
 69 | └── angular-ast-utils
 70 |     ├── @code-pushup/models
 71 |     ├── @code-pushup/utils
 72 |     ├── typescript-ast-utils
 73 |     └── styles-ast-utils
 74 | 
 75 | ds-component-coverage (most complex)
 76 | ├── @code-pushup/models
 77 | ├── @code-pushup/utils
 78 | ├── styles-ast-utils
 79 | └── angular-ast-utils
 80 | ```
 81 | 
 82 | ## Build Order
 83 | 
 84 | Based on dependencies, the correct build order is:
 85 | 
 86 | 1. **Foundation**: `models`, `typescript-ast-utils`
 87 | 2. **Intermediate**: `utils`, `styles-ast-utils`
 88 | 3. **Advanced**: `angular-ast-utils`
 89 | 4. **Top-level**: `ds-component-coverage`
 90 | 
 91 | ## Key Patterns
 92 | 
 93 | ### Dependency Injection Pattern
 94 | 
 95 | - Libraries accept dependencies through imports rather than direct instantiation
 96 | - Enables testing and modularity
 97 | 
 98 | ### Layered Architecture
 99 | 
100 | - Clear separation between foundation, intermediate, and advanced layers
101 | - Each layer builds upon the previous one
102 | 
103 | ### Single Responsibility
104 | 
105 | - Each library has a focused purpose
106 | - Cross-cutting concerns are handled by foundation libraries
107 | 
108 | ### No Circular Dependencies
109 | 
110 | - Clean acyclic dependency graph
111 | - Ensures predictable build order and runtime behavior
112 | 
113 | ## Usage Guidelines for AI
114 | 
115 | ### When to Use Each Library
116 | 
117 | - **models**: When you need CLI argument types or MCP tooling interfaces
118 | - **utils**: For file operations, string manipulation, or general utilities
119 | - **typescript-ast-utils**: For TypeScript code analysis and manipulation
120 | - **styles-ast-utils**: For CSS/SCSS parsing and analysis
121 | - **angular-ast-utils**: For Angular component analysis and template/style processing
122 | - **ds-component-coverage**: For Design System migration analysis and reporting
123 | 
124 | ### Common Import Patterns
125 | 
126 | ```typescript
127 | // Foundation types
128 | import { CliArgsObject, ToolSchemaOptions, DiagnosticsAware } from '@push-based/models';
129 | 
130 | // File operations
131 | import { resolveFile, findFilesWithPattern } from '@code-pushup/utils';
132 | 
133 | // Angular component parsing
134 | import {
135 |   parseComponents,
136 |   visitComponentTemplate,
137 | } from '@push-based/angular-ast-utils';
138 | 
139 | // CSS analysis
140 | import { parseStylesheet, visitStyleSheet } from '@push-based/styles-ast-utils';
141 | 
142 | // TypeScript utilities
143 | import {
144 |   isComponentDecorator,
145 |   removeQuotes,
146 | } from '@push-based/typescript-ast-utils';
147 | ```
148 | 
149 | ### Integration Points
150 | 
151 | - All libraries use `models` for CLI and MCP tooling type definitions
152 | - File operations flow through `utils`
153 | - AST operations are specialized by language (TS, CSS, Angular)
154 | - Complex analysis combines multiple AST utilities through `angular-ast-utils`
155 | 
156 | ## Maintenance Notes
157 | 
158 | - Changes to `models` affect all other libraries
159 | - `angular-ast-utils` is the most integration-heavy library
160 | - `ds-component-coverage` represents the full stack integration
161 | - Foundation libraries should remain stable and focused
162 | - New features should follow the established layering pattern
163 | 
```

--------------------------------------------------------------------------------
/packages/shared/utils/src/lib/execute-process.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   type ChildProcess,
  3 |   type ChildProcessByStdio,
  4 |   type SpawnOptionsWithStdioTuple,
  5 |   type StdioPipe,
  6 |   spawn,
  7 | } from 'node:child_process';
  8 | import type { Readable, Writable } from 'node:stream';
  9 | import { formatCommandLog } from './format-command-log.js';
 10 | import { isVerbose } from './logging.js';
 11 | import { calcDuration } from './utils.js';
 12 | 
 13 | /**
 14 |  * Represents the process result.
 15 |  * @category Types
 16 |  * @public
 17 |  * @property {string} stdout - The stdout of the process.
 18 |  * @property {string} stderr - The stderr of the process.
 19 |  * @property {number | null} code - The exit code of the process.
 20 |  */
 21 | export type ProcessResult = {
 22 |   stdout: string;
 23 |   stderr: string;
 24 |   code: number | null;
 25 |   date: string;
 26 |   duration: number;
 27 | };
 28 | 
 29 | /**
 30 |  * Error class for process errors.
 31 |  * Contains additional information about the process result.
 32 |  * @category Error
 33 |  * @public
 34 |  * @class
 35 |  * @extends Error
 36 |  * @example
 37 |  * const result = await executeProcess({})
 38 |  * .catch((error) => {
 39 |  *   if (error instanceof ProcessError) {
 40 |  *   console.error(error.code);
 41 |  *   console.error(error.stderr);
 42 |  *   console.error(error.stdout);
 43 |  *   }
 44 |  * });
 45 |  *
 46 |  */
 47 | export class ProcessError extends Error {
 48 |   code: number | null;
 49 |   stderr: string;
 50 |   stdout: string;
 51 | 
 52 |   constructor(result: ProcessResult) {
 53 |     super(result.stderr);
 54 |     this.code = result.code;
 55 |     this.stderr = result.stderr;
 56 |     this.stdout = result.stdout;
 57 |   }
 58 | }
 59 | 
 60 | /**
 61 |  * Process config object. Contains the command, args and observer.
 62 |  * @param cfg - process config object with command, args and observer (optional)
 63 |  * @category Types
 64 |  * @public
 65 |  * @property {string} command - The command to execute.
 66 |  * @property {string[]} args - The arguments for the command.
 67 |  * @property {ProcessObserver} observer - The observer for the process.
 68 |  *
 69 |  * @example
 70 |  *
 71 |  * // bash command
 72 |  * const cfg = {
 73 |  *   command: 'bash',
 74 |  *   args: ['-c', 'echo "hello world"']
 75 |  * };
 76 |  *
 77 |  * // node command
 78 |  * const cfg = {
 79 |  * command: 'node',
 80 |  * args: ['--version']
 81 |  * };
 82 |  *
 83 |  * // npx command
 84 |  * const cfg = {
 85 |  * command: 'npx',
 86 |  * args: ['--version']
 87 |  *
 88 |  */
 89 | export type ProcessConfig = Omit<
 90 |   SpawnOptionsWithStdioTuple<StdioPipe, StdioPipe, StdioPipe>,
 91 |   'stdio'
 92 | > & {
 93 |   command: string;
 94 |   args?: string[];
 95 |   observer?: ProcessObserver;
 96 |   ignoreExitCode?: boolean;
 97 | };
 98 | 
 99 | /**
100 |  * Process observer object. Contains the onStdout, error and complete function.
101 |  * @category Types
102 |  * @public
103 |  * @property {function} onStdout - The onStdout function of the observer (optional).
104 |  * @property {function} onError - The error function of the observer (optional).
105 |  * @property {function} onComplete - The complete function of the observer (optional).
106 |  *
107 |  * @example
108 |  * const observer = {
109 |  *  onStdout: (stdout) => console.info(stdout)
110 |  *  }
111 |  */
112 | export type ProcessObserver = {
113 |   onStdout?: (stdout: string, sourceProcess?: ChildProcess) => void;
114 |   onStderr?: (stderr: string, sourceProcess?: ChildProcess) => void;
115 |   onError?: (error: ProcessError) => void;
116 |   onComplete?: () => void;
117 | };
118 | 
119 | /**
120 |  * Executes a process and returns a promise with the result as `ProcessResult`.
121 |  *
122 |  * @example
123 |  *
124 |  * // sync process execution
125 |  * const result = await executeProcess({
126 |  *  command: 'node',
127 |  *  args: ['--version']
128 |  * });
129 |  *
130 |  * console.info(result);
131 |  *
132 |  * // async process execution
133 |  * const result = await executeProcess({
134 |  *    command: 'node',
135 |  *    args: ['download-data.js'],
136 |  *    observer: {
137 |  *      onStdout: updateProgress,
138 |  *      error: handleError,
139 |  *      complete: cleanLogs,
140 |  *    }
141 |  * });
142 |  *
143 |  * console.info(result);
144 |  *
145 |  * @param cfg - see {@link ProcessConfig}
146 |  */
147 | export function executeProcess(cfg: ProcessConfig): Promise<ProcessResult> {
148 |   const { command, args, observer, ignoreExitCode = false, ...options } = cfg;
149 |   const { onStdout, onStderr, onError, onComplete } = observer ?? {};
150 |   const date = new Date().toISOString();
151 |   const start = performance.now();
152 | 
153 |   if (isVerbose()) {
154 |     console.log(formatCommandLog(command, args, `${cfg.cwd ?? process.cwd()}`));
155 |   }
156 | 
157 |   return new Promise((resolve, reject) => {
158 |     // shell:true tells Windows to use shell command for spawning a child process
159 |     const spawnedProcess = spawn(command, args ?? [], {
160 |       shell: true,
161 |       windowsHide: true,
162 |       ...options,
163 |     }) as ChildProcessByStdio<Writable, Readable, Readable>;
164 | 
165 |     let stdout = '';
166 |     let stderr = '';
167 | 
168 |     spawnedProcess.stdout.on('data', (data) => {
169 |       stdout += String(data);
170 |       onStdout?.(String(data), spawnedProcess);
171 |     });
172 | 
173 |     spawnedProcess.stderr.on('data', (data) => {
174 |       stderr += String(data);
175 |       onStderr?.(String(data), spawnedProcess);
176 |     });
177 | 
178 |     spawnedProcess.on('error', (err) => {
179 |       stderr += err.toString();
180 |     });
181 | 
182 |     spawnedProcess.on('close', (code) => {
183 |       const timings = { date, duration: calcDuration(start) };
184 |       if (code === 0 || ignoreExitCode) {
185 |         onComplete?.();
186 |         resolve({ code, stdout, stderr, ...timings });
187 |       } else {
188 |         const errorMsg = new ProcessError({ code, stdout, stderr, ...timings });
189 |         onError?.(errorMsg);
190 |         reject(errorMsg);
191 |       }
192 |     });
193 |   });
194 | }
195 | 
```

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

```typescript
  1 | import { describe, it, expect, vi } from 'vitest';
  2 | 
  3 | // -----------------------------------------------------------------------------
  4 | // Mocking angular-ast-utils functions relied upon by extractSlotsAndDom
  5 | // -----------------------------------------------------------------------------
  6 | 
  7 | // Helper used by extractSlotsAndDom to traverse template
  8 | function visitEachTmplChild(nodes: any[], visitor: any): void {
  9 |   nodes.forEach((node) => {
 10 |     if (typeof node.visit === 'function') {
 11 |       node.visit(visitor);
 12 |     }
 13 |   });
 14 | }
 15 | 
 16 | // Mock implementation of visitComponentTemplate – supplies template nodes
 17 | async function visitComponentTemplate(
 18 |   parsedComponent: any,
 19 |   _options: any,
 20 |   cb: any,
 21 | ) {
 22 |   const templateAsset = {
 23 |     parse: async () => ({ nodes: parsedComponent.templateNodes }),
 24 |   };
 25 |   return cb(parsedComponent, templateAsset);
 26 | }
 27 | 
 28 | /* eslint-disable prefer-const */
 29 | 
 30 | vi.mock('@push-based/angular-ast-utils', () => {
 31 |   // Define a lightweight NoopTmplVisitor inside the mock factory to avoid
 32 |   // reference-before-initialization issues caused by hoisting.
 33 |   class NoopTmplVisitor {}
 34 | 
 35 |   return {
 36 |     visitComponentTemplate,
 37 |     visitEachTmplChild,
 38 |     NoopTmplVisitor,
 39 |     parseClassNames: (classStr: string) => classStr.trim().split(/\s+/),
 40 |   };
 41 | });
 42 | 
 43 | // -----------------------------------------------------------------------------
 44 | // Imports (after mocks)
 45 | // -----------------------------------------------------------------------------
 46 | import { extractSlotsAndDom } from '../utils/dom-slots.extractor.js';
 47 | 
 48 | // -----------------------------------------------------------------------------
 49 | // Minimal AST node builders
 50 | // -----------------------------------------------------------------------------
 51 | 
 52 | type AttributeNode = { name: string; value: string };
 53 | type InputNode = { name: string; value?: any };
 54 | type OutputNode = { name: string; handler: { source: string } };
 55 | 
 56 | type ElementNode = {
 57 |   name: string;
 58 |   attributes: AttributeNode[];
 59 |   inputs: InputNode[];
 60 |   outputs: OutputNode[];
 61 |   children: any[];
 62 |   visit: (v: any) => void;
 63 | };
 64 | 
 65 | function createElement(
 66 |   params: Partial<ElementNode> & { name: string },
 67 | ): ElementNode {
 68 |   const node: ElementNode = {
 69 |     name: params.name,
 70 |     attributes: params.attributes ?? [],
 71 |     inputs: params.inputs ?? [],
 72 |     outputs: params.outputs ?? [],
 73 |     children: params.children ?? [],
 74 |     visit(visitor: any) {
 75 |       // Call the appropriate visitor method
 76 |       if (typeof visitor.visitElement === 'function') {
 77 |         visitor.visitElement(this as any);
 78 |       }
 79 |     },
 80 |   } as ElementNode;
 81 |   return node;
 82 | }
 83 | 
 84 | function createContent(selector: string | undefined): any {
 85 |   return {
 86 |     selector,
 87 |     visit(visitor: any) {
 88 |       if (typeof visitor.visitContent === 'function') {
 89 |         visitor.visitContent(this);
 90 |       }
 91 |     },
 92 |   };
 93 | }
 94 | 
 95 | function createForLoopBlock(
 96 |   children: any[],
 97 |   expressionSrc = 'item of items',
 98 |   alias = 'item',
 99 | ): any {
100 |   return {
101 |     children,
102 |     expression: { source: expressionSrc },
103 |     item: { name: alias },
104 |     visit(visitor: any) {
105 |       if (typeof visitor.visitForLoopBlock === 'function') {
106 |         visitor.visitForLoopBlock(this);
107 |       }
108 |     },
109 |   };
110 | }
111 | 
112 | // -----------------------------------------------------------------------------
113 | // Test suites
114 | // -----------------------------------------------------------------------------
115 | 
116 | describe('extractSlotsAndDom', () => {
117 |   it('extracts default and named slots', async () => {
118 |     const defaultSlot = createContent(undefined);
119 |     const namedSlot = createContent('[slot=header]');
120 | 
121 |     const parsedComponent = {
122 |       templateNodes: [defaultSlot, namedSlot],
123 |     };
124 | 
125 |     const { slots } = await extractSlotsAndDom(parsedComponent as any);
126 | 
127 |     expect(slots).toHaveProperty('default');
128 |     expect(slots.default.selector).toBe('ng-content');
129 | 
130 |     expect(slots).toHaveProperty('header');
131 |     expect(slots.header.selector).toBe('ng-content[select="[slot=header]"]');
132 |   });
133 | 
134 |   it('builds DOM structure with parent-child links', async () => {
135 |     const span = createElement({
136 |       name: 'span',
137 |       attributes: [{ name: 'class', value: 'foo' }],
138 |     });
139 | 
140 |     const div = createElement({
141 |       name: 'div',
142 |       attributes: [{ name: 'id', value: 'root' }],
143 |       children: [span],
144 |     });
145 | 
146 |     const parsedComponent = { templateNodes: [div] };
147 | 
148 |     const { dom } = await extractSlotsAndDom(parsedComponent as any);
149 | 
150 |     const parentKey = 'div#root';
151 |     const childKey = 'div#root > span.foo';
152 | 
153 |     expect(Object.keys(dom)).toEqual([parentKey, childKey]);
154 | 
155 |     const parent = dom[parentKey] as any;
156 |     const child = dom[childKey] as any;
157 | 
158 |     expect(parent.children).toEqual([childKey]);
159 |     expect(child.parent).toBe(parentKey);
160 |   });
161 | 
162 |   it('captures structural directive context (for loop)', async () => {
163 |     const li = createElement({ name: 'li' });
164 |     const forBlock = createForLoopBlock([li]);
165 | 
166 |     const parsedComponent = { templateNodes: [forBlock] };
167 | 
168 |     const { dom } = await extractSlotsAndDom(parsedComponent as any);
169 | 
170 |     const liNode = dom['li'] as any;
171 |     expect(liNode).toBeTruthy();
172 |     expect(liNode.structural?.[0]).toEqual(
173 |       expect.objectContaining({ kind: 'for', alias: 'item' }),
174 |     );
175 |   });
176 | });
177 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/utils/diff-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { Difference } from 'microdiff';
  2 | import type { DomPathDictionary } from '../../shared/models/types.js';
  3 | import { createDomPathDictionary, processDomPaths } from './dom-path-utils.js';
  4 | 
  5 | /**
  6 |  * Enhanced version of consolidateAndPruneRemoveOperations with DOM path deduplication
  7 |  */
  8 | export function consolidateAndPruneRemoveOperationsWithDeduplication(
  9 |   diffResult: Difference[],
 10 | ): {
 11 |   processedResult: Difference[];
 12 |   domPathDict: DomPathDictionary;
 13 | } {
 14 |   const consolidatedResult = consolidateAndPruneRemoveOperations(diffResult);
 15 | 
 16 |   const domPathDict = createDomPathDictionary(),
 17 |     processedResult = consolidatedResult.map((c) =>
 18 |       processDomPaths(c, domPathDict),
 19 |     );
 20 | 
 21 |   return {
 22 |     processedResult,
 23 |     domPathDict,
 24 |   };
 25 | }
 26 | 
 27 | /**
 28 |  * Consolidates REMOVE operations for CSS rules in styles section and
 29 |  * prunes redundant child REMOVE operations from a microdiff result.
 30 |  */
 31 | export function consolidateAndPruneRemoveOperations(
 32 |   diffResult: Difference[],
 33 | ): Difference[] {
 34 |   const { removeOperations, nonRemoveOperations } = diffResult.reduce(
 35 |     (acc, change) => {
 36 |       (change.type === 'REMOVE'
 37 |         ? acc.removeOperations
 38 |         : acc.nonRemoveOperations
 39 |       ).push(change);
 40 |       return acc;
 41 |     },
 42 |     {
 43 |       removeOperations: [] as Difference[],
 44 |       nonRemoveOperations: [] as Difference[],
 45 |     },
 46 |   );
 47 | 
 48 |   if (removeOperations.length === 0) {
 49 |     return diffResult;
 50 |   }
 51 | 
 52 |   const cssRuleRemoves = new Map<string, Difference[]>();
 53 |   const otherRemoves: Difference[] = [];
 54 | 
 55 |   const isCssRuleRemove = (d: Difference) =>
 56 |     d.type === 'REMOVE' &&
 57 |     d.path.length === 3 &&
 58 |     d.path[0] === 'styles' &&
 59 |     d.path[1] === 'rules' &&
 60 |     typeof d.path[2] === 'string';
 61 | 
 62 |   for (const remove of removeOperations) {
 63 |     if (isCssRuleRemove(remove)) {
 64 |       const key = 'styles.rules';
 65 |       const group = cssRuleRemoves.get(key) ?? [];
 66 |       group.push(remove);
 67 |       cssRuleRemoves.set(key, group);
 68 |     } else {
 69 |       otherRemoves.push(remove);
 70 |     }
 71 |   }
 72 | 
 73 |   const consolidatedCssRuleChanges: Difference[] = [
 74 |     ...[...cssRuleRemoves.values()].flatMap((removes) =>
 75 |       removes.length > 1
 76 |         ? [
 77 |             {
 78 |               type: 'REMOVE',
 79 |               path: ['styles', 'rules'],
 80 |               oldValue: removes.map((r) => r.path[2]),
 81 |             } as Difference,
 82 |           ]
 83 |         : removes,
 84 |     ),
 85 |   ];
 86 | 
 87 |   otherRemoves.sort((a, b) => a.path.length - b.path.length);
 88 | 
 89 |   const prunedOtherRemoves: Difference[] = [];
 90 | 
 91 |   for (const currentRemove of otherRemoves) {
 92 |     let isRedundant = false;
 93 | 
 94 |     for (const existingRemove of prunedOtherRemoves) {
 95 |       if (isChildPath(currentRemove.path, existingRemove.path)) {
 96 |         isRedundant = true;
 97 |         break;
 98 |       }
 99 |     }
100 | 
101 |     if (!isRedundant) {
102 |       prunedOtherRemoves.push(currentRemove);
103 |     }
104 |   }
105 | 
106 |   return [
107 |     ...nonRemoveOperations,
108 |     ...consolidatedCssRuleChanges,
109 |     ...prunedOtherRemoves,
110 |   ];
111 | }
112 | 
113 | /**
114 |  * Checks if childPath is a descendant of parentPath.
115 |  * For example, ['a', 'b', 'c'] is a child of ['a', 'b']
116 |  */
117 | export function isChildPath(
118 |   childPath: (string | number)[],
119 |   parentPath: (string | number)[],
120 | ): boolean {
121 |   return (
122 |     childPath.length > parentPath.length &&
123 |     parentPath.every((seg, i) => childPath[i] === seg)
124 |   );
125 | }
126 | 
127 | /**
128 |  * Groups diff changes by domain first, then by type within each domain.
129 |  * Removes the domain from the path since it's captured in the grouping structure.
130 |  */
131 | export function groupChangesByDomainAndType(
132 |   changes: Difference[],
133 | ): Record<string, Record<string, any[]>> {
134 |   return changes.reduce(
135 |     (acc: Record<string, Record<string, any[]>>, change: Difference) => {
136 |       const { type, path, ...changeWithoutTypeAndPath } = change;
137 |       const domain = path[0] as string;
138 |       if (!acc[domain]) acc[domain] = {};
139 |       if (!acc[domain][type]) acc[domain][type] = [];
140 |       acc[domain][type].push({
141 |         ...changeWithoutTypeAndPath,
142 |         path: path.slice(1),
143 |       });
144 |       return acc;
145 |     },
146 |     {} as Record<string, Record<string, any[]>>,
147 |   );
148 | }
149 | 
150 | /**
151 |  * Generates a comprehensive summary of diff changes including totals and breakdowns by type and domain.
152 |  */
153 | export function generateDiffSummary(
154 |   processedResult: Difference[],
155 |   groupedChanges: Record<string, Record<string, any[]>>,
156 | ): {
157 |   totalChanges: number;
158 |   changeTypes: Record<string, number>;
159 |   changesByDomain: Record<string, Record<string, number>>;
160 | } {
161 |   return {
162 |     totalChanges: processedResult.length,
163 |     changeTypes: processedResult.reduce(
164 |       (acc: Record<string, number>, change: Difference) => {
165 |         acc[change.type] = (acc[change.type] ?? 0) + 1;
166 |         return acc;
167 |       },
168 |       {} as Record<string, number>,
169 |     ),
170 |     changesByDomain: Object.entries(groupedChanges).reduce(
171 |       (acc: Record<string, Record<string, number>>, [domain, types]) => {
172 |         acc[domain] = Object.entries(types).reduce(
173 |           (domainAcc: Record<string, number>, [type, changes]) => {
174 |             domainAcc[type] = changes.length;
175 |             return domainAcc;
176 |           },
177 |           {} as Record<string, number>,
178 |         );
179 |         return acc;
180 |       },
181 |       {} as Record<string, Record<string, number>>,
182 |     ),
183 |   };
184 | }
185 | 
```

--------------------------------------------------------------------------------
/packages/shared/LLMS.md:
--------------------------------------------------------------------------------

```markdown
  1 | # LLM Documentation Index
  2 | 
  3 | <a id="top"></a>
  4 | **Contents:**
  5 | 
  6 | - [Structure](#documentation-structure)
  7 | - [Foundation](#foundation-layer)
  8 | - [Intermediate](#intermediate-layer)
  9 | - [Advanced](#advanced-layer)
 10 | - [Navigation](#quick-navigation)
 11 | - [Related](#related-docs)
 12 | - [Tips](#tips)
 13 | - [Status](#doc-status)
 14 | 
 15 | This document provides quick access to all AI-friendly documentation across the shared libraries. Each library includes comprehensive API documentation, practical examples, and function references.
 16 | 
 17 | ## 📚 Documentation Structure <a id="documentation-structure"></a>
 18 | 
 19 | Each library provides three types of AI documentation:
 20 | 
 21 | - **FUNCTIONS.md**: A-Z quick reference for every public symbol
 22 | - **API.md**: Overview, key features, and minimal usage examples
 23 | - **EXAMPLES.md**: Practical, runnable code scenarios with expected outputs
 24 | 
 25 | ## 🏗️ Foundation Layer <a id="foundation-layer"></a>
 26 | 
 27 | ### @code-pushup/models <a id="models"></a>
 28 | 
 29 | Core types, interfaces, and Zod schemas for the entire ecosystem.
 30 | 
 31 | - [🔍 Functions Reference](./models/ai/FUNCTIONS.md)
 32 | - [📖 API Overview](./models/ai/API.md)
 33 | - [💡 Examples](./models/ai/EXAMPLES.md)
 34 | 
 35 | ### @push-based/typescript-ast-utils <a id="typescript-ast-utils"></a>
 36 | 
 37 | TypeScript AST parsing and manipulation utilities.
 38 | 
 39 | - [🔍 Functions Reference](./typescript-ast-utils/ai/FUNCTIONS.md)
 40 | - [📖 API Overview](./typescript-ast-utils/ai/API.md)
 41 | - [💡 Examples](./typescript-ast-utils/ai/EXAMPLES.md)
 42 | 
 43 | ## 🔧 Intermediate Layer <a id="intermediate-layer"></a>
 44 | 
 45 | ### @code-pushup/utils <a id="utils"></a>
 46 | 
 47 | General utility functions and file system operations.
 48 | 
 49 | - [🔍 Functions Reference](./utils/ai/FUNCTIONS.md)
 50 | - [📖 API Overview](./utils/ai/API.md)
 51 | - [💡 Examples](./utils/ai/EXAMPLES.md)
 52 | 
 53 | ### @push-based/styles-ast-utils <a id="styles-ast-utils"></a>
 54 | 
 55 | CSS/SCSS AST parsing and manipulation utilities.
 56 | 
 57 | - [🔍 Functions Reference](./styles-ast-utils/ai/FUNCTIONS.md)
 58 | - [📖 API Overview](./styles-ast-utils/ai/API.md)
 59 | - [💡 Examples](./styles-ast-utils/ai/EXAMPLES.md)
 60 | 
 61 | ## 🚀 Advanced Layer <a id="advanced-layer"></a>
 62 | 
 63 | ### @push-based/angular-ast-utils <a id="angular-ast-utils"></a>
 64 | 
 65 | Angular component parsing and template/style analysis.
 66 | 
 67 | - [🔍 Functions Reference](./angular-ast-utils/ai/FUNCTIONS.md)
 68 | - [📖 API Overview](./angular-ast-utils/ai/API.md)
 69 | - [💡 Examples](./angular-ast-utils/ai/EXAMPLES.md)
 70 | 
 71 | ### @push-based/ds-component-coverage <a id="ds-component-coverage"></a>
 72 | 
 73 | Design System component usage analysis and coverage reporting.
 74 | 
 75 | - [🔍 Functions Reference](./ds-component-coverage/ai/FUNCTIONS.md)
 76 | - [📖 API Overview](./ds-component-coverage/ai/API.md)
 77 | - [💡 Examples](./ds-component-coverage/ai/EXAMPLES.md)
 78 | 
 79 | ## 🎯 Quick Navigation by Use Case <a id="quick-navigation"></a>
 80 | 
 81 | ### Type Definitions & Schemas
 82 | 
 83 | - [models/FUNCTIONS.md](./models/ai/FUNCTIONS.md) - All available types and interfaces
 84 | - [models/API.md](./models/ai/API.md) - Core types and Zod schemas
 85 | 
 86 | ### File & String Operations
 87 | 
 88 | - [utils/FUNCTIONS.md](./utils/ai/FUNCTIONS.md) - Complete function reference
 89 | - [utils/API.md](./utils/ai/API.md) - File system and utility functions
 90 | - [utils/EXAMPLES.md](./utils/ai/EXAMPLES.md) - File operations and string manipulation
 91 | 
 92 | ### AST Analysis & Manipulation
 93 | 
 94 | - [typescript-ast-utils/FUNCTIONS.md](./typescript-ast-utils/ai/FUNCTIONS.md) - TS AST function reference
 95 | - [styles-ast-utils/FUNCTIONS.md](./styles-ast-utils/ai/FUNCTIONS.md) - CSS AST function reference
 96 | - [angular-ast-utils/FUNCTIONS.md](./angular-ast-utils/ai/FUNCTIONS.md) - Angular AST function reference
 97 | 
 98 | ### Angular Development
 99 | 
100 | - [angular-ast-utils/EXAMPLES.md](./angular-ast-utils/ai/EXAMPLES.md) - Component parsing and analysis
101 | 
102 | ### Design System Analysis
103 | 
104 | - [ds-component-coverage/FUNCTIONS.md](./ds-component-coverage/ai/FUNCTIONS.md) - DS analysis functions
105 | - [ds-component-coverage/API.md](./ds-component-coverage/ai/API.md) - DS migration and coverage analysis
106 | - [ds-component-coverage/EXAMPLES.md](./ds-component-coverage/ai/EXAMPLES.md) - Real-world DS analysis scenarios
107 | 
108 | ## 🔗 Related Documentation <a id="related-docs"></a>
109 | 
110 | - [DEPENDENCIES.md](./DEPENDENCIES.md) - Cross-dependencies and architecture overview
111 | - [Individual README files](./*/README.md) - Library-specific setup and build instructions
112 | 
113 | ## 💡 Tips for LLMs <a id="tips"></a>
114 | 
115 | 1. **Start with FUNCTIONS.md** for quick function lookup and signatures
116 | 2. **Use API.md** for understanding library capabilities and minimal usage
117 | 3. **Reference EXAMPLES.md** for practical implementation patterns
118 | 4. **Check DEPENDENCIES.md** to understand library relationships
119 | 5. **Follow the layered architecture** when combining multiple libraries
120 | 
121 | ## 📋 Documentation Status <a id="doc-status"></a>
122 | 
123 | All shared libraries have complete AI documentation:
124 | 
125 | | Library               | Functions | API | Examples | Status   |
126 | | --------------------- | --------- | --- | -------- | -------- |
127 | | models                | ✅        | ✅  | ✅       | Complete |
128 | | typescript-ast-utils  | ✅        | ✅  | ✅       | Complete |
129 | | utils                 | ✅        | ✅  | ✅       | Complete |
130 | | styles-ast-utils      | ✅        | ✅  | ✅       | Complete |
131 | | angular-ast-utils     | ✅        | ✅  | ✅       | Complete |
132 | | ds-component-coverage | ✅        | ✅  | ✅       | Complete |
133 | 
134 | _Last updated: 2025-06-13_
135 | 
```

--------------------------------------------------------------------------------
/packages/shared/models/ai/EXAMPLES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Examples
  2 | 
  3 | ## 1 — Working with CLI arguments
  4 | 
  5 | > Type-safe handling of command line arguments.
  6 | 
  7 | ```ts
  8 | import { type CliArgsObject, type ArgumentValue } from '@push-based/models';
  9 | 
 10 | // Basic CLI arguments
 11 | const args: CliArgsObject = {
 12 |   directory: './src/components',
 13 |   componentName: 'DsButton',
 14 |   groupBy: 'file',
 15 |   _: ['report-violations'],
 16 | };
 17 | 
 18 | console.log(args.directory); // → './src/components'
 19 | console.log(args._); // → ['report-violations']
 20 | 
 21 | // Typed CLI arguments with specific structure
 22 | interface MyToolArgs {
 23 |   directory: string;
 24 |   componentName: string;
 25 |   groupBy?: 'file' | 'folder';
 26 |   verbose?: boolean;
 27 | }
 28 | 
 29 | const typedArgs: CliArgsObject<MyToolArgs> = {
 30 |   directory: './packages/shared/models',
 31 |   componentName: 'DsCard',
 32 |   groupBy: 'folder',
 33 |   verbose: true,
 34 |   _: ['analyze'],
 35 | };
 36 | 
 37 | console.log(`Analyzing ${typedArgs.componentName} in ${typedArgs.directory}`);
 38 | // → 'Analyzing DsCard in ./packages/shared/models'
 39 | ```
 40 | 
 41 | ---
 42 | 
 43 | ## 2 — Creating MCP tools
 44 | 
 45 | > Build Model Context Protocol tools with proper typing.
 46 | 
 47 | ```ts
 48 | import { type ToolsConfig, type ToolSchemaOptions } from '@push-based/models';
 49 | 
 50 | // Define a simple MCP tool
 51 | const reportViolationsTool: ToolsConfig = {
 52 |   schema: {
 53 |     name: 'report-violations',
 54 |     description: 'Report deprecated DS CSS usage in a directory',
 55 |     inputSchema: {
 56 |       type: 'object',
 57 |       properties: {
 58 |         directory: {
 59 |           type: 'string',
 60 |           description: 'The relative path to the directory to scan',
 61 |         },
 62 |         componentName: {
 63 |           type: 'string',
 64 |           description: 'The class name of the component (e.g., DsButton)',
 65 |         },
 66 |         groupBy: {
 67 |           type: 'string',
 68 |           enum: ['file', 'folder'],
 69 |           default: 'file',
 70 |           description: 'How to group the results',
 71 |         },
 72 |       },
 73 |       required: ['directory', 'componentName'],
 74 |     },
 75 |   },
 76 |   handler: async (request) => {
 77 |     const { directory, componentName, groupBy = 'file' } = request.params.arguments as {
 78 |       directory: string;
 79 |       componentName: string;
 80 |       groupBy?: 'file' | 'folder';
 81 |     };
 82 | 
 83 |     // Tool implementation logic here
 84 |     const violations = await analyzeViolations(directory, componentName, groupBy);
 85 | 
 86 |     return {
 87 |       content: [
 88 |         {
 89 |           type: 'text',
 90 |           text: `Found ${violations.length} violations in ${directory}`,
 91 |         },
 92 |       ],
 93 |     };
 94 |   },
 95 | };
 96 | 
 97 | // Use the tool configuration
 98 | console.log(`Tool: ${reportViolationsTool.schema.name}`);
 99 | // → 'Tool: report-violations'
100 | ```
101 | 
102 | ---
103 | 
104 | ## 3 — Implementing diagnostics
105 | 
106 | > Create objects that can report issues and diagnostics.
107 | 
108 | ```ts
109 | import { type DiagnosticsAware } from '@push-based/models';
110 | 
111 | class ComponentAnalyzer implements DiagnosticsAware {
112 |   private issues: Array<{ code?: number; message: string; severity: string }> = [];
113 | 
114 |   analyze(componentPath: string): void {
115 |     // Simulate analysis
116 |     if (!componentPath.endsWith('.ts')) {
117 |       this.issues.push({
118 |         code: 1001,
119 |         message: 'Component file should have .ts extension',
120 |         severity: 'error',
121 |       });
122 |     }
123 | 
124 |     if (componentPath.includes('deprecated')) {
125 |       this.issues.push({
126 |         code: 2001,
127 |         message: 'Component uses deprecated patterns',
128 |         severity: 'warning',
129 |       });
130 |     }
131 |   }
132 | 
133 |   getIssues() {
134 |     return this.issues;
135 |   }
136 | 
137 |   clear(): void {
138 |     this.issues = [];
139 |   }
140 | }
141 | 
142 | // Usage
143 | const analyzer = new ComponentAnalyzer();
144 | analyzer.analyze('src/components/deprecated-button.js');
145 | 
146 | const issues = analyzer.getIssues();
147 | console.log(`Found ${issues.length} issues:`);
148 | issues.forEach((issue) => {
149 |   console.log(`  ${issue.severity}: ${issue.message} (code: ${issue.code})`);
150 | });
151 | // → Found 2 issues:
152 | // →   error: Component file should have .ts extension (code: 1001)
153 | // →   warning: Component uses deprecated patterns (code: 2001)
154 | 
155 | analyzer.clear();
156 | console.log(`Issues after clear: ${analyzer.getIssues().length}`); // → 0
157 | ```
158 | 
159 | ---
160 | 
161 | ## 4 — Advanced MCP tool with content results
162 | 
163 | > Create sophisticated MCP tools that return structured content.
164 | 
165 | ```ts
166 | import {
167 |   type ToolsConfig,
168 |   type ToolHandlerContentResult,
169 | } from '@push-based/models';
170 | 
171 | const buildComponentContractTool: ToolsConfig = {
172 |   schema: {
173 |     name: 'build-component-contract',
174 |     description: 'Generate a static surface contract for a component',
175 |     inputSchema: {
176 |       type: 'object',
177 |       properties: {
178 |         directory: { type: 'string' },
179 |         templateFile: { type: 'string' },
180 |         styleFile: { type: 'string' },
181 |         typescriptFile: { type: 'string' },
182 |         dsComponentName: { type: 'string' },
183 |       },
184 |       required: ['directory', 'templateFile', 'styleFile', 'typescriptFile', 'dsComponentName'],
185 |     },
186 |   },
187 |   handler: async (request) => {
188 |     const params = request.params.arguments as {
189 |       directory: string;
190 |       templateFile: string;
191 |       styleFile: string;
192 |       typescriptFile: string;
193 |       dsComponentName: string;
194 |     };
195 | 
196 |     // Generate contract
197 |     const contract = await generateContract(params);
198 | 
199 |     const content: ToolHandlerContentResult[] = [
200 |       {
201 |         type: 'text',
202 |         text: `Generated contract for ${params.dsComponentName}`,
203 |       },
204 |       {
205 |         type: 'text',
206 |         text: `Template inputs: ${contract.templateInputs.length}`,
207 |       },
208 |       {
209 |         type: 'text',
210 |         text: `Style classes: ${contract.styleClasses.length}`,
211 |       },
212 |     ];
213 | 
214 |     return { content };
215 |   },
216 | };
217 | 
218 | // Mock contract generation function
219 | async function generateContract(params: any) {
220 |   return {
221 |     templateInputs: ['@Input() label: string', '@Input() disabled: boolean'],
222 |     styleClasses: ['.ds-button', '.ds-button--primary'],
223 |   };
224 | }
225 | ```
226 | 
227 | These examples demonstrate the practical usage patterns of the `@push-based/models` library for building type-safe CLI tools, MCP integrations, and diagnostic utilities in the Angular MCP toolkit.
228 | 
```

--------------------------------------------------------------------------------
/.cursor/flows/component-refactoring/angular-20.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Angular Best Practices
  2 | 
  3 | This project adheres to modern Angular best practices, emphasizing maintainability, performance, accessibility, and scalability.
  4 | 
  5 | ## TypeScript Best Practices
  6 | 
  7 | - **Strict Type Checking:** Always enable and adhere to strict type checking. This helps catch errors early and improves code quality.
  8 | - **Prefer Type Inference:** Allow TypeScript to infer types when they are obvious from the context. This reduces verbosity while maintaining type safety.
  9 |   - **Bad:**
 10 |     ```typescript
 11 |     let name: string = 'Angular';
 12 |     ```
 13 |   - **Good:**
 14 |     ```typescript
 15 |     let name = 'Angular';
 16 |     ```
 17 | - **Avoid `any`:** Do not use the `any` type unless absolutely necessary as it bypasses type checking. Prefer `unknown` when a type is uncertain and you need to handle it safely.
 18 | 
 19 | ## Angular Best Practices
 20 | 
 21 | - **Standalone Components:** Always use standalone components, directives, and pipes. Avoid using `NgModules` for new features or refactoring existing ones.
 22 | - **Implicit Standalone:** When creating standalone components, you do not need to explicitly set `standalone: true` as it is implied by default when generating a standalone component.
 23 |   - **Bad:**
 24 |     ```typescript
 25 |     @Component({
 26 |       standalone: true,
 27 |       // ...
 28 |     })
 29 |     export class MyComponent {}
 30 |     ```
 31 |   - **Good:**
 32 |     ```typescript
 33 |     @Component({
 34 |       // `standalone: true` is implied
 35 |       // ...
 36 |     })
 37 |     export class MyComponent {}
 38 |     ```
 39 | - **Signals for State Management:** Utilize Angular Signals for reactive state management within components and services.
 40 | - **Lazy Loading:** Implement lazy loading for feature routes to improve initial load times of your application.
 41 | - **NgOptimizedImage:** Use `NgOptimizedImage` for all static images to automatically optimize image loading and performance.
 42 | 
 43 | ## Components
 44 | 
 45 | - **Single Responsibility:** Keep components small, focused, and responsible for a single piece of functionality.
 46 | - **`input()` and `output()` Functions:** Prefer `input()` and `output()` functions over the `@Input()` and `@Output()` decorators for defining component inputs and outputs.
 47 |   - **Old Decorator Syntax:**
 48 |     ```typescript
 49 |     @Input() userId!: string;
 50 |     @Output() userSelected = new EventEmitter<string>();
 51 |     ```
 52 |   - **New Function Syntax:**
 53 | 
 54 |     ```typescript
 55 |     import { input, output } from '@angular/core';
 56 | 
 57 |     // ...
 58 |     userId = input<string>('');
 59 |     userSelected = output<string>();
 60 |     ```
 61 | 
 62 | - **`computed()` for Derived State:** Use the `computed()` function from `@angular/core` for derived state based on signals.
 63 | - **`ChangeDetectionStrategy.OnPush`:** Always set `changeDetection: ChangeDetectionStrategy.OnPush` in the `@Component` decorator for performance benefits by reducing unnecessary change detection cycles.
 64 | - **Inline Templates:** Prefer inline templates (template: `...`) for small components to keep related code together. For larger templates, use external HTML files.
 65 | - **Reactive Forms:** Prefer Reactive forms over Template-driven forms for complex forms, validation, and dynamic controls due to their explicit, immutable, and synchronous nature.
 66 | - **No `ngClass` / `NgClass`:** Do not use the `ngClass` directive. Instead, use native `class` bindings for conditional styling.
 67 |   - **Bad:**
 68 |     ```html
 69 |     <section [ngClass]="{'active': isActive}"></section>
 70 |     ```
 71 |   - **Good:**
 72 |     ```html
 73 |     <section [class.active]="isActive"></section>
 74 |     <section [class]="{'active': isActive}"></section>
 75 |     <section [class]="myClasses"></section>
 76 |     ```
 77 | - **No `ngStyle` / `NgStyle`:** Do not use the `ngStyle` directive. Instead, use native `style` bindings for conditional inline styles.
 78 |   - **Bad:**
 79 |     ```html
 80 |     <section [ngStyle]="{'font-size': fontSize + 'px'}"></section>
 81 |     ```
 82 |   - **Good:**
 83 |     ```html
 84 |     <section [style.font-size.px]="fontSize"></section>
 85 |     <section [style]="myStyles"></section>
 86 |     ```
 87 | 
 88 | ## State Management
 89 | 
 90 | - **Signals for Local State:** Use signals for managing local component state.
 91 | - **`computed()` for Derived State:** Leverage `computed()` for any state that can be derived from other signals.
 92 | - **Pure and Predictable Transformations:** Ensure state transformations are pure functions (no side effects) and predictable.
 93 | 
 94 | ## Templates
 95 | 
 96 | - **Simple Templates:** Keep templates as simple as possible, avoiding complex logic directly in the template. Delegate complex logic to the component's TypeScript code.
 97 | - **Native Control Flow:** Use the new built-in control flow syntax (`@if`, `@for`, `@switch`) instead of the older structural directives (`*ngIf`, `*ngFor`, `*ngSwitch`).
 98 |   - **Old Syntax:**
 99 |     ```html
100 |     <section *ngIf="isVisible">Content</section>
101 |     <section *ngFor="let item of items">{{ item }}</section>
102 |     ```
103 |   - **New Syntax:**
104 |     ```html
105 |     @if (isVisible) {
106 |     <section>Content</section>
107 |     } @for (item of items; track item.id) {
108 |     <section>{{ item }}</section>
109 |     }
110 |     ```
111 | - **Async Pipe:** Use the `async` pipe to handle observables in templates. This automatically subscribes and unsubscribes, preventing memory leaks.
112 | 
113 | ## Services
114 | 
115 | - **Single Responsibility:** Design services around a single, well-defined responsibility.
116 | - **`providedIn: 'root'`:** Use the `providedIn: 'root'` option when declaring injectable services to ensure they are singletons and tree-shakable.
117 | - **`inject()` Function:** Prefer the `inject()` function over constructor injection when injecting dependencies, especially within `provide` functions, `computed` properties, or outside of constructor context.
118 |   - **Old Constructor Injection:**
119 |     ```typescript
120 |     constructor(private myService: MyService) {}
121 |     ```
122 |   - **New `inject()` Function:**
123 | 
124 |     ```typescript
125 |     import { inject } from '@angular/core';
126 | 
127 |     export class MyComponent {
128 |       private myService = inject(MyService);
129 |       // ...
130 |     }
131 |     ```
132 | 
```

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

```typescript
  1 | import { Component, signal } from '@angular/core';
  2 | import { ProductCardComponent, Product, ProductBadgeType } from './product-card.component';
  3 | 
  4 | @Component({
  5 |   selector: 'app-product-showcase',
  6 |   standalone: true,
  7 |   imports: [ProductCardComponent],
  8 |   template: `
  9 |     <div class="showcase-container">
 10 |       <h2>Product Card Showcase - Moderate Badge Complexity</h2>
 11 |       <p>This demonstrates a moderate level of badge complexity that should be more manageable to refactor to DsBadge.</p>
 12 |       
 13 |       <div class="showcase-grid">
 14 |         @for (product of products(); track product.id) {
 15 |           <app-product-card
 16 |             [product]="product"
 17 |             [badgeType]="getBadgeType(product)"
 18 |             [showBadge]="shouldShowBadge(product)"
 19 |             [animated]="true"
 20 |             [compact]="false"
 21 |             (productSelected)="onProductSelected($event)"
 22 |             (favoriteToggled)="onFavoriteToggled($event)"
 23 |             (addToCartClicked)="onAddToCart($event)"
 24 |             (quickViewClicked)="onQuickView($event)">
 25 |           </app-product-card>
 26 |         }
 27 |       </div>
 28 |       
 29 |       <div class="showcase-log">
 30 |         <h3>Event Log:</h3>
 31 |         <div class="log-entries">
 32 |           @for (entry of eventLog(); track $index) {
 33 |             <div class="log-entry">{{ entry }}</div>
 34 |           }
 35 |         </div>
 36 |       </div>
 37 |     </div>
 38 |   `,
 39 |   styles: [`
 40 |     .showcase-container {
 41 |       padding: 2rem;
 42 |       max-width: 1200px;
 43 |       margin: 0 auto;
 44 |     }
 45 |     
 46 |     .showcase-container h2 {
 47 |       color: #1f2937;
 48 |       margin-bottom: 1rem;
 49 |     }
 50 |     
 51 |     .showcase-container p {
 52 |       color: #6b7280;
 53 |       margin-bottom: 2rem;
 54 |     }
 55 |     
 56 |     .showcase-grid {
 57 |       display: grid;
 58 |       grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
 59 |       gap: 1.5rem;
 60 |       margin-bottom: 2rem;
 61 |     }
 62 |     
 63 |     .showcase-log {
 64 |       background: white;
 65 |       border-radius: 0.5rem;
 66 |       padding: 1.5rem;
 67 |       box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
 68 |     }
 69 |     
 70 |     .showcase-log h3 {
 71 |       margin: 0 0 1rem 0;
 72 |       color: #1f2937;
 73 |       font-size: 1.125rem;
 74 |     }
 75 |     
 76 |     .log-entries {
 77 |       max-height: 200px;
 78 |       overflow-y: auto;
 79 |     }
 80 |     
 81 |     .log-entry {
 82 |       padding: 0.5rem;
 83 |       border-bottom: 1px solid #f3f4f6;
 84 |       font-size: 0.875rem;
 85 |       color: #374151;
 86 |       font-family: monospace;
 87 |     }
 88 |     
 89 |     .log-entry:last-child {
 90 |       border-bottom: none;
 91 |     }
 92 |   `]
 93 | })
 94 | export class ProductShowcaseComponent {
 95 |   eventLog = signal<string[]>([]);
 96 |   
 97 |   products = signal<Product[]>([
 98 |     {
 99 |       id: 'prod-1',
100 |       name: 'Premium Wireless Headphones',
101 |       price: 199.99,
102 |       originalPrice: 249.99,
103 |       category: 'Electronics',
104 |       rating: 4.5,
105 |       reviewCount: 128,
106 |       inStock: true,
107 |       imageUrl: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=200&fit=crop',
108 |       tags: ['wireless', 'premium', 'noise-canceling']
109 |     },
110 |     {
111 |       id: 'prod-2',
112 |       name: 'Smart Fitness Watch',
113 |       price: 299.99,
114 |       category: 'Wearables',
115 |       rating: 4.8,
116 |       reviewCount: 256,
117 |       inStock: true,
118 |       imageUrl: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=300&h=200&fit=crop',
119 |       tags: ['fitness', 'smart', 'waterproof', 'gps']
120 |     },
121 |     {
122 |       id: 'prod-3',
123 |       name: 'Professional Camera Lens',
124 |       price: 899.99,
125 |       category: 'Photography',
126 |       rating: 4.9,
127 |       reviewCount: 89,
128 |       inStock: true,
129 |       imageUrl: 'https://images.unsplash.com/photo-1606983340126-99ab4feaa64a?w=300&h=200&fit=crop',
130 |       tags: ['professional', 'telephoto', 'canon']
131 |     },
132 |     {
133 |       id: 'prod-4',
134 |       name: 'Gaming Mechanical Keyboard',
135 |       price: 149.99,
136 |       originalPrice: 179.99,
137 |       category: 'Gaming',
138 |       rating: 4.6,
139 |       reviewCount: 342,
140 |       inStock: false,
141 |       imageUrl: 'https://images.unsplash.com/photo-1541140532154-b024d705b90a?w=300&h=200&fit=crop',
142 |       tags: ['mechanical', 'rgb', 'gaming', 'cherry-mx']
143 |     },
144 |     {
145 |       id: 'prod-5',
146 |       name: 'Eco-Friendly Water Bottle',
147 |       price: 24.99,
148 |       category: 'Lifestyle',
149 |       rating: 4.3,
150 |       reviewCount: 67,
151 |       inStock: true,
152 |       imageUrl: 'https://images.unsplash.com/photo-1602143407151-7111542de6e8?w=300&h=200&fit=crop',
153 |       tags: ['eco-friendly', 'stainless-steel', 'insulated']
154 |     },
155 |     {
156 |       id: 'prod-6',
157 |       name: 'Designer Laptop Backpack',
158 |       price: 79.99,
159 |       originalPrice: 99.99,
160 |       category: 'Accessories',
161 |       rating: 4.4,
162 |       reviewCount: 156,
163 |       inStock: true,
164 |       imageUrl: 'https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=300&h=200&fit=crop',
165 |       tags: ['designer', 'laptop', 'travel', 'waterproof']
166 |     }
167 |   ]);
168 | 
169 |   getBadgeType(product: Product): ProductBadgeType {
170 |     // Logic to determine badge type based on product characteristics
171 |     if (product.originalPrice && product.originalPrice > product.price) {
172 |       return 'sale';
173 |     }
174 |     if (product.rating >= 4.8) {
175 |       return 'bestseller';
176 |     }
177 |     if (!product.inStock) {
178 |       return 'limited';
179 |     }
180 |     if (product.tags.includes('new') || Date.now() % 2 === 0) { // Simulate new products
181 |       return 'new';
182 |     }
183 |     return 'sale';
184 |   }
185 | 
186 |   shouldShowBadge(product: Product): boolean {
187 |     // Show badge for sale items, high-rated items, or out of stock
188 |     return !!(product.originalPrice && product.originalPrice > product.price) ||
189 |            product.rating >= 4.7 ||
190 |            !product.inStock;
191 |   }
192 | 
193 |   onProductSelected(product: Product) {
194 |     this.addLogEntry(`Product selected: ${product.name}`);
195 |   }
196 | 
197 |   onFavoriteToggled(event: {product: Product, favorited: boolean}) {
198 |     this.addLogEntry(`${event.product.name} ${event.favorited ? 'added to' : 'removed from'} favorites`);
199 |   }
200 | 
201 |   onAddToCart(product: Product) {
202 |     this.addLogEntry(`Added to cart: ${product.name} - $${product.price.toFixed(2)}`);
203 |   }
204 | 
205 |   onQuickView(product: Product) {
206 |     this.addLogEntry(`Quick view opened: ${product.name}`);
207 |   }
208 | 
209 |   private addLogEntry(message: string) {
210 |     const timestamp = new Date().toLocaleTimeString();
211 |     const entry = `[${timestamp}] ${message}`;
212 |     this.eventLog.update(log => [entry, ...log.slice(0, 49)]);
213 |   }
214 | } 
```
Page 5/10FirstPrevNextLast