#
tokens: 49037/50000 35/473 files (page 4/10)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 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/shared/typescript-ast-utils/ai/FUNCTIONS.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Public API — Quick Reference
 2 | 
 3 | | Symbol                 | Kind     | Summary                                                         |
 4 | | ---------------------- | -------- | --------------------------------------------------------------- |
 5 | | `QUOTE_REGEX`          | constant | Regular expression for matching quotes at start/end of strings  |
 6 | | `getDecorators`        | function | Extract decorators from a TypeScript AST node safely            |
 7 | | `hasDecorators`        | function | Type guard to check if a node has decorators property           |
 8 | | `isComponentDecorator` | function | Check if a decorator is specifically a `@Component` decorator   |
 9 | | `isDecorator`          | function | Check if a decorator matches a specific name (or any decorator) |
10 | | `removeQuotes`         | function | Remove surrounding quotes from a string literal AST node        |
11 | 
12 | ## Function Signatures
13 | 
14 | ### `getDecorators(node: ts.Node): readonly ts.Decorator[]`
15 | 
16 | Safely extracts decorators from a TypeScript AST node, handling different TypeScript compiler API versions.
17 | 
18 | **Parameters:**
19 | 
20 | - `node` - The TypeScript AST node to extract decorators from
21 | 
22 | **Returns:** Array of decorators (empty array if none found)
23 | 
24 | ### `hasDecorators(node: ts.Node): node is ts.Node & { decorators: readonly ts.Decorator[] }`
25 | 
26 | Type guard function that checks if a node has a decorators property.
27 | 
28 | **Parameters:**
29 | 
30 | - `node` - The TypeScript AST node to check
31 | 
32 | **Returns:** Type predicate indicating if the node has decorators
33 | 
34 | ### `isComponentDecorator(decorator: ts.Decorator): boolean`
35 | 
36 | Convenience function to check if a decorator is specifically a `@Component` decorator.
37 | 
38 | **Parameters:**
39 | 
40 | - `decorator` - The decorator to check
41 | 
42 | **Returns:** `true` if the decorator is `@Component`, `false` otherwise
43 | 
44 | ### `isDecorator(decorator: ts.Decorator, decoratorName?: string): boolean`
45 | 
46 | Generic function to check if a decorator matches a specific name or is any valid decorator.
47 | 
48 | **Parameters:**
49 | 
50 | - `decorator` - The decorator to check
51 | - `decoratorName` - Optional name to match against (if omitted, checks if it's any valid decorator)
52 | 
53 | **Returns:** `true` if the decorator matches the criteria, `false` otherwise
54 | 
55 | ### `removeQuotes(node: ts.Node, sourceFile: ts.SourceFile): string`
56 | 
57 | Removes surrounding quotes from a string literal AST node's text content.
58 | 
59 | **Parameters:**
60 | 
61 | - `node` - The TypeScript AST node containing the quoted string
62 | - `sourceFile` - The source file context for getting node text
63 | 
64 | **Returns:** The string content without surrounding quotes
65 | 
66 | ## Constants
67 | 
68 | ### `QUOTE_REGEX: RegExp`
69 | 
70 | Regular expression pattern `/^['"`]+|['"`]+$/g` used to match and remove quotes from the beginning and end of strings. Supports single quotes (`'`), double quotes (`"` ), and backticks (``  ` ``).
71 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/shared/models/schema-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolSchemaOptions } from '@push-based/models';
  2 | 
  3 | /**
  4 |  * Common schema property definitions used across DS tools
  5 |  * Note: cwd and workspaceRoot are injected by MCP server configuration, not user inputs
  6 |  */
  7 | export const COMMON_SCHEMA_PROPERTIES = {
  8 |   directory: {
  9 |     type: 'string' as const,
 10 |     description:
 11 |       'The relative path to the directory (starting with "./path/to/dir") to scan. Respect the OS specifics.',
 12 |   },
 13 | 
 14 |   componentName: {
 15 |     type: 'string' as const,
 16 |     description: 'The class name of the component (e.g., DsButton)',
 17 |   },
 18 | 
 19 |   groupBy: {
 20 |     type: 'string' as const,
 21 |     enum: ['file', 'folder'] as const,
 22 |     description: 'How to group the results',
 23 |     default: 'file' as const,
 24 |   },
 25 | } as const;
 26 | 
 27 | /**
 28 |  * Creates a component input schema with a custom description
 29 |  */
 30 | export const createComponentInputSchema = (
 31 |   description: string,
 32 | ): ToolSchemaOptions['inputSchema'] => ({
 33 |   type: 'object',
 34 |   properties: {
 35 |     componentName: {
 36 |       ...COMMON_SCHEMA_PROPERTIES.componentName,
 37 |       description,
 38 |     },
 39 |   },
 40 |   required: ['componentName'],
 41 | });
 42 | 
 43 | /**
 44 |  * Creates a directory + component schema for tools that analyze both
 45 |  */
 46 | export const createDirectoryComponentSchema = (
 47 |   componentDescription: string,
 48 |   additionalProperties?: Record<string, any>,
 49 | ): ToolSchemaOptions['inputSchema'] => ({
 50 |   type: 'object',
 51 |   properties: {
 52 |     directory: COMMON_SCHEMA_PROPERTIES.directory,
 53 |     componentName: {
 54 |       ...COMMON_SCHEMA_PROPERTIES.componentName,
 55 |       description: componentDescription,
 56 |     },
 57 |     ...additionalProperties,
 58 |   },
 59 |   required: ['directory', 'componentName'],
 60 | });
 61 | 
 62 | /**
 63 |  * Creates a project analysis schema with common project properties
 64 |  * Note: cwd and workspaceRoot are handled by MCP server configuration, not user inputs
 65 |  */
 66 | export const createProjectAnalysisSchema = (
 67 |   additionalProperties?: Record<string, any>,
 68 | ): ToolSchemaOptions['inputSchema'] => ({
 69 |   type: 'object',
 70 |   properties: {
 71 |     directory: COMMON_SCHEMA_PROPERTIES.directory,
 72 |     ...additionalProperties,
 73 |   },
 74 |   required: ['directory'],
 75 | });
 76 | 
 77 | /**
 78 |  * Creates a violation reporting schema with grouping options
 79 |  */
 80 | export const createViolationReportingSchema = (
 81 |   additionalProperties?: Record<string, any>,
 82 | ): ToolSchemaOptions['inputSchema'] => ({
 83 |   type: 'object',
 84 |   properties: {
 85 |     directory: COMMON_SCHEMA_PROPERTIES.directory,
 86 |     componentName: COMMON_SCHEMA_PROPERTIES.componentName,
 87 |     groupBy: COMMON_SCHEMA_PROPERTIES.groupBy,
 88 |     ...additionalProperties,
 89 |   },
 90 |   required: ['directory', 'componentName'],
 91 | });
 92 | 
 93 | /**
 94 |  * Common annotation patterns for DS tools
 95 |  */
 96 | export const COMMON_ANNOTATIONS = {
 97 |   readOnly: {
 98 |     readOnlyHint: true,
 99 |     openWorldHint: true,
100 |     idempotentHint: true,
101 |   },
102 |   project: {
103 |     readOnlyHint: true,
104 |     openWorldHint: true,
105 |     idempotentHint: true,
106 |   },
107 | } as const;
108 | 
```

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

```typescript
 1 | import { FileExtension, FileType } from './types.js';
 2 | import {
 3 |   IMPORT_REGEXES,
 4 |   REGEX_CACHE_UTILS,
 5 | } from '../../shared/utils/regex-helpers.js';
 6 | 
 7 | const STYLES_EXTENSIONS = ['.css', '.scss', '.sass', '.less'] as const;
 8 | const SCRIPT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] as const;
 9 | const TEMPLATE_EXTENSIONS = ['.html'] as const;
10 | const FILE_EXTENSIONS = [
11 |   ...STYLES_EXTENSIONS,
12 |   ...SCRIPT_EXTENSIONS,
13 |   ...TEMPLATE_EXTENSIONS,
14 | ] as const;
15 | const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'] as const;
16 | const INDEX_FILES = [
17 |   '/index.ts',
18 |   '/index.tsx',
19 |   '/index.js',
20 |   '/index.jsx',
21 | ] as const;
22 | const EXCLUDE_PATTERNS = [
23 |   'node_modules',
24 |   'dist',
25 |   'build',
26 |   '.git',
27 |   '.vscode',
28 |   '.idea',
29 | ] as const;
30 | 
31 | const FILE_TYPE_MAP = {
32 |   '.ts': 'typescript',
33 |   '.tsx': 'typescript-react',
34 |   '.js': 'javascript',
35 |   '.jsx': 'javascript-react',
36 |   '.css': 'css',
37 |   '.scss': 'scss',
38 |   '.sass': 'sass',
39 |   '.less': 'less',
40 |   '.html': 'template',
41 | } as const satisfies Record<FileExtension, FileType>;
42 | 
43 | export const DEPENDENCY_ANALYSIS_CONFIG = {
44 |   stylesExtensions: STYLES_EXTENSIONS,
45 |   scriptExtensions: SCRIPT_EXTENSIONS,
46 |   fileExtensions: FILE_EXTENSIONS,
47 |   resolveExtensions: RESOLVE_EXTENSIONS,
48 |   indexFiles: INDEX_FILES,
49 |   excludePatterns: EXCLUDE_PATTERNS,
50 |   fileTypeMap: FILE_TYPE_MAP,
51 | } as const;
52 | 
53 | // Use shared regex patterns instead of duplicating them
54 | export const REGEX_PATTERNS = {
55 |   ES6_IMPORT: IMPORT_REGEXES.ES6_IMPORT,
56 |   COMMONJS_REQUIRE: IMPORT_REGEXES.COMMONJS_REQUIRE,
57 |   DYNAMIC_IMPORT: IMPORT_REGEXES.DYNAMIC_IMPORT,
58 |   CSS_IMPORT: IMPORT_REGEXES.CSS_IMPORT,
59 |   CSS_URL: IMPORT_REGEXES.CSS_URL,
60 |   ANGULAR_COMPONENT_DECORATOR: IMPORT_REGEXES.ANGULAR_COMPONENT_DECORATOR,
61 |   GLOB_WILDCARD_REPLACEMENT: /\*/g,
62 | } as const;
63 | 
64 | // Use shared regex cache utilities instead of duplicating cache management
65 | export const componentImportRegex = (componentName: string): RegExp =>
66 |   REGEX_CACHE_UTILS.getOrCreate(`component-import-${componentName}`, () =>
67 |     IMPORT_REGEXES.createComponentImportRegex(componentName),
68 |   );
69 | 
70 | export const getComponentImportRegex = (componentName: string): RegExp =>
71 |   componentImportRegex(componentName);
72 | 
73 | export const getCombinedComponentImportRegex = (
74 |   componentNames: string[],
75 | ): RegExp => {
76 |   const cacheKey = componentNames.sort().join('|');
77 |   return REGEX_CACHE_UTILS.getOrCreate(
78 |     `combined-component-import-${cacheKey}`,
79 |     () => IMPORT_REGEXES.createCombinedComponentImportRegex(componentNames),
80 |   );
81 | };
82 | 
83 | export const clearComponentImportRegexCache = (): void => {
84 |   REGEX_CACHE_UTILS.clear();
85 | };
86 | 
87 | export const getComponentImportRegexCacheStats = () => {
88 |   const stats = REGEX_CACHE_UTILS.getStats();
89 |   return {
90 |     singleComponentCacheSize: stats.size, // Combined cache now
91 |     combinedComponentCacheSize: 0, // No longer separate
92 |     totalCacheSize: stats.size,
93 |   };
94 | };
95 | 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-4/multi-violation-test.component.html:
--------------------------------------------------------------------------------

```html
 1 | <!-- Multi-violation test component using deprecated CSS classes from 4 DS components -->
 2 | 
 3 | <div class="test-container">
 4 |   <h2>Multi-Violation Test Component</h2>
 5 |   
 6 |   <!-- ❌ BAD: DsButton violations - using deprecated 'btn', 'btn-primary', 'legacy-button' -->
 7 |   <div class="button-section">
 8 |     <h3>Button Violations (DsButton)</h3>
 9 |     <button class="btn btn-primary" (click)="handleButtonClick()">
10 |       Primary Legacy Button
11 |     </button>
12 |     <button class="btn">
13 |       Basic Legacy Button  
14 |     </button>
15 |     <button class="legacy-button">
16 |       Legacy Button Style
17 |     </button>
18 |   </div>
19 | 
20 |   <!-- ❌ BAD: DsBadge violations - using deprecated 'offer-badge' -->
21 |   <div class="badge-section">
22 |     <h3>Badge Violations (DsBadge)</h3>
23 |     <span class="offer-badge">50% OFF</span>
24 |     <div class="product-item">
25 |       <span>Special Product</span>
26 |       <span class="offer-badge">NEW</span>
27 |     </div>
28 |   </div>
29 | 
30 |   <!-- ❌ BAD: DsTabsModule violations - using deprecated 'tab-nav', 'nav-tabs', 'tab-nav-item' -->
31 |   <div class="tabs-section">
32 |     <h3>Tabs Violations (DsTabsModule)</h3>
33 |     <ul class="nav-tabs tab-nav">
34 |       <li class="tab-nav-item" 
35 |           [class.active]="activeTab === 0"
36 |           (click)="switchTab(0)">
37 |         Tab 1
38 |       </li>
39 |       <li class="tab-nav-item" 
40 |           [class.active]="activeTab === 1"
41 |           (click)="switchTab(1)">
42 |         Tab 2
43 |       </li>
44 |       <li class="tab-nav-item" 
45 |           [class.active]="activeTab === 2"
46 |           (click)="switchTab(2)">
47 |         Tab 3
48 |       </li>
49 |     </ul>
50 |     <div class="tab-content">
51 |       <div *ngIf="activeTab === 0">Content for Tab 1</div>
52 |       <div *ngIf="activeTab === 1">Content for Tab 2</div>
53 |       <div *ngIf="activeTab === 2">Content for Tab 3</div>
54 |     </div>
55 |   </div>
56 | 
57 |   <!-- ❌ BAD: DsCard violations - using deprecated 'card' -->
58 |   <div class="card-section">
59 |     <h3>Card Violations (DsCard)</h3>
60 |     <div class="card" *ngIf="showCard">
61 |       <div class="card-header">
62 |         <h4>Legacy Card Header</h4>
63 |         <button class="btn" (click)="toggleCard()">×</button>
64 |       </div>
65 |       <div class="card-body">
66 |         <p>This is a legacy card implementation using deprecated CSS classes.</p>
67 |         <span class="offer-badge">FEATURED</span>
68 |       </div>
69 |       <div class="card-footer">
70 |         <button class="legacy-button">Action</button>
71 |       </div>
72 |     </div>
73 |   </div>
74 | 
75 |   <!-- Mixed violations in a single section -->
76 |   <div class="mixed-section">
77 |     <h3>Mixed Violations</h3>
78 |     <div class="card">
79 |       <nav class="tab-nav">
80 |         <span class="tab-nav-item">Settings</span>
81 |         <span class="offer-badge">{{notifications}}</span>
82 |       </nav>
83 |       <div class="card-body">
84 |         <button class="btn btn-primary">Save Settings</button>
85 |         <button class="legacy-button">Cancel</button>
86 |       </div>
87 |     </div>
88 |   </div>
89 | </div>
90 | 
```

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

```typescript
 1 | /* eslint-disable prefer-const */
 2 | import { describe, it, expect, vi, beforeEach } from 'vitest';
 3 | 
 4 | // -----------------------------------------------------------------------------
 5 | // Mocks
 6 | // -----------------------------------------------------------------------------
 7 | 
 8 | // @push-based/styles-ast-utils
 9 | let parseStylesheetMock: any;
10 | let visitEachChildMock: any;
11 | vi.mock('@push-based/styles-ast-utils', () => ({
12 |   get parseStylesheet() {
13 |     return parseStylesheetMock;
14 |   },
15 |   get visitEachChild() {
16 |     return visitEachChildMock;
17 |   },
18 | }));
19 | parseStylesheetMock = vi.fn();
20 | visitEachChildMock = vi.fn();
21 | 
22 | // css-match
23 | let selectorMatchesMock: any;
24 | vi.mock('../utils/css-match.js', () => ({
25 |   get selectorMatches() {
26 |     return selectorMatchesMock;
27 |   },
28 | }));
29 | selectorMatchesMock = vi.fn();
30 | 
31 | // SUT
32 | import { collectInlineStyles } from '../utils/inline-styles.collector.js';
33 | 
34 | // -----------------------------------------------------------------------------
35 | // Helpers
36 | // -----------------------------------------------------------------------------
37 | function createRule(selector: string, decls: Record<string, string>) {
38 |   return {
39 |     selector,
40 |     walkDecls(cb: (decl: { prop: string; value: string }) => void) {
41 |       Object.entries(decls).forEach(([prop, value]) => cb({ prop, value }));
42 |     },
43 |   } as any;
44 | }
45 | 
46 | function resetMocks() {
47 |   parseStylesheetMock.mockReset();
48 |   visitEachChildMock.mockReset();
49 |   selectorMatchesMock.mockReset();
50 | }
51 | 
52 | // -----------------------------------------------------------------------------
53 | // Tests
54 | // -----------------------------------------------------------------------------
55 | 
56 | describe('collectInlineStyles', () => {
57 |   beforeEach(() => {
58 |     resetMocks();
59 |     // Provide a simple PostCSS root mock
60 |     parseStylesheetMock.mockReturnValue({ root: { type: 'root' } });
61 |   });
62 | 
63 |   it('returns style declarations from inline styles', async () => {
64 |     // Fake ParsedComponent with inline styles array
65 |     const parsedComponent = {
66 |       fileName: '/cmp.ts',
67 |       styles: [
68 |         {
69 |           parse: async () => ({
70 |             toString: () => '.btn{color:red}',
71 |           }),
72 |         },
73 |       ],
74 |     } as any;
75 | 
76 |     // DOM snapshot with one matching element
77 |     const dom = {
78 |       button: {} as any,
79 |     };
80 | 
81 |     visitEachChildMock.mockImplementation((_root: any, visitor: any) => {
82 |       visitor.visitRule(createRule('.btn', { color: 'red' }));
83 |     });
84 | 
85 |     selectorMatchesMock.mockImplementation(
86 |       (cssSel: string, domKey: string) =>
87 |         cssSel === '.btn' && domKey === 'button',
88 |     );
89 | 
90 |     const styles = await collectInlineStyles(parsedComponent, dom as any);
91 | 
92 |     expect(styles.sourceFile).toBe('/cmp.ts');
93 |     expect(styles.rules['.btn']).toBeDefined();
94 |     expect(styles.rules['.btn'].properties).toEqual({ color: 'red' });
95 |     expect(styles.rules['.btn'].appliesTo).toEqual(['button']);
96 |   });
97 | });
98 | 
```

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

