Repository: Azure/azure-rest-api-specs
Pull request #34574: [LintDiff] Dependencies
Author: login: danieljurek
Labels: name: Approved-Avocado
name: PipelineBotTrigger
Created: 2025-05-09T21:49:07Z | Merged: 2025-05-21T19:07:26Z

Finish removing dependencies, many can be replaced with SpecModel

Example success run: https://github.com/Azure/azure-rest-api-specs/actions/runs/15123470540?pr=34574

Example error run: https://github.com/Azure/azure-rest-api-specs/actions/runs/15124377833?pr=34574

Example autorest fatal error run: https://github.com/Azure/azure-rest-api-specs/actions/runs/15124508223?pr=34574


Diff:
diff --git a/eng/tools/lint-diff/package.json b/eng/tools/lint-diff/package.json
index 36a44ff40356..7e2cdb82c12a 100644
--- a/eng/tools/lint-diff/package.json
+++ b/eng/tools/lint-diff/package.json
@@ -19,18 +19,14 @@
     "node": ">= 20.0.0"
   },
   "dependencies": {
-    "@apidevtools/json-schema-ref-parser": "^9.0.9",
-    "@azure-tools/openapi-tools-common": "^1.2.2",
+    "@apidevtools/json-schema-ref-parser": "^11.9.3",
     "@azure-tools/specs-shared": "file:../../../.github/shared",
-    "@azure/openapi-markdown": "^0.9.4",
     "@microsoft.azure/openapi-validator": "2.2.4",
-    "@types/js-yaml": "^3.12.10",
     "autorest": "3.6.1",
     "axios": "^1.8.3",
     "change-case": "^5.4.4",
-    "commonmark": "^0.31.2",
     "deep-eql": "^5.0.2",
-    "js-yaml": "^3.14.1"
+    "marked": "^15.0.7"
   },
   "devDependencies": {
     "@types/deep-eql": "^4.0.2",
diff --git a/eng/tools/lint-diff/src/correlateResults.ts b/eng/tools/lint-diff/src/correlateResults.ts
index 0f3c53013a15..d69e947ba227 100644
--- a/eng/tools/lint-diff/src/correlateResults.ts
+++ b/eng/tools/lint-diff/src/correlateResults.ts
@@ -1,8 +1,8 @@
-import { basename, join } from "path";
-import { readFile } from "fs/promises";
+import { basename, join, relative } from "path";
 import { relativizePath, pathExists, isFailure, isWarning } from "./util.js";
 import { AutorestRunResult, BeforeAfter, LintDiffViolation, Source } from "./lintdiff-types.js";
 import { getDefaultTag } from "./markdown-utils.js";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 export async function correlateRuns(
   beforePath: string,
@@ -12,8 +12,10 @@ export async function correlateRuns(
   const runCorrelations = new Map<string, BeforeAfter>();
   console.log("\nCorrelating runs...");
   for (const results of afterChecks) {
-    const { readme, tag } = results;
-    const key = tag ? `${readme}#${tag}` : readme;
+    const { rootPath, readme, tag } = results;
+    const readmePathRelative = relative(rootPath, readme.path);
+
+    const key = tag ? `${readmePathRelative}#${tag}` : readmePathRelative;
     if (runCorrelations.has(key)) {
       throw new Error(`Duplicate key found correlating autorest runs: ${key}`);
     }
@@ -31,10 +33,10 @@ export async function correlateRuns(
     }
 
     // Look for candidates with a matching default tag from the baseline
-    const beforeReadmePath = join(beforePath, readme);
+    const beforeReadmePath = join(beforePath, readmePathRelative);
     if (await pathExists(beforeReadmePath)) {
-      const readmeContent = await readFile(beforeReadmePath, { encoding: "utf-8" });
-      const defaultTag = getDefaultTag(readmeContent);
+      const beforeReadme = new Readme(beforeReadmePath); 
+      const defaultTag = await getDefaultTag(beforeReadme);
       if (!defaultTag) {
         throw new Error(`No default tag found for readme ${readme} in before state`);
       }
@@ -100,9 +102,13 @@ export function getViolations(
     );
 
     const [newItems, existingItems] = getNewItems(beforeViolations, afterViolations);
+
+    const beforeReadmePath = before ? relative(before?.rootPath, before?.readme.path) : "";
+    const afterReadmePath = relative(after.rootPath, after.readme.path);
+
     console.log("Correlation:");
-    console.log(`\tBefore: Readme: ${before?.readme} Tag: ${before?.tag}`);
-    console.log(`\tAfter: Readme : ${after.readme} Tag: ${after.tag}`);
+    console.log(`\tBefore: Readme: ${beforeReadmePath} Tag: ${before?.tag}`);
+    console.log(`\tAfter: Readme : ${afterReadmePath} Tag: ${after.tag}`);
 
     newViolations.push(...newItems);
     existingViolations.push(...existingItems);
diff --git a/eng/tools/lint-diff/src/generateReport.ts b/eng/tools/lint-diff/src/generateReport.ts
index a7d59317a29c..f672a93f59b7 100644
--- a/eng/tools/lint-diff/src/generateReport.ts
+++ b/eng/tools/lint-diff/src/generateReport.ts
@@ -1,4 +1,5 @@
 import { writeFile } from "node:fs/promises";
+import { relative } from "node:path";
 import { kebabCase } from "change-case";
 import { getRelatedArmRpcFromDoc } from "./markdown-utils.js";
 import { getPathToDependency, getDependencyVersion, relativizePath } from "./util.js";
@@ -20,7 +21,6 @@ export async function generateLintDiffReport(
   baseBranch: string,
   compareSha: string,
 ): Promise<boolean> {
-  
   console.log("Generating LintDiff report...");
 
   let pass = true;
@@ -78,7 +78,7 @@ export async function generateLintDiffReport(
       // New violations with level error or fatal fail the build. If all new
       // violations are warnings, the build passes.
       pass = false;
-    } else { 
+    } else {
       console.log("\t✅ No new violations with error or fatal level. LintDiff will pass.");
     }
 
@@ -137,7 +137,9 @@ export async function generateAutoRestErrorReport(
   for (const { result, errors } of autoRestErrors) {
     console.log(`AutoRest errors for ${result.readme} (${result.tag})`);
 
-    outputMarkdown += `Readme: \`${result.readme}\`\n`;
+    const readmePath = relative(result.rootPath, result.readme.path);
+
+    outputMarkdown += `Readme: \`${readmePath}\`\n`;
     outputMarkdown += `Tag: \`${result.tag}\`\n`;
     outputMarkdown += "Errors:\n";
     outputMarkdown += "| Level | Message |\n";
@@ -262,6 +264,7 @@ export function getName(result: AutorestRunResult) {
 }
 
 export function getPath(result: AutorestRunResult) {
-  const { readme, tag } = result;
-  return tag ? `${readme}#tag-${tag}` : readme;
+  const { rootPath, readme, tag } = result;
+  const readmePathRelative = relative(rootPath, readme.path);
+  return tag ? `${readmePathRelative}#tag-${tag}` : readmePathRelative;
 }
diff --git a/eng/tools/lint-diff/src/lint-diff.ts b/eng/tools/lint-diff/src/lint-diff.ts
index 034abd3e47eb..49011ec8b3c5 100644
--- a/eng/tools/lint-diff/src/lint-diff.ts
+++ b/eng/tools/lint-diff/src/lint-diff.ts
@@ -1,5 +1,5 @@
 import { parseArgs, ParseArgsConfig } from "node:util";
-import { pathExists } from "./util.js";
+import { pathExists, getDependencyVersion, getPathToDependency } from "./util.js";
 import { getRunList } from "./processChanges.js";
 import { runChecks, getAutorestErrors } from "./runChecks.js";
 import { correlateRuns } from "./correlateResults.js";
@@ -79,14 +79,10 @@ export async function main() {
     process.exit(1);
   }
 
-  // const versionResult = await executeCommand("npm exec -- autorest --version");
-  // if (versionResult.error) {
-  //   console.error("Error running autorest --version", versionResult.error);
-  //   process.exit(1);
-  // }
-
-  // console.log("Autorest version:");
-  // console.log(versionResult.stdout);
+  const validatorVersion = await getDependencyVersion(
+    await getPathToDependency("@microsoft.azure/openapi-validator"),
+  );
+  console.log(`Using @microsoft.azure/openapi-validator version: ${validatorVersion}\n`);
 
   await runLintDiff(
     beforeArg as string,
@@ -153,13 +149,9 @@ async function runLintDiff(
     console.error(`Lint-diff failed. See workflow summary report in ${outFile} for details.`);
   }
 
-  if (
-    process.env.GITHUB_SERVER_URL &&
-    process.env.GITHUB_REPOSITORY &&
-    process.env.GITHUB_RUN_ID
-  ) {
+  if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID) {
     console.log(
-    `See workflow summary at: ${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
+      `See workflow summary at: ${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,
     );
   }
 }
diff --git a/eng/tools/lint-diff/src/lintdiff-types.ts b/eng/tools/lint-diff/src/lintdiff-types.ts
index 8dcab6c3c5da..6515cbe3c760 100644
--- a/eng/tools/lint-diff/src/lintdiff-types.ts
+++ b/eng/tools/lint-diff/src/lintdiff-types.ts
@@ -1,10 +1,10 @@
+import { Readme } from "@azure-tools/specs-shared/readme";
 import { ExecException } from "node:child_process";
 
 // TODO: Reduce to minimal set of properties
 export type AutorestRunResult = {
   rootPath: string;
-
-  readme: string;
+  readme: Readme;
   tag: string;
 
   error: ExecException | null;
@@ -56,3 +56,8 @@ export type LintDiffViolation = LintingResultMessage & {
   lineNumber?: number;
   armRpcs?: string[];
 };
+
+export type ReadmeAffectedTags = {
+  readme: Readme;
+  changedTags: Set<string>;
+};
diff --git a/eng/tools/lint-diff/src/markdown-utils.ts b/eng/tools/lint-diff/src/markdown-utils.ts
index 0d3ac8ec2a9f..95e1913ec90a 100644
--- a/eng/tools/lint-diff/src/markdown-utils.ts
+++ b/eng/tools/lint-diff/src/markdown-utils.ts
@@ -1,13 +1,7 @@
-import * as commonmark from "commonmark";
-import { readFile } from "fs/promises";
-
-// TODO: Can this be eliminated?
-import { parseMarkdown } from "@azure-tools/openapi-tools-common";
-// TODO: Can this be eliminated?
-import * as amd from "@azure/openapi-markdown";
+import { marked } from "marked";
 import { kebabCase } from "change-case";
 import axios from "axios";
-import * as YAML from "js-yaml";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 export enum MarkdownType {
   Arm = "arm",
@@ -17,39 +11,23 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
+  const openapiType = (await readme.getGlobalConfig() as { "openapi-type"?: string })["openapi-type"];
+  if (openapiType && Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
+    return openapiType as MarkdownType;
   }
 
+
   // Fallback, no openapi-type found in the file. Look at path to determine type
   // resource-manager: Arm
   // data-plane: DataPlane
-  if (markdownFile.match(/.*specification\/.*\/resource-manager\/.*readme.md$/g)) {
+  if (readme.path.match(/.*specification\/.*\/resource-manager\/.*readme.md$/g)) {
     return MarkdownType.Arm;
-  } else if (markdownFile.match(/.*specification\/.*\/data-plane\/.*readme.md$/g)) {
+  } else if (readme.path.match(/.*specification\/.*\/data-plane\/.*readme.md$/g)) {
     return MarkdownType.DataPlane;
   }
 
@@ -57,51 +35,8 @@ export async function getOpenapiType(markdownFile: string): Promise<MarkdownType
   return MarkdownType.Default;
 }
 
-// TODO: Direct copy/paste from utils, factor appropriately
-function* parseCodeblocks(markdown: string): Iterable<commonmark.Node> {
-  const parsed = parseCommonmark(markdown);
-  const walker = parsed.walker();
-  let event;
-  while ((event = walker.next())) {
-    const node = event.node;
-    if (event.entering && node.type === "code_block") {
-      yield node;
-    }
-  }
-}
-
-// TODO: Direct copy/paste from utils, factor appropriately
-function parseCommonmark(markdown: string): commonmark.Node {
-  return new commonmark.Parser().parse(markdown);
-}
-
-/**
- * Returns all tags from the given readme document
- * @param readMeContent
- * @returns
- */
-export function getAllTags(readMeContent: string): string[] {
-  const cmd = parseMarkdown(readMeContent);
-  const allTags = new amd.ReadMeManipulator(
-    { error: (_msg: string) => {} },
-    new amd.ReadMeBuilder(),
-  ).getAllTags(cmd);
-  return [...allTags];
-}
-
 type TagInputFile = { tagName: string; inputFiles: readonly string[] };
 
-export function getTagsAndInputFiles(tags: string[], readmeContent: string): TagInputFile[] {
-  const tagResults: TagInputFile[] = [];
-  for (const tag of tags) {
-    const inputFiles = getInputFiles(readmeContent, tag);
-    if (inputFiles.length > 0) {
-      tagResults.push({ tagName: tag, inputFiles });
-    }
-  }
-  return tagResults;
-}
-
 /**
  * Given a list of tags and the content of a readme file, remove tags that are
  * subsets of other tags (reduces number of times autorest is called).
@@ -125,12 +60,6 @@ export function deduplicateTags(tagInfo: TagInputFile[]) {
     .map((tag) => tag.tagName);
 }
 
-export function getInputFiles(readMeContent: string, tag: string): readonly string[] {
-  const cmd = parseMarkdown(readMeContent);
-  const inputFiles = amd.getInputFilesForTag(cmd.markDown, tag);
-  return inputFiles || [];
-}
-
 export function getDocRawUrl(code: string) {
   if (code == "FATAL") {
     return `N/A`;
@@ -143,7 +72,6 @@ export function getDocRawUrl(code: string) {
 
 const rpcInfoCache = new Map<string, string[]>();
 
-// TODO: Tests
 export async function getRelatedArmRpcFromDoc(ruleName: string): Promise<string[]> {
   if (ruleName == "FATAL") {
     return [];
@@ -164,60 +92,34 @@ export async function getRelatedArmRpcFromDoc(ruleName: string): Promise<string[
     return rpcRules;
   }
 
-  let walker = new commonmark.Parser().parse(response.data).walker();
-  let event;
-  while ((event = walker.next())) {
-    const node = event.node;
+  // Use marked to parse the markdown and extract the related ARM guideline codes
+  const tokens = marked.lexer(response.data);
+  for (let i = 0; i < tokens.length; i++) {
+    const token = tokens[i];
     if (
-      event.entering &&
-      node.type === "heading" &&
-      node.firstChild?.literal?.toLowerCase() === "related arm guideline code"
+      token.type === "heading" &&
+      token.depth >= 1 &&
+      token.text.trim().toLowerCase() === "related arm guideline code"
     ) {
-      const next = node.next;
-      if (next?.type == "list") {
-        let currentItem = next.firstChild;
-        while (currentItem) {
-          const code = currentItem?.firstChild?.firstChild?.literal;
-          if (code) {
-            rpcRules.push(...code.split(",").map((c) => c.trim()));
+      // The next token should be a list
+      const next = tokens[i + 1];
+      if (next && next.type === "list" && Array.isArray(next.items)) {
+        for (const item of next.items) {
+          // item.text may contain comma-separated codes
+          if (typeof item.text === "string") {
+            rpcRules.push(...item.text.split(",").map((c: string) => c.trim()));
           }
-          currentItem = currentItem.next;
         }
       }
       break;
     }
   }
+
   rpcInfoCache.set(ruleName, rpcRules);
   return rpcRules;
 }
 
-export function getDefaultTag(markdownContent: string): string {
-  const parsed = parseMarkdown(markdownContent);
-  const startNode = parsed.markDown;
-  const codeBlockMap = amd.getCodeBlocksAndHeadings(startNode);
-
-  const latestHeader = "Basic Information";
-
-  const lh = codeBlockMap[latestHeader];
-  if (lh) {
-    const latestDefinition = YAML.load(lh.literal!, { schema: YAML.FAILSAFE_SCHEMA }) as undefined | { tag: string };
-    if (latestDefinition) {
-      return latestDefinition.tag;
-    }
-  } else {
-    for (let idx of Object.keys(codeBlockMap)) {
-      const lh = codeBlockMap[idx];
-      if (!lh || !lh.info || lh.info.trim().toLocaleLowerCase() !== "yaml") {
-        continue;
-      }
-      const latestDefinition = YAML.load(lh.literal!, { schema: YAML.FAILSAFE_SCHEMA }) as
-        | undefined
-        | { tag: string };
-
-      if (latestDefinition) {
-        return latestDefinition.tag;
-      }
-    }
-  }
-  return "";
+export async function getDefaultTag(readme: Readme): Promise<string> { 
+  const tag = (await readme.getGlobalConfig() as { tag?: string }).tag;
+  return tag ? tag : "";
 }
diff --git a/eng/tools/lint-diff/src/processChanges.ts b/eng/tools/lint-diff/src/processChanges.ts
index 9a2941a52d92..ee8b5f5091d1 100644
--- a/eng/tools/lint-diff/src/processChanges.ts
+++ b/eng/tools/lint-diff/src/processChanges.ts
@@ -2,20 +2,19 @@ import { join, relative, resolve, sep } from "path";
 import { readFile } from "fs/promises";
 import { pathExists } from "./util.js";
 import { specification, readme, swagger } from "@azure-tools/specs-shared/changed-files";
-import { SpecModel } from "@azure-tools/specs-shared/spec-model"
-import deepEqual from "deep-eql"
+import { SpecModel } from "@azure-tools/specs-shared/spec-model";
+import { ReadmeAffectedTags } from "./lintdiff-types.js";
+import deepEqual from "deep-eql";
 
-import {
-  getTagsAndInputFiles,
-  deduplicateTags,
-} from "./markdown-utils.js";
+import { deduplicateTags } from "./markdown-utils.js";
 import $RefParser from "@apidevtools/json-schema-ref-parser";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 export async function getRunList(
   beforePath: string,
   afterPath: string,
   changedFilesPath: string,
-): Promise<[Map<string, string[]>, Map<string, string[]>, Set<string>]> {
+): Promise<[Map<string, ReadmeAffectedTags>, Map<string, ReadmeAffectedTags>, Set<string>]> {
   // Forward slashes are OK list coming from changedFilesPath is from git which
   // always uses forward slashes as path separators
 
@@ -38,24 +37,36 @@ export async function getRunList(
 
   // In the future, the loop involving [beforePath, afterPath] can be eliminated
   // as well as beforeState
-  const [beforeState, ] = await buildState(changedSpecFiles, beforePath);
+  const [beforeState] = await buildState(changedSpecFiles, beforePath);
   const [afterState, afterSwaggers] = await buildState(changedSpecFiles, afterPath);
   const affectedSwaggerCandidates = new Set<string>(afterSwaggers);
   const [beforeTagMap, afterTagMap] = reconcileChangedFilesAndTags(beforeState, afterState);
 
-
-  const affectedSwaggers = await getChangedSwaggers(beforePath, afterPath, affectedSwaggerCandidates);
+  const affectedSwaggers = await getChangedSwaggers(
+    beforePath,
+    afterPath,
+    affectedSwaggerCandidates,
+  );
 
   console.log("Before readme and tags:");
-  console.table([...beforeTagMap].map(([readme, tags]) => ({ readme, tags })), ['readme', 'tags']);
+  console.table(
+    [...beforeTagMap].map(([readme, tags]) => ({ readme, tags: [...tags.changedTags] })),
+    ["readme", "tags"],
+  );
   console.log("\n");
 
   console.log("After readme and tags:");
-  console.table([...afterTagMap].map(([readme, tags]) => ({ readme, tags })), ['readme', 'tags']);
+  console.table(
+    [...afterTagMap].map(([readme, tags]) => ({ readme, tags: [...tags.changedTags] })),
+    ["readme", "tags"],
+  );
   console.log("\n");
 
-  console.log("Affected swaggers:"); 
-  console.table([...affectedSwaggers].map((swagger) => ({ swagger })), ['swagger']);
+  console.log("Affected swaggers:");
+  console.table(
+    [...affectedSwaggers].map((swagger) => ({ swagger })),
+    ["swagger"],
+  );
   console.log("\n");
 
   return [beforeTagMap, afterTagMap, affectedSwaggers];
@@ -64,7 +75,7 @@ export async function getRunList(
 export async function buildState(
   changedSpecFiles: string[],
   rootPath: string,
-): Promise<[Map<string, string[]>, string[]]> {
+): Promise<[Map<string, ReadmeAffectedTags>, string[]]> {
   // Filter changed files to include only those that exist in the rootPath
   const existingChangedFiles = [];
   for (const file of changedSpecFiles) {
@@ -84,45 +95,61 @@ export async function buildState(
     specModels.set(serviceDir, specModel);
   }
 
-  const readmeTags = new Map<string, Set<string>>();
+  // Build a map of readme.md files and tags affected by the changed files
+  const readmeTags = new Map<string, ReadmeAffectedTags>();
   for (const changedSwagger of existingChangedFiles.filter(swagger)) {
-    const readmeTagMapForChangedFile = 
-      await specModels.get(getService(changedSwagger))!.getAffectedReadmeTags(resolve(rootPath, changedSwagger));
-    
-    for (const [readmePath, tags] of readmeTagMapForChangedFile) {
-      if (!readmeTags.has(readmePath)) {
-        readmeTags.set(readmePath, new Set<string>());
-      }
-      for (const tagName of tags.keys()) {
-        readmeTags.get(readmePath)?.add(tagName);
+    const specModel = specModels.get(getService(changedSwagger))!;
+    const affectedReadmes = await specModel.getAffectedReadmeTags(
+      resolve(rootPath, changedSwagger),
+    );
+
+    for (const [readmePath, tags] of affectedReadmes) {
+      const affectedTags = readmeTags.get(readmePath) ?? {
+        readme: (await specModel.getReadmes()).get(readmePath)!,
+        changedTags: new Set(),
+      };
+      for (const [tagName,] of tags) {
+        affectedTags.changedTags.add(tagName);
       }
+      readmeTags.set(readmePath, affectedTags);
     }
   }
 
-  const changedFileAndTagsMap = new Map<string, string[]>();
+  // Deduplicate tags in readme files
+  const changedFileAndTagsMap = new Map<string, ReadmeAffectedTags>();
   for (const [readmeFile, tags] of readmeTags.entries()) {
-    const dedupedTags = deduplicateTags(
-      await getTagsAndInputFiles(
-        [...tags],
-        await readFile(readmeFile, { encoding: "utf-8" }),
-      ),
-    );
+    const tagMap = await tags.readme.getTags();
+    const tagsAndInputFiles = [...tags.changedTags].map(changedTag => {
+      return { 
+        tagName: changedTag,
+        inputFiles: [...tagMap.get(changedTag)!.inputFiles.keys()],
+      }
+    });
 
-    changedFileAndTagsMap.set(relative(rootPath, readmeFile), dedupedTags);
+    const dedupedTags = deduplicateTags(tagsAndInputFiles);
+    changedFileAndTagsMap.set(relative(rootPath, readmeFile), {
+      readme: tags.readme,
+      changedTags: new Set<string>(dedupedTags),
+    });
   }
 
   // For readme files that have changed but there are no affected swaggers,
   // add them to the map with no tags
   for (const changedReadme of existingChangedFiles.filter(readme)) {
     if (!changedFileAndTagsMap.has(changedReadme)) {
-      changedFileAndTagsMap.set(changedReadme, []);
+      changedFileAndTagsMap.set(changedReadme, {
+        readme: new Readme(changedReadme),
+        changedTags: new Set<string>(),
+      });
     }
   }
 
   const affectedSwaggers = new Set<string>();
   for (const changedSwagger of existingChangedFiles.filter(swagger)) {
     const service = getService(changedSwagger);
-    const swaggerSet = await specModels.get(service)!.getAffectedSwaggers(resolve(rootPath, changedSwagger));
+    const swaggerSet = await specModels
+      .get(service)!
+      .getAffectedSwaggers(resolve(rootPath, changedSwagger));
     for (const swaggerPath of swaggerSet.keys()) {
       affectedSwaggers.add(relative(rootPath, swaggerPath));
     }
@@ -140,18 +167,18 @@ export async function buildState(
  * @returns maps of readme files and tags to scan
  */
 export function reconcileChangedFilesAndTags(
-  before: Map<string, string[]>,
-  after: Map<string, string[]>,
-): Map<string, string[]>[] {
-  const beforeFinal = new Map<string, string[]>();
-  const afterFinal = new Map<string, string[]>();
+  before: Map<string, ReadmeAffectedTags>,
+  after: Map<string, ReadmeAffectedTags>,
+): Map<string, ReadmeAffectedTags>[] {
+  const beforeFinal = new Map<string, ReadmeAffectedTags>();
+  const afterFinal = new Map<string, ReadmeAffectedTags>();
 
   // Clone the maps so that changes to maps do not affect original object
   for (const [readme, tags] of before.entries()) {
-    beforeFinal.set(readme, [...tags]);
+    beforeFinal.set(readme, tags);
   }
   for (const [readme, tags] of after.entries()) {
-    afterFinal.set(readme, [...tags]);
+    afterFinal.set(readme, tags);
   }
 
   // If a tag is deleted in after and exists in before, do NOT scan the tag
@@ -162,11 +189,11 @@ export function reconcileChangedFilesAndTags(
       continue;
     }
 
-    const afterTags = new Set(afterFinal.get(readme)!);
-    beforeFinal.set(
-      readme,
-      tags.filter((tag) => afterTags.has(tag)),
-    );
+    const afterTags = new Set([...afterFinal.get(readme)!.changedTags]);
+    beforeFinal.set(readme, {
+      readme: tags.readme,
+      changedTags: new Set([...tags.changedTags].filter((t) => afterTags.has(t))),
+    });
   }
 
   return [beforeFinal, afterFinal];
@@ -225,20 +252,20 @@ export function getService(filePath: string): string {
 
 /**
  * Return true if the path contains "/examples/"
- * @param path 
- * @returns 
+ * @param path
+ * @returns
  */
 const excludeExamples = (path: string) => path.includes("/examples/");
 
 /**
  * Given a list of swagger files relatve to before and after root paths,
- * return a set of swagger files that have changed. Changes can be directly in 
- * the swagger file or in part of a referenced file that is included. Not all 
+ * return a set of swagger files that have changed. Changes can be directly in
+ * the swagger file or in part of a referenced file that is included. Not all
  * changes to a file in a $ref will affect a given swagger file.
- * @param beforeRoot 
- * @param afterRoot 
- * @param affectedSwaggerCandidates 
- * @returns 
+ * @param beforeRoot
+ * @param afterRoot
+ * @param affectedSwaggerCandidates
+ * @returns
  */
 export async function getChangedSwaggers(
   beforeRoot: string,
@@ -257,14 +284,12 @@ export async function getChangedSwaggers(
     const afterSwagger = join(afterRoot, swagger);
 
     // Using dereference which supports excluding $ref paths (in this case, examples)
-    const derefBefore = await $RefParser.dereference(
-      beforeSwagger, 
-      { dereference: { excludedPathMatcher: excludeExamples}},
-    );
-    const derefAfter = await $RefParser.dereference(
-      afterSwagger, 
-      { dereference: { excludedPathMatcher: excludeExamples}},
-    );
+    const derefBefore = await $RefParser.dereference(beforeSwagger, {
+      dereference: { excludedPathMatcher: excludeExamples },
+    });
+    const derefAfter = await $RefParser.dereference(afterSwagger, {
+      dereference: { excludedPathMatcher: excludeExamples },
+    });
 
     // Compare the dereferenced objects
     if (!deepEqual(derefBefore, derefAfter)) {
diff --git a/eng/tools/lint-diff/src/runChecks.ts b/eng/tools/lint-diff/src/runChecks.ts
index 29ad688e92a4..887ec894c124 100644
--- a/eng/tools/lint-diff/src/runChecks.ts
+++ b/eng/tools/lint-diff/src/runChecks.ts
@@ -1,10 +1,11 @@
 import { join } from "path";
+import { execNpmExec, isExecError, ExecError } from "@azure-tools/specs-shared/exec";
+import { debugLogger } from "@azure-tools/specs-shared/logger";
 
-import { getOpenapiType } from "./markdown-utils.js";
 import { getPathToDependency, isFailure } from "./util.js";
 import { AutoRestMessage, AutorestRunResult } from "./lintdiff-types.js";
-import { execNpmExec, isExecError, ExecError } from "@azure-tools/specs-shared/exec";
-import { debugLogger } from "@azure-tools/specs-shared/logger";
+import { ReadmeAffectedTags } from "./lintdiff-types.js";
+import { getOpenapiType } from "./markdown-utils.js";
 
 const MAX_EXEC_BUFFER = 64 * 1024 * 1024;
 
@@ -14,7 +15,7 @@ const AUTOREST_ERROR_PREFIX = '{"level":';
 
 export async function runChecks(
   path: string,
-  runList: Map<string, string[]>,
+  runList: Map<string, ReadmeAffectedTags>,
 ): Promise<AutorestRunResult[]> {
   const dependenciesDir = await getPathToDependency("@microsoft.azure/openapi-validator");
   const result: AutorestRunResult[] = [];
@@ -22,8 +23,7 @@ export async function runChecks(
   for (const [readme, tags] of runList.entries()) {
     const changedFilePath = join(path, readme);
 
-    // TODO: Move this into getRunList
-    let openApiType = await getOpenapiType(changedFilePath);
+    let openApiType = await getOpenapiType(tags.readme);
 
     // From momentOfTruth.ts:executeAutoRestWithLintDiff
     // This is a quick workaround for https://github.com/Azure/azure-sdk-tools/issues/6549
@@ -33,7 +33,7 @@ export async function runChecks(
     let openApiSubType = openApiType;
 
     // If the tags array is empty run the loop once but with a null tag
-    const coalescedTags = tags?.length ? tags : [null];
+    const coalescedTags = tags.changedTags?.size ? [...tags.changedTags] : [null];
     for (const tag of coalescedTags) {
       console.log(`::group::Autorest for type: ${openApiType} readme: ${readme} tag: ${tag}`);
 
@@ -67,14 +67,14 @@ export async function runChecks(
         lintDiffResult = {
           autorestCommand,
           rootPath: path,
-          readme,
+          readme: tags.readme,
           tag: tag ? tag : "",
           openApiType,
           error: null,
           ...executionResult,
         } as AutorestRunResult;
       } catch (error) {
-        if (!isExecError(error)) { 
+        if (!isExecError(error)) {
           throw error;
         }
 
@@ -82,7 +82,7 @@ export async function runChecks(
         lintDiffResult = {
           autorestCommand,
           rootPath: path,
-          readme,
+          readme: tags.readme,
           tag: tag ? tag : "",
           openApiType,
           error: execError,
diff --git a/eng/tools/lint-diff/test/correlateResults.test.ts b/eng/tools/lint-diff/test/correlateResults.test.ts
index 9c6010e3212a..131ce0d24a8a 100644
--- a/eng/tools/lint-diff/test/correlateResults.test.ts
+++ b/eng/tools/lint-diff/test/correlateResults.test.ts
@@ -15,6 +15,7 @@ import {
 } from "../src/correlateResults.js";
 import { relativizePath } from "../src/util.js";
 import { isWindows } from "./test-util.js";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 describe("getViolations", () => {
   test("returns a result", () => {
@@ -32,14 +33,14 @@ describe("getViolations", () => {
         {
           before: {
             rootPath: "before",
-            readme: "specification/service1/resource-manager/readme.md",
+            readme: new Readme("specification/service1/resource-manager/readme.md"),
             tag: "tag1",
             stdout: existingErrorInBefore,
             stderr: "",
           },
           after: {
             rootPath: "after",
-            readme: "specification/service1/resource-manager/readme.md",
+            readme: new Readme("specification/service1/resource-manager/readme.md"),
             tag: "tag1",
             stdout: `${newError}\n${correlatedErrorInAfter}`,
             stderr: "",
@@ -73,14 +74,14 @@ describe("getViolations", () => {
         {
           before: {
             rootPath: "before",
-            readme: "specification/service1/resource-manager/readme.md",
+            readme: new Readme("specification/service1/resource-manager/readme.md"),
             tag: "tag1",
             stdout: beforeViolation,
             stderr: "",
           },
           after: {
             rootPath: "after",
-            readme: "specification/service1/resource-manager/readme.md",
+            readme: new Readme("specification/service1/resource-manager/readme.md"),
             tag: "tag1",
             stdout: afterViolation,
             stderr: "",
@@ -114,7 +115,7 @@ describe("getViolations", () => {
           before: null,
           after: {
             rootPath: "after",
-            readme: "specification/service1/resource-manager/readme.md",
+            readme: new Readme("specification/service1/resource-manager/readme.md"),
             tag: "tag1",
             stdout: afterViolation,
             stderr: "",
@@ -159,7 +160,7 @@ describe("getLintDiffViolations", async () => {
   function createRunResult(stdout: string, stderr: string = ""): AutorestRunResult {
     return {
       rootPath: "string",
-      readme: "string",
+      readme: new Readme("string"),
       tag: "string",
       error: null,
       stdout: stdout,
diff --git a/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/data-plane/swagger-preview.json b/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/data-plane/swagger-preview.json
new file mode 100644
index 000000000000..b22a33bb8b49
--- /dev/null
+++ b/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/data-plane/swagger-preview.json
@@ -0,0 +1,525 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "title": "Contoso Widget Manager with a change that LintDiff will recognize",
+    "version": "2022-12-01",
+    "x-typespec-generated": [
+      {
+        "emitter": "@azure-tools/typespec-autorest"
+      }
+    ]
+  },
+  "schemes": [
+    "https"
+  ],
+  "produces": [
+    "application/json"
+  ],
+  "consumes": [
+    "application/json"
+  ],
+  "security": [
+    {
+      "AadOauth2Auth": [
+        "https://contoso.azure.com/.default"
+      ]
+    }
+  ],
+  "securityDefinitions": {
+    "AadOauth2Auth": {
+      "type": "oauth2",
+      "description": "The Azure Active Directory OAuth2 Flow",
+      "flow": "accessCode",
+      "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize",
+      "scopes": {
+        "https://contoso.azure.com/.default": ""
+      },
+      "tokenUrl": "https://login.microsoftonline.com/common/oauth2/token"
+    }
+  },
+  "tags": [],
+  "paths": {
+    "/widgets": {
+      "get": {
+        "operationId": "Widgets_ListWidgets",
+        "description": "List Widget resources",
+        "parameters": [
+          {
+            "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "The request has succeeded.",
+            "schema": {
+              "$ref": "#/definitions/PagedWidgetSuite"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse"
+            },
+            "headers": {
+              "x-ms-error-code": {
+                "type": "string",
+                "description": "String error code indicating what went wrong."
+              }
+            }
+          }
+        },
+        "x-ms-pageable": {
+          "nextLinkName": "nextLink"
+        }
+      }
+    },
+    "/widgets/{widgetName}": {
+      "get": {
+        "operationId": "Widgets_GetWidget",
+        "description": "Fetch a Widget by name.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter"
+          },
+          {
+            "name": "widgetName",
+            "in": "path",
+            "description": "The widget name.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "The request has succeeded.",
+            "schema": {
+              "$ref": "#/definitions/WidgetSuite"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse"
+            },
+            "headers": {
+              "x-ms-error-code": {
+                "type": "string",
+                "description": "String error code indicating what went wrong."
+              }
+            }
+          }
+        }
+      },
+      "patch": {
+        "operationId": "Widgets_CreateOrUpdateWidget",
+        "description": "Creates or updates a Widget asynchronously.",
+        "consumes": [
+          "application/merge-patch+json"
+        ],
+        "parameters": [
+          {
+            "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter"
+          },
+          {
+            "name": "widgetName",
+            "in": "path",
+            "description": "The widget name.",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "resource",
+            "in": "body",
+            "description": "The resource instance.",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/WidgetSuiteCreateOrUpdate"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "The request has succeeded.",
+            "schema": {
+              "$ref": "#/definitions/WidgetSuite"
+            },
+            "headers": {
+              "Operation-Location": {
+                "type": "string",
+                "format": "uri",
+                "description": "The location for monitoring the operation state."
+              }
+            }
+          },
+          "201": {
+            "description": "The request has succeeded and a new resource has been created as a result.",
+            "schema": {
+              "$ref": "#/definitions/WidgetSuite"
+            },
+            "headers": {
+              "Operation-Location": {
+                "type": "string",
+                "format": "uri",
+                "description": "The location for monitoring the operation state."
+              }
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse"
+            },
+            "headers": {
+              "x-ms-error-code": {
+                "type": "string",
+                "description": "String error code indicating what went wrong."
+              }
+            }
+          }
+        },
+        "x-ms-long-running-operation": true
+      },
+      "delete": {
+        "operationId": "Widgets_DeleteWidget",
+        "description": "Delete a Widget asynchronously.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter"
+          },
+          {
+            "name": "widgetName",
+            "in": "path",
+            "description": "The widget name.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "202": {
+            "description": "The request has been accepted for processing, but processing has not yet completed.",
+            "schema": {
+              "type": "object",
+              "description": "Provides status details for long running operations.",
+              "properties": {
+                "id": {
+                  "type": "string",
+                  "description": "The unique ID of the operation."
+                },
+                "status": {
+                  "$ref": "#/definitions/Azure.Core.Foundations.OperationState",
+                  "description": "The status of the operation"
+                },
+                "error": {
+                  "$ref": "#/definitions/Azure.Core.Foundations.Error",
+                  "description": "Error object that describes the error when status is \"Failed\"."
+                }
+              },
+              "required": [
+                "id",
+                "status"
+              ]
+            },
+            "headers": {
+              "Operation-Location": {
+                "type": "string",
+                "format": "uri",
+                "description": "The location for monitoring the operation state."
+              }
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse"
+            },
+            "headers": {
+              "x-ms-error-code": {
+                "type": "string",
+                "description": "String error code indicating what went wrong."
+              }
+            }
+          }
+        },
+        "x-ms-long-running-operation": true
+      }
+    },
+    "/widgets/{widgetName}/operations/{operationId}": {
+      "get": {
+        "operationId": "Widgets_GetWidgetOperationStatus",
+        "description": "Gets status of a Widget operation.",
+        "parameters": [
+          {
+            "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter"
+          },
+          {
+            "name": "widgetName",
+            "in": "path",
+            "description": "The widget name.",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "operationId",
+            "in": "path",
+            "description": "The unique ID of the operation.",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "The request has succeeded.",
+            "schema": {
+              "type": "object",
+              "description": "Provides status details for long running operations.",
+              "properties": {
+                "id": {
+                  "type": "string",
+                  "description": "The unique ID of the operation."
+                },
+                "status": {
+                  "$ref": "#/definitions/Azure.Core.Foundations.OperationState",
+                  "description": "The status of the operation"
+                },
+                "error": {
+                  "$ref": "#/definitions/Azure.Core.Foundations.Error",
+                  "description": "Error object that describes the error when status is \"Failed\"."
+                },
+                "result": {
+                  "$ref": "#/definitions/WidgetSuite",
+                  "description": "The result of the operation."
+                }
+              },
+              "required": [
+                "id",
+                "status"
+              ]
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse"
+            },
+            "headers": {
+              "x-ms-error-code": {
+                "type": "string",
+                "description": "String error code indicating what went wrong."
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "Azure.Core.Foundations.Error": {
+      "type": "object",
+      "description": "The error object.",
+      "properties": {
+        "code": {
+          "type": "string",
+          "description": "One of a server-defined set of error codes."
+        },
+        "message": {
+          "type": "string",
+          "description": "A human-readable representation of the error."
+        },
+        "target": {
+          "type": "string",
+          "description": "The target of the error."
+        },
+        "details": {
+          "type": "array",
+          "description": "An array of details about specific errors that led to this reported error.",
+          "items": {
+            "$ref": "#/definitions/Azure.Core.Foundations.Error"
+          },
+          "x-ms-identifiers": []
+        },
+        "innererror": {
+          "$ref": "#/definitions/Azure.Core.Foundations.InnerError",
+          "description": "An object containing more specific information than the current object about the error."
+        }
+      },
+      "required": [
+        "code",
+        "message"
+      ]
+    },
+    "Azure.Core.Foundations.ErrorResponse": {
+      "type": "object",
+      "description": "A response containing error details.",
+      "properties": {
+        "error": {
+          "$ref": "#/definitions/Azure.Core.Foundations.Error",
+          "description": "The error object."
+        }
+      },
+      "required": [
+        "error"
+      ]
+    },
+    "Azure.Core.Foundations.InnerError": {
+      "type": "object",
+      "description": "An object containing more specific information about the error. As per Microsoft One API guidelines - https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors.",
+      "properties": {
+        "code": {
+          "type": "string",
+          "description": "One of a server-defined set of error codes."
+        },
+        "innererror": {
+          "$ref": "#/definitions/Azure.Core.Foundations.InnerError",
+          "description": "Inner error."
+        }
+      }
+    },
+    "Azure.Core.Foundations.OperationState": {
+      "type": "string",
+      "description": "Enum describing allowed operation states.",
+      "enum": [
+        "NotStarted",
+        "Running",
+        "Succeeded",
+        "Failed",
+        "Canceled"
+      ],
+      "x-ms-enum": {
+        "name": "OperationState",
+        "modelAsString": true,
+        "values": [
+          {
+            "name": "NotStarted",
+            "value": "NotStarted",
+            "description": "The operation has not started."
+          },
+          {
+            "name": "Running",
+            "value": "Running",
+            "description": "The operation is in progress."
+          },
+          {
+            "name": "Succeeded",
+            "value": "Succeeded",
+            "description": "The operation has completed successfully."
+          },
+          {
+            "name": "Failed",
+            "value": "Failed",
+            "description": "The operation has failed."
+          },
+          {
+            "name": "Canceled",
+            "value": "Canceled",
+            "description": "The operation has been canceled by the user."
+          }
+        ]
+      }
+    },
+    "FakedSharedModel": {
+      "type": "object",
+      "description": "Faked shared model",
+      "properties": {
+        "tag": {
+          "type": "string",
+          "description": "The tag."
+        },
+        "createdAt": {
+          "type": "string",
+          "format": "date-time",
+          "description": "The created date."
+        }
+      },
+      "required": [
+        "tag",
+        "createdAt"
+      ]
+    },
+    "FakedSharedModelCreateOrUpdate": {
+      "type": "object",
+      "description": "Faked shared model",
+      "properties": {
+        "tag": {
+          "type": "string",
+          "description": "The tag."
+        },
+        "createdAt": {
+          "type": "string",
+          "format": "date-time",
+          "description": "The created date."
+        }
+      }
+    },
+    "PagedWidgetSuite": {
+      "type": "object",
+      "description": "Paged collection of WidgetSuite items",
+      "properties": {
+        "value": {
+          "type": "array",
+          "description": "The WidgetSuite items on this page",
+          "items": {
+            "$ref": "#/definitions/WidgetSuite"
+          },
+          "x-ms-identifiers": []
+        },
+        "nextLink": {
+          "type": "string",
+          "format": "uri",
+          "description": "The link to the next page of items"
+        }
+      },
+      "required": [
+        "value"
+      ]
+    },
+    "WidgetSuite": {
+      "type": "object",
+      "description": "A widget.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The widget name.",
+          "readOnly": true
+        },
+        "manufacturerId": {
+          "type": "string",
+          "description": "The ID of the widget's manufacturer."
+        },
+        "sharedModel": {
+          "$ref": "#/definitions/FakedSharedModel",
+          "description": "The faked shared model."
+        }
+      },
+      "required": [
+        "name",
+        "manufacturerId"
+      ]
+    },
+    "WidgetSuiteCreateOrUpdate": {
+      "type": "object",
+      "description": "A widget.",
+      "properties": {
+        "manufacturerId": {
+          "type": "string",
+          "description": "The ID of the widget's manufacturer."
+        },
+        "sharedModel": {
+          "$ref": "#/definitions/FakedSharedModelCreateOrUpdate",
+          "description": "The faked shared model."
+        }
+      }
+    }
+  },
+  "parameters": {
+    "Azure.Core.Foundations.ApiVersionParameter": {
+      "name": "api-version",
+      "in": "query",
+      "description": "The API version to use for this operation.",
+      "required": true,
+      "type": "string",
+      "minLength": 1,
+      "x-ms-parameter-location": "method",
+      "x-ms-client-name": "apiVersion"
+    }
+  }
+}
diff --git a/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/readme.md b/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/readme.md
index 92dd5181d70e..63ba76d2f138 100644
--- a/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/readme.md
+++ b/eng/tools/lint-diff/test/fixtures/buildState/specification/edit-in-place/readme.md
@@ -17,3 +17,8 @@ These settings apply only when `--tag=package-2022-12-01` is specified on the co
 input-file:
   - data-plane/swagger.json
 ```
+
+```yaml $(tag) == 'package-2022-12-01-preview'
+input-file:
+  - data-plane/swagger-preview.json
+```
\ No newline at end of file
diff --git a/eng/tools/lint-diff/test/fixtures/getInputFiles/readme.md b/eng/tools/lint-diff/test/fixtures/getInputFiles/readme.md
deleted file mode 100644
index 67e7ad4c8caf..000000000000
--- a/eng/tools/lint-diff/test/fixtures/getInputFiles/readme.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Contoso.WidgetManager
-
-## Configuration
-
-Minimal file to test the getInputFiles function.
-
-### Basic Information
-
-```yaml
-openapi-type: data-plane
-tag: package-2022-12-01
-```
-
-### Tag: package-2022-12-01
-
-These settings apply only when `--tag=package-2022-12-01` is specified on the command line.
-
-```yaml $(tag) == 'package-2022-12-01'
-input-file:
-  - Azure.Contoso.WidgetManager/stable/2022-12-01/widgets.json
-```
-
-### Tag: package-2022-11-01-preview
-
-These settings apply only when `--tag=package-2022-11-01-preview` is specified on the command line.
-
-```yaml $(tag) == 'package-2022-11-01-preview'
-input-file:
-  - Azure.Contoso.WidgetManager/preview/2022-11-01-preview/widgets.json
-```
diff --git a/eng/tools/lint-diff/test/fixtures/getTagsAndInputFiles/readme.md b/eng/tools/lint-diff/test/fixtures/getTagsAndInputFiles/readme.md
deleted file mode 100644
index 7106d594e875..000000000000
--- a/eng/tools/lint-diff/test/fixtures/getTagsAndInputFiles/readme.md
+++ /dev/null
@@ -1,250 +0,0 @@
-# AlertsManagement
-
-This sample was selected for multiple tags.
-
-> see https://aka.ms/autorest
-
-This is the AutoRest configuration file for AlertManagement.
-
----
-
-## Getting Started
-
-To build the SDK for AlertManagement, simply [Install AutoRest](https://aka.ms/autorest/install) and in this folder, run:
-
-> `autorest`
-
-To see additional help and options, run:
-
-> `autorest --help`
-
----
-
-## Configuration
-
-### Basic Information
-
-These are the global settings for the AlertManagement API.
-
-### Suppression
-``` yaml
-directive:
-  - suppress: R3025
-    reason: The rule applied incorrectly to base class.
-    where:
-      - $.definitions.ManagedResource
-  - suppress: R3026
-    reason: The rule applied incorrectly to base class.
-    where:
-      - $.definitions.ManagedResource
-  - suppress: TopLevelResourcesListBySubscription
-    reason: The list by scope includes also list by subscription, this is an extension resource.
-    from: AlertsManagement.json    
-```
-
-``` yaml
-title: AlertsManagementClient
-description: AlertsManagement Client
-openapi-type: arm
-tag: package-2023-03
-```
-
-### Tag: package-preview-2024-01
-
-These settings apply only when `--tag=package-preview-2024-01` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2024-01'
-input-file:
-  - Microsoft.AlertsManagement/preview/2024-01-01-preview/AlertsManagement.json  
-```
-### Tag: package-preview-2023-08
-
-These settings apply only when `--tag=package-preview-2023-08` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2023-08'
-input-file:
-  - Microsoft.AlertsManagement/preview/2023-08-01-preview/AlertRuleRecommendations.json
-```
-### Tag: package-preview-2023-07
-
-These settings apply only when `--tag=package-preview-2023-07` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2023-07'
-input-file:
-  - Microsoft.AlertsManagement/preview/2023-07-12-preview/AlertsManagement.json  
-```
-### Tag: package-preview-2023-04
-
-These settings apply only when `--tag=package-preview-2023-04` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2023-04'
-input-file:
-  - Microsoft.AlertsManagement/preview/2023-04-01-preview/TenantActivityLogAlerts.json
-```
-### Tag: package-preview-2023-01
-
-These settings apply only when `--tag=package-preview-2023-01` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2023-01'
-input-file:
-  - Microsoft.AlertsManagement/preview/2023-01-01-preview/AlertRuleRecommendations.json
-```
-### Tag: package-2023-03
-
-These settings apply only when `--tag=package-2023-03` is specified on the command line.
-
-```yaml $(tag) == 'package-2023-03'
-input-file:
-  - Microsoft.AlertsManagement/stable/2023-03-01/PrometheusRuleGroups.json
-  - Microsoft.AlertsManagement/preview/2024-01-01-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json
-  - Microsoft.AlertsManagement/preview/2023-08-01-preview/AlertRuleRecommendations.json
-  - Microsoft.AlertsManagement/preview/2021-08-08-preview/AlertProcessingRules.json
-```
-### Tag: package-2021-08
-
-These settings apply only when `--tag=package-2021-08` is specified on the command line.
-
-```yaml $(tag) == 'package-2021-08'
-input-file:
-  - Microsoft.AlertsManagement/stable/2021-08-08/AlertProcessingRules.json
-  - Microsoft.AlertsManagement/preview/2021-07-22-preview/PrometheusRuleGroups.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json
-  - Microsoft.AlertsManagement/preview/2023-01-01-preview/AlertRuleRecommendations.json
-  - Microsoft.AlertsManagement/preview/2023-04-01-preview/TenantActivityLogAlerts.json
-```
-
-### Tag: package-preview-2021-08
-
-These settings apply only when `--tag=package-preview-2021-08` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2021-08'
-input-file:
-  - Microsoft.AlertsManagement/preview/2021-08-08-preview/AlertProcessingRules.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json
-```
-
-### Tag: package-preview-2021-07
-
-These settings apply only when `--tag=package-preview-2021-07` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2021-07'
-input-file:
-  - Microsoft.AlertsManagement/preview/2021-07-22-preview/PrometheusRuleGroups.json
-```
-
-### Tag: package-2021-04-only
-
-These settings apply only when `--tag=package-2021-04-only` is specified on the command line.
-
-```yaml $(tag) == 'package-2021-04-only'
-input-file:
-  - Microsoft.AlertsManagement/stable/2019-03-01/AlertsManagement.json
-  - Microsoft.AlertsManagement/stable/2021-04-01/SmartDetectorAlertRulesApi.json
-```
-
-### Tag: package-preview-2021-01
-
-These settings apply only when `--tag=package-preview-2021-01` is specified on the command line.
-
-```yaml $(tag) == 'package-preview-2021-01'
-input-file:
-  - Microsoft.AlertsManagement/preview/2021-01-01-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2021-01-01-preview/MigrateFromSmartDetections.json
-```
-### Tag: package-2019-06-preview
-
-These settings apply only when `--tag=package-2019-06-preview` is specified on the command line.
-
-```yaml $(tag) == 'package-2019-06-preview'
-input-file:
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/ActionRules.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json
-  - Microsoft.AlertsManagement/stable/2019-06-01/SmartDetectorAlertRulesApi.json
-```
-
-
-### Tag: package-2019-06
-
-These settings apply only when `--tag=package-2019-06` is specified on the command line.
-
-```yaml $(tag) == 'package-2019-06'
-input-file:
-  - Microsoft.AlertsManagement/stable/2019-06-01/SmartDetectorAlertRulesApi.json
-```
-
-### Tag: package-2019-03
-
-These settings apply only when `--tag=package-2019-03` is specified on the command line.
-
-```yaml $(tag) == 'package-2019-03'
-input-file:
-  - Microsoft.AlertsManagement/stable/2019-03-01/AlertsManagement.json
-  - Microsoft.AlertsManagement/stable/2019-03-01/SmartDetectorAlertRulesApi.json
-```
-
-### Tag: package-preview-2019-05
-
-These settings apply only when `--tag=package-preview-2019-05` is specified on the command line.
-
-``` yaml $(tag) == 'package-preview-2019-05'
-input-file:
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/ActionRules.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/AlertsManagement.json
-  - Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json
-```
-
-### Tag: package-2018-05
-
-These settings apply only when `--tag=package-2018-05` is specified on the command line.
-
-``` yaml $(tag) == 'package-2018-05'
-input-file:
-- Microsoft.AlertsManagement/stable/2018-05-05/AlertsManagement.json
-```
-
-### Tag: package-2018-05-preview
-
-These settings apply only when `--tag=package-2018-05` is specified on the command line.
-
-``` yaml $(tag) == 'package-2018-05-preview'
-input-file:
-- Microsoft.AlertsManagement/preview/2018-05-05-preview/AlertsManagement.json
-```
-
----
-
-# Code Generation
-
-## Swagger to SDK
-
-This section describes what SDK should be generated by the automatic system.
-This is not used by Autorest itself.
-
-``` yaml $(swagger-to-sdk)
-swagger-to-sdk:
-  - repo: azure-sdk-for-net
-  - repo: azure-sdk-for-python
-  - repo: azure-libraries-for-java
-  - repo: azure-sdk-for-go
-  - repo: azure-sdk-for-trenton
-  - repo: azure-resource-manager-schemas
-  - repo: azure-powershell
-```
-
-## Python
-
-See configuration in [readme.python.md](./readme.python.md)
-
----
-
-## Go
-
-See configuration in [readme.go.md](./readme.go.md)
-
-## Java
-
-See configuration in [readme.java.md](./readme.java.md)
diff --git a/eng/tools/lint-diff/test/generateReport.test.ts b/eng/tools/lint-diff/test/generateReport.test.ts
index ec2658620322..b552ce09500e 100644
--- a/eng/tools/lint-diff/test/generateReport.test.ts
+++ b/eng/tools/lint-diff/test/generateReport.test.ts
@@ -29,6 +29,7 @@ vi.mock("node:fs/promises", async () => {
 });
 
 import { readFile } from "fs/promises";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 vi.mock("../src/util.js", async () => {
   const original = await vi.importActual("../src/util.js");
@@ -361,7 +362,7 @@ describe("generateLintDiffReport", () => {
       stdout: "",
       stderr: "",
       rootPath: "",
-      readme: "file1.md",
+      readme: new Readme("file1.md"),
       tag: "",
     } as AutorestRunResult;
     const afterResult = {
@@ -369,7 +370,7 @@ describe("generateLintDiffReport", () => {
       stdout: JSON.stringify(afterViolation),
       stderr: "",
       rootPath: "",
-      readme: "file1.md",
+      readme: new Readme("file1.md"),
       tag: "",
     } as AutorestRunResult;
 
@@ -419,7 +420,7 @@ describe("generateLintDiffReport", () => {
       stdout: "",
       stderr: "",
       rootPath: "",
-      readme: "file1.md",
+      readme: new Readme("file1.md"),
       tag: "",
     } as AutorestRunResult;
     const afterResult = {
@@ -427,7 +428,7 @@ describe("generateLintDiffReport", () => {
       stdout: JSON.stringify(afterViolation),
       stderr: "",
       rootPath: "",
-      readme: "file1.md",
+      readme: new Readme("file1.md"),
       tag: "",
     } as AutorestRunResult;
 
@@ -485,7 +486,7 @@ describe("generateLintDiffReport", () => {
         stdout: "",
         stderr: "",
         rootPath: "",
-        readme: "file1.md",
+        readme: new Readme("file1.md"),
         tag: "",
       } as AutorestRunResult;
       const afterResult = {
@@ -493,7 +494,7 @@ describe("generateLintDiffReport", () => {
         stdout: JSON.stringify(afterViolation),
         stderr: "",
         rootPath: "",
-        readme: "file1.md",
+        readme: new Readme("file1.md"),
         tag: "",
       } as AutorestRunResult;
 
@@ -543,7 +544,7 @@ describe("generateAutoRestErrorReport", () => {
     const autoRestErrors = [
       {
         result: {
-          readme: "readme.md",
+          readme: new Readme("dummy/rootPath/readme.md"),
           tag: "tag1",
           rootPath: "dummy/rootPath",
           error: null,
@@ -557,7 +558,7 @@ describe("generateAutoRestErrorReport", () => {
       },
       {
         result: {
-          readme: "readme2.md",
+          readme: new Readme("dummy/rootPath/readme2.md"),
           tag: "tag2",
           rootPath: "dummy/rootPath",
           error: null,
diff --git a/eng/tools/lint-diff/test/markdown-utils.test.ts b/eng/tools/lint-diff/test/markdown-utils.test.ts
index 33dfb0088443..2ab72ce31635 100644
--- a/eng/tools/lint-diff/test/markdown-utils.test.ts
+++ b/eng/tools/lint-diff/test/markdown-utils.test.ts
@@ -7,13 +7,11 @@ import axios from "axios";
 import {
   deduplicateTags,
   getDocRawUrl,
-  getInputFiles,
   getDefaultTag,
-  getAllTags,
   getOpenapiType,
-  getTagsAndInputFiles,
   getRelatedArmRpcFromDoc,
 } from "../src/markdown-utils.js";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 vi.mock("axios");
 
@@ -38,28 +36,6 @@ describe("deduplicateTags", () => {
   });
 });
 
-describe("getInputFiles", () => {
-  test("returns input files for a readme content's tag", async () => {
-    const readmeContent = await readFile(join(__dirname, "fixtures/getInputFiles/readme.md"), {
-      encoding: "utf-8",
-    });
-
-    const inputFiles = await getInputFiles(readmeContent, "package-2022-12-01");
-
-    expect(inputFiles).toEqual(["Azure.Contoso.WidgetManager/stable/2022-12-01/widgets.json"]);
-  });
-
-  test("returns empty array when no input files are found", async () => {
-    const readmeContent = await readFile(join(__dirname, "fixtures/getInputFiles/readme.md"), {
-      encoding: "utf-8",
-    });
-
-    const inputFiles = await getInputFiles(readmeContent, "TAG-NOT-FOUND");
-
-    expect(inputFiles).toEqual([]);
-  });
-});
-
 describe("getDocRawUrl", () => {
   test("returns the expected doc url", () => {
     const docUrl = getDocRawUrl("Post201Response");
@@ -78,42 +54,26 @@ describe("getDocRawUrl", () => {
 
 describe("getDefaultTag", () => {
   test("returns default tag when there is a Basic Information header", async () => {
-    const readmeContent = await readFile(
-      join(__dirname, "fixtures/getDefaultTag/hasBasicInformation.md"),
-      {
-        encoding: "utf-8",
-      },
+    const defaultTag = await getDefaultTag(
+      new Readme(join(__dirname, "fixtures/getDefaultTag/hasBasicInformation.md"))
     );
-    6;
-
-    const defaultTag = getDefaultTag(readmeContent);
 
     expect(defaultTag).toEqual("package-2022-12-01");
   });
 
   test("returns default tag when there is no Basic Information header", async () => {
-    const readmeContent = await readFile(
-      join(__dirname, "fixtures/getDefaultTag/noBasicInformation.md"),
-      {
-        encoding: "utf-8",
-      },
+    const defaultTag = await getDefaultTag(
+      new Readme(join(__dirname, "fixtures/getDefaultTag/noBasicInformation.md"))
     );
 
-    const defaultTag = getDefaultTag(readmeContent);
-
     expect(defaultTag).toEqual("package-2023-07-preview");
   });
 
   test("returns empty string when there is no default tag", async () => {
-    const readmeContent = await readFile(
-      join(__dirname, "fixtures/getDefaultTag/noDefaultTag.md"),
-      {
-        encoding: "utf-8",
-      },
+    const defaultTag = await getDefaultTag(
+      new Readme(join(__dirname, "fixtures/getDefaultTag/noDefaultTag.md"))
     );
 
-    const defaultTag = getDefaultTag(readmeContent);
-
     expect(defaultTag).toEqual("");
   });
 
@@ -138,8 +98,8 @@ tag: 2025-01-01
     },
   ])(
     "returns a string for default tag even when the tag is formatted like a date ($description)",
-    ({ readmeContent }) => {
-      const defaultTag = getDefaultTag(readmeContent);
+    async ({ readmeContent }) => {
+      const defaultTag = await getDefaultTag(new Readme("readme", { content: readmeContent }));
 
       expect(defaultTag).not.toBeInstanceOf(Date);
       expect(defaultTag).toBeTypeOf("string");
@@ -148,40 +108,11 @@ tag: 2025-01-01
   );
 });
 
-describe("getAllTags", () => {
-  test("returns all tags", async () => {
-    const readmeContent = await readFile(join(__dirname, "fixtures/getAllTags/readme.md"), {
-      encoding: "utf-8",
-    });
-
-    const tags = getAllTags(readmeContent);
-
-    expect(tags).toEqual([
-      "package-preview-2024-01",
-      "package-preview-2023-08",
-      "package-preview-2023-07",
-      "package-preview-2023-04",
-      "package-preview-2023-01",
-      "package-2023-03",
-      "package-2021-08",
-      "package-preview-2021-08",
-      "package-preview-2021-07",
-      "package-2021-04-only",
-      "package-preview-2021-01",
-      "package-2019-06-preview",
-      "package-2019-06",
-      "package-2019-03",
-      "package-preview-2019-05",
-      "package-2018-05",
-      "package-2018-05-preview",
-    ]);
-  });
-});
-
 describe("getOpenapiType", () => {
   test("openapi-type found and valid", async () => {
     const markdownFile = join(__dirname, "fixtures/getOpenapiType/type-found-and-valid.md");
-    const openapiType = await getOpenapiType(markdownFile);
+    const readme = new Readme(markdownFile);
+    const openapiType = await getOpenapiType(readme);
 
     expect(openapiType).toEqual("data-plane");
   });
@@ -191,7 +122,8 @@ describe("getOpenapiType", () => {
       __dirname,
       "fixtures/getOpenapiType/specification/service1/data-plane/type-found-not-valid-readme.md",
     );
-    const openapiType = await getOpenapiType(markdownFile);
+    const readme = new Readme(markdownFile);
+    const openapiType = await getOpenapiType(readme);
 
     expect(openapiType).toEqual("data-plane");
   });
@@ -201,40 +133,25 @@ describe("getOpenapiType", () => {
       __dirname,
       "fixtures/getOpenapiType/specification/service1/resource-manager/inferred-resource-manager-readme.md",
     );
-    const openApiType = await getOpenapiType(markdownFile);
-    expect(openApiType).toEqual("arm");
+    const readme = new Readme(markdownFile);
+    const openapiType = await getOpenapiType(readme);
+    expect(openapiType).toEqual("arm");
   });
   test.skipIf(isWindows())("openapi-type not found, type data-plane", async () => {
     const markdownFile = join(
       __dirname,
       "fixtures/getOpenapiType/specification/service1/data-plane/inferred-data-plane-readme.md",
     );
-    const openApiType = await getOpenapiType(markdownFile);
-    expect(openApiType).toEqual("data-plane");
+    const readme = new Readme(markdownFile);
+    const openapiType = await getOpenapiType(readme);
+    expect(openapiType).toEqual("data-plane");
   });
 
   test("openapi-type not found, type default", async () => {
     const markdownFile = join(__dirname, "fixtures/getOpenapiType/default.md");
-    const openApiType = await getOpenapiType(markdownFile);
-    expect(openApiType).toEqual("default");
-  });
-});
-
-describe("getTagsAndInputFiles", () => {
-  test("gets accurate input files for tag", async () => {
-    const readmeContent = await readFile(
-      join(__dirname, "fixtures/getTagsAndInputFiles/readme.md"),
-      { encoding: "utf-8" },
-    );
-
-    const actual = await getTagsAndInputFiles(["package-preview-2019-05"], readmeContent);
-    expect(actual.length).toEqual(1);
-    expect(actual[0].tagName).toEqual("package-preview-2019-05");
-    expect(actual[0].inputFiles).toEqual([
-      "Microsoft.AlertsManagement/preview/2019-05-05-preview/ActionRules.json",
-      "Microsoft.AlertsManagement/preview/2019-05-05-preview/AlertsManagement.json",
-      "Microsoft.AlertsManagement/preview/2019-05-05-preview/SmartGroups.json",
-    ]);
+    const readme = new Readme(markdownFile);
+    const openapiType = await getOpenapiType(readme);
+    expect(openapiType).toEqual("default");
   });
 });
 
diff --git a/eng/tools/lint-diff/test/processChanges.test.ts b/eng/tools/lint-diff/test/processChanges.test.ts
index 14ff9c3dfd6e..6ead65aed009 100644
--- a/eng/tools/lint-diff/test/processChanges.test.ts
+++ b/eng/tools/lint-diff/test/processChanges.test.ts
@@ -7,8 +7,10 @@ import {
   getChangedSwaggers,
   buildState,
 } from "../src/processChanges.js";
+import { ReadmeAffectedTags } from "../src/lintdiff-types.js";
 
 import { isWindows } from "./test-util.js";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 describe("getAffectedServices", () => {
   test.skipIf(isWindows())("returns single service with multiple files", async () => {
@@ -58,32 +60,78 @@ describe("getService", () => {
 
 describe("reconcileChangedFilesAndTags", () => {
   test("if a tag is deleted in after and exists in before, remove the tag from before", () => {
-    const before = new Map<string, string[]>([["specification/1/readme.md", ["tag1", "tag2"]]]);
-    const after = new Map<string, string[]>([["specification/1/readme.md", ["tag1"]]]);
+    const before = new Map<string, ReadmeAffectedTags>([
+      [
+        "specification/1/readme.md",
+        {
+          readme: new Readme("specification/1/readme.md"),
+          changedTags: new Set<string>(["tag1", "tag2"]),
+        },
+      ],
+    ]);
+    const after = new Map<string, ReadmeAffectedTags>([
+      [
+        "specification/1/readme.md",
+        {
+          readme: new Readme("specification/1/readme.md"),
+          changedTags: new Set<string>(["tag1"]),
+        },
+      ],
+    ]);
 
     const [beforeFinal, afterFinal] = reconcileChangedFilesAndTags(before, after);
     expect(beforeFinal).toEqual(
-      new Map<string, string[]>([["specification/1/readme.md", ["tag1"]]]),
+      new Map<string, string[]>([
+        [
+          "specification/1/readme.md",
+          expect.objectContaining({
+            changedTags: new Set<string>(["tag1"]),
+          }),
+        ],
+      ]),
     );
     expect(afterFinal).toEqual(after);
   });
 
   test("does not change if there is no change", () => {
-    const before = new Map<string, string[]>([["specification/1/readme.md", ["tag1", "tag2"]]]);
-    const after = new Map<string, string[]>([["specification/1/readme.md", ["tag1", "tag2"]]]);
+    const before = new Map<string, ReadmeAffectedTags>([
+      [
+        "specification/1/readme.md",
+        {
+          readme: new Readme("specification/1/readme.md"),
+          changedTags: new Set<string>(["tag1", "tag2"]),
+        },
+      ],
+    ]);
+    const after = new Map<string, ReadmeAffectedTags>([
+      [
+        "specification/1/readme.md",
+        {
+          readme: new Readme("specification/1/readme.md"),
+          changedTags: new Set<string>(["tag1", "tag2"]),
+        },
+      ],
+    ]);
 
     const [beforeFinal, afterFinal] = reconcileChangedFilesAndTags(before, after);
     expect(beforeFinal).toEqual(before);
     expect(afterFinal).toEqual(after);
   });
 
-  // TODO: Test this and ensure the behavior matches
   test("keeps a specification in before if it is deleted in after", () => {
-    const before = new Map<string, string[]>([["specification/1/readme.md", ["tag1", "tag2"]]]);
-    const after = new Map<string, string[]>();
+    const before = new Map<string, ReadmeAffectedTags>([
+      [
+        "specification/1/readme.md",
+        {
+          readme: new Readme("specification/1/readme.md"),
+          changedTags: new Set<string>(["tag1", "tag2"]),
+        },
+      ],
+    ]);
+    const after = new Map<string, ReadmeAffectedTags>();
 
     const [beforeFinal, afterFinal] = reconcileChangedFilesAndTags(before, after);
-    expect(beforeFinal).toEqual(beforeFinal);
+    expect(beforeFinal).toEqual(before);
     expect(afterFinal).toEqual(after);
   });
 });
@@ -154,9 +202,12 @@ describe("buildState", () => {
     expect(actual).toMatchInlineSnapshot(`
       [
         Map {
-          "specification/edit-in-place/readme.md" => [
-            "package-2022-12-01",
-          ],
+          "specification/edit-in-place/readme.md" => {
+            "changedTags": Set {
+              "package-2022-12-01",
+            },
+            "readme": Readme {},
+          },
         },
         [
           "specification/edit-in-place/data-plane/swagger.json",
@@ -174,7 +225,10 @@ describe("buildState", () => {
     expect(actual).toMatchInlineSnapshot(`
       [
         Map {
-          "specification/edit-in-place/readme.md" => [],
+          "specification/edit-in-place/readme.md" => {
+            "changedTags": Set {},
+            "readme": Readme {},
+          },
         },
         [],
       ]
diff --git a/eng/tools/lint-diff/test/runChecks.test.ts b/eng/tools/lint-diff/test/runChecks.test.ts
index 89a1a2dcfefb..c46ae153ddfc 100644
--- a/eng/tools/lint-diff/test/runChecks.test.ts
+++ b/eng/tools/lint-diff/test/runChecks.test.ts
@@ -16,7 +16,7 @@ vi.mock(import("../src/util.js"), async (importOriginal) => {
   };
 });
 
-vi.mock(import("../src/markdown-utils.js"), async (importOriginal) => { 
+vi.mock(import("../src/markdown-utils.js"), async (importOriginal) => {
   const actual = await importOriginal();
   return {
     ...actual,
@@ -27,55 +27,61 @@ vi.mock(import("../src/markdown-utils.js"), async (importOriginal) => {
 import { runChecks, getAutorestErrors } from "../src/runChecks.js";
 import { AutorestRunResult } from "../src/lintdiff-types.js";
 import { execNpmExec } from "@azure-tools/specs-shared/exec";
+import { ReadmeAffectedTags } from "../src/lintdiff-types.js";
+import { Readme } from "@azure-tools/specs-shared/readme";
 
 describe("runChecks", () => {
-  beforeEach(() => { 
-    vi.clearAllMocks(); 
+  beforeEach(() => {
+    vi.clearAllMocks();
   });
 
   test("sets outputs properly on tag", async () => {
     (execNpmExec as Mock).mockResolvedValue({ stdout: "out", stderr: "err" });
-    const runList = new Map([["readme.md", ["tag1"]]]);
+    const runList = new Map<string, ReadmeAffectedTags>([
+      ["readme.md", { readme: new Readme(""), changedTags: new Set<string>(["tag1"]) }],
+    ]);
 
     const actual = await runChecks("root", runList);
     expect(actual).toHaveLength(1);
     expect(actual[0].error).toBeNull();
     expect(actual[0].stdout).toBe("out");
     expect(actual[0].stderr).toBe("err");
-    
+
     expect(execNpmExec).toHaveBeenCalledWith(
-      expect.arrayContaining([
-        expect.stringContaining("--tag=tag1"),
-      ]),
-      expect.anything()
+      expect.arrayContaining([expect.stringContaining("--tag=tag1")]),
+      expect.anything(),
     );
   });
 
   test("coalesces null tag when no tags specified", async () => {
     (execNpmExec as Mock).mockResolvedValue({ stdout: "", stderr: "" });
-    const runList = new Map([["readme.md", []]]);
+    const runList = new Map<string, ReadmeAffectedTags>([
+      ["readme.md", { readme: new Readme(""), changedTags: new Set<string>() }],
+    ]);
 
     const actual = await runChecks("root", runList);
     expect(actual).toHaveLength(1);
     expect(execNpmExec).toHaveBeenCalledWith(
-      expect.not.arrayContaining([expect.stringContaining("--tag")]), 
-      expect.anything()
+      expect.not.arrayContaining([expect.stringContaining("--tag")]),
+      expect.anything(),
     );
   });
 
   test("error path populates error, stdout, stderr", async () => {
     // Consturct an error object that will return true when passed to isExecError
-    const err = new Error(); 
-    (err as any).stdout = "s"; 
+    const err = new Error();
+    (err as any).stdout = "s";
     (err as any).stderr = "e";
     (err as any).code = 1;
-    
+
     (execNpmExec as Mock).mockRejectedValue(err);
-    const runList = new Map([["readme.md", ["tag1", "tag2"]]]);
+    const runList = new Map<string, ReadmeAffectedTags>([
+      ["readme.md", { readme: new Readme(""), changedTags: new Set<string>(["tag1", "tag2"]) }],
+    ]);
 
     const actual = await runChecks("root", runList);
     expect(actual).toHaveLength(2);
-    actual.forEach(r => {
+    actual.forEach((r) => {
       expect(r.error).toBe(err);
       expect(r.stdout).toBe("s");
       expect(r.stderr).toBe("e");
@@ -83,17 +89,19 @@ describe("runChecks", () => {
   });
 
   test("error path throws an error that isn't an ExecError", async () => {
-    (execNpmExec as Mock).mockRejectedValue({ 
-      message: "some error for which isExecError returns false" 
+    (execNpmExec as Mock).mockRejectedValue({
+      message: "some error for which isExecError returns false",
     });
-    const runList = new Map([["readme.md", ["tag1", "tag2"]]]);
+    const runList = new Map<string, ReadmeAffectedTags>([
+      ["readme.md", { readme: new Readme(""), changedTags: new Set<string>(["tag1", "tag2"]) }],
+    ]);
     expect(runChecks("root", runList)).rejects.toThrow();
   });
 });
 
 describe("getAutorestErrors", () => {
-   test("filters only error and fatal levels", () => {
-     const lines =`{"pluginName":"spectral","extensionName":"@microsoft.azure/openapi-validator","level":"warning","message":"Use the latest version v6 of types.json.","code":"LatestVersionOfCommonTypesMustBeUsed","details":{"jsonpath":["definitions","SettingsResourceUpdate","allOf","0","$ref"],"validationCategory":"","providerNamespace":false,"resourceType":false,"rpcGuidelineCode":"","range":{"start":{"line":444,"column":18},"end":{"line":444,"column":111}}},"source":[{"document":"file:///mnt/vss/_work/1/azure-rest-api-specs-pr/specification/portalservices/resource-manager/Microsoft.PortalServices/settings/preview/2025-04-01-preview/settings.json","position":{"line":444,"column":11}}]}
+  test("filters only error and fatal levels", () => {
+    const lines = `{"pluginName":"spectral","extensionName":"@microsoft.azure/openapi-validator","level":"warning","message":"Use the latest version v6 of types.json.","code":"LatestVersionOfCommonTypesMustBeUsed","details":{"jsonpath":["definitions","SettingsResourceUpdate","allOf","0","$ref"],"validationCategory":"","providerNamespace":false,"resourceType":false,"rpcGuidelineCode":"","range":{"start":{"line":444,"column":18},"end":{"line":444,"column":111}}},"source":[{"document":"file:///mnt/vss/_work/1/azure-rest-api-specs-pr/specification/portalservices/resource-manager/Microsoft.PortalServices/settings/preview/2025-04-01-preview/settings.json","position":{"line":444,"column":11}}]}
 {"pluginName":"spectral","extensionName":"@microsoft.azure/openapi-validator","level":"information","message":"spectralPluginFunc: Return"}
 {"level":"fatal","message":"Process() cancelled due to failure "}
 {"level":"error","message":"!Error: There are multiple operations defined for \\n  'get: /providers/Microsoft.PortalServices/operations'.\\n\\n  You are probably trying to use an input with multiple API versions with an autorest V2 generator, and that will not work. "}
@@ -103,20 +111,22 @@ describe("getAutorestErrors", () => {
     const runResult = { stdout: lines, stderr: "" } as any;
 
     const errors = getAutorestErrors(runResult);
-     expect(errors).toEqual(expect.arrayContaining([
-      expect.objectContaining({ level: "fatal", message: "Process() cancelled due to failure " }),
-      expect.objectContaining({ level: "error", }),
-    ]));
-
-    expect(errors).not.toEqual(expect.arrayContaining([
-      expect.objectContaining({ level: "information" }),
-    ]));
-    expect(errors).not.toEqual(expect.arrayContaining([
-      expect.objectContaining({ level: "warning" }),
-    ]));
-   });
-
-   test("returns empty when none", () => {
+    expect(errors).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({ level: "fatal", message: "Process() cancelled due to failure " }),
+        expect.objectContaining({ level: "error" }),
+      ]),
+    );
+
+    expect(errors).not.toEqual(
+      expect.arrayContaining([expect.objectContaining({ level: "information" })]),
+    );
+    expect(errors).not.toEqual(
+      expect.arrayContaining([expect.objectContaining({ level: "warning" })]),
+    );
+  });
+
+  test("returns empty when none", () => {
     expect(getAutorestErrors({ stdout: "", stderr: "" } as AutorestRunResult)).toEqual([]);
-   });
+  });
 });
diff --git a/eng/tools/lint-diff/tsconfig.json b/eng/tools/lint-diff/tsconfig.json
index aa7a3cbd1f93..4f763a74bf87 100644
--- a/eng/tools/lint-diff/tsconfig.json
+++ b/eng/tools/lint-diff/tsconfig.json
@@ -11,4 +11,5 @@
     "src/**/*.ts",
     "test/**/*.ts",
   ],
+  "exclude": [ "*.test.ts" ]
 }
diff --git a/eng/tools/lint-diff/vite.config.ts b/eng/tools/lint-diff/vite.config.ts
deleted file mode 100644
index 5c76c1ed553b..000000000000
--- a/eng/tools/lint-diff/vite.config.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { defineConfig } from "vite";
-import { configDefaults } from "vitest/config";
-
-export default defineConfig({
-  test: {
-    ...configDefaults,
-    testTimeout: 20000,
-    coverage: {
-      provider: "v8",
-      reporter: ["text", "json", "html"],
-    },
-  },
-});


Reviews:
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T18:36:07Z
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T18:37:12Z
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T19:12:31Z
user: login: mikeharder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:08:32Z
user: login: mikeharder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:09:58Z
user: login: mikeharder
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T18:11:24Z
user: login: mikeharder
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T18:13:15Z
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T19:02:24Z
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T19:03:35Z
user: login: danieljurek
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T19:06:37Z

Review comments:
diff_hunk: @@ -84,45 +95,62 @@ export async function buildState(
     specModels.set(serviceDir, specModel);
   }
 
-  const readmeTags = new Map<string, Set<string>>();
+  const readmeTags = new Map<string, ReadmeAffectedTags>();
   for (const changedSwagger of existingChangedFiles.filter(swagger)) {
-    const readmeTagMapForChangedFile = 
-      await specModels.get(getService(changedSwagger))!.getAffectedReadmeTags(resolve(rootPath, changedSwagger));
-    
-    for (const [readmeEntry, tags] of readmeTagMapForChangedFile) { 
-      if (!readmeTags.has(readmeEntry.path)) { 
-        readmeTags.set(readmeEntry.path, new Set<string>());
+    const readmeTagMapForChangedFile = await specModels
+      .get(getService(changedSwagger))!
+      .getAffectedReadmeTags(resolve(rootPath, changedSwagger));
+
+    for (const [readmeEntry, tags] of readmeTagMapForChangedFile) {
+      if (!readmeTags.has(readmeEntry.path)) {
+        readmeTags.set(readmeEntry.path, { readme: readmeEntry, changedTags: new Set<string>() });
       }
-      for (const tag of tags) { 
-        readmeTags.get(readmeEntry.path)?.add(tag.name);
+      for (const tag of tags) {
+        readmeTags.get(readmeEntry.path)?.changedTags.add(tag.name);
       }
     }
   }
 
-  const changedFileAndTagsMap = new Map<string, string[]>();
+  const changedFileAndTagsMap = new Map<string, ReadmeAffectedTags>();
   for (const [readmeFile, tags] of readmeTags.entries()) {
-    const dedupedTags = deduplicateTags(
-      await getTagsAndInputFiles(
-        [...tags],
-        await readFile(readmeFile, { encoding: "utf-8" }),
-      ),
-    );
-
-    changedFileAndTagsMap.set(relative(rootPath, readmeFile), dedupedTags);
+    const tagsFromReadme = await tags.readme.getTags();
+    const tagsAndInputs = [...tags.changedTags].map((changedTag) => {
+      return {
+        tagName: changedTag,
+        // For the given changedTag, get input files that are associated with it
+        inputFiles: [...tagsFromReadme]
+          .filter((t) => t.name === changedTag)
+          .flatMap((f) => [...f.inputFiles].map((i) => i.path)),
path: eng/tools/lint-diff/src/processChanges.ts
user: login: danieljurek
body: `flatMap` is used here because a `Set<Tag>` could still contain multiple tags with the same `name`. It is, however, unlikely. A more proper way to do this would be to use a Map that maps tags to tag names to ensure unique tag names and tag properties. 
created_at: 2025-05-15T18:36:07Z
author_association: MEMBER
diff_hunk: @@ -59,30 +59,30 @@ export async function runChecks(
 
       let lintDiffResult: AutorestRunResult;
       try {
-        const executionResult = await execNpmExec(
-          autorestArgs, 
-          { maxBuffer: MAX_EXEC_BUFFER, logger: new ConsoleLogger(true) }
-        );
+        const executionResult = await execNpmExec(autorestArgs, {
+          maxBuffer: MAX_EXEC_BUFFER,
+          logger: new ConsoleLogger(true),
path: eng/tools/lint-diff/src/runChecks.ts
user: login: danieljurek
body: This will need reconciling with changes from https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html
created_at: 2025-05-15T18:37:12Z
author_association: MEMBER
diff_hunk: @@ -17,91 +11,32 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: danieljurek
body: This heuristic is used in Legacy LintDiff but it could easily go in `readme.js` 

Reasons to put in readme.js: 
* Code can be used in multiple places
* The heuristic is widely applicable and unlikely to change

Reasons against putting in readme.js: 
* It'd be a widely shared heuristic, changes to the heuristic could break dependant apps
* Don't share a heuristic widely, shared code should be able to make stronger guarantees

created_at: 2025-05-19T19:12:31Z
author_association: MEMBER
diff_hunk: @@ -17,91 +11,32 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: mikeharder
body: let's move it to readme.js, but in a second PR
created_at: 2025-05-21T18:08:32Z
author_association: MEMBER
diff_hunk: @@ -17,91 +11,32 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: mikeharder
body: Although, what's the value of converting this from string to enum?  Just for stronger typing?
created_at: 2025-05-21T18:09:57Z
author_association: MEMBER
diff_hunk: @@ -164,60 +92,34 @@ export async function getRelatedArmRpcFromDoc(ruleName: string): Promise<string[
     return rpcRules;
   }
 
-  let walker = new commonmark.Parser().parse(response.data).walker();
-  let event;
-  while ((event = walker.next())) {
-    const node = event.node;
+  // Use marked to parse the markdown and extract the related ARM guideline codes
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: mikeharder
body: For my understanding, why does LintDiff itself need to parse markdown?
created_at: 2025-05-21T18:11:24Z
author_association: MEMBER
diff_hunk: @@ -17,91 +11,32 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: danieljurek
body: The type does eventually get turned into a string when used. There's no real value other than constraining the possible output values. Doing that with strings would certainly make the code more readable.
created_at: 2025-05-21T19:02:24Z
author_association: MEMBER
diff_hunk: @@ -17,91 +11,32 @@ export enum MarkdownType {
 
 /**
  *
- * @param markdownFile Path to the markdown file to parse
+ * @param readme Readme object to extract
  * @returns {Promise<MarkdownType>} The type of OpenAPI spec in the markdown file, or "default" if not found
  */
-export async function getOpenapiType(markdownFile: string): Promise<MarkdownType> {
-  let markdownContent = await readFile(markdownFile, { encoding: "utf-8" });
-  for (const codeBlock of parseCodeblocks(markdownContent)) {
-    if (
-      !codeBlock.info ||
-      codeBlock.info.trim().toLocaleLowerCase() !== "yaml" ||
-      !codeBlock.literal
-    ) {
-      continue;
-    }
-
-    let lines = codeBlock.literal.trim().split("\n");
-
-    for (const line of lines) {
-      if (line.trim().startsWith("openapi-type:")) {
-        let openapiType = line.trim().split(":")[1].trim().toLowerCase();
-
-        if (Object.values(MarkdownType).includes(openapiType as MarkdownType)) {
-          return openapiType as MarkdownType;
-        }
-      }
-    }
+// TODO: Should this be placed in the Readme class?
+export async function getOpenapiType(readme: Readme): Promise<MarkdownType> {
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: danieljurek
body: [#34836](https://github.com/Azure/azure-rest-api-specs/issues/34836)
created_at: 2025-05-21T19:03:34Z
author_association: MEMBER
diff_hunk: @@ -164,60 +92,34 @@ export async function getRelatedArmRpcFromDoc(ruleName: string): Promise<string[
     return rpcRules;
   }
 
-  let walker = new commonmark.Parser().parse(response.data).walker();
-  let event;
-  while ((event = walker.next())) {
-    const node = event.node;
+  // Use marked to parse the markdown and extract the related ARM guideline codes
path: eng/tools/lint-diff/src/markdown-utils.ts
user: login: danieljurek
body: This code extracts the ARM RPC codes from docs. For example: https://github.com/Azure/azure-openapi-validator/blob/main/docs/consistent-response-schema-for-put.md

![image](https://github.com/user-attachments/assets/3092081e-9cd6-4896-9efb-99b81bcd0715)

This is used in the output table.

The code for this is specific to LintDiff but it could be applicable anywhere that results for openapi validator are interpreted. 
created_at: 2025-05-21T19:06:37Z
author_association: MEMBER

PR comments:
user: login: openapi-pipeline-app[bot]
created_at: 2025-05-09T21:49:10Z
body: <h2>Next Steps to Merge</h2>Next steps that must be taken to merge this PR: <br/><ul><li>❌ The required check named <code>Protected Files</code> has failed. Refer to the check in the PR's 'Checks' tab for details on how to fix it and consult the <a href="https://aka.ms/ci-fix">aka.ms/ci-fix</a> guide</li></ul>
author_association: NONE
user: login: openapi-pipeline-app[bot]
created_at: 2025-05-09T21:49:11Z
body: PR validation pipeline restarted successfully. If there is ApiView generated, it will be updated in this comment.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-09T23:30:27Z
body: ## API Change Check

APIView identified API level changes in this PR and created the following API reviews

| Language | API Review for Package |
|----------|---------|
| Swagger | [Azure.Contoso.WidgetManager](https://apiview.dev/Assemblies/Review/686aeac2a3cd485dacced99288438d02?revisionId=3ef61c91d80b47f78b734e65ace32e2c) |
<!-- Fetch URI: https://apiview.dev/api/pullrequests?pullRequestNumber=34574&repoName=Azure/azure-rest-api-specs&commitSHA=6582275dc43db736e7330958dd2641b8cc1ba1eb -->
author_association: NONE
user: login: openapi-pipeline-app[bot]
created_at: 2025-05-15T18:43:19Z
body: PR validation pipeline can not start as the pull request is not merged or mergeable - most likely it has merge conflicts.
author_association: NONE
user: login: danieljurek
created_at: 2025-05-19T19:06:04Z
body: Taking back to draft to rebase on spec model changes
author_association: MEMBER
user: login: mikeharder
created_at: 2025-05-21T18:15:29Z
body: Avocado is false positive trying to validate test fixtures
author_association: MEMBER
user: login: danieljurek
created_at: 2025-05-21T19:07:22Z
body: This can merge. Bypassing protected file and false Avocado failure
author_association: MEMBER