diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3025bee9..3be81625 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,7 +2,7 @@ name: Pull Request on: pull_request: - types: [opened, edited, reopened, synchronize] + types: [opened, reopened, synchronize] branches: [ main ] permissions: diff --git a/.gitignore b/.gitignore index cf465b59..8eb86537 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,9 @@ tools/* /jscpd /tmp /.idea/ + +.kiro/* +!.kiro/skills/ +!.kiro/skills/** +!.kiro/steering/ +!.kiro/steering/** diff --git a/.kiro/skills/cfn-lsp-development/SKILL.md b/.kiro/skills/cfn-lsp-development/SKILL.md index bc7568e6..9f3d8830 100644 --- a/.kiro/skills/cfn-lsp-development/SKILL.md +++ b/.kiro/skills/cfn-lsp-development/SKILL.md @@ -1,7 +1,7 @@ --- name: cfn-lsp-development -description: > - Development workflow for the CloudFormation Language Server. Guides agents through implementation, testing, and PR creation. Use when implementing features, fixing bugs, adding handlers/providers, or making changes to this repository. +description: Development workflow for the AWS CloudFormation Language Server (this repository). Use when implementing features, fixing bugs, adding tests, or making any code change in cloudformation-languageserver. Covers planning, telemetry, build/lint/test verification, and PR conventions. +license: Apache-2.0 --- # CFN LSP Development Workflow @@ -10,10 +10,51 @@ description: > These constraints apply to ALL changes in this repository: -- **No backwards-incompatible changes (hard rule)** — Never change or remove existing LSP protocol methods, request types, or response/return types. Additive changes only — breaking the wire contract breaks the already-shipped VS Code and JetBrains clients. -- **Cross-platform** — All changes must work on macOS, Windows, and Linux -- **No database locking** — Never lock LMDB or other shared resources; multiple concurrent LSP connections may exist -- **Performance** — Handlers must respond quickly; avoid blocking the event loop or doing synchronous I/O in request paths. Use `npm run benchmark` to confirm changes haven't regressed latency. +- **No backwards-incompatible changes (hard rule)** — Never change or remove existing LSP protocol methods, request + types, or response/return types. Additive changes only — breaking the wire contract breaks the already-shipped VS Code + and JetBrains clients. +- **Cross-platform** — All changes must work on macOS, Linux, and Windows. The DataStore layer in particular has two + persisted backends (LMDB on macOS / Linux, encrypted file store on Windows or when the `FileDb` feature flag is on); + any persistence change must work against both. See `architecture.md`. +- **No database locking** — Never hold long locks on LMDB or other shared resources; multiple concurrent LSP + connections may exist. +- **Performance** — Handlers must respond quickly; avoid blocking the event loop or doing synchronous I/O in request + paths. Use `npm run benchmark` to confirm changes haven't regressed latency. + +## Repository Layout + +You are working inside the `cloudformation-languageserver` repo. Source lives in `src/`, tests mirror the source tree +under `tst/`. See `.kiro/steering/structure.md` for directory-level guidance and `.kiro/steering/architecture.md` for +the component / handler model. + +## Scratch Files + +Put scratch files, debug scripts, manual repro templates, ad-hoc benchmark scripts, and any other throwaway artifacts +under `tmp/` at the repo root. `tmp/` (and `tmp-node-modules/`, `tmp-tst/`) is `.gitignore`d, so it will never be +committed accidentally. Do **not** scatter scratch files across `src/`, `tst/`, or the workspace root. + +## Code Quality + +Hold every change in this repo to a senior-engineer standard. The full rule sets are bundled with this skill — read +them when you write or modify code: + +- Source code: [`references/source-code-rules.md`](references/source-code-rules.md) — naming, reuse, architecture, + SOLID, immutability, error handling, null/thread safety, performance, dependency discipline, and public-API + compatibility. +- Test code: [`references/test-code-rules.md`](references/test-code-rules.md) — meaningful coverage, AAA structure, + mock quality, determinism, behavior-not-implementation, edge / failure paths. + +The two rules with the highest leverage in this repository: + +- **Reuse `src/utils/` and `tst/utils/` before writing new helpers.** Search both directories first + (`MockServerComponents`, `TemplateBuilder`, `MockContext`, `Expect`, `SchemaUtils`, `Delayer`, `Retry`, + `AwsErrorMapper`, `PathUtils`, `String`, `TypeCheck`, etc.). If a needed helper doesn't exist, add it to the + appropriate `utils/` directory rather than co-locating it in a feature folder, so the next caller can find it. +- **Respect the layer model.** Handlers stay thin and delegate to a Component or Service (see `architecture.md`). + Do not let handlers do AWS I/O, file I/O, or schema parsing directly. + +Apply both rule sets even when the user doesn't mention "quality" or "best practices" — they are the contract for +contributing to this repo. ## Developer Tools @@ -23,7 +64,8 @@ These constraints apply to ALL changes in this repository: npm run debug-tree -- --file ``` -Runs `tools/debug_tree.ts` — builds a `SyntaxTree`, traverses every node, and emits `Context` objects at key positions. The fastest way to diagnose parse/context problems when working on completion, hover, or definition. +Runs `tools/debug_tree.ts` — builds a `SyntaxTree`, traverses every node, and emits `Context` objects at key positions. +The fastest way to diagnose parse / context problems when working on completion, hover, or definition. ### Benchmarking performance @@ -32,114 +74,104 @@ npm run benchmark # default run npm run benchmark -- --iterations 100 --templates ./tst/resources --output results.md ``` -Runs `tools/benchmark.ts` — measures syntax-tree creation and context-lookup latency across iterations. Use this to verify the Performance constraint above. - -### Stability testing - -```bash -npm run test:stability -``` - -Runs `tools/stability/` — long-running tests that exercise completion and hover under sustained load. +Runs `tools/benchmark.ts` — measures syntax-tree creation and context-lookup latency across iterations. Use this to +verify the Performance constraint above. ## Workflow ### Step 1: Research -Before making changes, locate or set up local workspaces for affected packages: - -1. **Search parent directories** for existing checkouts: - - Look for `cloudformation-languageserver/` in `../`, `../../`, etc. - - Check `LOCAL_TESTING_SERVER_PATH` env var for an existing server bundle path - -2. **If not found**, ask the user: - - "Do you have a local checkout? If so, provide the path." - - "Should I clone the repository?" - -3. **Set up missing workspace:** - - `git clone https://github.com/aws-cloudformation/cloudformation-languageserver.git` - -Next, browse the source code. -- Identify conventions: file naming, class structure, test patterns -- Read files relevant to the task to understand wiring (imports, exports, registration) -- Check `.kiro/steering/` for architecture guidance when needed +Apply "Explore the Project Before Writing" from [`references/source-code-rules.md`](references/source-code-rules.md): +read the relevant feature directory (`src//`), its tests under `tst/`, and the shared helpers in +`src/utils/` and `tst/utils/` before designing the change. Use `.kiro/steering/architecture.md` and +`.kiro/steering/structure.md` when you need a map of where things live. ### Step 2: Plan Before writing code: -1. Read the task requirements -2. Identify affected source directories (see `structure.md` in steering) -3. Create an implementation plan - a. Create a markdown file at the workspace root: `./-plan.md` - The plan MUST include: - - **Summary** — what is being implemented and why - - **Affected packages** — which packages need changes - - **Approach** — high-level design decisions - - **Task checklist** — every discrete task as a checkbox item, ordered by execution sequence - -4. Present the plan to the user and ask them to review it -5. **STOP and wait for explicit user approval before writing any code** -6. As tasks are completed, update the checklist in the plan file (check off items) +1. Identify the affected source directories (see `structure.md`). +2. Create an implementation plan as `tmp/-plan.md` (under the gitignored `tmp/`), including: + - **Summary** — what is being implemented and why + - **Affected files / directories** + - **Approach** — high-level design decisions and any tradeoffs + - **Task checklist** — every discrete task as a checkbox item, ordered by execution sequence +3. Present the plan to the user. +4. **STOP and wait for explicit user approval before writing any code.** +5. As tasks are completed, check off items in the plan file. ### Step 3: Implement -1. Create a local branch for your changes -2. Write unit tests that define expected behavior (they should fail initially) -3. Implement the minimum code to make tests pass -4. Follow existing patterns in the relevant feature directory -5. Refactor for clarity while keeping tests green +1. Create a local branch for your changes. +2. Write unit tests that define the expected behavior (they should fail initially). +3. Implement the minimum code to make tests pass. +4. Follow existing patterns in the relevant feature directory. +5. Refactor for clarity while keeping tests green. + +For new persisted state, decide whether it belongs in `PersistedStores` (survives editor restart) or as a non-persisted +`MemoryStore`. See `src/datastore/DataStore.ts`. ### Step 4: Telemetry -Wire telemetry for new handlers using `ScopedTelemetry` public methods: +Wire telemetry on new handlers and any code path you care about observing in production. + +**Prefer the decorators in `src/telemetry/TelemetryDecorator.ts`** over direct `ScopedTelemetry` calls — they keep +metric scopes consistent and avoid boilerplate: ```typescript -// Measure duration + count + fault for a handler -const result = await this.telemetry.measureAsync('HandlerName', async () => { - /* handler logic */ -}); +import {Telemetry, Track} from '../telemetry/TelemetryDecorator'; +import {ScopedTelemetry} from '../telemetry/ScopedTelemetry'; + +@Telemetry({scope: 'CompletionRouter'}) +export class CompletionRouter { + // Field is injected by @Telemetry — no constructor wiring needed + private readonly telemetry!: ScopedTelemetry; + + @Track({name: 'getCompletions', captureErrorAttributes: true}) + public async getCompletions(params: CompletionParams): Promise { + /* handler logic */ + } +} +``` -// Track execution with response tracking -const result = this.telemetry.trackExecution('HandlerName', () => { - /* handler logic */ -}); +`@Track` emits `{Name}.count`, `{Name}.duration`, and `{Name}.fault` for each invocation. Use `@Telemetry` once per +class to bind a scope; use `@Track` on each method you want measured. -// Count-only (no duration) -const result = await this.telemetry.countExecutionAsync('HandlerName', async () => { - /* handler logic */ +Use the imperative `ScopedTelemetry` helpers only when you need to instrument something a decorator can't reach +(an inline closure, a lambda, a non-class function). The available helpers are: + +```typescript +// Inline async — emits {Name}.count, {Name}.duration, {Name}.fault +const result = await this.telemetry.measureAsync('Subtask', async () => { + /* logic */ }); ``` -**Metrics emitted by these methods:** -- `{Name}.count` — invocation count -- `{Name}.duration` — response time (measure/trackExecution only) -- `{Name}.fault` — unhandled exception - ### Step 5: Verify Before creating a PR, **all of these must pass**: ```bash -npm run build # TypeScript compilation -npm run lint # Linting (zero warnings) -npm run test # Unit tests + coverage (thresholds: 88% statements, 82% branches, 90% functions, 88% lines) +npm run build # TypeScript compilation +npm run lint # Linting (zero warnings — `--max-warnings 0`) +npm run test # Unit + integration + e2e + coverage ``` -Coverage runs automatically with `npm run test` (`coverage.enabled: true` in `vitest.config.ts`). +Coverage runs automatically with `npm run test` and thresholds are enforced from `vitest.config.ts`. +If you only need to run a subset while iterating: `npm run test:unit` or `npm run test:integration`. +Use `npm run lint:fix` for auto-fixable lint violations. ### Step 6: Client-Side Changes -Some changes require corresponding updates in the editor clients (e.g., features that need UX work). See the client repositories for their own build/test/contribution guides: +Some changes (new commands, new code lens actions, new UI surfaces) require corresponding updates in the editor +clients. See the client repositories for their own build / test / contribution guides: -| Client | Repository | CloudFormation path | -|--------|-----------|-------------------| -| VS Code | [`aws/aws-toolkit-vscode`](https://github.com/aws/aws-toolkit-vscode) (branch: `master`) | `packages/core/src/awsService/cloudformation/` | +| Client | Repository | CloudFormation path | +|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| +| VS Code | [`aws/aws-toolkit-vscode`](https://github.com/aws/aws-toolkit-vscode) (branch: `master`) | `packages/core/src/awsService/cloudformation/` | | JetBrains | [`aws/aws-toolkit-jetbrains`](https://github.com/aws/aws-toolkit-jetbrains) (branch: `main`) | `plugins/toolkit/jetbrains-core/src/software/aws/toolkits/jetbrains/services/cfnlsp` | ### Step 7: PR -- Ensure new code has unit tests (and integration tests for handlers) -- Confirm no breaking API changes -- Confirm cross-platform compatibility (no platform-specific paths, no OS-specific APIs without fallbacks) -- Note in PR description if client-side changes are also needed +Re-read the **Constraints** section above and confirm none are violated. Note in the PR description if matching +client-side changes are needed. diff --git a/.kiro/skills/cfn-lsp-development/references/source-code-rules.md b/.kiro/skills/cfn-lsp-development/references/source-code-rules.md new file mode 100644 index 00000000..a01d153c --- /dev/null +++ b/.kiro/skills/cfn-lsp-development/references/source-code-rules.md @@ -0,0 +1,307 @@ +# Source Code Rules + +Apply all rules below when writing or modifying source code. + +## Priority + +**Critical** — always enforce: +#1 Self-Documenting Names, #2 Comments Only for Why, #3 Reuse Before Writing, #4 Correct Abstraction Level, #5 +Architectural Layers, #6 Law of Demeter, #7 Existing Design Patterns, #8 Single Responsibility, #9 Immutability, #15 +Tell Don't Ask, #28 Null Safety, #30 Thread Safety, #34 Error Handling + +**Contextual** — apply with judgment: +#16 YAGNI vs Abstraction, #18 KISS vs SOLID, #23 Inheritance vs Composition + +**Standard** — apply consistently: all remaining rules. + +--- + +## Naming + +### 1. Self-Documenting Names — CRITICAL + +- Every name must be immediately understandable to an unfamiliar engineer without needing a comment +- Names unambiguously describe role, behavior, or purpose +- Never use vague or generic names: `data`, `info`, `temp`, `manager`, `processor`, `handler`, `Helper`, `utils`, + `stuff`, `val`, `obj` +- Short conventional names are acceptable when local context makes the meaning obvious — e.g., `item` as the element in + a loop, or `result` as a function's accumulated return value. Avoid a generic name only when it hides what the value + actually represents +- Function names describe the single action performed: `calculateTax`, not `process` or `doWork` +- Variable names describe what the value represents: `maxRetryCount`, not `n` or `count` +- Boolean names read as true/false questions: `isValid`, `hasPermission`, `canRetry` +- Collection names indicate plurality and content: `activeUsers`, not `list` or `data` + +### 2. Comments Only for Why — CRITICAL + +- Code must be readable without comments — if it isn't, improve the names and structure +- Only write comments that explain a non-obvious reason, constraint, or trade-off behind a decision +- Never write comments that restate what the code does: no `// initialize service`, `// return result`, + `// loop through items`, `// set the value`, `// check if null` +- Never write comments that restate a function or variable name +- Never add section-separator comments like `// --- Helper Methods ---` or `// ===== Private =====` +- Do not change or remove existing comments unless the code they describe has changed +- Javadoc/docstrings: only on public API boundaries where the contract isn't obvious from the signature + +## Reuse and Placement + +### 3. Reuse Before Writing — CRITICAL + +- Before writing new code, search the existing codebase for similar functionality and reuse it +- Extract duplicate or structurally identical code into shared methods +- Check new code against itself and the existing codebase for duplication +- Every piece of knowledge has a single authoritative representation (DRY) +- Caveat: do not force DRY onto code that is only incidentally similar. A premature or wrong abstraction couples + unrelated callers more tightly than the duplication it removes — when two usages may diverge, prefer duplication until + the right shared abstraction is clear + +### 4. Correct Abstraction Level — CRITICAL + +- Place shared code at the level where all its callers can reach it — no higher, no lower +- A utility used by one module belongs in that module, not in a shared utils package +- A utility used across modules belongs in a shared location accessible to all callers +- Do not create god-classes or catch-all utility files — group by cohesive purpose + +## Architecture + +### 5. Respect Architectural Layers — CRITICAL + +- Follow the established layer boundaries found in the project +- Never bypass layers (e.g., handler must not call DB directly — go through service layer) +- New code conforms to the same layer structure as existing code + +### 6. Law of Demeter — CRITICAL + +- Only call methods on objects you have direct access to +- Do not reach through a foreign object graph to pull out distant state: `order.getCustomer().getAddress().getCity()` + violates this +- If you need something deep in an object graph, add a method on the intermediate object that exposes only what the + caller needs +- This does not forbid chaining on a single fluent API: builders (`builder.withX().withY().build()`), stream pipelines ( + `stream.filter(...).map(...).collect(...)`), and other self-returning chains are fine — each call acts on the same + object or pipeline rather than navigating an object graph + +### 7. Follow Existing Design Patterns — CRITICAL + +- Conform to patterns already established in the project (Repository, Factory, Builder, etc.) +- New code of the same kind follows the same pattern +- Do not introduce a different pattern for the same concern + +## SOLID + +### 8. Single Responsibility — CRITICAL + +- Each class has one reason to change and a single well-defined purpose +- Each function does one thing — break down multi-step functions +- Do not mix unrelated concerns (business logic + persistence + serialization in one class) + +### 9. Immutability — CRITICAL + +- Use `final`/`const`/`readonly`/`val` by default for all variables and fields +- Only make mutable when mutation is required by the logic +- Fields set once (in constructor or initialization) must be immutable + +### 10. Open/Closed Principle + +- Design for extension without modification — use abstractions so new behavior can be added without changing existing + code + +### 11. Liskov Substitution + +- Subtypes must be substitutable for base types without breaking correctness +- Overrides must not violate the parent contract, throw unexpected exceptions, or no-op parent behavior + +### 12. Interface Segregation + +- No client should depend on methods it doesn't use +- Prefer small, focused interfaces over large general-purpose ones + +### 13. Dependency Inversion + +- High-level modules depend on abstractions, not concrete low-level classes +- Do not directly instantiate low-level dependencies from high-level code + +### 14. Command Query Separation + +- Prefer methods that either mutate state (command, returns void) or return data (query, no side effects) rather than + doing both — it keeps call sites easier to reason about +- This is a preference, not an absolute: well-established idioms that both mutate and return are fine — `stack.pop()`, + `map.put(key, value)` returning the previous value, `iterator.next()`, `queue.poll()`, atomic `getAndIncrement()`. + Follow the conventions of the language and its standard library + +### 15. Tell, Don't Ask — CRITICAL + +- Tell objects to perform actions — do not pull data out with getters and act on it externally +- Move logic to where the data lives + +### 16. YAGNI vs Abstraction + +- Do not add functionality or abstractions without a present requirement +- Do not write overly generic structures for a single concrete use +- Exception: interfaces for clear contracts are acceptable even with a single implementation + +### 17. Encapsulation + +- Apply the most restrictive visibility that works — do not default to `public` +- Hide implementation details — expose only what's necessary +- Do not expose internal data structures + +### 18. KISS + +- Keep it simple — avoid overly complex conditionals and clever one-liners +- Do not add premature abstractions +- When KISS conflicts with SOLID, favor SOLID for long-term maintainability + +## Clean Code + +### 19. Function Design + +- Two or fewer arguments ideally — more than three suggests encapsulating in an object +- Do not use boolean arguments that switch behavior — split into two distinct functions +- Prefer early returns over deeply nested conditionals + +### 20. Variables and Constants + +- No magic numbers or strings — use named constants for all literal values +- Extract complex expressions into well-named intermediate variables +- Signal errors explicitly rather than through sentinel return values like `null` or `-1`. Use the project's established + mechanism — exceptions, Result types, or `Optional` (see Error Handling) — and in languages where returning error + values is idiomatic (e.g., Go), follow that idiom + +### 21. Logging + +- Logs must be purposeful with a clear audience and meaningful context +- Do not log verbosely in loops or performance-sensitive paths + +### 22. Abstraction + +- Abstract classes and interfaces define clear contracts +- Do not expose internal data structures through public APIs + +### 23. Inheritance vs Composition + +1. True "is-a" relationship? → Consider inheritance +2. Need polymorphic behavior? → Interfaces + inheritance +3. Need code reuse only? → Composition +4. Hierarchy > 2-3 levels deep? → Refactor to composition + +### 24. Polymorphism + +- Prefer polymorphic dispatch over `instanceof`/`typeof` chains that branch on an object's runtime type +- Exception: exhaustive pattern matching over a closed/sealed set of types is a valid, often clearer modern + alternative — sealed classes/interfaces with an exhaustive `switch`, Kotlin `when`, Rust `match`, or TypeScript + discriminated unions +- Overrides must maintain the base class behavioral contract + +### 25. Program to Interfaces + +- Depend on abstractions for parameters and return types when possible +- Balance with YAGNI: single-implementation interfaces are fine for clear contracts + +### 26. Cohesion and Coupling + +- Group related functionality within classes (high cohesion) +- Minimize dependencies between classes (low coupling) +- Do not group unrelated methods in one class + +### 27. Code Hygiene + +- Do not write unnecessary getters/setters on simple data classes +- Do not write public symbols without a caller in the source — no dead code +- Do not leave commented-out code + +## Bug Prevention + +### 28. Null Safety — CRITICAL + +- Guard against null/undefined dereferences +- Add null checks before method calls and property access on nullable values +- Use null-safe operators (`?.`, `Optional`) where the language supports them +- Check bounds/existence before array/map access + +### 29. Resource Management + +- Always close resources — use try-with-resources, `using`, `defer`, or equivalent +- Do not leave resources open on error paths + +### 30. Thread Safety — CRITICAL + +- Synchronize shared mutable state properly +- Use thread-safe collections and atomic operations for concurrent access +- Maintain consistent lock ordering to avoid deadlocks + +### 31. Boundary Conditions + +- Validate: empty collections, zero values, max values, division by zero +- Guard against integer overflow/underflow +- Handle empty strings, null collections, and edge cases + +## Modern Standards + +### 32. Modern Language Features + +- Use modern syntax and idioms (arrow functions, destructuring, optional chaining, etc.) +- Do not use deprecated methods — use modern replacements + +### 33. Naming Conventions + +- Follow language-specific conventions (camelCase, PascalCase, snake_case) +- Be consistent with the existing codebase's naming patterns + +### 34. Error Handling — CRITICAL + +- Use the project's established mechanism: exception hierarchies, Result types, or Optional types +- Fail fast — validate inputs and preconditions at boundaries and raise immediately on violation. When code reaches an + unexpected condition or a state that should be impossible (an unhandled enum value or `switch`/`else` branch, a + violated invariant, a "can't happen" case), raise an error immediately rather than continuing, returning a default, or + silently ignoring it +- Never hide bugs, errors, or unexpected conditions. Do not suppress them, substitute a fallback or empty value to make + the problem disappear, or downgrade an unexpected failure to a silent no-op — surface them so they can be diagnosed + and fixed +- Catch the narrowest exception type that applies — never an empty block or catch-all that hides failures +- Never swallow an exception. If you catch it, handle it, rethrow it, or log it with context — never leave the handler + empty +- Preserve the original cause and stack trace when wrapping (chained exceptions / `cause`) — do not discard diagnostics +- Error messages must state what failed and the relevant context (identifiers, expected vs. actual) +- Do not use exceptions for normal control flow +- Release resources on every error path (see Resource Management) + +### 35. Performance + +- Choose efficient algorithms and data structures — know the complexity of the path you write +- Do not create unnecessary objects in loops +- Use StringBuilder/equivalent for string concatenation in loops +- Prefer efficient collection operations over nested loops +- Avoid N+1 access patterns — batch or join repeated per-item queries and calls +- Paginate or stream unbounded result sets instead of loading everything into memory +- Cache expensive, repeated, deterministic work — but only with a clear invalidation strategy +- Do not micro-optimize at the cost of readability without measurement showing it matters + +### 36. Modern API Usage + +- Use modern APIs over legacy equivalents +- Use functional constructs (streams, map/filter/reduce) where appropriate +- Use standard library functionality — do not reinvent it + +### 37. Complexity Reduction + +- Simplify complex conditionals — extract into well-named methods +- Replace nested if-else chains with polymorphism or early returns +- Replace switch/case with polymorphism or lookup tables when the cases are an open set likely to grow; for a + closed/sealed set, a single exhaustive switch or pattern match is fine and often clearer than spreading the logic + across subclasses + +### 38. Dependency Discipline + +- Prefer the standard library and existing project dependencies before adding a new one +- Every new third-party dependency must earn its place — weigh its benefit against the long-term cost of maintaining it +- Pin or constrain versions per the project's convention so builds are reproducible +- Prefer well-maintained, widely-used libraries over unmaintained or single-purpose micro-packages +- Remove dependencies once they are no longer used + +### 39. Public API and Compatibility + +- Treat published signatures, serialized formats, and persisted schemas as contracts — do not change them silently +- Make additive, backward-compatible changes by default +- Deprecate before removing; provide a migration path for breaking changes and version them per project convention +- Keep public surfaces minimal — expose only what callers need (see Encapsulation) \ No newline at end of file diff --git a/.kiro/skills/cfn-lsp-development/references/test-code-rules.md b/.kiro/skills/cfn-lsp-development/references/test-code-rules.md new file mode 100644 index 00000000..5a51ca85 --- /dev/null +++ b/.kiro/skills/cfn-lsp-development/references/test-code-rules.md @@ -0,0 +1,80 @@ +# Test Code Rules + +Apply all rules below when writing or modifying test code. Do not apply source code rules to test files. + +--- + +### 1. Meaningful Coverage + +- Every test validates a specific business behavior or functional requirement +- Never write tests that only check instantiation, getters/setters, or `!= null` +- Every assertion checks a concrete outcome tied to business logic + +### 2. Descriptive Names + +- Test names describe the scenario and expected outcome: `shouldRejectOrder_whenInventoryInsufficient` +- Never use generic names that don't explain what is being tested +- Follow the project's established test naming convention + +### 3. Arrange-Act-Assert Structure + +- Separate setup, execution, and verification clearly +- Each test method focuses on one specific behavior +- Do not mix multiple actions or assertions for unrelated behaviors + +### 4. Mock and Stub Quality + +- Mock external dependencies; use real objects when safe +- Do not over-mock (testing mocks instead of behavior) or under-mock (hitting real external systems) +- Mock expectations must match actual usage patterns + +### 5. Test Data + +- Use meaningful, realistic test data +- Extract repeated test data into constants or builders +- Never include sensitive or production-like data + +### 6. Independence + +- Each test runs independently in any order +- No shared mutable state between tests +- Proper setup and teardown for each test + +### 7. Assertion Quality + +- Use specific assertions: `assertEquals(expected, actual)` not `assertTrue(result != null)` +- Include descriptive error messages for debugging +- Match the assertion style used in the project's existing tests + +### 8. Test Code Hygiene + +- Simplify setup with test utilities or builders — reduce boilerplate +- Do not write comments that restate the test name +- Use the simplest mocking approach that works +- Follow the project's existing testing style, framework, and utilities + +### 9. Determinism — No Flaky Tests + +- A test must produce the same result on every run, in any order, on any machine +- Control time, randomness, and concurrency — inject clocks and seeds; never rely on wall-clock timing or `sleep` to + synchronize +- No dependence on execution order or state left behind by other tests +- No real network calls and no reliance on live external services or data + +### 10. Test Behavior, Not Implementation + +- Assert observable outcomes and contracts, not private internals or incidental call sequences +- Tests should survive any refactor that preserves behavior +- Do not assert on log output or incidental details unless that output is the behavior under test + +### 11. Cover Edge and Failure Paths + +- Test the happy path, boundary values, and the error/exception paths — not just the success case +- Include empty, null, zero, maximum, and malformed inputs where applicable +- Assert that the correct error is raised for invalid input, with the expected type and message + +### 12. Fast and Isolated + +- Unit tests run in-memory with no I/O, network, or sleeps, and stay fast enough to run on every change +- Each test sets up and tears down its own state +- Push slow, integration-level concerns into the appropriate separate test tier per project convention \ No newline at end of file diff --git a/.kiro/steering/architecture.md b/.kiro/steering/architecture.md index 4440df02..3fdf25ed 100644 --- a/.kiro/steering/architecture.md +++ b/.kiro/steering/architecture.md @@ -2,139 +2,130 @@ ## System Overview -The CloudFormation LSP is a multi-layer system: editor clients communicate with a TypeScript language server over JSON-RPC. +The CloudFormation LSP is a multi-layer system. Editor clients (VSCode in TypeScript, JetBrains in Kotlin) launch +the language server and forward editor events over JSON-RPC. The client sends notifications (`didOpen`, `didChange`, +`didClose`) and requests (`completion`, `hover`, `definition`) over stdio or IPC. -### LSP Layer (Language Clients) - -| Component | Technology | Responsibilities | -|-----------|-----------|-----------------| -| VSCode Client | TypeScript | Launch server, forward editor events, render UI | -| JetBrains Client | Kotlin | Same as VSCode, different plugin framework | - -The client sends JSON-RPC notifications (`didOpen`, `didChange`, `didClose`) and requests (`completion`, `hover`, `definition`) to the server over stdio or IPC. - -### Server Layer (Language Server — TypeScript/Node.js) +## Server Architecture The server uses a **Component-Handler** architecture with three pillars: -| Pillar | Role | Examples | -|--------|------|---------| -| **Handlers** | One per LSP request. Receives request, returns response. | CompletionRouter, HoverRouter, DefinitionProvider | -| **Components** | Internal state and logic. No I/O. | SyntaxTreeManager, ContextManager, SchemaStore, LMDBStore | -| **Services** | External I/O (network, disk, subprocess). | CfnLint (Pyodide), Auth, CFN API, Telemetry | +| Pillar | Role | Examples | +|----------------|----------------------------------------------------------|-----------------------------------------------------------| +| **Handlers** | One per LSP request. Receives request, returns response. | `completionHandler`, `hoverHandler`, `definitionHandler` | +| **Components** | Internal state and logic. No external I/O. | SyntaxTreeManager, ContextManager, SchemaStore, DataStore | +| **Services** | External I/O (network, disk, subprocess). | CfnLint, Guard, AwsCredentials, CFN API, Telemetry | ### Request Data Flow -1. User types in IDE → Client sends `textDocument/didChange` -2. Server updates AST via SyntaxTreeManager (tree-sitter incremental parse) -3. ContextManager rebuilds semantic model (entities, references) -4. If completion requested → CompletionRouter reads Context + Schema → returns suggestions -5. In parallel, DiagnosticCoordinator sends template to CfnLint worker → publishes diagnostics +1. User types in IDE → Client sends `textDocument/didChange`. +2. Server updates AST via `SyntaxTreeManager` (tree-sitter incremental parse). +3. `ContextManager` resolves the AST node at the cursor into a `Context` object on demand. +4. If completion requested → `CompletionRouter` reads Context + Schema → returns suggestions. +5. In parallel, `DiagnosticCoordinator` merges diagnostics from cfn-lint and Guard, debounces (200 ms), + then publishes `textDocument/publishDiagnostics`. ## Core Components ### SyntaxTreeManager (`src/context/syntaxtree/`) -Parses YAML/JSON templates into a language-agnostic AST using **tree-sitter** (native Node.js N-API bindings). +Parses YAML/JSON templates into a language-agnostic AST using **tree-sitter**. -- Maintains one AST per open file +- One `SyntaxTree` per open file - Incrementally re-parses only changed regions on `didChange` -- Error-tolerant: produces partial AST with ERROR nodes so completion/hover still work on valid portions +- Error-tolerant: produces a partial AST with ERROR nodes so completion/hover still work on valid portions ### ContextManager (`src/context/`) -Builds semantic understanding on top of the raw AST. +Builds semantic understanding on top of the raw AST. Resolves the cursor position into a `Context` (current node, path, +property path, entity root). -Data flow: `didChange` → SyntaxTreeManager → ContextManager → entity building → intrinsic resolution → logical ID reference finding +- `ContextManager.getContext(params)` → `Context | undefined` for cursor position +- `IntrinsicContext` resolves `Ref`, `Fn::GetAtt`, `Fn::Sub`, etc. +- `LogicalIdReferenceFinder` (`src/context/semantic/`) provides reverse-reference lookup for go-to-definition +- `EntityBuilder` (`src/context/semantic/`) walks the AST and constructs typed `Entity` objects -- `FileContextManager` — per-file state (resources, parameters, outputs, conditions, mappings) -- `EntityBuilder` (`src/context/semantic/`) — function module that walks AST and constructs typed Entity objects -- `IntrinsicContext` — resolves `Ref`, `Fn::GetAtt`, `Fn::Sub`, etc. -- `LogicalIdReferenceFinder` (`src/context/semantic/`) — function module providing reverse-reference map for go-to-definition +`FileContext` (`src/context/FileContext.ts`) holds per-file parsed sections (resources, parameters, outputs, conditions, +mappings) with lazy caching. `FileContextManager` is a thin factory that produces `FileContext` instances from the +`DocumentManager`. ### SchemaStore (`src/schema/`) -CloudFormation resource type schemas (~1,200 types, ~50,000 properties). +CloudFormation resource type schemas. -- Downloads schema ZIP from CloudFormation Registry per region on first launch -- Stored in LMDB for persistence across editor restarts +- Downloads schema ZIP from the CloudFormation Registry per region on first launch +- Persisted via the DataStore layer so schemas survive editor restarts - Background refresh checks for updates without blocking the user +- Schema transformers in `src/schema/transformers/` apply common normalizations (remove read-only properties, add + required write-only properties, etc.) -### LMDBStore (`src/datastore/lmdb/`) +### DataStore (`src/datastore/`) -Fast persistent key-value storage (Lightning Memory-Mapped Database). +Persistent and in-memory key-value storage. The `DataStore` interface (`src/datastore/DataStore.ts`) has **three +implementations** selected at runtime: -- Database file: `~/.cfn-lsp/lmdb/` -- Read operations are lock-free (MVCC) -- Write operations acquire exclusive lock — only one write transaction at a time -- `FileStoreFactory` (`src/datastore/`) + `KeyedFileStore` (`src/datastore/file/`) with `EncryptedFile`/`Encryption` for encrypted file-based storage on Windows or when `fileDb` feature flag is enabled +| Implementation | Module | Activation | +|--------------------------|--------------------------------|------------------------------------------------------| +| **LMDB store** (default) | `src/datastore/lmdb/` | All platforms by default (when not Windows / fileDb) | +| **File store** | `src/datastore/file/` | Windows OR `FileDb` feature flag enabled | +| **Memory store** | `src/datastore/MemoryStore.ts` | All non-persisted stores (e.g. `private_schemas`) | -### CfnLint Service (`src/services/cfnLint/`) +`MultiDataStoreFactoryProvider` (`src/datastore/DataStore.ts`) chooses LMDB vs File at startup based on platform and +feature flag, and pairs whichever persisted store is selected with `MemoryStoreFactory` for in-memory stores. -Python linter running in Pyodide (WebAssembly) worker thread. +- LMDB is the default persisted store on macOS / Linux. Database directory: `/lmdb/v5/`. +- File store is the encrypted-file alternative used on Windows or when LMDB is disabled. Database directory: + `/filedb/v3/`. One `.enc` file per key via `KeyedFileStore`. +- Memory store is used for `StoreName` values not in `PersistedStores` (currently `private_schemas`), so they are + loaded fresh each session. -- No system Python required — Pyodide bundles Python 3.13 in WASM -- Wheel files in `assets/wheels/` (cfn-lint, boto3, deps) -- `PyodideWorkerManager` spawns worker, loads runtime, installs wheels -- Fallback: local wheels → CDN fetch → cfn-lint unavailable (schema-only validation) +### CfnLint / Guard Services -### Auth Service (`src/auth/`) +`src/services/cfnLint/` and `src/services/guard/` produce diagnostics; both flow through `DiagnosticCoordinator`. -- Credentials passed from client via `aws/credentials/iam/update` notification -- Stored in memory only (never persisted) -- Server sends `aws/credentials/iam/expired` on 401/403 -- Supports: IAM Identity Center (SSO), IAM access keys, Builder ID +### DiagnosticCoordinator (`src/services/DiagnosticCoordinator.ts`) -### Telemetry (`src/telemetry/`) - -- OpenTelemetry metrics emitted as CloudWatch EMF -- Key metrics: `{Handler}.duration`, `{Handler}.fault`, `{Handler}.count`, `LMDB.{op}.duration`, `pyodide.init.success`, `worker.crash` -- Client-side telemetry is opt-in +Merges diagnostics from multiple sources (`cfn-lint`, `guard`, server-side validation) per URI and publishes the +combined result via `textDocument/publishDiagnostics`. Debounces publishing with a 200 ms `Delayer` to avoid +spamming the client during keystrokes. **Not** an LSP request handler — it is invoked by the diagnostic-producing +services. -## Handlers - -Request handlers are registered in `CfnServer.ts` as functions (e.g., `completionHandler`, `hoverHandler`) backed by implementation classes: +### Auth Service (`src/auth/`) -| Implementation Class | LSP Method | Function | -|---------------------|-----------|----------| -| CompletionRouter | `textDocument/completion` | Context-aware autocomplete | -| HoverRouter | `textDocument/hover` | Documentation on hover | -| DefinitionProvider | `textDocument/definition` | Jump to definition | -| CodeActionService | `textDocument/codeAction` | Quick fixes | -| DocumentSymbolRouter | `textDocument/documentSymbol` | Template outline | -| CodeLensProvider | `textDocument/codeLens` | Inline stack actions | +- Client → server: `aws/credentials/iam/update` (request) provides credentials; `aws/credentials/iam/delete` + (notification) clears them. Stored in memory only. +- Online features wrap their calls in `withOnlineGuard` (`src/utils/OnlineFeatureWrapper.ts`), which raises + `OnlineFeatureErrorCode.ExpiredCredentials` when AWS calls return `ExpiredToken` / `ExpiredTokenException`. -**DiagnosticCoordinator** (`src/services/`) pushes diagnostics via `textDocument/publishDiagnostics` — it is not a request handler but a service that reacts to document changes and publishes results asynchronously. +### Telemetry (`src/telemetry/`) -### Online Handlers (require auth) +OpenTelemetry metrics exported as CloudWatch EMF. Emit metrics through: -| Handler | LSP Method | Function | -|---------|-----------|----------| -| StackDeployment | `aws/cfn/stack/deployment/*` | Create/monitor deployments | -| StackValidation | `aws/cfn/stack/validation/*` | Pre-deployment validation | -| ChangeSetHandler | `aws/cfn/stack/changeSet/*` | Preview changes | -| ResourceList | `aws/cfn/resources/list` | Live resource autocomplete | -| StackEvents | `aws/cfn/stack/events` | Deployment timeline | +- `@Telemetry({ scope: 'Foo' })` and `@Track({ name: 'method' })` decorators in + `src/telemetry/TelemetryDecorator.ts` — preferred for class methods. +- `ScopedTelemetry` helpers (`measure`, `measureAsync`, `trackExecution`, `countExecution`, …) — for inline closures + the decorators can't reach. Each emits `{Name}.count`, `{Name}.fault`, and (for `measure*` / `trackExecution*`) + `{Name}.duration`. -## Distribution +Client-side telemetry is opt-in via the `aws.telemetryEnabled` initialization option (default `false`). -### Language Server +## Handlers -- **Repo:** https://github.com/aws-cloudformation/cloudformation-languageserver -- **Build:** webpack bundles into standalone `cfn-lsp-server-standalone.js` -- **Release:** GitHub Releases (v1.x.0 prod, v1.x.0-beta, v1.x.0-alpha) -- **Manifest:** `assets/release-manifest.json` tracks latest versions +Request handlers are registered in `CfnServer.ts` (`src/server/`) as functions backed by implementation classes: -### Clients +| LSP Method | Handler function | Implementation | +|-------------------------------|-------------------------|------------------------------------------------| +| `textDocument/completion` | `completionHandler` | `CompletionRouter` (`src/autocomplete/`) | +| `textDocument/hover` | `hoverHandler` | `HoverRouter` (`src/hover/`) | +| `textDocument/definition` | `definitionHandler` | `DefinitionProvider` (`src/definition/`) | +| `textDocument/codeAction` | `codeActionHandler` | `CodeActionService` (`src/services/`) | +| `textDocument/documentSymbol` | `documentSymbolHandler` | `DocumentSymbolRouter` (`src/documentSymbol/`) | +| `textDocument/codeLens` | `codeLensHandler` | `CodeLensProvider` (`src/codeLens/`) | -The language server is bundled with the [AWS Toolkit for VS Code](https://github.com/aws/aws-toolkit-vscode) and [AWS Toolkit for JetBrains](https://github.com/aws/aws-toolkit-jetbrains). See those repositories for client-specific build/test/contribution guides. +`textDocument/publishDiagnostics` is **pushed** by `DiagnosticCoordinator` rather than served via a request handler. -## Key Dependencies +### Online Handlers (require auth) -| Dependency | Risk | -|-----------|------| -| Pyodide CDN (cdn.jsdelivr.net) | Blocked in some environments | -| CloudFormation Registry schemas | Schema changes can break validation | -| tree-sitter WASM | Parser bugs affect all features | -| AWS Toolkit VSCode extension | Client changes need coordination | -| AWS Toolkit JetBrains plugin | Client changes need coordination | +`StackHandler` (`aws/cfn/stack/*`), `ResourceHandler` (`aws/cfn/resources/*`), and the validation / change-set / +events workflows under `aws/cfn/stack/...` are wrapped with `withOnlineGuard` (`src/utils/OnlineFeatureWrapper.ts`), +which short-circuits when credentials are missing or expired. diff --git a/.kiro/steering/product.md b/.kiro/steering/product.md index 50164a84..faf30872 100644 --- a/.kiro/steering/product.md +++ b/.kiro/steering/product.md @@ -1,6 +1,8 @@ # Product Overview -The AWS CloudFormation Language Server provides intelligent editing support for CloudFormation templates in JSON and YAML. It implements the Language Server Protocol (LSP) to deliver auto-completion, validation, navigation, and refactoring capabilities to any compatible editor. +The AWS CloudFormation Language Server provides intelligent editing support for CloudFormation templates in JSON and +YAML. It implements the Language Server Protocol (LSP) to deliver auto-completion, validation, navigation, and +refactoring capabilities to any compatible editor. ## Target Users @@ -15,7 +17,3 @@ The AWS CloudFormation Language Server provides intelligent editing support for - **Documentation** — Hover docs, go-to-definition, document symbols - **AWS Integration** — Stack operations, resource discovery, template deployment - **Code Lens** — Validate/deploy actions, open stack templates - -## Distribution - -The language server is bundled with the AWS Toolkit extensions for VS Code and JetBrains. Standalone editors use a direct installation. diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md index 3d4755c9..1a22c2d0 100644 --- a/.kiro/steering/structure.md +++ b/.kiro/steering/structure.md @@ -1,49 +1,96 @@ # Project Structure -Source is organized by feature, NOT by architectural layer. +Source is organized **by feature**, not by architectural layer. Tests mirror the source tree under `tst/`. ## Source (`src/`) -- `src/app/` — Entry points (standalone.ts, initialize.ts) -- `src/autocomplete/` — Completion providers -- `src/hover/` — Hover documentation -- `src/definition/` — Go-to-definition -- `src/codeLens/` — CodeLens providers -- `src/documentSymbol/` — Document symbol/outline -- `src/handlers/` — LSP request handler wiring -- `src/context/` — Semantic model (AST, entities, intrinsics) -- `src/schema/` — CloudFormation type schemas -- `src/datastore/` — LMDB persistence layer -- `src/services/` — External services (cfnLint, guard, auth) -- `src/server/` — LSP server setup -- `src/stacks/` — Stack operations (deploy, changeset, events) -- `src/resourceState/` — Cloud Control API resource state -- `src/relatedResources/` — Related resources feature -- `src/telemetry/` — OpenTelemetry metrics -- `src/auth/` — Credential management -- `src/protocol/` — LSP protocol extensions -- `src/settings/` — Server settings -- `src/featureFlag/` — Feature flags -- `src/artifacts/` — Artifact handling -- `src/artifactexporter/` — Artifact export -- `src/s3/` — S3 operations -- `src/document/` — Document management -- `src/cfnEnvironments/` — CFN environments -- `src/usageTracker/` — Usage tracking -- `src/utils/` — Shared utilities +### Entry points + +- `src/app/` — `standalone.ts` (CLI entry), `initialize.ts`, polyfills +- `src/server/` — `CfnServer` wires LSP handlers to components; `CfnInfraCore` constructs the dependency graph + +### LSP request handlers + +These wrap implementation classes and are registered in `CfnServer.ts`: + +- `src/handlers/` — Thin handler functions (one per LSP request) +- `src/protocol/` — LSP protocol extensions and request/notification type definitions + (`AuthProtocol`, `LspStackHandlers`, `LspResourceHandlers`, etc.) + +### Feature implementations + +- `src/autocomplete/` — `CompletionRouter` + per-position completion providers +- `src/hover/` — `HoverRouter` + per-position hover providers +- `src/definition/` — `DefinitionProvider` (go-to-definition) +- `src/codeLens/` — CodeLens providers (validate / deploy / open stack template) +- `src/documentSymbol/` — `DocumentSymbolRouter` (outline view) +- `src/relatedResources/` — Related-resource snippet insertion + +### Semantic model + +- `src/context/` — Cursor context, intrinsic resolution, file-level entity index + (`ContextManager`, `FileContext`, `IntrinsicContext`) +- `src/context/syntaxtree/` — `SyntaxTreeManager`, `SyntaxTree`, tree-sitter grammars +- `src/context/semantic/` — `EntityBuilder`, `LogicalIdReferenceFinder` + +### Schema, validation, and services + +- `src/schema/` — CloudFormation type schemas, regional/SAM/private schema sources, transformers +- `src/services/cfnLint/` — `cfn-lint` integration +- `src/services/guard/` — `cfn-guard` integration +- `src/services/extractToParameter/` — "Extract to parameter" refactor +- `src/services/DiagnosticCoordinator.ts` — Merges diagnostics from all sources, debounces publish +- `src/services/CodeActionService.ts` — Quick fixes, refactors, related-resource insertions + +### Persistence + +- `src/datastore/` — Pluggable `DataStore` interface with three implementations: + - `src/datastore/lmdb/` — Default persisted store on macOS / Linux + - `src/datastore/file/` — Encrypted-file-per-key store; default on Windows or when `FileDb` + feature flag is enabled + - `src/datastore/MemoryStore.ts` — In-memory, used for non-persisted stores +- `src/utils/Storage.ts` — Resolves platform-specific storage root + +### Online features (require AWS credentials) + +- `src/auth/` — Credential receipt and storage +- `src/stacks/` — Stack list/describe/deploy, change sets, events +- `src/resourceState/` — Cloud Control API resource state, IaC import +- `src/s3/`, `src/artifacts/`, `src/artifactexporter/` — Template artifact upload to S3 + +### Cross-cutting + +- `src/document/` — `DocumentManager`, YAML/JSON parsers +- `src/settings/` — Settings parsing and subscription +- `src/featureFlag/` — Local + dynamic feature flags (e.g. `FileDb`) +- `src/cfnEnvironments/` — Multi-environment template parsing +- `src/telemetry/` — `ScopedTelemetry`, `@Telemetry` / `@Track` decorators, OpenTelemetry export +- `src/usageTracker/` — Anonymous usage metrics +- `src/utils/` — Shared utilities (paths, retries, fault suppression, AWS error mapping, etc.) ## Tests (`tst/`) Tests mirror the source structure: + - `tst/unit/` — Unit tests (Vitest) -- `tst/integration/` — Integration tests -- `tst/e2e/` — End-to-end tests -- `tst/resources/` — Test fixtures -- `tst/utils/` — Test utilities +- `tst/integration/` — Integration tests (autocomplete, context, hover, goto, diagnostics) +- `tst/e2e/` — End-to-end LSP tests +- `tst/resources/` — Test fixtures (templates, schemas, private schemas, guard rules) +- `tst/utils/` — Shared test utilities (`MockServerComponents`, `TemplateBuilder`, `MockContext`, etc.) + +## Tooling (`tools/`) + +- `tools/debug_tree.ts` — Diagnostic tool for parse/context issues (`npm run debug-tree`) +- `tools/benchmark.ts` — Latency benchmark (`npm run benchmark`) +- `tools/lspClient/` — Minimal LSP client used by tooling +- `tools/generate-release-manifest.ts`, `tools/generate-attribution.ts` — Release helpers ## Conventions -- Before writing new code, check `src/utils/` and `tst/utils/` for existing reusable methods -- If you write a method that could be reused, move it to the appropriate utils file +- **Reuse utils before writing new code.** Search `src/utils/` for shared source-side helpers and `tst/utils/` for + shared test helpers (e.g., `MockServerComponents`, `TemplateBuilder`, `MockContext`, `Expect`, `SchemaUtils`) + before introducing a new helper of your own. +- If you write a helper that could be reused, lift it into the appropriate `utils/` directory rather than leaving it + in a feature folder. - New handlers go in their own feature directory under `src/` - Tests for a feature live in the same relative path under `tst/unit/` or `tst/integration/` diff --git a/.kiro/steering/tech.md b/.kiro/steering/tech.md index 65fa7e15..3287dab2 100644 --- a/.kiro/steering/tech.md +++ b/.kiro/steering/tech.md @@ -3,50 +3,42 @@ ## Language & Runtime - **TypeScript** on Node.js -- Strict mode enabled, CommonJS modules -- Target: Node 22+ (`^22.15.0`) +- Target: Node 22+ (`^22.15.0`), npm `>=10.5.0` ## Build & Tooling - **npm** — package manager -- **Vitest** — test framework (unit + integration), coverage built-in -- **ESLint** — linting -- **Webpack** — bundling for distribution (`bundle:alpha`, `bundle:beta`, `bundle:prod`) -- **tree-sitter** — fast syntax parsing for JSON and YAML +- **TypeScript compiler** — incremental builds to `out/` via `npm run build` +- **Vitest** — test framework (unit + integration + e2e), V8 coverage +- **ESLint** — linting (zero-warning policy enforced via `--max-warnings 0`) +- **Webpack** — bundles into `cfn-lsp-server-standalone.js` for distribution ## Key Libraries -- `vscode-languageserver` / `vscode-languageserver-protocol` — LSP implementation -- `lmdb` — embedded key-value store for schema caching and persistence -- `pyodide` — Python runtime in WebAssembly (runs cfn-lint) -- `@opentelemetry/*` — telemetry instrumentation - -## Architecture Pattern - -**Component-Handler** architecture: -- **Handlers** — one per LSP request (completion, hover, definition, diagnostics) -- **Components** — internal state/logic (SyntaxTreeManager, ContextManager, SchemaStore, LMDBStore) -- **Services** — external I/O (CfnLint/Pyodide, Auth, CFN API, Telemetry) +- `vscode-languageserver` / `vscode-languageserver-protocol` — LSP runtime +- `tree-sitter` (with the JSON and YAML grammars) — incremental AST parsing +- `lmdb` — embedded key-value store (default persisted DataStore on macOS / Linux) +- `@opentelemetry/*` — telemetry instrumentation (CloudWatch EMF export) +- `pino` — structured logging ## Testing -- Framework: Vitest -- Coverage runs automatically with `npm run test` (`coverage.enabled: true` in `vitest.config.ts`) -- Coverage thresholds: 88% statements, 82% branches, 90% functions, 88% lines -- Test structure mirrors `src/` under `tst/unit/`, `tst/integration/`, `tst/e2e/` +- Framework: **Vitest** (`vitest.config.ts`) +- Coverage runs automatically with `npm run test`; thresholds are enforced from `vitest.config.ts` +- Test layout mirrors `src/` under `tst/unit/`, `tst/integration/`, `tst/e2e/` - Test fixtures in `tst/resources/` +- Shared test utilities in `tst/utils/` ## Commands ```bash -npm ci # Install dependencies -npm run build # TypeScript compilation -npm run test # Unit tests + coverage -npm run lint -- --fix # Lint and auto-fix -npm run bundle:alpha # Webpack bundle (alpha) -npm run bundle:beta # Webpack bundle (beta) -npm run bundle:prod # Webpack bundle (production) +npm ci # Install dependencies +npm run build # TypeScript compilation +npm run test # All tests + coverage +npm run lint # Lint (zero warnings) +npm run lint:fix # Lint with auto-fix +npm run bundle # Webpack bundle (development) +npm run bundle:alpha # Webpack bundle (production, alpha env) npm run debug-tree -- --file