```typescript
 1 | import {
 2 |   createHandler,
 3 |   BaseHandlerOptions,
 4 | } from '../../shared/utils/handler-helpers.js';
 5 | import {
 6 |   resolveCrossPlatformPath,
 7 |   normalizePathsInObject,
 8 | } from '../../shared/utils/cross-platform-path.js';
 9 | import { diffComponentContractSchema } from './models/schema.js';
10 | import type { DomPathDictionary } from '../shared/models/types.js';
11 | import { loadContract } from '../shared/utils/contract-file-ops.js';
12 | import {
13 |   consolidateAndPruneRemoveOperationsWithDeduplication,
14 |   groupChangesByDomainAndType,
15 |   generateDiffSummary,
16 | } from './utils/diff-utils.js';
17 | import { writeFile, mkdir } from 'node:fs/promises';
18 | import diff from 'microdiff';
19 | 
20 | interface DiffComponentContractOptions extends BaseHandlerOptions {
21 |   saveLocation: string;
22 |   contractBeforePath: string;
23 |   contractAfterPath: string;
24 |   dsComponentName?: string;
25 | }
26 | 
27 | export const diffComponentContractHandler = createHandler<
28 |   DiffComponentContractOptions,
29 |   {
30 |     fileUrl: string;
31 |     domPathStats?: DomPathDictionary['stats'];
32 |   }
33 | >(
34 |   diffComponentContractSchema.name,
35 |   async (params, { cwd, workspaceRoot }) => {
36 |     const {
37 |       saveLocation,
38 |       contractBeforePath,
39 |       contractAfterPath,
40 |       dsComponentName = '',
41 |     } = params;
42 | 
43 |     const effectiveBeforePath = resolveCrossPlatformPath(
44 |       cwd,
45 |       contractBeforePath,
46 |     );
47 |     const effectiveAfterPath = resolveCrossPlatformPath(cwd, contractAfterPath);
48 | 
49 |     const contractBefore = await loadContract(effectiveBeforePath);
50 |     const contractAfter = await loadContract(effectiveAfterPath);
51 | 
52 |     const rawDiffResult = diff(contractBefore, contractAfter);
53 | 
54 |     const { processedResult, domPathDict } =
55 |       consolidateAndPruneRemoveOperationsWithDeduplication(rawDiffResult);
56 | 
57 |     const groupedChanges = groupChangesByDomainAndType(processedResult);
58 | 
59 |     const diffData = {
60 |       before: effectiveBeforePath,
61 |       after: effectiveAfterPath,
62 |       dsComponentName,
63 |       timestamp: new Date().toISOString(),
64 |       domPathDictionary: domPathDict.paths,
65 |       changes: groupedChanges,
66 |       summary: generateDiffSummary(processedResult, groupedChanges),
67 |     };
68 | 
69 |     const normalizedDiffData = normalizePathsInObject(diffData, workspaceRoot);
70 | 
71 |     const effectiveSaveLocation = resolveCrossPlatformPath(cwd, saveLocation);
72 | 
73 |     const { dirname } = await import('node:path');
74 |     await mkdir(dirname(effectiveSaveLocation), { recursive: true });
75 | 
76 |     const diffFilePath = effectiveSaveLocation;
77 | 
78 |     const formattedJson = JSON.stringify(normalizedDiffData, null, 2);
79 |     await writeFile(diffFilePath, formattedJson, 'utf-8');
80 | 
81 |     return {
82 |       fileUrl: `file://${diffFilePath}`,
83 |       domPathStats: domPathDict.stats,
84 |     };
85 |   },
86 |   (result) => {
87 |     return [result.fileUrl];
88 |   },
89 | );
90 | 
91 | export const diffComponentContractTools = [
92 |   {
93 |     schema: diffComponentContractSchema,
94 |     handler: diffComponentContractHandler,
95 |   },
96 | ];
97 | 
```

--------------------------------------------------------------------------------
/packages/shared/ds-component-coverage/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@push-based/ds-component-coverage",
 3 |   "version": "0.0.1",
 4 |   "private": true,
 5 |   "type": "module",
 6 |   "main": "./dist/index.js",
 7 |   "module": "./dist/index.js",
 8 |   "types": "./dist/index.d.ts",
 9 |   "exports": {
10 |     "./package.json": "./package.json",
11 |     ".": {
12 |       "development": "./src/index.ts",
13 |       "types": "./dist/index.d.ts",
14 |       "import": "./dist/index.js",
15 |       "default": "./dist/index.js"
16 |     }
17 |   },
18 |   "nx": {
19 |     "name": "ds-component-coverage",
20 |     "targets": {
21 |       "ds-component-coverage:demo": {
22 |         "command": "npx @code-pushup/cli collect --config=packages/shared/ds-component-coverage/mocks/fixtures/e2e/demo/code-pushup.config.ts",
23 |         "options": {
24 |           "onlyPlugins": "ds-component-coverage",
25 |           "progress": false
26 |         }
27 |       },
28 |       "ds-component-coverage:asset-location": {
29 |         "command": "npx @code-pushup/cli collect --config=packages/ds-component-coverage/mocks/fixtures/e2e/asset-location/code-pushup.config.ts",
30 |         "options": {
31 |           "onlyPlugins": "ds-component-coverage",
32 |           "progress": false
33 |         }
34 |       },
35 |       "ds-component-coverage:line-number": {
36 |         "command": "npx @code-pushup/cli collect --config=packages/ds-component-coverage/mocks/fixtures/e2e/line-number/code-pushup.config.ts",
37 |         "options": {
38 |           "onlyPlugins": "ds-component-coverage",
39 |           "progress": false
40 |         }
41 |       },
42 |       "ds-component-coverage:style-format": {
43 |         "command": "npx @code-pushup/cli collect --config=packages/ds-component-coverage/mocks/fixtures/e2e/style-format/code-pushup.config.ts",
44 |         "options": {
45 |           "onlyPlugins": "ds-component-coverage",
46 |           "progress": false
47 |         }
48 |       },
49 |       "ds-component-coverage:template-syntax": {
50 |         "command": "npx @code-pushup/cli collect --config=packages/ds-component-coverage/mocks/fixtures/e2e/template-syntax/code-pushup.config.ts",
51 |         "options": {
52 |           "onlyPlugins": "ds-component-coverage",
53 |           "progress": false
54 |         }
55 |       },
56 |       "ds-quality:demo": {
57 |         "command": "npx @code-pushup/cli collect --config=packages/ds-quality/mocks/fixtures/minimal-design-system/code-pushup.config.ts",
58 |         "options": {
59 |           "onlyPlugins": "ds-quality",
60 |           "progress": false
61 |         }
62 |       },
63 |       "ds-quality:variable-usage": {
64 |         "command": "npx @code-pushup/cli collect --config=packages/ds-quality/mocks/fixtures/variable-usage/code-pushup.config.ts",
65 |         "options": {
66 |           "onlyPlugins": "ds-quality",
67 |           "progress": false
68 |         }
69 |       },
70 |       "ds-quality:mixin-usage": {
71 |         "command": "npx @code-pushup/cli collect --config=packages/ds-quality/mocks/fixtures/mixin-usage/code-pushup.config.ts",
72 |         "options": {
73 |           "onlyPlugins": "ds-quality",
74 |           "progress": false
75 |         }
76 |       }
77 |     }
78 |   }
79 | }
80 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/project/report-deprecated-css.tool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ToolSchemaOptions } from '@push-based/models';
 2 | import * as path from 'node:path';
 3 | import {
 4 |   createHandler,
 5 |   BaseHandlerOptions,
 6 |   RESULT_FORMATTERS,
 7 | } from '../shared/utils/handler-helpers.js';
 8 | import {
 9 |   createDirectoryComponentSchema,
10 |   COMMON_ANNOTATIONS,
11 | } from '../shared/models/schema-helpers.js';
12 | import { getDeprecatedCssClasses } from '../component/utils/deprecated-css-helpers.js';
13 | import {
14 |   findStyleFiles,
15 |   analyzeStyleFile,
16 | } from './utils/styles-report-helpers.js';
17 | import { resolveCrossPlatformPath } from '../shared/utils/cross-platform-path.js';
18 | 
19 | interface ReportDeprecatedCssOptions extends BaseHandlerOptions {
20 |   directory: string;
21 |   componentName: string;
22 |   deprecatedCssClassesPath?: string;
23 | }
24 | 
25 | export const reportDeprecatedCssSchema: ToolSchemaOptions = {
26 |   name: 'report-deprecated-css',
27 |   description: `Report deprecated CSS classes found in styling files in a directory.`,
28 |   inputSchema: createDirectoryComponentSchema(
29 |     'The class name of the component to get deprecated classes for (e.g., DsButton)',
30 |   ),
31 |   annotations: {
32 |     title: 'Report Deprecated CSS',
33 |     ...COMMON_ANNOTATIONS.readOnly,
34 |   },
35 | };
36 | 
37 | export const reportDeprecatedCssHandler = createHandler<
38 |   ReportDeprecatedCssOptions,
39 |   string[]
40 | >(
41 |   reportDeprecatedCssSchema.name,
42 |   async (params, { cwd, deprecatedCssClassesPath }) => {
43 |     const { directory, componentName } = params;
44 | 
45 |     if (!deprecatedCssClassesPath) {
46 |       throw new Error(
47 |         'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.',
48 |       );
49 |     }
50 | 
51 |     const deprecated = await getDeprecatedCssClasses(
52 |       componentName,
53 |       deprecatedCssClassesPath,
54 |       cwd,
55 |     );
56 | 
57 |     if (!deprecated.length) {
58 |       return [`No deprecated CSS classes defined for ${componentName}`];
59 |     }
60 | 
61 |     const styleFiles = await findStyleFiles(
62 |       resolveCrossPlatformPath(cwd, directory),
63 |     );
64 | 
65 |     if (!styleFiles.length) {
66 |       return [`No styling files found in ${directory}`];
67 |     }
68 | 
69 |     const results = await Promise.all(
70 |       styleFiles.map((f) => analyzeStyleFile(f, deprecated)),
71 |     );
72 | 
73 |     const violations: string[] = [];
74 | 
75 |     for (const { filePath, foundClasses } of results) {
76 |       if (!foundClasses.length) continue;
77 | 
78 |       const relativePath = path.relative(cwd, filePath);
79 | 
80 |       for (const { className, lineNumber } of foundClasses) {
81 |         const lineInfo = lineNumber ? ` (line ${lineNumber})` : '';
82 |         violations.push(
83 |           `${relativePath}${lineInfo}: The selector's class \`${className}\` is deprecated.`,
84 |         );
85 |       }
86 |     }
87 | 
88 |     return violations.length ? violations : ['No deprecated CSS classes found'];
89 |   },
90 |   (result) => RESULT_FORMATTERS.list(result, 'Design System CSS Violations:'),
91 | );
92 | 
93 | export const reportDeprecatedCssTools = [
94 |   {
95 |     schema: reportDeprecatedCssSchema,
96 |     handler: reportDeprecatedCssHandler,
97 |   },
98 | ];
99 | 
```

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

```typescript
  1 | import * as fs from 'fs';
  2 | import * as path from 'path';
  3 | import {
  4 |   validateComponentName,
  5 |   componentNameToKebabCase,
  6 | } from '../../shared/utils/component-validation.js';
  7 | import { resolveCrossPlatformPath } from '../../shared/utils/cross-platform-path.js';
  8 | 
  9 | export interface ComponentPaths {
 10 |   componentName: string;
 11 |   folderSlug: string;
 12 |   srcPath: string;
 13 |   packageJsonPath: string;
 14 | }
 15 | 
 16 | export interface ComponentPathsInfo {
 17 |   srcPath: string;
 18 |   importPath: string;
 19 |   relativeSrcPath: string;
 20 | }
 21 | 
 22 | export function getComponentPaths(
 23 |   uiRoot: string,
 24 |   componentName: string,
 25 | ): ComponentPaths {
 26 |   const folderSlug = componentNameToKebabCase(componentName);
 27 |   const componentFolder = path.join(uiRoot, folderSlug);
 28 |   const srcPath = path.join(componentFolder, 'src');
 29 |   const packageJsonPath = path.join(componentFolder, 'package.json');
 30 | 
 31 |   return {
 32 |     componentName,
 33 |     folderSlug,
 34 |     srcPath,
 35 |     packageJsonPath,
 36 |   };
 37 | }
 38 | 
 39 | export function getImportPathFromPackageJson(
 40 |   packageJsonPath: string,
 41 | ): string | null {
 42 |   try {
 43 |     if (!fs.existsSync(packageJsonPath)) {
 44 |       return null;
 45 |     }
 46 | 
 47 |     const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
 48 |     return packageJson.name || null;
 49 |   } catch (ctx) {
 50 |     throw new Error(
 51 |       `Error reading import path from package.json: ${(ctx as Error).message}`,
 52 |     );
 53 |   }
 54 | }
 55 | 
 56 | /**
 57 |  * Reusable helper function to get component paths information
 58 |  * @param componentName - The class name of the component (e.g., DsBadge)
 59 |  * @param uiRoot - The UI root directory path
 60 |  * @param cwd - Current working directory (optional, defaults to process.cwd())
 61 |  * @returns Object containing source path and import path information
 62 |  */
 63 | export function getComponentPathsInfo(
 64 |   componentName: string,
 65 |   uiRoot: string,
 66 |   cwd: string = process.cwd(),
 67 | ): ComponentPathsInfo {
 68 |   try {
 69 |     validateComponentName(componentName);
 70 | 
 71 |     if (!uiRoot || typeof uiRoot !== 'string') {
 72 |       throw new Error('uiRoot must be provided and be a string path.');
 73 |     }
 74 | 
 75 |     const componentsBasePath = resolveCrossPlatformPath(cwd, uiRoot);
 76 |     const componentPaths = getComponentPaths(componentsBasePath, componentName);
 77 | 
 78 |     const relativeComponentPaths = getComponentPaths(uiRoot, componentName);
 79 | 
 80 |     if (!fs.existsSync(componentPaths.srcPath)) {
 81 |       throw new Error(
 82 |         `Component source directory not found: ${relativeComponentPaths.srcPath}`,
 83 |       );
 84 |     }
 85 | 
 86 |     const importPath = getImportPathFromPackageJson(
 87 |       componentPaths.packageJsonPath,
 88 |     );
 89 | 
 90 |     if (!importPath) {
 91 |       throw new Error(
 92 |         `Could not read import path from package.json for component: ${componentName}`,
 93 |       );
 94 |     }
 95 | 
 96 |     return {
 97 |       srcPath: componentPaths.srcPath,
 98 |       importPath,
 99 |       relativeSrcPath: relativeComponentPaths.srcPath,
100 |     };
101 |   } catch (ctx) {
102 |     throw new Error(
103 |       `Error retrieving component information: ${(ctx as Error).message}`,
104 |     );
105 |   }
106 | }
107 | 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/design-system/storybook-host-app/src/components/modal/modal-tabs/overview.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | You are tasked with creating a comprehensive refactoring plan for migrating legacy markup to a single design-system component. This plan should cover templates, class TypeScript files, styles, NgModules, and specs surfaced by the usage graph. Follow these instructions carefully to generate the plan:
 2 | 
 3 | 1. Review the following input variables:
 4 | <component_name>
 5 | {{COMPONENT_NAME}}
 6 | </component_name>
 7 | 
 8 | <subfolder>
 9 | {{SUBFOLDER}}
10 | </subfolder>
11 | 
12 | <violations>
13 | {{VIOLATIONS}}
14 | </violations>
15 | 
16 | <scan_result>
17 | {{SCAN_RESULT}}
18 | </scan_result>
19 | 
20 | <file_scan>
21 | {{FILE_SCAN}}
22 | </file_scan>
23 | 
24 | 2. Acquire reference material:
25 |    a. Call the function get-component-docs with the component name as the argument.
26 |    b. If there's an error or no docs are found, output a commentary message and stop.
27 |    c. Parse the docs to create a dsExemplar object containing markup, required parts, optional parts, and API information.
28 | 
29 | 3. Map dependencies and impact:
30 |    a. Call the function build-component-usage-graph with the subfolder as the argument.
31 |    b. If there's an error or any violation file is missing from the graph, output a commentary message and stop.
32 |    c. Derive working sets for host templates, classes, styles, specs, and modules.
33 | 
34 | 4. Generate baseline contracts:
35 |    a. For each host template, call the build_component_contract function.
36 |    b. If there's a contract build error, output a commentary message and stop.
37 | 
38 | 5. Analyze refactorability:
39 |    a. Compare each violation instance with the dsExemplar markup.
40 |    b. Classify as "non-viable", "requires-restructure", or "simple-swap".
41 |    c. Record template edits, CSS clean-up, and ancillary edits.
42 |    d. Calculate complexity scores.
43 |    e. Aggregate results into filePlans.
44 | 
45 | 6. Synthesize and output the plan:
46 |    a. Sort filePlans by complexity score, violation count, and file path.
47 |    b. Generate the plan in the following format:
48 | 
49 | <plan>
50 | [For each file in filePlans, include:
51 | - File path
52 | - File type (template|class|style|spec|module)
53 | - Refactor class (non-viable|requires-restructure|simple-swap)
54 | - Actions to take (bullet points for template edits, TS updates, style removal, NgModule changes, spec tweaks)
55 | - Complexity score]
56 | </plan>
57 | 
58 | 7. After the plan, ask the following question:
59 | 🛠️ Approve this plan or specify adjustments?
60 | 
61 | Important reminders:
62 | - Maintain NgModule-based structure (no stand-alone conversion).
63 | - Generate contracts only for host components (template + class).
64 | - Output must be minimal: a single <plan> block followed by one question.
65 | - All other messages or errors should be enclosed in <commentary> tags.
66 | - Ensure every host module needing a new import appears in filePlans.
67 | - Ensure every host spec appears in filePlans (even if action="none").
68 | - Verify that dsExemplar was referenced at least once.
69 | 
70 | Your final output should consist of only the <plan> block and the follow-up question. Any additional comments or error messages should be enclosed in <commentary> tags.
```

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

```typescript
  1 | import {
  2 |   createHandler,
  3 |   BaseHandlerOptions,
  4 | } from '../../shared/utils/handler-helpers.js';
  5 | import { buildComponentContractSchema } from './models/schema.js';
  6 | import { buildComponentContract } from './utils/build-contract.js';
  7 | import { generateContractSummary } from '../shared/utils/contract-file-ops.js';
  8 | import { ContractResult } from './models/types.js';
  9 | import { resolveCrossPlatformPath } from '../../shared/utils/cross-platform-path.js';
 10 | import { createHash } from 'node:crypto';
 11 | 
 12 | interface BuildComponentContractOptions extends BaseHandlerOptions {
 13 |   saveLocation: string;
 14 |   templateFile?: string;
 15 |   styleFile?: string;
 16 |   typescriptFile: string;
 17 |   dsComponentName?: string;
 18 | }
 19 | 
 20 | export const buildComponentContractHandler = createHandler<
 21 |   BuildComponentContractOptions,
 22 |   ContractResult
 23 | >(
 24 |   buildComponentContractSchema.name,
 25 |   async (params, { cwd, workspaceRoot: _workspaceRoot }) => {
 26 |     const {
 27 |       saveLocation,
 28 |       templateFile,
 29 |       styleFile,
 30 |       typescriptFile,
 31 |       dsComponentName = '',
 32 |     } = params;
 33 | 
 34 |     const effectiveTypescriptPath = resolveCrossPlatformPath(
 35 |       cwd,
 36 |       typescriptFile,
 37 |     );
 38 | 
 39 |     // If templateFile or styleFile are not provided, use the TypeScript file path
 40 |     // This indicates inline template/styles
 41 |     const effectiveTemplatePath = templateFile
 42 |       ? resolveCrossPlatformPath(cwd, templateFile)
 43 |       : effectiveTypescriptPath;
 44 |     const effectiveScssPath = styleFile
 45 |       ? resolveCrossPlatformPath(cwd, styleFile)
 46 |       : effectiveTypescriptPath;
 47 | 
 48 |     const contract = await buildComponentContract(
 49 |       effectiveTemplatePath,
 50 |       effectiveScssPath,
 51 |       cwd,
 52 |       effectiveTypescriptPath,
 53 |     );
 54 | 
 55 |     const contractString = JSON.stringify(contract, null, 2);
 56 |     const hash = createHash('sha256').update(contractString).digest('hex');
 57 | 
 58 |     const effectiveSaveLocation = resolveCrossPlatformPath(cwd, saveLocation);
 59 | 
 60 |     const { mkdir, writeFile } = await import('node:fs/promises');
 61 |     const { dirname } = await import('node:path');
 62 |     await mkdir(dirname(effectiveSaveLocation), { recursive: true });
 63 | 
 64 |     const contractData = {
 65 |       contract,
 66 |       hash: `sha256-${hash}`,
 67 |       metadata: {
 68 |         templatePath: effectiveTemplatePath,
 69 |         scssPath: effectiveScssPath,
 70 |         typescriptPath: effectiveTypescriptPath,
 71 |         timestamp: new Date().toISOString(),
 72 |         dsComponentName,
 73 |       },
 74 |     };
 75 | 
 76 |     await writeFile(
 77 |       effectiveSaveLocation,
 78 |       JSON.stringify(contractData, null, 2),
 79 |       'utf-8',
 80 |     );
 81 | 
 82 |     const contractFilePath = effectiveSaveLocation;
 83 | 
 84 |     return {
 85 |       contract,
 86 |       hash: `sha256-${hash}`,
 87 |       contractFilePath,
 88 |     };
 89 |   },
 90 |   (result) => {
 91 |     const summary = generateContractSummary(result.contract);
 92 |     return [
 93 |       `✅ Contract Hash: ${result.hash}`,
 94 |       `📁 Saved to: ${result.contractFilePath}`,
 95 |       ...summary,
 96 |     ];
 97 |   },
 98 | );
 99 | 
