diff --git a/docs/component-refactoring-flow.md b/docs/component-refactoring-flow.md index 508fb79..61a6a72 100644 --- a/docs/component-refactoring-flow.md +++ b/docs/component-refactoring-flow.md @@ -234,12 +234,12 @@ The rule implements a comprehensive validation process: ### Tools used - `build_component_contract` - Creates post-refactor component contract - - Parameters: `componentFile`, `dsComponentName` (set to "AUTO") + - Parameters: `saveLocation`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` - Returns: updated contract path with refactored component state - Purpose: Capture final component state for comparison - `diff_component_contract` - Compares baseline and updated contracts - - Parameters: `contractBeforePath`, `contractAfterPath`, `dsComponentName` (set to "AUTO") + - Parameters: `saveLocation`, `contractBeforePath`, `contractAfterPath`, `dsComponentName` - Returns: detailed diff analysis showing specific changes - Purpose: Identify and analyze all modifications made during refactoring diff --git a/docs/contracts.md b/docs/contracts.md index dc0f692..29a8ef7 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -65,11 +65,17 @@ Because contracts track every public-facing facet of a component, any refactor t Rules taking care about the contract building during the workflow, but if you need to build it "manually" say in the chat: ``` -build_component_contract(, dsComponentName) +build_component_contract(saveLocation, templateFile, styleFile, typescriptFile, dsComponentName) ``` -> Replace `` with the path to your component and set `dsComponentName` to the design-system component (e.g., `DsBadge`). The tool analyses the template, TypeScript, and styles, then saves a timestamped `*.contract.json` to -> `.cursor/tmp/contracts//`. +> Replace the parameters with: +> - `saveLocation`: Path where to save the contract file (supports absolute and relative paths) +> - `templateFile`: Path to the component template file (.html or .ts for inline) +> - `styleFile`: Path to the component style file (.scss, .css, etc.) +> - `typescriptFile`: Path to the TypeScript component file (.ts) +> - `dsComponentName`: Optional design system component name (e.g., `DsBadge`) +> +> The tool analyses the template, TypeScript, and styles, then saves the contract to your specified location. ## When to Build a Contract @@ -160,10 +166,13 @@ What happens when QA finds a bug or a reviewer requests changes **after** the in 3. **Locate the original baseline contract** – this is the contract that was captured for the initial state (usually the very first timestamp in the folder). 4. **Generate a diff** between the baseline and the latest contract: ``` - User: diff_component_contract(.contract.json, .contract.json, dsComponentName) + User: diff_component_contract(saveLocation, contractBeforePath, contractAfterPath, dsComponentName) ``` - The diff file will land under - `.cursor/tmp/contracts//diffs/`. + > Replace the parameters with: + > - `saveLocation`: Path where to save the diff result file (supports absolute and relative paths) + > - `contractBeforePath`: Path to the baseline contract file + > - `contractAfterPath`: Path to the latest contract file + > - `dsComponentName`: Optional design system component name 5. **Review the diff output using AI** – attach the diff and ask it to analyze it. * If only intentional changes appear, proceed to merge / re-test. * If unexpected API, DOM, or style changes surface, iterate on the fix and repeat steps 1-4. diff --git a/docs/ds-refactoring-flow.md b/docs/ds-refactoring-flow.md index a9ffa8e..b6427eb 100644 --- a/docs/ds-refactoring-flow.md +++ b/docs/ds-refactoring-flow.md @@ -566,7 +566,7 @@ This is your last chance to make changes before opening the pull request. - Features: Automatic ESLint config resolution, comprehensive rule coverage - `build_component_contract` - Creates contracts for refactored components - - Parameters: `directory`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` + - Parameters: `saveLocation`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` - Returns: JSON contract with public API, DOM structure, and styles - Purpose: Capture post-refactoring component state @@ -576,9 +576,9 @@ This is your last chance to make changes before opening the pull request. - Purpose: Identify before/after contract pairs for comparison - `diff_component_contract` - Compares component contracts - - Parameters: `directory`, `contractBeforePath`, `contractAfterPath`, `dsComponentName` + - Parameters: `saveLocation`, `contractBeforePath`, `contractAfterPath`, `dsComponentName` - Returns: Detailed diff highlighting changes in API, DOM, and styles - - Saves: Diff files to `.cursor/tmp/contracts//diffs/` + - Saves: Diff files to the specified saveLocation path ### Flow diff --git a/docs/tools.md b/docs/tools.md index 2fc1074..3756c91 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -113,10 +113,11 @@ This document provides comprehensive guidance for AI agents working with Angular **Purpose**: Creates static surface contracts for component templates and styles **AI Usage**: Generate contracts before refactoring to track breaking changes **Key Parameters**: -- `directory`: Component directory +- `saveLocation`: Path where to save the contract file (supports absolute and relative paths) - `templateFile`: Template file name (.html or .ts for inline) - `styleFile`: Style file name (.scss, .css, etc.) - `typescriptFile`: TypeScript component file (.ts) +- `dsComponentName`: Optional design system component name **Output**: Component contract file with API surface **Best Practice**: Create contracts before major refactoring for comparison @@ -124,9 +125,10 @@ This document provides comprehensive guidance for AI agents working with Angular **Purpose**: Compares before/after contracts to identify breaking changes **AI Usage**: Validate that refactoring doesn't introduce breaking changes **Key Parameters**: -- `directory`: Component directory +- `saveLocation`: Path where to save the diff result file (supports absolute and relative paths) - `contractBeforePath`: Path to pre-refactoring contract - `contractAfterPath`: Path to post-refactoring contract +- `dsComponentName`: Optional design system component name **Output**: Diff analysis showing breaking changes **Best Practice**: Essential validation step after component modifications diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts index e76ea7f..24a11d9 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts @@ -4,19 +4,16 @@ import { } from '../../shared/utils/handler-helpers.js'; import { buildComponentContractSchema } from './models/schema.js'; import { buildComponentContract } from './utils/build-contract.js'; -import { - saveContract, - generateContractSummary, -} from '../shared/utils/contract-file-ops.js'; +import { generateContractSummary } from '../shared/utils/contract-file-ops.js'; import { ContractResult } from './models/types.js'; import { resolveCrossPlatformPath } from '../../shared/utils/cross-platform-path.js'; interface BuildComponentContractOptions extends BaseHandlerOptions { - directory: string; + saveLocation: string; templateFile: string; styleFile: string; typescriptFile: string; - dsComponentName: string; + dsComponentName?: string; } export const buildComponentContractHandler = createHandler< @@ -24,22 +21,19 @@ export const buildComponentContractHandler = createHandler< ContractResult >( buildComponentContractSchema.name, - async (params, { cwd, workspaceRoot }) => { + async (params, { cwd, workspaceRoot: _workspaceRoot }) => { const { - directory, + saveLocation, templateFile, styleFile, typescriptFile, - dsComponentName, + dsComponentName = '', } = params; - const effectiveTemplatePath = resolveCrossPlatformPath( - directory, - templateFile, - ); - const effectiveScssPath = resolveCrossPlatformPath(directory, styleFile); + const effectiveTemplatePath = resolveCrossPlatformPath(cwd, templateFile); + const effectiveScssPath = resolveCrossPlatformPath(cwd, styleFile); const effectiveTypescriptPath = resolveCrossPlatformPath( - directory, + cwd, typescriptFile, ); @@ -50,18 +44,41 @@ export const buildComponentContractHandler = createHandler< effectiveTypescriptPath, ); - const { contractFilePath, hash } = await saveContract( + const contractString = JSON.stringify(contract, null, 2); + const hash = require('node:crypto') + .createHash('sha256') + .update(contractString) + .digest('hex'); + + const effectiveSaveLocation = resolveCrossPlatformPath(cwd, saveLocation); + + const { mkdir, writeFile } = await import('node:fs/promises'); + const { dirname } = await import('node:path'); + await mkdir(dirname(effectiveSaveLocation), { recursive: true }); + + const contractData = { contract, - workspaceRoot, - effectiveTemplatePath, - effectiveScssPath, - cwd, - dsComponentName, + hash: `sha256-${hash}`, + metadata: { + templatePath: effectiveTemplatePath, + scssPath: effectiveScssPath, + typescriptPath: effectiveTypescriptPath, + timestamp: new Date().toISOString(), + dsComponentName, + }, + }; + + await writeFile( + effectiveSaveLocation, + JSON.stringify(contractData, null, 2), + 'utf-8', ); + const contractFilePath = effectiveSaveLocation; + return { contract, - hash, + hash: `sha256-${hash}`, contractFilePath, }; }, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts index bb98b1a..0dc5f7f 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts @@ -1,8 +1,5 @@ import { ToolSchemaOptions } from '@push-based/models'; -import { - COMMON_ANNOTATIONS, - createProjectAnalysisSchema, -} from '../../../shared'; +import { COMMON_ANNOTATIONS } from '../../../shared'; /** * Schema for building component contracts @@ -12,33 +9,35 @@ export const buildComponentContractSchema: ToolSchemaOptions = { description: "Generate a static surface contract for a component's template and SCSS.", inputSchema: { - ...createProjectAnalysisSchema({ + type: 'object', + properties: { + saveLocation: { + type: 'string', + description: + 'Path where to save the contract file. Supports both absolute and relative paths.', + }, templateFile: { type: 'string', description: - 'File name of the component template file (.html) or TypeScript component file (.ts) for inline templates', + 'Path to the component template file (.html) or TypeScript component file (.ts) for inline templates. Supports both absolute and relative paths.', }, styleFile: { type: 'string', description: - 'File name of the component style file (.scss, .sass, .less, .css)', + 'Path to the component style file (.scss, .sass, .less, .css). Supports both absolute and relative paths.', }, typescriptFile: { type: 'string', - description: 'File name of the TypeScript component file (.ts)', + description: + 'Path to the TypeScript component file (.ts). Supports both absolute and relative paths.', }, dsComponentName: { type: 'string', description: 'The name of the design system component being used', + default: '', }, - }), - required: [ - 'directory', - 'templateFile', - 'styleFile', - 'typescriptFile', - 'dsComponentName', - ], + }, + required: ['saveLocation', 'templateFile', 'styleFile', 'typescriptFile'], }, annotations: { title: 'Build Component Contract', diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/diff-component-contract.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/diff-component-contract.tool.ts index eec13a0..92d16db 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/diff-component-contract.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/diff-component-contract.tool.ts @@ -9,8 +9,6 @@ import { import { diffComponentContractSchema } from './models/schema.js'; import type { DomPathDictionary } from '../shared/models/types.js'; import { loadContract } from '../shared/utils/contract-file-ops.js'; -import { componentNameToKebabCase } from '../../shared/utils/component-validation.js'; -import { basename } from 'node:path'; import { consolidateAndPruneRemoveOperationsWithDeduplication, groupChangesByDomainAndType, @@ -20,10 +18,10 @@ import { writeFile, mkdir } from 'node:fs/promises'; import diff from 'microdiff'; interface DiffComponentContractOptions extends BaseHandlerOptions { - directory: string; + saveLocation: string; contractBeforePath: string; contractAfterPath: string; - dsComponentName: string; + dsComponentName?: string; } export const diffComponentContractHandler = createHandler< @@ -34,15 +32,19 @@ export const diffComponentContractHandler = createHandler< } >( diffComponentContractSchema.name, - async (params, { workspaceRoot }) => { + async (params, { cwd, workspaceRoot }) => { + const { + saveLocation, + contractBeforePath, + contractAfterPath, + dsComponentName = '', + } = params; + const effectiveBeforePath = resolveCrossPlatformPath( - params.directory, - params.contractBeforePath, - ); - const effectiveAfterPath = resolveCrossPlatformPath( - params.directory, - params.contractAfterPath, + cwd, + contractBeforePath, ); + const effectiveAfterPath = resolveCrossPlatformPath(cwd, contractAfterPath); const contractBefore = await loadContract(effectiveBeforePath); const contractAfter = await loadContract(effectiveAfterPath); @@ -57,35 +59,21 @@ export const diffComponentContractHandler = createHandler< const diffData = { before: effectiveBeforePath, after: effectiveAfterPath, - dsComponentName: params.dsComponentName, + dsComponentName, timestamp: new Date().toISOString(), domPathDictionary: domPathDict.paths, changes: groupedChanges, summary: generateDiffSummary(processedResult, groupedChanges), }; - // Normalize absolute paths to relative paths for portability const normalizedDiffData = normalizePathsInObject(diffData, workspaceRoot); - // Create component-specific diffs directory - const componentKebab = componentNameToKebabCase(params.dsComponentName); - const diffDir = resolveCrossPlatformPath( - workspaceRoot, - `.cursor/tmp/contracts/${componentKebab}/diffs`, - ); - await mkdir(diffDir, { recursive: true }); + const effectiveSaveLocation = resolveCrossPlatformPath(cwd, saveLocation); + + const { dirname } = await import('node:path'); + await mkdir(dirname(effectiveSaveLocation), { recursive: true }); - // Generate simplified diff filename: diff-{componentName}-{timestamp}.json - const componentBaseName = basename( - effectiveBeforePath, - '.contract.json', - ).split('-')[0]; // Extract component name before timestamp - const timestamp = new Date() - .toISOString() - .replace(/[-:]/g, '') - .replace(/\.\d+Z$/, 'Z'); - const diffFileName = `diff-${componentBaseName}-${timestamp}.json`; - const diffFilePath = resolveCrossPlatformPath(diffDir, diffFileName); + const diffFilePath = effectiveSaveLocation; const formattedJson = JSON.stringify(normalizedDiffData, null, 2); await writeFile(diffFilePath, formattedJson, 'utf-8'); diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/models/schema.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/models/schema.ts index e3c3f34..7a33220 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/models/schema.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/models/schema.ts @@ -1,8 +1,5 @@ import { ToolSchemaOptions } from '@push-based/models'; -import { - createProjectAnalysisSchema, - COMMON_ANNOTATIONS, -} from '../../../shared'; +import { COMMON_ANNOTATIONS } from '../../../shared'; /** * Schema for diffing component contracts @@ -12,26 +9,30 @@ export const diffComponentContractSchema: ToolSchemaOptions = { description: 'Compare before/after contracts for parity and surface breaking changes.', inputSchema: { - ...createProjectAnalysisSchema({ + type: 'object', + properties: { + saveLocation: { + type: 'string', + description: + 'Path where to save the diff result file. Supports both absolute and relative paths.', + }, contractBeforePath: { type: 'string', - description: 'Path to the contract file before refactoring', + description: + 'Path to the contract file before refactoring. Supports both absolute and relative paths.', }, contractAfterPath: { type: 'string', - description: 'Path to the contract file after refactoring', + description: + 'Path to the contract file after refactoring. Supports both absolute and relative paths.', }, dsComponentName: { type: 'string', description: 'The name of the design system component being used', + default: '', }, - }), - required: [ - 'directory', - 'contractBeforePath', - 'contractAfterPath', - 'dsComponentName', - ], + }, + required: ['saveLocation', 'contractBeforePath', 'contractAfterPath'], }, annotations: { title: 'Diff Component Contract',