From 65e4bb2cfd92bf065c6df11ee744d1901a2f3bc6 Mon Sep 17 00:00:00 2001
From: Sigkoudis Efthymios <32438161+SigoudisEftimis@users.noreply.github.com>
Date: Fri, 8 May 2026 20:10:12 +0300
Subject: [PATCH 1/4] Release v0.1.0-beta (#24)
* chore: Bump version to 0.1.0-beta.1 across all packages and update package.json files
- Update version to 0.1.0-beta.1 in package.json for all related packages.
- Adjust package.json files to ensure consistency in dependencies and scripts.
- Reflect changes in subproject commit status to indicate a dirty state.
* Pre release v0.1.0 beta (#21)
* Enhance README with updated project description, features, and installation instructions. Introduce new output formats and examples for ngcompass analysis. Update configuration section to include presets and improve clarity on usage. Reflect changes in subproject commit status to indicate a dirty state.
* Update README logo and adjust HTML reporter tests for brand logo verification
- Replace logo image in README with a smaller version for better display.
- Modify HTML reporter tests to check for a specific base64 encoded image string in the output, ensuring accurate rendering of the brand logo.
* Enhance ngcompass analysis command and README documentation
- Introduce a new UI output format for ngcompass analysis, providing a visual report with search and filter capabilities.
- Update the README to include instructions for using the new UI format and clarify existing output formats.
- Modify the analyze command to improve the description of the --force option and ensure consistent reporting behavior across different formats.
- Improve error handling and messaging in the rules command for better user guidance.
- Update tests to verify the new summary output order and ensure accurate reporting behavior.
* Update README to reflect changes in ngcompass configuration and output formats
- Replace "HTML report" with "UI report" for clarity in the documentation.
- Add new preset options for ngcompass configuration, enhancing user guidance.
- Update the rules section to include additional rules and their categories.
- Modify output format descriptions to include the new UI format.
* Add PackedFileDriver for optimized caching and enhance result cache handling
- Introduce a new PackedFileDriver that consolidates cache entries into a single v8-serialized file, improving write performance.
- Update createCacheContext to utilize the new PackedFileDriver.
- Enhance result cache logic to support bulk writes when using drivers that implement the bulkSet method, optimizing the flushing of pending writes.
- Modify AsyncDriver interface to include an optional bulkSet method for atomic I/O operations.
- Update various services and commands to accommodate the new caching strategy and improve overall performance.
* Pre release v0.1.0 beta (#22)
* Enhance README with updated project description, features, and installation instructions. Introduce new output formats and examples for ngcompass analysis. Update configuration section to include presets and improve clarity on usage. Reflect changes in subproject commit status to indicate a dirty state.
* Update README logo and adjust HTML reporter tests for brand logo verification
- Replace logo image in README with a smaller version for better display.
- Modify HTML reporter tests to check for a specific base64 encoded image string in the output, ensuring accurate rendering of the brand logo.
* Enhance ngcompass analysis command and README documentation
- Introduce a new UI output format for ngcompass analysis, providing a visual report with search and filter capabilities.
- Update the README to include instructions for using the new UI format and clarify existing output formats.
- Modify the analyze command to improve the description of the --force option and ensure consistent reporting behavior across different formats.
- Improve error handling and messaging in the rules command for better user guidance.
- Update tests to verify the new summary output order and ensure accurate reporting behavior.
* Update README to reflect changes in ngcompass configuration and output formats
- Replace "HTML report" with "UI report" for clarity in the documentation.
- Add new preset options for ngcompass configuration, enhancing user guidance.
- Update the rules section to include additional rules and their categories.
- Modify output format descriptions to include the new UI format.
* Add PackedFileDriver for optimized caching and enhance result cache handling
- Introduce a new PackedFileDriver that consolidates cache entries into a single v8-serialized file, improving write performance.
- Update createCacheContext to utilize the new PackedFileDriver.
- Enhance result cache logic to support bulk writes when using drivers that implement the bulkSet method, optimizing the flushing of pending writes.
- Modify AsyncDriver interface to include an optional bulkSet method for atomic I/O operations.
- Update various services and commands to accommodate the new caching strategy and improve overall performance.
* Remove deliberately broken ngcompass configuration file and update test expectations for suggestion line format in config-reporter.spec.ts
* Update CI workflow to improve console output checks and reflect dirty subproject state
- Modify grep commands in the CI workflow to use extended regex for better matching of analysis output.
- Update subproject commit status to indicate a dirty state.
* Update AppComponent to display title and reflect changes in subproject state
- Modify AppComponent template to show a title instead of a button.
- Add title method to return a greeting.
- Update subproject commit status to indicate a dirty state.
* Update AppComponent method name and reflect dirty subproject state
- Rename title method to getTitle in AppComponent for clarity.
- Update subproject commit status to indicate a dirty state.
* Enhance ngcompass configuration and reporting features
- Update README to include additional file types in the configuration, improving analysis coverage.
- Modify the console and JSON reporters to incorporate new summary statistics, including failOnSeverity and maxWarnings.
- Refactor the HTML and SARIF reporters to display analysis status and results more clearly.
- Adjust tests to validate new reporting behavior and ensure accurate output for various scenarios.
* Add JSON loader to configuration discovery process
- Introduce a new jsonLoader function to handle JSON file parsing.
- Update findAndLoadConfig to support loading .json files and files without extensions using the new jsonLoader, enhancing configuration flexibility.
* Enhance configuration discovery and console reporting features
- Update discovery.ts to improve the jitiLoader function by dynamically determining the config package entry based on the current file's URL.
- Modify init.spec.ts to test loading configuration from a directory without local ngcompass dependencies, ensuring proper functionality.
- Introduce a new buildCompactRecommendationLine function in console-reporter.ts to align recommendation messages with violation messages for better readability.
- Add a test case in console-reporter.spec.ts to verify the alignment of compact recommendations with their corresponding violation messages.
* Update init.spec.ts timeout and reflect dirty subproject state
- Adjust the timeout in init.spec.ts to 15000 milliseconds for improved test reliability.
- Update subproject commit status to indicate a dirty state.
* Refactor init.spec.ts and update subproject state
- Remove outdated test case for loading configuration without local ngcompass dependencies in init.spec.ts to streamline tests.
- Update subproject commit status to reflect a dirty state.
* Refactor analysis reporting and update status labels
- Introduce a Spinner for running analysis to enhance user experience.
- Refactor createFileProgressLogger to accept a custom line writer.
- Update analysis status labels from 'PASS WITH WARNINGS' to 'WARN' across various reporters for consistency.
- Adjust tests to reflect the updated status labels in console, HTML, JSON, and SARIF reporters.
- Update subproject commit status to indicate a dirty state.
* Enhance README and reporting options for ngcompass
- Update README to include new HTML report format options and clarify the UI report description.
- Modify analyze command options to support HTML output and improve documentation on global and analyze options.
- Remove verbose option from ConsoleReporter to streamline reporting.
- Adjust smoke tests to validate JSON output structure and update rule detail names for consistency.
* Update console-reporter.ts to remove redundant stack header line skip constant and reflect dirty subproject state
* Update analyze command to remove unused debug options and reflect dirty subproject state
* Remove BETA_TEST_PLAN.md and clean up unused scripts (#23)
* Remove BETA_TEST_PLAN.md and clean up unused scripts
- Deleted the BETA_TEST_PLAN.md file as it is no longer needed.
- Removed scripts for adding side effects and replacing imports, which were not utilized in the current project structure.
- Updated the subproject commit status to reflect a dirty state.
* Remove unused worktree and configuration files for the nice-taussig package
- Delete the entire nice-taussig package, including its package.json, lock files, TypeScript configurations, and source files.
- Update .gitignore to include the new worktree directory.
- Remove the silly-black worktree as part of the cleanup process.
* Update package.json and presets to enhance functionality and security
- Removed repository and bugs sections from package.json, updated homepage URL.
- Added new scripts for package validation and prerelease checks.
- Introduced 'security' and 'ssr' presets in the rules configuration for improved analysis.
- Enhanced existing presets with additional rules and descriptions for clarity.
- Updated smoke tests to include new fixture-based content tests and validation scenarios.
---
.claude/settings.local.json | 5 +-
.claude/worktrees/nice-taussig/package.json | 71 -
.../nice-taussig/packages/ast/package.json | 50 -
.../packages/ast/parser-output-utf8.json | 1518 -----
.../packages/ast/parser-output.json | Bin 124652 -> 0 bytes
.../ast/src/analyzers/component-analyzer.ts | 361 --
.../packages/ast/src/analyzers/index.ts | 21 -
.../ast/src/analyzers/template-analyzer.ts | 187 -
.../packages/ast/src/ast/index.ts | 2 -
.../packages/ast/src/ast/matchers.ts | 309 -
.../packages/ast/src/ast/types.ts | 171 -
.../nice-taussig/packages/ast/src/index.ts | 20 -
.../packages/ast/src/node-streams.ts | 167 -
.../packages/ast/src/parsers/css.ts | 49 -
.../packages/ast/src/parsers/html.ts | 45 -
.../packages/ast/src/parsers/index.ts | 5 -
.../ast/src/parsers/template-extractor.ts | 143 -
.../packages/ast/src/parsers/ts.ts | 32 -
.../nice-taussig/packages/ast/src/visitor.ts | 70 -
.../analyzers/component-analyzer.test.ts | 70 -
.../tests/analyzers/template-analyzer.test.ts | 41 -
.../packages/ast/tests/ast/matchers.test.ts | 60 -
.../packages/ast/tests/node-streams.test.ts | 63 -
.../packages/ast/tests/parsers/css.test.ts | 17 -
.../packages/ast/tests/parsers/html.test.ts | 16 -
.../tests/parsers/template-extractor.test.ts | 47 -
.../packages/ast/tests/parsers/ts.test.ts | 16 -
.../packages/ast/tests/visitor.test.ts | 44 -
.../nice-taussig/packages/ast/tsconfig.json | 14 -
.../nice-taussig/packages/ast/tsup.config.ts | 5 -
.../nice-taussig/packages/cache/package.json | 52 -
.../packages/cache/src/constants.ts | 8 -
.../packages/cache/src/context.ts | 190 -
.../packages/cache/src/drivers/atomic.ts | 211 -
.../packages/cache/src/drivers/disk.ts | 87 -
.../packages/cache/src/drivers/json-file.ts | 117 -
.../packages/cache/src/drivers/memory.ts | 45 -
.../packages/cache/src/drivers/types.ts | 55 -
.../packages/cache/src/env-fingerprint.ts | 205 -
.../packages/cache/src/hashing.ts | 37 -
.../nice-taussig/packages/cache/src/index.ts | 24 -
.../packages/cache/src/key-context.ts | 135 -
.../packages/cache/src/services/ast-cache.ts | 60 -
.../cache/src/services/config-cache.ts | 24 -
.../packages/cache/src/services/file-cache.ts | 18 -
.../packages/cache/src/services/hashing.ts | 15 -
.../packages/cache/src/services/meta-cache.ts | 33 -
.../packages/cache/src/services/plan-cache.ts | 21 -
.../cache/src/services/result-cache.ts | 292 -
.../cache/src/services/source-cache.ts | 25 -
.../nice-taussig/packages/cache/src/types.ts | 65 -
.../packages/cache/src/utils/fs.ts | 39 -
.../cache/src/utils/stable-serialize.ts | 123 -
.../packages/cache/tests/context.test.ts | 57 -
.../cache/tests/drivers/atomic.test.ts | 53 -
.../packages/cache/tests/drivers/disk.test.ts | 49 -
.../cache/tests/drivers/json-file.test.ts | 43 -
.../cache/tests/drivers/memory.test.ts | 40 -
.../cache/tests/env-fingerprint.test.ts | 25 -
.../packages/cache/tests/hashing.test.ts | 24 -
.../packages/cache/tests/key-context.test.ts | 33 -
.../cache/tests/services/result-cache.test.ts | 72 -
.../cache/tests/services/services.test.ts | 69 -
.../packages/cache/tests/utils/fs.test.ts | 55 -
.../tests/utils/stable-serialize.test.ts | 61 -
.../nice-taussig/packages/cache/tsconfig.json | 14 -
.../packages/cache/tsup.config.ts | 5 -
.../nice-taussig/packages/cli/package.json | 60 -
.../packages/cli/src/bin/ngcompass.ts | 153 -
.../packages/cli/src/commands/analyze.ts | 298 -
.../packages/cli/src/commands/cache.ts | 61 -
.../packages/cli/src/commands/config.ts | 34 -
.../packages/cli/src/commands/exit.ts | 25 -
.../packages/cli/src/commands/index.ts | 14 -
.../packages/cli/src/commands/init.ts | 31 -
.../nice-taussig/packages/cli/src/index.ts | 1 -
.../cli/tests/commands/analyze.test.ts | 145 -
.../packages/cli/tests/commands/cache.test.ts | 92 -
.../cli/tests/commands/config.test.ts | 76 -
.../packages/cli/tests/commands/init.test.ts | 81 -
.../nice-taussig/packages/cli/tsconfig.json | 16 -
.../nice-taussig/packages/cli/tsup.config.ts | 5 -
.../nice-taussig/packages/common/package.json | 49 -
.../packages/common/src/ast/utils.ts | 103 -
.../packages/common/src/constants.ts | 33 -
.../packages/common/src/errors.ts | 147 -
.../nice-taussig/packages/common/src/index.ts | 29 -
.../packages/common/src/interfaces.ts | 341 -
.../packages/common/src/logger.ts | 242 -
.../nice-taussig/packages/common/src/types.ts | 634 --
.../nice-taussig/packages/common/src/utils.ts | 197 -
.../packages/common/src/utils/locator.ts | 38 -
.../common/src/utils/stable-serialize.ts | 123 -
.../packages/common/tests/logger.test.ts | 396 --
.../packages/common/tests/utils.test.ts | 212 -
.../packages/common/tsconfig.json | 14 -
.../packages/common/tsup.config.ts | 5 -
.../nice-taussig/packages/config/package.json | 52 -
.../config/src/actions/healthcheck.ts | 29 -
.../packages/config/src/actions/init.ts | 218 -
.../packages/config/src/health/checks/base.ts | 93 -
.../config/src/health/checks/cross-fields.ts | 52 -
.../config/src/health/checks/deprecated.ts | 55 -
.../config/src/health/checks/extends.ts | 131 -
.../config/src/health/checks/globs.ts | 159 -
.../config/src/health/checks/index.ts | 9 -
.../config/src/health/checks/paths.ts | 115 -
.../config/src/health/checks/profiles.ts | 113 -
.../config/src/health/checks/rules.ts | 84 -
.../packages/config/src/health/constants.ts | 14 -
.../packages/config/src/health/context.ts | 19 -
.../packages/config/src/health/enricher.ts | 140 -
.../packages/config/src/health/index.ts | 7 -
.../packages/config/src/health/messages.ts | 214 -
.../packages/config/src/health/types.ts | 34 -
.../packages/config/src/health/validator.ts | 308 -
.../nice-taussig/packages/config/src/index.ts | 9 -
.../packages/config/src/loaders/discovery.ts | 94 -
.../packages/config/src/loaders/loader.ts | 99 -
.../packages/config/src/plugin-loader.ts | 233 -
.../packages/config/src/schemas/defaults.ts | 54 -
.../packages/config/src/schemas/schema.ts | 154 -
.../packages/config/tests/config-core.spec.ts | 185 -
.../config/tests/extends-validation.spec.ts | 143 -
.../config/tests/semantic-isolation.spec.ts | 173 -
.../packages/config/tsconfig.json | 14 -
.../packages/config/tsup.config.ts | 5 -
.../nice-taussig/packages/engine/package.json | 52 -
.../packages/engine/src/analysis-context.ts | 194 -
.../packages/engine/src/analysis-stats.ts | 61 -
.../packages/engine/src/constants.ts | 59 -
.../nice-taussig/packages/engine/src/index.ts | 56 -
.../packages/engine/src/orchestrator.ts | 328 -
.../engine/src/project-context-builder.ts | 657 --
.../engine/src/rule-context-factory.ts | 310 -
.../packages/engine/src/rule-executor.ts | 75 -
.../packages/engine/src/rule-handler.ts | 171 -
.../packages/engine/src/runner.ts | 146 -
.../packages/engine/src/single-pass-engine.ts | 252 -
.../packages/engine/src/spinner.ts | 47 -
.../packages/engine/src/type-aware-context.ts | 110 -
.../nice-taussig/packages/engine/src/types.ts | 246 -
.../packages/engine/src/visitor-registry.ts | 107 -
.../packages/engine/src/worker-pool.ts | 225 -
.../engine/tests/analysis-context.spec.ts | 144 -
.../engine/tests/analysis-stats.spec.ts | 187 -
.../packages/engine/tests/constants.spec.ts | 89 -
.../engine/tests/orchestrator.spec.ts | 232 -
.../engine/tests/rule-context-factory.spec.ts | 176 -
.../engine/tests/rule-executor.spec.ts | 102 -
.../engine/tests/rule-handler.spec.ts | 200 -
.../packages/engine/tests/runner.spec.ts | 261 -
.../engine/tests/single-pass-engine.spec.ts | 235 -
.../engine/tests/visitor-registry.spec.ts | 202 -
.../packages/engine/tests/worker-pool.spec.ts | 162 -
.../packages/engine/tsc_errors.txt | Bin 208490 -> 0 bytes
.../packages/engine/tsc_errors_utf8.txt | 847 ---
.../packages/engine/tsconfig.json | 14 -
.../packages/engine/tsup.config.ts | 5 -
.../packages/planner/package.json | 49 -
.../packages/planner/src/builder.ts | 660 --
.../packages/planner/src/component-graph.ts | 101 -
.../packages/planner/src/file-type.ts | 136 -
.../packages/planner/src/hashing.ts | 307 -
.../packages/planner/src/incremental.ts | 320 -
.../packages/planner/src/index.ts | 105 -
.../packages/planner/src/indexes.ts | 373 --
.../packages/planner/src/integration.ts | 142 -
.../packages/planner/src/overrides.ts | 116 -
.../packages/planner/src/resources.ts | 310 -
.../packages/planner/src/serialize.ts | 302 -
.../packages/planner/src/task-builder.ts | 464 --
.../packages/planner/src/types.ts | 469 --
.../packages/planner/src/utils.ts | 22 -
.../packages/planner/src/worker.ts | 99 -
.../planner/tests/component-graph.test.ts | 151 -
.../packages/planner/tests/file-type.test.ts | 160 -
.../packages/planner/tests/hashing.test.ts | 237 -
.../planner/tests/incremental.test.ts | 241 -
.../packages/planner/tests/serialize.test.ts | 212 -
.../planner/tests/task-builder.test.ts | 208 -
.../packages/planner/tsconfig.json | 14 -
.../packages/planner/tsup.config.ts | 5 -
.../packages/reporters/package.json | 50 -
.../packages/reporters/src/code-frame.ts | 163 -
.../packages/reporters/src/factory.ts | 61 -
.../packages/reporters/src/index.ts | 10 -
.../packages/reporters/src/output.ts | 83 -
.../packages/reporters/src/reporters/cache.ts | 57 -
.../reporters/src/reporters/config.ts | 198 -
.../src/reporters/console-reporter.ts | 503 --
.../reporters/src/reporters/html-reporter.ts | 1166 ----
.../reporters/src/reporters/json-reporter.ts | 140 -
.../packages/reporters/src/severity-utils.ts | 82 -
.../packages/reporters/src/types.ts | 191 -
.../reporters/tests/config-reporter.spec.ts | 95 -
.../reporters/tests/console-reporter.spec.ts | 99 -
.../reporters/tests/json-reporter.spec.ts | 95 -
.../packages/reporters/tsconfig.json | 15 -
.../packages/reporters/tsup.config.ts | 5 -
.../nice-taussig/packages/rules/package.json | 52 -
.../packages/rules/src/engine/adapter.ts | 167 -
.../packages/rules/src/execution-worker.ts | 69 -
.../nice-taussig/packages/rules/src/index.ts | 85 -
.../packages/rules/src/presets/all.ts | 57 -
.../packages/rules/src/presets/index.ts | 37 -
.../packages/rules/src/presets/performance.ts | 25 -
.../packages/rules/src/presets/reactivity.ts | 28 -
.../packages/rules/src/presets/recommended.ts | 38 -
.../packages/rules/src/presets/strict.ts | 41 -
.../packages/rules/src/recommendations.ts | 344 -
.../rules/src/registry/register-all.ts | 86 -
.../rules/src/registry/rule-registry.ts | 256 -
.../packages/rules/src/resolution/loader.ts | 109 -
.../packages/rules/src/resolution/merger.ts | 82 -
.../rules/src/resolution/normalize.ts | 58 -
.../packages/rules/src/resolution/resolver.ts | 183 -
.../packages/rules/src/rule-utils.ts | 941 ---
...component-no-manual-detect-changes.rule.ts | 203 -
.../rxjs-no-nested-subscribe.rule.ts | 133 -
...gnal-effect-must-be-destroy-scoped.rule.ts | 104 -
...signal-no-side-effects-in-computed.rule.ts | 117 -
.../rules/modern-api/prefer-inject.rule.ts | 59 -
.../signal-prefer-input-signal.rule.ts | 57 -
.../modern-api/signal-prefer-model.rule.ts | 83 -
.../signal-prefer-output-function.rule.ts | 68 -
.../rules/performance/prefer-on-push.rule.ts | 126 -
.../template-no-array-literal-binding.rule.ts | 61 -
.../template-no-call-expression.rule.ts | 183 -
...template-no-object-literal-binding.rule.ts | 61 -
.../template-trackby-required.rule.ts | 71 -
.../rxjs-avoid-subject-as-event-bus.rule.ts | 139 -
.../rxjs-no-subscribe-in-component.rule.ts | 104 -
...refer-to-signal-for-template-state.rule.ts | 101 -
.../rxjs-require-take-until-destroyed.rule.ts | 59 -
.../signal-avoid-untracked-overuse.rule.ts | 82 -
...l-prefer-computed-over-sync-effect.rule.ts | 104 -
.../to-signal-require-initial-value.rule.ts | 42 -
.../security/no-bypass-sanitization.rule.ts | 43 -
.../template-no-unsafe-bindings.rule.ts | 45 -
.../src/rules/ssr/no-document-access.rule.ts | 140 -
...-after-render-over-after-view-init.rule.ts | 72 -
...template-no-async-pipe-duplication.rule.ts | 133 -
.../template-prefer-control-flow.rule.ts | 46 -
.../testing/spec-no-focused-test.rule.ts | 105 -
.../packages/rules/tests/correctness.spec.ts | 334 -
.../packages/rules/tests/helpers.ts | 106 -
.../packages/rules/tests/modern-api.spec.ts | 261 -
.../packages/rules/tests/performance.spec.ts | 402 --
.../packages/rules/tests/reactivity.spec.ts | 525 --
.../packages/rules/tests/security.spec.ts | 239 -
.../packages/rules/tests/setup.test.ts | 8 -
.../packages/rules/tests/ssr.spec.ts | 382 --
.../template-no-call-expression.repro.spec.ts | 53 -
.../packages/rules/tests/template.spec.ts | 245 -
.../packages/rules/tests/testing.spec.ts | 170 -
.../nice-taussig/packages/rules/tsconfig.json | 13 -
.../packages/rules/tsup.config.ts | 6 -
.../packages/scanner/package.json | 50 -
.../packages/scanner/src/filters.ts | 144 -
.../nice-taussig/packages/scanner/src/git.ts | 138 -
.../packages/scanner/src/gitignore.ts | 139 -
.../nice-taussig/packages/scanner/src/glob.ts | 56 -
.../packages/scanner/src/index.ts | 29 -
.../packages/scanner/src/normalize.ts | 57 -
.../packages/scanner/src/patterns.ts | 67 -
.../nice-taussig/packages/scanner/src/scan.ts | 389 --
.../packages/scanner/src/stats.ts | 144 -
.../packages/scanner/src/types.ts | 154 -
.../packages/scanner/tests/filters.test.ts | 92 -
.../packages/scanner/tests/git.test.ts | 184 -
.../packages/scanner/tests/gitignore.test.ts | 53 -
.../packages/scanner/tests/glob.test.ts | 184 -
.../packages/scanner/tests/normalize.test.ts | 70 -
.../packages/scanner/tests/patterns.test.ts | 66 -
.../packages/scanner/tests/scan.test.ts | 209 -
.../packages/scanner/tests/stats.test.ts | 79 -
.../packages/scanner/tsconfig.json | 14 -
.../packages/scanner/tsup.config.ts | 5 -
.../packages/testing/package.json | 50 -
.../testing/playwright-report/index.html | 85 -
.../packages/testing/playwright.config.ts | 18 -
.../packages/testing/src/index.ts | 1 -
.../testing/tests/e2e/scanner/scanner.spec.ts | 201 -
.../packages/testing/tsconfig.json | 23 -
.../packages/testing/tsup.config.ts | 5 -
.claude/worktrees/nice-taussig/pnpm-lock.yaml | 5672 -----------------
.../nice-taussig/pnpm-workspace.yaml | 10 -
.../nice-taussig/scripts/add-side-effects.mjs | 34 -
.../nice-taussig/scripts/check-coverage.js | 196 -
.../nice-taussig/scripts/replace-imports.cjs | 36 -
.../nice-taussig/scripts/verify-setup.ps1 | 22 -
.../nice-taussig/tsconfig.build.json | 15 -
.claude/worktrees/nice-taussig/tsconfig.json | 25 -
.../worktrees/nice-taussig/tsconfig.spec.json | 13 -
.claude/worktrees/nice-taussig/tsup.config.ts | 91 -
.claude/worktrees/nice-taussig/turbo.json | 104 -
.../worktrees/nice-taussig/vitest.config.ts | 59 -
.claude/worktrees/silly-black | 1 -
.github/workflows/ci.yml | 65 +
.gitignore | 3 +-
BETA_TEST_PLAN.md | 317 -
README.md | 298 +-
assets/logo-base64.txt | 1 +
assets/logo-small.png | Bin 0 -> 99273 bytes
assets/logo.png | Bin 0 -> 1436124 bytes
benchmark-results.json | 206 +
docs/architecture.md | 1108 ++++
ngcompass.config.broken.ts | 117 +
package.json | 16 +-
packages/ast/package.json | 117 +-
packages/cache/package.json | 121 +-
packages/cache/src/context.ts | 4 +-
packages/cache/src/drivers/packed.ts | 94 +
packages/cache/src/drivers/types.ts | 2 +
packages/cache/src/services/result-cache.ts | 41 +-
packages/cli/package.json | 135 +-
packages/cli/src/bin/ngcompass.ts | 15 +-
packages/cli/src/commands/analyze.ts | 162 +-
packages/cli/src/commands/cache.ts | 2 +-
packages/cli/src/commands/rules.ts | 4 +-
packages/cli/src/spinner.ts | 62 +
packages/cli/tests/commands/analyze.test.ts | 55 +
packages/common/package.json | 115 +-
packages/common/src/types.ts | 33 +-
packages/config/package.json | 123 +-
packages/config/src/actions/init.ts | 52 +-
packages/config/src/define-config.ts | 5 +
packages/config/src/index.ts | 1 +
packages/config/src/loaders/discovery.ts | 18 +-
packages/config/src/plugin-loader.ts | 6 +-
packages/config/tests/define-config.spec.ts | 14 +
packages/config/tests/init.spec.ts | 60 +
packages/engine/package.json | 123 +-
packages/engine/src/orchestrator.ts | 168 +-
packages/engine/src/rule-context-factory.ts | 100 +-
packages/engine/src/single-pass-engine.ts | 17 +-
packages/engine/src/type-aware-context.ts | 14 +-
packages/engine/src/worker-pool.ts | 76 +-
packages/engine/tests/orchestrator.spec.ts | 26 +
.../engine/tests/rule-context-factory.spec.ts | 104 +
.../engine/tests/single-pass-engine.spec.ts | 31 +-
packages/planner/package.json | 117 +-
packages/planner/src/builder.ts | 60 +-
packages/planner/src/component-graph.ts | 72 +-
packages/planner/src/file-type.ts | 10 +
packages/planner/src/indexes.ts | 2 +
packages/planner/src/resources.ts | 55 +-
packages/planner/src/task-builder.ts | 15 +-
packages/planner/src/types.ts | 2 +
.../planner/tests/component-graph.test.ts | 142 +-
packages/reporters/package.json | 117 +-
packages/reporters/src/analysis-status.ts | 27 +
packages/reporters/src/code-frame.ts | 62 +-
packages/reporters/src/factory.ts | 125 +-
packages/reporters/src/reporters/cache.ts | 123 +-
packages/reporters/src/reporters/config.ts | 16 +-
.../src/reporters/console-reporter.ts | 222 +-
.../reporters/src/reporters/html-reporter.ts | 622 +-
.../reporters/src/reporters/json-reporter.ts | 102 +-
.../reporters/src/reporters/sarif-reporter.ts | 40 +-
packages/reporters/src/types.ts | 24 +-
.../reporters/tests/config-reporter.spec.ts | 9 +-
.../reporters/tests/console-reporter.spec.ts | 129 +-
.../reporters/tests/html-reporter.spec.ts | 65 +-
.../reporters/tests/json-reporter.spec.ts | 65 +-
.../reporters/tests/sarif-reporter.spec.ts | 37 +-
packages/rules/package.json | 121 +-
packages/rules/src/execution-worker.ts | 39 +-
packages/rules/src/presets/index.ts | 6 +-
packages/rules/src/presets/performance.ts | 10 +-
packages/rules/src/presets/reactivity.ts | 26 +-
packages/rules/src/presets/recommended.ts | 44 +-
packages/rules/src/presets/security.ts | 21 +
packages/rules/src/presets/ssr.ts | 19 +
packages/rules/src/presets/strict.ts | 43 +-
packages/rules/src/recommendations.ts | 58 +-
...component-no-manual-detect-changes.rule.ts | 406 +-
.../rxjs-no-nested-subscribe.rule.ts | 266 +-
...gnal-effect-must-be-destroy-scoped.rule.ts | 208 +-
...signal-no-side-effects-in-computed.rule.ts | 234 +-
.../rules/modern-api/prefer-inject.rule.ts | 119 +-
.../signal-prefer-input-signal.rule.ts | 114 +-
.../modern-api/signal-prefer-model.rule.ts | 166 +-
.../signal-prefer-output-function.rule.ts | 143 +-
.../rules/performance/prefer-on-push.rule.ts | 280 +-
.../template-no-array-literal-binding.rule.ts | 122 +-
.../template-no-call-expression.rule.ts | 370 +-
...template-no-object-literal-binding.rule.ts | 122 +-
.../template-trackby-required.rule.ts | 142 +-
.../rxjs-avoid-subject-as-event-bus.rule.ts | 306 +-
.../rxjs-no-subscribe-in-component.rule.ts | 212 +-
...refer-to-signal-for-template-state.rule.ts | 2 +-
.../rxjs-require-take-until-destroyed.rule.ts | 8 +-
.../signal-avoid-untracked-overuse.rule.ts | 2 +-
...l-prefer-computed-over-sync-effect.rule.ts | 2 +-
.../to-signal-require-initial-value.rule.ts | 2 +-
.../security/no-bypass-sanitization.rule.ts | 2 +-
.../template-no-unsafe-bindings.rule.ts | 2 +-
.../src/rules/ssr/no-document-access.rule.ts | 2 +-
...-after-render-over-after-view-init.rule.ts | 4 +-
...template-no-async-pipe-duplication.rule.ts | 2 +-
.../template-prefer-control-flow.rule.ts | 2 +-
.../testing/spec-no-focused-test.rule.ts | 10 +-
packages/rules/tests/helpers.ts | 47 +-
.../template-no-call-expression.repro.spec.ts | 39 +
packages/scanner/package.json | 119 +-
scripts/add-side-effects.mjs | 34 -
scripts/benchmark-ngcompass.ps1 | 102 +
scripts/check-coverage.js | 2 +-
scripts/replace-imports.cjs | 36 -
scripts/smoke-test.mjs | 686 ++
scripts/validate-packages.mjs | 242 +
413 files changed, 7916 insertions(+), 47178 deletions(-)
delete mode 100644 .claude/worktrees/nice-taussig/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/parser-output-utf8.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/parser-output.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/analyzers/component-analyzer.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/analyzers/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/analyzers/template-analyzer.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/ast/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/ast/matchers.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/ast/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/node-streams.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/parsers/css.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/parsers/html.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/parsers/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/parsers/template-extractor.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/parsers/ts.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/src/visitor.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/analyzers/component-analyzer.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/analyzers/template-analyzer.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/ast/matchers.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/node-streams.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/parsers/css.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/parsers/html.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/parsers/template-extractor.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/parsers/ts.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tests/visitor.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/ast/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/constants.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/context.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/drivers/atomic.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/drivers/disk.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/drivers/json-file.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/drivers/memory.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/drivers/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/env-fingerprint.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/hashing.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/key-context.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/ast-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/config-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/file-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/hashing.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/meta-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/plan-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/result-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/services/source-cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/utils/fs.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/src/utils/stable-serialize.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/context.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/drivers/atomic.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/drivers/disk.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/drivers/json-file.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/drivers/memory.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/env-fingerprint.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/hashing.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/key-context.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/services/result-cache.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/services/services.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/utils/fs.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tests/utils/stable-serialize.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/cache/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/bin/ngcompass.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/analyze.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/exit.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/commands/init.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tests/commands/analyze.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tests/commands/cache.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tests/commands/config.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tests/commands/init.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/cli/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/ast/utils.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/constants.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/errors.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/interfaces.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/logger.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/utils.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/utils/locator.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/src/utils/stable-serialize.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/tests/logger.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/tests/utils.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/common/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/actions/healthcheck.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/actions/init.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/base.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/cross-fields.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/deprecated.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/extends.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/globs.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/paths.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/profiles.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/checks/rules.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/constants.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/context.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/enricher.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/messages.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/health/validator.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/loaders/discovery.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/loaders/loader.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/plugin-loader.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/schemas/defaults.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/src/schemas/schema.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/tests/config-core.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/tests/extends-validation.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/tests/semantic-isolation.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/config/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/analysis-context.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/analysis-stats.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/constants.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/orchestrator.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/project-context-builder.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/rule-context-factory.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/rule-executor.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/rule-handler.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/runner.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/single-pass-engine.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/spinner.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/type-aware-context.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/visitor-registry.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/src/worker-pool.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/analysis-context.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/analysis-stats.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/constants.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/orchestrator.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/rule-context-factory.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/rule-executor.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/rule-handler.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/runner.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/single-pass-engine.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/visitor-registry.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tests/worker-pool.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tsc_errors.txt
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tsc_errors_utf8.txt
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/engine/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/builder.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/component-graph.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/file-type.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/hashing.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/incremental.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/indexes.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/integration.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/overrides.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/resources.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/serialize.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/task-builder.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/utils.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/src/worker.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/component-graph.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/file-type.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/hashing.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/incremental.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/serialize.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tests/task-builder.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/planner/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/code-frame.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/factory.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/output.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/reporters/cache.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/reporters/config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/reporters/console-reporter.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/reporters/html-reporter.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/reporters/json-reporter.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/severity-utils.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/tests/config-reporter.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/tests/console-reporter.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/tests/json-reporter.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/reporters/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/engine/adapter.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/execution-worker.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/all.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/performance.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/reactivity.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/recommended.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/presets/strict.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/recommendations.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/registry/register-all.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/registry/rule-registry.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/resolution/loader.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/resolution/merger.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/resolution/normalize.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/resolution/resolver.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rule-utils.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/correctness/component-no-manual-detect-changes.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/correctness/rxjs-no-nested-subscribe.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/correctness/signal-effect-must-be-destroy-scoped.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/correctness/signal-no-side-effects-in-computed.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/modern-api/prefer-inject.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/modern-api/signal-prefer-input-signal.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/modern-api/signal-prefer-model.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/modern-api/signal-prefer-output-function.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/performance/prefer-on-push.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/performance/template-no-array-literal-binding.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/performance/template-no-call-expression.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/performance/template-no-object-literal-binding.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/performance/template-trackby-required.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/rxjs-avoid-subject-as-event-bus.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/rxjs-no-subscribe-in-component.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/rxjs-prefer-to-signal-for-template-state.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/rxjs-require-take-until-destroyed.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/signal-avoid-untracked-overuse.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/signal-prefer-computed-over-sync-effect.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/reactivity/to-signal-require-initial-value.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/security/no-bypass-sanitization.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/security/template-no-unsafe-bindings.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/ssr/no-document-access.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/ssr/prefer-after-render-over-after-view-init.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/template/template-no-async-pipe-duplication.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/template/template-prefer-control-flow.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/src/rules/testing/spec-no-focused-test.rule.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/correctness.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/helpers.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/modern-api.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/performance.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/reactivity.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/security.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/setup.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/ssr.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/template-no-call-expression.repro.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/template.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tests/testing.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/rules/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/filters.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/git.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/gitignore.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/glob.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/normalize.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/patterns.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/scan.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/stats.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/src/types.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/filters.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/git.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/gitignore.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/glob.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/normalize.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/patterns.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/scan.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tests/stats.test.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/scanner/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/package.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/playwright-report/index.html
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/playwright.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/src/index.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/tests/e2e/scanner/scanner.spec.ts
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/packages/testing/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/pnpm-lock.yaml
delete mode 100644 .claude/worktrees/nice-taussig/pnpm-workspace.yaml
delete mode 100644 .claude/worktrees/nice-taussig/scripts/add-side-effects.mjs
delete mode 100644 .claude/worktrees/nice-taussig/scripts/check-coverage.js
delete mode 100644 .claude/worktrees/nice-taussig/scripts/replace-imports.cjs
delete mode 100644 .claude/worktrees/nice-taussig/scripts/verify-setup.ps1
delete mode 100644 .claude/worktrees/nice-taussig/tsconfig.build.json
delete mode 100644 .claude/worktrees/nice-taussig/tsconfig.json
delete mode 100644 .claude/worktrees/nice-taussig/tsconfig.spec.json
delete mode 100644 .claude/worktrees/nice-taussig/tsup.config.ts
delete mode 100644 .claude/worktrees/nice-taussig/turbo.json
delete mode 100644 .claude/worktrees/nice-taussig/vitest.config.ts
delete mode 160000 .claude/worktrees/silly-black
delete mode 100644 BETA_TEST_PLAN.md
create mode 100644 assets/logo-base64.txt
create mode 100644 assets/logo-small.png
create mode 100644 assets/logo.png
create mode 100644 benchmark-results.json
create mode 100644 docs/architecture.md
create mode 100644 ngcompass.config.broken.ts
create mode 100644 packages/cache/src/drivers/packed.ts
create mode 100644 packages/cli/src/spinner.ts
create mode 100644 packages/config/src/define-config.ts
create mode 100644 packages/config/tests/define-config.spec.ts
create mode 100644 packages/config/tests/init.spec.ts
create mode 100644 packages/reporters/src/analysis-status.ts
create mode 100644 packages/rules/src/presets/security.ts
create mode 100644 packages/rules/src/presets/ssr.ts
delete mode 100644 scripts/add-side-effects.mjs
create mode 100644 scripts/benchmark-ngcompass.ps1
delete mode 100644 scripts/replace-imports.cjs
create mode 100644 scripts/smoke-test.mjs
create mode 100644 scripts/validate-packages.mjs
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 908f8a7..c2912d0 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -145,7 +145,10 @@
"Bash(pnpm --filter @ngcompass/rules exec eslint src --ext .ts --fix)",
"Bash(pnpm --filter @ngcompass/cache lint)",
"Bash(echo \"EXIT: $?\")",
- "Bash(pnpm --filter @ngcompass/planner build)"
+ "Bash(pnpm --filter @ngcompass/planner build)",
+ "Bash(npm list *)",
+ "WebFetch(domain:ngcompass.dev)",
+ "PowerShell(& \"C:\\\\Users\\\\30694\\\\Desktop\\\\ngcompass_\\\\scripts\\\\benchmark-ngcompass.ps1\" 2>&1)"
]
}
}
diff --git a/.claude/worktrees/nice-taussig/package.json b/.claude/worktrees/nice-taussig/package.json
deleted file mode 100644
index 52f0641..0000000
--- a/.claude/worktrees/nice-taussig/package.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
- "name": "ngcompass",
- "version": "0.0.0",
- "private": true,
- "description": "Angular static analysis tool for architecture, performance, SSR, and code quality",
- "type": "module",
- "packageManager": "pnpm@10.26.0",
- "scripts": {
- "build": "turbo build",
- "build:prod": "NODE_ENV=production turbo build:prod",
- "build:benchmark": "BENCHMARK=true pnpm run build",
- "build:profile": "turbo build --profile=profile.json && echo 'Profile saved to profile.json'",
- "build:trace": "turbo build --trace",
- "test": "vitest run",
- "test:watch": "vitest",
- "test:ui": "vitest --ui",
- "test:coverage": "vitest run --coverage",
- "lint": "turbo lint",
- "clean": "turbo clean && rimraf node_modules .turbo",
- "clean:cache": "rimraf .turbo/cache",
- "dev": "turbo dev",
- "typecheck": "turbo typecheck",
- "changeset": "changeset",
- "version-packages": "changeset version",
- "release": "pnpm run build:prod && changeset publish",
- "turbo:summary": "turbo build --dry=json | jq '.tasks[] | {task: .taskId, cache: .cache.status, time: .cache.timeSaved}'",
- "turbo:graph": "turbo build --graph",
- "turbo:daemon": "turbo daemon",
- "turbo:daemon:status": "turbo daemon status",
- "ci": "turbo build test lint typecheck --concurrency=4",
- "ci:affected": "turbo build test lint typecheck --filter=[HEAD^1]"
- },
- "keywords": [
- "angular",
- "linter",
- "static-analysis",
- "code-quality",
- "architecture",
- "performance",
- "ssr"
- ],
- "author": "ngcompass",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0",
- "pnpm": ">=8.0.0"
- },
- "devDependencies": {
- "@changesets/cli": "^2.29.8",
- "@swc-node/register": "^1.11.1",
- "@swc/cli": "^0.7.10",
- "@swc/core": "^1.15.11",
- "@swc/helpers": "^0.5.18",
- "@typescript-eslint/eslint-plugin": "^8.54.0",
- "@typescript-eslint/parser": "^8.54.0",
- "@vitest/coverage-v8": "^4.0.18",
- "@vitest/ui": "^4.0.18",
- "eslint": "^8.57.1",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-boundaries": "^5.4.0",
- "eslint-plugin-prettier": "^5.5.5",
- "fast-check": "^4.5.3",
- "prettier": "^3.8.1",
- "rimraf": "^6.1.2",
- "tsup": "^8.5.1",
- "turbo": "^2.8.3",
- "typescript": "^5.9.3",
- "unplugin-swc": "^1.5.9",
- "vitest": "^4.0.18"
- }
-}
diff --git a/.claude/worktrees/nice-taussig/packages/ast/package.json b/.claude/worktrees/nice-taussig/packages/ast/package.json
deleted file mode 100644
index d6bca1e..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/package.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "name": "@ngcompass/ast",
- "version": "0.0.1",
- "description": "AST types, parsers, analyzers and visitor for ngcompass",
- "sideEffects": false,
- "main": "./dist/index.cjs",
- "module": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "type": "module",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
- }
- },
- "files": [
- "dist"
- ],
- "scripts": {
- "build": "tsup",
- "build:prod": "NODE_ENV=production tsup --minify",
- "dev": "tsup --watch",
- "test": "vitest run",
- "test:watch": "vitest",
- "lint": "eslint src --ext .ts",
- "typecheck": "tsc --noEmit",
- "clean": "rimraf dist .turbo"
- },
- "dependencies": {
- "@ngcompass/common": "workspace:*",
- "angular-html-parser": "^10.4.0",
- "lightningcss": "^1.31.1",
- "oxc-parser": "^0.112.0"
- },
- "peerDependencies": {
- "typescript": "catalog:"
- },
- "devDependencies": {
- "@types/node": "^25.1.0"
- },
- "keywords": [
- "angular",
- "ast",
- "parser",
- "analyzer"
- ],
- "author": "ngcompass",
- "license": "MIT"
-}
\ No newline at end of file
diff --git a/.claude/worktrees/nice-taussig/packages/ast/parser-output-utf8.json b/.claude/worktrees/nice-taussig/packages/ast/parser-output-utf8.json
deleted file mode 100644
index 3534b04..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/parser-output-utf8.json
+++ /dev/null
@@ -1,1518 +0,0 @@
-[
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n
{{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 0,
- "line": 0,
- "col": 0
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 0,
- "line": 0,
- "col": 0
- },
- "details": null
- },
- "kind": "text",
- "value": "\n@for (item of items; track item.id) {\n ",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n@for (item of items; track item.id) {\n "
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 0,
- "line": 0,
- "col": 0
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 0,
- "line": 0,
- "col": 0
- },
- "details": null
- }
- }
- ]
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "details": null
- },
- "kind": "element",
- "name": "div",
- "attrs": [],
- "directives": [],
- "children": [
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "details": null
- },
- "kind": "text",
- "value": "{{ item.name }}",
- "tokens": [
- {
- "type": 5,
- "parts": [
- ""
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "details": null
- }
- },
- {
- "type": 8,
- "parts": [
- "{{",
- " item.name ",
- "}}"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "details": null
- }
- },
- {
- "type": 5,
- "parts": [
- ""
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "details": null
- }
- }
- ]
- }
- ],
- "isSelfClosing": false,
- "startSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 46,
- "line": 2,
- "col": 7
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 41,
- "line": 2,
- "col": 2
- },
- "details": null
- },
- "endSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 61,
- "line": 2,
- "col": 22
- },
- "details": null
- },
- "nameSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 42,
- "line": 2,
- "col": 3
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 45,
- "line": 2,
- "col": 6
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 42,
- "line": 2,
- "col": 3
- },
- "details": null
- },
- "isVoid": false
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "details": null
- },
- "kind": "text",
- "value": "\n} @empty {\n ",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n} @empty {\n "
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 67,
- "line": 2,
- "col": 28
- },
- "details": null
- }
- }
- ]
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "details": null
- },
- "kind": "element",
- "name": "p",
- "attrs": [],
- "directives": [],
- "children": [
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 84,
- "line": 4,
- "col": 5
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 89,
- "line": 4,
- "col": 10
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 84,
- "line": 4,
- "col": 5
- },
- "details": null
- },
- "kind": "text",
- "value": "Empty",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "Empty"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 84,
- "line": 4,
- "col": 5
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 89,
- "line": 4,
- "col": 10
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 84,
- "line": 4,
- "col": 5
- },
- "details": null
- }
- }
- ]
- }
- ],
- "isSelfClosing": false,
- "startSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 84,
- "line": 4,
- "col": 5
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 81,
- "line": 4,
- "col": 2
- },
- "details": null
- },
- "endSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 89,
- "line": 4,
- "col": 10
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 89,
- "line": 4,
- "col": 10
- },
- "details": null
- },
- "nameSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 82,
- "line": 4,
- "col": 3
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 83,
- "line": 4,
- "col": 4
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 82,
- "line": 4,
- "col": 3
- },
- "details": null
- },
- "isVoid": false
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "details": null
- },
- "kind": "text",
- "value": "\n}\n\n@if (condition) {\n ",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n}\n\n@if (condition) {\n "
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 93,
- "line": 4,
- "col": 14
- },
- "details": null
- }
- }
- ]
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "details": null
- },
- "kind": "element",
- "name": "div",
- "attrs": [],
- "directives": [],
- "children": [
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 122,
- "line": 8,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 126,
- "line": 8,
- "col": 11
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 122,
- "line": 8,
- "col": 7
- },
- "details": null
- },
- "kind": "text",
- "value": "True",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "True"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 122,
- "line": 8,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 126,
- "line": 8,
- "col": 11
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 122,
- "line": 8,
- "col": 7
- },
- "details": null
- }
- }
- ]
- }
- ],
- "isSelfClosing": false,
- "startSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 122,
- "line": 8,
- "col": 7
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 117,
- "line": 8,
- "col": 2
- },
- "details": null
- },
- "endSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 126,
- "line": 8,
- "col": 11
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 126,
- "line": 8,
- "col": 11
- },
- "details": null
- },
- "nameSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 118,
- "line": 8,
- "col": 3
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 121,
- "line": 8,
- "col": 6
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 118,
- "line": 8,
- "col": 3
- },
- "details": null
- },
- "isVoid": false
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "details": null
- },
- "kind": "text",
- "value": "\n} @else if (other) {\n ",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n} @else if (other) {\n "
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 132,
- "line": 8,
- "col": 17
- },
- "details": null
- }
- }
- ]
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "details": null
- },
- "kind": "element",
- "name": "div",
- "attrs": [],
- "directives": [],
- "children": [
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 161,
- "line": 10,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 166,
- "line": 10,
- "col": 12
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 161,
- "line": 10,
- "col": 7
- },
- "details": null
- },
- "kind": "text",
- "value": "Other",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "Other"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 161,
- "line": 10,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 166,
- "line": 10,
- "col": 12
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 161,
- "line": 10,
- "col": 7
- },
- "details": null
- }
- }
- ]
- }
- ],
- "isSelfClosing": false,
- "startSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 161,
- "line": 10,
- "col": 7
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 156,
- "line": 10,
- "col": 2
- },
- "details": null
- },
- "endSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 166,
- "line": 10,
- "col": 12
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 166,
- "line": 10,
- "col": 12
- },
- "details": null
- },
- "nameSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 157,
- "line": 10,
- "col": 3
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 160,
- "line": 10,
- "col": 6
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 157,
- "line": 10,
- "col": 3
- },
- "details": null
- },
- "isVoid": false
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "details": null
- },
- "kind": "text",
- "value": "\n} @else {\n ",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n} @else {\n "
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 172,
- "line": 10,
- "col": 18
- },
- "details": null
- }
- }
- ]
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "details": null
- },
- "kind": "element",
- "name": "div",
- "attrs": [],
- "directives": [],
- "children": [
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 190,
- "line": 12,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 195,
- "line": 12,
- "col": 12
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 190,
- "line": 12,
- "col": 7
- },
- "details": null
- },
- "kind": "text",
- "value": "False",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "False"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 190,
- "line": 12,
- "col": 7
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 195,
- "line": 12,
- "col": 12
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 190,
- "line": 12,
- "col": 7
- },
- "details": null
- }
- }
- ]
- }
- ],
- "isSelfClosing": false,
- "startSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 190,
- "line": 12,
- "col": 7
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 185,
- "line": 12,
- "col": 2
- },
- "details": null
- },
- "endSourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 195,
- "line": 12,
- "col": 12
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 195,
- "line": 12,
- "col": 12
- },
- "details": null
- },
- "nameSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 186,
- "line": 12,
- "col": 3
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 189,
- "line": 12,
- "col": 6
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 186,
- "line": 12,
- "col": 3
- },
- "details": null
- },
- "isVoid": false
- },
- {
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 204,
- "line": 14,
- "col": 0
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "details": null
- },
- "kind": "text",
- "value": "\n}\n",
- "tokens": [
- {
- "type": 5,
- "parts": [
- "\n}\n"
- ],
- "sourceSpan": {
- "start": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "end": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 204,
- "line": 14,
- "col": 0
- },
- "fullStart": {
- "file": {
- "content": "\n@for (item of items; track item.id) {\n {{ item.name }}
\n} @empty {\n Empty
\n}\n\n@if (condition) {\n True
\n} @else if (other) {\n Other
\n} @else {\n False
\n}\n",
- "url": "angular-html-parser"
- },
- "offset": 201,
- "line": 12,
- "col": 18
- },
- "details": null
- }
- }
- ]
- }
-]
diff --git a/.claude/worktrees/nice-taussig/packages/ast/parser-output.json b/.claude/worktrees/nice-taussig/packages/ast/parser-output.json
deleted file mode 100644
index fc53d4b4426530bb36689584ac0e46b99b139320..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 124652
zcmeHQO>Z2>5uLMv{0CuA4qz*yB2koW7y^Ri>Vu73oIMCKWecWAfue#Kg8q7vsvSO7
zf6i9V4mCTw4}#d_Y;{l1Y**K-&;H+kfA0R&ebGITe;@CD)4x{T`|hgy&|P=0y7TVG
z?p^n?yOgWnWR#%35sK^Xrb^S(z3$$07cydPB&F&pU&;M1WxVsv&z^qO{Y(CO(Y=<>
zu7&PP8BHnP%I{Zl^}*fW@4Ij1uN$FyDNpcw@X14=`K9}_T=}>eP4D)dTzS*|yZc_Q
zeT--?rT<&G!-MXV{QIu^%N?c8{xz``^S2*T78Sn4XyZ=o4>Xp8q{_LOS
z&1T+T$}IJHbsjhJUw^O7@h5qz4|3PqY%b)x(-~vrD;e=OxpG|{@rOH#ZDWSdhj;c5
zc}M*{^WOT5tM034ZQ(oazh_-z7q{zSB_mySzsl2H$Zyx(*D~)Ld4m`7o36m?&68aV
zHCAmn)~CvSR-3i)deax{^HG)dLf+e@ysdu4)#>#6!n_}4^C`_`C8O#$Jhb*#vKq)C
z%30))er>NdyD(PT=i`tk-HDS&9#`o?9(nBKk=GmUzYv*2i9(4&i2?~%mMFi7Oi?~j
zneqNsvRvMBP$(nQ-YdaGD(4`LRx-+Y_aFJ0O6!&2B9-roIPMpP<#)aj8Tj2u2;Jzt
zIK+Aou^$6sx9@v819#(*)&cYkawNVG{|MWpSN
znm5KCTFFXN;=7s+3S*{Rx1B!Zy0H?@t9f9Sp9=basG+RyJ`dkfjP7ZbR$KQ&(c5U1
zR=Yba-G^e8u1naI$7_;xwecgsT9`}Jkhj2@9<=J0mi-LqxhbtldTz?Lj?${ENyF3b
zS=BP)L!T*+oZU$ta71Z7tvHh$LtiJ>Q{PUBCrc}|#xZkA6)!?epCz|VtE5-tgIIvr
zpS-%mL$FWxp6T44{8DV78WUjFd}BsWTdw+%sOe%wr0tYD6UI)t8rRX5G~FlNX~K`7M(f4FMjz_B
z&oW65x}n*6@G-{mOMUA>Yc8$1pWm8$T8;0pmFdINMj!Ip<1W`eK{?EAwC5tSPW{b{
zG1R!sU1AKe>L}%g`Ob^i@jh##<@mi8TY`~FF($*2xGA=`kvuUYpf00weCmxMF?xS2
ziS;R4MDY1~D;^pX4T8~C|7g)9tY51}VLgdbTIHHh8^mp#ay?>Ni5Hr&EH6wi~JiFb0gC}YAnOeWlaxvu3
zL60Fk0~*z1#>DG~{e2no=z*4ziN{e>0^c`UO5JIt?mp{5wLD*23uE*kYA4iA`uaeJ
z6u}LiK)bK|BY*N%z?gN-^G->Qq3+{j_#PJ?%;N;?#Je|G#-bgaXucYRY^&v-
z{K5=q;G({rk6-4IDPA0!avS1%iS$_Q#xlU}D@tW9uB=;##!0b~{+M!>?nB_NUgIjq
z*->^;cI}7kV!a!l?UBm>p6vldspr1LXM4;iSH|N#ShIN24o}+YPwy$!kJ}{+elC>v
zMCw4qwrt4NaVYPvSq&FrJYY4RMzvd|i1~P=7PmHzM^2qQ(&p)=P6R&V*;GJZ(`EIS
zc5%H+*+(>wOc{^w)bIOG@!RUzgQ=$%p5^cj#8!#6I)UD*9^_~8GqBja{5+qHgI-^r
zS(={ds?vQZb9pY5cvh|0m!iZcRgEjo=z`?IE)Yr}TvI90lCMRnLaBnabF5NjJWky8
zRK}rdk?EeM_3ifWe8u4>NOvqNh87d>N*`}aHR!FpQ9R8MP4ynr=SlGON2
z+mhB?N7?9^;)t4-ZS*mo$WPJ$<|9bjd(XNx%|KMASCzk2rI`Q|(ht#5}j3NLTHFTi@r
z(>)V8oMThWQd{qCg+B4F
z^|ZS3)BOki`+IJWsQ|qnBU=ypPUt)N{HG*%&aFd&M{#e``)MDaWQsn_rd3AOCGiDdWQA(*%LvJ)~k1b-t#mb8$HHD
z+pdpTPx35zc7M)D+^(dr3@}Y6Yyl`&N8P-^%;E>8{1M^Qz++
zWczvs*_3-yP3o5S9B-Y(n&%-#@?O8^c8`HyuG1TL9NxJ3c%;7eeMmayk=)Bx;#gh(
z%)&gEs2fITYJI3Yf|XgzA!hX3=TmNj-H5e!S$j9HyLc#7+3NNoZ_G$6#Y*;{l(UpS
z!CkDq3!kl8XlS$9&x#w2oM!FaTn4cAZjZFe7KB=y)P2hpuiY_~N-K$D@6Wqq?OnCC
zek$k|tSh?Ax}s<9+Pig|MwoNX+Pmi)e*}3X&X7d}<}PuE^V##J&5mPkoLcD~-bq!;
zl*6j$K9@`xkMCG}mpexe&4>v!gBn+^U0O*NOAY-yX{f7
z>2BxC(<vN|FmAdcr&99@kgnO6DWt~>+#@Y9C910^)91Y?-0JX)g1F%
z4d)wu$Zt8`2i-8Ywc&q^NzNnZErav+SI&9$2=H8-f23FAE0(boa(s;B70+Htr1!Pt
z=bU$pNTB!Vu)Ie@a`t}hXtXxd+Psvi)+bW2w|4f*I4)7^by`CwltPeU%rj!F+cIO_
z=94U5oS5=kUiquFYHuHRm0OTmx%V4E-!Zbr4i~n}6+Vt!VSMNNV%t3t>0XoXHrHLO
zO}Vtkxals0>)O(`YuO(3IFI!>V@2%~x1CnJjyBg_xJ|J~imoy8Fu&2di`enr%S`mW
z8~Vno+b7=JPf6!R?RcX#KF(46nEJgxiN*JWSbS3~kGuP#LO?x?e7#X37_B8*#QNA1
z)pkauU2ol&TkPSuJKEOh5bMXwEt&|%ZTERwPsVs1Q;e5=@6$z-V@$N3Rj#x(uHB}w;67e|0O--C^;_y1
z40?E;sMUXfvC)!;Sqo;k77SJjT1wsv*+bOcJ(c8b)r{~>#L+Ies%`lbi-!SiOt(&S
z16J^51z)Yd!1_Bqzm$=~TzP7YuVH-6a^h?1{x>h4j`4FYdrN4!6&?H0Fg@AF;mMv7
z?Uq)kN|ybSD_%63J`F5LtmDgSJm%Fuw3F)9zFV^tjgw?0Uv|n{{VqYoN5r=r#K(wg
z*7D8e0Bs3FwuJd)%XrksYQA^_%$+;D+__rL`8ce{dcLgZdx#p5_Y}K2H-!-`r&(-o
zSs!YP<)v+pC)4)W3qR+y?bSKK7ZVm+!@Y84Jj!EDU)J-#x;HC%pa#!
zkXFIvSOuTDt>=2e-B%eB>^U$*i+PM7IHqim
zDfMcsp^{9;XFVopL*?k}wc{Fnh&@VGo&sIi!wNUhxIS+5FkMsMb<4I2)MagoY3njJ_9Er7HRLaFrw6JpLFq}#q@ddx?rsC8?GkWd9k~&=Ha2$
zJUo?ppk-e%IKVIKtw*T+=K02;OZOpM*VcoTLGPFT)!PvL&Ep?E95tBqXiIcN{owpdDS3;VvOubwrudixbz@~fOyqd07?v8}1DgIbS}
z-!#v%g#DFzXwI#e+wR_4fs&f*acHi#LiUII_Ep+>u{+*rjgxaMKc>d-4f
z(Sa{Y1hb7T%(#ly8#Q9|{xhMiPxwr={ZXmcTk+7CSP`4YdwIA0iPl3F4bY=)$2iuL
zSm&5xNbGx`e%Hm=wcQ7wqb=JNx9c3%zZDBv&kz&Ubr8^do~T79!MNmGRtY_nDxuqT
z7hCny@a>Q;gM0Znx6Pk`vC)!?$;F4m#oKfsTlLd$@l?{cRY$^e#4#|qs%`lbi;@9t
zOqXg1toBQb=u#}A$LJSjo@boX^{)HAqdXs$=kxJNT|4%B;^OGET>h3YbF2N|kBMpT
zKMs5Ud_1$nn44ZIvF0yp{^qqF4=2{TZZjGy%}O?*vOFX1Bko&HeGf)kGtR1%ceAa2
zgJt8Ghg(lVMUGBsOv)En_Oy+)CBJ;}+9Olxw33*^ml8#IF4+wl$)K93#vkTU65hyT
z>SBmc*43{6U)-I&)ki-lH{8g7jru%>Xn;9&v1gSCnz4w_TceS9k9aSYd%72-6^`P)
zf4V4{bf3q4YEdxtDs5iBTP(R){a0i2AIdl0OIKEvKeVdyPvzv;T0H7Fp7F|-@mP=b
ze_8*R^?!RktCSHlr`>~yk%)0Qi1AFSgn6ak<0@qs7oBH{l`t$l!_0f$E$?|gYs8$=
zWjtn_x(2Y>dsAK5m&cV`CVP2))BPb9%=26OWqo(@=WCU#{f6@|ea&%6e}Dt*}@deLLm;hmlK?8lP!fwmQ}KL-h4qX{|?yDM~e-Kshw$mh39O
zzXNw>4BBz{J?#6Q&aR6whgPrBlsT@B+A(g|6%omV>m-^DpddNsacSxX_;$H<+ved!_U5qf-@is@GtO|;Atf9lfKFJ)t|YA#(v
z{sMP;(5qi^60IHwTAflaEUijioh4NISWbwmWdV=nlak(7no}$8B-hZYf?c+6=i(Y&
z;dL8l8h=pJ;l@c9FIr4FF0Tj1+P>jxqOBLZ3w>O>`M9vErrez{X3EvLj|y)TyD
z=T&rIbKQ;KO1UW*dzjqLBA5+`c?0RV|2=4ET2?2F%
zVa8RopQpBO_)Mkv5X@#>i4(!+ZT4~WLffiKSijcz5$j2;a}3#h_`cEJhmL`mH+U<|*V5k1hx7L>XU0(#FAwdf=mw;df>Px3AKcD2d<{&jQGomPZzJIfDi39V<9
zD{YN$YnDxnk9I1)9nxiRFV8b67q*-gh_TU}nZdc749>cZ(vPaQX}=}R
z+=`L?n3$1jj8tpYTuUodB}`c}olmxSF=_fduqCm!FRSq!a&6zbzk94SE7^on?&^06
z;y&WOrQkkBTWi)0&rr{0!4l%ErnGjJ_VPONn^V4w$9||j$lAWFzj$c%7tgxW#6Awo
z;8?`xt-naTN4&Qbyr+3W&nM;+*7jxQ_MvBP&m~*NV?EaPWo=*9_N{4f^V&U#7>O8{
zf*6%kp12WXoifbLeAK0x5GCsiJ;Tg<-YxHWF8koT(q%km+`YE1>H%r)mM>?koU7%{
z(OPXzE4!9+wBFxFfBu~9F=h|ywUvIZ=}x3n>*{{Tn!OJsLmTJ!eUx+I9&Ao8qXzsr
zG+S{{*$UYoj;mp8n`_E(+j|V7
zbBsL9Z{u>E{PCQa1$mFWw`*qN&`Q>IR*LuBZ)xX6{otZFw@9zXS1cQ;zuy1-e?LX+
A?f?J)
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/component-analyzer.ts b/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/component-analyzer.ts
deleted file mode 100644
index 3ecee23..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/component-analyzer.ts
+++ /dev/null
@@ -1,361 +0,0 @@
-/**
- * Component Analyzer (Cached, Tri-State, Zero-Copy)
- *
- * PERFORMANCE GUARANTEE:
- * - First call: O(D) where D = decorator properties
- * - Subsequent calls: O(1) WeakMap lookup
- * - Zero allocation after cache warm-up
- *
- * RULES MUST NEVER:
- * - Parse decorators themselves
- * - Retry inference on non-literal values
- * - Call this analyzer multiple times (it's cached!)
- */
-
-import type { ClassDeclaration } from '../ast/types.js';
-import {
- hasDecorator,
- getDecoratorNameUnsafe,
- getDecoratorObjectArgUnsafe,
- getObjectPropertyUnsafe,
- matchesMemberExpression,
- getLiteralStringValueUnsafe,
- getLiteralBooleanValueUnsafe,
-} from '../ast/matchers.js';
-import type { ArrayExpression, ObjectExpression, Expression, Identifier } from '../ast/types.js';
-
-// ============================================
-// TRI-STATE METADATA (Literal | Non-Literal | Missing)
-// ============================================
-
-/**
- * Tri-state value (no Option wrapper for performance).
- */
-export type LiteralValue = { readonly kind: 'literal'; readonly value: T };
-export type NonLiteralValue = { readonly kind: 'non-literal' };
-export type MissingValue = { readonly kind: 'missing' };
-
-export type MetadataValue = LiteralValue | NonLiteralValue | MissingValue;
-
-// Pre-allocated singletons (zero allocation)
-const NON_LITERAL: NonLiteralValue = { kind: 'non-literal' };
-const MISSING: MissingValue = { kind: 'missing' };
-
-/**
- * Creates literal value (only allocation when literal is found).
- */
-const literal = (value: T): LiteralValue => ({ kind: 'literal', value });
-
-// ============================================
-// COMPONENT METADATA
-// ============================================
-
-export enum ChangeDetectionStrategy {
- Default = 0,
- OnPush = 1,
-}
-
-/**
- * Host directive metadata.
- */
-export interface HostDirectiveMetadata {
- readonly directive: string | undefined;
- readonly inputs: ReadonlyArray<{ readonly internal: string; readonly external: string }>;
- readonly outputs: ReadonlyArray<{ readonly internal: string; readonly external: string }>;
-}
-
-/**
- * Component metadata (tri-state for all fields).
- *
- * PERFORMANCE: Allocated once per component, cached in WeakMap.
- */
-export interface ComponentMetadata {
- readonly className: string | undefined;
- readonly selector: MetadataValue;
- readonly changeDetection: MetadataValue;
- readonly standalone: MetadataValue;
- readonly templateUrl: MetadataValue;
- readonly template: MetadataValue;
- readonly hostDirectives: MetadataValue>;
- readonly decoratorStart: number; // Position of decorator for error reporting
- readonly type: 'Component' | 'Directive';
-}
-
-// ============================================
-// CACHE (Per-File Execution)
-// ============================================
-
-/**
- * Component metadata cache (WeakMap for automatic GC).
- *
- * CRITICAL: This is the ONLY place component analysis happens.
- * Rules MUST call this, never parse decorators themselves.
- */
-const componentCache = new WeakMap();
-
-/**
- * Cache statistics (scoped accumulator – avoids loose mutable globals).
- */
-interface CacheStatsAccumulator {
- hits: number;
- misses: number;
-}
-
-const cacheStats: CacheStatsAccumulator = { hits: 0, misses: 0 };
-
-export const getComponentCacheStats = (): Readonly => ({ hits: cacheStats.hits, misses: cacheStats.misses });
-export const resetComponentCacheStats = (): void => { cacheStats.hits = 0; cacheStats.misses = 0; };
-
-// ============================================
-// MAIN ANALYZER (Cached)
-// ============================================
-
-/**
- * Analyzes @Component decorator metadata.
- *
- * @returns ComponentMetadata if @Component found, null otherwise
- *
- * PERFORMANCE:
- * - First call: O(D) where D = decorator properties
- * - Subsequent calls: O(1) WeakMap lookup
- *
- * RULES MUST:
- * - Call this once per component
- * - Handle null (not a component)
- * - Handle tri-state values (literal/non-literal/missing)
- *
- * RULES MUST NOT:
- * - Retry inference on non-literal values
- * - Parse decorators themselves
- * - Allocate collections
- */
-export const analyzeComponent = (classNode: ClassDeclaration): ComponentMetadata | null => {
- // Check cache (O(1))
- const cached = componentCache.get(classNode);
- if (cached !== undefined) {
- cacheStats.hits++;
- return cached;
- }
-
- cacheStats.misses++;
-
- // Not a component or directive? Cache negative result.
- const isComp = hasDecorator(classNode, 'Component');
- const isDir = !isComp && hasDecorator(classNode, 'Directive');
-
- if (!isComp && !isDir) {
- componentCache.set(classNode, null);
- return null;
- }
-
- const decoratorName = isComp ? 'Component' : 'Directive';
-
- // Find @Component decorator
- const decorators = classNode.decorators;
- if (!decorators) {
- componentCache.set(classNode, null);
- return null;
- }
-
- let angularDecorator = undefined;
- for (let i = 0; i < decorators.length; i++) {
- const name = getDecoratorNameUnsafe(decorators[i]);
- if (name === decoratorName) {
- angularDecorator = decorators[i];
- break;
- }
- }
-
- if (!angularDecorator) {
- componentCache.set(classNode, null);
- return null;
- }
-
- // Extract metadata object
- const metadataObject = getDecoratorObjectArgUnsafe(angularDecorator);
-
- // Build metadata (allocated once, cached)
- const metadata: ComponentMetadata = {
- className: classNode.id?.name,
- selector: metadataObject ? extractSelector(metadataObject) : MISSING,
- changeDetection: isComp && metadataObject ? extractChangeDetection(metadataObject) : MISSING,
- standalone: metadataObject ? extractStandalone(metadataObject) : MISSING,
- templateUrl: isComp && metadataObject ? extractTemplateUrl(metadataObject) : MISSING,
- template: isComp && metadataObject ? extractTemplate(metadataObject) : MISSING,
- hostDirectives: metadataObject ? extractHostDirectives(metadataObject) : MISSING,
- decoratorStart: angularDecorator.start ?? angularDecorator.span?.start ?? 0, // Track decorator position
- type: decoratorName,
- };
-
- componentCache.set(classNode, metadata);
- return metadata;
-};
-
-// ============================================
-// PRIVATE EXTRACTORS (Tri-State, Zero-Copy)
-// ============================================
-
-const extractSelector = (metadataObject: any): MetadataValue => {
- const selectorNode = getObjectPropertyUnsafe(metadataObject, 'selector');
- if (!selectorNode) return MISSING;
-
- const value = getLiteralStringValueUnsafe(selectorNode);
- if (value !== undefined) return literal(value);
-
- return NON_LITERAL;
-};
-
-const extractChangeDetection = (metadataObject: any): MetadataValue => {
- const cdNode = getObjectPropertyUnsafe(metadataObject, 'changeDetection');
- if (!cdNode) return MISSING;
-
- // Identifier: OnPush or Default
- if (cdNode.type === 'Identifier') {
- const name = (cdNode as any).name;
- if (name === 'OnPush') return literal(ChangeDetectionStrategy.OnPush);
- if (name === 'Default') return literal(ChangeDetectionStrategy.Default);
- return NON_LITERAL;
- }
-
- // Member: ChangeDetectionStrategy.OnPush
- if (matchesMemberExpression(cdNode, 'ChangeDetectionStrategy', 'OnPush')) {
- return literal(ChangeDetectionStrategy.OnPush);
- }
-
- if (matchesMemberExpression(cdNode, 'ChangeDetectionStrategy', 'Default')) {
- return literal(ChangeDetectionStrategy.Default);
- }
-
- return NON_LITERAL;
-};
-
-const extractStandalone = (metadataObject: any): MetadataValue => {
- const standaloneNode = getObjectPropertyUnsafe(metadataObject, 'standalone');
- if (!standaloneNode) return MISSING;
-
- const value = getLiteralBooleanValueUnsafe(standaloneNode);
- if (value !== undefined) return literal(value);
-
- return NON_LITERAL;
-};
-
-const extractTemplateUrl = (metadataObject: any): MetadataValue => {
- const templateUrlNode = getObjectPropertyUnsafe(metadataObject, 'templateUrl');
- if (!templateUrlNode) return MISSING;
-
- const value = getLiteralStringValueUnsafe(templateUrlNode);
- if (value !== undefined) return literal(value);
-
- return NON_LITERAL;
-};
-
-const extractTemplate = (metadataObject: any): MetadataValue => {
- const templateNode = getObjectPropertyUnsafe(metadataObject, 'template');
- if (!templateNode) return MISSING;
-
- const value = getLiteralStringValueUnsafe(templateNode);
- if (value !== undefined) return literal(value);
-
- return NON_LITERAL;
-};
-
-const extractHostDirectives = (metadataObject: any): MetadataValue> => {
- const hostNode = getObjectPropertyUnsafe(metadataObject, 'hostDirectives');
- if (!hostNode) return MISSING;
-
- if (hostNode.type !== 'ArrayExpression') return NON_LITERAL;
-
- const hostArr = hostNode as ArrayExpression;
- const results: HostDirectiveMetadata[] = [];
- const elements = hostArr.elements;
-
- for (let i = 0; i < elements.length; i++) {
- const el = elements[i];
- if (!el) continue;
-
- // Case 1: Class reference [MyDir]
- if (el.type === 'Identifier') {
- results.push({ directive: (el as Identifier).name, inputs: [], outputs: [] });
- continue;
- }
-
- // Case 2: Object { directive: MyDir, inputs: [...], outputs: [...] }
- if (el.type === 'ObjectExpression') {
- const objEl = el as ObjectExpression;
- const dirNode = getObjectPropertyUnsafe(objEl, 'directive');
- const directive = dirNode?.type === 'Identifier' ? (dirNode as Identifier).name : undefined;
-
- const inputs = extractRenames(getObjectPropertyUnsafe(objEl, 'inputs'));
- const outputs = extractRenames(getObjectPropertyUnsafe(objEl, 'outputs'));
-
- results.push({ directive, inputs, outputs });
- continue;
- }
-
- // Unknown type
- }
-
- return literal(results);
-};
-
-const extractRenames = (node: Expression | undefined): ReadonlyArray<{ internal: string, external: string }> => {
- if (!node || node.type !== 'ArrayExpression') return [];
-
- const arrNode = node as ArrayExpression;
- const renames: { internal: string, external: string }[] = [];
- const elements = arrNode.elements;
-
- for (let i = 0; i < elements.length; i++) {
- const el = elements[i];
- if (!el) continue;
-
- const value = getLiteralStringValueUnsafe(el);
- if (!value) continue;
-
- if (value.includes(':')) {
- const [internal, external] = value.split(':').map(s => s.trim());
- renames.push({ internal, external });
- } else {
- renames.push({ internal: value, external: value });
- }
- }
-
- return renames;
-};
-
-// ============================================
-// HIGH-LEVEL CHECKS (Convenience Functions)
-// ============================================
-
-/**
- * Checks if class is an Angular component.
- *
- * PERFORMANCE: O(1) after first call (cached).
- */
-export const isComponent = (classNode: ClassDeclaration): boolean => {
- return analyzeComponent(classNode) !== null;
-};
-
-/**
- * Checks if component uses OnPush (literal value only).
- *
- * PERFORMANCE: O(1) after first call.
- */
-export const usesOnPush = (classNode: ClassDeclaration): boolean => {
- const component = analyzeComponent(classNode);
- if (!component) return false;
-
- const cd = component.changeDetection;
- return cd.kind === 'literal' && cd.value === ChangeDetectionStrategy.OnPush;
-};
-
-/**
- * Checks if component is standalone: true (literal value only).
- */
-export const isStandalone = (classNode: ClassDeclaration): boolean => {
- const component = analyzeComponent(classNode);
- if (!component) return false;
-
- const standalone = component.standalone;
- return standalone.kind === 'literal' && standalone.value === true;
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/index.ts b/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/index.ts
deleted file mode 100644
index eba1a21..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * Cached Analyzers
- *
- * Centralized, cached, tri-state metadata extraction.
- */
-
-export {
- analyzeComponent,
- isComponent,
- usesOnPush,
- isStandalone,
- getComponentCacheStats,
- resetComponentCacheStats,
- ChangeDetectionStrategy,
- type ComponentMetadata,
- type MetadataValue,
- type LiteralValue,
- type NonLiteralValue,
- type MissingValue,
-} from './component-analyzer.js';
-export { analyzeTemplate } from './template-analyzer.js';
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/template-analyzer.ts b/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/template-analyzer.ts
deleted file mode 100644
index 18ca7fa..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/analyzers/template-analyzer.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-
-export interface HtmlParserResult {
- rootNodes: readonly unknown[];
-}
-import type { TemplateExpressionNode, TemplateAttributeNode, TemplateBlockNode } from '../node-streams.js';
-import { parseSync } from 'oxc-parser';
-
-/**
- * Traverses HTML AST and extracts Angular expressions.
- */
-export const analyzeTemplate = (htmlResult: HtmlParserResult): {
- expressions: TemplateExpressionNode[];
- attributes: TemplateAttributeNode[];
- blocks: TemplateBlockNode[];
-} => {
- const expressions: TemplateExpressionNode[] = [];
- const attributes: TemplateAttributeNode[] = [];
- const blocks: TemplateBlockNode[] = [];
-
- const visit = (node: any) => {
- if (!node) return;
-
- // Recurse into children first (generated AST from angular-html-parser).
- // for-of replaces forEach to avoid allocating a closure on every call.
- if (node.children) {
- for (const child of node.children) visit(child);
- }
-
- // 1. Attributes (Inputs, Structural Directives)
- if (node.attrs) {
- for (const attr of node.attrs) {
- const name: string = attr.name;
- const value: string = attr.value;
- const attrOffset = attr.valueSpan?.start?.offset ?? 0;
-
- // Always register to the attributes stream
- attributes.push({
- name,
- value,
- sourceSpan: {
- start: attr.sourceSpan?.start?.offset ?? 0,
- end: attr.sourceSpan?.end?.offset ?? 0,
- },
- });
-
- if (!value) continue;
-
- // *ngIf="expr" — structural directives use microsyntax
- if (name.startsWith('*')) {
- parseAndAdd(value, attrOffset, expressions);
- // [prop]="expr" — property binding
- } else if (name.startsWith('[') && name.endsWith(']')) {
- parseAndAdd(value, attrOffset, expressions);
- // bind-prop="expr" — long-form property binding
- } else if (name.startsWith('bind-')) {
- parseAndAdd(value, attrOffset, expressions);
- }
- }
- }
-
- // 2. Text and Interpolations
- const isText = node.kind === 'text' || node.type === 'text' || node.constructor?.name === 'Text';
- if (isText && node.value) {
- // Use tokens when available (some angular-html-parser versions provide them)
- if (node.tokens) {
- for (const token of node.tokens) {
- if (token.type === 8 && token.parts?.length === 3) {
- const expr = token.parts[1];
- const startOffset = token.sourceSpan.start.offset + token.parts[0].length;
- parseAndAdd(expr, startOffset, expressions);
- }
- }
- } else {
- // Manual interpolation extraction: scan for {{ ... }} pairs
- const textValue = node.value as string;
- const nodeStart = node.sourceSpan?.start?.offset ?? 0;
- let lastIndex = 0;
-
- let start = textValue.indexOf('{{', lastIndex);
- while (start !== -1) {
- const end = textValue.indexOf('}}', start + 2);
- if (end === -1) break;
-
- parseAndAdd(
- textValue.substring(start + 2, end),
- nodeStart + start + 2,
- expressions
- );
-
- lastIndex = end + 2;
- start = textValue.indexOf('{{', lastIndex);
- }
- }
- }
-
- // 3. Control Flow Blocks (@for, @if, etc.)
- if (node.kind === 'block' || node.constructor?.name === 'Block') {
- const blockName = node.name;
- const parameters = node.parameters ?? [];
-
- blocks.push({
- name: blockName,
- parameters: parameters.map((p: any) => ({
- expression: p.expression,
- sourceSpan: {
- start: p.sourceSpan?.start?.offset ?? 0,
- end: p.sourceSpan?.end?.offset ?? 0,
- }
- })),
- sourceSpan: {
- start: node.sourceSpan?.start?.offset ?? 0,
- end: node.sourceSpan?.end?.offset ?? 0,
- }
- });
-
- for (const param of parameters) {
- if (param.expression) {
- const offset = param.sourceSpan?.start?.offset ?? 0;
-
- // Special handling for @for microsyntax: (item of items; track item.id)
- // The parser might provide the whole string.
- // For now, we'll try to parse the expression part.
- if (blockName === 'for') {
- if (param.expression.includes(' track ')) {
- const trackPart = param.expression.split(' track ')[1];
- const trackOffset = offset + param.expression.indexOf(' track ') + 7;
- parseAndAdd(trackPart, trackOffset, expressions);
- } else if (param.expression.startsWith('track ')) {
- const trackPart = param.expression.substring(6);
- const trackOffset = offset + 6;
- parseAndAdd(trackPart, trackOffset, expressions);
- }
- // Also try to parse the collection expression
- if (param.expression.includes(' of ')) {
- const collectionPart = param.expression.split(' of ')[1].split(';')[0];
- const collectionOffset = offset + param.expression.indexOf(' of ') + 4;
- parseAndAdd(collectionPart, collectionOffset, expressions);
- }
- } else {
- parseAndAdd(param.expression, offset, expressions);
- }
- }
- }
- }
- };
-
- for (const rootNode of htmlResult.rootNodes) visit(rootNode);
-
- return { expressions, attributes, blocks };
-};
-
-const parseAndAdd = (code: string, offset: number, outcomes: TemplateExpressionNode[]) => {
- if (!code.trim()) return;
-
- try {
- // Wrap in parentheses so that object literals like { color: 'red' } are parsed as
- // ObjectExpression (inside an ExpressionStatement) rather than as a BlockStatement.
- // This is safe for all Angular template expressions — identifiers, calls, ternaries,
- // binary pipes (a | b), etc. all remain valid when wrapped.
- const wrappedCode = `(${code})`;
- const ret = parseSync('template.ts', wrappedCode, { sourceType: 'module', lang: 'ts' });
-
- if (ret.program.body.length > 0) {
- const stmt = ret.program.body[0];
- // Expect ExpressionStatement provided it's an expression
- if (stmt.type === 'ExpressionStatement' && stmt.expression) {
- // Unwrap the ParenthesizedExpression node that oxc-parser creates when
- // the source text is wrapped in `(...)`. Without unwrapping, rules that
- // test for e.g. ObjectExpression / ArrayExpression / BinaryExpression
- // would see a ParenthesizedExpression root and miss the match.
- let expr: any = stmt.expression;
- if (expr.type === 'ParenthesizedExpression' && expr.expression) {
- expr = expr.expression;
- }
- outcomes.push({
- expression: expr,
- sourceSpan: {
- start: offset,
- end: offset + code.length
- }
- });
- }
- }
- } catch (e) {
- // Ignore parse errors for fragments
- }
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/ast/index.ts b/.claude/worktrees/nice-taussig/packages/ast/src/ast/index.ts
deleted file mode 100644
index 5ddd3d2..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/ast/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './types.js';
-export * from './matchers.js';
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/ast/matchers.ts b/.claude/worktrees/nice-taussig/packages/ast/src/ast/matchers.ts
deleted file mode 100644
index 4c37011..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/ast/matchers.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-/**
- * AST Matchers (Zero-Allocation, Pure Functions)
- *
- * PERFORMANCE RULES:
- * - No object creation in hot paths
- * - No array allocations
- * - No string concatenation
- * - Return primitives or pre-existing references only
- *
- * "Unsafe" suffix convention: May return undefined, caller must handle.
- */
-
-import type {
- Decorator,
- ClassDeclaration,
- ObjectExpression,
- Expression,
- MemberExpression,
- CallExpression,
- Identifier,
- StringLiteral,
- BooleanLiteral,
-} from './types.js';
-
-// ============================================
-// DECORATOR CHECKS (Zero Allocation)
-// ============================================
-
-/**
- * Checks if a node has a specific decorator.
- *
- * @returns boolean (primitive, zero allocation)
- */
-export const hasDecorator = (
- classNode: ClassDeclaration,
- decoratorName: string
-): boolean => {
- const decorators = classNode.decorators;
- if (!decorators) return false;
-
- for (let i = 0; i < decorators.length; i++) {
- const decorator = decorators[i];
- const name = getDecoratorNameUnsafe(decorator);
- if (name === decoratorName) return true;
- }
-
- return false;
-};
-
-/**
- * Gets decorator name (unsafe: may return undefined).
- *
- * PERFORMANCE: Returns string reference from AST (zero copy).
- * Rules must handle undefined.
- */
-export const getDecoratorNameUnsafe = (decorator: Decorator): string | undefined => {
- const expr = decorator.expression;
- if (!expr) return undefined;
-
- if (expr.type === 'CallExpression') {
- const callee = expr.callee;
-
- // Simple: @Component
- if (callee.type === 'Identifier') {
- return (callee as Identifier).name;
- }
-
- // Member: @core.Component
- if (callee.type === 'MemberExpression' || callee.type === 'StaticMemberExpression') {
- const prop = (callee as MemberExpression).property;
- if (prop.type === 'Identifier') {
- return prop.name;
- }
- }
- }
-
- return undefined;
-};
-
-/**
- * Gets first decorator argument if it's an object literal.
- *
- * PERFORMANCE: Returns AST reference (zero copy).
- */
-export const getDecoratorObjectArgUnsafe = (
- decorator: Decorator
-): ObjectExpression | undefined => {
- const expr = decorator.expression;
- if (!expr || expr.type !== 'CallExpression') return undefined;
-
- const args = expr.arguments;
- if (!args || args.length === 0) return undefined;
-
- const first = args[0];
- return first.type === 'ObjectExpression' ? (first as ObjectExpression) : undefined;
-};
-
-// ============================================
-// OBJECT PROPERTY LOOKUP (Zero Allocation)
-// ============================================
-
-/**
- * Checks if object has a property with given key.
- *
- * PERFORMANCE: No allocation, early return.
- */
-export const hasObjectProperty = (
- objectExpr: ObjectExpression,
- keyName: string
-): boolean => {
- const properties = objectExpr.properties;
- if (!properties) return false;
-
- for (let i = 0; i < properties.length; i++) {
- const prop = properties[i];
- if (!prop || prop.type === 'SpreadElement') continue;
-
- const objectProp = prop;
- const actualKeyName = getKeyNameUnsafe(objectProp.key);
-
- if (actualKeyName === keyName) return true;
- }
-
- return false;
-};
-
-/**
- * Gets property value by key name (unsafe: may return undefined).
- *
- * PERFORMANCE: Returns AST reference (zero copy).
- */
-export const getObjectPropertyUnsafe = (
- objectExpr: ObjectExpression,
- keyName: string
-): Expression | undefined => {
- const properties = objectExpr.properties;
- if (!properties) return undefined;
-
- for (let i = 0; i < properties.length; i++) {
- const prop = properties[i];
- if (!prop || prop.type === 'SpreadElement') continue;
-
- const objectProp = prop;
- const actualKeyName = getKeyNameUnsafe(objectProp.key);
-
- if (actualKeyName === keyName) {
- return objectProp.value;
- }
- }
-
- return undefined;
-};
-
-/**
- * Gets key name from object key (unsafe).
- *
- * PERFORMANCE: Returns string reference from AST.
- */
-export const getKeyNameUnsafe = (key: Expression): string | undefined => {
- if (!key) return undefined;
-
- // Identifier: { foo: ... }
- if (key.type === 'Identifier') return (key as Identifier).name;
-
- // String literal: { "foo": ... }
- if (key.type === 'StringLiteral' || key.type === 'Literal') {
- const lit = key as StringLiteral;
- return typeof lit.value === 'string' ? lit.value : undefined;
- }
-
- return undefined;
-};
-
-// ============================================
-// MEMBER EXPRESSION CHECKS
-// ============================================
-
-/**
- * Checks if member expression matches pattern (e.g., ChangeDetectionStrategy.OnPush).
- *
- * PERFORMANCE: No allocation, early return.
- */
-export const matchesMemberExpression = (
- expr: Expression,
- objectName: string,
- propertyName: string
-): boolean => {
- if (!expr) return false;
-
- if (expr.type !== 'MemberExpression' && expr.type !== 'StaticMemberExpression') {
- return false;
- }
-
- const memberExpr = expr as MemberExpression;
-
- // Check property
- const prop = memberExpr.property;
- if (!prop || prop.type !== 'Identifier' || prop.name !== propertyName) {
- return false;
- }
-
- // Check object
- const obj = memberExpr.object;
- if (!obj) return false;
-
- // Simple: ChangeDetectionStrategy.OnPush
- if (obj.type === 'Identifier' && (obj as Identifier).name === objectName) {
- return true;
- }
-
- // Nested: core.ChangeDetectionStrategy.OnPush
- if ((obj.type === 'MemberExpression' || obj.type === 'StaticMemberExpression') &&
- (obj as MemberExpression).property.type === 'Identifier' &&
- (obj as MemberExpression).property.name === objectName) {
- return true;
- }
-
- return false;
-};
-
-/**
- * Gets literal string value (unsafe: may return undefined).
- *
- * PERFORMANCE: Returns string reference from AST.
- */
-export const getLiteralStringValueUnsafe = (node: Expression): string | undefined => {
- if (!node) return undefined;
-
- if (node.type === 'StringLiteral' || node.type === 'Literal') {
- const value = (node as StringLiteral).value;
- return typeof value === 'string' ? value : undefined;
- }
-
- return undefined;
-};
-
-/**
- * Gets literal boolean value (unsafe: may return undefined).
- *
- * PERFORMANCE: Returns boolean primitive from AST.
- */
-export const getLiteralBooleanValueUnsafe = (node: Expression): boolean | undefined => {
- if (!node) return undefined;
-
- if (node.type === 'BooleanLiteral' || node.type === 'Literal') {
- const value = (node as BooleanLiteral).value;
- return typeof value === 'boolean' ? value : undefined;
- }
-
- return undefined;
-};
-
-/**
- * Checks if expression is an Angular input() signal call.
- */
-export const isInputSignal = (expr: Expression): boolean => {
- if (!expr || expr.type !== 'CallExpression') return false;
- const callee = (expr as CallExpression).callee;
-
- // input()
- if (callee.type === 'Identifier' && (callee as Identifier).name === 'input') return true;
-
- // input.required()
- if (callee.type === 'MemberExpression' || callee.type === 'StaticMemberExpression') {
- const member = callee as MemberExpression;
- return member.object.type === 'Identifier' &&
- (member.object as Identifier).name === 'input' &&
- member.property.type === 'Identifier' &&
- member.property.name === 'required';
- }
-
- return false;
-};
-
-/**
- * Extracts alias from input() signal call if present.
- */
-export const getInputSignalAliasUnsafe = (callExpr: CallExpression): string | undefined => {
- const callee = callExpr.callee;
- const args = callExpr.arguments;
- if (!args || args.length === 0) return undefined;
-
- const isRequired = callee.type === 'MemberExpression' || callee.type === 'StaticMemberExpression';
-
- if (isRequired) {
- // input.required({ alias: 'alias' })
- const first = args[0];
- if (first && first.type === 'ObjectExpression') {
- return getLiteralStringValueUnsafe(getObjectPropertyUnsafe(first as ObjectExpression, 'alias') as Expression);
- }
- } else {
- // input('alias')
- if (args.length === 1) {
- const first = args[0];
- if (first.type === 'StringLiteral' || first.type === 'Literal') {
- return getLiteralStringValueUnsafe(first);
- }
- }
- // input(val, { alias: 'alias' })
- if (args.length >= 2) {
- const second = args[1];
- if (second && second.type === 'ObjectExpression') {
- return getLiteralStringValueUnsafe(getObjectPropertyUnsafe(second as ObjectExpression, 'alias') as Expression);
- }
- }
- }
-
- return undefined;
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/ast/types.ts b/.claude/worktrees/nice-taussig/packages/ast/src/ast/types.ts
deleted file mode 100644
index 26f0c35..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/ast/types.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-/**
- * Minimal AST Type Definitions (Zero-Copy, ESTree-like)
- *
- * These types match Oxc's ESTree-compatible AST structure.
- * Only include fields we actually use to minimize overhead.
- */
-
-export interface Node {
- readonly type: string;
- readonly span?: { start: number; end: number };
- readonly start?: number;
- readonly end?: number;
-}
-
-export interface Identifier extends Node {
- readonly type: 'Identifier';
- readonly name: string;
-}
-
-export interface Decorator extends Node {
- readonly type: 'Decorator';
- readonly expression?: CallExpression | Identifier;
-}
-
-export interface CallExpression extends Node {
- readonly type: 'CallExpression';
- readonly callee: Expression;
- readonly arguments: ReadonlyArray;
-}
-
-export interface NewExpression extends Node {
- readonly type: 'NewExpression';
- readonly callee: Expression;
- readonly arguments: ReadonlyArray;
-}
-
-export interface MemberExpression extends Node {
- readonly type: 'MemberExpression' | 'StaticMemberExpression';
- readonly object: Expression;
- readonly property: Identifier;
-}
-
-export interface ObjectExpression extends Node {
- readonly type: 'ObjectExpression';
- readonly properties: ReadonlyArray;
-}
-
-export interface ObjectProperty extends Node {
- readonly type: 'ObjectProperty' | 'Property';
- readonly key: Expression;
- readonly value: Expression;
-}
-
-export interface SpreadElement extends Node {
- readonly type: 'SpreadElement';
-}
-
-export interface ArrayExpression extends Node {
- readonly type: 'ArrayExpression';
- readonly elements: ReadonlyArray;
-}
-
-export interface StringLiteral extends Node {
- readonly type: 'StringLiteral' | 'Literal';
- readonly value: string;
-}
-
-export interface BooleanLiteral extends Node {
- readonly type: 'BooleanLiteral' | 'Literal';
- readonly value: boolean;
-}
-
-export interface ArrowFunctionExpression extends Node {
- readonly type: 'ArrowFunctionExpression';
- readonly body: Expression | BlockStatement;
- readonly expression: boolean;
-}
-
-export interface FunctionExpression extends Node {
- readonly type: 'FunctionExpression';
- readonly body: BlockStatement;
-}
-
-export interface BlockStatement extends Node {
- readonly type: 'BlockStatement';
- readonly body: ReadonlyArray;
-}
-
-export interface ExpressionStatement extends Node {
- readonly type: 'ExpressionStatement';
- readonly expression: Expression;
-}
-
-export interface AssignmentExpression extends Node {
- readonly type: 'AssignmentExpression';
- readonly left: Expression;
- readonly right: Expression;
- readonly operator: string;
-}
-
-export interface UpdateExpression extends Node {
- readonly type: 'UpdateExpression';
- readonly operator: string;
- readonly argument: Expression;
- readonly prefix: boolean;
-}
-
-export interface IfStatement extends Node {
- readonly type: 'IfStatement';
- readonly test: Expression;
- readonly consequent: Node;
- readonly alternate?: Node;
-}
-
-export interface ReturnStatement extends Node {
- readonly type: 'ReturnStatement';
- readonly argument?: Expression;
-}
-
-export interface ClassDeclaration extends Node {
- readonly type: 'ClassDeclaration';
- readonly id?: Identifier;
- readonly decorators?: ReadonlyArray;
- readonly body?: ClassBody;
-}
-
-export interface ClassBody extends Node {
- readonly type: 'ClassBody';
- readonly body: ReadonlyArray;
-}
-
-export interface PropertyDefinition extends Node {
- readonly type: 'PropertyDefinition';
- readonly key: Expression;
- readonly value?: Expression;
- readonly decorators?: ReadonlyArray;
-}
-
-export interface MethodDefinition extends Node {
- readonly type: 'MethodDefinition';
- readonly key: Expression;
-}
-
-export interface TemplateBlock extends Node {
- readonly type: 'Block';
- readonly name: string;
- readonly parameters: ReadonlyArray;
- readonly children: ReadonlyArray;
-}
-
-export interface TemplateBlockParameter extends Node {
- readonly type: 'BlockParameter';
- readonly expression: string;
-}
-
-export type Expression =
- | Identifier
- | CallExpression
- | NewExpression
- | MemberExpression
- | ObjectExpression
- | ArrayExpression
- | StringLiteral
- | BooleanLiteral
- | ArrowFunctionExpression
- | FunctionExpression
- | AssignmentExpression
- | UpdateExpression
- | TemplateBlock
- | TemplateBlockParameter
- | Node;
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/index.ts b/.claude/worktrees/nice-taussig/packages/ast/src/index.ts
deleted file mode 100644
index de5568c..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @ngcompass/ast
- *
- * AST types, parsers, analyzers, node streams and visitor for the ngcompass analysis engine.
- */
-
-// AST types and matchers
-export * from './ast/index.js';
-
-// Analyzers (component + template)
-export * from './analyzers/index.js';
-
-// Node streams and stream filter functions
-export * from './node-streams.js';
-
-// Parsers (html, css, ts, template-extractor) — merged into @ngcompass/ast
-export * from './parsers/index.js';
-
-// AST visitor
-export { walkProgram } from './visitor.js';
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/node-streams.ts b/.claude/worktrees/nice-taussig/packages/ast/src/node-streams.ts
deleted file mode 100644
index 398df03..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/node-streams.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * Node Streams (Pre-Filtered Semantic Dispatch)
- *
- * PERFORMANCE RULE:
- * Rules must subscribe to the most specific stream possible.
- *
- * FORBIDDEN:
- * - Rules checking "is this a component?" (use AngularClassStream)
- * - Rules checking "is this decorated?" (use DecoratedPropertyStream)
- * - Rules checking node types (dispatcher handles this)
- */
-
-import type { ClassDeclaration, PropertyDefinition, Decorator, Expression, CallExpression, NewExpression } from './ast/types.js';
-import { analyzeComponent, type ComponentMetadata } from './analyzers/component-analyzer.js';
-import { getDecoratorNameUnsafe } from './ast/matchers.js';
-
-// ============================================
-// STREAM DEFINITIONS
-// ============================================
-
-export interface TemplateExpressionNode {
- readonly expression: Expression;
- readonly sourceSpan: { start: number, end: number };
-}
-
-export interface TemplateAttributeNode {
- readonly name: string;
- readonly value?: string;
- readonly sourceSpan: { start: number, end: number };
-}
-
-export interface TemplateBlockNode {
- readonly name: string;
- readonly parameters: ReadonlyArray<{
- readonly expression: string;
- readonly sourceSpan: { start: number, end: number };
- }>;
- readonly sourceSpan: { start: number, end: number };
-}
-
-export interface TemplateAnalysis {
- readonly expressions: ReadonlyArray;
- readonly attributes: ReadonlyArray;
- readonly blocks: ReadonlyArray;
-}
-
-/**
- * Angular Decorator Stream: ClassDeclaration nodes with @Component or @Directive.
- *
- * Rules subscribing to this stream are guaranteed:
- * - Node is a ClassDeclaration
- * - Node has @Component or @Directive decorator
- * - Metadata is pre-analyzed and cached
- */
-export interface AngularClassNode {
- readonly node: ClassDeclaration;
- readonly metadata: ComponentMetadata; // Covers both Components and Directives
-}
-
-/**
- * Any Angular Decorated Class Stream: ClassDeclaration nodes with ANY Angular decorator.
- *
- * Rules subscribing to this stream are guaranteed:
- * - Node is a ClassDeclaration
- * - Node has at least one of: @Component, @Directive, @Pipe, @Injectable, @NgModule
- *
- * Use this when the rule applies beyond @Component/@Directive (e.g. naming rules
- * for pipes, services, or guards).
- */
-export interface AnyAngularClassNode {
- readonly node: ClassDeclaration;
- readonly decoratorName: string; // e.g. 'Pipe', 'Injectable', 'Component', …
- readonly className: string | undefined;
- readonly decoratorStart: number;
-}
-
-/**
- * Decorated Property Stream: PropertyDefinition nodes with decorators.
- *
- * Rules subscribing to this stream are guaranteed:
- * - Node is a PropertyDefinition
- * - Node has at least one decorator (@Input, @Output, @ViewChild, etc.)
- */
-export interface DecoratedPropertyNode {
- readonly node: PropertyDefinition;
- readonly decorators: ReadonlyArray; // Pre-extracted
-}
-
-// ============================================
-// STREAM FILTERS (Called by Engine)
-// ============================================
-
-/**
- * Filters ClassDeclaration nodes to Angular components or directives.
- *
- * PERFORMANCE: O(1) after first call (cached).
- * Called by engine during traversal, not by rules.
- */
-export const toAngularClassStream = (
- classNode: ClassDeclaration
-): AngularClassNode | null => {
- const metadata = analyzeComponent(classNode);
- if (!metadata) return null;
-
- return {
- node: classNode,
- metadata, // Already analyzed, zero-copy reference
- };
-};
-
-/** Angular decorators that qualify a class for the AnyAngularClass stream. */
-const ANY_ANGULAR_DECORATORS = new Set(['Component', 'Directive', 'Pipe', 'Injectable', 'NgModule']);
-
-/**
- * Filters ClassDeclaration nodes to ANY Angular-decorated class.
- *
- * PERFORMANCE: O(D) where D = number of decorators on the class (usually 1).
- * Called by engine during traversal, not by rules.
- */
-export const toAnyAngularClassStream = (
- classNode: ClassDeclaration
-): AnyAngularClassNode | null => {
- const decorators = classNode.decorators;
- if (!decorators) return null;
-
- for (let i = 0; i < decorators.length; i++) {
- const name = getDecoratorNameUnsafe(decorators[i]);
- if (name && ANY_ANGULAR_DECORATORS.has(name)) {
- return {
- node: classNode,
- decoratorName: name,
- className: classNode.id?.name,
- decoratorStart: decorators[i].start ?? decorators[i].span?.start ?? 0,
- };
- }
- }
-
- return null;
-};
-
-/**
- * Filters PropertyDefinition nodes to decorated properties.
- *
- * PERFORMANCE: O(1) decorator array access.
- * Called by engine, not by rules.
- */
-export const toDecoratedPropertyStream = (
- propertyNode: PropertyDefinition
-): DecoratedPropertyNode | null => {
- const decorators = propertyNode.decorators;
- if (!decorators || decorators.length === 0) return null;
-
- return {
- node: propertyNode,
- decorators, // Zero-copy reference
- };
-};
-
-/**
- * Pass-through filter for CallExpression stream.
- */
-export const toCallExpressionStream = (node: CallExpression): CallExpression => node;
-
-/**
- * Pass-through filter for NewExpression stream.
- */
-export const toNewExpressionStream = (node: NewExpression): NewExpression => node;
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/css.ts b/.claude/worktrees/nice-taussig/packages/ast/src/parsers/css.ts
deleted file mode 100644
index 69bbfe9..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/css.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { transform } from "lightningcss";
-
-export interface CssParserResult {
- code: Buffer | Uint8Array;
- map?: Buffer | Uint8Array | void;
-}
-
-/**
- * Result type for CSS parsing and validation.
- */
-export type CssResult =
- | { ok: true; code: Buffer | Uint8Array; map?: Buffer | Uint8Array | void }
- | { ok: false; error: unknown };
-
-/**
- * Parses and validates CSS using Lightning CSS.
- *
- * @param content - CSS source text
- * @param filePath - Source filename for diagnostics
- * @returns CssResult with transformed output or an error
- */
-export const parseCss = (content: string, filePath: string): CssResult => {
- try {
- const result = runLightningCssTransform(content, filePath);
- return { ok: true, ...result };
- } catch (error) {
- return { ok: false, error };
- }
-};
-
-/**
- * Executes a Lightning CSS transform pass for validation.
- *
- * @param content - CSS source text
- * @param filePath - Source filename for diagnostics
- * @returns Lightning CSS output payload
- */
-const runLightningCssTransform = (content: string, filePath: string): CssParserResult => {
- const code = Buffer.from(content);
-
- const result = transform({
- filename: filePath,
- code,
- minify: false,
- sourceMap: false,
- });
-
- return { code: result.code, map: result.map };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/html.ts b/.claude/worktrees/nice-taussig/packages/ast/src/parsers/html.ts
deleted file mode 100644
index 19c1512..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/html.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { parse } from "angular-html-parser";
-
-export interface HtmlParserResult {
- rootNodes: unknown[];
- errors: unknown[];
- /**
- * Byte offset in the *source file* where the template content begins.
- *
- * - For external `.html` files this is always `0` — the HTML file is the
- * template, so its offsets are already file-absolute.
- * - For inline templates inside a `.ts` file this is the position of the
- * first content character (right after the opening quote/backtick) in
- * the TypeScript source.
- *
- * Template rules MUST add this value to `node.sourceSpan.start` before
- * calling `context.locator.location()` so that reported line/column
- * numbers are correct for both inline and external templates.
- */
- templateStartOffset: number;
-}
-
-/**
- * Parses Angular HTML template source.
- *
- * @param content - Template source text (content only, no surrounding quotes)
- * @param templateStartOffset - Byte offset in the original source file where
- * `content[0]` lives. Use `0` for external `.html`
- * files; supply the value from `extractTemplateFromProgram`
- * for inline templates in `.ts` files.
- * @returns Parsed root nodes, parse errors, and the start offset.
- */
-export const parseHtml = (content: string, templateStartOffset = 0): HtmlParserResult => {
- const result = runAngularHtmlParse(content);
- return { rootNodes: result.rootNodes, errors: result.errors, templateStartOffset };
-};
-
-/**
- * Executes Angular HTML parsing with fixed configuration.
- *
- * @param content - Template source text
- * @returns Parser output
- */
-const runAngularHtmlParse = (content: string): { rootNodes: any[]; errors: any[] } => {
- return parse(content, { tokenizeAngularBlocks: true }) as any;
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/index.ts b/.claude/worktrees/nice-taussig/packages/ast/src/parsers/index.ts
deleted file mode 100644
index 5e10a6c..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// Barrel for the parsers sub-folder (now part of @ngcompass/ast)
-export * from './ts.js';
-export * from './html.js';
-export * from './css.js';
-export * from './template-extractor.js';
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/template-extractor.ts b/.claude/worktrees/nice-taussig/packages/ast/src/parsers/template-extractor.ts
deleted file mode 100644
index 73b773c..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/template-extractor.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * Template Extractor
- *
- * Extracts the inline template string from an Oxc-parsed TypeScript program.
- * Moved from orchestrator.ts to make it independently testable and reusable.
- *
- * Uses walkProgram() (the shared visitor) instead of a hand-rolled recursion,
- * which is consistent with how the rules engine traverses the AST and
- * benefits from the same early-exit optimisation (returning false stops descent).
- *
- * Handles both:
- * - StringLiteral: template: 'Hello
'
- * - TemplateLiteral: template: `{{ name }}
`
- */
-
-import { walkProgram } from '../visitor.js';
-import type { Program } from 'oxc-parser';
-
-// ============================================
-// PUBLIC API
-// ============================================
-
-/**
- * Result of extracting an inline template from a TypeScript program.
- */
-export interface ExtractedTemplate {
- /** The raw HTML content of the template (no surrounding quotes/backticks). */
- readonly content: string;
- /**
- * Byte offset in the TypeScript source file where `content[0]` lives.
- *
- * This is used to convert template-relative offsets (produced by the HTML
- * parser, which only sees the template content) back into file-absolute
- * offsets (required by `Locator.location()`).
- *
- * For external .html files this value is always 0 — the HTML file IS the
- * template, so its offsets are already file-absolute.
- */
- readonly startOffset: number;
-}
-
-/**
- * Extracts the inline template string from the first @Component class found
- * in the given Oxc program.
- *
- * @param program - Oxc-parsed Program node
- * @returns ExtractedTemplate with content and its start offset in the file,
- * or `{ content: '', startOffset: 0 }` if no template was found.
- */
-export function extractTemplateFromProgram(program: Program): ExtractedTemplate {
- let result: ExtractedTemplate = { content: '', startOffset: 0 };
-
- walkProgram(program, (node: any) => {
- // Once we have the template, stop walking (false = skip children)
- if (result.content) return false;
-
- if (node.type === 'ClassDeclaration' && Array.isArray(node.decorators)) {
- const extracted = tryExtractFromClass(node);
- if (extracted) {
- result = extracted;
- return false; // stop traversal
- }
- }
- });
-
- return result;
-}
-
-// ============================================
-// PRIVATE HELPERS
-// ============================================
-
-/**
- * Attempts to extract the `template` property value from a class decorated
- * with @Component.
- */
-function tryExtractFromClass(classNode: any): ExtractedTemplate | null {
- for (const decorator of classNode.decorators) {
- const call = decorator?.expression;
- if (!call || call.type !== 'CallExpression') continue;
-
- const callee = call.callee;
- if (!callee || callee.type !== 'Identifier' || callee.name !== 'Component') continue;
-
- const objectArg = call.arguments?.[0];
- if (!objectArg || objectArg.type !== 'ObjectExpression') continue;
-
- const templateValue = findPropertyValue(objectArg.properties, 'template');
- if (templateValue) {
- return extractStringValueWithOffset(templateValue);
- }
- }
- return null;
-}
-
-/**
- * Finds the value expression of a named property in an ObjectExpression.
- */
-function findPropertyValue(properties: any[], keyName: string): any {
- if (!Array.isArray(properties)) return null;
- for (const prop of properties) {
- const key = prop?.key;
- const value = prop?.value;
- if (!key || !value) continue;
- const name = key?.name ?? key?.value;
- if (name === keyName) return value;
- }
- return null;
-}
-
-/**
- * Extracts a string value AND its start offset from a StringLiteral or
- * TemplateLiteral AST node.
- *
- * The startOffset is the position of the *first content character* in the
- * original TypeScript source file — i.e. the position right after the
- * opening quote or backtick.
- */
-function extractStringValueWithOffset(node: any): ExtractedTemplate {
- if (!node) return { content: '', startOffset: 0 };
-
- if (node.type === 'StringLiteral' || node.type === 'Literal') {
- // node.start / node.span.start → position of the opening quote in the file
- const nodeStart: number = node.start ?? node.span?.start ?? 0;
- return {
- content: node.value ?? '',
- startOffset: nodeStart + 1, // +1 to skip the opening ' or "
- };
- }
-
- if (node.type === 'TemplateLiteral') {
- const quasis: any[] = node.quasis ?? [];
- const content = quasis.map((q: any) => q.value?.raw ?? '').join('');
- // The first quasi's start position points to the opening backtick
- const firstStart: number = quasis[0]?.start ?? quasis[0]?.span?.start ?? 0;
- return {
- content,
- startOffset: firstStart + 1, // +1 to skip the opening `
- };
- }
-
- return { content: '', startOffset: 0 };
-}
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/ts.ts b/.claude/worktrees/nice-taussig/packages/ast/src/parsers/ts.ts
deleted file mode 100644
index 8c5b124..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/parsers/ts.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { parseSync, type Program, type ParseResult } from "oxc-parser";
-
-export interface TsParserResult {
- program: Program;
- errors: unknown[];
-}
-
-/**
- * Parses TypeScript / TSX source code using Oxc.
- *
- * @param content - Source text
- * @param filePath - Source filename (used for diagnostics)
- * @returns Parsed program and parse errors
- */
-export const parseTs = (content: string, filePath: string): TsParserResult => {
- const result = runOxcParse(filePath, content);
- return { program: result.program, errors: result.errors };
-};
-
-/**
- * Executes a synchronous Oxc parse with fixed, deterministic options.
- *
- * @param filePath - Source filename
- * @param content - Source text
- * @returns Oxc ParseResult
- */
-const runOxcParse = (filePath: string, content: string): ParseResult => {
- return parseSync(filePath, content, {
- sourceType: "module",
- lang: "tsx",
- });
-};
diff --git a/.claude/worktrees/nice-taussig/packages/ast/src/visitor.ts b/.claude/worktrees/nice-taussig/packages/ast/src/visitor.ts
deleted file mode 100644
index eabaf92..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/src/visitor.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Keys that are never AST children — skipped in every node to avoid false traversal.
- * Defined at module level so the Set is allocated once, not per call.
- */
-const NON_CHILD_KEYS = new Set(['parent', 'span', 'loc', 'range', 'start', 'end', 'type']);
-
-/**
- * Iterative pre-order DFS walker for Oxc AST.
- *
- * Replaces the previous recursive implementation to eliminate two hotpath costs:
- * 1. Call-stack overflow risk on deeply nested ASTs (large files with many nested
- * arrow functions, ternaries, optional chaining, etc.).
- * 2. `Object.keys()` allocation on every node — on a 10 000-node AST that was
- * 10 000 short-lived string arrays per file. `for...in` iterates without
- * allocating the intermediate array.
- *
- * Traversal order: identical to the recursive version (pre-order DFS, children
- * visited in property-key insertion order). The engine relies only on the guarantee
- * that every node is visited; it does NOT depend on parent-before-child ordering
- * beyond what pre-order naturally provides.
- *
- * @param root - The Program (or any sub-tree root) to walk
- * @param visitor - Called for each node. Return `false` to skip that node's children.
- */
-export function walkProgram(root: any, visitor: (node: any) => void | boolean): void {
- if (!root) return;
-
- // Explicit stack replaces the call stack — no recursion depth limit.
- // We push children in reverse insertion order so that the first property's
- // subtree is processed before subsequent ones (matching recursive pre-order).
- const stack: any[] = [root];
-
- while (stack.length > 0) {
- const node = stack.pop();
- if (!node || typeof node !== 'object' || !node.type) continue;
-
- const shouldContinue = visitor(node);
- if (shouldContinue === false) continue;
-
- // Collect children before pushing so we can reverse them for correct order.
- // A local temp array here is unavoidable for reversal, but it is tiny
- // (typically 2–8 entries) compared to the 50–100 key array that
- // Object.keys() was allocating on every node previously.
- const children: any[] = [];
-
- for (const key in node) {
- // Skip non-child properties without allocating an array
- if (NON_CHILD_KEYS.has(key)) continue;
-
- const val = node[key];
- if (!val || typeof val !== 'object') continue;
-
- if (Array.isArray(val)) {
- for (let i = 0; i < val.length; i++) {
- const child = val[i];
- if (child && typeof child === 'object' && child.type) {
- children.push(child);
- }
- }
- } else if (val.type) {
- children.push(val);
- }
- }
-
- // Push in reverse so the first child is popped (processed) first
- for (let i = children.length - 1; i >= 0; i--) {
- stack.push(children[i]);
- }
- }
-}
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/component-analyzer.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/component-analyzer.test.ts
deleted file mode 100644
index d4a2627..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/component-analyzer.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { describe, it, expect, beforeEach } from 'vitest';
-import { analyzeComponent, isComponent, usesOnPush, isStandalone, resetComponentCacheStats, getComponentCacheStats } from '../../src/analyzers/component-analyzer.js';
-import { parseTs } from '../../src/parsers/ts.js';
-
-describe('component-analyzer', () => {
- beforeEach(() => {
- resetComponentCacheStats();
- });
-
- it('extracts literal scalar metadata from @Component', () => {
- const source = `
- @Component({
- selector: 'app-test',
- standalone: true,
- changeDetection: ChangeDetectionStrategy.OnPush,
- templateUrl: './xyz.html',
- template: '',
- })
- class TestComp {}
- `;
- const result = parseTs(source, 'test.ts');
- const cls = result.program.body[0] as any;
- const metadata = analyzeComponent(cls);
-
- expect(metadata).not.toBeNull();
- if (metadata) { // Narrow checks
- expect(metadata.selector.kind).toBe('literal');
- expect((metadata.selector as any).value).toBe('app-test');
-
- expect(metadata.standalone.kind).toBe('literal');
- expect((metadata.standalone as any).value).toBe(true);
-
- expect(metadata.changeDetection.kind).toBe('literal');
- expect((metadata.changeDetection as any).value).toBe(1); // OnPush
-
- expect(metadata.templateUrl.kind).toBe('literal');
- expect((metadata.templateUrl as any).value).toBe('./xyz.html');
-
- expect(metadata.template.kind).toBe('literal');
- expect((metadata.template as any).value).toBe('');
- }
- });
-
- it('caches successive calls on the same class declaration', () => {
- const source = `
- @Component({ selector: 'test' }) class TestComp {}
- `;
- const result = parseTs(source, 'test.ts');
- const cls = result.program.body[0] as any;
-
- analyzeComponent(cls);
- analyzeComponent(cls);
- analyzeComponent(cls);
-
- const stats = getComponentCacheStats();
- expect(stats.hits).toBe(2);
- expect(stats.misses).toBe(1);
- });
-
- it('returns null for empty classes', () => {
- const source = `class TestComp {}`;
- const result = parseTs(source, 'test.ts');
- const cls = result.program.body[0] as any;
-
- expect(analyzeComponent(cls)).toBeNull();
- expect(isComponent(cls)).toBe(false);
- expect(usesOnPush(cls)).toBe(false);
- expect(isStandalone(cls)).toBe(false);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/template-analyzer.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/template-analyzer.test.ts
deleted file mode 100644
index 3dd74fc..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/analyzers/template-analyzer.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { analyzeTemplate } from '../../src/analyzers/template-analyzer.js';
-import { parseHtml } from '../../src/parsers/html.js';
-
-describe('template-analyzer', () => {
- it('analyzes attributes from HTML elements', () => {
- const html = '';
- const parsed = parseHtml(html);
- const analyzed = analyzeTemplate(parsed);
-
- expect(analyzed.attributes.length).toBeGreaterThan(0);
- // Note: Id and Class may either be extracted or ignored depending on internal angular-html-parser representation,
- // but 'id' and 'class' should emit attributes at the very least.
- const ids = analyzed.attributes.filter(a => a.name === 'id');
- expect(ids.length).toBe(1);
- expect(ids[0].value).toBe('test');
- });
-
- it('analyzes structural directives', () => {
- const html = '';
- const parsed = parseHtml(html);
- const analyzed = analyzeTemplate(parsed);
-
- expect(analyzed.expressions.length).toBeGreaterThan(0);
- // "show" wrapped expression will be available
- const expr = analyzed.expressions[0];
- expect(expr.expression).toBeDefined();
- // Since it's parsed via oxc into ts AST, it will have type Identifier
- expect(expr.expression.type).toBe('Identifier');
- });
-
- it('analyzes property bindings', () => {
- const html = '';
- const parsed = parseHtml(html);
- const analyzed = analyzeTemplate(parsed);
-
- expect(analyzed.attributes.some(a => a.name === '[prop]')).toBe(true);
- expect(analyzed.expressions.length).toBeGreaterThan(0);
- expect(analyzed.expressions[0].expression.type).toBe('Identifier');
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/ast/matchers.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/ast/matchers.test.ts
deleted file mode 100644
index 63edd49..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/ast/matchers.test.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseTs } from '../../src/parsers/ts.js';
-import { hasDecorator, getDecoratorNameUnsafe, isInputSignal } from '../../src/ast/matchers.js';
-import { walkProgram } from '../../src/visitor.js';
-
-describe('ast/matchers', () => {
- it('hasDecorator and getDecoratorNameUnsafe handle standard decorators', () => {
- const source = `
- @Component({ selector: 'test' }) class TestComp {}
- @Injectable() class TestService {}
- `;
- const result = parseTs(source, 'test.ts');
- let componentsFound = 0;
-
- walkProgram(result.program, node => {
- if (node.type === 'ClassDeclaration') {
- if (hasDecorator(node as any, 'Component')) {
- componentsFound++;
- const name = getDecoratorNameUnsafe((node as any).decorators[0] as any);
- expect(name).toBe('Component');
- }
- if (hasDecorator(node as any, 'Injectable')) {
- const name = getDecoratorNameUnsafe((node as any).decorators[0] as any);
- expect(name).toBe('Injectable');
- }
- }
- });
- expect(componentsFound).toBe(1);
- });
-
- it('isInputSignal handles input() and input.required()', () => {
- const source = `
- const a = input();
- const b = input.required();
- const c = someOtherCall();
- `;
- const result = parseTs(source, 'test.ts');
- let inputs = 0;
- let requiredInputs = 0;
- let missing = 0;
-
- walkProgram(result.program, node => {
- if (node.type === 'CallExpression') {
- if (isInputSignal(node as any)) {
- if ((node as any).callee.type === 'Identifier') {
- inputs++;
- } else {
- requiredInputs++;
- }
- } else {
- missing++;
- }
- }
- });
-
- expect(inputs).toBe(1);
- expect(requiredInputs).toBe(1);
- expect(missing).toBe(1); // someOtherCall
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/node-streams.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/node-streams.test.ts
deleted file mode 100644
index 7ac5697..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/node-streams.test.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseTs } from '../src/parsers/ts.js';
-import { toAngularClassStream, toAnyAngularClassStream, toDecoratedPropertyStream } from '../src/node-streams.js';
-import { walkProgram } from '../src/visitor.js';
-
-describe('node-streams', () => {
- it('toAngularClassStream identifies @Component', () => {
- const source = `
- @Component({ selector: 'test' }) class TestComp {}
- `;
- const result = parseTs(source, 'test.ts');
- let matched = false;
- walkProgram(result.program, node => {
- if (node.type === 'ClassDeclaration') {
- const stream = toAngularClassStream(node as any);
- if (stream) {
- matched = true;
- expect(stream.metadata).toBeDefined();
- }
- }
- });
- expect(matched).toBe(true);
- });
-
- it('toAnyAngularClassStream identifies @Injectable', () => {
- const source = `
- @Injectable() class TestService {}
- `;
- const result = parseTs(source, 'test.ts');
- let matched = false;
- walkProgram(result.program, node => {
- if (node.type === 'ClassDeclaration') {
- const stream = toAnyAngularClassStream(node as any);
- if (stream) {
- matched = true;
- expect(stream.decoratorName).toBe('Injectable');
- expect(stream.className).toBe('TestService');
- }
- }
- });
- expect(matched).toBe(true);
- });
-
- it('toDecoratedPropertyStream identifies @Input()', () => {
- const source = `
- class TestComp {
- @Input() myProp: string;
- }
- `;
- const result = parseTs(source, 'test.ts');
- let matched = false;
- walkProgram(result.program, node => {
- if (node.type === 'PropertyDefinition') {
- const stream = toDecoratedPropertyStream(node as any);
- if (stream) {
- matched = true;
- expect(stream.decorators.length).toBe(1);
- }
- }
- });
- expect(matched).toBe(true);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/css.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/css.test.ts
deleted file mode 100644
index 4b6e22b..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/css.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseCss } from '../../src/parsers/css.js';
-
-describe('parseCss', () => {
- it('returns ok for valid css', () => {
- const result = parseCss('.class { color: red; }', 'test.css');
- expect(result.ok).toBe(true);
- if (result.ok) {
- expect(result.code).toBeDefined();
- }
- });
-
- it('returns error for invalid css', () => {
- const result = parseCss('.class { color: red;;!@# ', 'test.css');
- expect(result.ok).toBe(false);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/html.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/html.test.ts
deleted file mode 100644
index 2894126..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/html.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseHtml } from '../../src/parsers/html.js';
-
-describe('parseHtml', () => {
- it('returns parsed root nodes and empty errors for valid html', () => {
- const result = parseHtml('Hello
', 0);
- expect(result.errors.length).toBe(0);
- expect(result.rootNodes.length).toBeGreaterThan(0);
- expect(result.templateStartOffset).toBe(0);
- });
-
- it('handles templateStartOffset correctly', () => {
- const result = parseHtml('Hi', 10);
- expect(result.templateStartOffset).toBe(10);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/template-extractor.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/template-extractor.test.ts
deleted file mode 100644
index 7588271..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/template-extractor.test.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { extractTemplateFromProgram } from '../../src/parsers/template-extractor.js';
-import { parseTs } from '../../src/parsers/ts.js';
-
-describe('extractTemplateFromProgram', () => {
- it('extracts StringLiteral templates', () => {
- const source = `
- import { Component } from '@angular/core';
- @Component({
- template: 'Hello
'
- })
- export class TestComponent {}
- `;
- const result = parseTs(source, 'test.ts');
- const template = extractTemplateFromProgram(result.program);
- expect(template.content).toBe('Hello
');
- expect(template.startOffset).toBeGreaterThan(0);
- });
-
- it('extracts TemplateLiteral templates', () => {
- const source = `
- import { Component } from '@angular/core';
- @Component({
- template: \`Hi {{name}}\`
- })
- export class TestComponent {}
- `;
- const result = parseTs(source, 'test.ts');
- const template = extractTemplateFromProgram(result.program);
- expect(template.content).toBe('Hi {{name}}');
- expect(template.startOffset).toBeGreaterThan(0);
- });
-
- it('returns empty when no template is present', () => {
- const source = `
- import { Component } from '@angular/core';
- @Component({
- templateUrl: './xyz.html'
- })
- export class TestComponent {}
- `;
- const result = parseTs(source, 'test.ts');
- const template = extractTemplateFromProgram(result.program);
- expect(template.content).toBe('');
- expect(template.startOffset).toBe(0);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/ts.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/ts.test.ts
deleted file mode 100644
index 3114bf5..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/parsers/ts.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseTs } from '../../src/parsers/ts.js';
-
-describe('parseTs', () => {
- it('parses valid typescript code', () => {
- const result = parseTs('const x: number = 10;', 'test.ts');
- expect(result.errors.length).toBe(0);
- expect(result.program).toBeDefined();
- expect(result.program.type).toBe('Program');
- });
-
- it('captures parsing errors for invalid ts', () => {
- const result = parseTs('const class class = ;', 'test.ts');
- expect(result.errors.length).toBeGreaterThan(0);
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tests/visitor.test.ts b/.claude/worktrees/nice-taussig/packages/ast/tests/visitor.test.ts
deleted file mode 100644
index c6a89dd..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tests/visitor.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import { parseTs } from '../src/parsers/ts.js';
-import { walkProgram } from '../src/visitor.js';
-
-describe('visitor', () => {
- it('visits every node in the AST', () => {
- const source = 'const x = 1 + 2;';
- const result = parseTs(source, 'test.ts');
-
- const nodeTypes: string[] = [];
- walkProgram(result.program, (node) => {
- if (node.type) {
- nodeTypes.push(node.type);
- }
- });
-
- expect(nodeTypes).toContain('Program');
- expect(nodeTypes).toContain('VariableDeclaration');
- expect(nodeTypes).toContain('VariableDeclarator');
- expect(nodeTypes).toContain('BinaryExpression');
- expect(nodeTypes).toContain('Literal');
- });
-
- it('skips children when visitor returns false', () => {
- const source = 'const x = 1 + 2;';
- const result = parseTs(source, 'test.ts');
-
- let binaryExpressionSeen = false;
- let numericLiteralSeen = false;
-
- walkProgram(result.program, (node) => {
- if (node.type === 'BinaryExpression') {
- binaryExpressionSeen = true;
- return false; // Skip traversing into the 1 + 2
- }
- if (node.type === 'Literal') {
- numericLiteralSeen = true;
- }
- });
-
- expect(binaryExpressionSeen).toBe(true);
- expect(numericLiteralSeen).toBe(false); // Should not have visited the children
- });
-});
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tsconfig.json b/.claude/worktrees/nice-taussig/packages/ast/tsconfig.json
deleted file mode 100644
index c81871f..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tsconfig.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist",
- "rootDir": "./src",
- "composite": false,
- "incremental": false,
- "moduleResolution": "bundler",
- "module": "esnext",
- "types": ["vitest/globals", "node"]
- },
- "include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
-}
diff --git a/.claude/worktrees/nice-taussig/packages/ast/tsup.config.ts b/.claude/worktrees/nice-taussig/packages/ast/tsup.config.ts
deleted file mode 100644
index 3e0be3c..0000000
--- a/.claude/worktrees/nice-taussig/packages/ast/tsup.config.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createConfig } from '../../tsup.config.js';
-
-export default createConfig({
- entry: ['src/index.ts'],
-});
diff --git a/.claude/worktrees/nice-taussig/packages/cache/package.json b/.claude/worktrees/nice-taussig/packages/cache/package.json
deleted file mode 100644
index bc02567..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/package.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "name": "@ngcompass/cache",
- "version": "0.0.1",
- "description": "Caching layer for ngcompass",
- "sideEffects": false,
- "main": "./dist/index.cjs",
- "module": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "type": "module",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
- }
- },
- "files": [
- "dist"
- ],
- "scripts": {
- "build": "tsup",
- "build:prod": "NODE_ENV=production tsup --minify",
- "dev": "tsup --watch",
- "test": "vitest run ",
- "test:watch": "vitest",
- "lint": "eslint src --ext .ts",
- "typecheck": "tsc --noEmit",
- "clean": "rimraf dist .turbo"
- },
- "dependencies": {
- "@ngcompass/common": "workspace:*",
- "cacache": "^20.0.3",
- "lru-cache": "^11.2.5",
- "write-file-atomic": "^7.0.0",
- "xxhash-wasm": "^1.1.0"
- },
- "peerDependencies": {
- "typescript": "catalog:"
- },
- "devDependencies": {
- "@types/cacache": "^20.0.0",
- "@types/node": "^25.1.0",
- "@types/write-file-atomic": "^4.0.3"
- },
- "keywords": [
- "angular",
- "cache",
- "ngcompass"
- ],
- "author": "ngcompass",
- "license": "MIT"
-}
\ No newline at end of file
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/constants.ts b/.claude/worktrees/nice-taussig/packages/cache/src/constants.ts
deleted file mode 100644
index a6a2aec..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/constants.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const CACHE_VERSION = '1.0.0';
-export const SCHEMA_VERSION = 1;
-
-/**
- * Prefix for cache keys to ensure versioning.
- * Format: v{CACHE_VERSION}-s{SCHEMA_VERSION}
- */
-export const CACHE_KEY_PREFIX = `v${CACHE_VERSION}-s${SCHEMA_VERSION}`;
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/context.ts b/.claude/worktrees/nice-taussig/packages/cache/src/context.ts
deleted file mode 100644
index c3d2687..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/context.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-import path from 'path';
-import { createMemoryDriver } from './drivers/memory.js';
-import { createDiskDriver } from './drivers/disk.js';
-import { createAtomicDriver } from './drivers/atomic.js';
-import { createJsonFileDriver } from './drivers/json-file.js';
-import { createSourceCache, SourceEntry } from './services/source-cache.js';
-import { createAstCache, AstEntry } from './services/ast-cache.js';
-import { createResultCache } from './services/result-cache.js';
-import { createConfigCache } from './services/config-cache.js';
-import { createMetaCache, FileMeta } from './services/meta-cache.js';
-import { createPlanCache } from './services/plan-cache.js';
-import { createFileCache, FileCacheEntry } from './services/file-cache.js';
-import { CacheConfig } from './drivers/types.js';
-import { computeCompositeHash } from './services/hashing.js';
-import { CACHE_VERSION } from './constants.js';
-import { CacheContext } from './types.js';
-
-/**
- * Initializes the caching system.
- */
-export const createCacheContext = (config: CacheConfig = {}): CacheContext => {
- const cwd = process.cwd();
- const defaultBaseDir = path.resolve(cwd, 'node_modules', '.cache', 'ngcompass');
-
- // 1. Drivers
- // Sources: Memory Only
- const sourceDriver = createMemoryDriver({
- maxItems: config.memory?.maxItems
- });
-
- // ASTs: Tiered (Memory + Disk)
- const astL1 = createMemoryDriver({
- maxItems: 200 // Keep top 200 ASTs in memory
- });
-
- const astL2 = createDiskDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'ast'),
- ttl: config.disk?.ttl
- });
-
- const resultDriver = createAtomicDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'results')
- });
-
- const configDriver = createAtomicDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'config')
- });
-
- const metaDriver = createJsonFileDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'meta')
- });
-
- // Use regular disk driver for plan cache
- const planDriver = createDiskDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'plans'),
- });
-
- const fileDriver = createDiskDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'files'),
- });
-
- // 2. Services
- const sources = createSourceCache(sourceDriver);
- const asts = createAstCache(astL1, astL2);
- const results = createResultCache(resultDriver);
- const configs = createConfigCache(configDriver);
- const metas = createMetaCache(metaDriver);
- const plans = createPlanCache(planDriver);
- const files = createFileCache(fileDriver);
-
- // Analysis results cache (disk driver, keyed by global hash)
- const analysisDriver = createDiskDriver({
- path: path.join(config.disk?.path ?? defaultBaseDir, 'analysis')
- });
- const analysis = createResultCache(analysisDriver);
-
- // Shared helper: clear every driver — single source of truth for "clear all".
- const clearAllDrivers = async (): Promise => {
- sourceDriver.clear();
- astL1.clear();
- await astL2.clear();
- await resultDriver.clear();
- await configDriver.clear();
- await metaDriver.clear();
- await planDriver.clear();
- await fileDriver.clear();
- await analysisDriver.clear();
- };
-
- return {
- sources,
- asts,
- results,
- configs,
- metas,
- plans,
- files,
- analysis,
- computeHash: computeCompositeHash,
- prune: async () => {
- await astL2.prune();
- },
- clear: clearAllDrivers,
- clearType: async (type) => {
- switch (type) {
- case 'ast':
- astL1.clear();
- await astL2.clear();
- break;
- case 'config':
- await configDriver.clear();
- break;
- case 'results':
- await resultDriver.clear();
- await analysisDriver.clear();
- break;
- case 'all':
- await clearAllDrivers();
- break;
- }
- },
- flush: async () => {
- await metaDriver.flush();
- },
- getCachePath: () => config.disk?.path ?? defaultBaseDir,
- getInfo: async () => {
- const astL1Stats = astL1.getStats();
- const [
- astL2Stats,
- configStats,
- resultStats,
- metaStats,
- planStats,
- fileStats,
- analysisStats,
- ] = await Promise.all([
- astL2.getStats(),
- configDriver.getStats(),
- resultDriver.getStats(),
- metaDriver.getStats(),
- planDriver.getStats(),
- fileDriver.getStats(),
- analysisDriver.getStats(),
- ]);
-
- const totalSize =
- astL1Stats.size +
- astL2Stats.size +
- configStats.size +
- resultStats.size +
- metaStats.size +
- planStats.size +
- fileStats.size +
- analysisStats.size;
-
- return {
- ast: {
- l1: { entries: astL1Stats.entries, maxEntries: 200, size: astL1Stats.size },
- l2: { entries: astL2Stats.entries, size: astL2Stats.size }
- },
- config: { entries: configStats.entries, size: configStats.size },
- results: { entries: resultStats.entries, size: resultStats.size },
- totalSize,
- location: config.disk?.path ?? defaultBaseDir,
- version: CACHE_VERSION
- };
- }
- };
-};
-
-let globalCache: CacheContext | null = null;
-
-/**
- * Returns a globally shared cache context.
- * Useful for CLI and long-running processes to avoid redundant initializations.
- */
-export const getCacheContext = (config?: CacheConfig): CacheContext => {
- if (!globalCache) {
- globalCache = createCacheContext(config);
- }
- return globalCache;
-};
-
-/**
- * Resets the global cache singleton to null.
- * Call this in tests between runs to get a clean slate without restarting the process.
- */
-export const resetGlobalCache = (): void => {
- globalCache = null;
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/atomic.ts b/.claude/worktrees/nice-taussig/packages/cache/src/drivers/atomic.ts
deleted file mode 100644
index 18130f8..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/atomic.ts
+++ /dev/null
@@ -1,211 +0,0 @@
-import writeFileAtomic from 'write-file-atomic';
-import fs from 'node:fs/promises';
-import path from 'node:path';
-import { AsyncDriver, DiskDriverConfig } from './types.js';
-import { getDirectoryStats } from '../utils/fs.js';
-
-/**
- * Creates a storage driver that writes each key as a separate file.
- * Uses write-file-atomic for safe writes.
- *
- * Performance Optimization (P0):
- * Maintains an in-memory "Directory Catalog" to avoid thousands of fs.access calls.
- * - initialized lazily via fs.readdir
- * - maintained in-sync on set/delete/clear
- *
- * Concurrency Safety:
- * - coalesces concurrent catalog builds
- * - guards catalog writes with a monotonic generation to avoid clear()/readdir() races
- * - ensures set/delete update catalog correctly even while initialization is in-flight
- */
-export const createAtomicDriver = (config: DiskDriverConfig): AsyncDriver => {
- const cacheDir = config.path;
-
- let catalog: Set | null = null;
- let catalogPromise: Promise | null = null;
- let catalogGeneration = 0;
-
- const getFilePath = (key: string): string => path.join(cacheDir, `${key}.json`);
-
- const ensureDir = async (): Promise => {
- try {
- await fs.mkdir(cacheDir, { recursive: true });
- } catch {
- // Intentionally ignored (directory may already exist or be created concurrently).
- }
- };
-
- const listJsonKeys = (files: string[]): string[] =>
- files.filter(isJsonFile).map(stripJsonExtension);
-
- const isJsonFile = (fileName: string): boolean => fileName.endsWith('.json');
-
- const stripJsonExtension = (fileName: string): string => fileName.slice(0, -5);
-
- const safeReadDir = async (dir: string): Promise => {
- try {
- return await fs.readdir(dir);
- } catch {
- // Intentionally returns empty list if directory does not exist or cannot be read.
- return [];
- }
- };
-
- const tryReadJsonFile = async (filePath: string): Promise => {
- try {
- return await fs.readFile(filePath, 'utf-8');
- } catch {
- // Intentionally returns undefined when file cannot be read (missing, deleted, etc.).
- return undefined;
- }
- };
-
- const tryUnlink = async (filePath: string): Promise => {
- try {
- await fs.unlink(filePath);
- } catch {
- // Intentionally ignored (file may not exist).
- }
- };
-
- const tryRemoveDir = async (dir: string): Promise => {
- try {
- await fs.rm(dir, { recursive: true, force: true });
- } catch {
- // Intentionally ignored (directory may not exist).
- }
- };
-
- const markMissingInCatalog = (key: string): void => {
- if (catalog) catalog.delete(key);
- };
-
- const addToCatalog = (key: string): void => {
- if (catalog) catalog.add(key);
- };
-
- const removeFromCatalog = (key: string): void => {
- if (catalog) catalog.delete(key);
- };
-
- const resetCatalogEmpty = (): void => {
- catalog = new Set();
- };
-
- const awaitCatalogInitIfInFlight = async (): Promise => {
- if (catalogPromise) {
- await catalogPromise;
- }
- };
-
- /**
- * Initializes the catalog if not already loaded.
- * Coalesces multiple concurrent calls into a single readdir.
- * Uses generation guard to avoid clear()/readdir() races.
- */
- const ensureCatalog = async (): Promise => {
- if (catalog) return;
-
- if (!catalogPromise) {
- const localGeneration = catalogGeneration;
-
- catalogPromise = (async () => {
- try {
- await ensureDir();
- const files = await safeReadDir(cacheDir);
- const keys = listJsonKeys(files);
-
- // Only publish if generation hasn't changed (e.g., clear() happened).
- if (catalogGeneration === localGeneration) {
- catalog = new Set(keys);
- }
- } finally {
- // Always release the promise, even if errors occurred.
- catalogPromise = null;
-
- // Ensure catalog is never left null after initialization attempt.
- if (!catalog) {
- catalog = new Set();
- }
- }
- })();
- }
-
- await catalogPromise;
- };
-
- const publishCatalogAfterClear = (): void => {
- catalogGeneration += 1;
- resetCatalogEmpty();
- };
-
- const syncCatalogAfterSet = async (key: string): Promise => {
- await awaitCatalogInitIfInFlight();
- addToCatalog(key);
- };
-
- const syncCatalogAfterDelete = async (key: string): Promise => {
- await awaitCatalogInitIfInFlight();
- removeFromCatalog(key);
- };
-
- return {
- get: async (key: string): Promise => {
- // Intentionally avoids forcing catalog initialization for single reads.
- if (catalog && !catalog.has(key)) {
- return undefined;
- }
-
- const filePath = getFilePath(key);
- const data = await tryReadJsonFile(filePath);
-
- if (data === undefined) {
- markMissingInCatalog(key);
- return undefined;
- }
-
- try {
- return JSON.parse(data) as T;
- } catch {
- // Intentionally treats corrupt entries as cache misses and removes from catalog.
- markMissingInCatalog(key);
- return undefined;
- }
- },
-
- set: async (key: string, value: T): Promise => {
- await ensureDir();
- const filePath = getFilePath(key);
-
- await writeFileAtomic(filePath, JSON.stringify(value), { encoding: 'utf-8' });
-
- if (catalog || catalogPromise) {
- await syncCatalogAfterSet(key);
- }
- },
-
- has: async (key: string): Promise => {
- await ensureCatalog();
- return catalog!.has(key);
- },
-
- delete: async (key: string): Promise => {
- await tryUnlink(getFilePath(key));
-
- if (catalog || catalogPromise) {
- await syncCatalogAfterDelete(key);
- }
- },
-
- clear: async (): Promise => {
- await tryRemoveDir(cacheDir);
- await ensureDir();
-
- publishCatalogAfterClear();
- },
-
- getStats: async () => {
- return getDirectoryStats(cacheDir);
- },
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/disk.ts b/.claude/worktrees/nice-taussig/packages/cache/src/drivers/disk.ts
deleted file mode 100644
index 9543b0a..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/disk.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import cacache from 'cacache';
-import v8 from 'node:v8';
-import { AsyncDriver, DiskDriverConfig } from './types.js';
-import { getDirectoryStats } from '../utils/fs.js';
-
-/**
- * Creates an asynchronous disk storage driver backed by cacache.
- * Uses V8 serialization for high-performance binary storage.
- * Includes resilience against deserialization errors (Try-Catch-Delete).
- */
-export const createDiskDriver = (
- config: DiskDriverConfig
-): AsyncDriver & { prune: () => Promise } => {
- const cachePath = config.path;
-
- return {
- get: async (key: string): Promise => {
- try {
- const tReadStart = performance.now();
- const result = await cacache.get(cachePath, key);
- const tReadEnd = performance.now();
-
- const tDeserStart = performance.now();
- const deserialized = v8.deserialize(result.data) as T;
- const tDeserEnd = performance.now();
-
- // Optional: debug logging for cache driver timing
- if (process.env.DEBUG_CACHE) {
- console.log(`[disk-driver] read: ${(tReadEnd - tReadStart).toFixed(2)}ms, deser: ${(tDeserEnd - tDeserStart).toFixed(2)}ms, size: ${(result.data.length / 1024).toFixed(1)}KB`);
- }
-
- return deserialized;
- } catch (err: unknown) {
- const code = typeof err === 'object' && err !== null && 'code' in err ? (err as { code: unknown }).code : undefined;
-
- if (code === 'ENOENT') {
- return undefined; // Not found
- }
-
- // Defensive: If deserialization fails (corruption, version mismatch),
- // delete the entry to self-heal.
- try {
- await cacache.rm.entry(cachePath, key);
- } catch {
- // Ignore delete errors
- }
- return undefined;
- }
- },
-
- set: async (key: string, value: T): Promise => {
- // Serialize to Buffer using V8
- const buffer = v8.serialize(value);
- await cacache.put(cachePath, key, buffer);
- },
-
- has: async (key: string): Promise => {
- try {
- const info = await cacache.get.info(cachePath, key);
- return !!info;
- } catch {
- return false;
- }
- },
-
- delete: async (key: string): Promise => {
- await cacache.rm.entry(cachePath, key);
- },
-
- clear: async (): Promise => {
- await cacache.rm.all(cachePath);
- },
-
- prune: async (): Promise => {
- // cleans up old/unused entries
- await cacache.verify(cachePath);
- },
-
- getStats: async () => {
- // cacache stores content in content-addressable blobs.
- // Counting entries is hard without listing keys.
- // Ideally we'd use cacache.ls() but that's slow.
- // For "size", getting the folder size is accurate enough for user consumption.
- return getDirectoryStats(cachePath);
- }
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/json-file.ts b/.claude/worktrees/nice-taussig/packages/cache/src/drivers/json-file.ts
deleted file mode 100644
index 472806f..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/json-file.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import fs from 'node:fs/promises';
-import path from 'node:path';
-import writeFileAtomic from 'write-file-atomic';
-import { debug } from '@ngcompass/common';
-import { AsyncDriver, DiskDriverConfig, DriverStats } from './types.js';
-
-export const createJsonFileDriver = (
- config: DiskDriverConfig
-): AsyncDriver & { flush: () => Promise } => {
- const filePath = path.join(config.path, 'meta.json');
- const debounceMs = 2000;
-
- let cache: Map | null = null;
- let loadPromise: Promise | null = null;
- let saveTimeout: NodeJS.Timeout | null = null;
- let savePromise: Promise | null = null;
-
- const load = async (): Promise => {
- if (cache) return;
- if (loadPromise) return loadPromise;
-
- loadPromise = (async () => {
- try {
- await fs.mkdir(config.path, { recursive: true });
- const content = await fs.readFile(filePath, 'utf-8');
- const data = JSON.parse(content);
- if (data && typeof data === 'object') {
- cache = new Map(Object.entries(data));
- } else {
- cache = new Map();
- }
- } catch {
- cache = new Map();
- } finally {
- loadPromise = null;
- }
- })();
-
- return loadPromise;
- };
-
- const flush = async (): Promise => {
- if (saveTimeout) {
- clearTimeout(saveTimeout);
- saveTimeout = null;
- }
-
- if (!cache) return;
- if (savePromise) await savePromise;
-
- savePromise = (async () => {
- try {
- await fs.mkdir(config.path, { recursive: true });
- const data = Object.fromEntries(cache);
- await writeFileAtomic(filePath, JSON.stringify(data));
- } catch (err) {
- const msg = err instanceof Error ? err.message : String(err);
- debug('cache', `Failed to flush metadata cache to ${filePath}: ${msg}`);
- } finally {
- savePromise = null;
- }
- })();
-
- await savePromise;
- };
-
- const scheduleSave = () => {
- if (saveTimeout) return;
- saveTimeout = setTimeout(() => {
- saveTimeout = null;
- void flush();
- }, debounceMs);
- saveTimeout.unref();
- };
-
- return {
- get: async (key: string): Promise => {
- await load();
- return cache!.get(key);
- },
- set: async (key: string, value: T): Promise => {
- await load();
- cache!.set(key, value);
- scheduleSave();
- },
- has: async (key: string): Promise => {
- await load();
- return cache!.has(key);
- },
- delete: async (key: string): Promise => {
- await load();
- if (cache!.delete(key)) {
- scheduleSave();
- }
- },
- clear: async (): Promise => {
- if (saveTimeout) {
- clearTimeout(saveTimeout);
- saveTimeout = null;
- }
- cache = new Map();
- try {
- await fs.unlink(filePath);
- } catch {
- // Ignore
- }
- },
- getStats: async (): Promise => {
- await load();
- return {
- entries: cache!.size,
- size: 0
- };
- },
- flush
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/memory.ts b/.claude/worktrees/nice-taussig/packages/cache/src/drivers/memory.ts
deleted file mode 100644
index 0abf901..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/memory.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { LRUCache } from 'lru-cache';
-import { MemoryDriverConfig, SyncDriver } from './types.js';
-
-/**
- * Creates a synchronous memory storage driver backed by LRU Cache.
- */
-export const createMemoryDriver = (
- config: MemoryDriverConfig = {}
-): SyncDriver => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const cache = new LRUCache({
- max: config.maxItems ?? 500,
- maxSize: config.maxSize ?? 50 * 1024 * 1024, // Default 50MB
- sizeCalculation: (value) => {
- // Rough estimate using JSON length. Fast enough for writes.
- try {
- return JSON.stringify(value).length;
- } catch {
- return 1000; // Fallback for circular/non-serializable
- }
- },
- ttl: config.ttl,
- });
-
- return {
- get: (key: string): T | undefined => cache.get(key) as T | undefined,
-
- set: (key: string, value: T): void => {
- cache.set(key, value);
- },
-
- has: (key: string): boolean => cache.has(key),
-
- delete: (key: string): void => {
- cache.delete(key);
- },
-
- clear: (): void => cache.clear(),
-
- getStats: () => ({
- entries: cache.size,
- size: cache.calculatedSize
- }),
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/types.ts b/.claude/worktrees/nice-taussig/packages/cache/src/drivers/types.ts
deleted file mode 100644
index 0153023..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/drivers/types.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Generic storage driver interface for asynchronous operations.
- * Used for Disk and Network IO.
- */
-export interface AsyncDriver {
- get(key: string): Promise;
- set(key: string, value: T): Promise;
- has(key: string): Promise;
- delete(key: string): Promise;
- clear(): Promise;
- getStats(): Promise;
-}
-
-/**
- * Generic storage driver interface for synchronous operations.
- * Used for Memory operations.
- */
-export interface SyncDriver {
- get(key: string): T | undefined;
- set(key: string, value: T): void;
- has(key: string): boolean;
- delete(key: string): void;
- clear(): void;
- getStats(): DriverStats;
-}
-
-export interface DriverStats {
- entries: number;
- size: number; // Bytes
-}
-
-/**
- * Configuration for the memory driver.
- */
-export interface MemoryDriverConfig {
- maxItems?: number;
- maxSize?: number; // Maximum size in bytes
- ttl?: number;
-}
-
-/**
- * Configuration for the disk driver.
- */
-export interface DiskDriverConfig {
- path: string;
- ttl?: number;
-}
-
-/**
- * Global cache configuration.
- */
-export interface CacheConfig {
- memory?: MemoryDriverConfig;
- disk?: Partial;
-}
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/env-fingerprint.ts b/.claude/worktrees/nice-taussig/packages/cache/src/env-fingerprint.ts
deleted file mode 100644
index 8b6c6bc..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/env-fingerprint.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * Environment Fingerprint
- *
- * Computes a deterministic, hermetic fingerprint representing all non-file
- * inputs that influence analysis correctness.
- *
- * Design: pure functions, explicit dependencies, no module-level mutable state.
- * The fingerprint is computed once at startup and stored in CacheContext —
- * callers receive it via explicit parameter, never via a global singleton.
- *
- * Fingerprint components (all declared, none implicit):
- * toolVersion — CACHE_VERSION constant
- * schemaVersion — SCHEMA_VERSION integer
- * nodeVersion — major.minor of process.versions.node
- * platform — process.platform
- * arch — process.arch
- * typescriptVersion — resolved from typescript/package.json
- * oxcParserVersion — resolved from oxc-parser/package.json
- * htmlParserVersion — resolved from angular-html-parser/package.json
- * cssParserVersion — resolved from lightningcss/package.json
- * ruleRegistryFp — SHA-256 of sorted registered rule names
- *
- * Any change to any component causes a cache miss on all persistent caches —
- * no manual cache clearing required.
- */
-
-import crypto from 'node:crypto';
-import { createRequire } from 'node:module';
-import { debug } from '@ngcompass/common';
-import { CACHE_VERSION, SCHEMA_VERSION } from './constants.js';
-
-// ─── Public types ─────────────────────────────────────────────────────────────
-
-/**
- * All environment inputs that influence cache correctness.
- * Exposed so callers can log or display individual fields (observability).
- */
-export interface EnvFingerprintComponents {
- readonly toolVersion: string;
- readonly schemaVersion: number;
- /** Node major.minor — patch omitted (ABI-stable within minor series) */
- readonly nodeVersion: string;
- readonly platform: string;
- readonly arch: string;
- readonly typescriptVersion: string;
- readonly oxcParserVersion: string;
- readonly htmlParserVersion: string;
- readonly cssParserVersion: string;
- /** SHA-256 of sorted registered rule names */
- readonly ruleRegistryFp: string;
-}
-
-/**
- * Computed fingerprint + its source components.
- * Stored in CacheContext and passed explicitly wherever it is needed.
- */
-export interface EnvFingerprint {
- /** SHA-256 hex digest — used as cache key prefix */
- readonly value: string;
- /** Raw inputs — used for diagnostics / cache info output */
- readonly components: EnvFingerprintComponents;
-}
-
-// ─── Internal pure helpers ────────────────────────────────────────────────────
-
-/**
- * Reads `version` from a package.json without throwing.
- * Returns 'unknown' when the package is absent or unresolvable.
- */
-const readPackageVersion = (packageName: string): string => {
- try {
- const req = createRequire(import.meta.url);
- const pkg = req(`${packageName}/package.json`) as { version?: string };
- return pkg.version ?? 'unknown';
- } catch {
- return 'unknown';
- }
-};
-
-/**
- * Extracts major.minor from a semver string.
- * "20.11.1" → "20.11"
- *
- * Patch is excluded because Node patch releases are ABI-stable — V8
- * serialization format does not change within a major.minor series.
- */
-const toMajorMinor = (version: string): string => {
- const [major = '0', minor = '0'] = version.replace(/^v/, '').split('.');
- return `${major}.${minor}`;
-};
-
-/**
- * Stable JSON: recursively sorts object keys so identical objects always
- * produce the same string regardless of property insertion order.
- */
-const stableJson = (value: unknown): string => {
- const normalize = (v: unknown): unknown => {
- if (Array.isArray(v)) return v.map(normalize);
- if (v !== null && typeof v === 'object') {
- const sorted: Record = {};
- for (const k of Object.keys(v).sort()) {
- sorted[k] = normalize((v as Record)[k]);
- }
- return sorted;
- }
- return v;
- };
- return JSON.stringify(normalize(value));
-};
-
-/** SHA-256 hex digest of a UTF-8 string. */
-const sha256 = (input: string): string =>
- crypto.createHash('sha256').update(input, 'utf8').digest('hex');
-
-// ─── Rule registry fingerprint ────────────────────────────────────────────────
-
-/**
- * Computes a deterministic fingerprint of the rule registry.
- *
- * Captures: rule additions, removals, and renames (name-set changes).
- * Does NOT capture inline logic changes to an existing rule without a rename.
- * For that guarantee, bump SCHEMA_VERSION when shipping rule logic changes.
- *
- * Imported lazily to avoid circular deps at module-load time and to ensure
- * all registerNewEngineRule side-effect imports have run first.
- */
-const computeRuleRegistryFp = async (): Promise => {
- try {
- // Dynamic import to avoid circular dependency (cache ← engine ← rules).
- // At runtime the CLI has already loaded @ngcompass/rules before this runs.
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const moduleName = '@ngcompass/rules';
- const mod = await import(/* webpackIgnore: true */ moduleName) as {
- getGlobalRegistry: () => { getRuleNames: () => Iterable };
- };
- const { getGlobalRegistry } = mod;
- const ruleNames = [...getGlobalRegistry().getRuleNames()].sort();
-
- if (ruleNames.length === 0) return sha256('empty-registry');
-
- const fp = sha256(ruleNames.join('\n'));
- debug('env-fingerprint', `Rule registry fp: ${fp.slice(0, 8)}… (${ruleNames.length} rules)`);
- return fp;
- } catch (err) {
- debug('env-fingerprint', `Rule registry fp error: ${String(err)}`);
- return sha256('registry-error');
- }
-};
-
-// ─── Component assembly ───────────────────────────────────────────────────────
-
-const gatherComponents = async (): Promise => {
- const ruleRegistryFp = await computeRuleRegistryFp();
-
- return {
- toolVersion: CACHE_VERSION,
- schemaVersion: SCHEMA_VERSION,
- nodeVersion: toMajorMinor(process.versions.node),
- platform: process.platform,
- arch: process.arch,
- typescriptVersion: readPackageVersion('typescript'),
- oxcParserVersion: readPackageVersion('oxc-parser'),
- htmlParserVersion: readPackageVersion('angular-html-parser'),
- cssParserVersion: readPackageVersion('lightningcss'),
- ruleRegistryFp,
- };
-};
-
-// ─── Public API ───────────────────────────────────────────────────────────────
-
-/**
- * Computes the environment fingerprint.
- *
- * Call once at startup; store the result in CacheContext; pass it explicitly
- * to all cache key builders. Do not call in hot paths.
- *
- * The returned `value` is a SHA-256 hex digest that changes whenever any
- * environment input changes, automatically invalidating all keyed caches.
- */
-export const computeEnvFingerprint = async (): Promise => {
- const components = await gatherComponents();
- const value = sha256(stableJson(components as unknown as Record));
-
- debug('env-fingerprint', `Env fingerprint: ${value.slice(0, 16)}…`);
- debug('env-fingerprint', ` tool: ${components.toolVersion} (schema v${components.schemaVersion})`);
- debug('env-fingerprint', ` node: ${components.nodeVersion} ${components.platform}/${components.arch}`);
- debug('env-fingerprint', ` ts: ${components.typescriptVersion}`);
- debug('env-fingerprint', ` oxc: ${components.oxcParserVersion}`);
- debug('env-fingerprint', ` html: ${components.htmlParserVersion}`);
- debug('env-fingerprint', ` css: ${components.cssParserVersion}`);
- debug('env-fingerprint', ` rules: ${components.ruleRegistryFp.slice(0, 8)}…`);
-
- return { value, components };
-};
-
-/**
- * Builds an env-scoped cache key: `::`.
- *
- * The `::` separator cannot appear in SHA-256 hex digests, so prefixed keys
- * are always distinguishable from un-prefixed ones.
- *
- * Pure function — same inputs always produce the same output, no side effects.
- */
-export const buildEnvScopedKey = (fingerprint: EnvFingerprint, originalKey: string): string =>
- `${fingerprint.value}::${originalKey}`;
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/hashing.ts b/.claude/worktrees/nice-taussig/packages/cache/src/hashing.ts
deleted file mode 100644
index 88d888b..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/hashing.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Content Hashing — Core utility
- *
- * Deterministic hashing for cache invalidation.
- * Uses xxhash-wasm (fast) with SHA-256 fallback before initialization.
- * The full planner/hashing.ts (file-level helpers) will import computeHash from here.
- */
-
-import crypto from 'node:crypto';
-import xxhash from 'xxhash-wasm';
-
-let h64: ((input: string | Uint8Array) => string) | undefined;
-
-/**
- * Initializes the xxhash hasher (call once at startup).
- */
-export const initHasher = async (): Promise => {
- if (h64) return;
- const { h64: hasher } = await xxhash();
- h64 = (input: string | Uint8Array) =>
- hasher(input as any)
- .toString(16)
- .padStart(16, '0');
-};
-
-/**
- * Computes a hash using xxhash if initialized, SHA-256 otherwise.
- *
- * @param content - Data to hash
- * @returns Hex-encoded hash string
- */
-export const computeHash = (content: string | Uint8Array): string => {
- if (h64) return h64(content);
- const hasher = crypto.createHash('sha256');
- hasher.update(content);
- return hasher.digest('hex');
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/index.ts b/.claude/worktrees/nice-taussig/packages/cache/src/index.ts
deleted file mode 100644
index 70bc8f7..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * @ngcompass/cache
- *
- * Caching layer for the ngcompass analysis engine.
- * Provides disk, memory, and JSON-file backed drivers,
- * plus cache services for AST, results, config, and meta data.
- */
-
-// Core types
-export * from './types.js';
-export * from './constants.js';
-
-// Hashing primitives (also used by @ngcompass/planner)
-export { computeHash, initHasher } from './hashing.js';
-export { stableSerialize, SerializationError } from './utils/stable-serialize.js';
-
-// Cache key context
-export * from './key-context.js';
-
-// Drivers
-export type { CacheConfig } from './drivers/types.js';
-
-// Context (CacheContext factory)
-export * from './context.js';
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/key-context.ts b/.claude/worktrees/nice-taussig/packages/cache/src/key-context.ts
deleted file mode 100644
index 9e44c6d..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/key-context.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * Cache Key Context
- *
- * Encapsulates all version-related inputs that affect cache validity.
- * Must be included in EVERY cache key so that stale entries from a previous
- * version of the tool, parser, or rule set are never reused.
- *
- * This resolves the critical result-cache staleness bug where upgrading the
- * tool changes the globalHash (plan cache miss) but NOT the individual taskIds,
- * allowing old per-task results from the previous engine version to be served.
- *
- * Exact key formulas:
- *
- * Config key:
- * SHA-256(contentHash + "::" + profile + "::" + serializeCacheKeyContext(ctx))
- *
- * Global hash (plan + analysis cache key):
- * hash(sortedFilePairs + "||" + rulesHash + "||||" + serializeCacheKeyContext(ctx))
- *
- * Task ID (result cache key):
- * hash(ctx.toolVersion + "::" + ctx.ruleRegistryHash + "::" + ruleName + "::" + inputs...)
- */
-
-import { createRequire } from 'node:module';
-import { PACKAGE_VERSION, CACHE_VERSION } from '@ngcompass/common';
-import { computeHash } from './hashing.js';
-import { stableSerialize } from './utils/stable-serialize.js';
-
-// ============================================================
-// INTERFACE
-// ============================================================
-
-export interface CacheKeyContext {
- /**
- * The ngcompass package version (e.g. "1.2.3").
- * Bumped on every release → invalidates all caches on upgrade.
- */
- readonly toolVersion: string;
-
- /**
- * The cache schema version (CACHE_VERSION constant).
- * Bumped when the serialization format of cached values changes.
- */
- readonly schemaVersion: string;
-
- /**
- * A hash of all registered rule names + versions.
- * Changes when a rule is added, removed, or its plugin is updated.
- */
- readonly ruleRegistryHash: string;
-
- /**
- * The oxc-parser version, if detectable; "unknown" otherwise.
- * Changes when the parser is upgraded, ensuring AST-dependent caches
- * are invalidated even if tool version is unchanged.
- */
- readonly parserVersion: string;
-
- /**
- * The OS platform string (e.g. "linux", "darwin", "win32").
- * Prevents cross-platform cache sharing where path separators or
- * binary outputs differ.
- */
- readonly platform: string;
-}
-
-// ============================================================
-// FACTORY
-// ============================================================
-
-/**
- * Builds a CacheKeyContext from the current runtime environment and
- * the registered rule names.
- *
- * Call this ONCE per process, after all plugins are loaded and all rules
- * are registered. Pass the resulting context to buildExecutionPlan() and
- * resolveConfig().
- *
- * @param ruleNames - All registered rule names (from RuleRegistry)
- */
-export function buildCacheKeyContext(
- ruleNames: ReadonlyArray
-): CacheKeyContext {
- const sortedNames = [...ruleNames].sort();
- const ruleRegistryHash = computeHash(stableSerialize(sortedNames));
-
- return Object.freeze({
- toolVersion: PACKAGE_VERSION,
- schemaVersion: CACHE_VERSION,
- ruleRegistryHash,
- parserVersion: resolveParserVersion(),
- platform: process.platform,
- });
-}
-
-// ============================================================
-// SERIALIZATION
-// ============================================================
-
-/**
- * Serializes the context to a stable string for embedding in cache keys.
- *
- * Format: "toolVersion::schemaVersion::parserVersion::ruleRegistryHash::platform"
- *
- * All components are separated by "::" which cannot appear in the individual
- * values (version strings and hashes use only alphanumeric characters, dots,
- * and hyphens).
- */
-export function serializeCacheKeyContext(ctx: CacheKeyContext): string {
- return [
- ctx.toolVersion,
- ctx.schemaVersion,
- ctx.parserVersion,
- ctx.ruleRegistryHash,
- ctx.platform,
- ].join('::');
-}
-
-// ============================================================
-// INTERNAL HELPERS
-// ============================================================
-
-/**
- * Attempts to read the oxc-parser version from its package.json.
- * Returns "unknown" if the package is not resolvable.
- */
-function resolveParserVersion(): string {
- try {
- const require = createRequire(import.meta.url);
- const pkg = require('oxc-parser/package.json') as { version?: string };
- return String(pkg.version ?? 'unknown');
- } catch {
- return 'unknown';
- }
-}
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/ast-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/ast-cache.ts
deleted file mode 100644
index fe7fe0f..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/ast-cache.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { AsyncDriver, SyncDriver } from '../drivers/types.js';
-import { debug } from '@ngcompass/common';
-
-export interface AstEntry {
- filePath: string;
- ast: unknown;
-}
-
-export interface AstCache {
- get: (hash: string) => Promise;
- set: (hash: string, entry: AstEntry) => Promise;
- invalidate: (hash: string) => Promise;
-}
-
-/**
- * Creates a Two-Tiered AST Cache.
- * Strategy:
- * 1. Read: Memory (L1) -> Disk (L2).
- * 2. Write: Memory (L1) -> Disk (L2).
- * 3. Promotion: If found in L2, promote to L1.
- * Key: Content Hash.
- */
-export const createAstCache = (
- l1: SyncDriver,
- l2: AsyncDriver
-): AstCache => {
- return {
- get: async (hash: string): Promise => {
- // 1. Check L1
- const hot = l1.get(hash);
- if (hot) return hot;
-
- // 2. Check L2
- try {
- const cold = await l2.get(hash);
- if (cold) {
- // 3. Promote to L1
- l1.set(hash, cold);
- return cold;
- }
- } catch (error) {
- debug('cache', `Failed to read AST from L2 for ${hash}: ${error instanceof Error ? error.message : String(error)}`);
- return undefined;
- }
-
- return undefined;
- },
-
- set: async (hash: string, entry: AstEntry): Promise => {
- // Write to both
- l1.set(hash, entry);
- await l2.set(hash, entry);
- },
-
- invalidate: async (hash: string): Promise => {
- l1.delete(hash);
- await l2.delete(hash);
- }
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/config-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/config-cache.ts
deleted file mode 100644
index 2189bf2..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/config-cache.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { ConfigValidationResult } from '@ngcompass/common';
-import { AsyncDriver } from '../drivers/types.js';
-
-export interface ConfigCache {
- get: (hash: string) => Promise;
- set: (hash: string, report: ConfigValidationResult) => Promise;
-}
-
-/**
- * Creates a Configuration Cache.
- * Strategy: Persistent Disk (Atomic files).
- * Key: Raw Configuration Hash.
- */
-export const createConfigCache = (driver: AsyncDriver): ConfigCache => {
- return {
- get: async (hash: string): Promise => {
- return driver.get(hash);
- },
-
- set: async (hash: string, report: ConfigValidationResult): Promise => {
- await driver.set(hash, report);
- }
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/file-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/file-cache.ts
deleted file mode 100644
index a173a09..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/file-cache.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { AsyncDriver } from '../drivers/types.js';
-
-export interface FileCacheEntry {
- files: string[];
- timestamp: number;
-}
-
-export interface FileCache {
- get: (key: string) => Promise;
- set: (key: string, files: string[]) => Promise;
-}
-
-export const createFileCache = (driver: AsyncDriver): FileCache => {
- return {
- get: (key) => driver.get(key),
- set: (key, files) => driver.set(key, { files, timestamp: Date.now() })
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/hashing.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/hashing.ts
deleted file mode 100644
index 6f92cae..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/hashing.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import crypto from 'node:crypto';
-import { CACHE_KEY_PREFIX } from '../constants.js';
-
-/**
- * Computes a customized SHA-256 hash for content.
- * Accepts an optional salt (e.g., tool version) to ensure cache invalidation on upgrades.
- */
-export const computeCompositeHash = (content: string, salt: string = CACHE_KEY_PREFIX): string => {
- const hash = crypto.createHash('sha256');
- hash.update(content);
- if (salt) {
- hash.update(salt);
- }
- return hash.digest('hex');
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/meta-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/meta-cache.ts
deleted file mode 100644
index 6118ecd..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/meta-cache.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { AsyncDriver } from '../drivers/types.js';
-
-export interface FileMeta {
- readonly mtime: number;
- readonly size: number;
- readonly hash: string;
-}
-
-export interface MetaCache {
- get: (filePath: string) => Promise;
- set: (filePath: string, meta: FileMeta) => Promise;
- delete: (filePath: string) => Promise;
- flush: () => Promise;
-}
-
-/**
- * Creates a File Meta Cache.
- * Stores file stats (mtime, size) and their computed content hashes.
- * Strategy: Persistent Disk Storage.
- * Key: Absolute File Path.
- */
-export const createMetaCache = (driver: AsyncDriver): MetaCache => {
- return {
- get: (filePath) => driver.get(filePath),
- set: (filePath, meta) => driver.set(filePath, meta),
- delete: (filePath) => driver.delete(filePath),
- flush: async () => {
- if ('flush' in driver && typeof (driver as any).flush === 'function') {
- await (driver as any).flush();
- }
- }
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/plan-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/plan-cache.ts
deleted file mode 100644
index 5df9d93..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/plan-cache.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { AsyncDriver } from '../drivers/types.js';
-
-export interface PlanCache {
- get: (key: string) => Promise;
- set: (key: string, plan: any) => Promise;
- delete: (key: string) => Promise;
-}
-
-/**
- * Creates a Plan Cache.
- * Stores the entire built execution plan as a binary blob using V8 serialization.
- * Strategy: Persistent Disk Storage.
- * Key: Combined hash of files + rules.
- */
-export const createPlanCache = (driver: AsyncDriver): PlanCache => {
- return {
- get: (key) => driver.get(key),
- set: (key, plan) => driver.set(key, plan),
- delete: (key) => driver.delete(key),
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/result-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/result-cache.ts
deleted file mode 100644
index 8be3dc7..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/result-cache.ts
+++ /dev/null
@@ -1,292 +0,0 @@
-import { AsyncDriver } from '../drivers/types.js';
-import { debug } from '@ngcompass/common';
-
-/**
- * Cache metadata for tracking usage and freshness
- */
-export interface CacheMetadata {
- /** Task ID (content hash) */
- readonly taskId: string;
- /** When result was cached */
- readonly timestamp: number;
- /** Number of cache hits */
- readonly hits: number;
- /** Last access time (for LRU) */
- readonly lastAccess: number;
-}
-
-/**
- * Cache entry with metadata
- */
-export interface CacheEntry {
- /** The cached result */
- readonly result: T;
- /** Cache metadata */
- readonly metadata: CacheMetadata;
-}
-
-/**
- * Result Cache interface with bulk operations for Phase 2.0
- */
-export interface ResultCache {
- // Single operations (existing)
- get: (hash: string) => Promise;
- set: (hash: string, result: T) => Promise;
- has: (hash: string) => Promise;
- delete: (hash: string) => Promise;
-
- // Bulk operations (Phase 2.0)
- getMany: (hashes: ReadonlyArray) => Promise>;
- setMany: (entries: ReadonlyArray) => Promise;
- hasMany: (hashes: ReadonlyArray) => Promise>;
-
- // Metadata operations (Phase 2.0)
- getWithMetadata: (hash: string) => Promise | undefined>;
- getManyWithMetadata: (hashes: ReadonlyArray) => Promise>>;
- updateMetadata: (hash: string, updates: Partial) => Promise;
-}
-
-/**
- * Creates a Result Cache with bulk operations and metadata tracking.
- * Strategy: Distributed Files (One file per result).
- * Key: Content Hash (taskId).
- *
- * Phase 2.0: Enhanced with:
- * - Bulk operations (getMany, setMany, hasMany)
- * - Metadata tracking (hits, timestamp, lastAccess)
- * - Cache warmth analysis
- */
-/**
- * Maximum number of result entries to read/write in a single parallel batch.
- * Sized to stay well within typical OS per-process file-descriptor limits (~1024)
- * while keeping batch overhead low. Benchmarked at 200 on ext4 and APFS.
- */
-const RESULT_BATCH_SIZE = 200;
-
-/**
- * Maximum number of metadata entries to write in a single parallel batch.
- * Kept lower than RESULT_BATCH_SIZE because metadata writes are paired with
- * result reads — running both at 200 would double the concurrent fd usage.
- */
-const METADATA_BATCH_SIZE = 100;
-
-export const createResultCache = (driver: AsyncDriver): ResultCache => {
-
- /**
- * Get metadata key for a task
- */
- const getMetadataKey = (hash: string): string => `${hash}.meta`;
-
- /**
- * Get or create metadata for a task
- */
- const getOrCreateMetadata = async (hash: string): Promise => {
- const metaKey = getMetadataKey(hash);
- const existing = await driver.get(metaKey) as CacheMetadata | undefined;
-
- if (existing) {
- return existing;
- }
-
- const newMeta: CacheMetadata = {
- taskId: hash,
- timestamp: Date.now(),
- hits: 0,
- lastAccess: Date.now(),
- };
-
- await driver.set(metaKey, newMeta);
- return newMeta;
- };
-
- /**
- * Increment hit count and update last access
- */
- const incrementHits = async (hash: string): Promise => {
- const meta = await getOrCreateMetadata(hash);
- const updated: CacheMetadata = {
- ...meta,
- hits: meta.hits + 1,
- lastAccess: Date.now(),
- };
- await driver.set(getMetadataKey(hash), updated);
- };
-
- return {
- // Single operations
- get: async (hash: string): Promise => {
- const result = await driver.get(hash) as T | undefined;
- if (result !== undefined) {
- // Track cache hit (fire and forget)
- incrementHits(hash).catch(err => {
- debug('cache', `incrementHits failed for ${hash}: ${err instanceof Error ? err.message : String(err)}`);
- });
- }
- return result;
- },
-
- set: async (hash: string, result: T): Promise => {
- await driver.set(hash, result);
- // Create initial metadata
- await getOrCreateMetadata(hash);
- },
-
- has: async (hash: string): Promise => {
- return driver.has(hash);
- },
-
- delete: async (hash: string): Promise => {
- await driver.delete(hash);
- try {
- await driver.delete(getMetadataKey(hash));
- } catch (error) {
- debug('cache', `Failed to delete metadata for ${hash}: ${error instanceof Error ? error.message : String(error)}`);
- }
- },
-
- // Bulk operations
- getMany: async (hashes: ReadonlyArray): Promise> => {
- if (hashes.length === 0) {
- return new Map();
- }
-
- const results = new Map();
-
- // Batched concurrency to avoid overwhelming OS with unbounded parallel I/O
- for (let i = 0; i < hashes.length; i += RESULT_BATCH_SIZE) {
- const batch = hashes.slice(i, i + RESULT_BATCH_SIZE);
- await Promise.all(
- batch.map(async (hash) => {
- const result = await driver.get(hash) as T | undefined;
- if (result !== undefined) {
- results.set(hash, result);
- // Track hit (fire and forget)
- incrementHits(hash).catch(err => {
- debug('cache', `incrementHits failed for ${hash}: ${err instanceof Error ? err.message : String(err)}`);
- });
- }
- })
- );
- }
-
- return results;
- },
-
- setMany: async (entries: ReadonlyArray): Promise => {
- if (entries.length === 0) return;
-
- // Batched concurrency to avoid overwhelming OS with unbounded parallel writes
- for (let i = 0; i < entries.length; i += METADATA_BATCH_SIZE) {
- const batch = entries.slice(i, i + METADATA_BATCH_SIZE);
- await Promise.all(
- batch.map(async ([hash, result]) => {
- await driver.set(hash, result);
- await getOrCreateMetadata(hash);
- })
- );
- }
- },
-
- hasMany: async (hashes: ReadonlyArray): Promise> => {
- // Short-circuit: nothing to check
- if (hashes.length === 0) {
- return new Set();
- }
-
- // Short-circuit: if the cache directory is empty/missing, skip all 137K+ fs.access calls
- try {
- const stats = await driver.getStats();
- if (stats.entries === 0) {
- return new Set();
- }
- } catch {
- // If getStats fails (e.g. dir missing), cache is empty
- return new Set();
- }
-
- const existing = new Set();
-
- // Batched concurrency to avoid overwhelming the OS with unbounded parallel fs.access
- for (let i = 0; i < hashes.length; i += RESULT_BATCH_SIZE) {
- const batch = hashes.slice(i, i + RESULT_BATCH_SIZE);
- await Promise.all(
- batch.map(async (hash) => {
- const exists = await driver.has(hash);
- if (exists) {
- existing.add(hash);
- }
- })
- );
- }
-
- return existing;
- },
-
- // Metadata operations
- getWithMetadata: async (hash: string): Promise | undefined> => {
- const result = await driver.get(hash) as T | undefined;
- if (result === undefined) {
- return undefined;
- }
-
- const metadata = await getOrCreateMetadata(hash);
-
- // Update access time
- await incrementHits(hash);
-
- return { result, metadata };
- },
-
- getManyWithMetadata: async (hashes: ReadonlyArray): Promise>> => {
- if (hashes.length === 0) {
- return new Map>();
- }
-
- const entries = new Map>();
- const metadataUpdates: Array<[string, CacheMetadata]> = [];
-
- // Batched concurrency to avoid overwhelming OS with unbounded parallel I/O
- for (let i = 0; i < hashes.length; i += RESULT_BATCH_SIZE) {
- const batch = hashes.slice(i, i + RESULT_BATCH_SIZE);
- await Promise.all(
- batch.map(async (hash) => {
- const result = await driver.get(hash) as T | undefined;
- if (result !== undefined) {
- const metadata = await getOrCreateMetadata(hash);
- const updated: CacheMetadata = {
- ...metadata,
- hits: metadata.hits + 1,
- lastAccess: Date.now(),
- };
- entries.set(hash, { result, metadata: updated });
- metadataUpdates.push([hash, updated]);
- }
- })
- );
- }
-
- // Batch update all metadata at once (non-blocking)
- if (metadataUpdates.length > 0) {
- for (let i = 0; i < metadataUpdates.length; i += METADATA_BATCH_SIZE) {
- const batch = metadataUpdates.slice(i, i + METADATA_BATCH_SIZE);
- await Promise.all(
- batch.map(async ([hash, updated]) => {
- await driver.set(getMetadataKey(hash), updated);
- })
- );
- }
- }
-
- return entries;
- },
-
- updateMetadata: async (hash: string, updates: Partial): Promise => {
- const existing = await driver.get(getMetadataKey(hash)) as CacheMetadata | undefined ?? await getOrCreateMetadata(hash);
- const updated: CacheMetadata = {
- ...existing,
- ...updates,
- };
- await driver.set(getMetadataKey(hash), updated);
- },
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/services/source-cache.ts b/.claude/worktrees/nice-taussig/packages/cache/src/services/source-cache.ts
deleted file mode 100644
index b01b7c3..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/services/source-cache.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { SyncDriver } from '../drivers/types.js';
-
-export interface SourceEntry {
- content: string;
- filePath: string;
-}
-
-export interface SourceCache {
- get: (hash: string) => SourceEntry | undefined;
- set: (hash: string, entry: SourceEntry) => void;
- has: (hash: string) => boolean;
-}
-
-/**
- * Creates a Source Cache.
- * Strategy: Memory Only (Source code needs to be hot).
- * Key: Content Hash.
- */
-export const createSourceCache = (driver: SyncDriver): SourceCache => {
- return {
- get: (hash) => driver.get(hash),
- set: (hash, entry) => driver.set(hash, entry),
- has: (hash) => driver.has(hash),
- };
-};
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/types.ts b/.claude/worktrees/nice-taussig/packages/cache/src/types.ts
deleted file mode 100644
index f84f5a5..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/types.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { SourceCache } from './services/source-cache.js';
-import { AstCache } from './services/ast-cache.js';
-import { ResultCache } from './services/result-cache.js';
-import { ConfigCache } from './services/config-cache.js';
-import { MetaCache } from './services/meta-cache.js';
-import { PlanCache } from './services/plan-cache.js';
-import { FileCache } from './services/file-cache.js';
-
-export type { SourceCache, AstCache, ResultCache, ConfigCache, MetaCache, PlanCache, FileCache };
-export type { FileMeta } from './services/meta-cache.js';
-
-export interface CacheInfo {
- ast: {
- l1: { entries: number; maxEntries: number; size: number };
- l2: { entries: number; size: number };
- };
- config: { entries: number; size: number };
- results: { entries: number; size: number };
- totalSize: number;
- location: string;
- version: string;
-}
-
-export interface CacheContext {
- sources: SourceCache;
- asts: AstCache;
- results: ResultCache;
- configs: ConfigCache;
- metas: MetaCache;
- plans: PlanCache;
- files: FileCache;
- /**
- * Stores full analysis results keyed by global hash (for warm run short-circuiting)
- */
- analysis: ResultCache;
- /**
- * Computes a hash suitable for caching keys
- */
- computeHash: (content: string, salt?: string) => string;
- /**
- * Clears old disk cache entries
- */
- prune: () => Promise;
- /**
- * Clears all caches
- */
- clear: () => Promise;
- /**
- * Clear specific cache type
- */
- clearType: (type: 'ast' | 'config' | 'results' | 'all') => Promise;
- /**
- * Get cache statistics and info
- */
- getInfo: () => Promise;
- /**
- * Get absolute path to cache directory
- */
- getCachePath: () => string;
- /**
- * Flushes all pending writes to disk.
- * Call this before process exit to prevent data loss.
- */
- flush: () => Promise;
-}
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/utils/fs.ts b/.claude/worktrees/nice-taussig/packages/cache/src/utils/fs.ts
deleted file mode 100644
index 6cd93e1..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/utils/fs.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import fs from 'node:fs/promises';
-import path from 'node:path';
-
-/**
- * Recursively gets the number of files and total size of a directory.
- */
-export async function getDirectoryStats(dirPath: string): Promise<{ entries: number; size: number }> {
- let entries = 0;
- let size = 0;
-
- try {
- await fs.access(dirPath);
- } catch {
- return { entries: 0, size: 0 };
- }
-
- async function traverse(currentPath: string) {
- const files = await fs.readdir(currentPath, { withFileTypes: true });
-
- for (const file of files) {
- const fullPath = path.join(currentPath, file.name);
- if (file.isDirectory()) {
- await traverse(fullPath);
- } else {
- entries++;
- const stats = await fs.stat(fullPath);
- size += stats.size;
- }
- }
- }
-
- try {
- await traverse(dirPath);
- } catch {
- // Ignore errors during traversal (e.g. race conditions)
- }
-
- return { entries, size };
-}
diff --git a/.claude/worktrees/nice-taussig/packages/cache/src/utils/stable-serialize.ts b/.claude/worktrees/nice-taussig/packages/cache/src/utils/stable-serialize.ts
deleted file mode 100644
index c7b9e33..0000000
--- a/.claude/worktrees/nice-taussig/packages/cache/src/utils/stable-serialize.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Stable Serialization Utility
- *
- * Produces a deterministic JSON string suitable for use in cache keys and hashes.
- *
- * Guarantees:
- * - Objects: keys sorted alphabetically at every depth level
- * - Arrays: elements preserved in insertion order (NOT sorted)
- * - undefined values in objects: omitted (same as JSON.stringify)
- * - Date: serialized as ISO 8601 string
- * - RegExp: serialized as its string representation (e.g. "/abc/gi")
- * - Circular references: throws SerializationError (never silently sanitizes)
- * - Functions: throws SerializationError
- * - NaN / Infinity: throws SerializationError
- *
- * This replaces bare JSON.stringify() wherever the insertion order of object
- * keys must not affect the resulting string (e.g. batch keys, cache keys).
- */
-
-export class SerializationError extends Error {
- constructor(
- message: string,
- public readonly path: string[]
- ) {
- super(message);
- this.name = 'SerializationError';
- }
-}
-
-/**
- * Produces a deterministic JSON string by sorting object keys recursively.
- *
- * @param value - The value to serialize
- * @param _path - Internal: tracks key path for error messages
- * @param _seen - Internal: tracks visited objects for circular detection
- * @returns A stable, deterministic JSON string
- * @throws {SerializationError} on circular references, functions, or non-finite numbers
- */
-export function stableSerialize(
- value: unknown,
- _path: string[] = [],
- _seen: WeakSet