100 | export const buildComponentContractTools = [
101 |   {
102 |     schema: buildComponentContractSchema,
103 |     handler: buildComponentContractHandler,
104 |   },
105 | ];
106 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp/src/main.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import express from 'express';
  3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
  4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | import yargs from 'yargs';
  6 | import { hideBin } from 'yargs/helpers';
  7 | import { AngularMcpServerWrapper } from '@push-based/angular-mcp-server';
  8 | 
  9 | interface ArgvType {
 10 |   sse: boolean;
 11 |   port?: number;
 12 |   _: (string | number)[];
 13 |   $0: string;
 14 | 
 15 |   [x: string]: unknown;
 16 | }
 17 | 
 18 | const argv = yargs(hideBin(process.argv))
 19 |   .command('$0', 'Start the angular-mcp server')
 20 |   .option('workspaceRoot', {
 21 |     describe: 'The root directory of the workspace as absolute path',
 22 |     type: 'string',
 23 |     required: true,
 24 |   })
 25 |   .option('ds.storybookDocsRoot', {
 26 |     describe:
 27 |       'The root directory of the storybook docs relative from workspace root',
 28 |     type: 'string',
 29 |   })
 30 |   .option('ds.deprecatedCssClassesPath', {
 31 |     describe:
 32 |       'The path to the deprecated classes file relative from workspace root',
 33 |     type: 'string',
 34 |   })
 35 |   .option('ds.uiRoot', {
 36 |     describe:
 37 |       'The root directory of the actual Angular components relative from workspace root',
 38 |     type: 'string',
 39 |   })
 40 |   .option('sse', {
 41 |     describe: 'Configure the server to use SSE (Server-Sent Events)',
 42 |     type: 'boolean',
 43 |     default: false,
 44 |   })
 45 |   .option('port', {
 46 |     alias: 'p',
 47 |     describe: 'Port to use for the SSE server (default: 9921)',
 48 |     type: 'number',
 49 |   })
 50 |   .check((argv) => {
 51 |     if (argv.port !== undefined && !argv.sse) {
 52 |       throw new Error(
 53 |         'The --port option can only be used when --sse is enabled',
 54 |       );
 55 |     }
 56 |     return true;
 57 |   })
 58 |   .help()
 59 |   .parseSync() as ArgvType;
 60 | 
 61 | const { workspaceRoot, ds } = argv as unknown as {
 62 |   workspaceRoot: string;
 63 |   ds: {
 64 |     storybookDocsRoot?: string;
 65 |     deprecatedCssClassesPath?: string;
 66 |     uiRoot: string;
 67 |   };
 68 | };
 69 | const { storybookDocsRoot, deprecatedCssClassesPath, uiRoot } = ds;
 70 | 
 71 | async function startServer() {
 72 |   const server = await AngularMcpServerWrapper.create({
 73 |     workspaceRoot: workspaceRoot as string,
 74 |     ds: {
 75 |       storybookDocsRoot,
 76 |       deprecatedCssClassesPath,
 77 |       uiRoot,
 78 |     },
 79 |   });
 80 | 
 81 |   if (argv.sse) {
 82 |     const port = argv.port ?? 9921;
 83 | 
 84 |     const app = express();
 85 |     let transport: SSEServerTransport;
 86 |     app.get('/sse', async (_, res) => {
 87 |       transport = new SSEServerTransport('/messages', res);
 88 |       await server.getMcpServer().connect(transport);
 89 |     });
 90 | 
 91 |     app.post('/messages', async (req, res) => {
 92 |       if (!transport) {
 93 |         res.status(400).send('No transport found');
 94 |         return;
 95 |       }
 96 |       await transport.handlePostMessage(req, res);
 97 |     });
 98 | 
 99 |     const server_instance = app.listen(port);
100 | 
101 |     process.on('exit', () => {
102 |       server_instance.close();
103 |     });
104 |   } else {
105 |     const transport = new StdioServerTransport();
106 |     server.getMcpServer().connect(transport);
107 |   }
108 | }
109 | 
110 | // eslint-disable-next-line unicorn/prefer-top-level-await
111 | startServer().catch((ctx) => {
112 |   console.error('Failed to start server:', ctx);
113 |   process.exit(1);
114 | });
115 | 
```

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

```typescript
 1 | const AGNOSTIC_PATH_SEP_REGEX = /[/\\]/g;
 2 | const OS_AGNOSTIC_PATH_SEP = '/';
 3 | const OS_AGNOSTIC_CWD = `<CWD>`;
 4 | 
 5 | /**
 6 |  * Converts a given file path to an OS-agnostic path by replacing the current working directory with '<CWD>'
 7 |  * and normalizing path separators to '/'.
 8 |  *
 9 |  * @param filePath - The file path to be converted.
10 |  * @param separator - The path separator to use for normalization. Defaults to the OS-specific separator.
11 |  * @returns The OS-agnostic path.
12 |  *
13 |  * @example
14 |  *
15 |  * At CWD on Ubuntu (Linux)
16 |  * Input: /home/projects/my-folder/my-file.ts
17 |  * Output: <CWD>/my-folder/my-file.ts
18 |  *
19 |  * At CWD on Windows
20 |  * Input: D:\projects\my-folder\my-file.ts
21 |  * Output: <CWD>/my-folder/my-file.ts
22 |  *
23 |  * At CWD on macOS
24 |  * Input: /Users/projects/my-folder/my-file.ts
25 |  * Output: <CWD>/my-folder/my-file.ts
26 |  *
27 |  * Out of CWD on all OS
28 |  * Input: /Users/projects/../my-folder/my-file.ts
29 |  * Output: ../my-folder/my-file.ts
30 |  *
31 |  * Absolute paths (all OS)
32 |  * Input: \\my-folder\\my-file.ts
33 |  * Output: /my-folder/my-file.ts
34 |  *
35 |  * Relative paths (all OS)
36 |  * Input: ..\\my-folder\\my-file.ts
37 |  * Output: ../my-folder/my-file.ts
38 |  *
39 |  */
40 | 
41 | export function osAgnosticPath(filePath: undefined): undefined;
42 | export function osAgnosticPath(filePath: string): string;
43 | export function osAgnosticPath(filePath?: string): string | undefined {
44 |   if (filePath == null) {
45 |     return filePath;
46 |   }
47 |   // prepare the path for comparison
48 |   // normalize path separators od cwd: "Users\\repo" => "Users/repo"
49 |   const osAgnosticCwd = process
50 |     .cwd()
51 |     .split(AGNOSTIC_PATH_SEP_REGEX)
52 |     .join(OS_AGNOSTIC_PATH_SEP);
53 |   // normalize path separators  => "..\\folder\\repo.ts" => => "../folder/repo.ts"
54 |   const osAgnosticFilePath = filePath
55 |     .split(AGNOSTIC_PATH_SEP_REGEX)
56 |     .join(OS_AGNOSTIC_PATH_SEP);
57 |   // remove the current working directory for easier comparison
58 |   const osAgnosticPathWithoutCwd = osAgnosticFilePath
59 |     .replace(osAgnosticCwd, '')
60 |     // consider already agnostic paths
61 |     .replace(OS_AGNOSTIC_CWD, '');
62 | 
63 |   // path is outside cwd (Users/repo/../my-folder/my-file.ts)
64 |   if (
65 |     osAgnosticPathWithoutCwd.startsWith(
66 |       `${OS_AGNOSTIC_PATH_SEP}..${OS_AGNOSTIC_PATH_SEP}`,
67 |     )
68 |   ) {
69 |     return osAgnosticPathWithoutCwd.slice(1); // remove the leading '/'
70 |   }
71 | 
72 |   // path is at cwd (Users/repo/my-folder/my-file.ts)
73 |   if (
74 |     osAgnosticFilePath.startsWith(osAgnosticCwd) ||
75 |     osAgnosticFilePath.startsWith(OS_AGNOSTIC_CWD)
76 |   ) {
77 |     // Add a substitute for the current working directory
78 |     return `${OS_AGNOSTIC_CWD}${osAgnosticPathWithoutCwd}`;
79 |   }
80 | 
81 |   // Notice: I kept the following conditions for documentation purposes
82 | 
83 |   // path is absolute (/my-folder/my-file.ts)
84 |   if (osAgnosticPathWithoutCwd.startsWith(OS_AGNOSTIC_PATH_SEP)) {
85 |     return osAgnosticPathWithoutCwd;
86 |   }
87 | 
88 |   // path is relative (./my-folder/my-file.ts)
89 |   if (osAgnosticPathWithoutCwd.startsWith(`.${OS_AGNOSTIC_PATH_SEP}`)) {
90 |     return osAgnosticPathWithoutCwd;
91 |   }
92 | 
93 |   // path is segment (my-folder/my-file.ts or my-folder/sub-folder)
94 |   return osAgnosticPathWithoutCwd;
95 | }
96 | 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/design-system/storybook-host-app/src/components/segmented-control/segmented-control-tabs/api.mdx:
--------------------------------------------------------------------------------

```markdown
 1 | ## Inputs
 2 | 
 3 | ### DsSegmentedControl
 4 | 
 5 | | Name                | Type                          | Default     | Description                                                   |
 6 | | ------------------- | ----------------------------- | ----------- | ------------------------------------------------------------- |
 7 | | `activeOption`      | `string`                      | `''`        | Name of the currently selected option.                        |
 8 | | `fullWidth`         | `boolean`                     | `false`     | Whether the segmented control takes the full container width. |
 9 | | `inverse`           | `boolean`                     | `false`     | Applies inverse color scheme to the component.                |
10 | | `roleType`          | `'radiogroup'` \| `'tablist'` | `'tablist'` | Sets the ARIA role of the segmented control group.            |
11 | | `twoLineTruncation` | `boolean`                     | `false`     | Enables two-line truncation for long labels.                  |
12 | 
13 | ### DsSegmentedOption
14 | 
15 | | Name    | Type     | Required | Default | Description                     |
16 | | ------- | -------- | -------- | ------- | ------------------------------- |
17 | | `name`  | `string` | Yes      | —       | Unique name for the option.     |
18 | | `title` | `string` | No       | `''`    | Displayed label for the option. |
19 | 
20 | <sup>**1**</sup> **activeOption note:** **name** needs to be added to segmented
21 | option to have possibility to define selected item
22 | 
23 | ---
24 | 
25 | ## Outputs
26 | 
27 | ### DsSegmentedControl
28 | 
29 | | Name                 | Type                   | Description                                   |
30 | | -------------------- | ---------------------- | --------------------------------------------- |
31 | | `activeOptionChange` | `EventEmitter<string>` | Emits the new selected option name on change. |
32 | 
33 | ```json
34 | {
35 |   "index": 2,
36 |   "event": {
37 |     "isTrusted": true,
38 |     "altKey": false,
39 |     "altitudeAngle": 1.5707963267948966,
40 |     "azimuthAngle": 0,
41 |     "bubbles": true,
42 |     "button": 0,
43 |     "buttons": 0,
44 |     "cancelBubble": false,
45 |     "cancelable": true,
46 |     "clientX": 433,
47 |     "clientY": 85,
48 |     "composed": true,
49 |     "ctrlKey": false
50 |   }
51 | }
52 | ```
53 | 
54 | ### DsSegmentedOption
55 | 
56 | | Name           | Type                   | Description                        |
57 | | -------------- | ---------------------- | ---------------------------------- |
58 | | `selectOption` | `EventEmitter<string>` | Emits when the option is selected. |
59 | 
60 | ---
61 | 
62 | ## Content Projection
63 | 
64 | | Selector      | Description                                |
65 | | ------------- | ------------------------------------------ |
66 | | `#dsTemplate` | A `TemplateRef` for custom option content. |
67 | 
68 | ---
69 | 
70 | ## Host Bindings
71 | 
72 | ### DsSegmentedControl
73 | 
74 | - `class="ds-segmented-control"` — base class
75 | - `class.ds-segment-full-width` — applied when `fullWidth` is true
76 | - `class.ds-segment-inverse` — applied when `inverse` is true
77 | - `class.ds-sc-ready` — applied after view initialization
78 | - `role` — set to `tablist` or `radiogroup` depending on `roleType`
79 | 
80 | ### DsSegmentedOption
81 | 
82 | - Focus state and tabindex are controlled dynamically.
83 | - Host uses `rxHostPressedListener` for click-based selection.
84 | 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-4/multi-violation-test.component.scss:
--------------------------------------------------------------------------------

```scss
  1 | /* Multi-violation test component styles using deprecated CSS classes */
  2 | 
  3 | .test-container {
  4 |   padding: 20px;
  5 |   max-width: 800px;
  6 |   margin: 0 auto;
  7 | }
  8 | 
  9 | /* ❌ BAD: DsButton deprecated styles - 'btn', 'btn-primary', 'legacy-button' */
 10 | .btn {
 11 |   display: inline-block;
 12 |   padding: 8px 16px;
 13 |   margin: 4px;
 14 |   border: 1px solid #ccc;
 15 |   border-radius: 4px;
 16 |   background-color: #f8f9fa;
 17 |   color: #333;
 18 |   cursor: pointer;
 19 |   font-size: 14px;
 20 |   text-decoration: none;
 21 |   
 22 |   &:hover {
 23 |     background-color: #e9ecef;
 24 |   }
 25 | }
 26 | 
 27 | .btn-primary {
 28 |   background-color: #007bff;
 29 |   border-color: #007bff;
 30 |   color: white;
 31 |   
 32 |   &:hover {
 33 |     background-color: #0056b3;
 34 |   }
 35 | }
 36 | 
 37 | .legacy-button {
 38 |   background: linear-gradient(45deg, #ff6b6b, #feca57);
 39 |   border: none;
 40 |   padding: 10px 20px;
 41 |   color: white;
 42 |   border-radius: 8px;
 43 |   cursor: pointer;
 44 |   font-weight: bold;
 45 |   margin: 4px;
 46 |   
 47 |   &:hover {
 48 |     transform: translateY(-2px);
 49 |     box-shadow: 0 4px 8px rgba(0,0,0,0.2);
 50 |   }
 51 | }
 52 | 
 53 | /* ❌ BAD: DsBadge deprecated styles - 'offer-badge' */
 54 | .offer-badge {
 55 |   background-color: #ff4757;
 56 |   color: white;
 57 |   padding: 4px 8px;
 58 |   border-radius: 12px;
 59 |   font-size: 12px;
 60 |   font-weight: bold;
 61 |   text-transform: uppercase;
 62 |   margin-left: 8px;
 63 |   display: inline-block;
 64 |   
 65 |   &:before {
 66 |     content: "🔥 ";
 67 |   }
 68 | }
 69 | 
 70 | /* ❌ BAD: DsTabsModule deprecated styles - 'tab-nav', 'nav-tabs', 'tab-nav-item' */
 71 | .nav-tabs {
 72 |   display: flex;
 73 |   list-style: none;
 74 |   padding: 0;
 75 |   margin: 0;
 76 |   border-bottom: 2px solid #dee2e6;
 77 | }
 78 | 
 79 | .tab-nav {
 80 |   background-color: #f8f9fa;
 81 |   border-radius: 4px 4px 0 0;
 82 | }
 83 | 
 84 | .tab-nav-item {
 85 |   padding: 12px 16px;
 86 |   cursor: pointer;
 87 |   border: 1px solid transparent;
 88 |   border-bottom: none;
 89 |   background-color: #f8f9fa;
 90 |   color: #495057;
 91 |   
 92 |   &:hover {
 93 |     background-color: #e9ecef;
 94 |   }
 95 |   
 96 |   &.active {
 97 |     background-color: white;
 98 |     border-color: #dee2e6;
 99 |     color: #007bff;
100 |     border-bottom: 2px solid white;
101 |     margin-bottom: -2px;
102 |   }
103 | }
104 | 
105 | /* ❌ BAD: DsCard deprecated styles - 'card' */
106 | .card {
107 |   border: 1px solid #dee2e6;
108 |   border-radius: 8px;
109 |   background-color: white;
110 |   box-shadow: 0 2px 4px rgba(0,0,0,0.1);
111 |   margin: 16px 0;
112 |   overflow: hidden;
113 | }
114 | 
115 | .card-header {
116 |   padding: 16px;
117 |   background-color: #f8f9fa;
118 |   border-bottom: 1px solid #dee2e6;
119 |   display: flex;
120 |   justify-content: space-between;
121 |   align-items: center;
122 |   
123 |   h4 {
124 |     margin: 0;
125 |     color: #495057;
126 |   }
127 | }
128 | 
129 | .card-body {
130 |   padding: 16px;
131 |   
132 |   p {
133 |     margin: 0 0 12px 0;
134 |     color: #6c757d;
135 |   }
136 | }
137 | 
138 | .card-footer {
139 |   padding: 16px;
140 |   background-color: #f8f9fa;
141 |   border-top: 1px solid #dee2e6;
142 |   text-align: right;
143 | }
144 | 
145 | /* Section styling */
146 | .button-section,
147 | .badge-section,
148 | .tabs-section,
149 | .card-section,
150 | .mixed-section {
151 |   margin-bottom: 32px;
152 |   
153 |   h3 {
154 |     color: #495057;
155 |     border-bottom: 1px solid #dee2e6;
156 |     padding-bottom: 8px;
157 |     margin-bottom: 16px;
158 |   }
159 | }
160 | 
161 | .product-item {
162 |   display: flex;
163 |   align-items: center;
164 |   justify-content: space-between;
165 |   padding: 8px;
166 |   border: 1px solid #dee2e6;
167 |   border-radius: 4px;
168 |   margin: 8px 0;
169 |   background-color: #f8f9fa;
170 | }
171 | 
172 | .tab-content {
173 |   padding: 20px;
174 |   border: 1px solid #dee2e6;
175 |   border-top: none;
176 |   background-color: white;
177 |   min-height: 100px;
178 | }
179 | 
```

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

