@aburi/diff 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aburi/diff",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Aburi diff engine: 5-stage semantic matcher (id / git-rename / logic fingerprint / name+signature / dropped weak) followed by status classification and per-Symbol delta computation, emitting aburi.diff.v1-conformant output for PR-review workflows",
5
5
  "license": "Apache-2.0",
6
6
  "author": "kage1020",
@@ -25,7 +25,6 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "src",
29
28
  "README.md"
30
29
  ],
31
30
  "sideEffects": false,
@@ -33,15 +32,15 @@
33
32
  "access": "public"
34
33
  },
35
34
  "dependencies": {
36
- "@aburi/core": "0.3.0",
37
- "@aburi/types": "0.3.0"
35
+ "@aburi/core": "0.4.0",
36
+ "@aburi/types": "0.4.0"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@types/node": "^26.0.1",
41
40
  "ajv": "^8.20.0",
42
41
  "tsdown": "^0.22.3",
43
42
  "typescript": "^6.0.3",
44
- "vitest": "^4.1.9"
43
+ "vitest": "^4.1.11"
45
44
  },
46
45
  "scripts": {
47
46
  "build": "tsdown",
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/components.ts","../src/delta.ts","../src/match.ts","../src/diff.ts","../src/errors.ts","../src/signature.ts","../src/similarity.ts","../src/slice.ts","../src/status.ts"],"mappings":";;;;;;;AA2BA;;;;;;;;;;iBAAgB,cAAA,CACd,IAAA,WAAe,SAAA,IACf,IAAA,WAAe,SAAA,KACd,aAAA;;;;AAAa;AAqDhB;;;;;;;;;;UAAiB,kBAAA;EAQf;;;;;;;EAAA,WAAA,EAAa,WAAA,CAAY,kBAAA,EAAoB,YAAA;EAOE;AAAA;AAcjD;;;;EAdE,SAAA,EAAW,WAAA,CAAY,YAAA,EAAc,UAAA;AAAA;;;AAcuB;AAyB9D;;;;;;;;iBAzBgB,kBAAA,CAAmB,EAAA,EAAI,EAAA,GAAK,kBAAkB;;;;;;;;;;;;;;;;AA6Bd;;iBAJhC,gBAAA,CACd,IAAA,WAAe,UAAA,IACf,IAAA,WAAe,UAAA,IACf,KAAA;EAAS,IAAA,EAAM,kBAAA;EAAoB,IAAA,EAAM,kBAAA;AAAA,IACxC,cAAA;EAAmB,OAAA,EAAS,iBAAA;AAAA;;;cChIlB,iBAAA;AAAA,cACA,aAAA;AAAA,cACA,aAAA;AAAA,UAEI,YAAA;EDUa;;;;;;ECH5B,QAAQ;AAAA;;;;;;ADMM;iBCGA,kBAAA,CACd,IAAA,EAAM,MAAA,EACN,IAAA,EAAM,MAAA,EACN,OAAA,GAAS,YAAA,GACR,WAAA;;;;;;ADVH;;UEjBiB,UAAA;EACf,IAAA,EAAM,MAAA;EACN,IAAA,EAAM,MAAA;EACN,SAAA,EAAW,cAAA;AAAA;;KAID,YAAA,GAAe,WAAW;;;;;;AFatB;AAqDhB;;;;;iBEuBgB,YAAA,CACd,IAAA,WAAe,MAAA,IACf,IAAA,WAAe,MAAA;EAEf,OAAA,EAAS,UAAA;EACT,aAAA,EAAe,MAAA;EACf,aAAA,EAAe,MAAA;AAAA;;;;;;;;;;;AFdgC;iBE6CjC,mBAAA,CACd,aAAA,WAAwB,MAAA,IACxB,aAAA,WAAwB,MAAA,IACxB,SAAA,EAAW,YAAA;EAEX,OAAA,EAAS,UAAA;EACT,aAAA,EAAe,MAAA;EACf,aAAA,EAAe,MAAA;AAAA;;;;AFtC6C;AAyB9D;;;;;;iBE+FgB,0BAAA,CACd,aAAA,WAAwB,MAAA,IACxB,aAAA,WAAwB,MAAA;EAExB,OAAA,EAAS,UAAA;EACT,aAAA,EAAe,MAAA;EACf,aAAA,EAAe,MAAA;AAAA;;;;;;;;;;;;;;AFjG+B;;;;AChIhD;;;;AAA8B;AAC9B;;;;AAA0B;AAC1B;;;;AAA0B;AAE1B;;;iBCuWgB,uBAAA,CACd,aAAA,WAAwB,MAAA,IACxB,aAAA,WAAwB,MAAA;EAExB,OAAA,EAAS,UAAA;EACT,aAAA,EAAe,MAAA;EACf,aAAA,EAAe,MAAA;AAAA;;;;;;;;;;;;;;;ADzVH;;;;AC3Bd;;;;;;;;;;iBAgnBgB,qBAAA,CACd,aAAA,WAAwB,MAAA,IACxB,aAAA,WAAwB,MAAA;EAExB,OAAA,EAAS,UAAA;EACT,aAAA,EAAe,MAAA;EACf,aAAA,EAAe,MAAA;AAAA;;;AFrmBjB;;;;;;;;AAAA,UGeU,eAAA;EACR,OAAA;EACA,WAAW;AAAA;AAAA,UAGI,SAAA;EACf,MAAA,EAAQ,EAAA;EACR,MAAA,EAAQ,EAAA;EHkCO;EGhCf,IAAA,EAAM,KAAA;EACN,IAAA,EAAM,KAAA;EHuCmB;EGrCzB,SAAA;IAAc,IAAA;IAAc,OAAA;EAAA;EH4CjB;EG1CX,UAAA,GAAa,YAAA;EH0CS;EGxCtB,KAAA,GAAQ,YAAA;AAAA;;;;;;;;iBAYM,SAAA,CACd,KAAA,EAAO,SAAA,GACN,UAAA;EAAe,WAAA,EAAa,eAAA;EAAmB,OAAA,EAAS,OAAA,GAAU,eAAA;AAAA;;;;;;AHwCP;AAyB9D;;;;;;;;iBGkWgB,kBAAA,CAAmB,IAAA,EAAM,UAAA,EAAY,OAAA,GAAS,gBAAqB;;;;;;;KCtevE,aAAA;;;;;;;;;;AJyBI;AAqDhB;;;;;;;;;;;;;UIjDiB,eAAA;EACf,IAAA,EAAM,aAAa;EJ+DnB;EI7DA,KAAA;AAAA;AAAA,cAGW,SAAA,SAAkB,KAAA;EAAA,SACpB,IAAA,EAAM,aAAA;EAAA,SACN,KAAA;cAEG,OAAA,UAAiB,MAAA,EAAQ,eAAA,EAAiB,OAAA;IAAY,KAAA;EAAA;AAAA;;;;;;AJjBpE;;;;;;;;;;;;iBKVgB,mBAAA,CACd,IAAA,EAAM,SAAA,qBACN,IAAA,EAAM,SAAS;;;;;;;ALQjB;;;;;;;;;;;;;;AAGgB;AAqDhB;;;;;;AAxDA,iBMDgB,YAAA,CAAa,KAAa;;;;;;iBAsD1B,OAAA,CAAQ,CAAA,qBAAsB,CAAoB;;;;;;iBAkBlD,aAAA,CAAc,CAAA,UAAW,CAAS;;ANAD;AAcjD;;;iBMAgB,cAAA,CAAe,QAAA,UAAkB,QAAgB;;;;;ANAH;iBMS9C,gBAAA,CAAiB,QAAA,UAAkB,QAAgB;;;;;;;;;;;;;;;;;;;;;;;;ANoBnB;iBMQhC,mBAAA,CAAoB,QAAA,UAAkB,QAAgB;;iBAkQtD,WAAA,CAAY,KAAa;;;;UClZxB,UAAA;EPsBD;EOpBd,OAAA,WAAkB,YAAA;;;;;;EAMlB,aAAA,WAAwB,QAAA;EPeT;EObf,aAAA,WAAwB,QAAA;AAAA;;;;APeV;AAqDhB;;;;;;;;;;;;;;;iBO9CgB,aAAA,CAAc,KAAA,EAAO,UAAA,GAAa,WAAW;;;;;AP6DZ;AAcjD;;;;;iBOTgB,WAAA,CAAY,MAAA,EAAQ,WAAA,GAAc,QAAQ;;KAa9C,kBAAA,GPJkD;UOgB7C,oBAAA;EACf,IAAA,EAAM,kBAAkB;EPST;EOPf,OAAA;EPQe;EONf,OAAA;AAAA;;;;;;;;;APQ8C;;;iBOMhC,oBAAA,CAAqB,KAAA,YAAiB,oBAAoB;ANtI1E;;;;AAA8B;AAA9B,iBMsOgB,0BAAA,CAA2B,MAAmB,EAAX,WAAW;;;KCjPlD,YAAA;;;ARyBZ;;;;;;;;;;iBQXgB,cAAA,CAAe,IAAA,EAAM,MAAA,EAAU,IAAA,EAAM,MAAA,GAAW,YAAA;;KAepD,aAAA;AAAA,iBAEI,aAAA,CAAc,IAAA,EAAM,MAAA,GAAW,aAAa"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/components.ts","../src/errors.ts","../src/delta.ts","../src/signature.ts","../src/similarity.ts","../src/match.ts","../src/slice.ts","../src/status.ts","../src/diff.ts"],"sourcesContent":["import type {\n Component,\n ComponentDiff,\n ComponentId,\n Dependency,\n DependencyDiff,\n DependencyEndpoint,\n DependencyUnknown,\n DiffSkippedFile,\n IR,\n RelativePath,\n SkipReason,\n} from \"@aburi/types\"\n\n/**\n * `docs/design/diff-algorithm.md` §6.1 — Component diff. Assumes `components[].id` is unique on each side (ir-schema.md §14\n * #2) and does not check it: `buildDiff` establishes that before calling, and a caller\n * reaching this export directly owns the obligation, because the lookup map here is\n * last-write-wins and a repeat is lost rather than reported.\n *\n * Component identity is `id`; when a Component is present in both\n * base and head with the same id, the three delta booleans describe which axes actually\n * moved (roots reshuffle, public API globs, or framework hint list). `modified` deltas\n * are intentionally absent from this diff (diff-algorithm.md §5.2.3): fields are reported as before/after\n * pairs via the `changed[]` entries so consumers can render them without stringifying\n * arrays.\n */\nexport function diffComponents(\n base: readonly Component[],\n head: readonly Component[],\n): ComponentDiff {\n const baseById = new Map<ComponentId, Component>()\n for (const c of base) baseById.set(c.id, c)\n const headById = new Map<ComponentId, Component>()\n for (const c of head) headById.set(c.id, c)\n\n const added: Component[] = []\n const removed: Component[] = []\n const changed: ComponentDiff[\"changed\"] = []\n\n for (const [id, headComp] of headById) {\n const baseComp = baseById.get(id)\n if (baseComp === undefined) {\n added.push(headComp)\n continue\n }\n const rootsChanged = !stringArraysEqual(baseComp.roots, headComp.roots)\n const publicApiChanged = !stringArraysEqual(baseComp.publicApi ?? [], headComp.publicApi ?? [])\n const frameworksChanged = !stringArraysEqual(\n baseComp.frameworks ?? [],\n headComp.frameworks ?? [],\n )\n if (rootsChanged || publicApiChanged || frameworksChanged) {\n changed.push({\n before: baseComp,\n after: headComp,\n delta: { rootsChanged, publicApiChanged, frameworksChanged },\n })\n }\n }\n for (const [id, baseComp] of baseById) {\n if (!headById.has(id)) removed.push(baseComp)\n }\n sortById(added)\n sortById(removed)\n changed.sort((a, b) => (a.after.id < b.after.id ? -1 : a.after.id > b.after.id ? 1 : 0))\n return { added, removed, changed }\n}\n\n/**\n * What one document knows about itself, for deciding whether the *other* document's silence\n * about an edge is evidence.\n *\n * `symbolFiles` is keyed on the endpoint id exactly as `dependencies[]` spells it, and its\n * values come from `symbols[].source.file` — the same space `stats.skippedFiles[].path` is in,\n * and the same space `buildDiff` classifies Symbols by. Reading the file out of the id's path\n * segment instead would introduce a second answer to \"which file is this endpoint in\", and\n * nothing in the schema forces the two to agree; a Symbol reported `unknown` while its edges\n * stayed `removed` is precisely the inconsistency this exists to remove.\n *\n * A Component endpoint is absent from the map, which is how it stays out of the reclassification\n * without a special case: a Component is an aggregate over roots and has no file to lose.\n */\nexport interface DependencySideView {\n /**\n * `source.file` of every Symbol this document holds, by the id an endpoint would name it\n * with. Keyed by `DependencyEndpoint` rather than `SymbolId` because that is the question\n * being asked: the lookup happens with an endpoint whose kind is not yet known, and \"absent\"\n * is the answer for a Component id. Demanding the kind up front would move that decision to\n * the caller, where it would need a second Symbol-id silhouette test to make.\n */\n symbolFiles: ReadonlyMap<DependencyEndpoint, RelativePath>\n /**\n * Files this document never analysed, by path, with the reason it gave. `RelativePath` on\n * both sides of the file space is what states the bridge these two maps exist to make — one\n * map's values are the other's keys — though the alias is unbranded, so it is intent rather\n * than enforcement.\n */\n lostFiles: ReadonlyMap<RelativePath, SkipReason>\n}\n\n/**\n * Build a side view from a document. The only construction site there is.\n *\n * `buildDiff` reads `lostFiles` for its own Symbol classification too, from this same object,\n * so \"a Symbol reported unknown and the edges it took with it cannot disagree about which file\n * went missing\" is a property of the wiring rather than a claim in a comment.\n *\n * Exported because `DependencySideView` is public and `diffDependencies` requires one: without\n * a factory a caller would have to reproduce the `symbols[].source.file` keying and the\n * `stats.skippedFiles` read, and the likeliest outcome of that is a caller who gets it wrong.\n */\nexport function dependencySideView(ir: IR): DependencySideView {\n const symbolFiles = new Map<DependencyEndpoint, RelativePath>()\n for (const symbol of ir.symbols) symbolFiles.set(symbol.id, symbol.source.file)\n const lostFiles = new Map<RelativePath, SkipReason>()\n for (const file of ir.stats.skippedFiles ?? []) lostFiles.set(file.path, file.reason)\n return { symbolFiles, lostFiles }\n}\n\n/**\n * `docs/design/diff-algorithm.md` §6.2 — Dependency diff. Identity is the composite\n * `(from, to, via)` triple; direction and effect changes are surfaced as an added + removed\n * pair so `modified` is not part of the schema (§6.2 tail). Uniqueness of that triple is the caller's obligation on the same\n * terms as `diffComponents` above — a repeat here is indistinguishable in the output from\n * the flip it encodes.\n *\n * `sides` is what separates a deletion from a loss (`docs/design/diff-algorithm.md` §6.2.1).\n * It is required rather than optional: omitting it would classify every edge into a lost file\n * as a deletion again — silently, and while still writing `unknown: []`, which this schema\n * defines as \"nothing was unknown\" rather than \"nobody looked\". A caller with no skip list to\n * offer says so by passing a side view whose `lostFiles` is empty, which is the honest spelling\n * of an IR written before `stats.skippedFiles` existed.\n *\n * The return type declares `unknown` present, where the schema leaves it optional for documents\n * that predate the field. A reader may find it missing; this function always writes it.\n */\nexport function diffDependencies(\n base: readonly Dependency[],\n head: readonly Dependency[],\n sides: { base: DependencySideView; head: DependencySideView },\n): DependencyDiff & { unknown: DependencyUnknown[] } {\n const baseKeys = new Map<string, Dependency>()\n for (const d of base) baseKeys.set(dependencyKey(d), d)\n const headKeys = new Map<string, Dependency>()\n for (const d of head) headKeys.set(dependencyKey(d), d)\n const added: Dependency[] = []\n const removed: Dependency[] = []\n const unknown: DependencyUnknown[] = []\n for (const [key, dep] of headKeys) {\n const b = baseKeys.get(key)\n if (b === undefined) {\n // Held by head and not by base, so its endpoints resolve against head — the document\n // that has the Symbols — and the question is whether base could have seen them.\n const lostFiles = endpointsLostBy(dep, sides.head, sides.base)\n if (lostFiles.length > 0) unknown.push({ dependency: dep, absentFrom: \"base\", lostFiles })\n else added.push(dep)\n continue\n }\n // A direction or effect flip. No loss check: both documents hold the edge, so neither is\n // silent about it, and `unknown` exists only to explain a silence. That holds without\n // appealing to any invariant — nothing forbids a path from being both a `source.file` and a\n // skipped one, so \"they both have it, therefore neither lost the file\" would not be sound.\n if (b.direction !== dep.direction || (b.effect ?? null) !== (dep.effect ?? null)) {\n removed.push(b)\n added.push(dep)\n }\n }\n for (const [key, dep] of baseKeys) {\n if (headKeys.has(key)) continue\n const lostFiles = endpointsLostBy(dep, sides.base, sides.head)\n if (lostFiles.length > 0) unknown.push({ dependency: dep, absentFrom: \"head\", lostFiles })\n else removed.push(dep)\n }\n added.sort(compareDependencies)\n removed.sort(compareDependencies)\n unknown.sort((a, b) => compareDependencies(a.dependency, b.dependency))\n return { added, removed, unknown }\n}\n\n/**\n * The endpoint files `absent` never analysed, read through `holder` because that is the\n * document the edge — and therefore the Symbol behind each endpoint — actually comes from.\n *\n * Both endpoints are checked: an edge dies when *either* end's file goes, including one whose\n * other end survived, and that half is the easiest to miss because the surviving Symbol is\n * right there in both documents.\n *\n * Deduped and sorted by path, so an intra-file edge collapses to the one file it lost.\n */\nfunction endpointsLostBy(\n dep: Dependency,\n holder: DependencySideView,\n absent: DependencySideView,\n): DiffSkippedFile[] {\n // Keyed by path, so an intra-file edge collapses to the one file it lost. Keying by reason\n // instead would look identical on every fixture whose two endpoints were skipped for\n // different reasons, and silently drop one of two files skipped for the same reason.\n const byPath = new Map<RelativePath, SkipReason>()\n for (const endpoint of [dep.from, dep.to]) {\n const file = holder.symbolFiles.get(endpoint)\n // Normally a Component endpoint: an aggregate over roots, with no file to lose, which is\n // why component-level edges need no special case to stay out of this.\n //\n // The other way to land here is a symbol-shaped endpoint with no Symbol behind it, which\n // `ir-schema.md` §14 #4 forbids. `buildDiff` does not run the integrity checker, and the\n // CLI cannot reach it because `readIR` rejects such a document first — so this is a library\n // caller who assembled an IR by hand, and the edge quietly reverts to the misclassification\n // this function exists to remove. Treated the same as a Component endpoint deliberately:\n // there is no diagnostics channel here, and refusing would take down the legitimate case\n // that shares the branch.\n if (file === undefined) continue\n const reason = absent.lostFiles.get(file)\n if (reason === undefined) continue\n byPath.set(file, reason)\n }\n return [...byPath.entries()]\n .map(([path, reason]) => ({ path, reason }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n}\n\n/**\n * `docs/design/diff-algorithm.md` §6.2 — the fields Dependency identity is made of, in key order, and the join that turns\n * them into one. Both halves are exported so the entry-point uniqueness check keys on\n * exactly what this file keys on — a check with its own notion of identity would let through\n * the duplicates that actually collide here, and a check that shared only the join would\n * still be free to disagree about which fields identity is made of.\n *\n * Core's invariant #13 joins the same triple with a different separator. The two agree for\n * every endpoint that satisfies the id grammars of ir-schema.md §3.1 and §4, which is every\n * Document the integrity checker accepts; they could in principle disagree for one that does\n * not, and `buildDiff` runs no grammar check on the IRs it is handed.\n */\nexport const DEPENDENCY_IDENTITY_FIELDS = [\"from\", \"to\", \"via\"] as const\n\nexport function dependencyIdentity(parts: readonly string[]): string {\n return parts.join(\"::\")\n}\n\nfunction dependencyKey(d: Dependency): string {\n return dependencyIdentity(DEPENDENCY_IDENTITY_FIELDS.map((field) => d[field]))\n}\n\nfunction compareDependencies(a: Dependency, b: Dependency): number {\n const keyA = dependencyKey(a)\n const keyB = dependencyKey(b)\n return keyA < keyB ? -1 : keyA > keyB ? 1 : 0\n}\n\nfunction stringArraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\n\nfunction sortById<T extends { id: string }>(items: T[]): void {\n items.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n}\n","/**\n * Coded error class for every @aburi/diff failure. Consumers branch on `code` without\n * parsing message text. Codes are stable and additions are non-breaking; renames are not.\n */\n\nexport type DiffErrorCode =\n /** `base.$schema` and `head.$schema` disagree; a diff across schema versions cannot be trusted (§9.1). */\n | \"schema-mismatch\"\n /** `config.diff.lineFuzz` was outside the documented [0, 10] range (diff-algorithm.md §5.2.1). */\n | \"invalid-line-fuzz\"\n /**\n * `baseIR` or `headIR` failed the top-level shape check that `buildDiff` enforces\n * before touching any Symbol. Missing / non-array `symbols`, `components`,\n * `dependencies`, or an absent `$schema` — anything that would otherwise crash with a\n * cryptic `TypeError: undefined is not iterable` deep in a matching stage.\n */\n | \"ir-shape-invalid\"\n /**\n * `baseIR` or `headIR` repeats an identity the diff keys on: a `symbols[].id`, a\n * `components[].id`, or a `dependencies[]` `(from, to, via)` triple. Distinct from\n * `ir-shape-invalid` because the Document is well-formed — the collision changes the\n * answer rather than preventing one, and ir-schema.md §14 #1 / #2 / #13 already forbid it.\n */\n | \"ir-identity-collision\"\n /**\n * A `SliceRecord` broke the derivation invariant of slice-view.md §7.1 / §8.2:\n * `members[]` empty, `members[]` not in strictly ascending order, or `id` not\n * equal to `\"slice:\" + members[0]`. The last two compare one property against\n * another and so have no JSON Schema equivalent; the pass checks them itself\n * (§7.4). Raised only by an Aburi bug, never by user input — see\n * `SliceRecordViolation.kind` for which clause broke.\n */\n | \"slice-invariant-violated\"\n\nexport interface DiffErrorDetail {\n code: DiffErrorCode\n /** Offending value (schema id, fuzz value, etc.) when applicable. */\n value?: string\n}\n\nexport class DiffError extends Error {\n readonly code: DiffErrorCode\n readonly value: string | undefined\n\n constructor(message: string, detail: DiffErrorDetail, options?: { cause?: unknown }) {\n super(message, options)\n this.name = \"DiffError\"\n this.code = detail.code\n this.value = detail.value\n }\n}\n","import type {\n ArrayDelta,\n Call,\n Decorator,\n Effect,\n Symbol as IRSymbol,\n Rule,\n Signature,\n SignatureDelta,\n SymbolDelta,\n} from \"@aburi/types\"\nimport { DiffError } from \"./errors\"\n\nexport const DEFAULT_LINE_FUZZ = 2\nexport const MAX_LINE_FUZZ = 10\nexport const MIN_LINE_FUZZ = 0\n\nexport interface DeltaOptions {\n /**\n * §5.2.1 line fuzz for rule/call identity. Must be an integer in\n * `[MIN_LINE_FUZZ, MAX_LINE_FUZZ]` (`0..10`); anything outside — or a non-finite value —\n * throws `DiffError({ code: \"invalid-line-fuzz\" })`. Setting `0` disables fuzz; omitting\n * the field falls back to `DEFAULT_LINE_FUZZ` (2).\n */\n lineFuzz?: number\n}\n\n/**\n * §5 — Compute the full per-Symbol delta between two paired Symbols. The three axis\n * booleans (`apiChanged` / `logicChanged` / `syntaxChanged`) come from fingerprint\n * comparison; the array deltas (`rules` / `effects` / `calls` / `decorators`) come from\n * identity-preserving diff with configurable line fuzz.\n */\nexport function computeSymbolDelta(\n base: IRSymbol,\n head: IRSymbol,\n options: DeltaOptions = {},\n): SymbolDelta {\n const fuzz = validateLineFuzz(options.lineFuzz ?? DEFAULT_LINE_FUZZ)\n const delta: SymbolDelta = {\n apiChanged: base.fingerprint.api !== head.fingerprint.api,\n logicChanged: base.fingerprint.logic !== head.fingerprint.logic,\n syntaxChanged: base.fingerprint.syntax !== head.fingerprint.syntax,\n componentChanged: (base.component ?? null) !== (head.component ?? null),\n visibilityChanged: base.visibility !== head.visibility,\n }\n delta.rules = diffRules(base.rules, head.rules, fuzz)\n delta.effects = diffEffects(base.effects, head.effects)\n delta.calls = diffCalls(base.calls, head.calls, fuzz)\n delta.decorators = diffDecorators(base.decorators, head.decorators, fuzz)\n const sigDelta = diffSignature(base.signature ?? null, head.signature ?? null)\n delta.signature = sigDelta\n return delta\n}\n\n/**\n * §5.2.1 range check. Loud on violation rather than silently clamping so a config typo\n * (`lineFuzz: 999`) or an upstream numeric bug (`NaN`, `Infinity`) surfaces at the diff\n * boundary instead of quietly rounding into a range that produces the wrong deltas.\n */\nfunction validateLineFuzz(value: number): number {\n if (!Number.isFinite(value) || !Number.isInteger(value)) {\n throw new DiffError(\n `config.diff.lineFuzz must be an integer in [${MIN_LINE_FUZZ}, ${MAX_LINE_FUZZ}]; got ${String(value)}.`,\n { code: \"invalid-line-fuzz\", value: String(value) },\n )\n }\n if (value < MIN_LINE_FUZZ || value > MAX_LINE_FUZZ) {\n throw new DiffError(\n `config.diff.lineFuzz must be within [${MIN_LINE_FUZZ}, ${MAX_LINE_FUZZ}]; got ${value}.`,\n { code: \"invalid-line-fuzz\", value: String(value) },\n )\n }\n return value\n}\n\ninterface Identified<T> {\n item: T\n key: string\n line: number\n}\n\n/**\n * §5.2 — pair the two sides by identity key within ±`lineFuzz`, then classify each element\n * into `added` / `removed` / `modified`. `modified` fires only when a pairing holds and the\n * content differs, so a cosmetic line shift produces nothing.\n *\n * Several elements of one Symbol routinely share a key — two `guard` rules, two calls to one\n * target, two `@Get` — so which base element a head element takes is a real choice. Two\n * passes make it: first the elements whose key **and content** agree, then whatever is left.\n * An untouched element is therefore claimed by its own counterpart before an edited or\n * deleted neighbour can take it, and the remainder pairs by proximity — where a genuine edit\n * lands. Taking the first key hit instead reported a deleted `guard@1` by removing its\n * untouched `guard@3` neighbour *and* listing that same neighbour as modified.\n *\n * Each pass is an order-preserving assignment rather than a per-element search, because a\n * greedy pass takes a pairing that leaves a better set unreachable. Two identical guards\n * shifted down two lines are the smallest case: the first head element is nearest to the\n * *second* base element, and claiming it strands the other head outside the window, so a\n * block that moved intact comes back as an `added` and a `removed`.\n */\nfunction differentiate<T>(\n base: readonly Identified<T>[],\n head: readonly Identified<T>[],\n isEqual: (a: T, b: T) => boolean,\n lineFuzz: number,\n): ArrayDelta {\n const freeBase = new Set(base.map((_, index) => index))\n const freeHead = new Set(head.map((_, index) => index))\n const partnerOf = new Map<number, Identified<T>>()\n for (const contentMustAgree of [true, false]) {\n const admits = (b: Identified<T>, h: Identified<T>): boolean =>\n b.key === h.key &&\n Math.abs(b.line - h.line) <= lineFuzz &&\n (!contentMustAgree || isEqual(b.item, h.item))\n for (const [baseIndex, headIndex] of assignInOrder(base, head, freeBase, freeHead, admits)) {\n freeBase.delete(baseIndex)\n freeHead.delete(headIndex)\n const counterpart = base[baseIndex]\n if (counterpart !== undefined) partnerOf.set(headIndex, counterpart)\n }\n }\n\n const added: T[] = []\n const modified: T[] = []\n for (const [index, h] of head.entries()) {\n const counterpart = partnerOf.get(index)\n if (counterpart === undefined) added.push(h.item)\n else if (!isEqual(counterpart.item, h.item)) modified.push(h.item)\n }\n const removed = base.filter((_, index) => freeBase.has(index)).map((b) => b.item)\n return { added, removed, modified }\n}\n\n/** How good an assignment is: more pairings first, then less total line movement. */\ninterface Quality {\n pairs: number\n distance: number\n}\n\nconst NO_PAIRINGS: Quality = { pairs: 0, distance: 0 }\n\n/** `a` is preferred over `b`: more pairings wins, and among equals the tighter one does. */\nfunction better(a: Quality, b: Quality): boolean {\n return a.pairs !== b.pairs ? a.pairs > b.pairs : a.distance < b.distance\n}\n\n/**\n * The best set of non-crossing pairings between the still-free elements of `base` and `head`,\n * as `[baseIndex, headIndex]` in ascending order — most pairings first, then least total line\n * movement.\n *\n * Non-crossing is the whole content of the rule, and ir-schema §14 #11 is what licenses it:\n * these arrays are ordered by line, so `i < j` means element `i` sits above element `j` in the\n * file. Two pairings that cross would have an element move above one it was below, which is\n * not a line shift — it is a different element. Restricting to non-crossing sets also makes\n * the optimum reachable by a suffix recurrence rather than by a general assignment algorithm.\n *\n * Maximising the count before minimising distance is what stops a near pairing from being\n * taken at the cost of a far one that would otherwise have no partner at all.\n */\nfunction assignInOrder<T>(\n base: readonly Identified<T>[],\n head: readonly Identified<T>[],\n freeBase: ReadonlySet<number>,\n freeHead: ReadonlySet<number>,\n admits: (b: Identified<T>, h: Identified<T>) => boolean,\n): Array<[number, number]> {\n // best[i][j] is the quality of the best assignment over base[i..] and head[j..].\n const best: Quality[][] = Array.from({ length: base.length + 1 }, () =>\n Array.from({ length: head.length + 1 }, () => NO_PAIRINGS),\n )\n const pairingAt = (i: number, j: number): Quality | null => {\n const b = base[i]\n const h = head[j]\n if (b === undefined || h === undefined) return null\n if (!freeBase.has(i) || !freeHead.has(j) || !admits(b, h)) return null\n const rest = best[i + 1]?.[j + 1] ?? NO_PAIRINGS\n return { pairs: rest.pairs + 1, distance: rest.distance + Math.abs(b.line - h.line) }\n }\n for (let i = base.length - 1; i >= 0; i--) {\n for (let j = head.length - 1; j >= 0; j--) {\n const skipBase = best[i + 1]?.[j] ?? NO_PAIRINGS\n const skipHead = best[i]?.[j + 1] ?? NO_PAIRINGS\n let winner = better(skipBase, skipHead) ? skipBase : skipHead\n const paired = pairingAt(i, j)\n if (paired !== null && better(paired, winner)) winner = paired\n const row = best[i]\n if (row !== undefined) row[j] = winner\n }\n }\n\n // Walk the table back down, taking a pairing wherever it is what the optimum was built from.\n const chosen: Array<[number, number]> = []\n let i = 0\n let j = 0\n while (i < base.length && j < head.length) {\n const here = best[i]?.[j] ?? NO_PAIRINGS\n const paired = pairingAt(i, j)\n if (paired !== null && paired.pairs === here.pairs && paired.distance === here.distance) {\n chosen.push([i, j])\n i++\n j++\n continue\n }\n const skipBase = best[i + 1]?.[j] ?? NO_PAIRINGS\n if (skipBase.pairs === here.pairs && skipBase.distance === here.distance) i++\n else j++\n }\n return chosen\n}\n\nfunction diffRules(base: readonly Rule[], head: readonly Rule[], lineFuzz: number): ArrayDelta {\n const mapper = (r: Rule): Identified<Rule> => ({\n item: r,\n key: r.type,\n line: r.line,\n })\n return differentiate(base.map(mapper), head.map(mapper), rulesEqual, lineFuzz)\n}\n\nfunction rulesEqual(a: Rule, b: Rule): boolean {\n return (\n a.type === b.type &&\n (a.condition ?? null) === (b.condition ?? null) &&\n (a.what ?? null) === (b.what ?? null) &&\n (a.expr ?? null) === (b.expr ?? null) &&\n (a.loopKind ?? null) === (b.loopKind ?? null)\n )\n}\n\nfunction diffEffects(base: readonly Effect[], head: readonly Effect[]): ArrayDelta {\n const mapper = (e: Effect): Identified<Effect> => ({\n item: e,\n key: `${e.id}::${e.target}`,\n // Propagated entries (effect-propagation.md §5.1) omit `line`. An infinite fuzz admits\n // every same-key candidate, so no placeholder can put an effect outside the window — but\n // §5.2.0 ranks the admitted candidates by distance, so `0` does read as \"at the top of\n // the Symbol\" and a propagated effect prefers the earliest local one carrying its key.\n // Effects are already keyed by `(id, target)`, which is the whole of their identity in\n // ir-schema §7, so a tie only arises between two entries that agree on it; the choice\n // between those is what §5.2.0's exact-content pass settles.\n line: e.line ?? 0,\n })\n return differentiate(base.map(mapper), head.map(mapper), effectsEqual, Number.POSITIVE_INFINITY)\n}\n\nfunction effectsEqual(a: Effect, b: Effect): boolean {\n return (\n a.id === b.id && a.target === b.target && a.plugin === b.plugin && a.confidence === b.confidence\n )\n}\n\nfunction diffCalls(base: readonly Call[], head: readonly Call[], lineFuzz: number): ArrayDelta {\n const mapper = (c: Call): Identified<Call> => ({\n item: c,\n key: c.target,\n line: c.line,\n })\n return differentiate(base.map(mapper), head.map(mapper), callsEqual, lineFuzz)\n}\n\nfunction callsEqual(a: Call, b: Call): boolean {\n return a.target === b.target && (a.resolved ?? null) === (b.resolved ?? null)\n}\n\n/**\n * §5.2.2 — decorator diff. Identity is `name`; when the same-named decorator is present\n * on both sides within the line-fuzz window, the argument list determines whether the\n * entry is a modified pair or an implicit no-op.\n */\nfunction diffDecorators(\n base: readonly Decorator[],\n head: readonly Decorator[],\n lineFuzz: number,\n): ArrayDelta {\n const mapper = (d: Decorator): Identified<Decorator> => ({\n item: d,\n key: d.name,\n line: d.line,\n })\n return differentiate(base.map(mapper), head.map(mapper), decoratorsEqual, lineFuzz)\n}\n\nfunction decoratorsEqual(a: Decorator, b: Decorator): boolean {\n if (a.name !== b.name) return false\n if (a.arguments.length !== b.arguments.length) return false\n for (let i = 0; i < a.arguments.length; i++) {\n if (a.arguments[i] !== b.arguments[i]) return false\n }\n return true\n}\n\n/**\n * §5.3 — signature delta. Three cases:\n * - both sides `null` → `null` (nothing to compare)\n * - exactly one side `null` → the present side is emitted verbatim as\n * `added` (when only head has a signature) or `removed` (when only base has one);\n * the three axis booleans compare the missing side against `false` defaults\n * - both sides non-null → per-list sub-deltas:\n * - `inputs`: strict positional compare via `differentiate(..., lineFuzz: 0)` — index\n * used as the line key so ordered parameter lists match position-for-position\n * - `outputs`: positional compare (added/removed emitted when the arrays diverge at\n * an index), no `modified` category\n * - `throws`: unordered set diff — duplicates collapse per Set semantics\n * - `async` / `generator` / `typeParameters` change flags follow the raw fields\n */\nfunction diffSignature(base: Signature | null, head: Signature | null): SignatureDelta | null {\n if (base === null && head === null) return null\n if (base === null || head === null) {\n const empty: ArrayDelta = { added: [], removed: [], modified: [] }\n const present = base ?? head\n if (present === null) return null\n const isBaseNull = base === null\n const inputs: ArrayDelta = isBaseNull\n ? { added: [...present.inputs], removed: [], modified: [] }\n : { added: [], removed: [...present.inputs], modified: [] }\n const outputs: ArrayDelta = isBaseNull\n ? { added: [...present.outputs], removed: [], modified: [] }\n : { added: [], removed: [...present.outputs], modified: [] }\n const throws: ArrayDelta = isBaseNull\n ? { added: [...present.throws], removed: [], modified: [] }\n : { added: [], removed: [...present.throws], modified: [] }\n return {\n inputs,\n outputs,\n throws,\n asyncChanged: (base?.async ?? false) !== (head?.async ?? false),\n generatorChanged: (base?.generator ?? false) !== (head?.generator ?? false),\n typeParametersChanged: !arraysEqual(\n base?.typeParameters ?? empty.added.map(String),\n head?.typeParameters ?? empty.added.map(String),\n ),\n }\n }\n /**\n * Parameters are positional, so the index is part of the identity and the fuzz below is 0.\n * That makes each key unique within its list, so §5.2.0 never has a choice to make here and\n * both of its passes reduce to the same positional comparison. Dropping the index from the\n * key would make two same-named parameters ambiguous and hand the choice back to §5.2.0,\n * which is not what a positional list wants.\n */\n const inputMapper = (\n input: { name: string; type: string },\n index: number,\n ): Identified<{ name: string; type: string }> => ({\n item: input,\n key: `${index}:${input.name}`,\n line: index,\n })\n const inputs = differentiate(\n base.inputs.map(inputMapper),\n head.inputs.map(inputMapper),\n (a, b) => a.name === b.name && a.type === b.type,\n 0,\n )\n const outputs = diffStringList(base.outputs, head.outputs)\n const throws = diffStringSet(base.throws, head.throws)\n return {\n inputs,\n outputs,\n throws,\n asyncChanged: base.async !== head.async,\n generatorChanged: base.generator !== head.generator,\n typeParametersChanged: !arraysEqual(base.typeParameters, head.typeParameters),\n }\n}\n\nfunction diffStringList(base: readonly string[], head: readonly string[]): ArrayDelta {\n const added: string[] = []\n const removed: string[] = []\n const max = Math.max(base.length, head.length)\n for (let i = 0; i < max; i++) {\n const b = i < base.length ? base[i] : undefined\n const h = i < head.length ? head[i] : undefined\n if (b === undefined && h !== undefined) added.push(h)\n else if (h === undefined && b !== undefined) removed.push(b)\n else if (b !== h) {\n if (b !== undefined) removed.push(b)\n if (h !== undefined) added.push(h)\n }\n }\n return { added, removed, modified: [] }\n}\n\nfunction diffStringSet(base: readonly string[], head: readonly string[]): ArrayDelta {\n const setB = new Set(base)\n const setH = new Set(head)\n const added: string[] = []\n const removed: string[] = []\n for (const h of setH) if (!setB.has(h)) added.push(h)\n for (const b of setB) if (!setH.has(b)) removed.push(b)\n return { added, removed, modified: [] }\n}\n\nfunction arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\n","import type { Signature } from \"@aburi/types\"\n\n/**\n * §3.4.2 signatureSimilarity — averaged type-similarity across `inputs`, `outputs`, and\n * `throws`. Follows diff-algorithm.md §3.4.2:\n * - null + null → 1.0 (both symbols are signature-less; nothing to compare)\n * - null + non-null (either side) → 0.0\n * - both non-null: mean of three subscores\n *\n * Subscores:\n * - inputs: ordered type-string equality rate (`i-th type equal` counts as 1, else 0)\n * - outputs: same shape, but outputs is an array of type strings\n * - throws: unordered set intersection over union\n *\n * Empty subarrays on both sides count as 1.0 (they are trivially equal); on one side only\n * they count as 0.0 (asymmetric information).\n */\nexport function signatureSimilarity(\n base: Signature | null | undefined,\n head: Signature | null | undefined,\n): number {\n const baseSig = base ?? null\n const headSig = head ?? null\n if (baseSig === null && headSig === null) return 1\n if (baseSig === null || headSig === null) return 0\n const inputsScore = compareOrderedTypes(\n baseSig.inputs.map((i) => i.type),\n headSig.inputs.map((i) => i.type),\n )\n const outputsScore = compareOrderedTypes(baseSig.outputs, headSig.outputs)\n const throwsScore = compareUnorderedSet(baseSig.throws, headSig.throws)\n return (inputsScore + outputsScore + throwsScore) / 3\n}\n\nfunction compareOrderedTypes(a: readonly string[], b: readonly string[]): number {\n if (a.length === 0 && b.length === 0) return 1\n if (a.length !== b.length) return 0\n let matches = 0\n for (let i = 0; i < a.length; i++) {\n if (a[i] === b[i]) matches++\n }\n return matches / a.length\n}\n\nfunction compareUnorderedSet(a: readonly string[], b: readonly string[]): number {\n if (a.length === 0 && b.length === 0) return 1\n const setA = new Set(a)\n const setB = new Set(b)\n let intersection = 0\n for (const t of setA) if (setB.has(t)) intersection++\n const union = setA.size + setB.size - intersection\n return union === 0 ? 1 : intersection / union\n}\n","/**\n * String similarity utilities dedicated to the diff engine. Kept in one file so the\n * name/signature/owner formulas are auditable side by side against diff-algorithm.md §3.4.\n */\n\n/**\n * Tokenise a qualified name into normalised lowercase word segments. Splits on:\n * - camelCase / PascalCase boundaries (`fooBar` → `foo`, `Bar`)\n * - snake_case underscores (`foo_bar` → `foo`, `bar`)\n * - dotted namespaces (`ns.Class` → `ns`, `Class`)\n * - `::` static-method separator (`Class::method` → `Class`, `method`)\n * - runs of digits (`get2Users` → `get`, `2`, `Users`)\n *\n * The result is de-duplicated so `InvoiceService.createInvoice` collapses to\n * `[\"invoice\", \"service\", \"create\"]` — Jaccard similarity relies on set semantics, so\n * repeated tokens must not double-count.\n *\n * **The camel boundary is ASCII.** `isCamelBoundary` compares code points against `a`–`z`,\n * `A`–`Z` and `0`–`9`, so a name written in a script with no ASCII case boundary and no\n * separator comes back whole: `获取用户信息` and `ユーザー情報を取得する` are one token each,\n * and so is `получитьПользователя` — its camel hump does not register. A separator still\n * splits (`ユーザー.取得` gives two), and a mixed name splits on its ASCII half.\n *\n * That makes the token count a poor measure of how much such a name says, which matters\n * wherever a count is read as a proxy for that — see §3.4.3's admissibility rule.\n */\nexport function tokenizeName(input: string): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const raw of input.split(/[.:_\\-\\s/]+/)) {\n if (raw.length === 0) continue\n for (const piece of splitCamel(raw)) {\n const norm = piece.toLowerCase()\n if (norm.length === 0 || seen.has(norm)) continue\n seen.add(norm)\n out.push(norm)\n }\n }\n return out\n}\n\nfunction splitCamel(word: string): string[] {\n const chunks: string[] = []\n let current = \"\"\n for (const ch of word) {\n if (current === \"\") {\n current = ch\n continue\n }\n const prev = current.slice(-1)\n const boundary = isCamelBoundary(prev, ch)\n if (boundary) {\n chunks.push(current)\n current = ch\n } else {\n current += ch\n }\n }\n if (current.length > 0) chunks.push(current)\n return chunks\n}\n\nfunction isCamelBoundary(prev: string, curr: string): boolean {\n const prevLower = prev >= \"a\" && prev <= \"z\"\n const prevUpper = prev >= \"A\" && prev <= \"Z\"\n const prevDigit = prev >= \"0\" && prev <= \"9\"\n const currLower = curr >= \"a\" && curr <= \"z\"\n const currUpper = curr >= \"A\" && curr <= \"Z\"\n const currDigit = curr >= \"0\" && curr <= \"9\"\n if (prevLower && currUpper) return true\n if ((prevLower || prevUpper) && currDigit) return true\n if (prevDigit && (currLower || currUpper)) return true\n return false\n}\n\n/**\n * Jaccard similarity between two token multisets. Formula: |A ∩ B| / |A ∪ B|.\n * Empty on both sides returns 1.0 (both are \"no tokens\", which are equivalent for the\n * purpose of §3.4.1). Empty on one side only returns 0.0.\n */\nexport function jaccard(a: readonly string[], b: readonly string[]): number {\n return jaccardSets(new Set(a), new Set(b))\n}\n\nfunction jaccardSets(a: ReadonlySet<string>, b: ReadonlySet<string>): number {\n if (a.size === 0 && b.size === 0) return 1\n if (a.size === 0 || b.size === 0) return 0\n const [small, large] = a.size <= b.size ? [a, b] : [b, a]\n let intersection = 0\n for (const token of small) if (large.has(token)) intersection++\n return intersection / (a.size + b.size - intersection)\n}\n\n/**\n * Jaccard over the tokens of two strings. Part of the module's surface rather than an\n * internal shortcut: the two formulas below go through a token table they share for one\n * matching pass, so this has no caller inside this file.\n */\nexport function jaccardTokens(a: string, b: string): number {\n return jaccard(tokenizeName(a), tokenizeName(b))\n}\n\n/** How a formula gets the token set of a name. The only thing the memo below changes. */\ntype TokenSetOf = (value: string) => ReadonlySet<string>\n\nconst tokenizeEveryTime: TokenSetOf = (value) => new Set(tokenizeName(value))\n\n/**\n * §3.4.1 — Jaccard over the tokens of the full qualified name. The last segment is not\n * given extra weight; the entire path contributes uniformly because the intersection is\n * what the score should reward.\n */\nexport function nameSimilarity(baseName: string, headName: string): number {\n return nameFormula(tokenizeEveryTime, baseName, headName)\n}\n\n/**\n * §3.4.1 — Jaccard over the tokens of the **last segment**: the member name with its owner\n * removed. What §3.4's composite reads, because §3.4.6 decides the owner separately and\n * reading it on both axes charges for it twice.\n */\nexport function memberSimilarity(baseName: string, headName: string): number {\n return memberFormula(tokenizeEveryTime, baseName, headName)\n}\n\n/**\n * §3.4.6 (R-8) — whether two Symbols are close enough in *scope* to be the same Symbol: the\n * same owner, or one whose owner was renamed. A gate rather than a score, because grading the\n * owner cannot do what R-8 asks. `UserRepo.findById` and `AdminRepo.findById` agree on their\n * member name and their signature, so a shared `Repo` token at weight 0.2 carries them to\n * 0.8667 against a 0.85 threshold — while `UsersRepository.findById`, which *is* the rename,\n * shares no owner token and scores 0.8000. The collision outscores the rename, and raising the\n * weight only moves both: at 0.3 two three-token class names sharing two tokens land on\n * exactly 0.85.\n *\n * Owner extraction:\n * - `Class::method` → `Class`\n * - `A.B.C.method` → `A.B.C`\n * - `topLevel` → `` (empty)\n *\n * Two empty owners are compatible: top-level Symbols share the one outer scope. One empty and\n * one not are never compatible — the two live at different depths.\n *\n * Otherwise the owners must correspond segment for segment, and within a segment every token\n * on each side must find a distinct partner on the other under `sameWord`. Both sides must be\n * covered, so `UserRepo` and `UserRepoV2` are two classes rather than one renamed — an added\n * token is as much evidence of a sibling as of a rename, and R-8's business is refusing the\n * collision.\n */\nexport function ownersAreCompatible(baseName: string, headName: string): boolean {\n return ownerGate(tokenizeEveryTime, baseName, headName)\n}\n\n/**\n * §3.4.6 — the tokens `a` and `b` name the same thing.\n *\n * Equal, or the same word inflected: `user`/`users`, `entity`/`entities`. Nothing else, and\n * that is the whole of the rule rather than a first approximation.\n *\n * A bare prefix test is the obvious generalisation and it cannot be made to work. It admits\n * `repo`/`report`, `cache`/`cached`, `con`/`controller` — two distinct classes, which is\n * exactly the collision R-8 exists to refuse. Every measure that might separate those from a\n * real rename fails, because the two populations interleave rather than sitting on opposite\n * sides of anything:\n *\n * ```\n * dice levenshtein\n * accept UserRepo/UsersRepository 0.571 7\n * reject RepoManager/ReportManager 0.818 2\n * accept Repo/Repository 0.500 6\n * reject CacheStore/CachedStore 0.842 1\n * ```\n *\n * The renames to accept score *lower* than the collisions to refuse, on both. There is no\n * threshold, and a length-growth rule fares no better: `con`→`controller` grows by 7 and\n * `user`→`users` by 1, so anything admitting the second admits the first.\n *\n * Inflection is not on that spectrum. It is a closed, mechanical relation between two spellings\n * of one word, so it can be recognised rather than estimated — and it covers the case a plain\n * equality test misses most often, a class pluralised in place.\n *\n * What this gives up is the abbreviation family: `UserRepo` → `UsersRepository` no longer\n * clears the gate on `repo`/`repository`. That is a real rename reported as `added` +\n * `removed`, and §3.4.6 records it as the price of refusing `repo`/`report`, which no rule\n * over the two strings alone can tell apart. The evidence that would settle it is not in the\n * strings: the owner is itself a Symbol, and whether *it* paired is the question being\n * guessed at here.\n */\nfunction sameWord(a: string, b: string): boolean {\n if (a === b) return true\n const [short, long] = a.length <= b.length ? [a, b] : [b, a]\n return pluralises(short, long)\n}\n\n/**\n * The longest owner segment the matching will search.\n *\n * `augment` is Kuhn's, so the search is cubic in the token count and recursive with it. A\n * class name is a handful of words; anything past this is not a name the gate has an opinion\n * about, and `buildDiff` takes IR JSON from a caller rather than only from `aburi scan`, so\n * the bound is what turns an adversarial 1600-token identifier from a hang into an answer.\n * Refusing is the safe direction: the pair falls to `added` + `removed`.\n */\nconst MAX_OWNER_SEGMENT_TOKENS = 32\n\n/** English noun inflection, which is what a pluralised class name goes through. */\nfunction pluralises(singular: string, plural: string): boolean {\n if (plural === `${singular}s` || plural === `${singular}es`) return true\n return singular.endsWith(\"y\") && plural === `${singular.slice(0, -1)}ies`\n}\n\nfunction nameFormula(setOf: TokenSetOf, baseName: string, headName: string): number {\n return jaccardSets(setOf(baseName), setOf(headName))\n}\n\nfunction memberFormula(setOf: TokenSetOf, baseName: string, headName: string): number {\n return jaccardSets(setOf(lastSegment(baseName)), setOf(lastSegment(headName)))\n}\n\nfunction ownerGate(setOf: TokenSetOf, baseName: string, headName: string): boolean {\n const baseOwner = extractOwner(baseName)\n const headOwner = extractOwner(headName)\n if (baseOwner === \"\" && headOwner === \"\") return true\n if (baseOwner === \"\" || headOwner === \"\") return false\n // The common case, and the one a bucket of methods on one class hits every time.\n if (baseOwner === headOwner) return true\n return segmentsCorrespond(baseOwner, headOwner, setOf)\n}\n\n/**\n * §3.4.6 — the two owners name the same scope: segment for segment, token for token.\n *\n * Compared per segment rather than over the owner as a whole, because `tokenizeName` dedups\n * and an owner is a *path*. `Users.UserRepo` collapses to `{users, user, repo}` while\n * `Users.UsersRepository` collapses to `{users, repository}` — the namespace and the class\n * share a word, so one side loses a token and the two stop being comparable before any\n * spelling is looked at. Segment by segment they line up: `Users`/`Users`, then\n * `UserRepo`/`UsersRepository`.\n *\n * A rename changes what a class is called, not how deeply it is nested, so a differing segment\n * count is a differing scope.\n */\nfunction segmentsCorrespond(baseOwner: string, headOwner: string, setOf: TokenSetOf): boolean {\n const baseSegments = baseOwner.split(\".\")\n const headSegments = headOwner.split(\".\")\n if (baseSegments.length !== headSegments.length) return false\n return baseSegments.every((segment, index) => {\n const counterpart = headSegments[index]\n if (counterpart === undefined) return false\n if (segment === counterpart) return true\n return coversBothWays(setOf(segment), setOf(counterpart))\n })\n}\n\n/**\n * Whether the two token sets admit a perfect matching under `sameWord`.\n *\n * Sizes must agree, after which an injection from one side is a bijection — so only one\n * direction is searched. The search is augmenting-path rather than greedy, because a greedy\n * pass can strand a token a different choice would have matched: over `{user, users}` and\n * `{user, userx}`, taking the identical pair first leaves `users` with nothing, though\n * `user`→`userx` with `users`→`user` covers both. One segment runs to a handful of tokens, so\n * the exact answer costs nothing worth saving.\n */\nfunction coversBothWays(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {\n if (a.size !== b.size) return false\n if (a.size > MAX_OWNER_SEGMENT_TOKENS) return false\n const right = [...b]\n const partnerOf = new Map<string, string>()\n for (const token of a) {\n if (!augment(token, right, partnerOf, new Set())) return false\n }\n return true\n}\n\n/** Kuhn's step: claim a free partner for `token`, or displace one that can move on. */\nfunction augment(\n token: string,\n right: readonly string[],\n partnerOf: Map<string, string>,\n visited: Set<string>,\n): boolean {\n for (const candidate of right) {\n if (visited.has(candidate) || !sameWord(token, candidate)) continue\n visited.add(candidate)\n const holder = partnerOf.get(candidate)\n if (holder === undefined || augment(holder, right, partnerOf, visited)) {\n partnerOf.set(candidate, token)\n return true\n }\n }\n return false\n}\n\n/**\n * §3.4.6 — a necessary condition on two non-empty owners, cheaper than the full gate: their\n * first segments must correspond, since segments are compared positionally.\n *\n * A filter, not a decision. It relaxes `coversBothWays`' perfect matching to \"every token\n * finds some partner\", so it can only ever admit more than the gate does; answering `true`\n * says the gate has to be asked. Loosening it further costs correctness nothing and speed a\n * little — refusing something the gate would accept is the direction that would break recall.\n */\nfunction firstSegmentsCouldAgree(baseOwner: string, headOwner: string, setOf: TokenSetOf): boolean {\n const baseFirst = baseOwner.slice(0, dotOrEnd(baseOwner))\n const headFirst = headOwner.slice(0, dotOrEnd(headOwner))\n if (baseFirst === headFirst) return true\n const baseTokens = setOf(baseFirst)\n const headTokens = setOf(headFirst)\n if (baseTokens.size !== headTokens.size) return false\n // Still a necessary condition at the ceiling, because `coversBothWays` refuses a segment\n // this wide outright — and refusing here keeps that an O(1) answer rather than an all-pairs\n // scan run ahead of it, which is the adversarial input the ceiling exists for.\n if (baseTokens.size > MAX_OWNER_SEGMENT_TOKENS) return false\n for (const token of baseTokens) {\n let partnered = false\n for (const candidate of headTokens) {\n if (sameWord(token, candidate)) {\n partnered = true\n break\n }\n }\n if (!partnered) return false\n }\n return true\n}\n\nfunction dotOrEnd(owner: string): number {\n const dot = owner.indexOf(\".\")\n return dot < 0 ? owner.length : dot\n}\n\n/** The formulas §3.4 reads, over a token table shared for one matching pass. */\nexport interface NameScorer {\n name(baseName: string, headName: string): number\n member(baseName: string, headName: string): number\n ownersCompatible(baseName: string, headName: string): boolean\n}\n\n/**\n * A scorer that tokenises each distinct name once.\n *\n * Stage 4 scores every (base, head) pair in a bucket, so a bucket of K on each side asks for\n * K² similarities over 2K distinct names — tokenising on every call splits the same strings\n * into the same tokens hundreds of thousands of times. The table lives for one call rather\n * than for the process, because the names it holds are only the ones that pass is comparing.\n */\nexport function createNameScorer(): NameScorer {\n const sets = new Map<string, ReadonlySet<string>>()\n const setOf: TokenSetOf = (value) => {\n const cached = sets.get(value)\n if (cached !== undefined) return cached\n const built: ReadonlySet<string> = new Set(tokenizeName(value))\n sets.set(value, built)\n return built\n }\n const owners = new Map<string, string>()\n const ownerOf = (qname: string): string => {\n const cached = owners.get(qname)\n if (cached !== undefined) return cached\n const built = extractOwner(qname)\n owners.set(qname, built)\n return built\n }\n /**\n * §3.4.6's gate, with the segment split and the matching skipped where they can be.\n *\n * The gate is the costliest thing stage 4 asks per candidate — it splits both owners into\n * segments, tokenises each and runs an augmenting-path matching. Two shortcuts, in order of\n * how much they save:\n *\n * - **Identical owners.** A bucket of methods on one class asks about the same owner over\n * and over, and `ownerGate` already answers those on a string compare; doing it here skips\n * the two `extractOwner` calls as well, which are substrings on every candidate.\n * - **A different first segment.** Segments correspond positionally, so owners whose first\n * segments cannot be the same word are incompatible whatever the rest says. The check is\n * one `sameWord` over two token sets the table already holds, against a full matching over\n * every segment.\n *\n * Not memoised on the pair. A bulk rename produces as many distinct owner pairs as\n * candidates, so a table keyed on them grows with the cross-product — enough to exceed\n * V8's Map limit on the very input this is meant to make fast.\n */\n const compatible = (baseName: string, headName: string): boolean => {\n const baseOwner = ownerOf(baseName)\n const headOwner = ownerOf(headName)\n if (baseOwner === headOwner) return true\n if (baseOwner === \"\" || headOwner === \"\") return false\n if (!firstSegmentsCouldAgree(baseOwner, headOwner, setOf)) return false\n return segmentsCorrespond(baseOwner, headOwner, setOf)\n }\n return {\n name: (baseName, headName) => nameFormula(setOf, baseName, headName),\n member: (baseName, headName) => memberFormula(setOf, baseName, headName),\n ownersCompatible: compatible,\n }\n}\n\nfunction extractOwner(qname: string): string {\n const staticIdx = qname.indexOf(\"::\")\n if (staticIdx >= 0) return qname.slice(0, staticIdx)\n const lastDot = qname.lastIndexOf(\".\")\n if (lastDot >= 0) return qname.slice(0, lastDot)\n return \"\"\n}\n\n/** §3.4.1 tail note — used by drop-4.5 weak matcher and threshold lookup. */\nexport function lastSegment(qname: string): string {\n const staticIdx = qname.indexOf(\"::\")\n if (staticIdx >= 0) return qname.slice(staticIdx + 2)\n const lastDot = qname.lastIndexOf(\".\")\n if (lastDot >= 0) return qname.slice(lastDot + 1)\n return qname\n}\n","import { trySymbolId } from \"@aburi/core\"\nimport type { Symbol as IRSymbol, MatchRationale, SymbolId } from \"@aburi/types\"\nimport { signatureSimilarity } from \"./signature\"\nimport { createNameScorer, lastSegment, type NameScorer, tokenizeName } from \"./similarity\"\n\n/**\n * A concluded pairing of one base Symbol with one head Symbol, along with the\n * `MatchRationale` that placed them in the same pair. Stage 4 uses this shape both to\n * carry results out and to feed the delta / status classifier downstream.\n */\nexport interface SymbolPair {\n base: IRSymbol\n head: IRSymbol\n rationale: MatchRationale\n}\n\n/** Optional map returned by git for stage 2 (`old path` → `new path`). */\nexport type GitRenameMap = ReadonlyMap<string, string>\n\n/** Fingerprint for the 12-hex-char \"everything zero\" (i.e. dropped) canonical hash. */\nconst ZERO_LOGIC_FP = \"000000000000\"\n\n/** §3.3 — the similarity a multi-candidate logic-fingerprint group must reach to pair. */\nconst NAME_DISAMBIGUATION_THRESHOLD = 0.85\n\n/** One candidate pairing and the score that ranks it against the others. */\ninterface ScoredPair {\n base: IRSymbol\n head: IRSymbol\n score: number\n}\n\n/** What one sweep settled: the pairings, and the ids each side spent on them. */\ninterface Assignment {\n pairs: readonly ScoredPair[]\n baseIds: ReadonlySet<SymbolId>\n headIds: ReadonlySet<SymbolId>\n}\n\n/**\n * §3.8 — accept candidate pairings highest score first, taking each base and each head at\n * most once. Stages 3, 4 and 4.5 all choose from a set of possible pairings, and all three\n * used to decide one head at a time — which let an earlier head consume a base that was a\n * later head's exact match, and reported the loser as `added` beside the pair it lost.\n *\n * The two id sets come back with the pairings because every caller needs them to work out\n * what is left over, and deriving them again at the call site is a chance to derive the\n * wrong one: reading the head ids out as the base ids typechecks, and reproduces the very\n * symptom this exists to prevent.\n *\n * Not free, and §8.2 carries the bound: `candidates` is every pairing that clears its\n * threshold, so this holds O(base × head) records for stages 3 and 4 in the worst case where\n * the per-head loop it replaced held one, and sorting them is O(C log C). Stage 4 buys that\n * back and more with `createNameScorer`.\n *\n * It is not an optimal assignment — a greedy sweep can still strand a pair whose partners\n * were both taken by higher-scoring ones — but it never passes over the best available\n * pairing, which is the case that shows up in a review as one symbol appearing twice.\n *\n * Stage 4.5 does not call this, because that licence is what it cannot borrow: §3.4.5's\n * candidates are all worth the same, so no pairing is the best available and stranding one\n * buys nothing. See `bestPairing`.\n */\nfunction acceptInScoreOrder(candidates: ScoredPair[]): Assignment {\n candidates.sort(compareCandidates)\n const baseIds = new Set<SymbolId>()\n const headIds = new Set<SymbolId>()\n const pairs: ScoredPair[] = []\n for (const candidate of candidates) {\n if (baseIds.has(candidate.base.id) || headIds.has(candidate.head.id)) continue\n baseIds.add(candidate.base.id)\n headIds.add(candidate.head.id)\n pairs.push(candidate)\n }\n return { pairs, baseIds, headIds }\n}\n\n/**\n * Highest score first, then `(base.id, head.id)` ascending. The two id keys are what make\n * the order total, and they are a total order only because ids are unique within a Document\n * (ir-schema.md §14 #1), which `buildDiff` establishes before the first stage runs. Without\n * them equal scores resolve to enumeration order, and the diff stops being a function of the\n * two Documents.\n */\nfunction compareCandidates(a: ScoredPair, b: ScoredPair): number {\n if (a.score !== b.score) return b.score - a.score\n if (a.base.id !== b.base.id) return a.base.id < b.base.id ? -1 : 1\n return a.head.id < b.head.id ? -1 : a.head.id > b.head.id ? 1 : 0\n}\n\n/** The entries of `symbols` that no accepted pairing claimed, in their original order. */\nfunction unclaimed(symbols: readonly IRSymbol[], claimed: ReadonlySet<SymbolId>): IRSymbol[] {\n return symbols.filter((symbol) => !claimed.has(symbol.id))\n}\n\n/**\n * §3.1 — segments Symbols with identical `id` into paired matches. Runs first because it\n * is the highest-confidence signal (no heuristics, just hash lookup).\n *\n * Assumes `id` is unique on each side (ir-schema.md §14 #1) and does not check it:\n * `buildDiff` establishes that before calling, and a caller reaching this export directly\n * owns the obligation. A repeat on the head side loses all but the last entry to the lookup\n * map and then removes every one of them from `remainingHead`; a repeat on the base side\n * pairs the same head Symbol more than once. The later stages track consumed base Symbols\n * by id and lose a repeat the same way.\n */\nexport function matchStageId(\n base: readonly IRSymbol[],\n head: readonly IRSymbol[],\n): {\n matched: SymbolPair[]\n remainingBase: IRSymbol[]\n remainingHead: IRSymbol[]\n} {\n const headById = new Map<SymbolId, IRSymbol>()\n for (const s of head) headById.set(s.id, s)\n const matched: SymbolPair[] = []\n const remainingBase: IRSymbol[] = []\n const usedHead = new Set<SymbolId>()\n for (const b of base) {\n const h = headById.get(b.id)\n if (h !== undefined) {\n matched.push({ base: b, head: h, rationale: \"id-match\" })\n usedHead.add(b.id)\n } else {\n remainingBase.push(b)\n }\n }\n const remainingHead = head.filter((h) => !usedHead.has(h.id))\n return { matched, remainingBase, remainingHead }\n}\n\n/**\n * §3.2 — git rename detection. When a rename map is available (typical of ref-driven\n * scans), rewrite the base id with the head-side file path and look for a hit. Two base\n * files renamed onto one target predict the same head id, so the claimants are collected\n * before one is chosen.\n *\n * The rewriter only touches the `file` portion of the id — the `<language>:` prefix and\n * the trailing `#<qualified-name>` segment stay unchanged so a moved file with the same\n * qualified name is picked up correctly. When a Symbol's file is not in the rename map,\n * it is left for later stages.\n */\nexport function matchStageGitRename(\n remainingBase: readonly IRSymbol[],\n remainingHead: readonly IRSymbol[],\n renameMap: GitRenameMap | null,\n): {\n matched: SymbolPair[]\n remainingBase: IRSymbol[]\n remainingHead: IRSymbol[]\n} {\n if (renameMap === null || renameMap.size === 0) {\n return { matched: [], remainingBase: [...remainingBase], remainingHead: [...remainingHead] }\n }\n const headById = new Map<SymbolId, IRSymbol>()\n for (const h of remainingHead) headById.set(h.id, h)\n\n // Collect every base that predicts a given head before choosing, rather than letting the\n // first one in the array take it. Two files renamed onto one target both predict the same\n // id, and which of them is the move source should not be a property of the array order.\n const claimants = new Map<SymbolId, { head: IRSymbol; bases: [IRSymbol, ...IRSymbol[]] }>()\n for (const b of remainingBase) {\n const newPath = renameMap.get(b.source.file)\n if (newPath === undefined) continue\n const expectedId = rewriteIdFile(b.id, b.source.file, newPath)\n if (expectedId === null) continue\n const head = headById.get(expectedId)\n if (head === undefined) continue\n const claim = claimants.get(expectedId)\n if (claim === undefined) claimants.set(expectedId, { head, bases: [b] })\n else claim.bases.push(b)\n }\n\n const matched: SymbolPair[] = []\n const usedBase = new Set<SymbolId>()\n const usedHead = new Set<SymbolId>()\n for (const { head, bases } of claimants.values()) {\n const base = lowestId(bases)\n matched.push({ base, head, rationale: \"git-rename\" })\n usedBase.add(base.id)\n usedHead.add(head.id)\n }\n return {\n matched,\n remainingBase: unclaimed(remainingBase, usedBase),\n remainingHead: unclaimed(remainingHead, usedHead),\n }\n}\n\n/** Total on the non-empty claim lists above: a claim is created holding its first base. */\nfunction lowestId(symbols: readonly [IRSymbol, ...IRSymbol[]]): IRSymbol {\n let lowest = symbols[0]\n for (const symbol of symbols) {\n if (symbol.id < lowest.id) lowest = symbol\n }\n return lowest\n}\n\n/**\n * Predict the id a base Symbol would carry after git moved its file. The second Symbol-id\n * derivation in the codebase after `makeSymbolId` itself, so it goes back through the same\n * constructor rather than re-concatenating the parts: `trySymbolId` rejects a rename target\n * the id grammar cannot express (a backslash path, say) instead of minting an id no head\n * Symbol can ever equal.\n *\n * Returns `null` for that case, and the unchanged id when no rename applies — the caller\n * treats a null the same way it treats a lookup miss, leaving the pair for stage 3.\n */\nfunction rewriteIdFile(id: SymbolId, oldPath: string, newPath: string): SymbolId | null {\n const colon = id.indexOf(\":\")\n const hash = id.indexOf(\"#\")\n if (colon < 0 || hash < 0 || hash < colon) return id\n const filePart = id.slice(colon + 1, hash)\n if (filePart !== oldPath) return id\n return trySymbolId({\n language: id.slice(0, colon),\n file: newPath,\n qualifiedName: id.slice(hash + 1),\n })\n}\n\n/**\n * §3.3 — group both sides by `fingerprint.logic` and pair within each group. Two branches:\n * - single base candidate → paired with `logic-fingerprint`, no similarity test\n * - several → `nameSimilarity` disambiguates at ≥ 0.85; a group that cannot reach it is left\n * whole for stage 4 to re-evaluate\n *\n * Dropped symbols are excluded (§3.3 tail) — their logic fingerprint is the sentinel\n * `\"000000000000\"` and would collide with every other dropped Symbol in the workspace.\n * They flow to the stage-4.5 weak matcher instead.\n */\nexport function matchStageLogicFingerprint(\n remainingBase: readonly IRSymbol[],\n remainingHead: readonly IRSymbol[],\n): {\n matched: SymbolPair[]\n remainingBase: IRSymbol[]\n remainingHead: IRSymbol[]\n} {\n const baseGroups = groupByLogic(remainingBase)\n const headGroups = groupByLogic(remainingHead)\n const scorer = createNameScorer()\n const matched: SymbolPair[] = []\n for (const [logic, heads] of headGroups) {\n const bases = baseGroups.get(logic)\n if (bases === undefined) continue\n matched.push(...pairWithinLogicGroup(bases, heads, scorer))\n }\n const usedBase = new Set(matched.map((pair) => pair.base.id))\n const usedHead = new Set(matched.map((pair) => pair.head.id))\n return {\n matched,\n remainingBase: unclaimed(remainingBase, usedBase),\n remainingHead: unclaimed(remainingHead, usedHead),\n }\n}\n\n/**\n * Symbols by logic fingerprint, skipping the ones §3.3 excludes. Both sides are grouped, so\n * a group is a self-contained problem: the Symbols in it can only pair with each other, and\n * no group’s outcome depends on any other’s.\n */\nfunction groupByLogic(symbols: readonly IRSymbol[]): Map<string, IRSymbol[]> {\n const groups = new Map<string, IRSymbol[]>()\n for (const symbol of symbols) {\n if (symbol.dropped || symbol.fingerprint.logic === ZERO_LOGIC_FP) continue\n const bucket = groups.get(symbol.fingerprint.logic) ?? []\n bucket.push(symbol)\n groups.set(symbol.fingerprint.logic, bucket)\n }\n return groups\n}\n\n/**\n * §3.3’s two branches, over one logic-fingerprint group.\n *\n * A lone base candidate pairs with no similarity test — identical logic is taken as proof on\n * its own. With more than one, names disambiguate and a group that cannot reach the\n * threshold is left whole for stage 4.\n *\n * The loop is what keeps the second branch feeding the first: a scored round that consumes\n * all but one base leaves that one unconditional, which is how the per-head version behaved\n * when its candidate list shrank to a single entry.\n */\nfunction pairWithinLogicGroup(\n bases: readonly IRSymbol[],\n heads: readonly IRSymbol[],\n scorer: NameScorer,\n): SymbolPair[] {\n let freeBase = [...bases]\n let freeHead = [...heads]\n const matched: SymbolPair[] = []\n while (freeBase.length > 0 && freeHead.length > 0) {\n const lone = freeBase.length === 1 ? freeBase[0] : undefined\n if (lone !== undefined) {\n const head = closestNameTo(lone, freeHead, scorer)\n if (head === undefined) break\n matched.push({ base: lone, head, rationale: \"logic-fingerprint\" })\n break\n }\n const candidates: ScoredPair[] = []\n for (const base of freeBase) {\n for (const head of freeHead) {\n const score = scorer.name(base.name, head.name)\n if (score >= NAME_DISAMBIGUATION_THRESHOLD) candidates.push({ base, head, score })\n }\n }\n const accepted = acceptInScoreOrder(candidates)\n if (accepted.pairs.length === 0) break\n for (const { base, head } of accepted.pairs) {\n matched.push({ base, head, rationale: \"logic-fingerprint+name-disambiguation\" })\n }\n freeBase = unclaimed(freeBase, accepted.baseIds)\n freeHead = unclaimed(freeHead, accepted.headIds)\n }\n return matched\n}\n\n/**\n * The head whose name is closest to `base`, ties going to the lower id. The lone-candidate\n * branch pairs whatever it is given, so this only decides *which* head it takes — but that\n * decision was array order, and it is visible in the diff.\n */\nfunction closestNameTo(\n base: IRSymbol,\n heads: readonly IRSymbol[],\n scorer: NameScorer,\n): IRSymbol | undefined {\n let best: { head: IRSymbol; score: number } | undefined\n for (const head of heads) {\n const score = scorer.name(base.name, head.name)\n if (best === undefined || score > best.score) {\n best = { head, score }\n continue\n }\n if (score === best.score && head.id < best.head.id) best = { head, score }\n }\n return best?.head\n}\n\n/**\n * §3.4 — name + signature similarity with `(kind, signatureNullness)` bucket pre-filter.\n * `signatureNullness` distinguishes symbols with no signature (`interface`, `type`,\n * `class`) from callable ones so that a class body is never paired against a function.\n *\n * §3.4.6's owner gate runs before the score: a pair whose owners are not the same class, or\n * one a rename of the other, is not the same Symbol however well the rest agrees, and no\n * weight small enough to leave the name axis meaning something can outvote a matching member\n * name and signature.\n *\n * The composite score, over the pairs that clear it:\n * 0.5 * memberSimilarity + 0.3 * signatureSimilarity + 0.2 * OWNER_AXIS_SATISFIED\n *\n * `memberSimilarity` reads the last segment, not the whole qualified name: the gate has\n * already settled the owner, and reading it again on the name axis charges for it twice —\n * which is what kept a renamed class from ever pairing (§3.4.6).\n *\n * §3.4.3 threshold table, applied per head by the token count of its last name segment:\n * - 1 token → 1.0 (an exact match on both remaining axes)\n * - 2 tokens → 0.95 (strict, avoids `getUser` vs `getUsers`)\n * - default → 0.85\n *\n * §3.4.3 also names two Symbols the stage does not read at all, because for them the score is\n * high on evidence it does not have rather than on a resemblance: one whose signature is\n * null, since `signatureSimilarity(null, null)` is 1.0 and the bucket key restricts it to\n * candidates that are equally signature-less; and one whose qualified name carries a single\n * distinct token, since that is the whole of what its identity amounts to. Neither can be\n * expressed as a threshold — the second wants a bar above the top of the scale.\n *\n * Every pairing in a bucket that clears its head’s threshold becomes a candidate, and the\n * candidates are settled in score order rather than one head at a time — see\n * `acceptInScoreOrder`. That doubles the pairings scored, from a bucket that shrank as heads\n * consumed it to the full bucket every time; `createNameScorer` more than pays for it by\n * tokenising each distinct name once per pass instead of once per comparison.\n */\nexport function matchStageNameSignature(\n remainingBase: readonly IRSymbol[],\n remainingHead: readonly IRSymbol[],\n): {\n matched: SymbolPair[]\n remainingBase: IRSymbol[]\n remainingHead: IRSymbol[]\n} {\n const buckets = new Map<string, Bucket>()\n for (const b of remainingBase) {\n if (b.dropped) continue\n // §3.4.3 — see `saysEnoughToPair`. Read on the base as well as the head: the property\n // belongs to a pairing, and either end being short is enough to make the score unearned.\n if (!saysEnoughToPair(b.name)) continue\n const key = bucketKey(b)\n const bucket: Bucket = buckets.get(key) ?? { members: [], byToken: new Map() }\n const position = bucket.members.length\n bucket.members.push(b)\n for (const token of memberTokens(b.name)) {\n const sharing = bucket.byToken.get(token) ?? []\n sharing.push(position)\n bucket.byToken.set(token, sharing)\n }\n buckets.set(key, bucket)\n }\n const visitsOf = new Map<Bucket, Int32Array>()\n for (const bucket of buckets.values()) visitsOf.set(bucket, new Int32Array(bucket.members.length))\n const scorer = createNameScorer()\n const candidates: ScoredPair[] = []\n let visitCount = 0\n for (const h of remainingHead) {\n if (h.dropped) continue\n // §3.4.3 tail — `signatureSimilarity(null, null)` is 1.0, so a signature-less head would\n // score every candidate high on that axis. The bucket key already partitions by\n // signature nullness, so a signature-less head only ever sees signature-less candidates:\n // there is nothing here it could legitimately pair with.\n if (h.signature === null || h.signature === undefined) continue\n // §3.4.3 — a name of one distinct token scores 1.0 against any other of the same\n // signature, which is the top of the scale and so above no threshold. See\n // `saysEnoughToPair`.\n if (!saysEnoughToPair(h.name)) continue\n const bucket = buckets.get(bucketKey(h))\n if (bucket === undefined) continue\n const threshold = thresholdFor(h.name)\n // §3.4.0 — the least a member name can score and still leave the threshold reachable,\n // granting the other two axes in full.\n const memberFloor = lowestUsefulMember(threshold)\n // A base is reached once per token it shares, so a head whose postings add up to at least\n // the whole bucket is not being narrowed by the index — only charged for the overlap.\n // Every Symbol named `handleRequest` puts the entire bucket under both of its tokens.\n // Walking the members straight through covers that without the de-duplication, and keeps\n // the index from costing more than not having one. A bound rather than a promise of the\n // cheaper branch: `reach` double-counts, so a head can trip it while covering much less.\n const tokens = memberTokens(h.name)\n let reach = 0\n for (const token of tokens) reach += bucket.byToken.get(token)?.length ?? 0\n if (reach >= bucket.members.length) {\n for (const b of bucket.members) {\n collectCandidate(b, h, scorer, memberFloor, threshold, candidates)\n }\n continue\n }\n const visited = visitsOf.get(bucket)\n if (visited === undefined) continue\n visitCount++\n for (const token of tokens) {\n for (const position of bucket.byToken.get(token) ?? []) {\n if (visited[position] === visitCount) continue\n visited[position] = visitCount\n const b = bucket.members[position]\n if (b !== undefined) collectCandidate(b, h, scorer, memberFloor, threshold, candidates)\n }\n }\n }\n const accepted = acceptInScoreOrder(candidates)\n return {\n matched: accepted.pairs.map(({ base, head }) => ({\n base,\n head,\n rationale: \"name-signature\" as const,\n })),\n remainingBase: unclaimed(remainingBase, accepted.baseIds),\n remainingHead: unclaimed(remainingHead, accepted.headIds),\n }\n}\n\n/**\n * §3.4 — score one candidate pairing and keep it if it clears the head's threshold.\n *\n * A plain function rather than a closure over the head loop: both branches of the walk need\n * it, and a closure built per head is the cross-product moved into the allocator — measurably\n * so, at around 1.6x on a bucket the index cannot narrow.\n *\n * §3.4.3's floor is read before §3.4.6's gate and is the only early exit here. It is one\n * Jaccard over token sets the pass already holds, where the gate splits both owners into\n * segments, tokenises each and runs an augmenting-path matching. The gate short-circuits\n * internally on identical owners and on first segments that cannot correspond, so there is\n * nothing left worth hoisting out of it.\n */\nfunction collectCandidate(\n base: IRSymbol,\n head: IRSymbol,\n scorer: NameScorer,\n memberFloor: number,\n threshold: number,\n candidates: ScoredPair[],\n): void {\n const member = scorer.member(base.name, head.name)\n if (member < memberFloor) return\n if (!scorer.ownersCompatible(base.name, head.name)) return\n const score =\n 0.5 * member +\n 0.3 * signatureSimilarity(base.signature ?? null, head.signature ?? null) +\n 0.2 * OWNER_AXIS_SATISFIED\n // Filtering here rather than after the sweep keeps the candidate list to the pairings that\n // could actually be accepted; the threshold belongs to the head, so a pair below it is never\n // acceptable however the rest of the group resolves.\n if (score >= threshold) candidates.push({ base, head, score })\n}\n\nfunction bucketKey(s: IRSymbol): string {\n const sig = s.signature === null || s.signature === undefined ? \"no-sig\" : \"has-sig\"\n return `${s.kind}::${sig}`\n}\n\n/**\n * §3.4.0 — the least `memberSimilarity` that leaves `threshold` reachable.\n *\n * `0.5 * member + 0.3 * signature + 0.2 * 1` must reach the threshold, and the signature axis\n * is worth at most 0.3, so `member >= 2 * (threshold - 0.5)`. Refusing below this is exact\n * rather than approximate: nothing the other two axes can do rescues a pair under it.\n *\n * Reading it before §3.4.6's gate is what keeps stage 4 off the cross-product. The gate splits\n * and tokenises two owners; this is one Jaccard over token sets the pass has already built,\n * and it refuses most of what a shared token admits.\n */\nfunction lowestUsefulMember(threshold: number): number {\n return 2 * (threshold - 0.5)\n}\n\n/**\n * One `(kind, signatureNullness)` bucket: its base Symbols, and an index from each token a\n * member name carries to the positions holding it.\n *\n * The de-duplication stamp lives beside the bucket rather than in it, in `visitsOf`, and is\n * sized once the bucket is complete — a half-built bucket is then not representable, and the\n * stamp cannot be indexed out of range. It is per bucket rather than per head because a head\n * reaching every base would otherwise allocate a set the size of the bucket once per head,\n * which is the cross-product the index exists to avoid moved into the allocator.\n */\ninterface Bucket {\n members: IRSymbol[]\n byToken: Map<string, number[]>\n}\n\n/**\n * §3.4.0 — the token that stands in for a member name carrying none.\n *\n * `Foo.Bar.` has an empty last segment and two tokens in its qualified name, so it is\n * admissible, and two such Symbols score 1.0 on an axis comparing two empty sets. They have\n * no token to be indexed under and cannot pair with anything that has one, so they share a\n * key of their own. A real token cannot collide with it: `tokenizeName` never emits a space.\n */\nconst NO_MEMBER_TOKENS = \" none\"\n\nfunction memberTokens(qname: string): readonly string[] {\n const tokens = tokenizeName(lastSegment(qname))\n return tokens.length === 0 ? [NO_MEMBER_TOKENS] : tokens\n}\n\n/**\n * §3.4.3 — the composite score a pair must reach, by how many tokens the head's last name\n * segment has. A one-token segment still asks for the whole scale, which a pair reaches on an\n * identical member name and signature past §3.4.6's gate: `0.5 + 0.3 + 0.2`, exactly 1 in\n * IEEE 754. The owner need not be identical, only compatible — `UserRepo.get` and\n * `UserRepos.get` reach it.\n */\nconst EXACT_MATCH_ONLY = 1\nconst TWO_TOKEN_THRESHOLD = 0.95\nconst DEFAULT_THRESHOLD = 0.85\n\n/**\n * §3.4.6 — what the owner axis is worth to a pair that clears its gate, which is every pair\n * that reaches the score. Written as a value rather than folded into the weights so the\n * composite keeps the 0.5/0.3/0.2 shape §3.4.3's rows are calibrated against: dropping the\n * term and renormalising would move every threshold without changing what any of them means.\n */\nconst OWNER_AXIS_SATISFIED = 1\n\nfunction thresholdFor(qname: string): number {\n const tokens = tokenizeName(lastSegment(qname)).length\n if (tokens <= 1) return EXACT_MATCH_ONLY\n if (tokens === 2) return TWO_TOKEN_THRESHOLD\n return DEFAULT_THRESHOLD\n}\n\n/**\n * §3.4.3 — whether a Symbol's qualified name says enough for stage 4 to read it at all.\n *\n * The score is built from three things the name supplies: its tokens, its owner's tokens,\n * and a signature. A name of one distinct token supplies one word and an owner that either\n * repeats it or is empty, and `main(x: string): void` against another `main(x: string): void`\n * scores the full 1.0 on that — two unrelated CLI entry points reported as one move. The\n * table's first row was written to refuse this and could not: 1.0 is the top of the scale,\n * so no threshold sits above it. Being unpairable is a property of the name, not a score it\n * failed to reach, so it is decided here rather than by the comparison.\n *\n * Counted over the whole qualified name, because that is what the score reads.\n * `UserRepo.get` supplies three tokens and pairs, though its last segment is one — the\n * measure `thresholdFor` uses, and the wrong one for this question. Tokens are deduped, so\n * `Main.main` supplies one and does not.\n *\n * Read off both sides, because the property belongs to a pairing. It was once read off the\n * head alone, on the arithmetic that a short name anywhere capped the score at 0.75 — true\n * while the name axis read the whole qualified name, and false since §3.4.6 became a gate and\n * the axis moved to the last segment. `Main.main` is one deduped token and clears the gate\n * against `Mains.main`, whose member name is identical, so the pair reaches the top of the\n * scale from a name saying one word.\n *\n * The count stands in for how much the name says, and `tokenizeName` splits on ASCII case\n * boundaries, so a name written in a script that has none is one token however long it is:\n * `ユーザー情報を取得する` is refused here on the same footing as `main`. For that name the\n * proxy is wrong — two unrelated Symbols do not carry it by coincidence the way two carry\n * `main` — and the cost is a stage-4 move this stage will not find. Fixing it means measuring\n * the name by something other than a bare token count, which is §3.4.1's to change.\n */\nfunction saysEnoughToPair(qname: string): boolean {\n return tokenizeName(qname).length > 1\n}\n\n/**\n * §3.4.5 — dropped-only weak matcher. For dropped Symbols the fingerprint is zeroed, so the\n * only signals left are the trailing segment of the qualified name and the file basename.\n * Either one alone is enough to pair, which is deliberately lax: §3.4.5 accepts a\n * false-positive risk because dropped Symbols sit outside the IR's main review surface.\n *\n * What it does not accept is a signal that identifies nothing. A basename hit on `index.ts`\n * — the most common filename in a TypeScript monorepo — paired every dropped Symbol of one\n * kind under one with every other, at a score they all tied on, and the results landed in\n * `summary.moved`, which `--fail-on moved` gates on. So a half counts only when exactly one\n * dropped base and one dropped head carry that key: such a key names a pair, where a key\n * several Symbols carry names a group, and a group is not a pairing.\n *\n * That makes the candidates the identifying keys themselves — at most one pairing each,\n * two axes, so at most `2 × min(base, head)` of them rather than the cross-product a shared\n * basename used to produce.\n *\n * \"Exactly one\" is counted over the Symbols this stage is handed, not over every dropped\n * Symbol in the Document — stages 1 and 2 have already taken theirs, and a key they emptied\n * out identifies again. That is the intended reading, since the question is which leftover a\n * key picks out, but it is also how a shared `index.ts` comes back: three dropped classes\n * under one, two of them matched by id, and the basename identifies the two that are left.\n *\n * It also leaves nothing for §3.4.5's 0.5-per-half scale to rank, so §3.8's sweep is the\n * wrong instrument: it settles conflicts by score, and with none to settle by it would drop\n * one identified pairing for another over nothing but the id it sorts under. `bestPairing`\n * takes a maximum matching instead.\n */\nexport function matchStageDroppedWeak(\n remainingBase: readonly IRSymbol[],\n remainingHead: readonly IRSymbol[],\n): {\n matched: SymbolPair[]\n remainingBase: IRSymbol[]\n remainingHead: IRSymbol[]\n} {\n const bases = remainingBase.filter((s) => s.dropped)\n const heads = remainingHead.filter((s) => s.dropped)\n\n // Both halves have the same standing, so the two axes are collected rather than consulted\n // in an order. Both naming the same pairing needs no special handling: each key is sole on\n // both sides, so neither Symbol carries another pairing and the repeat is a component of\n // its own, which `bestPairing` takes once.\n const identified: WeakEdge[] = []\n for (const keyOf of [nameKey, fileKey]) {\n identified.push(...pairsIdentifiedBy(bases, heads, keyOf))\n }\n\n const matched = bestPairing(identified)\n return {\n matched: matched.map(({ base, head }) => ({\n base,\n head,\n rationale: \"dropped-weak-match\" as const,\n })),\n remainingBase: unclaimed(remainingBase, new Set(matched.map((edge) => edge.base.id))),\n remainingHead: unclaimed(remainingHead, new Set(matched.map((edge) => edge.head.id))),\n }\n}\n\n/** A pairing that one of §3.4.5's two keys identifies. */\ninterface WeakEdge {\n base: IRSymbol\n head: IRSymbol\n}\n\n/**\n * As many of the identified pairings as can hold at once.\n *\n * Each axis identifies a Symbol at most once, so a Symbol carries at most two pairings and\n * the whole set is the union of two matchings — which is a disjoint union of simple paths and\n * even cycles. Alternate pairings along a component are therefore a maximum matching of it,\n * and walking each component from a fixed end makes the choice among the maximum matchings\n * canonical rather than a property of the input order.\n *\n * The alternative would be §3.8's sweep, and it is the wrong instrument once §3.4.5's score\n * is gone: over `Shared.ts#Alpha`, `Other.ts#Beta` against `Other.ts#Alpha`,\n * `Shared.ts#Gamma`, all three pairings are identified and it takes one where two hold.\n */\nfunction bestPairing(edges: readonly WeakEdge[]): WeakEdge[] {\n const ordered = [...edges].sort(byEndpoints)\n const atBase = new Map<SymbolId, number[]>()\n const atHead = new Map<SymbolId, number[]>()\n for (const [index, edge] of ordered.entries()) {\n appendTo(atBase, edge.base.id, index)\n appendTo(atHead, edge.head.id, index)\n }\n const adjacent = (index: number): number[] => {\n const edge = ordered[index]\n if (edge === undefined) return []\n const sharing = [...(atBase.get(edge.base.id) ?? []), ...(atHead.get(edge.head.id) ?? [])]\n return sharing.filter((other) => other !== index)\n }\n\n const walked = ordered.map(() => false)\n const matching: WeakEdge[] = []\n /** Follow a component from `start`, taking every other pairing along it. */\n const walkFrom = (start: number): void => {\n let step = start\n let take = true\n while (!walked[step]) {\n walked[step] = true\n const edge = ordered[step]\n if (take && edge !== undefined) matching.push(edge)\n take = !take\n const next = adjacent(step).find((other) => walked[other] === false)\n if (next === undefined) return\n step = next\n }\n }\n\n // Paths first, from an end — a component walked from the middle would not alternate to a\n // maximum. What is unwalked afterwards is on a cycle, where every pairing is an equally\n // good place to start and the lowest-ordered one is the canonical choice.\n for (const [index] of ordered.entries()) {\n if (!walked[index] && adjacent(index).length <= 1) walkFrom(index)\n }\n for (const [index] of ordered.entries()) {\n if (!walked[index]) walkFrom(index)\n }\n return matching\n}\n\nfunction byEndpoints(a: WeakEdge, b: WeakEdge): number {\n if (a.base.id !== b.base.id) return a.base.id < b.base.id ? -1 : 1\n return a.head.id < b.head.id ? -1 : a.head.id > b.head.id ? 1 : 0\n}\n\nfunction appendTo(table: Map<SymbolId, number[]>, key: SymbolId, index: number): void {\n const bucket = table.get(key)\n if (bucket === undefined) table.set(key, [index])\n else bucket.push(index)\n}\n\n/**\n * The pairings a key picks out on its own: those whose key exactly one dropped base and one\n * dropped head carry. A key held by two Symbols on either side is discarded rather than\n * resolved — with the fingerprint zeroed there is nothing left to tell the members apart, and\n * guessing between them is the false pairing this rule exists to refuse.\n */\nfunction pairsIdentifiedBy(\n bases: readonly IRSymbol[],\n heads: readonly IRSymbol[],\n keyOf: (symbol: IRSymbol) => string,\n): WeakEdge[] {\n const soleBase = soleCarriers(bases, keyOf)\n const soleHead = soleCarriers(heads, keyOf)\n const pairs: WeakEdge[] = []\n for (const [key, base] of soleBase) {\n const head = soleHead.get(key)\n if (head !== undefined) pairs.push({ base, head })\n }\n return pairs\n}\n\n/** Keys carried by exactly one of `symbols`, mapped to it. */\nfunction soleCarriers(\n symbols: readonly IRSymbol[],\n keyOf: (symbol: IRSymbol) => string,\n): Map<string, IRSymbol> {\n const sole = new Map<string, IRSymbol>()\n const shared = new Set<string>()\n for (const symbol of symbols) {\n const key = keyOf(symbol)\n if (shared.has(key)) continue\n if (sole.has(key)) {\n sole.delete(key)\n shared.add(key)\n continue\n }\n sole.set(key, symbol)\n }\n return sole\n}\n\n/**\n * The two halves §3.4.5 scores. `kind` leads because it gates a pair before either half is\n * read, and `/` separates because no kind, qualified-name segment or basename contains one.\n */\nfunction nameKey(symbol: IRSymbol): string {\n return `${symbol.kind}/${lastSegment(symbol.name)}`\n}\n\nfunction fileKey(symbol: IRSymbol): string {\n return `${symbol.kind}/${basename(symbol.source.file)}`\n}\n\nfunction basename(path: string): string {\n const slash = path.lastIndexOf(\"/\")\n return slash >= 0 ? path.slice(slash + 1) : path\n}\n","import { type CallEdge, computeWeaklyConnectedComponents, RESERVED_LANGUAGE_IDS } from \"@aburi/core\"\nimport type { SliceId, SliceRecord, SymbolChange, SymbolId } from \"@aburi/types\"\nimport { DiffError } from \"./errors\"\n\n/** The three inputs of the Slice View pass — docs/design/slice-view.md §3. */\nexport interface SliceInput {\n /** SymbolChange records produced by `buildDiff` (pre-sort is not required). */\n changes: readonly SymbolChange[]\n /**\n * Resolved call edges from the base IR. Typically produced by\n * `reconstructCallEdgesFromIR(baseIR)` inside `buildDiff` — Slice View\n * consumes only resolved edges (§5.4), never `Symbol.calls[]` directly.\n */\n baseCallEdges: readonly CallEdge[]\n /** Resolved call edges from the head IR (same source rule as base). */\n headCallEdges: readonly CallEdge[]\n}\n\n/**\n * Slice View clustering pass — docs/design/slice-view.md §2, §4–§8.\n *\n * Groups the changed-Symbol set of a diff into weakly-connected components\n * over the union of base and head call edges. Emits a `SliceRecord[]` whose\n * cluster ids are `\"slice:\" + <smallest-member Symbol id>`, both `slices[]`\n * and each `members[]` sorted ascending.\n *\n * Pure function: `(changes, baseCallEdges, headCallEdges)` → `SliceRecord[]`.\n * Determinism, idempotence, input-order insensitivity, and locality are all\n * guaranteed — see `computeWeaklyConnectedComponents` in `@aburi/core`.\n *\n * Returns `[]` when no SymbolChange is Node-eligible per §4.1; callers should\n * still serialise the empty array — the Markdown projection omits the section\n * (§9.4 / §12.5) but the JSON always emits the key so consumers never\n * distinguish \"field absent\" from \"no slices\" (§11.2).\n *\n * Every returned record has passed `assertSliceRecordInvariant` (§7.4).\n */\nexport function computeSlices(input: SliceInput): SliceRecord[] {\n const nodeIds = collectNodeIds(input.changes)\n if (nodeIds.length === 0) return []\n\n const nodeIdSet = new Set<SymbolId>(nodeIds)\n const edges = collectEdges(input.baseCallEdges, input.headCallEdges, nodeIdSet)\n\n const components = computeWeaklyConnectedComponents<SymbolId>(nodeIds, edges, (id) => id)\n\n return components.map(makeSliceRecord)\n}\n\nconst SLICE_ID_PREFIX = \"slice:\"\n\n/**\n * §7.1 — the single place production code derives a Slice id. Everything else\n * in `src/` either receives an id or renders one. (Tests spell the prefix out\n * literally on purpose: an expectation written in terms of the function under\n * test would agree with it no matter what it produced.)\n *\n * The cast is the one place a `SliceId` comes into existence. `SliceId` and\n * `SymbolId` are separate brands precisely so this concatenation cannot be\n * open-coded anywhere else: a bare `\"slice:\" + x` evaluates to `string`, which\n * `SliceRecord.id` no longer accepts.\n */\nfunction sliceIdFor(anchor: SymbolId): SliceId {\n return `${SLICE_ID_PREFIX}${anchor}` as SliceId\n}\n\n/**\n * Build one SliceRecord from an ascending-sorted component and check its own\n * post-condition before letting it out (§7.4 layer 1).\n *\n * The check is not defending against untrusted input — this function builds\n * the id itself, so the derivation clause is true by construction here and\n * only the two clauses about `members` can actually fire. Those are the point:\n * `members[0]` is the anchor only because `computeWeaklyConnectedComponents`\n * returns each component sorted ascending, a guarantee that lives one layer\n * down and is invisible from this file. If it ever stops holding, the diff\n * would otherwise keep emitting well-formed-looking SliceRecords naming the\n * wrong anchor. Here it fails instead.\n */\nfunction makeSliceRecord(members: readonly SymbolId[]): SliceRecord {\n const anchor = members[0]\n if (anchor === undefined) {\n throw new DiffError(\n \"computeSlices: the clustering utility returned an empty component; every weakly-connected \" +\n \"component contains at least the node that seeded it (slice-view.md §6).\",\n { code: \"slice-invariant-violated\" },\n )\n }\n const record: SliceRecord = { id: sliceIdFor(anchor), members: [...members] }\n assertSliceRecordInvariant(record)\n return record\n}\n\n/**\n * §7.1 — the anchor of a Slice: its lexicographically smallest member.\n *\n * Answers from `members[0]`. `id` is derived from the anchor, so\n * reconstructing the anchor by stripping the `\"slice:\"` prefix is circular at\n * best and, for a record that broke the derivation, silently wrong: it would\n * name a Symbol that is not in the Slice. `id` is read here only to name the\n * offending record in the error message. Consumers that need the anchor call\n * this; consumers that need a label keep using `id` directly.\n */\nexport function sliceAnchor(record: SliceRecord): SymbolId {\n const anchor = record.members[0]\n if (anchor === undefined) {\n const violation = emptyMembersViolation(record.id)\n throw new DiffError(violation.message, {\n code: \"slice-invariant-violated\",\n value: violation.subject,\n })\n }\n return anchor\n}\n\n/** Which clause of the §7.1 / §8.2 invariant a `SliceRecord` broke. */\nexport type SliceViolationKind =\n /** Not a `SliceRecord` at all: not an object, or `id` / `members` of the wrong type. */\n | \"malformed-shape\"\n /** `members[]` has no entries, so the Slice has no anchor (§11.1). */\n | \"members-empty\"\n /** `members[]` is not in strictly ascending order, so `members[0]` need not be the smallest (§8.2, §11.1). */\n | \"members-unordered\"\n /** `id` is not `\"slice:\" + members[0]` (§7.1). */\n | \"id-not-derived\"\n /** The anchor is itself in a reserved id namespace, so `id` would read as a doubled prefix (§7.5). */\n | \"anchor-in-reserved-namespace\"\n\nexport interface SliceRecordViolation {\n kind: SliceViolationKind\n /** The offending record's `id` when it has a usable one, else a short stand-in. */\n subject: string\n /** Human-facing explanation, suitable for an error message or a validator error. */\n message: string\n}\n\n/**\n * Report which clause of the §7.1 / §8.2 invariant a value breaks, or `null`\n * when it is a well-formed `SliceRecord`. Non-throwing counterpart of\n * `assertSliceRecordInvariant`: the pass wants an exception, a schema\n * validator wants a verdict it can turn into its own error.\n *\n * Takes `unknown` rather than `SliceRecord` deliberately. §7.4 layer 2 points\n * this at documents written by third-party or older producers — data that has\n * not been type-checked by definition — so anything that assumed a well-typed\n * argument would crash on exactly the input it exists to reject.\n */\nexport function sliceRecordViolation(value: unknown): SliceRecordViolation | null {\n if (typeof value !== \"object\" || value === null) {\n return {\n kind: \"malformed-shape\",\n subject: \"<not an object>\",\n message: `Expected a SliceRecord object; got ${value === null ? \"null\" : typeof value}.`,\n }\n }\n const { id, members } = value as { id?: unknown; members?: unknown }\n const subject = typeof id === \"string\" ? id : \"<missing id>\"\n if (typeof id !== \"string\") {\n return {\n kind: \"malformed-shape\",\n subject,\n message: `SliceRecord ${subject}: id must be a string; got ${typeof id}.`,\n }\n }\n if (!Array.isArray(members) || members.some((member) => typeof member !== \"string\")) {\n return {\n kind: \"malformed-shape\",\n subject,\n message: `SliceRecord ${subject}: members must be an array of strings.`,\n }\n }\n\n const anchor = members[0]\n if (anchor === undefined) return emptyMembersViolation(subject)\n // A Symbol id in the `slice:` namespace derives to `slice:slice:…`, which is\n // self-consistent — it passes the derivation clause below — but names an id no reader can\n // tell from a Slice id. `makeSymbolId` refuses to build such a Symbol id and\n // `checkIRIntegrity` #16 rejects one read from disk, but `buildDiff` is public API and\n // runs no integrity check, so the doubled prefix is caught here too.\n const reservedAnchor = reservedNamespaceOf(anchor)\n if (reservedAnchor !== null) {\n return {\n kind: \"anchor-in-reserved-namespace\",\n subject,\n message:\n `SliceRecord anchor \"${anchor}\" uses the reserved language token \"${reservedAnchor}\", ` +\n `so its Slice id would repeat the prefix (ir-schema.md §3.5, slice-view.md §7.5).`,\n }\n }\n for (let i = 1; i < members.length; i++) {\n const previous = members[i - 1] as string\n const current = members[i] as string\n if (previous < current) continue\n return {\n kind: \"members-unordered\",\n subject,\n message:\n `SliceRecord ${subject}: members[] is not in strictly ascending order at index ${i} ` +\n `(\"${current}\" follows \"${previous}\"), so members[0] is not necessarily the anchor ` +\n `(slice-view.md §8.2 for the order, §11.1 for uniqueness).`,\n }\n }\n // The one place outside `sliceIdFor` that asserts an id brand, and the reason this\n // function takes `unknown`: the anchor has been checked to be a string and nothing more.\n // Whether it is a well-formed Symbol id is not this check's question — a record whose\n // members are gibberish still has to be told apart from one whose id disagrees with them.\n const expected = sliceIdFor(anchor as SymbolId)\n if (id !== expected) {\n return {\n kind: \"id-not-derived\",\n subject,\n message:\n `SliceRecord id \"${id}\" is not derived from the anchor \"${anchor}\"; ` +\n `expected \"${expected}\" (slice-view.md §7.1).`,\n }\n }\n return null\n}\n\n/** The reserved token an id opens with, or `null`. Shares the list `@aburi/core` enforces. */\nfunction reservedNamespaceOf(id: string): string | null {\n const colon = id.indexOf(\":\")\n if (colon < 0) return null\n const token = id.slice(0, colon)\n return RESERVED_LANGUAGE_IDS.has(token) ? token : null\n}\n\n/** Single source of the empty-`members[]` wording, shared with `sliceAnchor`. */\nfunction emptyMembersViolation(subject: string): SliceRecordViolation {\n return {\n kind: \"members-empty\",\n subject,\n message:\n `SliceRecord ${subject}: members[] is empty, so the Slice has no anchor ` +\n `(slice-view.md §11.1 requires at least one member).`,\n }\n}\n\n/**\n * Throwing form of `sliceRecordViolation`. Raised as a coded `DiffError` so\n * callers branch on `code` rather than parsing the message; which clause broke\n * is `SliceRecordViolation.kind`, for callers that need to tell them apart.\n */\nexport function assertSliceRecordInvariant(record: SliceRecord): void {\n const violation = sliceRecordViolation(record)\n if (violation === null) return\n throw new DiffError(violation.message, {\n code: \"slice-invariant-violated\",\n value: violation.subject,\n })\n}\n\n/**\n * §4.1 Node selection: keep every SymbolChange whose status is\n * added / removed / changed / moved+changed / dropped-toggled. The identity\n * is the head-side Symbol id where present (added / changed / moved+changed /\n * dropped-toggled), the base-side id for removed. Pure moved and unchanged\n * (already stripped upstream) are excluded — §4.1 / §4.3.\n *\n * The returned array is deliberately not deduplicated: a well-formed\n * SymbolChange list from `buildDiff` never mentions the same Symbol id\n * twice, and Union-Find would coalesce any accidental duplicate anyway.\n */\nfunction collectNodeIds(changes: readonly SymbolChange[]): SymbolId[] {\n const ids: SymbolId[] = []\n for (const change of changes) {\n const id = nodeIdOf(change)\n if (id === null) continue\n ids.push(id)\n }\n return ids\n}\n\nfunction nodeIdOf(change: SymbolChange): SymbolId | null {\n switch (change.status) {\n case \"added\":\n return change.symbol.id\n case \"removed\":\n // §4.1 — removed uses base-side id because there is no head symbol.\n return change.symbol.id\n case \"unknown\":\n // The one side that exists carries the id, as for added / removed. An unknown entry\n // is a thing a reviewer has to look at, so it belongs in the cluster its neighbours\n // are in — a Symbol whose file went missing is most useful read beside the callers\n // that still reference it.\n return change.symbol.id\n case \"changed\":\n case \"moved+changed\":\n case \"dropped-toggled\":\n return change.after.id\n case \"moved\":\n // §4.3 — pure moved is not a Node and is not used for bridging either.\n return null\n }\n // Exhaustive: if SymbolChange grows a new status the compile error above\n // fires before this fallback is reachable.\n}\n\n/**\n * §5.1 Edge selection: union of base and head, restricted to edges whose\n * BOTH endpoints are Nodes, canonicalised to `(u, v)` with `u < v` (self-\n * loops implicitly dropped). Multi-edges collapse naturally inside the WCC\n * primitive — no explicit dedup needed.\n *\n * The union is the load-bearing rule of §5.3: a controller that called an\n * old service in base and a new service in head needs both edges to land\n * all three Symbols in a single Slice.\n */\nfunction collectEdges(\n baseEdges: readonly CallEdge[],\n headEdges: readonly CallEdge[],\n nodeIds: ReadonlySet<SymbolId>,\n): [SymbolId, SymbolId][] {\n const pairs: [SymbolId, SymbolId][] = []\n for (const edge of baseEdges) appendEdgeIfBothNodes(edge, nodeIds, pairs)\n for (const edge of headEdges) appendEdgeIfBothNodes(edge, nodeIds, pairs)\n return pairs\n}\n\nfunction appendEdgeIfBothNodes(\n edge: CallEdge,\n nodeIds: ReadonlySet<SymbolId>,\n out: [SymbolId, SymbolId][],\n): void {\n if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) return\n out.push([edge.from, edge.to])\n}\n","import type { Symbol as IRSymbol } from \"@aburi/types\"\n\nexport type SymbolStatus = \"unchanged\" | \"moved\" | \"changed\" | \"moved+changed\" | \"dropped-toggled\"\n\n/**\n * §4 — determine which of the five statuses a matched pair falls into. `dropped-toggled`\n * has absolute priority: whenever `dropped` differs between base and head, every other\n * signal is ignored because the underlying fingerprint change is a consequence of the\n * drop-rule flip, not a genuine code edit (§4.1 rationale).\n *\n * \"pathChanged\" catches both physical file relocation AND in-file rename: any Symbol id\n * mismatch between base and head means the identifier moved, and DF9 (\"method rename\n * (same file, same logic) → moved\") relies on this. Stage-1 pairs cannot trigger the id branch\n * (they match by exact id), so this only affects stage-2+ pairings where the pair was\n * discovered via fingerprint or name/signature similarity.\n */\nexport function classifyStatus(base: IRSymbol, head: IRSymbol): SymbolStatus {\n const droppedToggled = base.dropped !== head.dropped\n if (droppedToggled) return \"dropped-toggled\"\n const pathChanged = base.source.file !== head.source.file || base.id !== head.id\n const fingerprintChanged =\n base.fingerprint.api !== head.fingerprint.api ||\n base.fingerprint.logic !== head.fingerprint.logic ||\n base.fingerprint.syntax !== head.fingerprint.syntax\n if (pathChanged && fingerprintChanged) return \"moved+changed\"\n if (pathChanged) return \"moved\"\n if (fingerprintChanged) return \"changed\"\n return \"unchanged\"\n}\n\n/** Direction of a `dropped-toggled` transition (§7.1 SymbolDroppedToggled). */\nexport type DropDirection = \"to-dropped\" | \"to-kept\"\n\nexport function dropDirection(head: IRSymbol): DropDirection {\n return head.dropped ? \"to-dropped\" : \"to-kept\"\n}\n","import { reconstructCallEdgesFromIR, type SerializeOptions, serializeCanonical } from \"@aburi/core\"\nimport type {\n DiffResult,\n IR,\n IRRef,\n NotComparedFile,\n RelativePath,\n SkipReason,\n Summary,\n SymbolChange,\n} from \"@aburi/types\"\nimport {\n DEPENDENCY_IDENTITY_FIELDS,\n dependencyIdentity,\n dependencySideView,\n diffComponents,\n diffDependencies,\n} from \"./components\"\nimport { computeSymbolDelta, type DeltaOptions } from \"./delta\"\nimport { DiffError } from \"./errors\"\nimport {\n type GitRenameMap,\n matchStageDroppedWeak,\n matchStageGitRename,\n matchStageId,\n matchStageLogicFingerprint,\n matchStageNameSignature,\n type SymbolPair,\n} from \"./match\"\nimport { computeSlices } from \"./slice\"\nimport { classifyStatus, dropDirection } from \"./status\"\n\nconst DIFF_SCHEMA = \"https://aburi.kage1020.com/schema/aburi.diff.v1.json\"\n\n/**\n * The two counters `buildDiff` always writes, narrowed off `Summary`'s optionals.\n *\n * They are optional on the wire so a diff written before they existed stays valid, and that\n * optionality is about *documents*, not about this function — a caller holding a value it just\n * built should not have to re-decide what \"absent\" would have meant. Same reason\n * `diffDependencies` declares its `unknown` array present.\n */\ninterface UnknownCounters {\n unknown: number\n depsUnknown: number\n}\n\nexport interface DiffInput {\n baseIR: IR\n headIR: IR\n /** IR reference metadata (git ref / file path) for provenance. */\n base: IRRef\n head: IRRef\n /** Generator record for the diff output. Defaults to `{name: \"aburi\", version: \"0.0.0\"}`. */\n generator?: { name: string; version: string }\n /** Optional stage-2 rename table. When null/undefined stage 2 is skipped. */\n gitRenames?: GitRenameMap | null\n /** Passed through to computeSymbolDelta (§5.2.1 line fuzz). */\n delta?: DeltaOptions\n}\n\nconst DEFAULT_GENERATOR = { name: \"aburi\", version: \"0.0.0\" }\n\n/**\n * Top-level entry: run the 5-stage matcher, classify each pair, produce array deltas,\n * fold in Component / Dependency diffs, and assemble the `aburi.diff.v1` JSON projection.\n * The function is pure; write-to-disk is delegated to `writeCanonicalDiff` so callers can\n * pipe the result through additional steps (Markdown projection, `--fail-on` gate) before\n * serialisation.\n */\nexport function buildDiff(\n input: DiffInput,\n): DiffResult & { notCompared: NotComparedFile[]; summary: Summary & UnknownCounters } {\n assertDiffable(input.baseIR, \"baseIR\")\n assertDiffable(input.headIR, \"headIR\")\n ensureSchemasAgree(input.baseIR, input.headIR)\n const stage1 = matchStageId(input.baseIR.symbols, input.headIR.symbols)\n const stage2 = matchStageGitRename(\n stage1.remainingBase,\n stage1.remainingHead,\n input.gitRenames ?? null,\n )\n const stage3 = matchStageLogicFingerprint(stage2.remainingBase, stage2.remainingHead)\n const stage4 = matchStageNameSignature(stage3.remainingBase, stage3.remainingHead)\n const stage4_5 = matchStageDroppedWeak(stage4.remainingBase, stage4.remainingHead)\n\n const pairs: SymbolPair[] = [\n ...stage1.matched,\n ...stage2.matched,\n ...stage3.matched,\n ...stage4.matched,\n ...stage4_5.matched,\n ]\n\n // Typed with the two counters present. They are optional on the wire, but this function\n // writes both on every diff, and the local type is what carries that from here to the\n // return without a cast.\n const summary: Summary & UnknownCounters = {\n unknown: 0,\n depsUnknown: 0,\n added: 0,\n removed: 0,\n moved: 0,\n movedChanged: 0,\n changed: 0,\n droppedToggled: 0,\n unchanged: 0,\n droppedAdded: 0,\n droppedRemoved: 0,\n componentsAdded: 0,\n componentsRemoved: 0,\n componentsChanged: 0,\n depsAdded: 0,\n depsRemoved: 0,\n }\n // Counted locally and assigned once below, where the other totals are, so the increment\n // sites do not have to reason about a counter that is optional on the wire.\n let unknown = 0\n\n const symbols: SymbolChange[] = []\n for (const pair of pairs) {\n const status = classifyStatus(pair.base, pair.head)\n if (status === \"unchanged\") {\n summary.unchanged++\n continue\n }\n if (status === \"dropped-toggled\") {\n summary.droppedToggled++\n symbols.push({\n status: \"dropped-toggled\",\n before: pair.base,\n after: pair.head,\n direction: dropDirection(pair.head),\n })\n continue\n }\n if (status === \"moved\") {\n summary.moved++\n symbols.push({\n status: \"moved\",\n before: pair.base,\n after: pair.head,\n rationale: pair.rationale,\n })\n continue\n }\n if (status === \"changed\") {\n summary.changed++\n symbols.push({\n status: \"changed\",\n before: pair.base,\n after: pair.head,\n delta: computeSymbolDelta(pair.base, pair.head, input.delta ?? {}),\n })\n continue\n }\n // moved+changed\n summary.movedChanged++\n symbols.push({\n status: \"moved+changed\",\n before: pair.base,\n after: pair.head,\n rationale: pair.rationale,\n delta: computeSymbolDelta(pair.base, pair.head, input.delta ?? {}),\n })\n }\n\n const finalRemainingHead = stage4_5.remainingHead\n const finalRemainingBase = stage4_5.remainingBase\n\n // Read after the five matching stages, never before them. A Symbol that crossed files\n // between the two revisions is paired by stage 2, 3 or 4 and comes out `moved` or\n // `moved+changed`, whichever end of the move sits in the lost file — the other document\n // holds real evidence for it either way. Only the leftovers are absences, and only an\n // absence in a file that was never analysed is unexplained.\n //\n // The two loops below read opposite ends: a base leftover is looked up by the file it\n // came from, a head leftover by the file it arrived in. In both cases the question is the\n // same — did the document that lacks this Symbol ever read the file it is in?\n // One construction per side, shared with the Dependency diff below. The Symbol loops and\n // `diffDependencies` therefore read the same two maps rather than two answers to the same\n // question — which is what makes \"a Symbol reported unknown and the edges it took with it\n // cannot disagree about which file went missing\" structural instead of aspirational.\n const baseSide = dependencySideView(input.baseIR)\n const headSide = dependencySideView(input.headIR)\n const lostByHead = headSide.lostFiles\n const lostByBase = baseSide.lostFiles\n\n for (const h of finalRemainingHead) {\n if (h.dropped) {\n summary.droppedAdded++\n continue\n }\n const reason = lostByBase.get(h.source.file)\n if (reason !== undefined) {\n unknown++\n symbols.push({ status: \"unknown\", symbol: h, absentFrom: \"base\", reason })\n continue\n }\n summary.added++\n symbols.push({ status: \"added\", symbol: h })\n }\n for (const b of finalRemainingBase) {\n if (b.dropped) {\n summary.droppedRemoved++\n continue\n }\n const reason = lostByHead.get(b.source.file)\n if (reason !== undefined) {\n unknown++\n symbols.push({ status: \"unknown\", symbol: b, absentFrom: \"head\", reason })\n continue\n }\n summary.removed++\n symbols.push({ status: \"removed\", symbol: b })\n }\n\n const components = diffComponents(input.baseIR.components, input.headIR.components)\n summary.componentsAdded = components.added.length\n summary.componentsRemoved = components.removed.length\n summary.componentsChanged = components.changed.length\n\n const dependencies = diffDependencies(input.baseIR.dependencies, input.headIR.dependencies, {\n base: baseSide,\n head: headSide,\n })\n summary.depsAdded = dependencies.added.length\n summary.depsRemoved = dependencies.removed.length\n // No `?? 0`: `diffDependencies` declares `unknown` present when it is given side views, so\n // absorbing an absence here would launder a mis-wiring into a confident `depsUnknown: 0`.\n summary.depsUnknown = dependencies.unknown.length\n summary.unknown = unknown\n\n symbols.sort(compareSymbolChange)\n\n // Slice View clustering (docs/design/slice-view.md §2). Runs after status /\n // delta computation and before Markdown projection. Consumes the resolved\n // CallEdge[] reconstructed from each IR — §5.4 requires only resolved edges,\n // never `Symbol.calls[]` directly. Emits `slices[]` unconditionally (§11.2);\n // the Markdown side omits the section when the array is empty (§12.5).\n const slices = computeSlices({\n changes: symbols,\n baseCallEdges: reconstructCallEdgesFromIR(input.baseIR),\n headCallEdges: reconstructCallEdgesFromIR(input.headIR),\n })\n\n return {\n $schema: DIFF_SCHEMA,\n generator: input.generator ?? DEFAULT_GENERATOR,\n base: input.base,\n head: input.head,\n summary,\n symbols,\n components,\n dependencies,\n slices,\n notCompared: filesNeitherSideRead(lostByBase, lostByHead),\n }\n}\n\n/**\n * Paths both documents record as never analysed, with each side's own reason.\n *\n * This is the loss `unknown` cannot describe. That status is derived from the matcher's\n * leftovers — a Symbol one document holds and the other lacks — and a file skipped on both\n * sides contributes Symbols to neither, so it leaves no leftover to classify and the whole\n * document falls silent about it. Silence is the one thing it must not do here: a diff with\n * nothing to say about a path is exactly what a diff that compared it and found it unchanged\n * looks like.\n *\n * The intersection, not the union. A one-sided loss has leftovers on the other side and is\n * reported as `unknown` there; listing it here as well would count one loss twice, in two\n * vocabularies that mean different things.\n *\n * Always returns an array, empty included. Optionality in the schema covers documents written\n * before the field existed, and no arithmetic elsewhere in a diff would let a reader tell that\n * case from a run that missed nothing (docs/design/diff-algorithm.md §10.1).\n */\nfunction filesNeitherSideRead(\n lostByBase: ReadonlyMap<RelativePath, SkipReason>,\n lostByHead: ReadonlyMap<RelativePath, SkipReason>,\n): NotComparedFile[] {\n const both: NotComparedFile[] = []\n for (const [path, baseReason] of lostByBase) {\n const headReason = lostByHead.get(path)\n if (headReason === undefined) continue\n both.push({ path, baseReason, headReason })\n }\n return both.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n}\n\n/** §9.1 — refuse to diff across schema versions. */\nfunction ensureSchemasAgree(base: IR, head: IR): void {\n if (base.$schema !== head.$schema) {\n throw new DiffError(\n `Base IR schema \"${base.$schema}\" does not match head IR schema \"${head.$schema}\"; a diff across schema versions is not supported.`,\n { code: \"schema-mismatch\", value: base.$schema },\n )\n }\n}\n\n/** Which of the two inputs a message is about. */\ntype IRSide = \"baseIR\" | \"headIR\"\n\n/**\n * A collection `buildDiff` keys by identity. One descriptor drives both halves of the\n * entry-point check — that the entries are objects carrying string identity fields, and that\n * no identity repeats — so the two cannot come to describe different collections, and a\n * fourth entry added here is guarded by both or by neither. Reporting order is the order of\n * this array, base side before head side.\n */\ninterface IdentifiedCollection {\n readonly field: \"symbols\" | \"components\" | \"dependencies\"\n /** The fields identity is read from, in the order `keyOf` receives them. */\n readonly identityFields: readonly string[]\n /** Join them the way the diff itself keys on them, or the check guards nothing. */\n readonly keyOf: (parts: readonly string[]) => string\n /** How a message names the repeated value. */\n readonly noun: string\n /** The repeated value as the IR spells it; also what `DiffError.value` carries. */\n readonly show: (parts: readonly string[]) => string\n /** What the diff does with a repeat, and the invariant that forbids it. */\n readonly consequence: string\n}\n\n/** Identity is a single field, so joining the one-member tuple is joining nothing. */\nconst soleField = (parts: readonly string[]): string => parts.join(\"\")\n\nconst IDENTIFIED_COLLECTIONS: readonly IdentifiedCollection[] = [\n {\n field: \"symbols\",\n identityFields: [\"id\"],\n keyOf: soleField,\n noun: \"id\",\n show: soleField,\n consequence:\n \"stage 1 pairs Symbols by id and every later stage tracks the base Symbols it has \" +\n \"consumed by id, so a repeat leaves one entry out of the diff entirely or classifies \" +\n \"its counterpart twice (ir-schema.md §14 #1)\",\n },\n {\n field: \"components\",\n identityFields: [\"id\"],\n keyOf: soleField,\n noun: \"id\",\n show: soleField,\n consequence:\n \"Component identity is the id, so a repeat hides one entry and can report a change \" +\n \"the two revisions do not contain (ir-schema.md §14 #2)\",\n },\n {\n field: \"dependencies\",\n identityFields: DEPENDENCY_IDENTITY_FIELDS,\n keyOf: dependencyIdentity,\n noun: \"(from, to, via) triple\",\n show: (parts) => `(${parts.join(\", \")})`,\n consequence:\n \"direction and effect are deliberately outside Dependency identity (§6.2), so a \" +\n \"repeat surfaces as an added + removed pair no reader can tell from a real flip \" +\n \"(ir-schema.md §14 #13)\",\n },\n]\n\n/**\n * The two things `buildDiff` needs before stage 1 runs: a Document it can walk, and\n * identities it can key on. diff-algorithm.md §3.7 states the second and why it is checked\n * here as well as at extraction time.\n *\n * Neither half is a full schema validation. A Symbol that reaches stage 1 is an object with\n * a string `id`, because the identity scan reads that much; nothing checks it carries a\n * `fingerprint`, which is `checkIRIntegrity` #20's job and runs when the CLI reads an IR off\n * disk. What this does buy is that a malformed collection is named — `symbols: undefined`\n * used to surface as `TypeError: undefined is not iterable` and `symbols: [null]` as\n * `TypeError: Cannot read properties of null (reading 'id')`, both from inside\n * `matchStageId`, with neither the collection nor the index named.\n */\nfunction assertDiffable(ir: IR, name: IRSide): void {\n if (ir === null || typeof ir !== \"object\") {\n throw shapeError(name, `${name} must be an IR object; got ${describeValue(ir)}.`)\n }\n if (typeof ir.$schema !== \"string\" || ir.$schema.length === 0) {\n throw shapeError(`${name}.$schema`, `${name}.$schema must be a non-empty schema URL.`)\n }\n for (const collection of IDENTIFIED_COLLECTIONS) {\n assertUniqueIdentity(ir[collection.field], `${name}.${collection.field}`, collection)\n }\n}\n\nfunction assertUniqueIdentity(\n entries: unknown,\n subject: string,\n collection: IdentifiedCollection,\n): void {\n if (!Array.isArray(entries)) {\n throw shapeError(subject, `${subject} must be an array.`)\n }\n const firstSeen = new Map<string, number>()\n for (const [index, entry] of entries.entries()) {\n const entrySubject = `${subject}[${index}]`\n const parts = identityFieldsOf(entry, entrySubject, collection)\n const key = collection.keyOf(parts)\n const first = firstSeen.get(key)\n if (first === undefined) {\n firstSeen.set(key, index)\n continue\n }\n const shown = collection.show(parts)\n throw new DiffError(\n `${entrySubject} repeats the ${collection.noun} \"${shown}\" first seen at index ` +\n `${first}; ${collection.consequence}.`,\n { code: \"ir-identity-collision\", value: shown },\n )\n }\n}\n\n/**\n * The identity fields of one entry, established as strings on the way out. Reading them is\n * what forces the check: without it a Symbol carrying no `id` has nothing to collide with,\n * passes, and derives a Slice anchored on `undefined` several stages later — which\n * `assertSliceRecordInvariant` reports as `slice-invariant-violated`, the one code the CLI\n * presents as a bug in Aburi rather than in the caller's IR.\n */\nfunction identityFieldsOf(\n entry: unknown,\n subject: string,\n collection: IdentifiedCollection,\n): string[] {\n if (typeof entry !== \"object\" || entry === null || Array.isArray(entry)) {\n throw shapeError(subject, `${subject} must be an object; got ${describeValue(entry)}.`)\n }\n const parts: string[] = []\n for (const field of collection.identityFields) {\n const value = (entry as Record<string, unknown>)[field]\n if (typeof value !== \"string\") {\n throw shapeError(\n `${subject}.${field}`,\n `${subject}.${field} must be a string; got ${describeValue(value)}.`,\n )\n }\n parts.push(value)\n }\n return parts\n}\n\nfunction shapeError(subject: string, message: string): DiffError {\n return new DiffError(message, { code: \"ir-shape-invalid\", value: subject })\n}\n\nfunction describeValue(value: unknown): string {\n if (value === undefined) return \"undefined\"\n if (value === null) return \"null\"\n if (Array.isArray(value)) return \"an array\"\n return `a ${typeof value}`\n}\n\n/**\n * Deterministic ordering of the `symbols[]` output:\n * 1. by status (alphabetical) — pins added/changed/moved sections\n * 2. by the \"reference\" id (`after` for change/move/toggle, otherwise `symbol`)\n *\n * The result is stable byte-for-byte for equal inputs, matching the canonicalisation\n * guarantee the diff JSON must uphold when persisted to `out/diff.json`.\n */\nfunction compareSymbolChange(a: SymbolChange, b: SymbolChange): number {\n if (a.status !== b.status) return a.status < b.status ? -1 : 1\n const idA = referenceId(a)\n const idB = referenceId(b)\n return idA < idB ? -1 : idA > idB ? 1 : 0\n}\n\nfunction referenceId(change: SymbolChange): string {\n if (change.status === \"added\" || change.status === \"removed\" || change.status === \"unknown\") {\n return change.symbol.id\n }\n return change.after.id\n}\n\n/**\n * The files a document's scan never analysed, by path, with why.\n *\n * `stats.skippedFiles` is Class B: absent means the writer predates the field, not that\n * nothing was lost. An empty map is the honest answer either way — with no enumeration\n * there is no absence this function can explain, and guessing from\n * `totalFiles > parsedFiles` would attach a reason to whichever Symbols happened to be\n * missing. The CLI says so on stderr instead; a diff cannot invent the list.\n */\n/**\n * Byte-deterministic serialiser for a DiffResult. Delegates to `@aburi/core`\n * `serializeCanonical` so the sort order, NFC normalisation, and codepoint key sort are\n * shared with the IR side.\n */\nexport function writeCanonicalDiff(diff: DiffResult, options: SerializeOptions = {}): string {\n return serializeCanonical(diff, options)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2BA,SAAgB,eACd,MACA,MACe;CACf,MAAM,2BAAW,IAAI,IAA4B;CACjD,KAAK,MAAM,KAAK,MAAM,SAAS,IAAI,EAAE,IAAI,CAAC;CAC1C,MAAM,2BAAW,IAAI,IAA4B;CACjD,KAAK,MAAM,KAAK,MAAM,SAAS,IAAI,EAAE,IAAI,CAAC;CAE1C,MAAM,QAAqB,CAAC;CAC5B,MAAM,UAAuB,CAAC;CAC9B,MAAM,UAAoC,CAAC;CAE3C,KAAK,MAAM,CAAC,IAAI,aAAa,UAAU;EACrC,MAAM,WAAW,SAAS,IAAI,EAAE;EAChC,IAAI,aAAa,QAAW;GAC1B,MAAM,KAAK,QAAQ;GACnB;EACF;EACA,MAAM,eAAe,CAAC,kBAAkB,SAAS,OAAO,SAAS,KAAK;EACtE,MAAM,mBAAmB,CAAC,kBAAkB,SAAS,aAAa,CAAC,GAAG,SAAS,aAAa,CAAC,CAAC;EAC9F,MAAM,oBAAoB,CAAC,kBACzB,SAAS,cAAc,CAAC,GACxB,SAAS,cAAc,CAAC,CAC1B;EACA,IAAI,gBAAgB,oBAAoB,mBACtC,QAAQ,KAAK;GACX,QAAQ;GACR,OAAO;GACP,OAAO;IAAE;IAAc;IAAkB;GAAkB;EAC7D,CAAC;CAEL;CACA,KAAK,MAAM,CAAC,IAAI,aAAa,UAC3B,IAAI,CAAC,SAAS,IAAI,EAAE,GAAG,QAAQ,KAAK,QAAQ;CAE9C,SAAS,KAAK;CACd,SAAS,OAAO;CAChB,QAAQ,MAAM,GAAG,MAAO,EAAE,MAAM,KAAK,EAAE,MAAM,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,MAAM,KAAK,IAAI,CAAE;CACvF,OAAO;EAAE;EAAO;EAAS;CAAQ;AACnC;;;;;;;;;;;;AA6CA,SAAgB,mBAAmB,IAA4B;CAC7D,MAAM,8BAAc,IAAI,IAAsC;CAC9D,KAAK,MAAM,UAAU,GAAG,SAAS,YAAY,IAAI,OAAO,IAAI,OAAO,OAAO,IAAI;CAC9E,MAAM,4BAAY,IAAI,IAA8B;CACpD,KAAK,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,GAAG,UAAU,IAAI,KAAK,MAAM,KAAK,MAAM;CACpF,OAAO;EAAE;EAAa;CAAU;AAClC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,iBACd,MACA,MACA,OACmD;CACnD,MAAM,2BAAW,IAAI,IAAwB;CAC7C,KAAK,MAAM,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC;CACtD,MAAM,2BAAW,IAAI,IAAwB;CAC7C,KAAK,MAAM,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC;CACtD,MAAM,QAAsB,CAAC;CAC7B,MAAM,UAAwB,CAAC;CAC/B,MAAM,UAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,QAAQ,UAAU;EACjC,MAAM,IAAI,SAAS,IAAI,GAAG;EAC1B,IAAI,MAAM,QAAW;GAGnB,MAAM,YAAY,gBAAgB,KAAK,MAAM,MAAM,MAAM,IAAI;GAC7D,IAAI,UAAU,SAAS,GAAG,QAAQ,KAAK;IAAE,YAAY;IAAK,YAAY;IAAQ;GAAU,CAAC;QACpF,MAAM,KAAK,GAAG;GACnB;EACF;EAKA,IAAI,EAAE,cAAc,IAAI,cAAc,EAAE,UAAU,WAAW,IAAI,UAAU,OAAO;GAChF,QAAQ,KAAK,CAAC;GACd,MAAM,KAAK,GAAG;EAChB;CACF;CACA,KAAK,MAAM,CAAC,KAAK,QAAQ,UAAU;EACjC,IAAI,SAAS,IAAI,GAAG,GAAG;EACvB,MAAM,YAAY,gBAAgB,KAAK,MAAM,MAAM,MAAM,IAAI;EAC7D,IAAI,UAAU,SAAS,GAAG,QAAQ,KAAK;GAAE,YAAY;GAAK,YAAY;GAAQ;EAAU,CAAC;OACpF,QAAQ,KAAK,GAAG;CACvB;CACA,MAAM,KAAK,mBAAmB;CAC9B,QAAQ,KAAK,mBAAmB;CAChC,QAAQ,MAAM,GAAG,MAAM,oBAAoB,EAAE,YAAY,EAAE,UAAU,CAAC;CACtE,OAAO;EAAE;EAAO;EAAS;CAAQ;AACnC;;;;;;;;;;;AAYA,SAAS,gBACP,KACA,QACA,QACmB;CAInB,MAAM,yBAAS,IAAI,IAA8B;CACjD,KAAK,MAAM,YAAY,CAAC,IAAI,MAAM,IAAI,EAAE,GAAG;EACzC,MAAM,OAAO,OAAO,YAAY,IAAI,QAAQ;EAW5C,IAAI,SAAS,QAAW;EACxB,MAAM,SAAS,OAAO,UAAU,IAAI,IAAI;EACxC,IAAI,WAAW,QAAW;EAC1B,OAAO,IAAI,MAAM,MAAM;CACzB;CACA,OAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACzB,KAAK,CAAC,MAAM,aAAa;EAAE;EAAM;CAAO,EAAE,CAAC,CAC3C,MAAM,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,CAAE;AACpE;;;;;;;;;;;;;AAcA,MAAa,6BAA6B;CAAC;CAAQ;CAAM;AAAK;AAE9D,SAAgB,mBAAmB,OAAkC;CACnE,OAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,cAAc,GAAuB;CAC5C,OAAO,mBAAmB,2BAA2B,KAAK,UAAU,EAAE,MAAM,CAAC;AAC/E;AAEA,SAAS,oBAAoB,GAAe,GAAuB;CACjE,MAAM,OAAO,cAAc,CAAC;CAC5B,MAAM,OAAO,cAAc,CAAC;CAC5B,OAAO,OAAO,OAAO,KAAK,OAAO,OAAO,IAAI;AAC9C;AAEA,SAAS,kBAAkB,GAAsB,GAA+B;CAC9E,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,EAAE,IAAI,OAAO;CAC7D,OAAO;AACT;AAEA,SAAS,SAAmC,OAAkB;CAC5D,MAAM,MAAM,GAAG,MAAO,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,CAAE;AAC/D;;;;ACzNA,IAAa,YAAb,cAA+B,MAAM;CACnC,AAAS;CACT,AAAS;CAET,YAAY,SAAiB,QAAyB,SAA+B;EACnF,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO,OAAO;EACnB,KAAK,QAAQ,OAAO;CACtB;AACF;;;;ACrCA,MAAa,oBAAoB;AACjC,MAAa,gBAAgB;AAC7B,MAAa,gBAAgB;;;;;;;AAkB7B,SAAgB,mBACd,MACA,MACA,UAAwB,CAAC,GACZ;CACb,MAAM,OAAO,iBAAiB,QAAQ,aAA6B;CACnE,MAAM,QAAqB;EACzB,YAAY,KAAK,YAAY,QAAQ,KAAK,YAAY;EACtD,cAAc,KAAK,YAAY,UAAU,KAAK,YAAY;EAC1D,eAAe,KAAK,YAAY,WAAW,KAAK,YAAY;EAC5D,mBAAmB,KAAK,aAAa,WAAW,KAAK,aAAa;EAClE,mBAAmB,KAAK,eAAe,KAAK;CAC9C;CACA,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,OAAO,IAAI;CACpD,MAAM,UAAU,YAAY,KAAK,SAAS,KAAK,OAAO;CACtD,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,OAAO,IAAI;CACpD,MAAM,aAAa,eAAe,KAAK,YAAY,KAAK,YAAY,IAAI;CAExE,MAAM,YADW,cAAc,KAAK,aAAa,MAAM,KAAK,aAAa,IAChD;CACzB,OAAO;AACT;;;;;;AAOA,SAAS,iBAAiB,OAAuB;CAC/C,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,UAAU,KAAK,GACpD,MAAM,IAAI,UACR,iDAA6D,OAAkB,SAAS,OAAO,KAAK,EAAE,IACtG;EAAE,MAAM;EAAqB,OAAO,OAAO,KAAK;CAAE,CACpD;CAEF,IAAI,aAAyB,YAC3B,MAAM,IAAI,UACR,0CAAsD,OAAkB,SAAS,MAAM,IACvF;EAAE,MAAM;EAAqB,OAAO,OAAO,KAAK;CAAE,CACpD;CAEF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AA2BA,SAAS,cACP,MACA,MACA,SACA,UACY;CACZ,MAAM,WAAW,IAAI,IAAI,KAAK,KAAK,GAAG,UAAU,KAAK,CAAC;CACtD,MAAM,WAAW,IAAI,IAAI,KAAK,KAAK,GAAG,UAAU,KAAK,CAAC;CACtD,MAAM,4BAAY,IAAI,IAA2B;CACjD,KAAK,MAAM,oBAAoB,CAAC,MAAM,KAAK,GAAG;EAC5C,MAAM,UAAU,GAAkB,MAChC,EAAE,QAAQ,EAAE,OACZ,KAAK,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,aAC5B,CAAC,oBAAoB,QAAQ,EAAE,MAAM,EAAE,IAAI;EAC9C,KAAK,MAAM,CAAC,WAAW,cAAc,cAAc,MAAM,MAAM,UAAU,UAAU,MAAM,GAAG;GAC1F,SAAS,OAAO,SAAS;GACzB,SAAS,OAAO,SAAS;GACzB,MAAM,cAAc,KAAK;GACzB,IAAI,gBAAgB,QAAW,UAAU,IAAI,WAAW,WAAW;EACrE;CACF;CAEA,MAAM,QAAa,CAAC;CACpB,MAAM,WAAgB,CAAC;CACvB,KAAK,MAAM,CAAC,OAAO,MAAM,KAAK,QAAQ,GAAG;EACvC,MAAM,cAAc,UAAU,IAAI,KAAK;EACvC,IAAI,gBAAgB,QAAW,MAAM,KAAK,EAAE,IAAI;OAC3C,IAAI,CAAC,QAAQ,YAAY,MAAM,EAAE,IAAI,GAAG,SAAS,KAAK,EAAE,IAAI;CACnE;CAEA,OAAO;EAAE;EAAO,SADA,KAAK,QAAQ,GAAG,UAAU,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,IACtD;EAAG;CAAS;AACpC;AAQA,MAAM,cAAuB;CAAE,OAAO;CAAG,UAAU;AAAE;;AAGrD,SAAS,OAAO,GAAY,GAAqB;CAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE;AAClE;;;;;;;;;;;;;;;AAgBA,SAAS,cACP,MACA,MACA,UACA,UACA,QACyB;CAEzB,MAAM,OAAoB,MAAM,KAAK,EAAE,QAAQ,KAAK,SAAS,EAAE,SAC7D,MAAM,KAAK,EAAE,QAAQ,KAAK,SAAS,EAAE,SAAS,WAAW,CAC3D;CACA,MAAM,aAAa,GAAW,MAA8B;EAC1D,MAAM,IAAI,KAAK;EACf,MAAM,IAAI,KAAK;EACf,IAAI,MAAM,UAAa,MAAM,QAAW,OAAO;EAC/C,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,GAAG,OAAO;EAClE,MAAM,OAAO,KAAK,IAAI,EAAE,GAAG,IAAI,MAAM;EACrC,OAAO;GAAE,OAAO,KAAK,QAAQ;GAAG,UAAU,KAAK,WAAW,KAAK,IAAI,EAAE,OAAO,EAAE,IAAI;EAAE;CACtF;CACA,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KACpC,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;EACzC,MAAM,WAAW,KAAK,IAAI,EAAE,GAAG,MAAM;EACrC,MAAM,WAAW,KAAK,EAAE,GAAG,IAAI,MAAM;EACrC,IAAI,SAAS,OAAO,UAAU,QAAQ,IAAI,WAAW;EACrD,MAAM,SAAS,UAAU,GAAG,CAAC;EAC7B,IAAI,WAAW,QAAQ,OAAO,QAAQ,MAAM,GAAG,SAAS;EACxD,MAAM,MAAM,KAAK;EACjB,IAAI,QAAQ,QAAW,IAAI,KAAK;CAClC;CAIF,MAAM,SAAkC,CAAC;CACzC,IAAI,IAAI;CACR,IAAI,IAAI;CACR,OAAO,IAAI,KAAK,UAAU,IAAI,KAAK,QAAQ;EACzC,MAAM,OAAO,KAAK,EAAE,GAAG,MAAM;EAC7B,MAAM,SAAS,UAAU,GAAG,CAAC;EAC7B,IAAI,WAAW,QAAQ,OAAO,UAAU,KAAK,SAAS,OAAO,aAAa,KAAK,UAAU;GACvF,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;GAClB;GACA;GACA;EACF;EACA,MAAM,WAAW,KAAK,IAAI,EAAE,GAAG,MAAM;EACrC,IAAI,SAAS,UAAU,KAAK,SAAS,SAAS,aAAa,KAAK,UAAU;OACrE;CACP;CACA,OAAO;AACT;AAEA,SAAS,UAAU,MAAuB,MAAuB,UAA8B;CAC7F,MAAM,UAAU,OAA+B;EAC7C,MAAM;EACN,KAAK,EAAE;EACP,MAAM,EAAE;CACV;CACA,OAAO,cAAc,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM,GAAG,YAAY,QAAQ;AAC/E;AAEA,SAAS,WAAW,GAAS,GAAkB;CAC7C,OACE,EAAE,SAAS,EAAE,SACZ,EAAE,aAAa,WAAW,EAAE,aAAa,UACzC,EAAE,QAAQ,WAAW,EAAE,QAAQ,UAC/B,EAAE,QAAQ,WAAW,EAAE,QAAQ,UAC/B,EAAE,YAAY,WAAW,EAAE,YAAY;AAE5C;AAEA,SAAS,YAAY,MAAyB,MAAqC;CACjF,MAAM,UAAU,OAAmC;EACjD,MAAM;EACN,KAAK,GAAG,EAAE,GAAG,IAAI,EAAE;EAQnB,MAAM,EAAE,QAAQ;CAClB;CACA,OAAO,cAAc,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM,GAAG,cAAc,OAAO,iBAAiB;AACjG;AAEA,SAAS,aAAa,GAAW,GAAoB;CACnD,OACE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE;AAE1F;AAEA,SAAS,UAAU,MAAuB,MAAuB,UAA8B;CAC7F,MAAM,UAAU,OAA+B;EAC7C,MAAM;EACN,KAAK,EAAE;EACP,MAAM,EAAE;CACV;CACA,OAAO,cAAc,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM,GAAG,YAAY,QAAQ;AAC/E;AAEA,SAAS,WAAW,GAAS,GAAkB;CAC7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,WAAW,EAAE,YAAY;AAC1E;;;;;;AAOA,SAAS,eACP,MACA,MACA,UACY;CACZ,MAAM,UAAU,OAAyC;EACvD,MAAM;EACN,KAAK,EAAE;EACP,MAAM,EAAE;CACV;CACA,OAAO,cAAc,KAAK,IAAI,MAAM,GAAG,KAAK,IAAI,MAAM,GAAG,iBAAiB,QAAQ;AACpF;AAEA,SAAS,gBAAgB,GAAc,GAAuB;CAC5D,IAAI,EAAE,SAAS,EAAE,MAAM,OAAO;CAC9B,IAAI,EAAE,UAAU,WAAW,EAAE,UAAU,QAAQ,OAAO;CACtD,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,UAAU,QAAQ,KACtC,IAAI,EAAE,UAAU,OAAO,EAAE,UAAU,IAAI,OAAO;CAEhD,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAS,cAAc,MAAwB,MAA+C;CAC5F,IAAI,SAAS,QAAQ,SAAS,MAAM,OAAO;CAC3C,IAAI,SAAS,QAAQ,SAAS,MAAM;EAClC,MAAM,QAAoB;GAAE,OAAO,CAAC;GAAG,SAAS,CAAC;GAAG,UAAU,CAAC;EAAE;EACjE,MAAM,UAAU,QAAQ;EACxB,IAAI,YAAY,MAAM,OAAO;EAC7B,MAAM,aAAa,SAAS;EAU5B,OAAO;GACL,QAVyB,aACvB;IAAE,OAAO,CAAC,GAAG,QAAQ,MAAM;IAAG,SAAS,CAAC;IAAG,UAAU,CAAC;GAAE,IACxD;IAAE,OAAO,CAAC;IAAG,SAAS,CAAC,GAAG,QAAQ,MAAM;IAAG,UAAU,CAAC;GAAE;GAS1D,SAR0B,aACxB;IAAE,OAAO,CAAC,GAAG,QAAQ,OAAO;IAAG,SAAS,CAAC;IAAG,UAAU,CAAC;GAAE,IACzD;IAAE,OAAO,CAAC;IAAG,SAAS,CAAC,GAAG,QAAQ,OAAO;IAAG,UAAU,CAAC;GAAE;GAO3D,QANyB,aACvB;IAAE,OAAO,CAAC,GAAG,QAAQ,MAAM;IAAG,SAAS,CAAC;IAAG,UAAU,CAAC;GAAE,IACxD;IAAE,OAAO,CAAC;IAAG,SAAS,CAAC,GAAG,QAAQ,MAAM;IAAG,UAAU,CAAC;GAAE;GAK1D,eAAe,MAAM,SAAS,YAAY,MAAM,SAAS;GACzD,mBAAmB,MAAM,aAAa,YAAY,MAAM,aAAa;GACrE,uBAAuB,CAAC,YACtB,MAAM,kBAAkB,MAAM,MAAM,IAAI,MAAM,GAC9C,MAAM,kBAAkB,MAAM,MAAM,IAAI,MAAM,CAChD;EACF;CACF;;;;;;;;CAQA,MAAM,eACJ,OACA,WACgD;EAChD,MAAM;EACN,KAAK,GAAG,MAAM,GAAG,MAAM;EACvB,MAAM;CACR;CASA,OAAO;EACL,QATa,cACb,KAAK,OAAO,IAAI,WAAW,GAC3B,KAAK,OAAO,IAAI,WAAW,IAC1B,GAAG,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAC5C,CAKK;EACL,SAJc,eAAe,KAAK,SAAS,KAAK,OAI1C;EACN,QAJa,cAAc,KAAK,QAAQ,KAAK,MAIxC;EACL,cAAc,KAAK,UAAU,KAAK;EAClC,kBAAkB,KAAK,cAAc,KAAK;EAC1C,uBAAuB,CAAC,YAAY,KAAK,gBAAgB,KAAK,cAAc;CAC9E;AACF;AAEA,SAAS,eAAe,MAAyB,MAAqC;CACpF,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAoB,CAAC;CAC3B,MAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,KAAK,MAAM;CAC7C,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,IAAI,IAAI,KAAK,SAAS,KAAK,KAAK;EACtC,MAAM,IAAI,IAAI,KAAK,SAAS,KAAK,KAAK;EACtC,IAAI,MAAM,UAAa,MAAM,QAAW,MAAM,KAAK,CAAC;OAC/C,IAAI,MAAM,UAAa,MAAM,QAAW,QAAQ,KAAK,CAAC;OACtD,IAAI,MAAM,GAAG;GAChB,IAAI,MAAM,QAAW,QAAQ,KAAK,CAAC;GACnC,IAAI,MAAM,QAAW,MAAM,KAAK,CAAC;EACnC;CACF;CACA,OAAO;EAAE;EAAO;EAAS,UAAU,CAAC;CAAE;AACxC;AAEA,SAAS,cAAc,MAAyB,MAAqC;CACnF,MAAM,OAAO,IAAI,IAAI,IAAI;CACzB,MAAM,OAAO,IAAI,IAAI,IAAI;CACzB,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,MAAM,KAAK,CAAC;CACpD,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,QAAQ,KAAK,CAAC;CACtD,OAAO;EAAE;EAAO;EAAS,UAAU,CAAC;CAAE;AACxC;AAEA,SAAS,YAAY,GAAsB,GAA+B;CACxE,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,EAAE,IAAI,OAAO;CAC7D,OAAO;AACT;;;;;;;;;;;;;;;;;;;AC9XA,SAAgB,oBACd,MACA,MACQ;CACR,MAAM,UAAU,QAAQ;CACxB,MAAM,UAAU,QAAQ;CACxB,IAAI,YAAY,QAAQ,YAAY,MAAM,OAAO;CACjD,IAAI,YAAY,QAAQ,YAAY,MAAM,OAAO;CACjD,MAAM,cAAc,oBAClB,QAAQ,OAAO,KAAK,MAAM,EAAE,IAAI,GAChC,QAAQ,OAAO,KAAK,MAAM,EAAE,IAAI,CAClC;CACA,MAAM,eAAe,oBAAoB,QAAQ,SAAS,QAAQ,OAAO;CACzE,MAAM,cAAc,oBAAoB,QAAQ,QAAQ,QAAQ,MAAM;CACtE,QAAQ,cAAc,eAAe,eAAe;AACtD;AAEA,SAAS,oBAAoB,GAAsB,GAA8B;CAC/E,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,OAAO;CAC7C,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,IAAI,UAAU;CACd,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAC5B,IAAI,EAAE,OAAO,EAAE,IAAI;CAErB,OAAO,UAAU,EAAE;AACrB;AAEA,SAAS,oBAAoB,GAAsB,GAA8B;CAC/E,IAAI,EAAE,WAAW,KAAK,EAAE,WAAW,GAAG,OAAO;CAC7C,MAAM,OAAO,IAAI,IAAI,CAAC;CACtB,MAAM,OAAO,IAAI,IAAI,CAAC;CACtB,IAAI,eAAe;CACnB,KAAK,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI,CAAC,GAAG;CACvC,MAAM,QAAQ,KAAK,OAAO,KAAK,OAAO;CACtC,OAAO,UAAU,IAAI,IAAI,eAAe;AAC1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1BA,SAAgB,aAAa,OAAyB;CACpD,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,OAAO,MAAM,MAAM,aAAa,GAAG;EAC5C,IAAI,IAAI,WAAW,GAAG;EACtB,KAAK,MAAM,SAAS,WAAW,GAAG,GAAG;GACnC,MAAM,OAAO,MAAM,YAAY;GAC/B,IAAI,KAAK,WAAW,KAAK,KAAK,IAAI,IAAI,GAAG;GACzC,KAAK,IAAI,IAAI;GACb,IAAI,KAAK,IAAI;EACf;CACF;CACA,OAAO;AACT;AAEA,SAAS,WAAW,MAAwB;CAC1C,MAAM,SAAmB,CAAC;CAC1B,IAAI,UAAU;CACd,KAAK,MAAM,MAAM,MAAM;EACrB,IAAI,YAAY,IAAI;GAClB,UAAU;GACV;EACF;EAGA,IADiB,gBADJ,QAAQ,MAAM,EACS,GAAG,EAC5B,GAAG;GACZ,OAAO,KAAK,OAAO;GACnB,UAAU;EACZ,OACE,WAAW;CAEf;CACA,IAAI,QAAQ,SAAS,GAAG,OAAO,KAAK,OAAO;CAC3C,OAAO;AACT;AAEA,SAAS,gBAAgB,MAAc,MAAuB;CAC5D,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,IAAI,aAAa,WAAW,OAAO;CACnC,KAAK,aAAa,cAAc,WAAW,OAAO;CAClD,IAAI,cAAc,aAAa,YAAY,OAAO;CAClD,OAAO;AACT;;;;;;AAOA,SAAgB,QAAQ,GAAsB,GAA8B;CAC1E,OAAO,YAAY,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AAC3C;AAEA,SAAS,YAAY,GAAwB,GAAgC;CAC3E,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,OAAO;CACzC,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,OAAO;CACzC,MAAM,CAAC,OAAO,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;CACxD,IAAI,eAAe;CACnB,KAAK,MAAM,SAAS,OAAO,IAAI,MAAM,IAAI,KAAK,GAAG;CACjD,OAAO,gBAAgB,EAAE,OAAO,EAAE,OAAO;AAC3C;;;;;;AAOA,SAAgB,cAAc,GAAW,GAAmB;CAC1D,OAAO,QAAQ,aAAa,CAAC,GAAG,aAAa,CAAC,CAAC;AACjD;AAKA,MAAM,qBAAiC,UAAU,IAAI,IAAI,aAAa,KAAK,CAAC;;;;;;AAO5E,SAAgB,eAAe,UAAkB,UAA0B;CACzE,OAAO,YAAY,mBAAmB,UAAU,QAAQ;AAC1D;;;;;;AAOA,SAAgB,iBAAiB,UAAkB,UAA0B;CAC3E,OAAO,cAAc,mBAAmB,UAAU,QAAQ;AAC5D;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,oBAAoB,UAAkB,UAA2B;CAC/E,OAAO,UAAU,mBAAmB,UAAU,QAAQ;AACxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,SAAS,GAAW,GAAoB;CAC/C,IAAI,MAAM,GAAG,OAAO;CACpB,MAAM,CAAC,OAAO,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;CAC3D,OAAO,WAAW,OAAO,IAAI;AAC/B;;;;;;;;;;AAWA,MAAM,2BAA2B;;AAGjC,SAAS,WAAW,UAAkB,QAAyB;CAC7D,IAAI,WAAW,GAAG,SAAS,MAAM,WAAW,GAAG,SAAS,KAAK,OAAO;CACpE,OAAO,SAAS,SAAS,GAAG,KAAK,WAAW,GAAG,SAAS,MAAM,GAAG,EAAE,EAAE;AACvE;AAEA,SAAS,YAAY,OAAmB,UAAkB,UAA0B;CAClF,OAAO,YAAY,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC;AACrD;AAEA,SAAS,cAAc,OAAmB,UAAkB,UAA0B;CACpF,OAAO,YAAY,MAAM,YAAY,QAAQ,CAAC,GAAG,MAAM,YAAY,QAAQ,CAAC,CAAC;AAC/E;AAEA,SAAS,UAAU,OAAmB,UAAkB,UAA2B;CACjF,MAAM,YAAY,aAAa,QAAQ;CACvC,MAAM,YAAY,aAAa,QAAQ;CACvC,IAAI,cAAc,MAAM,cAAc,IAAI,OAAO;CACjD,IAAI,cAAc,MAAM,cAAc,IAAI,OAAO;CAEjD,IAAI,cAAc,WAAW,OAAO;CACpC,OAAO,mBAAmB,WAAW,WAAW,KAAK;AACvD;;;;;;;;;;;;;;AAeA,SAAS,mBAAmB,WAAmB,WAAmB,OAA4B;CAC5F,MAAM,eAAe,UAAU,MAAM,GAAG;CACxC,MAAM,eAAe,UAAU,MAAM,GAAG;CACxC,IAAI,aAAa,WAAW,aAAa,QAAQ,OAAO;CACxD,OAAO,aAAa,OAAO,SAAS,UAAU;EAC5C,MAAM,cAAc,aAAa;EACjC,IAAI,gBAAgB,QAAW,OAAO;EACtC,IAAI,YAAY,aAAa,OAAO;EACpC,OAAO,eAAe,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC;CAC1D,CAAC;AACH;;;;;;;;;;;AAYA,SAAS,eAAe,GAAwB,GAAiC;CAC/E,IAAI,EAAE,SAAS,EAAE,MAAM,OAAO;CAC9B,IAAI,EAAE,OAAO,0BAA0B,OAAO;CAC9C,MAAM,QAAQ,CAAC,GAAG,CAAC;CACnB,MAAM,4BAAY,IAAI,IAAoB;CAC1C,KAAK,MAAM,SAAS,GAClB,IAAI,CAAC,QAAQ,OAAO,OAAO,2BAAW,IAAI,IAAI,CAAC,GAAG,OAAO;CAE3D,OAAO;AACT;;AAGA,SAAS,QACP,OACA,OACA,WACA,SACS;CACT,KAAK,MAAM,aAAa,OAAO;EAC7B,IAAI,QAAQ,IAAI,SAAS,KAAK,CAAC,SAAS,OAAO,SAAS,GAAG;EAC3D,QAAQ,IAAI,SAAS;EACrB,MAAM,SAAS,UAAU,IAAI,SAAS;EACtC,IAAI,WAAW,UAAa,QAAQ,QAAQ,OAAO,WAAW,OAAO,GAAG;GACtE,UAAU,IAAI,WAAW,KAAK;GAC9B,OAAO;EACT;CACF;CACA,OAAO;AACT;;;;;;;;;;AAWA,SAAS,wBAAwB,WAAmB,WAAmB,OAA4B;CACjG,MAAM,YAAY,UAAU,MAAM,GAAG,SAAS,SAAS,CAAC;CACxD,MAAM,YAAY,UAAU,MAAM,GAAG,SAAS,SAAS,CAAC;CACxD,IAAI,cAAc,WAAW,OAAO;CACpC,MAAM,aAAa,MAAM,SAAS;CAClC,MAAM,aAAa,MAAM,SAAS;CAClC,IAAI,WAAW,SAAS,WAAW,MAAM,OAAO;CAIhD,IAAI,WAAW,OAAO,0BAA0B,OAAO;CACvD,KAAK,MAAM,SAAS,YAAY;EAC9B,IAAI,YAAY;EAChB,KAAK,MAAM,aAAa,YACtB,IAAI,SAAS,OAAO,SAAS,GAAG;GAC9B,YAAY;GACZ;EACF;EAEF,IAAI,CAAC,WAAW,OAAO;CACzB;CACA,OAAO;AACT;AAEA,SAAS,SAAS,OAAuB;CACvC,MAAM,MAAM,MAAM,QAAQ,GAAG;CAC7B,OAAO,MAAM,IAAI,MAAM,SAAS;AAClC;;;;;;;;;AAiBA,SAAgB,mBAA+B;CAC7C,MAAM,uBAAO,IAAI,IAAiC;CAClD,MAAM,SAAqB,UAAU;EACnC,MAAM,SAAS,KAAK,IAAI,KAAK;EAC7B,IAAI,WAAW,QAAW,OAAO;EACjC,MAAM,QAA6B,IAAI,IAAI,aAAa,KAAK,CAAC;EAC9D,KAAK,IAAI,OAAO,KAAK;EACrB,OAAO;CACT;CACA,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,WAAW,UAA0B;EACzC,MAAM,SAAS,OAAO,IAAI,KAAK;EAC/B,IAAI,WAAW,QAAW,OAAO;EACjC,MAAM,QAAQ,aAAa,KAAK;EAChC,OAAO,IAAI,OAAO,KAAK;EACvB,OAAO;CACT;;;;;;;;;;;;;;;;;;;;CAoBA,MAAM,cAAc,UAAkB,aAA8B;EAClE,MAAM,YAAY,QAAQ,QAAQ;EAClC,MAAM,YAAY,QAAQ,QAAQ;EAClC,IAAI,cAAc,WAAW,OAAO;EACpC,IAAI,cAAc,MAAM,cAAc,IAAI,OAAO;EACjD,IAAI,CAAC,wBAAwB,WAAW,WAAW,KAAK,GAAG,OAAO;EAClE,OAAO,mBAAmB,WAAW,WAAW,KAAK;CACvD;CACA,OAAO;EACL,OAAO,UAAU,aAAa,YAAY,OAAO,UAAU,QAAQ;EACnE,SAAS,UAAU,aAAa,cAAc,OAAO,UAAU,QAAQ;EACvE,kBAAkB;CACpB;AACF;AAEA,SAAS,aAAa,OAAuB;CAC3C,MAAM,YAAY,MAAM,QAAQ,IAAI;CACpC,IAAI,aAAa,GAAG,OAAO,MAAM,MAAM,GAAG,SAAS;CACnD,MAAM,UAAU,MAAM,YAAY,GAAG;CACrC,IAAI,WAAW,GAAG,OAAO,MAAM,MAAM,GAAG,OAAO;CAC/C,OAAO;AACT;;AAGA,SAAgB,YAAY,OAAuB;CACjD,MAAM,YAAY,MAAM,QAAQ,IAAI;CACpC,IAAI,aAAa,GAAG,OAAO,MAAM,MAAM,YAAY,CAAC;CACpD,MAAM,UAAU,MAAM,YAAY,GAAG;CACrC,IAAI,WAAW,GAAG,OAAO,MAAM,MAAM,UAAU,CAAC;CAChD,OAAO;AACT;;;;;ACzYA,MAAM,gBAAgB;;AAGtB,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;AAwCtC,SAAS,mBAAmB,YAAsC;CAChE,WAAW,KAAK,iBAAiB;CACjC,MAAM,0BAAU,IAAI,IAAc;CAClC,MAAM,0BAAU,IAAI,IAAc;CAClC,MAAM,QAAsB,CAAC;CAC7B,KAAK,MAAM,aAAa,YAAY;EAClC,IAAI,QAAQ,IAAI,UAAU,KAAK,EAAE,KAAK,QAAQ,IAAI,UAAU,KAAK,EAAE,GAAG;EACtE,QAAQ,IAAI,UAAU,KAAK,EAAE;EAC7B,QAAQ,IAAI,UAAU,KAAK,EAAE;EAC7B,MAAM,KAAK,SAAS;CACtB;CACA,OAAO;EAAE;EAAO;EAAS;CAAQ;AACnC;;;;;;;;AASA,SAAS,kBAAkB,GAAe,GAAuB;CAC/D,IAAI,EAAE,UAAU,EAAE,OAAO,OAAO,EAAE,QAAQ,EAAE;CAC5C,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,KAAK;CACjE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,IAAI;AAClE;;AAGA,SAAS,UAAU,SAA8B,SAA4C;CAC3F,OAAO,QAAQ,QAAQ,WAAW,CAAC,QAAQ,IAAI,OAAO,EAAE,CAAC;AAC3D;;;;;;;;;;;;AAaA,SAAgB,aACd,MACA,MAKA;CACA,MAAM,2BAAW,IAAI,IAAwB;CAC7C,KAAK,MAAM,KAAK,MAAM,SAAS,IAAI,EAAE,IAAI,CAAC;CAC1C,MAAM,UAAwB,CAAC;CAC/B,MAAM,gBAA4B,CAAC;CACnC,MAAM,2BAAW,IAAI,IAAc;CACnC,KAAK,MAAM,KAAK,MAAM;EACpB,MAAM,IAAI,SAAS,IAAI,EAAE,EAAE;EAC3B,IAAI,MAAM,QAAW;GACnB,QAAQ,KAAK;IAAE,MAAM;IAAG,MAAM;IAAG,WAAW;GAAW,CAAC;GACxD,SAAS,IAAI,EAAE,EAAE;EACnB,OACE,cAAc,KAAK,CAAC;CAExB;CAEA,OAAO;EAAE;EAAS;EAAe,eADX,KAAK,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CACd;CAAE;AACjD;;;;;;;;;;;;AAaA,SAAgB,oBACd,eACA,eACA,WAKA;CACA,IAAI,cAAc,QAAQ,UAAU,SAAS,GAC3C,OAAO;EAAE,SAAS,CAAC;EAAG,eAAe,CAAC,GAAG,aAAa;EAAG,eAAe,CAAC,GAAG,aAAa;CAAE;CAE7F,MAAM,2BAAW,IAAI,IAAwB;CAC7C,KAAK,MAAM,KAAK,eAAe,SAAS,IAAI,EAAE,IAAI,CAAC;CAKnD,MAAM,4BAAY,IAAI,IAAoE;CAC1F,KAAK,MAAM,KAAK,eAAe;EAC7B,MAAM,UAAU,UAAU,IAAI,EAAE,OAAO,IAAI;EAC3C,IAAI,YAAY,QAAW;EAC3B,MAAM,aAAa,cAAc,EAAE,IAAI,EAAE,OAAO,MAAM,OAAO;EAC7D,IAAI,eAAe,MAAM;EACzB,MAAM,OAAO,SAAS,IAAI,UAAU;EACpC,IAAI,SAAS,QAAW;EACxB,MAAM,QAAQ,UAAU,IAAI,UAAU;EACtC,IAAI,UAAU,QAAW,UAAU,IAAI,YAAY;GAAE;GAAM,OAAO,CAAC,CAAC;EAAE,CAAC;OAClE,MAAM,MAAM,KAAK,CAAC;CACzB;CAEA,MAAM,UAAwB,CAAC;CAC/B,MAAM,2BAAW,IAAI,IAAc;CACnC,MAAM,2BAAW,IAAI,IAAc;CACnC,KAAK,MAAM,EAAE,MAAM,WAAW,UAAU,OAAO,GAAG;EAChD,MAAM,OAAO,SAAS,KAAK;EAC3B,QAAQ,KAAK;GAAE;GAAM;GAAM,WAAW;EAAa,CAAC;EACpD,SAAS,IAAI,KAAK,EAAE;EACpB,SAAS,IAAI,KAAK,EAAE;CACtB;CACA,OAAO;EACL;EACA,eAAe,UAAU,eAAe,QAAQ;EAChD,eAAe,UAAU,eAAe,QAAQ;CAClD;AACF;;AAGA,SAAS,SAAS,SAAuD;CACvE,IAAI,SAAS,QAAQ;CACrB,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,KAAK,OAAO,IAAI,SAAS;CAEtC,OAAO;AACT;;;;;;;;;;;AAYA,SAAS,cAAc,IAAc,SAAiB,SAAkC;CACtF,MAAM,QAAQ,GAAG,QAAQ,GAAG;CAC5B,MAAM,OAAO,GAAG,QAAQ,GAAG;CAC3B,IAAI,QAAQ,KAAK,OAAO,KAAK,OAAO,OAAO,OAAO;CAElD,IADiB,GAAG,MAAM,QAAQ,GAAG,IAC1B,MAAM,SAAS,OAAO;CACjC,OAAO,YAAY;EACjB,UAAU,GAAG,MAAM,GAAG,KAAK;EAC3B,MAAM;EACN,eAAe,GAAG,MAAM,OAAO,CAAC;CAClC,CAAC;AACH;;;;;;;;;;;AAYA,SAAgB,2BACd,eACA,eAKA;CACA,MAAM,aAAa,aAAa,aAAa;CAC7C,MAAM,aAAa,aAAa,aAAa;CAC7C,MAAM,SAAS,iBAAiB;CAChC,MAAM,UAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,OAAO,UAAU,YAAY;EACvC,MAAM,QAAQ,WAAW,IAAI,KAAK;EAClC,IAAI,UAAU,QAAW;EACzB,QAAQ,KAAK,GAAG,qBAAqB,OAAO,OAAO,MAAM,CAAC;CAC5D;CACA,MAAM,WAAW,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,KAAK,EAAE,CAAC;CAC5D,MAAM,WAAW,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,KAAK,EAAE,CAAC;CAC5D,OAAO;EACL;EACA,eAAe,UAAU,eAAe,QAAQ;EAChD,eAAe,UAAU,eAAe,QAAQ;CAClD;AACF;;;;;;AAOA,SAAS,aAAa,SAAuD;CAC3E,MAAM,yBAAS,IAAI,IAAwB;CAC3C,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,WAAW,OAAO,YAAY,UAAU,eAAe;EAClE,MAAM,SAAS,OAAO,IAAI,OAAO,YAAY,KAAK,KAAK,CAAC;EACxD,OAAO,KAAK,MAAM;EAClB,OAAO,IAAI,OAAO,YAAY,OAAO,MAAM;CAC7C;CACA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAS,qBACP,OACA,OACA,QACc;CACd,IAAI,WAAW,CAAC,GAAG,KAAK;CACxB,IAAI,WAAW,CAAC,GAAG,KAAK;CACxB,MAAM,UAAwB,CAAC;CAC/B,OAAO,SAAS,SAAS,KAAK,SAAS,SAAS,GAAG;EACjD,MAAM,OAAO,SAAS,WAAW,IAAI,SAAS,KAAK;EACnD,IAAI,SAAS,QAAW;GACtB,MAAM,OAAO,cAAc,MAAM,UAAU,MAAM;GACjD,IAAI,SAAS,QAAW;GACxB,QAAQ,KAAK;IAAE,MAAM;IAAM;IAAM,WAAW;GAAoB,CAAC;GACjE;EACF;EACA,MAAM,aAA2B,CAAC;EAClC,KAAK,MAAM,QAAQ,UACjB,KAAK,MAAM,QAAQ,UAAU;GAC3B,MAAM,QAAQ,OAAO,KAAK,KAAK,MAAM,KAAK,IAAI;GAC9C,IAAI,SAAS,+BAA+B,WAAW,KAAK;IAAE;IAAM;IAAM;GAAM,CAAC;EACnF;EAEF,MAAM,WAAW,mBAAmB,UAAU;EAC9C,IAAI,SAAS,MAAM,WAAW,GAAG;EACjC,KAAK,MAAM,EAAE,MAAM,UAAU,SAAS,OACpC,QAAQ,KAAK;GAAE;GAAM;GAAM,WAAW;EAAwC,CAAC;EAEjF,WAAW,UAAU,UAAU,SAAS,OAAO;EAC/C,WAAW,UAAU,UAAU,SAAS,OAAO;CACjD;CACA,OAAO;AACT;;;;;;AAOA,SAAS,cACP,MACA,OACA,QACsB;CACtB,IAAI;CACJ,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,OAAO,KAAK,KAAK,MAAM,KAAK,IAAI;EAC9C,IAAI,SAAS,UAAa,QAAQ,KAAK,OAAO;GAC5C,OAAO;IAAE;IAAM;GAAM;GACrB;EACF;EACA,IAAI,UAAU,KAAK,SAAS,KAAK,KAAK,KAAK,KAAK,IAAI,OAAO;GAAE;GAAM;EAAM;CAC3E;CACA,OAAO,MAAM;AACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,wBACd,eACA,eAKA;CACA,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,MAAM,KAAK,eAAe;EAC7B,IAAI,EAAE,SAAS;EAGf,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG;EAC/B,MAAM,MAAM,UAAU,CAAC;EACvB,MAAM,SAAiB,QAAQ,IAAI,GAAG,KAAK;GAAE,SAAS,CAAC;GAAG,yBAAS,IAAI,IAAI;EAAE;EAC7E,MAAM,WAAW,OAAO,QAAQ;EAChC,OAAO,QAAQ,KAAK,CAAC;EACrB,KAAK,MAAM,SAAS,aAAa,EAAE,IAAI,GAAG;GACxC,MAAM,UAAU,OAAO,QAAQ,IAAI,KAAK,KAAK,CAAC;GAC9C,QAAQ,KAAK,QAAQ;GACrB,OAAO,QAAQ,IAAI,OAAO,OAAO;EACnC;EACA,QAAQ,IAAI,KAAK,MAAM;CACzB;CACA,MAAM,2BAAW,IAAI,IAAwB;CAC7C,KAAK,MAAM,UAAU,QAAQ,OAAO,GAAG,SAAS,IAAI,QAAQ,IAAI,WAAW,OAAO,QAAQ,MAAM,CAAC;CACjG,MAAM,SAAS,iBAAiB;CAChC,MAAM,aAA2B,CAAC;CAClC,IAAI,aAAa;CACjB,KAAK,MAAM,KAAK,eAAe;EAC7B,IAAI,EAAE,SAAS;EAKf,IAAI,EAAE,cAAc,QAAQ,EAAE,cAAc,QAAW;EAIvD,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG;EAC/B,MAAM,SAAS,QAAQ,IAAI,UAAU,CAAC,CAAC;EACvC,IAAI,WAAW,QAAW;EAC1B,MAAM,YAAY,aAAa,EAAE,IAAI;EAGrC,MAAM,cAAc,mBAAmB,SAAS;EAOhD,MAAM,SAAS,aAAa,EAAE,IAAI;EAClC,IAAI,QAAQ;EACZ,KAAK,MAAM,SAAS,QAAQ,SAAS,OAAO,QAAQ,IAAI,KAAK,CAAC,EAAE,UAAU;EAC1E,IAAI,SAAS,OAAO,QAAQ,QAAQ;GAClC,KAAK,MAAM,KAAK,OAAO,SACrB,iBAAiB,GAAG,GAAG,QAAQ,aAAa,WAAW,UAAU;GAEnE;EACF;EACA,MAAM,UAAU,SAAS,IAAI,MAAM;EACnC,IAAI,YAAY,QAAW;EAC3B;EACA,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,YAAY,OAAO,QAAQ,IAAI,KAAK,KAAK,CAAC,GAAG;GACtD,IAAI,QAAQ,cAAc,YAAY;GACtC,QAAQ,YAAY;GACpB,MAAM,IAAI,OAAO,QAAQ;GACzB,IAAI,MAAM,QAAW,iBAAiB,GAAG,GAAG,QAAQ,aAAa,WAAW,UAAU;EACxF;CAEJ;CACA,MAAM,WAAW,mBAAmB,UAAU;CAC9C,OAAO;EACL,SAAS,SAAS,MAAM,KAAK,EAAE,MAAM,YAAY;GAC/C;GACA;GACA,WAAW;EACb,EAAE;EACF,eAAe,UAAU,eAAe,SAAS,OAAO;EACxD,eAAe,UAAU,eAAe,SAAS,OAAO;CAC1D;AACF;;;;;;;;;;;;;;AAeA,SAAS,iBACP,MACA,MACA,QACA,aACA,WACA,YACM;CACN,MAAM,SAAS,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI;CACjD,IAAI,SAAS,aAAa;CAC1B,IAAI,CAAC,OAAO,iBAAiB,KAAK,MAAM,KAAK,IAAI,GAAG;CACpD,MAAM,QACJ,KAAM,SACN,KAAM,oBAAoB,KAAK,aAAa,MAAM,KAAK,aAAa,IAAI,IACxE,KAAM;CAIR,IAAI,SAAS,WAAW,WAAW,KAAK;EAAE;EAAM;EAAM;CAAM,CAAC;AAC/D;AAEA,SAAS,UAAU,GAAqB;CACtC,MAAM,MAAM,EAAE,cAAc,QAAQ,EAAE,cAAc,SAAY,WAAW;CAC3E,OAAO,GAAG,EAAE,KAAK,IAAI;AACvB;;;;;;;;;;;;AAaA,SAAS,mBAAmB,WAA2B;CACrD,OAAO,KAAK,YAAY;AAC1B;;;;;;;;;AAyBA,MAAM,mBAAmB;AAEzB,SAAS,aAAa,OAAkC;CACtD,MAAM,SAAS,aAAa,YAAY,KAAK,CAAC;CAC9C,OAAO,OAAO,WAAW,IAAI,CAAC,gBAAgB,IAAI;AACpD;;;;;;;;AASA,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;;;;;;;AAQ1B,MAAM,uBAAuB;AAE7B,SAAS,aAAa,OAAuB;CAC3C,MAAM,SAAS,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC;CAChD,IAAI,UAAU,GAAG,OAAO;CACxB,IAAI,WAAW,GAAG,OAAO;CACzB,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,iBAAiB,OAAwB;CAChD,OAAO,aAAa,KAAK,CAAC,CAAC,SAAS;AACtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,sBACd,eACA,eAKA;CACA,MAAM,QAAQ,cAAc,QAAQ,MAAM,EAAE,OAAO;CACnD,MAAM,QAAQ,cAAc,QAAQ,MAAM,EAAE,OAAO;CAMnD,MAAM,aAAyB,CAAC;CAChC,KAAK,MAAM,SAAS,CAAC,SAAS,OAAO,GACnC,WAAW,KAAK,GAAG,kBAAkB,OAAO,OAAO,KAAK,CAAC;CAG3D,MAAM,UAAU,YAAY,UAAU;CACtC,OAAO;EACL,SAAS,QAAQ,KAAK,EAAE,MAAM,YAAY;GACxC;GACA;GACA,WAAW;EACb,EAAE;EACF,eAAe,UAAU,eAAe,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;EACpF,eAAe,UAAU,eAAe,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;CACtF;AACF;;;;;;;;;;;;;;AAqBA,SAAS,YAAY,OAAwC;CAC3D,MAAM,UAAU,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,WAAW;CAC3C,MAAM,yBAAS,IAAI,IAAwB;CAC3C,MAAM,yBAAS,IAAI,IAAwB;CAC3C,KAAK,MAAM,CAAC,OAAO,SAAS,QAAQ,QAAQ,GAAG;EAC7C,SAAS,QAAQ,KAAK,KAAK,IAAI,KAAK;EACpC,SAAS,QAAQ,KAAK,KAAK,IAAI,KAAK;CACtC;CACA,MAAM,YAAY,UAA4B;EAC5C,MAAM,OAAO,QAAQ;EACrB,IAAI,SAAS,QAAW,OAAO,CAAC;EAEhC,OAAO,CADU,GAAI,OAAO,IAAI,KAAK,KAAK,EAAE,KAAK,CAAC,GAAI,GAAI,OAAO,IAAI,KAAK,KAAK,EAAE,KAAK,CAAC,CAC1E,CAAC,CAAC,QAAQ,UAAU,UAAU,KAAK;CAClD;CAEA,MAAM,SAAS,QAAQ,UAAU,KAAK;CACtC,MAAM,WAAuB,CAAC;;CAE9B,MAAM,YAAY,UAAwB;EACxC,IAAI,OAAO;EACX,IAAI,OAAO;EACX,OAAO,CAAC,OAAO,OAAO;GACpB,OAAO,QAAQ;GACf,MAAM,OAAO,QAAQ;GACrB,IAAI,QAAQ,SAAS,QAAW,SAAS,KAAK,IAAI;GAClD,OAAO,CAAC;GACR,MAAM,OAAO,SAAS,IAAI,CAAC,CAAC,MAAM,UAAU,OAAO,WAAW,KAAK;GACnE,IAAI,SAAS,QAAW;GACxB,OAAO;EACT;CACF;CAKA,KAAK,MAAM,CAAC,UAAU,QAAQ,QAAQ,GACpC,IAAI,CAAC,OAAO,UAAU,SAAS,KAAK,CAAC,CAAC,UAAU,GAAG,SAAS,KAAK;CAEnE,KAAK,MAAM,CAAC,UAAU,QAAQ,QAAQ,GACpC,IAAI,CAAC,OAAO,QAAQ,SAAS,KAAK;CAEpC,OAAO;AACT;AAEA,SAAS,YAAY,GAAa,GAAqB;CACrD,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,KAAK;CACjE,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,IAAI;AAClE;AAEA,SAAS,SAAS,OAAgC,KAAe,OAAqB;CACpF,MAAM,SAAS,MAAM,IAAI,GAAG;CAC5B,IAAI,WAAW,QAAW,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC;MAC3C,OAAO,KAAK,KAAK;AACxB;;;;;;;AAQA,SAAS,kBACP,OACA,OACA,OACY;CACZ,MAAM,WAAW,aAAa,OAAO,KAAK;CAC1C,MAAM,WAAW,aAAa,OAAO,KAAK;CAC1C,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,KAAK,SAAS,UAAU;EAClC,MAAM,OAAO,SAAS,IAAI,GAAG;EAC7B,IAAI,SAAS,QAAW,MAAM,KAAK;GAAE;GAAM;EAAK,CAAC;CACnD;CACA,OAAO;AACT;;AAGA,SAAS,aACP,SACA,OACuB;CACvB,MAAM,uBAAO,IAAI,IAAsB;CACvC,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,MAAM,MAAM;EACxB,IAAI,OAAO,IAAI,GAAG,GAAG;EACrB,IAAI,KAAK,IAAI,GAAG,GAAG;GACjB,KAAK,OAAO,GAAG;GACf,OAAO,IAAI,GAAG;GACd;EACF;EACA,KAAK,IAAI,KAAK,MAAM;CACtB;CACA,OAAO;AACT;;;;;AAMA,SAAS,QAAQ,QAA0B;CACzC,OAAO,GAAG,OAAO,KAAK,GAAG,YAAY,OAAO,IAAI;AAClD;AAEA,SAAS,QAAQ,QAA0B;CACzC,OAAO,GAAG,OAAO,KAAK,GAAG,SAAS,OAAO,OAAO,IAAI;AACtD;AAEA,SAAS,SAAS,MAAsB;CACtC,MAAM,QAAQ,KAAK,YAAY,GAAG;CAClC,OAAO,SAAS,IAAI,KAAK,MAAM,QAAQ,CAAC,IAAI;AAC9C;;;;;;;;;;;;;;;;;;;;;;;ACvvBA,SAAgB,cAAc,OAAkC;CAC9D,MAAM,UAAU,eAAe,MAAM,OAAO;CAC5C,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC;CAElC,MAAM,YAAY,IAAI,IAAc,OAAO;CAK3C,OAFmB,iCAA2C,SAFhD,aAAa,MAAM,eAAe,MAAM,eAAe,SAEM,IAAI,OAAO,EAEtE,CAAC,CAAC,IAAI,eAAe;AACvC;AAEA,MAAM,kBAAkB;;;;;;;;;;;;AAaxB,SAAS,WAAW,QAA2B;CAC7C,OAAO,GAAG,kBAAkB;AAC9B;;;;;;;;;;;;;;AAeA,SAAS,gBAAgB,SAA2C;CAClE,MAAM,SAAS,QAAQ;CACvB,IAAI,WAAW,QACb,MAAM,IAAI,UACR,qKAEA,EAAE,MAAM,2BAA2B,CACrC;CAEF,MAAM,SAAsB;EAAE,IAAI,WAAW,MAAM;EAAG,SAAS,CAAC,GAAG,OAAO;CAAE;CAC5E,2BAA2B,MAAM;CACjC,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,YAAY,QAA+B;CACzD,MAAM,SAAS,OAAO,QAAQ;CAC9B,IAAI,WAAW,QAAW;EACxB,MAAM,YAAY,sBAAsB,OAAO,EAAE;EACjD,MAAM,IAAI,UAAU,UAAU,SAAS;GACrC,MAAM;GACN,OAAO,UAAU;EACnB,CAAC;CACH;CACA,OAAO;AACT;;;;;;;;;;;;AAkCA,SAAgB,qBAAqB,OAA6C;CAChF,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;EACL,MAAM;EACN,SAAS;EACT,SAAS,sCAAsC,UAAU,OAAO,SAAS,OAAO,MAAM;CACxF;CAEF,MAAM,EAAE,IAAI,YAAY;CACxB,MAAM,UAAU,OAAO,OAAO,WAAW,KAAK;CAC9C,IAAI,OAAO,OAAO,UAChB,OAAO;EACL,MAAM;EACN;EACA,SAAS,eAAe,QAAQ,6BAA6B,OAAO,GAAG;CACzE;CAEF,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,MAAM,WAAW,OAAO,WAAW,QAAQ,GAChF,OAAO;EACL,MAAM;EACN;EACA,SAAS,eAAe,QAAQ;CAClC;CAGF,MAAM,SAAS,QAAQ;CACvB,IAAI,WAAW,QAAW,OAAO,sBAAsB,OAAO;CAM9D,MAAM,iBAAiB,oBAAoB,MAAM;CACjD,IAAI,mBAAmB,MACrB,OAAO;EACL,MAAM;EACN;EACA,SACE,uBAAuB,OAAO,sCAAsC,eAAe;CAEvF;CAEF,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,WAAW,QAAQ,IAAI;EAC7B,MAAM,UAAU,QAAQ;EACxB,IAAI,WAAW,SAAS;EACxB,OAAO;GACL,MAAM;GACN;GACA,SACE,eAAe,QAAQ,0DAA0D,EAAE,KAC9E,QAAQ,aAAa,SAAS;EAEvC;CACF;CAKA,MAAM,WAAW,WAAW,MAAkB;CAC9C,IAAI,OAAO,UACT,OAAO;EACL,MAAM;EACN;EACA,SACE,mBAAmB,GAAG,oCAAoC,OAAO,eACpD,SAAS;CAC1B;CAEF,OAAO;AACT;;AAGA,SAAS,oBAAoB,IAA2B;CACtD,MAAM,QAAQ,GAAG,QAAQ,GAAG;CAC5B,IAAI,QAAQ,GAAG,OAAO;CACtB,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK;CAC/B,OAAO,sBAAsB,IAAI,KAAK,IAAI,QAAQ;AACpD;;AAGA,SAAS,sBAAsB,SAAuC;CACpE,OAAO;EACL,MAAM;EACN;EACA,SACE,eAAe,QAAQ;CAE3B;AACF;;;;;;AAOA,SAAgB,2BAA2B,QAA2B;CACpE,MAAM,YAAY,qBAAqB,MAAM;CAC7C,IAAI,cAAc,MAAM;CACxB,MAAM,IAAI,UAAU,UAAU,SAAS;EACrC,MAAM;EACN,OAAO,UAAU;CACnB,CAAC;AACH;;;;;;;;;;;;AAaA,SAAS,eAAe,SAA8C;CACpE,MAAM,MAAkB,CAAC;CACzB,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,KAAK,SAAS,MAAM;EAC1B,IAAI,OAAO,MAAM;EACjB,IAAI,KAAK,EAAE;CACb;CACA,OAAO;AACT;AAEA,SAAS,SAAS,QAAuC;CACvD,QAAQ,OAAO,QAAf;EACE,KAAK,SACH,OAAO,OAAO,OAAO;EACvB,KAAK,WAEH,OAAO,OAAO,OAAO;EACvB,KAAK,WAKH,OAAO,OAAO,OAAO;EACvB,KAAK;EACL,KAAK;EACL,KAAK,mBACH,OAAO,OAAO,MAAM;EACtB,KAAK,SAEH,OAAO;CACX;AAGF;;;;;;;;;;;AAYA,SAAS,aACP,WACA,WACA,SACwB;CACxB,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,QAAQ,WAAW,sBAAsB,MAAM,SAAS,KAAK;CACxE,KAAK,MAAM,QAAQ,WAAW,sBAAsB,MAAM,SAAS,KAAK;CACxE,OAAO;AACT;AAEA,SAAS,sBACP,MACA,SACA,KACM;CACN,IAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,EAAE,GAAG;CACtD,IAAI,KAAK,CAAC,KAAK,MAAM,KAAK,EAAE,CAAC;AAC/B;;;;;;;;;;;;;;;;ACtTA,SAAgB,eAAe,MAAgB,MAA8B;CAE3E,IADuB,KAAK,YAAY,KAAK,SACzB,OAAO;CAC3B,MAAM,cAAc,KAAK,OAAO,SAAS,KAAK,OAAO,QAAQ,KAAK,OAAO,KAAK;CAC9E,MAAM,qBACJ,KAAK,YAAY,QAAQ,KAAK,YAAY,OAC1C,KAAK,YAAY,UAAU,KAAK,YAAY,SAC5C,KAAK,YAAY,WAAW,KAAK,YAAY;CAC/C,IAAI,eAAe,oBAAoB,OAAO;CAC9C,IAAI,aAAa,OAAO;CACxB,IAAI,oBAAoB,OAAO;CAC/B,OAAO;AACT;AAKA,SAAgB,cAAc,MAA+B;CAC3D,OAAO,KAAK,UAAU,eAAe;AACvC;;;;ACHA,MAAM,cAAc;AA6BpB,MAAM,oBAAoB;CAAE,MAAM;CAAS,SAAS;AAAQ;;;;;;;;AAS5D,SAAgB,UACd,OACqF;CACrF,eAAe,MAAM,QAAQ,QAAQ;CACrC,eAAe,MAAM,QAAQ,QAAQ;CACrC,mBAAmB,MAAM,QAAQ,MAAM,MAAM;CAC7C,MAAM,SAAS,aAAa,MAAM,OAAO,SAAS,MAAM,OAAO,OAAO;CACtE,MAAM,SAAS,oBACb,OAAO,eACP,OAAO,eACP,MAAM,cAAc,IACtB;CACA,MAAM,SAAS,2BAA2B,OAAO,eAAe,OAAO,aAAa;CACpF,MAAM,SAAS,wBAAwB,OAAO,eAAe,OAAO,aAAa;CACjF,MAAM,WAAW,sBAAsB,OAAO,eAAe,OAAO,aAAa;CAEjF,MAAM,QAAsB;EAC1B,GAAG,OAAO;EACV,GAAG,OAAO;EACV,GAAG,OAAO;EACV,GAAG,OAAO;EACV,GAAG,SAAS;CACd;CAKA,MAAM,UAAqC;EACzC,SAAS;EACT,aAAa;EACb,OAAO;EACP,SAAS;EACT,OAAO;EACP,cAAc;EACd,SAAS;EACT,gBAAgB;EAChB,WAAW;EACX,cAAc;EACd,gBAAgB;EAChB,iBAAiB;EACjB,mBAAmB;EACnB,mBAAmB;EACnB,WAAW;EACX,aAAa;CACf;CAGA,IAAI,UAAU;CAEd,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,eAAe,KAAK,MAAM,KAAK,IAAI;EAClD,IAAI,WAAW,aAAa;GAC1B,QAAQ;GACR;EACF;EACA,IAAI,WAAW,mBAAmB;GAChC,QAAQ;GACR,QAAQ,KAAK;IACX,QAAQ;IACR,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,WAAW,cAAc,KAAK,IAAI;GACpC,CAAC;GACD;EACF;EACA,IAAI,WAAW,SAAS;GACtB,QAAQ;GACR,QAAQ,KAAK;IACX,QAAQ;IACR,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,WAAW,KAAK;GAClB,CAAC;GACD;EACF;EACA,IAAI,WAAW,WAAW;GACxB,QAAQ;GACR,QAAQ,KAAK;IACX,QAAQ;IACR,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,OAAO,mBAAmB,KAAK,MAAM,KAAK,MAAM,MAAM,SAAS,CAAC,CAAC;GACnE,CAAC;GACD;EACF;EAEA,QAAQ;EACR,QAAQ,KAAK;GACX,QAAQ;GACR,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,WAAW,KAAK;GAChB,OAAO,mBAAmB,KAAK,MAAM,KAAK,MAAM,MAAM,SAAS,CAAC,CAAC;EACnE,CAAC;CACH;CAEA,MAAM,qBAAqB,SAAS;CACpC,MAAM,qBAAqB,SAAS;CAepC,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,MAAM,aAAa,SAAS;CAC5B,MAAM,aAAa,SAAS;CAE5B,KAAK,MAAM,KAAK,oBAAoB;EAClC,IAAI,EAAE,SAAS;GACb,QAAQ;GACR;EACF;EACA,MAAM,SAAS,WAAW,IAAI,EAAE,OAAO,IAAI;EAC3C,IAAI,WAAW,QAAW;GACxB;GACA,QAAQ,KAAK;IAAE,QAAQ;IAAW,QAAQ;IAAG,YAAY;IAAQ;GAAO,CAAC;GACzE;EACF;EACA,QAAQ;EACR,QAAQ,KAAK;GAAE,QAAQ;GAAS,QAAQ;EAAE,CAAC;CAC7C;CACA,KAAK,MAAM,KAAK,oBAAoB;EAClC,IAAI,EAAE,SAAS;GACb,QAAQ;GACR;EACF;EACA,MAAM,SAAS,WAAW,IAAI,EAAE,OAAO,IAAI;EAC3C,IAAI,WAAW,QAAW;GACxB;GACA,QAAQ,KAAK;IAAE,QAAQ;IAAW,QAAQ;IAAG,YAAY;IAAQ;GAAO,CAAC;GACzE;EACF;EACA,QAAQ;EACR,QAAQ,KAAK;GAAE,QAAQ;GAAW,QAAQ;EAAE,CAAC;CAC/C;CAEA,MAAM,aAAa,eAAe,MAAM,OAAO,YAAY,MAAM,OAAO,UAAU;CAClF,QAAQ,kBAAkB,WAAW,MAAM;CAC3C,QAAQ,oBAAoB,WAAW,QAAQ;CAC/C,QAAQ,oBAAoB,WAAW,QAAQ;CAE/C,MAAM,eAAe,iBAAiB,MAAM,OAAO,cAAc,MAAM,OAAO,cAAc;EAC1F,MAAM;EACN,MAAM;CACR,CAAC;CACD,QAAQ,YAAY,aAAa,MAAM;CACvC,QAAQ,cAAc,aAAa,QAAQ;CAG3C,QAAQ,cAAc,aAAa,QAAQ;CAC3C,QAAQ,UAAU;CAElB,QAAQ,KAAK,mBAAmB;CAOhC,MAAM,SAAS,cAAc;EAC3B,SAAS;EACT,eAAe,2BAA2B,MAAM,MAAM;EACtD,eAAe,2BAA2B,MAAM,MAAM;CACxD,CAAC;CAED,OAAO;EACL,SAAS;EACT,WAAW,MAAM,aAAa;EAC9B,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ;EACA;EACA;EACA;EACA;EACA,aAAa,qBAAqB,YAAY,UAAU;CAC1D;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,qBACP,YACA,YACmB;CACnB,MAAM,OAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,MAAM,eAAe,YAAY;EAC3C,MAAM,aAAa,WAAW,IAAI,IAAI;EACtC,IAAI,eAAe,QAAW;EAC9B,KAAK,KAAK;GAAE;GAAM;GAAY;EAAW,CAAC;CAC5C;CACA,OAAO,KAAK,MAAM,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,CAAE;AAC7E;;AAGA,SAAS,mBAAmB,MAAU,MAAgB;CACpD,IAAI,KAAK,YAAY,KAAK,SACxB,MAAM,IAAI,UACR,mBAAmB,KAAK,QAAQ,mCAAmC,KAAK,QAAQ,qDAChF;EAAE,MAAM;EAAmB,OAAO,KAAK;CAAQ,CACjD;AAEJ;;AA2BA,MAAM,aAAa,UAAqC,MAAM,KAAK,EAAE;AAErE,MAAM,yBAA0D;CAC9D;EACE,OAAO;EACP,gBAAgB,CAAC,IAAI;EACrB,OAAO;EACP,MAAM;EACN,MAAM;EACN,aACE;CAGJ;CACA;EACE,OAAO;EACP,gBAAgB,CAAC,IAAI;EACrB,OAAO;EACP,MAAM;EACN,MAAM;EACN,aACE;CAEJ;CACA;EACE,OAAO;EACP,gBAAgB;EAChB,OAAO;EACP,MAAM;EACN,OAAO,UAAU,IAAI,MAAM,KAAK,IAAI,EAAE;EACtC,aACE;CAGJ;AACF;;;;;;;;;;;;;;AAeA,SAAS,eAAe,IAAQ,MAAoB;CAClD,IAAI,OAAO,QAAQ,OAAO,OAAO,UAC/B,MAAM,WAAW,MAAM,GAAG,KAAK,6BAA6B,cAAc,EAAE,EAAE,EAAE;CAElF,IAAI,OAAO,GAAG,YAAY,YAAY,GAAG,QAAQ,WAAW,GAC1D,MAAM,WAAW,GAAG,KAAK,WAAW,GAAG,KAAK,yCAAyC;CAEvF,KAAK,MAAM,cAAc,wBACvB,qBAAqB,GAAG,WAAW,QAAQ,GAAG,KAAK,GAAG,WAAW,SAAS,UAAU;AAExF;AAEA,SAAS,qBACP,SACA,SACA,YACM;CACN,IAAI,CAAC,MAAM,QAAQ,OAAO,GACxB,MAAM,WAAW,SAAS,GAAG,QAAQ,mBAAmB;CAE1D,MAAM,4BAAY,IAAI,IAAoB;CAC1C,KAAK,MAAM,CAAC,OAAO,UAAU,QAAQ,QAAQ,GAAG;EAC9C,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM;EACzC,MAAM,QAAQ,iBAAiB,OAAO,cAAc,UAAU;EAC9D,MAAM,MAAM,WAAW,MAAM,KAAK;EAClC,MAAM,QAAQ,UAAU,IAAI,GAAG;EAC/B,IAAI,UAAU,QAAW;GACvB,UAAU,IAAI,KAAK,KAAK;GACxB;EACF;EACA,MAAM,QAAQ,WAAW,KAAK,KAAK;EACnC,MAAM,IAAI,UACR,GAAG,aAAa,eAAe,WAAW,KAAK,IAAI,MAAM,wBACpD,MAAM,IAAI,WAAW,YAAY,IACtC;GAAE,MAAM;GAAyB,OAAO;EAAM,CAChD;CACF;AACF;;;;;;;;AASA,SAAS,iBACP,OACA,SACA,YACU;CACV,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,MAAM,WAAW,SAAS,GAAG,QAAQ,0BAA0B,cAAc,KAAK,EAAE,EAAE;CAExF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,WAAW,gBAAgB;EAC7C,MAAM,QAAS,MAAkC;EACjD,IAAI,OAAO,UAAU,UACnB,MAAM,WACJ,GAAG,QAAQ,GAAG,SACd,GAAG,QAAQ,GAAG,MAAM,yBAAyB,cAAc,KAAK,EAAE,EACpE;EAEF,MAAM,KAAK,KAAK;CAClB;CACA,OAAO;AACT;AAEA,SAAS,WAAW,SAAiB,SAA4B;CAC/D,OAAO,IAAI,UAAU,SAAS;EAAE,MAAM;EAAoB,OAAO;CAAQ,CAAC;AAC5E;AAEA,SAAS,cAAc,OAAwB;CAC7C,IAAI,UAAU,QAAW,OAAO;CAChC,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;CACjC,OAAO,KAAK,OAAO;AACrB;;;;;;;;;AAUA,SAAS,oBAAoB,GAAiB,GAAyB;CACrE,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO,EAAE,SAAS,EAAE,SAAS,KAAK;CAC7D,MAAM,MAAM,YAAY,CAAC;CACzB,MAAM,MAAM,YAAY,CAAC;CACzB,OAAO,MAAM,MAAM,KAAK,MAAM,MAAM,IAAI;AAC1C;AAEA,SAAS,YAAY,QAA8B;CACjD,IAAI,OAAO,WAAW,WAAW,OAAO,WAAW,aAAa,OAAO,WAAW,WAChF,OAAO,OAAO,OAAO;CAEvB,OAAO,OAAO,MAAM;AACtB;;;;;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,MAAkB,UAA4B,CAAC,GAAW;CAC3F,OAAO,mBAAmB,MAAM,OAAO;AACzC"}