```typescript
  1 | import { describe, it, expect } from 'vitest';
  2 | 
  3 | import ts from 'typescript';
  4 | 
  5 | import {
  6 |   extractPublicMethods,
  7 |   extractLifecycleHooks,
  8 |   extractImports,
  9 | } from '../utils/typescript-analyzer.js';
 10 | 
 11 | // -----------------------------------------------------------------------------
 12 | // Helpers
 13 | // -----------------------------------------------------------------------------
 14 | 
 15 | function parseSource(code: string, fileName = 'comp.ts') {
 16 |   return ts.createSourceFile(
 17 |     fileName,
 18 |     code,
 19 |     ts.ScriptTarget.Latest,
 20 |     true,
 21 |     ts.ScriptKind.TS,
 22 |   );
 23 | }
 24 | 
 25 | function getFirstClass(sourceFile: ts.SourceFile): ts.ClassDeclaration {
 26 |   const cls = sourceFile.statements.find(ts.isClassDeclaration);
 27 |   if (!cls) throw new Error('Class not found');
 28 |   return cls;
 29 | }
 30 | 
 31 | // -----------------------------------------------------------------------------
 32 | // Tests
 33 | // -----------------------------------------------------------------------------
 34 | 
 35 | describe('typescript-analyzer utilities', () => {
 36 |   it('extractPublicMethods returns only public non-lifecycle methods', () => {
 37 |     const code = `
 38 |       class MyComponent {
 39 |         foo(a: number): void {}
 40 |         private hidden() {}
 41 |         protected prot() {}
 42 |         ngOnInit() {}
 43 |         static util() {}
 44 |         async fetchData() {}
 45 |       }
 46 |     `;
 47 | 
 48 |     const sf = parseSource(code);
 49 |     const cls = getFirstClass(sf);
 50 | 
 51 |     const methods = extractPublicMethods(cls, sf);
 52 | 
 53 |     expect(methods).toHaveProperty('foo');
 54 |     expect(methods).toHaveProperty('util');
 55 |     expect(methods).toHaveProperty('fetchData');
 56 |     expect(methods).not.toHaveProperty('hidden');
 57 |     expect(methods).not.toHaveProperty('prot');
 58 |     expect(methods).not.toHaveProperty('ngOnInit');
 59 | 
 60 |     const foo = methods.foo;
 61 |     expect(foo.parameters[0]).toEqual(
 62 |       expect.objectContaining({ name: 'a', type: 'number' }),
 63 |     );
 64 |     expect(foo.isStatic).toBe(false);
 65 |     expect(foo.isAsync).toBe(false);
 66 | 
 67 |     expect(methods.util.isStatic).toBe(true);
 68 |     expect(methods.fetchData.isAsync).toBe(true);
 69 |   });
 70 | 
 71 |   it('extractLifecycleHooks detects implemented and method-based hooks', () => {
 72 |     const code = `
 73 |       interface OnInit { ngOnInit(): void; }
 74 |       class MyComponent implements OnInit {
 75 |         ngOnInit() {}
 76 |         ngAfterViewInit() {}
 77 |         foo() {}
 78 |       }
 79 |     `;
 80 | 
 81 |     const sf = parseSource(code);
 82 |     const cls = getFirstClass(sf);
 83 | 
 84 |     const hooks = extractLifecycleHooks(cls);
 85 |     expect(hooks).toEqual(expect.arrayContaining(['OnInit', 'AfterViewInit']));
 86 |   });
 87 | 
 88 |   it('extractImports lists imported symbols with their paths', () => {
 89 |     const code = `
 90 |       import { HttpClient } from '@angular/common/http';
 91 |       import * as _ from 'lodash';
 92 |       import defaultExport from 'lib';
 93 |       import { MatButtonModule as MB } from '@angular/material/button';
 94 |     `;
 95 | 
 96 |     const sf = parseSource(code);
 97 |     const imports = extractImports(sf);
 98 | 
 99 |     expect(imports).toEqual(
100 |       expect.arrayContaining([
101 |         { name: 'HttpClient', path: '@angular/common/http' },
102 |         { name: '_', path: 'lodash' },
103 |         { name: 'defaultExport', path: 'lib' },
104 |         { name: 'MB', path: '@angular/material/button' },
105 |       ]),
106 |     );
107 |   });
108 | });
109 | 
```

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

```typescript
  1 | import { Rule } from 'postcss';
  2 | import { Issue } from '@code-pushup/models';
  3 | import { DiagnosticsAware } from '@push-based/models';
  4 | import {
  5 |   CssAstVisitor,
  6 |   styleAstRuleToSource,
  7 | } from '@push-based/styles-ast-utils';
  8 | 
  9 | import {
 10 |   EXTERNAL_ASSET_ICON,
 11 |   INLINE_ASSET_ICON,
 12 |   STYLES_ASSET_ICON,
 13 | } from './constants.js';
 14 | import { ComponentReplacement } from './schema.js';
 15 | 
 16 | export type ClassDefinitionVisitor = CssAstVisitor & DiagnosticsAware;
 17 | 
 18 | /**
 19 |  * Visits a `CssAstVisitor` that is `DiagnosticsAware`and collects the definition of deprecated class names.
 20 |  *
 21 |  * @example
 22 |  * const ast: Root = postcss.parse(`
 23 |  *   .btn {
 24 |  *     color: red;
 25 |  *   }
 26 |  * `);
 27 |  * const visitor = createClassDefinitionVisitor(ast, { deprecatedCssClasses: ['btn'] });
 28 |  * // The visitor will check each `Rule` definition for matching deprecateCssClasses
 29 |  * visitEachStyleNode(ast.nodes, visitor);
 30 |  *
 31 |  * // The visitor is `DiagnosticsAware` and xou can get the issues over a public API.
 32 |  * const issues: Issue & { coed?: number } = visitor.getIssues();
 33 |  *
 34 |  * // Subsequent usags will add to the issues.
 35 |  * // You can also clear the issues
 36 |  * visitor.clear();
 37 |  *
 38 |  * @param componentReplacement
 39 |  * @param startLine
 40 |  */
 41 | export const createClassDefinitionVisitor = (
 42 |   componentReplacement: ComponentReplacement,
 43 |   startLine = 0,
 44 | ): ClassDefinitionVisitor => {
 45 |   const { deprecatedCssClasses = [] } = componentReplacement;
 46 |   let diagnostics: Issue[] = [];
 47 | 
 48 |   return {
 49 |     getIssues(): Issue[] {
 50 |       return diagnostics;
 51 |     },
 52 | 
 53 |     clear(): void {
 54 |       diagnostics = [];
 55 |     },
 56 | 
 57 |     visitRule(rule: Rule) {
 58 |       const matchingClassNames = getMatchingClassNames(
 59 |         { selector: rule.selector },
 60 |         deprecatedCssClasses,
 61 |       );
 62 | 
 63 |       if (matchingClassNames.length > 0) {
 64 |         const message = classUsageMessage({
 65 |           className: matchingClassNames.join(', '),
 66 |           rule,
 67 |           componentName: componentReplacement.componentName,
 68 |           docsUrl: componentReplacement.docsUrl,
 69 |         });
 70 |         const isInline = rule.source?.input.file?.match(/\.ts$/) != null;
 71 |         diagnostics.push({
 72 |           message,
 73 |           severity: 'error',
 74 |           source: styleAstRuleToSource(rule, isInline ? startLine : 0),
 75 |         });
 76 |       }
 77 |     },
 78 |   };
 79 | };
 80 | 
 81 | function classUsageMessage({
 82 |   className,
 83 |   rule,
 84 |   componentName,
 85 |   docsUrl,
 86 | }: Pick<ComponentReplacement, 'componentName' | 'docsUrl'> & {
 87 |   className: string;
 88 |   rule: Rule;
 89 | }): string {
 90 |   const isInline = rule.source?.input.file?.match(/\.ts$/) != null;
 91 |   const iconString = `${
 92 |     isInline ? INLINE_ASSET_ICON : EXTERNAL_ASSET_ICON
 93 |   }${STYLES_ASSET_ICON}`;
 94 |   const docsLink = docsUrl
 95 |     ? ` <a href="${docsUrl}" target="_blank">Learn more</a>.`
 96 |     : '';
 97 |   return `${iconString}️ The selector's class <code>${className}</code> is deprecated. Use <code>${componentName}</code> and delete the styles.${docsLink}`;
 98 | }
 99 | 
100 | export function getMatchingClassNames(
101 |   { selector }: Pick<Rule, 'selector'>,
102 |   targetClassNames: string[],
103 | ): string[] {
104 |   const classNames = selector.match(/\.[\w-]+/g) || [];
105 |   return classNames
106 |     .map((className) => className.slice(1)) // Strip the leading "."
107 |     .filter((className) => targetClassNames.includes(className));
108 | }
109 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/component-contract/shared/models/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Component Contract Types
  3 |  */
  4 | 
  5 | export type TemplateType = 'inline' | 'external';
  6 | 
  7 | export interface ComponentContract {
  8 |   meta: Meta;
  9 |   publicApi: PublicApi;
 10 |   slots: Slots;
 11 |   dom: DomStructure;
 12 |   styles: StyleDeclarations;
 13 | }
 14 | 
 15 | export interface Meta {
 16 |   name: string;
 17 |   selector: string;
 18 |   sourceFile: string;
 19 |   templateType: TemplateType;
 20 |   generatedAt: string;
 21 |   hash: string;
 22 | }
 23 | 
 24 | export interface PublicApi {
 25 |   properties: Record<string, PropertyBinding>;
 26 |   events: Record<string, EventBinding>;
 27 |   methods: Record<string, MethodSignature>;
 28 |   lifecycle: string[];
 29 |   imports: ImportInfo[];
 30 | }
 31 | 
 32 | export interface PropertyBinding {
 33 |   type: string;
 34 |   isInput: boolean;
 35 |   required: boolean;
 36 |   transform?: string;
 37 | }
 38 | 
 39 | export interface EventBinding {
 40 |   type: string;
 41 | }
 42 | 
 43 | export interface MethodSignature {
 44 |   name: string;
 45 |   parameters: ParameterInfo[];
 46 |   returnType: string;
 47 |   isPublic: boolean;
 48 |   isStatic: boolean;
 49 |   isAsync: boolean;
 50 | }
 51 | 
 52 | export interface ParameterInfo {
 53 |   name: string;
 54 |   type: string;
 55 |   optional: boolean;
 56 |   defaultValue?: string;
 57 | }
 58 | 
 59 | export interface ImportInfo {
 60 |   name: string;
 61 |   path: string;
 62 | }
 63 | 
 64 | export interface Slots {
 65 |   [slotName: string]: {
 66 |     selector: string;
 67 |   };
 68 | }
 69 | 
 70 | /**
 71 |  * Flat DOM structure with CSS selector keys for efficient lookups and minimal diff noise
 72 |  */
 73 | export interface DomStructure {
 74 |   [selectorKey: string]: DomElement;
 75 | }
 76 | 
 77 | export interface DomElement {
 78 |   tag: string;
 79 |   parent: string | null;
 80 |   children: string[];
 81 |   bindings: Binding[];
 82 |   attributes: Attribute[];
 83 |   events: Event[];
 84 |   /**
 85 |    * Optional stack of active structural directives (Angular control-flow / template constructs)
 86 |    * that wrap this DOM element, preserving the order of nesting from outermost → innermost.
 87 |    * Kept optional so that existing contracts without this field remain valid.
 88 |    */
 89 |   structural?: StructuralDirectiveContext[];
 90 | }
 91 | 
 92 | export interface Binding {
 93 |   type: 'class' | 'style' | 'property' | 'attribute';
 94 |   name: string;
 95 |   source: string;
 96 | }
 97 | 
 98 | export interface Attribute {
 99 |   type: 'attribute';
100 |   name: string;
101 |   source: string;
102 | }
103 | 
104 | export interface Event {
105 |   name: string;
106 |   handler: string;
107 | }
108 | 
109 | /**
110 |  * Style declarations with explicit DOM relationships for property-level change detection
111 |  */
112 | export interface StyleDeclarations {
113 |   sourceFile: string;
114 |   rules: Record<string, StyleRule>;
115 | }
116 | 
117 | export interface StyleRule {
118 |   appliesTo: string[];
119 |   properties: Record<string, string>;
120 | }
121 | 
122 | /**
123 |  * DOM path deduplication utility for diff operations
124 |  */
125 | export interface DomPathDictionary {
126 |   paths: string[];
127 |   lookup: Map<string, number>;
128 |   stats: {
129 |     totalPaths: number;
130 |     uniquePaths: number;
131 |     duplicateReferences: number;
132 |     bytesBeforeDeduplication: number;
133 |     bytesAfterDeduplication: number;
134 |   };
135 | }
136 | 
137 | /**
138 |  * Captures context of Angular structural directives (v17 control-flow blocks and classic *ngX)
139 |  */
140 | export interface StructuralDirectiveContext {
141 |   kind: 'for' | 'if' | 'switch' | 'switchCase' | 'switchDefault' | 'defer';
142 |   /** Raw expression text (loop expression, condition…) when available */
143 |   expression?: string;
144 |   /** Optional alias / let-var (e.g., let-item) or trackBy identifier */
145 |   alias?: string;
146 |   /** Optional branch inside control-flow blocks (then/else, empty, case…). */
147 |   branch?: 'then' | 'else' | 'empty' | 'case' | 'default';
148 | }
149 | 
```

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

```markdown
 1 | # Public API — Quick Reference
 2 | 
 3 | | Symbol                 | Kind      | Summary                                            |
 4 | | ---------------------- | --------- | -------------------------------------------------- |
 5 | | `CssAstVisitor`        | interface | Visitor interface for traversing CSS AST nodes     |
 6 | | `NodeType`             | type      | Type mapping for visitor method parameters         |
 7 | | `parseStylesheet`      | function  | Parse CSS content and return PostCSS AST           |
 8 | | `styleAstRuleToSource` | function  | Convert CSS rule to linkable source location       |
 9 | | `stylesAstUtils`       | function  | Utility function (returns package identifier)      |
10 | | `visitEachChild`       | function  | Traverse AST calling visitor methods for each node |
11 | | `visitEachStyleNode`   | function  | Recursively visit CSS nodes with visitor pattern   |
12 | | `visitStyleSheet`      | function  | Visit top-level stylesheet nodes                   |
13 | 
14 | ## Interface Details
15 | 
16 | ### `CssAstVisitor<T = void>`
17 | 
18 | Visitor interface for processing different types of CSS AST nodes:
19 | 
20 | - `visitRoot?: (root: Container) => T` - Called once for the root node
21 | - `visitAtRule?: (atRule: AtRule) => T` - Called for @rule nodes (@media, @charset, etc.)
22 | - `visitRule?: (rule: Rule) => T` - Called for CSS rule nodes (.btn, .box, etc.)
23 | - `visitDecl?: (decl: Declaration) => T` - Called for property declarations (color: red, etc.)
24 | - `visitComment?: (comment: Comment) => T` - Called for comment nodes (/_ comment _/)
25 | 
26 | ### `NodeType<K extends keyof CssAstVisitor>`
27 | 
28 | Type utility that maps visitor method names to their corresponding PostCSS node types:
29 | 
30 | - `visitRoot` → `Container`
31 | - `visitAtRule` → `AtRule`
32 | - `visitRule` → `Rule`
33 | - `visitDecl` → `Declaration`
34 | - `visitComment` → `Comment`
35 | 
36 | ## Function Details
37 | 
38 | ### `parseStylesheet(content: string, filePath: string)`
39 | 
40 | Parse CSS content using PostCSS with safe parsing. Returns a PostCSS `LazyResult` object.
41 | 
42 | **Parameters:**
43 | 
44 | - `content` - The CSS content to parse
45 | - `filePath` - File path for source mapping and error reporting
46 | 
47 | **Returns:** PostCSS `LazyResult` with parsed AST
48 | 
49 | ### `styleAstRuleToSource(rule: Pick<Rule, 'source'>, startLine?: number)`
50 | 
51 | Convert a PostCSS rule to a linkable source location for issue reporting.
52 | 
53 | **Parameters:**
54 | 
55 | - `rule` - PostCSS rule with source information
56 | - `startLine` - Optional offset for line numbers (0-indexed, default: 0)
57 | 
58 | **Returns:** `Issue['source']` object with file path and position information
59 | 
60 | ### `visitEachChild<T>(root: Root, visitor: CssAstVisitor<T>)`
61 | 
62 | Single function that traverses the entire AST, calling specialized visitor methods for each node type.
63 | 
64 | **Parameters:**
65 | 
66 | - `root` - PostCSS Root node to traverse
67 | - `visitor` - Visitor object with optional methods for different node types
68 | 
69 | ### `visitEachStyleNode<T>(nodes: Root['nodes'], visitor: CssAstVisitor<T>)`
70 | 
71 | Recursively visit CSS nodes using the visitor pattern, processing nested structures.
72 | 
73 | **Parameters:**
74 | 
75 | - `nodes` - Array of PostCSS nodes to visit
76 | - `visitor` - Visitor object with optional methods for different node types
77 | 
78 | ### `visitStyleSheet<T>(root: Root, visitor: CssAstVisitor<T>)`
79 | 
80 | Visit only the top-level nodes of a stylesheet (non-recursive).
81 | 
82 | **Parameters:**
83 | 
84 | - `root` - PostCSS Root node
85 | - `visitor` - Visitor object with optional methods for different node types
86 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/handler-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   CallToolRequest,
  3 |   CallToolResult,
  4 | } from '@modelcontextprotocol/sdk/types.js';
  5 | import { validateComponentName } from './component-validation.js';
  6 | import { buildTextResponse, throwError } from './output.utils.js';
  7 | import * as process from 'node:process';
  8 | 
  9 | /**
 10 |  * Common handler options interface - includes both user inputs and MCP server injected config
 11 |  */
 12 | export interface BaseHandlerOptions {
 13 |   cwd?: string;
 14 |   directory?: string;
 15 |   componentName?: string;
 16 |   workspaceRoot?: string;
 17 |   // MCP server injected configuration
 18 |   storybookDocsRoot?: string;
 19 |   deprecatedCssClassesPath?: string;
 20 |   uiRoot?: string;
 21 | }
 22 | 
 23 | /**
 24 |  * Handler context with all available configuration
 25 |  */
 26 | export interface HandlerContext {
 27 |   cwd: string;
 28 |   workspaceRoot: string;
 29 |   storybookDocsRoot?: string;
 30 |   deprecatedCssClassesPath?: string;
 31 |   uiRoot: string;
 32 | }
 33 | 
 34 | /**
 35 |  * Validates common input parameters
 36 |  */
 37 | export function validateCommonInputs(params: BaseHandlerOptions): void {
 38 |   if (params.componentName) {
 39 |     validateComponentName(params.componentName);
 40 |   }
 41 | 
 42 |   if (params.directory && typeof params.directory !== 'string') {
 43 |     throw new Error('Directory parameter is required and must be a string');
 44 |   }
 45 | }
 46 | 
 47 | /**
 48 |  * Sets up common environment for handlers
 49 |  */
 50 | export function setupHandlerEnvironment(
 51 |   params: BaseHandlerOptions,
 52 | ): HandlerContext {
 53 |   const originalCwd = process.cwd();
 54 |   const cwd = params.cwd || originalCwd;
 55 | 
 56 |   if (cwd !== originalCwd) {
 57 |     process.chdir(cwd);
 58 |   }
 59 | 
 60 |   return {
 61 |     cwd,
 62 |     workspaceRoot: params.workspaceRoot || cwd,
 63 |     storybookDocsRoot: params.storybookDocsRoot,
 64 |     deprecatedCssClassesPath: params.deprecatedCssClassesPath,
 65 |     uiRoot: params.uiRoot || '',
 66 |   };
 67 | }
 68 | 
 69 | /**
 70 |  * Generic handler wrapper that provides common functionality
 71 |  */
 72 | export function createHandler<TParams extends BaseHandlerOptions, TResult>(
 73 |   toolName: string,
 74 |   handlerFn: (params: TParams, context: HandlerContext) => Promise<TResult>,
 75 |   formatResult: (result: TResult) => string[],
 76 | ) {
 77 |   return async (options: CallToolRequest): Promise<CallToolResult> => {
 78 |     try {
 79 |       const params = options.params.arguments as TParams;
 80 | 
 81 |       validateCommonInputs(params);
 82 |       const context = setupHandlerEnvironment(params);
 83 | 
 84 |       const result = await handlerFn(params, context);
 85 |       const formattedLines = formatResult(result);
 86 | 
 87 |       return buildTextResponse(formattedLines);
 88 |     } catch (ctx) {
 89 |       return throwError(`${toolName}: ${(ctx as Error).message || ctx}`);
 90 |     }
 91 |   };
 92 | }
 93 | 
 94 | /**
 95 |  * Common result formatters
 96 |  */
 97 | export const RESULT_FORMATTERS = {
 98 |   /**
 99 |    * Formats a simple success message
100 |    */
101 |   success: (message: string): string[] => [message],
102 | 
103 |   /**
104 |    * Formats a list of items
105 |    */
106 |   list: (items: string[], title?: string): string[] =>
107 |     title ? [title, ...items.map((item) => `  - ${item}`)] : items,
108 | 
109 |   /**
110 |    * Formats key-value pairs
111 |    */
112 |   keyValue: (pairs: Record<string, any>): string[] =>
113 |     Object.entries(pairs).map(
114 |       ([key, value]) =>
115 |         `${key}: ${typeof value === 'object' ? JSON.stringify(value, null, 2) : value}`,
116 |     ),
117 | 
118 |   /**
119 |    * Formats errors with context
120 |    */
121 |   error: (error: string, context?: string): string[] =>
122 |     context ? [`Error in ${context}:`, `  ${error}`] : [`Error: ${error}`],
123 | 
124 |   /**
125 |    * Formats empty results
126 |    */
127 |   empty: (entityType: string): string[] => [`No ${entityType} found`],
128 | } as const;
129 | 
```

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

```typescript
  1 | import * as ts from 'typescript';
  2 | import type { PublicApi } from '../../shared/models/types.js';
  3 | import {
  4 |   extractClassDeclaration,
  5 |   extractPublicMethods,
  6 |   extractLifecycleHooks,
  7 |   extractImports,
  8 |   extractInputsAndOutputs,
  9 | } from './typescript-analyzer.js';
 10 | import { ParsedComponent } from '@push-based/angular-ast-utils';
 11 | 
 12 | type InputMeta = {
 13 |   type?: string;
 14 |   required?: boolean;
 15 |   transform?: string;
 16 | };
 17 | 
 18 | type OutputMeta = {
 19 |   type?: string;
 20 | };
 21 | 
 22 | /**
 23 |  * `ParsedComponent` provided by `angular-ast-utils` does not yet expose
 24 |  * `inputs` / `outputs`.  We extend it locally in a non-breaking fashion
 25 |  * (both properties remain optional).
 26 |  */
 27 | type ParsedComponentWithIO = ParsedComponent & {
 28 |   inputs?: Record<string, InputMeta>;
 29 |   outputs?: Record<string, OutputMeta>;
 30 | };
 31 | 
 32 | /**
 33 |  * Extract Public API from TypeScript class analysis
 34 |  */
 35 | export function extractPublicApi(
 36 |   parsedComponent: ParsedComponentWithIO,
 37 | ): PublicApi {
 38 |   const publicApi: PublicApi = {
 39 |     properties: {},
 40 |     events: {},
 41 |     methods: {},
 42 |     lifecycle: [],
 43 |     imports: [],
 44 |   };
 45 | 
 46 |   if (parsedComponent.inputs) {
 47 |     for (const [name, config] of Object.entries(
 48 |       parsedComponent.inputs as Record<string, InputMeta>,
 49 |     )) {
 50 |       publicApi.properties[name] = {
 51 |         type: config?.type ?? 'any',
 52 |         isInput: true,
 53 |         required: config?.required ?? false,
 54 |         transform: config?.transform,
 55 |       };
 56 |     }
 57 |   }
 58 | 
 59 |   if (parsedComponent.outputs) {
 60 |     for (const [name, config] of Object.entries(
 61 |       parsedComponent.outputs as Record<string, OutputMeta>,
 62 |     )) {
 63 |       publicApi.events[name] = {
 64 |         type: config?.type ?? 'EventEmitter<any>',
 65 |       };
 66 |     }
 67 |   }
 68 | 
 69 |   const classDeclaration = extractClassDeclaration(parsedComponent);
 70 |   if (classDeclaration) {
 71 |     const program = ts.createProgram([parsedComponent.fileName], {
 72 |       target: ts.ScriptTarget.Latest,
 73 |       module: ts.ModuleKind.ESNext,
 74 |       experimentalDecorators: true,
 75 |     });
 76 |     const sourceFile = program.getSourceFile(parsedComponent.fileName);
 77 | 
 78 |     if (sourceFile) {
 79 |       const { inputs: allInputs, outputs: allOutputs } =
 80 |         extractInputsAndOutputs(classDeclaration, sourceFile);
 81 | 
 82 |       for (const [name, config] of Object.entries(allInputs)) {
 83 |         const isSignalInput = 'defaultValue' in config || 'transform' in config;
 84 | 
 85 |         publicApi.properties[name] = {
 86 |           type: config.type ?? 'any',
 87 |           isInput: true,
 88 |           required: config.required ?? false,
 89 |           ...(isSignalInput && {
 90 |             transform: (config as any).transform,
 91 |             defaultValue: (config as any).defaultValue,
 92 |           }),
 93 |           ...(!isSignalInput && {
 94 |             alias: (config as any).alias,
 95 |           }),
 96 |         };
 97 | 
 98 |         const propRef = publicApi.properties[name] as any;
 99 |         if (
100 |           propRef.transform === 'booleanAttribute' &&
101 |           propRef.type === 'any'
102 |         ) {
103 |           propRef.type = 'boolean';
104 |         }
105 |       }
106 | 
107 |       for (const [name, config] of Object.entries(allOutputs)) {
108 |         publicApi.events[name] = {
109 |           type: config.type ?? 'EventEmitter<any>',
110 |           ...('alias' in config && { alias: config.alias }),
111 |         };
112 |       }
113 | 
114 |       publicApi.methods = extractPublicMethods(classDeclaration, sourceFile);
115 |       publicApi.lifecycle = extractLifecycleHooks(classDeclaration);
116 |       publicApi.imports = extractImports(sourceFile);
117 |     }
118 |   }
119 | 
120 |   return publicApi;
121 | }
122 | 
```

--------------------------------------------------------------------------------
/packages/shared/ds-component-coverage/mocks/fixtures/e2e/demo/code-pushup.config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ComponentReplacement } from '../../../../src/lib/runner/audits/ds-coverage/schema';
  2 | import { dsComponentUsagePluginCoreConfig } from '../../../../src/core.config.js';
  3 | import * as path from 'path';
  4 | import { fileURLToPath } from 'url';
  5 | 
  6 | const currentDir = path.dirname(fileURLToPath(import.meta.url));
  7 | const packageRoot = path.resolve(currentDir, '../../../..');
  8 | 
  9 | const dsComponents: ComponentReplacement[] = [
 10 |   {
 11 |     componentName: 'DSButton',
 12 |     deprecatedCssClasses: ['btn', 'btn-primary', 'legacy-button'],
 13 |     docsUrl:
 14 |       'https://storybook.company.com/latest/?path=/docs/components-button--overview',
 15 |   },
 16 |   {
 17 |     componentName: 'DSTabsModule',
 18 |     deprecatedCssClasses: ['ms-tab-bar', 'legacy-tabs', 'custom-tabs'],
 19 |     docsUrl:
 20 |       'https://storybook.company.com/latest/?path=/docs/components-tabsgroup--overview',
 21 |   },
 22 |   {
 23 |     componentName: 'DSCard',
 24 |     deprecatedCssClasses: ['card', 'legacy-card', 'custom-card'],
 25 |     docsUrl:
 26 |       'https://storybook.company.com/latest/?path=/docs/components-card--overview',
 27 |   },
 28 |   {
 29 |     componentName: 'DSModal',
 30 |     deprecatedCssClasses: ['modal', 'popup', 'legacy-dialog'],
 31 |     docsUrl:
 32 |       'https://storybook.company.com/latest/?path=/docs/components-modal--overview',
 33 |   },
 34 |   {
 35 |     componentName: 'DSInput',
 36 |     deprecatedCssClasses: ['input', 'form-control', 'legacy-input'],
 37 |     docsUrl:
 38 |       'https://storybook.company.com/latest/?path=/docs/components-input--overview',
 39 |   },
 40 |   {
 41 |     componentName: 'DSDropdown',
 42 |     deprecatedCssClasses: ['dropdown', 'legacy-dropdown', 'custom-dropdown'],
 43 |     docsUrl:
 44 |       'https://storybook.company.com/latest/?path=/docs/components-dropdown--overview',
 45 |   },
 46 |   {
 47 |     componentName: 'DSAccordion',
 48 |     deprecatedCssClasses: ['accordion', 'collapse-panel', 'legacy-accordion'],
 49 |     docsUrl:
 50 |       'https://storybook.company.com/latest/?path=/docs/components-accordion--overview',
 51 |   },
 52 |   {
 53 |     componentName: 'DSAlert',
 54 |     deprecatedCssClasses: ['alert', 'notification', 'legacy-alert'],
 55 |     docsUrl:
 56 |       'https://storybook.company.com/latest/?path=/docs/components-alert--overview',
 57 |   },
 58 |   {
 59 |     componentName: 'DSTooltip',
 60 |     deprecatedCssClasses: ['tooltip', 'legacy-tooltip', 'info-bubble'],
 61 |     docsUrl:
 62 |       'https://storybook.company.com/latest/?path=/docs/components-tooltip--overview',
 63 |   },
 64 |   {
 65 |     componentName: 'DSBreadcrumb',
 66 |     deprecatedCssClasses: ['breadcrumb', 'legacy-breadcrumb', 'nav-breadcrumb'],
 67 |     docsUrl:
 68 |       'https://storybook.company.com/latest/?path=/docs/components-breadcrumb--overview',
 69 |   },
 70 |   {
 71 |     componentName: 'DSProgressBar',
 72 |     deprecatedCssClasses: ['progress-bar', 'loading-bar', 'legacy-progress'],
 73 |     docsUrl:
 74 |       'https://storybook.company.com/latest/?path=/docs/components-progressbar--overview',
 75 |   },
 76 |   {
 77 |     componentName: 'DSSlider',
 78 |     deprecatedCssClasses: ['slider', 'range-slider', 'legacy-slider'],
 79 |     docsUrl:
 80 |       'https://storybook.company.com/latest/?path=/docs/components-slider--overview',
 81 |   },
 82 |   {
 83 |     componentName: 'DSNavbar',
 84 |     deprecatedCssClasses: ['navbar', 'navigation', 'legacy-navbar'],
 85 |     docsUrl: 'https://storybook.company.com/latest/?p',
 86 |   },
 87 | ];
 88 | 
 89 | export default {
 90 |   persist: {
 91 |     outputDir: path.join(
 92 |       packageRoot,
 93 |       '.code-pushup/ds-component-coverage/demo',
 94 |     ),
 95 |     format: ['json', 'md'],
 96 |   },
 97 |   ...(await dsComponentUsagePluginCoreConfig({
 98 |     directory: currentDir,
 99 |     dsComponents,
100 |   })),
101 | };
102 | 
```

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

```typescript
  1 | /* eslint-disable prefer-const */
  2 | import { describe, it, expect } from 'vitest';
  3 | 
  4 | import type { Difference } from 'microdiff';
  5 | import {
  6 |   consolidateAndPruneRemoveOperations,
  7 |   consolidateAndPruneRemoveOperationsWithDeduplication,
  8 |   isChildPath,
  9 |   groupChangesByDomainAndType,
 10 |   generateDiffSummary,
 11 | } from '../utils/diff-utils.js';
 12 | 
 13 | function makeRemove(path: (string | number)[]): Difference {
 14 |   return { type: 'REMOVE', path, oldValue: 'dummy' } as any;
 15 | }
 16 | 
 17 | function makeAdd(path: (string | number)[], value: any): Difference {
 18 |   return { type: 'ADD', path, value } as any;
 19 | }
 20 | 
 21 | describe('diff-utils', () => {
 22 |   describe('isChildPath', () => {
 23 |     it('identifies descendant paths correctly', () => {
 24 |       expect(isChildPath(['a', 'b', 'c'], ['a', 'b'])).toBe(true);
 25 |       expect(isChildPath(['a', 'b'], ['a', 'b', 'c'])).toBe(false);
 26 |       expect(isChildPath(['x'], ['x'])).toBe(false);
 27 |     });
 28 |   });
 29 | 
 30 |   describe('consolidateAndPruneRemoveOperations', () => {
 31 |     it('consolidates CSS rule removals and prunes redundant child removals', () => {
 32 |       const diff: Difference[] = [
 33 |         makeRemove(['styles', 'rules', 'div']),
 34 |         makeRemove(['styles', 'rules', 'span']),
 35 |         makeRemove(['dom', 'elements', 0, 'attributes']),
 36 |         makeRemove(['dom', 'elements']),
 37 |         makeAdd(['meta', 'name'], 'Foo'),
 38 |       ];
 39 | 
 40 |       const processed = consolidateAndPruneRemoveOperations(diff);
 41 | 
 42 |       expect(processed).toEqual(
 43 |         expect.arrayContaining([expect.objectContaining({ type: 'ADD' })]),
 44 |       );
 45 | 
 46 |       expect(processed).toContainEqual({
 47 |         type: 'REMOVE',
 48 |         path: ['styles', 'rules'],
 49 |         oldValue: ['div', 'span'],
 50 |       } as any);
 51 | 
 52 |       expect(
 53 |         processed.filter((c) => JSON.stringify(c.path).includes('dom')),
 54 |       ).toHaveLength(1);
 55 |       expect(processed).toContainEqual(makeRemove(['dom', 'elements']));
 56 |     });
 57 |   });
 58 | 
 59 |   describe('consolidateAndPruneRemoveOperationsWithDeduplication', () => {
 60 |     it('deduplicates DOM paths into dictionary', () => {
 61 |       const LONG_PATH = 'div#root > span.foo > button.bar';
 62 | 
 63 |       const diff: Difference[] = [
 64 |         {
 65 |           type: 'CHANGE',
 66 |           path: ['dom', 'elementPath'],
 67 |           oldValue: LONG_PATH,
 68 |           value: `${LONG_PATH} > svg.icon`,
 69 |         } as any,
 70 |       ];
 71 | 
 72 |       const { processedResult, domPathDict } =
 73 |         consolidateAndPruneRemoveOperationsWithDeduplication(diff);
 74 | 
 75 |       const refObj = { $domPath: 0 };
 76 |       expect((processedResult[0] as any).oldValue).toEqual(refObj);
 77 | 
 78 |       expect(domPathDict.paths).toEqual([LONG_PATH, `${LONG_PATH} > svg.icon`]);
 79 |       expect(domPathDict.stats.uniquePaths).toBe(2);
 80 |     });
 81 |   });
 82 | 
 83 |   describe('groupChangesByDomainAndType / generateDiffSummary', () => {
 84 |     it('groups changes and summarizes stats', () => {
 85 |       const diff: Difference[] = [
 86 |         makeAdd(['meta', 'name'], 'Foo'),
 87 |         makeRemove(['styles', 'rules', 'div']),
 88 |       ];
 89 | 
 90 |       const grouped = groupChangesByDomainAndType(diff);
 91 | 
 92 |       expect(grouped).toHaveProperty('meta');
 93 |       expect(grouped.meta).toHaveProperty('ADD');
 94 |       expect(grouped.meta.ADD).toHaveLength(1);
 95 |       expect(grouped).toHaveProperty('styles');
 96 | 
 97 |       const summary = generateDiffSummary(diff, grouped);
 98 | 
 99 |       expect(summary.totalChanges).toBe(2);
100 |       expect(summary.changeTypes.ADD).toBe(1);
101 |       expect(summary.changeTypes.REMOVE).toBe(1);
102 |       expect(summary.changesByDomain.meta.ADD).toBe(1);
103 |     });
104 |   });
105 | });
106 | 
```

--------------------------------------------------------------------------------
/packages/shared/utils/src/lib/file/find-in-file.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Dirent } from 'node:fs';
  2 | import * as fs from 'node:fs/promises';
  3 | import * as path from 'node:path';
  4 | 
  5 | /**
  6 |  * Searches for `.ts` files containing the search pattern.
  7 |  * @param {string} baseDir - The directory to search. Should be absolute or resolved by the caller.
  8 |  * @param {RegExp | string} searchPattern - The pattern to match.
  9 |  */
 10 | export async function findFilesWithPattern(
 11 |   baseDir: string,
 12 |   searchPattern: string,
 13 | ) {
 14 |   const resolvedBaseDir = path.resolve(baseDir);
 15 | 
 16 |   const tsFiles: string[] = [];
 17 |   for await (const file of findAllFiles(
 18 |     resolvedBaseDir,
 19 |     (file) => file.endsWith('.ts') && !file.endsWith('.spec.ts'),
 20 |   )) {
 21 |     tsFiles.push(file);
 22 |   }
 23 | 
 24 |   const results: SourceLocation[] = [];
 25 |   for (const file of tsFiles) {
 26 |     try {
 27 |       const hits = await findInFile(file, searchPattern);
 28 |       if (hits.length > 0) {
 29 |         results.push(...hits);
 30 |       }
 31 |     } catch (ctx) {
 32 |       console.error(`Error searching file ${file}:`, ctx);
 33 |     }
 34 |   }
 35 | 
 36 |   return results.map((r: SourceLocation) => r.file);
 37 | }
 38 | 
 39 | /**
 40 |  * Finds all files in a directory and its subdirectories that match a predicate
 41 |  */
 42 | export async function* findAllFiles(
 43 |   baseDir: string,
 44 |   predicate: (file: string) => boolean = (fullPath) => fullPath.endsWith('.ts'),
 45 | ): AsyncGenerator<string> {
 46 |   const entries = await getDirectoryEntries(baseDir);
 47 | 
 48 |   for (const entry of entries) {
 49 |     const fullPath = path.join(baseDir, entry.name);
 50 |     if (entry.isDirectory()) {
 51 |       // Skip node_modules and other common exclude directories
 52 |       if (!isExcludedDirectory(entry.name)) {
 53 |         yield* findAllFiles(fullPath, predicate);
 54 |       }
 55 |     } else if (entry.isFile() && predicate(fullPath)) {
 56 |       yield fullPath;
 57 |     }
 58 |   }
 59 | }
 60 | 
 61 | export function isExcludedDirectory(fileName: string) {
 62 |   return (
 63 |     fileName.startsWith('.') ||
 64 |     fileName === 'node_modules' ||
 65 |     fileName === 'dist' ||
 66 |     fileName === 'coverage'
 67 |   );
 68 | }
 69 | 
 70 | async function getDirectoryEntries(dir: string): Promise<Dirent[]> {
 71 |   try {
 72 |     return await fs.readdir(dir, { withFileTypes: true });
 73 |   } catch (ctx) {
 74 |     console.error(`Error reading directory ${dir}:`, ctx);
 75 |     return [];
 76 |   }
 77 | }
 78 | 
 79 | export function* accessContent(content: string): Generator<string> {
 80 |   for (const line of content.split('\n')) {
 81 |     yield line;
 82 |   }
 83 | }
 84 | 
 85 | export function getLineHits(
 86 |   content: string,
 87 |   pattern: string,
 88 |   bail = false,
 89 | ): LinePosition[] {
 90 |   const hits: LinePosition[] = [];
 91 |   let index = content.indexOf(pattern);
 92 | 
 93 |   while (index !== -1) {
 94 |     hits.push({ startColumn: index, endColumn: index + pattern.length });
 95 |     if (bail) {
 96 |       return hits;
 97 |     }
 98 |     index = content.indexOf(pattern, index + 1);
 99 |   }
100 |   return hits;
101 | }
102 | 
103 | export type LinePosition = {
104 |   startColumn: number;
105 |   endColumn?: number;
106 | };
107 | 
108 | export type SourcePosition = {
109 |   startLine: number;
110 |   endLine?: number;
111 | } & LinePosition;
112 | 
113 | export type SourceLocation = {
114 |   file: string;
115 |   position: SourcePosition;
116 | };
117 | 
118 | export async function findInFile(
119 |   file: string,
120 |   searchPattern: string,
121 |   bail = false,
122 | ): Promise<SourceLocation[]> {
123 |   const hits: SourceLocation[] = [];
124 |   const content = await fs.readFile(file, 'utf8');
125 |   let startLine = 0;
126 |   for (const line of accessContent(content)) {
127 |     startLine++;
128 |     getLineHits(line, searchPattern, bail).forEach((position) => {
129 |       hits.push({
130 |         file,
131 |         position: {
132 |           startLine,
133 |           ...position,
134 |         },
135 |       });
136 |     });
137 |   }
138 |   return hits;
139 | }
140 | 
```

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

```typescript
  1 | import { readdir, stat, readFile } from 'node:fs/promises';
  2 | import { join } from 'node:path';
  3 | import {
  4 |   createHandler,
  5 |   BaseHandlerOptions,
  6 | } from '../../shared/utils/handler-helpers.js';
  7 | import { resolveCrossPlatformPath } from '../../shared/utils/cross-platform-path.js';
  8 | import { listComponentContractsSchema } from './models/schema.js';
  9 | import type { ContractFileInfo } from './models/types.js';
 10 | import {
 11 |   extractComponentNameFromFile,
 12 |   formatBytes,
 13 |   formatContractsByComponent,
 14 | } from './utils/contract-list-utils.js';
 15 | 
 16 | interface ListComponentContractsOptions extends BaseHandlerOptions {
 17 |   directory: string;
 18 | }
 19 | 
 20 | /**
 21 |  * Recursively scan directory for contract files
 22 |  */
 23 | async function scanContractsRecursively(
 24 |   dirPath: string,
 25 |   contracts: ContractFileInfo[],
 26 | ): Promise<void> {
 27 |   try {
 28 |     const entries = await readdir(dirPath, { withFileTypes: true });
 29 | 
 30 |     for (const entry of entries) {
 31 |       const fullPath = join(dirPath, entry.name);
 32 | 
 33 |       if (entry.isDirectory()) {
 34 |         // Recursively scan subdirectories
 35 |         await scanContractsRecursively(fullPath, contracts);
 36 |       } else if (entry.isFile() && entry.name.endsWith('.contract.json')) {
 37 |         // Process contract file
 38 |         const stats = await stat(fullPath);
 39 | 
 40 |         try {
 41 |           const contractData = JSON.parse(await readFile(fullPath, 'utf-8'));
 42 |           const componentName =
 43 |             contractData.metadata?.componentName ||
 44 |             extractComponentNameFromFile(entry.name);
 45 | 
 46 |           contracts.push({
 47 |             fileName: entry.name,
 48 |             filePath: fullPath,
 49 |             componentName,
 50 |             timestamp:
 51 |               contractData.metadata?.timestamp || stats.mtime.toISOString(),
 52 |             hash: contractData.hash || 'unknown',
 53 |             size: formatBytes(stats.size),
 54 |           });
 55 |         } catch {
 56 |           console.warn(`Skipping invalid contract file: ${fullPath}`);
 57 |           continue;
 58 |         }
 59 |       }
 60 |     }
 61 |   } catch (ctx) {
 62 |     // Silently skip directories that can't be read
 63 |     if ((ctx as NodeJS.ErrnoException).code !== 'ENOENT') {
 64 |       console.warn(`Error scanning directory ${dirPath}:`, ctx);
 65 |     }
 66 |   }
 67 | }
 68 | 
 69 | export const listComponentContractsHandler = createHandler<
 70 |   ListComponentContractsOptions,
 71 |   ContractFileInfo[]
 72 | >(
 73 |   listComponentContractsSchema.name,
 74 |   async (_, { cwd: _cwd, workspaceRoot }) => {
 75 |     const contractDir = resolveCrossPlatformPath(
 76 |       workspaceRoot,
 77 |       '.cursor/tmp/contracts',
 78 |     );
 79 |     const contracts: ContractFileInfo[] = [];
 80 | 
 81 |     await scanContractsRecursively(contractDir, contracts);
 82 | 
 83 |     // Sort by timestamp (newest first)
 84 |     contracts.sort(
 85 |       (a, b) =>
 86 |         new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
 87 |     );
 88 | 
 89 |     return contracts;
 90 |   },
 91 |   (contracts) => {
 92 |     if (contracts.length === 0) {
 93 |       return [
 94 |         '📁 No component contracts found',
 95 |         '💡 Use the build_component_contract tool to generate contracts',
 96 |         '🎯 Contracts are stored in .cursor/tmp/contracts/*.contract.json',
 97 |       ];
 98 |     }
 99 | 
100 |     const output: string[] = [];
101 | 
102 |     output.push(...formatContractsByComponent(contracts));
103 | 
104 |     output.push('💡 Use diff_component_contract to compare contracts');
105 |     output.push('🔄 Newer contracts appear first within each component');
106 | 
107 |     return output;
108 |   },
109 | );
110 | 
111 | export const listComponentContractsTools = [
112 |   {
113 |     schema: listComponentContractsSchema,
114 |     handler: listComponentContractsHandler,
115 |   },
116 | ];
117 | 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/report-violations/report-all-violations.tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {
  2 |   BaseHandlerOptions,
  3 |   createHandler,
  4 | } from '../shared/utils/handler-helpers.js';
  5 | import {
  6 |   COMMON_ANNOTATIONS,
  7 |   createProjectAnalysisSchema,
  8 | } from '../shared/models/schema-helpers.js';
  9 | import {
 10 |   analyzeProjectCoverage,
 11 |   extractComponentName,
 12 | } from '../shared/violation-analysis/coverage-analyzer.js';
 13 | import {
 14 |   formatViolations,
 15 |   filterFailedAudits,
 16 |   groupIssuesByFile,
 17 | } from '../shared/violation-analysis/formatters.js';
 18 | import { loadAndValidateDsComponentsFile } from '../../../validation/ds-components-file-loader.validation.js';
 19 | import { RESULT_FORMATTERS } from '../shared/utils/handler-helpers.js';
 20 | 
 21 | interface ReportAllViolationsOptions extends BaseHandlerOptions {
 22 |   directory: string;
 23 |   groupBy?: 'file' | 'folder';
 24 | }
 25 | 
 26 | export const reportAllViolationsSchema = {
 27 |   name: 'report-all-violations',
 28 |   description:
 29 |     'Scan a directory for deprecated design system CSS classes defined in the config at `deprecatedCssClassesPath`, and output a usage report',
 30 |   inputSchema: createProjectAnalysisSchema({
 31 |     groupBy: {
 32 |       type: 'string',
 33 |       enum: ['file', 'folder'],
 34 |       description: 'How to group the results',
 35 |       default: 'file',
 36 |     },
 37 |   }),
 38 |   annotations: {
 39 |     title: 'Report All Violations',
 40 |     ...COMMON_ANNOTATIONS.readOnly,
 41 |   },
 42 | };
 43 | 
 44 | export const reportAllViolationsHandler = createHandler<
 45 |   ReportAllViolationsOptions,
 46 |   string[]
 47 | >(
 48 |   reportAllViolationsSchema.name,
 49 |   async (params, { cwd, deprecatedCssClassesPath }) => {
 50 |     if (!deprecatedCssClassesPath) {
 51 |       throw new Error(
 52 |         'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.',
 53 |       );
 54 |     }
 55 |     const groupBy = params.groupBy || 'file';
 56 |     const dsComponents = await loadAndValidateDsComponentsFile(
 57 |       cwd,
 58 |       deprecatedCssClassesPath || '',
 59 |     );
 60 | 
 61 |     const coverageResult = await analyzeProjectCoverage({
 62 |       cwd,
 63 |       returnRawData: true,
 64 |       directory: params.directory,
 65 |       dsComponents,
 66 |     });
 67 | 
 68 |     const raw = coverageResult.rawData?.rawPluginResult;
 69 |     if (!raw) return [];
 70 | 
 71 |     const failedAudits = filterFailedAudits(raw);
 72 |     if (failedAudits.length === 0) return ['No violations found.'];
 73 | 
 74 |     if (groupBy === 'file') {
 75 |       const lines: string[] = [];
 76 |       for (const audit of failedAudits) {
 77 |         extractComponentName(audit.title);
 78 |         const fileGroups = groupIssuesByFile(
 79 |           audit.details?.issues ?? [],
 80 |           params.directory,
 81 |         );
 82 |         for (const [fileName, { lines: fileLines, message }] of Object.entries(
 83 |           fileGroups,
 84 |         )) {
 85 |           const sorted =
 86 |             fileLines.length > 1
 87 |               ? [...fileLines].sort((a, b) => a - b)
 88 |               : fileLines;
 89 |           const lineInfo =
 90 |             sorted.length > 1
 91 |               ? `lines ${sorted.join(', ')}`
 92 |               : `line ${sorted[0]}`;
 93 |           lines.push(`${fileName} (${lineInfo}): ${message}`);
 94 |         }
 95 |       }
 96 |       return lines;
 97 |     }
 98 | 
 99 |     const formattedContent = formatViolations(raw, params.directory, {
100 |       groupBy: 'folder',
101 |     });
102 |     return formattedContent.map(
103 |       (item: { type?: string; text?: string } | string) =>
104 |         typeof item === 'string' ? item : (item?.text ?? String(item)),
105 |     );
106 |   },
107 |   (result) => RESULT_FORMATTERS.list(result, 'Design System Violations:'),
108 | );
109 | 
110 | export const reportAllViolationsTools = [
111 |   {
112 |     schema: reportAllViolationsSchema,
113 |     handler: reportAllViolationsHandler,
114 |   },
115 | ];
116 | 
```

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

```typescript
  1 | import { describe, it, expect } from 'vitest';
  2 | 
  3 | import {
  4 |   extractBindings,
  5 |   extractAttributes,
  6 |   extractEvents,
  7 | } from '../utils/element-helpers.js';
  8 | 
  9 | type MockASTWithSource = { source: string };
 10 | 
 11 | type MockPosition = {
 12 |   offset: number;
 13 |   file: { url: string };
 14 | };
 15 | 
 16 | type MockSourceSpan = {
 17 |   start: MockPosition;
 18 |   end: MockPosition;
 19 | };
 20 | 
 21 | function makeSourceSpan(
 22 |   start: number,
 23 |   end: number,
 24 |   fileUrl = '/comp.html',
 25 | ): MockSourceSpan {
 26 |   return {
 27 |     start: { offset: start, file: { url: fileUrl } },
 28 |     end: { offset: end, file: { url: fileUrl } },
 29 |   };
 30 | }
 31 | 
 32 | interface MockInput {
 33 |   name: string;
 34 |   value: MockASTWithSource;
 35 |   sourceSpan?: MockSourceSpan;
 36 | }
 37 | 
 38 | interface MockAttribute {
 39 |   name: string;
 40 |   value: string;
 41 | }
 42 | 
 43 | interface MockOutput {
 44 |   name: string;
 45 |   handler: MockASTWithSource;
 46 | }
 47 | 
 48 | interface MockElement {
 49 |   inputs: MockInput[];
 50 |   attributes: MockAttribute[];
 51 |   outputs: MockOutput[];
 52 | }
 53 | 
 54 | function createElement(partial: Partial<MockElement>): MockElement {
 55 |   return {
 56 |     inputs: [],
 57 |     attributes: [],
 58 |     outputs: [],
 59 |     ...partial,
 60 |   } as MockElement;
 61 | }
 62 | 
 63 | describe('element-helpers', () => {
 64 |   describe('extractBindings', () => {
 65 |     it('class, style, attribute, and property binding types are detected', () => {
 66 |       const element = createElement({
 67 |         inputs: [
 68 |           { name: 'class.foo', value: { source: 'cond' } },
 69 |           { name: 'style.color', value: { source: 'expr' } },
 70 |           { name: 'attr.data-id', value: { source: 'id' } },
 71 |           { name: 'value', value: { source: 'val' } },
 72 |         ],
 73 |       });
 74 | 
 75 |       const bindings = extractBindings(element as any);
 76 | 
 77 |       expect(bindings).toEqual([
 78 |         {
 79 |           type: 'class',
 80 |           name: 'class.foo',
 81 |           source: 'cond',
 82 |           sourceSpan: undefined,
 83 |         },
 84 |         {
 85 |           type: 'style',
 86 |           name: 'style.color',
 87 |           source: 'expr',
 88 |           sourceSpan: undefined,
 89 |         },
 90 |         {
 91 |           type: 'attribute',
 92 |           name: 'attr.data-id',
 93 |           source: 'id',
 94 |           sourceSpan: undefined,
 95 |         },
 96 |         {
 97 |           type: 'property',
 98 |           name: 'value',
 99 |           source: 'val',
100 |           sourceSpan: undefined,
101 |         },
102 |       ]);
103 |     });
104 | 
105 |     it('maps sourceSpan information', () => {
106 |       const span = makeSourceSpan(5, 15);
107 |       const element = createElement({
108 |         inputs: [
109 |           { name: 'value', value: { source: 'expr' }, sourceSpan: span },
110 |         ],
111 |       });
112 | 
113 |       const [binding] = extractBindings(element as any);
114 |       expect(binding.sourceSpan).toEqual({
115 |         start: 5,
116 |         end: 15,
117 |         file: '/comp.html',
118 |       });
119 |     });
120 |   });
121 | 
122 |   describe('extractAttributes', () => {
123 |     it('returns attribute objects with type="attribute"', () => {
124 |       const element = createElement({
125 |         attributes: [
126 |           { name: 'id', value: 'root' },
127 |           { name: 'role', value: 'banner' },
128 |         ],
129 |       });
130 | 
131 |       const attrs = extractAttributes(element as any);
132 |       expect(attrs).toEqual([
133 |         { type: 'attribute', name: 'id', source: 'root' },
134 |         { type: 'attribute', name: 'role', source: 'banner' },
135 |       ]);
136 |     });
137 |   });
138 | 
139 |   describe('extractEvents', () => {
140 |     it('returns event objects with handler source', () => {
141 |       const element = createElement({
142 |         outputs: [{ name: 'click', handler: { source: 'onClick()' } }],
143 |       });
144 | 
145 |       const events = extractEvents(element as any);
146 |       expect(events).toEqual([{ name: 'click', handler: 'onClick()' }]);
147 |     });
148 |   });
149 | });
150 | 
```

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

```typescript
  1 | import { Component, signal } from '@angular/core';
  2 | import { ComplexBadgeWidgetComponent, BadgeConfig } from './complex-badge-widget.component';
  3 | 
  4 | @Component({
  5 |   selector: 'app-complex-widget-demo',
  6 |   standalone: true,
  7 |   imports: [ComplexBadgeWidgetComponent],
  8 |   template: `
  9 |     <div class="demo-container">
 10 |       <h2>Complex Badge Widget Demo</h2>
 11 |       <p>This component demonstrates a complex badge implementation that will fail when refactored to DsBadge.</p>
 12 |       
 13 |       <app-complex-badge-widget
 14 |         [initialBadges]="customBadges()"
 15 |         (badgeSelected)="onBadgeSelected($event)"
 16 |         (badgeModified)="onBadgeModified($event)"
 17 |         (badgeDeleted)="onBadgeDeleted($event)">
 18 |       </app-complex-badge-widget>
 19 |       
 20 |       <div class="demo-log">
 21 |         <h3>Event Log:</h3>
 22 |         <div class="log-entries">
 23 |           @for (entry of eventLog(); track $index) {
 24 |             <div class="log-entry">{{ entry }}</div>
 25 |           }
 26 |         </div>
 27 |       </div>
 28 |     </div>
 29 |   `,
 30 |   styles: [`
 31 |     .demo-container {
 32 |       padding: 2rem;
 33 |       max-width: 1200px;
 34 |       margin: 0 auto;
 35 |     }
 36 |     
 37 |     .demo-container h2 {
 38 |       color: #1f2937;
 39 |       margin-bottom: 1rem;
 40 |     }
 41 |     
 42 |     .demo-container p {
 43 |       color: #6b7280;
 44 |       margin-bottom: 2rem;
 45 |     }
 46 |     
 47 |     .demo-log {
 48 |       background: white;
 49 |       border-radius: 0.5rem;
 50 |       padding: 1.5rem;
 51 |       box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
 52 |       margin-top: 2rem;
 53 |     }
 54 |     
 55 |     .demo-log h3 {
 56 |       margin: 0 0 1rem 0;
 57 |       color: #1f2937;
 58 |       font-size: 1.125rem;
 59 |     }
 60 |     
 61 |     .log-entries {
 62 |       max-height: 200px;
 63 |       overflow-y: auto;
 64 |     }
 65 |     
 66 |     .log-entry {
 67 |       padding: 0.5rem;
 68 |       border-bottom: 1px solid #f3f4f6;
 69 |       font-size: 0.875rem;
 70 |       color: #374151;
 71 |       font-family: monospace;
 72 |     }
 73 |     
 74 |     .log-entry:last-child {
 75 |       border-bottom: none;
 76 |     }
 77 |   `]
 78 | })
 79 | export class ComplexWidgetDemoComponent {
 80 |   eventLog = signal<string[]>([]);
 81 |   
 82 |   customBadges = signal<BadgeConfig[]>([
 83 |     {
 84 |       id: 'complex-1',
 85 |       text: 'Ultra Premium',
 86 |       type: 'offer-badge',
 87 |       level: 'critical',
 88 |       interactive: true,
 89 |       customData: { 
 90 |         prop: 'ultra-premium', 
 91 |         complexity: 'high',
 92 |         features: ['animations', 'tooltips', 'dom-manipulation'],
 93 |         breakingPoints: ['nested-structure', 'pseudo-elements', 'direct-dom-access']
 94 |       }
 95 |     },
 96 |     {
 97 |       id: 'complex-2',
 98 |       text: 'System Health',
 99 |       type: 'status',
100 |       level: 'high',
101 |       interactive: false,
102 |       customData: { 
103 |         prop: 'health-monitor',
104 |         realTime: true,
105 |         dependencies: ['custom-animations', 'complex-selectors'],
106 |         refactorRisk: 'high'
107 |       }
108 |     },
109 |     {
110 |       id: 'complex-3',
111 |       text: 'Critical Priority',
112 |       type: 'priority',
113 |       level: 'critical',
114 |       interactive: true,
115 |       customData: { 
116 |         prop: 'priority-alert',
117 |         escalated: true,
118 |         customBehaviors: ['hover-effects', 'click-handlers', 'tooltip-system'],
119 |         migrationComplexity: 'very-high'
120 |       }
121 |     }
122 |   ]);
123 | 
124 |   onBadgeSelected(badgeId: string) {
125 |     this.addLogEntry(`Badge selected: ${badgeId}`);
126 |   }
127 | 
128 |   onBadgeModified(badge: BadgeConfig) {
129 |     this.addLogEntry(`Badge modified: ${badge.id} - ${badge.text}`);
130 |   }
131 | 
132 |   onBadgeDeleted(badgeId: string) {
133 |     this.addLogEntry(`Badge deleted: ${badgeId}`);
134 |   }
135 | 
136 |   private addLogEntry(message: string) {
137 |     const timestamp = new Date().toLocaleTimeString();
138 |     const entry = `[${timestamp}] ${message}`;
139 |     this.eventLog.update(log => [entry, ...log.slice(0, 49)]);
140 |   }
141 | } 
```

--------------------------------------------------------------------------------
/packages/minimal-repo/packages/application/src/app/components/validation-tests/valid.component.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Component } from '@angular/core';
  2 | import { CommonModule } from '@angular/common';
  3 | import { FormsModule } from '@angular/forms';
  4 | 
  5 | @Component({
  6 |   selector: 'app-valid',
  7 |   standalone: true,
  8 |   imports: [CommonModule, FormsModule],
  9 |   template: `
 10 |     <div class="valid-component">
 11 |       <h2>{{ title }}</h2>
 12 |       <div class="todo-section">
 13 |         <input 
 14 |           [(ngModel)]="newTodo" 
 15 |           placeholder="Enter a new todo"
 16 |           (keyup.enter)="addTodo()"
 17 |           class="todo-input"
 18 |         />
 19 |         <button (click)="addTodo()" [disabled]="!newTodo.trim()">
 20 |           Add Todo
 21 |         </button>
 22 |       </div>
 23 |       
 24 |       <ul class="todo-list" *ngIf="todos.length > 0">
 25 |         <li *ngFor="let todo of todos; let i = index" class="todo-item">
 26 |           <span [class.completed]="todo.completed">{{ todo.text }}</span>
 27 |           <div class="todo-actions">
 28 |             <button (click)="toggleTodo(i)" class="toggle-btn">
 29 |               {{ todo.completed ? 'Undo' : 'Complete' }}
 30 |             </button>
 31 |             <button (click)="removeTodo(i)" class="remove-btn">Remove</button>
 32 |           </div>
 33 |         </li>
 34 |       </ul>
 35 |       
 36 |       <div class="stats" *ngIf="todos.length > 0">
 37 |         <p>Total: {{ todos.length }} | 
 38 |            Completed: {{ completedCount }} | 
 39 |            Remaining: {{ remainingCount }}
 40 |         </p>
 41 |       </div>
 42 |     </div>
 43 |   `,
 44 |   styles: [`
 45 |     .valid-component {
 46 |       max-width: 500px;
 47 |       margin: 20px auto;
 48 |       padding: 20px;
 49 |       border: 2px solid #4CAF50;
 50 |       border-radius: 8px;
 51 |       background: #f9fff9;
 52 |     }
 53 |     
 54 |     .todo-section {
 55 |       display: flex;
 56 |       gap: 10px;
 57 |       margin-bottom: 20px;
 58 |     }
 59 |     
 60 |     .todo-input {
 61 |       flex: 1;
 62 |       padding: 8px;
 63 |       border: 1px solid #ddd;
 64 |       border-radius: 4px;
 65 |     }
 66 |     
 67 |     .todo-list {
 68 |       list-style: none;
 69 |       padding: 0;
 70 |     }
 71 |     
 72 |     .todo-item {
 73 |       display: flex;
 74 |       justify-content: space-between;
 75 |       align-items: center;
 76 |       padding: 10px;
 77 |       margin-bottom: 5px;
 78 |       background: white;
 79 |       border-radius: 4px;
 80 |       border: 1px solid #eee;
 81 |     }
 82 |     
 83 |     .completed {
 84 |       text-decoration: line-through;
 85 |       color: #888;
 86 |     }
 87 |     
 88 |     .todo-actions {
 89 |       display: flex;
 90 |       gap: 5px;
 91 |     }
 92 |     
 93 |     .toggle-btn, .remove-btn {
 94 |       padding: 4px 8px;
 95 |       border: none;
 96 |       border-radius: 3px;
 97 |       cursor: pointer;
 98 |       font-size: 12px;
 99 |     }
100 |     
101 |     .toggle-btn {
102 |       background: #2196F3;
103 |       color: white;
104 |     }
105 |     
106 |     .remove-btn {
107 |       background: #f44336;
108 |       color: white;
109 |     }
110 |     
111 |     .stats {
112 |       margin-top: 20px;
113 |       padding: 10px;
114 |       background: #e8f5e8;
115 |       border-radius: 4px;
116 |       text-align: center;
117 |     }
118 |   `]
119 | })
120 | export class ValidComponent {
121 |   title = 'Valid Todo Component';
122 |   newTodo = '';
123 |   todos: { text: string; completed: boolean }[] = [
124 |     { text: 'Learn Angular', completed: true },
125 |     { text: 'Build awesome apps', completed: false }
126 |   ];
127 | 
128 |   addTodo(): void {
129 |     if (this.newTodo.trim()) {
130 |       this.todos.push({
131 |         text: this.newTodo.trim(),
132 |         completed: false
133 |       });
134 |       this.newTodo = '';
135 |     }
136 |   }
137 | 
138 |   toggleTodo(index: number): void {
139 |     if (index >= 0 && index < this.todos.length) {
140 |       this.todos[index].completed = !this.todos[index].completed;
141 |     }
142 |   }
143 | 
144 |   removeTodo(index: number): void {
145 |     if (index >= 0 && index < this.todos.length) {
146 |       this.todos.splice(index, 1);
147 |     }
148 |   }
149 | 
150 |   get completedCount(): number {
151 |     return this.todos.filter(todo => todo.completed).length;
152 |   }
153 | 
154 |   get remainingCount(): number {
155 |     return this.todos.filter(todo => !todo.completed).length;
156 |   }
157 | } 
```

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

```typescript
  1 | import { readFile, mkdir, writeFile } from 'node:fs/promises';
  2 | import { existsSync } from 'node:fs';
  3 | import { resolve, basename, extname } from 'node:path';
  4 | import { createHash } from 'node:crypto';
  5 | import { ComponentContract } from '../models/types.js';
  6 | import { resolveCrossPlatformPath } from '../../../shared/utils/cross-platform-path.js';
  7 | import { componentNameToKebabCase } from '../../../shared/utils/component-validation.js';
  8 | 
  9 | /**
 10 |  * Load a contract from a JSON file, handling both wrapped and direct formats
 11 |  */
 12 | export async function loadContract(path: string): Promise<ComponentContract> {
 13 |   if (!existsSync(path)) {
 14 |     throw new Error(`Contract file not found: ${path}`);
 15 |   }
 16 | 
 17 |   const content = await readFile(path, 'utf-8');
 18 |   const data = JSON.parse(content);
 19 | 
 20 |   return data.contract || data;
 21 | }
 22 | 
 23 | /**
 24 |  * Save a contract to the standard location with metadata
 25 |  */
 26 | export async function saveContract(
 27 |   contract: ComponentContract,
 28 |   workspaceRoot: string,
 29 |   templatePath: string,
 30 |   scssPath: string,
 31 |   cwd: string,
 32 |   dsComponentName?: string,
 33 | ): Promise<{ contractFilePath: string; hash: string }> {
 34 |   const componentName = basename(templatePath, extname(templatePath));
 35 | 
 36 |   // Stringify early so we can compute a deterministic hash before naming the file
 37 |   const contractString = JSON.stringify(contract, null, 2);
 38 | 
 39 |   const hash = createHash('sha256').update(contractString).digest('hex');
 40 | 
 41 |   const timestamp = new Date()
 42 |     .toISOString()
 43 |     .replace(/[-:]/g, '')
 44 |     .replace(/\.\d+Z$/, 'Z');
 45 | 
 46 |   const contractFileName = `${componentName}-${timestamp}.contract.json`;
 47 | 
 48 |   // Determine final directory: .cursor/tmp/contracts/<kebab-scope>
 49 |   let contractDir = resolveCrossPlatformPath(
 50 |     workspaceRoot,
 51 |     '.cursor/tmp/contracts',
 52 |   );
 53 | 
 54 |   if (dsComponentName) {
 55 |     const folderSlug = componentNameToKebabCase(dsComponentName);
 56 |     contractDir = resolveCrossPlatformPath(
 57 |       workspaceRoot,
 58 |       `.cursor/tmp/contracts/${folderSlug}`,
 59 |     );
 60 |   }
 61 | 
 62 |   await mkdir(contractDir, { recursive: true });
 63 | 
 64 |   const contractFilePath = resolve(contractDir, contractFileName);
 65 | 
 66 |   const contractData = {
 67 |     contract,
 68 |     hash: `sha256-${hash}`,
 69 |     metadata: {
 70 |       templatePath: resolve(cwd, templatePath),
 71 |       scssPath: resolve(cwd, scssPath),
 72 |       timestamp: new Date().toISOString(),
 73 |       componentName,
 74 |     },
 75 |   };
 76 | 
 77 |   await writeFile(
 78 |     contractFilePath,
 79 |     JSON.stringify(contractData, null, 2),
 80 |     'utf-8',
 81 |   );
 82 | 
 83 |   return {
 84 |     contractFilePath,
 85 |     hash: `sha256-${hash}`,
 86 |   };
 87 | }
 88 | 
 89 | /**
 90 |  * Generate a standardized contract summary for display
 91 |  */
 92 | export function generateContractSummary(contract: ComponentContract): string[] {
 93 |   return [
 94 |     `🎯 DOM Elements: ${Object.keys(contract.dom).length}`,
 95 |     `🎨 Style Rules: ${Object.keys(contract.styles.rules).length}`,
 96 |     `📥 Properties: ${Object.keys(contract.publicApi.properties).length}`,
 97 |     `📤 Events: ${Object.keys(contract.publicApi.events).length}`,
 98 |     `⚙️  Methods: ${Object.keys(contract.publicApi.methods).length}`,
 99 |     `🔄 Lifecycle Hooks: ${contract.publicApi.lifecycle.length}`,
100 |     `📦 Imports: ${contract.publicApi.imports.length}`,
101 |     `🎪 Slots: ${Object.keys(contract.slots).length}`,
102 |     `📁 Source: ${contract.meta.sourceFile}`,
103 |   ];
104 | }
105 | 
106 | /**
107 |  * Generate a timestamped filename for diff results
108 |  */
109 | export function generateDiffFileName(
110 |   beforePath: string,
111 |   afterPath: string,
112 | ): string {
113 |   const beforeName = basename(beforePath, '.contract.json');
114 |   const afterName = basename(afterPath, '.contract.json');
115 |   const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
116 |   return `diff-${beforeName}-vs-${afterName}-${timestamp}.json`;
117 | }
118 | 
```

--------------------------------------------------------------------------------
/docs/writing-custom-tools.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Writing Custom Tools for Angular MCP Server
  2 | 
  3 | > **Goal:** Enable developers to add new, type-safe MCP tools that can be called by LLMs or scripts.
  4 | 
  5 | ---
  6 | 
  7 | ## 1. Anatomy of a Tool
  8 | 
  9 | Every tool consists of **three parts**:
 10 | 
 11 | 1. **Schema** – JSON-schema (via `zod`) that describes the tool name, description and arguments.
 12 | 2. **Handler** – an async function that receives a typed `CallToolRequest`, executes logic, and returns a `CallToolResult`.
 13 | 3. **Registration** – export a `ToolsConfig` object and add it to the category list (`dsTools`, etc.).
 14 | 
 15 | Directory convention
 16 | ```
 17 | packages/angular-mcp-server/src/lib/tools/<category>/my-feature/hello-world.tool.ts
 18 | ```
 19 | File name **must** end with `.tool.ts` so tests & registries can auto-discover it.
 20 | 
 21 | ---
 22 | 
 23 | ## 2. Boilerplate Template
 24 | 
 25 | ```ts
 26 | import { z } from 'zod';
 27 | import { ToolSchemaOptions, ToolsConfig } from '@push-based/models';
 28 | import { createHandler, RESULT_FORMATTERS } from '../shared/utils/handler-helpers.js';
 29 | 
 30 | // 1️⃣ Schema
 31 | const helloWorldSchema: ToolSchemaOptions = {
 32 |   name: 'hello-world',
 33 |   description: 'Echo a friendly greeting',
 34 |   inputSchema: z.object({
 35 |     name: z.string().describe('Name to greet'),
 36 |   }),
 37 |   annotations: {
 38 |     title: 'Hello World',
 39 |   },
 40 | };
 41 | 
 42 | // 2️⃣ Handler (business logic)
 43 | const helloWorldHandler = createHandler<{ name: string }, string>(
 44 |   helloWorldSchema.name,
 45 |   async (params) => {
 46 |     return `Hello, ${params.name}! 👋`;
 47 |   },
 48 |   (result) => RESULT_FORMATTERS.success(result),
 49 | );
 50 | 
 51 | // 3️⃣ Registration
 52 | export const helloWorldTools: ToolsConfig[] = [
 53 |   { schema: helloWorldSchema, handler: helloWorldHandler },
 54 | ];
 55 | ```
 56 | 
 57 | Key points:
 58 | * `createHandler` automatically validates common arguments, injects workspace paths, and formats output.
 59 | * Generic parameters `<{ name: string }, string>` indicate input shape and raw result type.
 60 | * Use `RESULT_FORMATTERS` helpers to produce consistent textual arrays.
 61 | 
 62 | ---
 63 | 
 64 | ## 3. Adding to the Registry
 65 | 
 66 | Open the category file (e.g. `tools/ds/tools.ts`) and spread your array:
 67 | 
 68 | ```ts
 69 | import { helloWorldTools } from './my-feature/hello-world.tool';
 70 | 
 71 | export const dsTools: ToolsConfig[] = [
 72 |   // …existing
 73 |   ...helloWorldTools,
 74 | ];
 75 | ```
 76 | 
 77 | The server will now expose `hello-world` via `list_tools`.
 78 | 
 79 | ---
 80 | 
 81 | ## 4. Parameter Injection
 82 | 
 83 | The server adds workspace-specific paths to every call so you don’t need to pass them manually:
 84 | 
 85 | | Field | Injected Value |
 86 | |-------|----------------|
 87 | | `cwd` | Current working dir (may be overridden) |
 88 | | `workspaceRoot` | `--workspaceRoot` CLI flag |
 89 | | `storybookDocsRoot` | Relative path from CLI flags |
 90 | | `deprecatedCssClassesPath` | Path to deprecated CSS map |
 91 | | `uiRoot` | Path to DS component source |
 92 | 
 93 | Access them inside the handler via the second argument of `createHandler`:
 94 | 
 95 | ```ts
 96 | async (params, ctx) => {
 97 |   console.log('Workspace root:', ctx.workspaceRoot);
 98 | }
 99 | ```
100 | 
101 | ---
102 | 
103 | ## 5. Validation Helpers
104 | 
105 | * `validateCommonInputs` – ensures `directory` is string, `componentName` matches `Ds[A-Z]…` pattern.
106 | * Custom validation: extend the Zod `inputSchema` with additional constraints.
107 | 
108 | ---
109 | 
110 | ## 6. Testing Your Tool
111 | 
112 | 1. **Unit tests** (recommended): import the handler function directly and assert on the returned `CallToolResult`.
113 | 
114 | ---
115 | 
116 | ## 7. Documentation Checklist
117 | 
118 | After publishing a tool:
119 | 
120 | - [ ] Add an entry to `docs/tools.md` with purpose, parameters, output.
121 | 
122 | ---
123 | 
124 | ## 8. Common Pitfalls
125 | 
126 | | Pitfall | Fix |
127 | |---------|-----|
128 | | Tool not listed via `list_tools` | Forgot to spread into `dsTools` array. |
129 | | “Unknown argument” error | Ensure `inputSchema` matches argument names exactly. |
130 | | Long-running sync FS operations | Use async `fs/promises` or worker threads to keep server responsive. |
131 | 
132 | ---
133 | 
134 | Happy tooling! 🎉 
```

--------------------------------------------------------------------------------
/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/regex-helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Shared regex utilities for DS tools
  3 |  * Consolidates regex patterns used across multiple tools to avoid duplication
  4 |  */
  5 | 
  6 | // CSS Processing Regexes
  7 | export const CSS_REGEXES = {
  8 |   /**
  9 |    * Escapes special regex characters in a string for safe use in regex patterns
 10 |    */
 11 |   escape: (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
 12 | 
 13 |   /**
 14 |    * Creates a regex to match CSS classes from a list of class names
 15 |    */
 16 |   createClassMatcher: (classNames: string[]): RegExp =>
 17 |     new RegExp(
 18 |       `\\.(${classNames.map(CSS_REGEXES.escape).join('|')})(?![\\w-])`,
 19 |       'g',
 20 |     ),
 21 | 
 22 |   /**
 23 |    * Style file extensions
 24 |    */
 25 |   STYLE_EXTENSIONS: /\.(css|scss|sass|less)$/i,
 26 | } as const;
 27 | 
 28 | // Path Processing Regexes
 29 | export const PATH_REGEXES = {
 30 |   /**
 31 |    * Normalizes file paths to use forward slashes
 32 |    */
 33 |   normalizeToUnix: (path: string): string => path.replace(/\\/g, '/'),
 34 | 
 35 |   /**
 36 |    * Removes directory prefix from file paths
 37 |    */
 38 |   removeDirectoryPrefix: (filePath: string, directory: string): string => {
 39 |     const normalizedFilePath = PATH_REGEXES.normalizeToUnix(filePath);
 40 |     const normalizedDirectory = PATH_REGEXES.normalizeToUnix(directory);
 41 |     const directoryPrefix = normalizedDirectory.endsWith('/')
 42 |       ? normalizedDirectory
 43 |       : normalizedDirectory + '/';
 44 | 
 45 |     return normalizedFilePath.startsWith(directoryPrefix)
 46 |       ? normalizedFilePath.replace(directoryPrefix, '')
 47 |       : normalizedFilePath;
 48 |   },
 49 | } as const;
 50 | 
 51 | // Component Name Processing Regexes
 52 | export const COMPONENT_REGEXES = {
 53 |   /**
 54 |    * Converts DS component name to kebab-case
 55 |    */
 56 |   toKebabCase: (componentName: string): string =>
 57 |     componentName
 58 |       .replace(/^Ds/, '')
 59 |       .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
 60 |       .toLowerCase(),
 61 | 
 62 |   /**
 63 |    * Validates DS component name format (accepts both "DsButton" and "Button" formats)
 64 |    */
 65 |   isValidDsComponent: (name: string): boolean =>
 66 |     /^(Ds)?[A-Z][a-zA-Z0-9]*$/.test(name),
 67 | 
 68 |   /**
 69 |    * Extracts component name from coverage titles
 70 |    */
 71 |   extractFromCoverageTitle: (title: string): string | null => {
 72 |     const match = title.match(/Usage coverage for (\w+) component/);
 73 |     return match ? match[1] : null;
 74 |   },
 75 | } as const;
 76 | 
 77 | // Import/Dependency Processing Regexes (from component-usage-graph)
 78 | export const IMPORT_REGEXES = {
 79 |   ES6_IMPORT:
 80 |     /import\s+(?:(?:[\w\s{},*]+\s+from\s+)?['"`]([^'"`]+)['"`]|['"`]([^'"`]+)['"`])/g,
 81 |   COMMONJS_REQUIRE: /require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
 82 |   DYNAMIC_IMPORT: /import\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
 83 |   CSS_IMPORT: /@import\s+['"`]([^'"`]+)['"`]/g,
 84 |   CSS_URL: /url\s*\(\s*['"`]?([^'"`)]+)['"`]?\s*\)/g,
 85 |   ANGULAR_COMPONENT_DECORATOR: /@Component/,
 86 | 
 87 |   /**
 88 |    * Creates cached regex for component imports
 89 |    */
 90 |   createComponentImportRegex: (componentName: string): RegExp =>
 91 |     new RegExp(`import[\\s\\S]*?\\b${componentName}\\b[\\s\\S]*?from`, 'gm'),
 92 | 
 93 |   /**
 94 |    * Creates combined regex for multiple component imports
 95 |    */
 96 |   createCombinedComponentImportRegex: (componentNames: string[]): RegExp =>
 97 |     new RegExp(
 98 |       `import[\\s\\S]*?\\b(${componentNames.join('|')})\\b[\\s\\S]*?from`,
 99 |       'gm',
100 |     ),
101 | } as const;
102 | 
103 | // Regex Cache Management
104 | const REGEX_CACHE = new Map<string, RegExp>();
105 | 
106 | export const REGEX_CACHE_UTILS = {
107 |   /**
108 |    * Gets or creates a cached regex
109 |    */
110 |   getOrCreate: (key: string, factory: () => RegExp): RegExp => {
111 |     let regex = REGEX_CACHE.get(key);
112 |     if (!regex) {
113 |       regex = factory();
114 |       REGEX_CACHE.set(key, regex);
115 |     }
116 |     regex.lastIndex = 0; // Reset for consistent behavior
117 |     return regex;
118 |   },
119 | 
120 |   /**
121 |    * Clears the regex cache
122 |    */
123 |   clear: (): void => REGEX_CACHE.clear(),
124 | 
125 |   /**
126 |    * Gets cache statistics
127 |    */
128 |   getStats: () => ({ size: REGEX_CACHE.size }),
129 | } as const;
130 | 
```

--------------------------------------------------------------------------------
/packages/shared/ds-component-coverage/ai/EXAMPLES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Examples
  2 | 
  3 | ## 1 — Basic plugin setup
  4 | 
  5 | > Create a DS component coverage plugin to detect deprecated CSS classes.
  6 | 
  7 | ```ts
  8 | import { dsComponentCoveragePlugin } from '@push-based/ds-component-coverage';
  9 | 
 10 | const plugin = dsComponentCoveragePlugin({
 11 |   directory: './src/app',
 12 |   dsComponents: [
 13 |     {
 14 |       componentName: 'DsButton',
 15 |       deprecatedCssClasses: ['btn', 'button-primary'],
 16 |       docsUrl: 'https://design-system.com/button',
 17 |     },
 18 |   ],
 19 | });
 20 | 
 21 | console.log(plugin.slug); // → 'ds-component-coverage'
 22 | ```
 23 | 
 24 | ---
 25 | 
 26 | ## 2 — Running coverage analysis
 27 | 
 28 | > Execute the runner function to analyze Angular components for deprecated CSS usage.
 29 | 
 30 | ```ts
 31 | import { runnerFunction } from '@push-based/ds-component-coverage';
 32 | 
 33 | const results = await runnerFunction({
 34 |   directory: './src/app',
 35 |   dsComponents: [
 36 |     {
 37 |       componentName: 'DsCard',
 38 |       deprecatedCssClasses: ['card', 'card-header', 'card-body'],
 39 |     },
 40 |   ],
 41 | });
 42 | 
 43 | console.log(results.length); // → Number of audit outputs
 44 | ```
 45 | 
 46 | ---
 47 | 
 48 | ## 3 — Multiple component tracking
 49 | 
 50 | > Track multiple design system components and their deprecated classes.
 51 | 
 52 | ```ts
 53 | import { dsComponentCoveragePlugin } from '@push-based/ds-component-coverage';
 54 | 
 55 | const plugin = dsComponentCoveragePlugin({
 56 |   directory: './src',
 57 |   dsComponents: [
 58 |     {
 59 |       componentName: 'DsButton',
 60 |       deprecatedCssClasses: ['btn', 'button'],
 61 |       docsUrl: 'https://design-system.com/button',
 62 |     },
 63 |     {
 64 |       componentName: 'DsModal',
 65 |       deprecatedCssClasses: ['modal', 'dialog'],
 66 |       docsUrl: 'https://design-system.com/modal',
 67 |     },
 68 |     {
 69 |       componentName: 'DsInput',
 70 |       deprecatedCssClasses: ['form-control', 'input-field'],
 71 |     },
 72 |   ],
 73 | });
 74 | 
 75 | console.log(plugin.audits.length); // → 3 audits (one per component)
 76 | ```
 77 | 
 78 | ---
 79 | 
 80 | ## 4 — Code Pushup integration
 81 | 
 82 | > Integrate with Code Pushup for automated design system migration tracking.
 83 | 
 84 | ```ts
 85 | import {
 86 |   dsComponentCoveragePlugin,
 87 |   getAngularDsUsageCategoryRefs,
 88 | } from '@push-based/ds-component-coverage';
 89 | 
 90 | const dsComponents = [
 91 |   {
 92 |     componentName: 'DsBadge',
 93 |     deprecatedCssClasses: ['badge', 'label'],
 94 |     docsUrl: 'https://design-system.com/badge',
 95 |   },
 96 | ];
 97 | 
 98 | // Use in code-pushup.config.ts
 99 | export default {
100 |   plugins: [
101 |     dsComponentCoveragePlugin({
102 |       directory: './src/app',
103 |       dsComponents,
104 |     }),
105 |   ],
106 |   categories: [
107 |     {
108 |       slug: 'design-system-usage',
109 |       title: 'Design System Usage',
110 |       description: 'Usage of design system components',
111 |       refs: getAngularDsUsageCategoryRefs(dsComponents),
112 |     },
113 |   ],
114 | };
115 | ```
116 | 
117 | ---
118 | 
119 | ## 5 — Category references for reporting
120 | 
121 | > Generate category references for organizing audit results.
122 | 
123 | ```ts
124 | import { getAngularDsUsageCategoryRefs } from '@push-based/ds-component-coverage';
125 | 
126 | const dsComponents = [
127 |   {
128 |     componentName: 'DsButton',
129 |     deprecatedCssClasses: ['btn'],
130 |   },
131 |   {
132 |     componentName: 'DsCard',
133 |     deprecatedCssClasses: ['card'],
134 |   },
135 | ];
136 | 
137 | const categoryRefs = getAngularDsUsageCategoryRefs(dsComponents);
138 | console.log(categoryRefs); // → Array of category references for each component
139 | ```
140 | 
141 | ---
142 | 
143 | ## 6 — Custom configuration with schema validation
144 | 
145 | > Use schema validation to ensure proper configuration structure.
146 | 
147 | ```ts
148 | import {
149 |   ComponentCoverageRunnerOptionsSchema,
150 |   ComponentReplacementSchema,
151 | } from '@push-based/ds-component-coverage';
152 | 
153 | // Validate individual component replacement
154 | const componentConfig = ComponentReplacementSchema.parse({
155 |   componentName: 'DsAlert',
156 |   deprecatedCssClasses: ['alert', 'notification'],
157 |   docsUrl: 'https://design-system.com/alert',
158 | });
159 | 
160 | // Validate full runner options
161 | const runnerConfig = ComponentCoverageRunnerOptionsSchema.parse({
162 |   directory: './src/app',
163 |   dsComponents: [componentConfig],
164 | });
165 | 
166 | console.log(runnerConfig.directory); // → './src/app'
167 | ```
168 | 
```

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

```typescript
  1 | import { ChildProcess } from 'node:child_process';
  2 | import { describe, expect, it, vi } from 'vitest';
  3 | import { getAsyncProcessRunnerConfig } from '@push-based/testing-utils';
  4 | import { type ProcessObserver, executeProcess } from './execute-process.js';
  5 | 
  6 | describe('executeProcess', () => {
  7 |   const spyObserver: ProcessObserver = {
  8 |     onStdout: vi.fn(),
  9 |     onStderr: vi.fn(),
 10 |     onError: vi.fn(),
 11 |     onComplete: vi.fn(),
 12 |   };
 13 |   const errorSpy = vi.fn();
 14 | 
 15 |   beforeEach(() => {
 16 |     vi.clearAllMocks();
 17 |   });
 18 | 
 19 |   it('should work with node command `node -v`', async () => {
 20 |     const processResult = await executeProcess({
 21 |       command: `node`,
 22 |       args: ['-v'],
 23 |       observer: spyObserver,
 24 |     });
 25 | 
 26 |     // Note: called once or twice depending on environment (2nd time for a new line)
 27 |     expect(spyObserver.onStdout).toHaveBeenCalled();
 28 |     expect(spyObserver.onComplete).toHaveBeenCalledOnce();
 29 |     expect(spyObserver.onError).not.toHaveBeenCalled();
 30 |     expect(processResult.stdout).toMatch(/v\d{1,2}(\.\d{1,2}){0,2}/);
 31 |   });
 32 | 
 33 |   it('should work with npx command `npx --help`', async () => {
 34 |     const processResult = await executeProcess({
 35 |       command: `npx`,
 36 |       args: ['--help'],
 37 |       observer: spyObserver,
 38 |     });
 39 |     expect(spyObserver.onStdout).toHaveBeenCalledOnce();
 40 |     expect(spyObserver.onComplete).toHaveBeenCalledOnce();
 41 |     expect(spyObserver.onError).not.toHaveBeenCalled();
 42 |     expect(processResult.stdout).toContain('npm exec');
 43 |   });
 44 | 
 45 |   it('should work with script `node custom-script.js`', async () => {
 46 |     const processResult = await executeProcess({
 47 |       ...getAsyncProcessRunnerConfig({ interval: 10, runs: 4 }),
 48 |       observer: spyObserver,
 49 |     }).catch(errorSpy);
 50 | 
 51 |     expect(errorSpy).not.toHaveBeenCalled();
 52 |     expect(processResult.stdout).toContain('process:complete');
 53 |     expect(spyObserver.onStdout).toHaveBeenCalledTimes(6); // intro + 4 runs + complete
 54 |     expect(spyObserver.onError).not.toHaveBeenCalled();
 55 |     expect(spyObserver.onComplete).toHaveBeenCalledOnce();
 56 |   });
 57 | 
 58 |   it('should work with async script `node custom-script.js` that throws an error', async () => {
 59 |     const processResult = await executeProcess({
 60 |       ...getAsyncProcessRunnerConfig({
 61 |         interval: 10,
 62 |         runs: 1,
 63 |         throwError: true,
 64 |       }),
 65 |       observer: spyObserver,
 66 |     }).catch(errorSpy);
 67 | 
 68 |     expect(errorSpy).toHaveBeenCalledOnce();
 69 |     expect(processResult).toBeUndefined();
 70 |     expect(spyObserver.onStdout).toHaveBeenCalledTimes(2); // intro + 1 run before error
 71 |     expect(spyObserver.onStdout).toHaveBeenLastCalledWith(
 72 |       'process:update\n',
 73 |       expect.any(ChildProcess),
 74 |     );
 75 |     expect(spyObserver.onStderr).toHaveBeenCalled();
 76 |     expect(spyObserver.onStderr).toHaveBeenCalledWith(
 77 |       expect.stringContaining('dummy-error'),
 78 |       expect.any(ChildProcess),
 79 |     );
 80 |     expect(spyObserver.onError).toHaveBeenCalledOnce();
 81 |     expect(spyObserver.onComplete).not.toHaveBeenCalled();
 82 |   });
 83 | 
 84 |   it('should successfully exit process after an error is thrown when ignoreExitCode is set', async () => {
 85 |     const processResult = await executeProcess({
 86 |       ...getAsyncProcessRunnerConfig({
 87 |         interval: 10,
 88 |         runs: 1,
 89 |         throwError: true,
 90 |       }),
 91 |       observer: spyObserver,
 92 |       ignoreExitCode: true,
 93 |     }).catch(errorSpy);
 94 | 
 95 |     expect(errorSpy).not.toHaveBeenCalled();
 96 |     expect(processResult.code).toBe(1);
 97 |     expect(processResult.stdout).toContain('process:update');
 98 |     expect(processResult.stderr).toContain('dummy-error');
 99 |     expect(spyObserver.onStdout).toHaveBeenCalledTimes(2); // intro + 1 run before error
100 |     expect(spyObserver.onStderr).toHaveBeenCalled();
101 |     expect(spyObserver.onError).not.toHaveBeenCalled();
102 |     expect(spyObserver.onComplete).toHaveBeenCalledOnce();
103 |   });
104 | });
105 | 
```

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

```typescript
  1 | /* eslint-disable prefer-const */
  2 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  3 | 
  4 | import {
  5 |   extractComponentNameFromFile,
  6 |   formatBytes,
  7 |   getTimeAgo,
  8 |   formatContractsByComponent,
  9 | } from '../utils/contract-list-utils.js';
 10 | 
 11 | import type { ContractFileInfo } from '../models/types.js';
 12 | 
 13 | function advanceSystemTimeTo(fixed: Date) {
 14 |   vi.useFakeTimers();
 15 |   vi.setSystemTime(fixed);
 16 | }
 17 | 
 18 | function restoreSystemTime() {
 19 |   vi.useRealTimers();
 20 | }
 21 | 
 22 | describe('contract-list-utils', () => {
 23 |   afterEach(() => {
 24 |     restoreSystemTime();
 25 |   });
 26 | 
 27 |   describe('extractComponentNameFromFile', () => {
 28 |     it('extracts simple component names', () => {
 29 |       expect(
 30 |         extractComponentNameFromFile('foo-20240208T123456.contract.json'),
 31 |       ).toBe('foo');
 32 |       expect(extractComponentNameFromFile('bar.contract.json')).toBe('bar');
 33 |     });
 34 | 
 35 |     it('handles multi-part component names', () => {
 36 |       const file = 'my-super-button-20240208T123456.contract.json';
 37 |       expect(extractComponentNameFromFile(file)).toBe('my');
 38 |     });
 39 |   });
 40 | 
 41 |   describe('formatBytes', () => {
 42 |     it('formats bytes into readable units', () => {
 43 |       expect(formatBytes(0)).toBe('0 B');
 44 |       expect(formatBytes(512)).toBe('512 B');
 45 |       expect(formatBytes(2048)).toBe('2 KB');
 46 |       expect(formatBytes(1024 * 1024)).toBe('1 MB');
 47 |     });
 48 |   });
 49 | 
 50 |   describe('getTimeAgo', () => {
 51 |     beforeEach(() => {
 52 |       advanceSystemTimeTo(new Date('2024-02-10T12:00:00Z'));
 53 |     });
 54 | 
 55 |     it('returns minutes ago for <1h', () => {
 56 |       const ts = new Date('2024-02-10T11:45:00Z').toISOString();
 57 |       expect(getTimeAgo(ts)).toBe('15m ago');
 58 |     });
 59 | 
 60 |     it('returns hours ago for <24h', () => {
 61 |       const ts = new Date('2024-02-10T08:00:00Z').toISOString();
 62 |       expect(getTimeAgo(ts)).toBe('4h ago');
 63 |     });
 64 | 
 65 |     it('returns days ago for <7d', () => {
 66 |       const ts = new Date('2024-02-07T12:00:00Z').toISOString();
 67 |       expect(getTimeAgo(ts)).toBe('3d ago');
 68 |     });
 69 | 
 70 |     it('returns locale date for older timestamps', () => {
 71 |       const ts = new Date('2023-12-25T00:00:00Z').toISOString();
 72 |       expect(getTimeAgo(ts)).toContain('2023');
 73 |     });
 74 |   });
 75 | 
 76 |   describe('formatContractsByComponent', () => {
 77 |     beforeEach(() => {
 78 |       advanceSystemTimeTo(new Date('2024-02-10T12:00:00Z'));
 79 |     });
 80 | 
 81 |     it('groups contracts by component and formats output', () => {
 82 |       const contracts: ContractFileInfo[] = [
 83 |         {
 84 |           fileName: 'foo-20240210T090000.contract.json',
 85 |           filePath: '/contracts/foo-20240210T090000.contract.json',
 86 |           componentName: 'foo',
 87 |           timestamp: new Date('2024-02-10T09:00:00Z').toISOString(),
 88 |           hash: 'abcdef1234567890',
 89 |           size: '5 KB',
 90 |         },
 91 |         {
 92 |           fileName: 'foo-20240209T090000.contract.json',
 93 |           filePath: '/contracts/foo-20240209T090000.contract.json',
 94 |           componentName: 'foo',
 95 |           timestamp: new Date('2024-02-09T09:00:00Z').toISOString(),
 96 |           hash: '123456abcdef7890',
 97 |           size: '4 KB',
 98 |         },
 99 |         {
100 |           fileName: 'bar-20240210T090000.contract.json',
101 |           filePath: '/contracts/bar-20240210T090000.contract.json',
102 |           componentName: 'bar',
103 |           timestamp: new Date('2024-02-10T09:00:00Z').toISOString(),
104 |           hash: 'fedcba9876543210',
105 |           size: '6 KB',
106 |         },
107 |       ];
108 | 
109 |       const output = formatContractsByComponent(contracts);
110 | 
111 |       expect(output).toEqual(
112 |         expect.arrayContaining([
113 |           expect.stringMatching(/^🎯 foo:/),
114 |           expect.stringMatching(/^🎯 bar:/),
115 |         ]),
116 |       );
117 | 
118 |       expect(output).toEqual(
119 |         expect.arrayContaining([
120 |           expect.stringContaining('foo-20240210T090000.contract.json'),
121 |           expect.stringContaining('bar-20240210T090000.contract.json'),
122 |         ]),
123 |       );
124 |     });
125 |   });
126 | });
127 | 
```
Page 4/10FirstPrevNextLast