@ctxo/cli 0.13.0 → 0.14.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.
Files changed (42) hide show
  1. package/README.md +20 -4
  2. package/dist/chunk-2Q76LH2H.js +186 -0
  3. package/dist/chunk-2Q76LH2H.js.map +1 -0
  4. package/dist/chunk-72CEQEQD.js +53 -0
  5. package/dist/chunk-72CEQEQD.js.map +1 -0
  6. package/dist/chunk-7HSA5JVP.js +1049 -0
  7. package/dist/chunk-7HSA5JVP.js.map +1 -0
  8. package/dist/chunk-G4BMKCKG.js +76 -0
  9. package/dist/chunk-G4BMKCKG.js.map +1 -0
  10. package/dist/chunk-L2CAUER6.js +508 -0
  11. package/dist/chunk-L2CAUER6.js.map +1 -0
  12. package/dist/chunk-LAX6VVBU.js +98 -0
  13. package/dist/chunk-LAX6VVBU.js.map +1 -0
  14. package/dist/chunk-SRQZDGFZ.js +128 -0
  15. package/dist/chunk-SRQZDGFZ.js.map +1 -0
  16. package/dist/{chunk-DT3ZH4AU.js → chunk-X4TVFJ4C.js} +6 -1
  17. package/dist/chunk-X4TVFJ4C.js.map +1 -0
  18. package/dist/{chunk-BYX3UFBJ.js → chunk-ZL36YS2V.js} +785 -1624
  19. package/dist/chunk-ZL36YS2V.js.map +1 -0
  20. package/dist/{cli-router-RR4CM62C.js → cli-router-AZCDNX5R.js} +982 -506
  21. package/dist/cli-router-AZCDNX5R.js.map +1 -0
  22. package/dist/index.js +13 -2874
  23. package/dist/index.js.map +1 -1
  24. package/dist/internal.d.ts +747 -0
  25. package/dist/internal.js +13 -0
  26. package/dist/{json-index-reader-OXTRTGE6.js → json-index-reader-DBW32TCN.js} +6 -4
  27. package/dist/json-index-reader-DBW32TCN.js.map +1 -0
  28. package/dist/mcp-server-6YG2QE6E.js +2604 -0
  29. package/dist/mcp-server-6YG2QE6E.js.map +1 -0
  30. package/dist/module-host-KNMKS62R.js +13 -0
  31. package/dist/module-host-KNMKS62R.js.map +1 -0
  32. package/dist/module-loader-B3LYF3W7.js +14 -0
  33. package/dist/module-loader-B3LYF3W7.js.map +1 -0
  34. package/dist/plugin-discovery-XRXI3IJF.js +13 -0
  35. package/dist/plugin-discovery-XRXI3IJF.js.map +1 -0
  36. package/llms-full.txt +105 -1
  37. package/llms.txt +21 -1
  38. package/package.json +15 -4
  39. package/dist/chunk-BYX3UFBJ.js.map +0 -1
  40. package/dist/chunk-DT3ZH4AU.js.map +0 -1
  41. package/dist/cli-router-RR4CM62C.js.map +0 -1
  42. /package/dist/{json-index-reader-OXTRTGE6.js.map → internal.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/co-change/co-change-window.ts","../src/core/config/load-config.ts","../src/core/config/config-schema.ts","../src/core/test-files/test-file-classifier.ts","../src/adapters/git/simple-git-adapter.ts","../src/core/data-coupling/data-kinds.ts","../src/core/graph/symbol-graph.ts"],"sourcesContent":["/** A duration such as `12m`: the unit is days, weeks, calendar months or calendar years. */\nexport interface CoChangeSince {\n readonly amount: number;\n readonly unit: 'd' | 'w' | 'm' | 'y';\n}\n\nconst SINCE_PATTERN = /^(\\d+)([dwmy])$/;\nconst SECONDS_PER_DAY = 86_400;\n\n/**\n * Parses a `cochange.since` duration: digits followed by `d`, `w`, `m` or `y` (for example `30d`,\n * `12m`). Returns `undefined` for anything else, and for an amount too large to be an exact integer.\n * A zero amount is valid; it keeps only the commits made at or after HEAD's committer date.\n */\nexport function parseCoChangeSince(value: string): CoChangeSince | undefined {\n const match = SINCE_PATTERN.exec(value);\n if (!match) return undefined;\n const amount = Number.parseInt(match[1]!, 10);\n if (!Number.isSafeInteger(amount)) return undefined;\n return { amount, unit: match[2] as CoChangeSince['unit'] };\n}\n\n/**\n * The unix-second instant `since` before HEAD's committer date. Months and years are calendar\n * spans in UTC: the day of the month is kept when the target month has it and otherwise clamps to\n * the last day (31 March minus one month is 28 February, or 29 in a leap year), and the time of day\n * is kept. Days and weeks are fixed 24 hour spans. The wall clock and the local time zone play no\n * part, so one HEAD always gives one cutoff.\n *\n * Returns `-Infinity` (no restriction) when the span reaches beyond every date a JavaScript Date\n * can hold, or when `headCommittedAt` is not a finite number.\n */\nexport function sinceCutoff(headCommittedAt: number, since: CoChangeSince): number {\n const cutoff = subtractSince(headCommittedAt, since);\n return Number.isFinite(cutoff) ? cutoff : Number.NEGATIVE_INFINITY;\n}\n\nfunction subtractSince(headCommittedAt: number, since: CoChangeSince): number {\n switch (since.unit) {\n case 'd':\n return headCommittedAt - since.amount * SECONDS_PER_DAY;\n case 'w':\n return headCommittedAt - since.amount * 7 * SECONDS_PER_DAY;\n case 'm':\n return subtractUtcMonths(headCommittedAt, since.amount);\n case 'y':\n return subtractUtcMonths(headCommittedAt, since.amount * 12);\n }\n}\n\nfunction subtractUtcMonths(epochSeconds: number, months: number): number {\n const date = new Date(epochSeconds * 1000);\n const dayOfMonth = date.getUTCDate();\n // Park on the 1st so that moving the month cannot overflow into the one after it.\n date.setUTCDate(1);\n date.setUTCMonth(date.getUTCMonth() - months);\n date.setUTCDate(Math.min(dayOfMonth, daysInUtcMonth(date)));\n return date.getTime() / 1000;\n}\n\nfunction daysInUtcMonth(date: Date): number {\n const lastDay = new Date(date.getTime());\n // Day 0 of the next month is the last day of this one.\n lastDay.setUTCMonth(lastDay.getUTCMonth() + 1, 0);\n return lastDay.getUTCDate();\n}\n\n/**\n * The cutoff of a co-change window: its `since` setting counted back from HEAD's committer date with\n * `sinceCutoff`, or `undefined` (no cutoff) when there is no `since` or it does not parse. The one place\n * this rule lives, for the matrix of `ctxo index` and for the window the module host hands out.\n */\nexport function windowCutoff(coChange: { readonly since?: string }, headCommittedAt: number): number | undefined {\n const since = coChange.since !== undefined ? parseCoChangeSince(coChange.since) : undefined;\n return since !== undefined ? sinceCutoff(headCommittedAt, since) : undefined;\n}\n\n/**\n * Picks the commits a co-change matrix is built from. `commits` must be newest first, as the change\n * log returns them. Any commit with a committer date works: canonical commits for the matrix, raw\n * change sets (renames intact) for the module host. The commits come back unchanged.\n *\n * `maxCommits` applies first and takes the newest N in log order ('all' takes every commit). `cutoff`\n * (a unix second from `sinceCutoff`) then keeps the commits whose committer date is at or after it.\n * The cutoff is a plain filter: a commit with a skewed older date is dropped without hiding the\n * newer commits behind it.\n *\n * A `maxCommits` below 1 or NaN selects nothing and a fractional value rounds down, so a bad value\n * can only narrow the window, never widen it.\n */\nexport function selectWindow<T extends { readonly committedAt: number }>(\n commits: readonly T[],\n maxCommits: number | 'all',\n cutoff?: number,\n): T[] {\n const newest = maxCommits === 'all' ? [...commits] : commits.slice(0, wholeCommitCount(maxCommits));\n return cutoff === undefined ? newest : newest.filter((commit) => commit.committedAt >= cutoff);\n}\n\nfunction wholeCommitCount(maxCommits: number): number {\n return maxCommits >= 1 ? Math.floor(maxCommits) : 0;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport picomatch from 'picomatch';\nimport { parseCoChangeSince } from '../co-change/co-change-window.js';\nimport { createLogger } from '../logger.js';\nimport type { CommunityDetectorOptions } from '../overlay/community-detector.js';\nimport {\n CtxoConfigSchema,\n DEFAULT_COCHANGE_MAX_COMMITS,\n DEFAULT_COCHANGE_MAX_FILES_PER_COMMIT,\n DEFAULT_COCHANGE_MIN_SHARED,\n DEFAULT_CONFIG,\n DEFAULT_TECHNICAL_TERMS,\n DEFAULT_WATCH_SNAPSHOT_MIN_FILE_CHANGES,\n type CtxoConfig,\n} from './config-schema.js';\n\nconst log = createLogger('ctxo:config');\n\nexport interface LoadedConfig {\n readonly config: CtxoConfig;\n readonly errors: readonly string[];\n readonly invalidGlobs: readonly string[];\n readonly path: string;\n readonly exists: boolean;\n}\n\nfunction isValidGlob(pattern: string): boolean {\n try {\n picomatch(pattern);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction validateGlobs(patterns: string[] | undefined, field: string, invalid: string[]): void {\n if (!patterns) return;\n for (const p of patterns) {\n if (!isValidGlob(p)) invalid.push(`${field}: ${p}`);\n }\n}\n\nexport function loadConfig(ctxoRoot: string): LoadedConfig {\n const path = join(ctxoRoot, 'config.yaml');\n const errors: string[] = [];\n const invalidGlobs: string[] = [];\n\n if (!existsSync(path)) {\n return { config: DEFAULT_CONFIG, errors, invalidGlobs, path, exists: false };\n }\n\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n errors.push(`Cannot read ${path}: ${(err as Error).message}`);\n return { config: DEFAULT_CONFIG, errors, invalidGlobs, path, exists: true };\n }\n\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (err) {\n errors.push(`Invalid YAML in ${path}: ${(err as Error).message}`);\n log.warn(`Invalid YAML in config.yaml, falling back to defaults: ${(err as Error).message}`);\n return { config: DEFAULT_CONFIG, errors, invalidGlobs, path, exists: true };\n }\n\n if (parsed === null || parsed === undefined) {\n return { config: DEFAULT_CONFIG, errors, invalidGlobs, path, exists: true };\n }\n\n const result = CtxoConfigSchema.safeParse(parsed);\n if (!result.success) {\n for (const issue of result.error.issues) {\n errors.push(`${issue.path.join('.') || '<root>'}: ${issue.message}`);\n }\n log.warn(`Invalid config.yaml (${errors.length} issue(s)), falling back to defaults`);\n return { config: DEFAULT_CONFIG, errors, invalidGlobs, path, exists: true };\n }\n\n validateGlobs(result.data.index?.ignore, 'index.ignore', invalidGlobs);\n validateGlobs(result.data.index?.ignoreProjects, 'index.ignoreProjects', invalidGlobs);\n validateGlobs(result.data.index?.tests, 'index.tests', invalidGlobs);\n validateGlobs(result.data.cochange?.include, 'cochange.include', invalidGlobs);\n validateGlobs(result.data.cochange?.exclude, 'cochange.exclude', invalidGlobs);\n validateGlobs(result.data.contextsView?.exclude, 'contextsView.exclude', invalidGlobs);\n validateGlobs(result.data.contextsView?.hubs?.paths, 'contextsView.hubs.paths', invalidGlobs);\n for (const pattern of invalidGlobs) {\n log.warn(`Invalid glob pattern in config.yaml — ${pattern}`);\n }\n\n return { config: result.data, errors, invalidGlobs, path, exists: true };\n}\n\nexport function statsEnabled(config: CtxoConfig): boolean {\n return config.stats?.enabled !== false;\n}\n\nexport function indexIgnorePatterns(config: CtxoConfig): string[] {\n return (config.index?.ignore ?? []).filter(isValidGlob);\n}\n\nexport function indexIgnoreProjectPatterns(config: CtxoConfig): string[] {\n return (config.index?.ignoreProjects ?? []).filter(isValidGlob);\n}\n\n/** True when facts extraction is requested (`index.facts: true`). */\nexport function factsRequested(config: CtxoConfig): boolean {\n return config.index?.facts === true;\n}\n\nexport function indexTestPatterns(config: CtxoConfig): string[] {\n return (config.index?.tests ?? []).filter(isValidGlob);\n}\n\n/**\n * The `communities` section as detector options. Only usable values are passed on, so the detector\n * keeps its own defaults (1.0, 3, 25) for anything absent or invalid.\n */\nexport function communityDetectorOptions(config: CtxoConfig): CommunityDetectorOptions {\n const section = config.communities;\n const resolution = section?.resolution;\n const minBridged = section?.godNodeMinBridged;\n const maxResults = section?.godNodeMaxResults;\n return {\n ...(typeof resolution === 'number' && Number.isFinite(resolution) && resolution > 0 ? { resolution } : {}),\n ...(typeof minBridged === 'number' && Number.isInteger(minBridged) && minBridged >= 1\n ? { godNodeMinBridged: minBridged }\n : {}),\n ...(typeof maxResults === 'number' && Number.isInteger(maxResults) && maxResults >= 0\n ? { godNodeMaxResults: maxResults }\n : {}),\n };\n}\n\nexport function maskingClusterLabelPatterns(config: CtxoConfig): string[] {\n return (config.masking?.clusterLabels ?? []).filter(isValidGlob);\n}\n\nexport function watchSnapshotMinFileChanges(config: CtxoConfig): number {\n const value = config.watch?.snapshotMinFileChanges;\n if (typeof value === 'number' && Number.isFinite(value) && value >= 1) return value;\n return DEFAULT_WATCH_SNAPSHOT_MIN_FILE_CHANGES;\n}\n\n/** The co-change settings with every default applied; what `buildCoChangeMatrix` and its callers use. */\nexport interface ResolvedCoChangeConfig {\n readonly maxCommits: number | 'all';\n readonly since?: string;\n readonly maxFilesPerCommit: number;\n readonly minShared: number;\n readonly include: readonly string[];\n readonly exclude: readonly string[];\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 1;\n}\n\nfunction positiveIntegerOr(value: unknown, fallback: number): number {\n return isPositiveInteger(value) ? value : fallback;\n}\n\nfunction validMaxCommits(value: unknown): number | 'all' | undefined {\n if (value === 'all') return 'all';\n return isPositiveInteger(value) ? value : undefined;\n}\n\nfunction validSince(value: unknown): string | undefined {\n return typeof value === 'string' && parseCoChangeSince(value) !== undefined ? value : undefined;\n}\n\n/**\n * Resolves the `cochange` section. Each setting comes from the first source that has a usable value:\n * the CLI override (`--cochange-commits`, `--cochange-since`), then `.ctxo/config.yaml`, then the\n * built-in default (2000 commits, 30 files per commit, 2 shared commits, no `since`).\n *\n * A value the schema would reject is skipped rather than trusted, and so is an invalid glob. That\n * matters for a value that did not come through `loadConfig`, and above all for `maxCommits`, where\n * a zero or negative number would otherwise turn into an unbounded window.\n */\nexport function resolveCoChangeConfig(\n config: CtxoConfig,\n overrides: { maxCommits?: number | 'all'; since?: string } = {},\n): ResolvedCoChangeConfig {\n const section = config.cochange;\n const since = validSince(overrides.since) ?? validSince(section?.since);\n return {\n maxCommits:\n validMaxCommits(overrides.maxCommits) ??\n validMaxCommits(section?.maxCommits) ??\n DEFAULT_COCHANGE_MAX_COMMITS,\n ...(since !== undefined ? { since } : {}),\n maxFilesPerCommit: positiveIntegerOr(section?.maxFilesPerCommit, DEFAULT_COCHANGE_MAX_FILES_PER_COMMIT),\n minShared: positiveIntegerOr(section?.minShared, DEFAULT_COCHANGE_MIN_SHARED),\n include: (section?.include ?? []).filter(isValidGlob),\n exclude: (section?.exclude ?? []).filter(isValidGlob),\n };\n}\n\n/** The `contextsView` section with every default applied; what the file graph and clustering use. */\nexport interface ResolvedContextsViewConfig {\n readonly weights: { readonly dependency: number; readonly coChange: number; readonly data: number };\n readonly exclude: readonly string[];\n readonly hubs: {\n readonly auto: boolean;\n readonly topPercent: number;\n readonly minDependents: number;\n readonly paths: readonly string[];\n };\n readonly resolution: number;\n readonly minCandidateFiles: number;\n readonly technicalTerms: readonly string[];\n}\n\nfunction nonNegativeOr(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n}\n\nfunction positiveOr(value: unknown, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n}\n\n/**\n * Resolves the `contextsView` section (defaults: weights 1, 1 and data 0, hubs auto with top 1% and at least\n * 10 dependents, resolution 1, minCandidateFiles 3). Unusable values and invalid globs are skipped.\n */\nexport function resolveContextsViewConfig(config: CtxoConfig): ResolvedContextsViewConfig {\n const section = config.contextsView;\n const hubs = section?.hubs;\n const topPercent = hubs?.topPercent;\n return {\n weights: {\n dependency: nonNegativeOr(section?.weights?.dependency, 1),\n coChange: nonNegativeOr(section?.weights?.coChange, 1),\n data: nonNegativeOr(section?.weights?.data, 0),\n },\n exclude: (section?.exclude ?? []).filter(isValidGlob),\n hubs: {\n auto: hubs?.auto !== false,\n topPercent: typeof topPercent === 'number' && topPercent > 0 && topPercent <= 100 ? topPercent : 1,\n minDependents: positiveIntegerOr(hubs?.minDependents, 10),\n paths: (hubs?.paths ?? []).filter(isValidGlob),\n },\n resolution: positiveOr(section?.resolution, 1),\n minCandidateFiles: positiveIntegerOr(section?.minCandidateFiles, 3),\n technicalTerms: section?.technicalTerms ? [...section.technicalTerms] : [...DEFAULT_TECHNICAL_TERMS],\n };\n}\n\n/**\n * Build a single predicate that returns true when the given path matches any\n * of the supplied glob patterns. Paths are normalised to forward slashes.\n */\nexport function makeGlobMatcher(patterns: string[]): (path: string) => boolean {\n if (patterns.length === 0) return () => false;\n const matchers = patterns.map((p) => picomatch(p, { dot: true }));\n return (input: string) => {\n const normalised = input.replace(/\\\\/g, '/');\n return matchers.some((m) => m(normalised));\n };\n}\n","import { z } from 'zod';\nimport { parseCoChangeSince } from '../co-change/co-change-window.js';\n\n/**\n * Zod schema for `.ctxo/config.yaml`.\n *\n * Unknown fields on the root are ignored (forward compatibility) but unknown\n * fields under recognised sections (`stats`, `index`) are rejected so typos\n * surface immediately via `ctxo doctor`.\n */\nexport const StatsConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n })\n .strict();\n\nexport const IndexConfigSchema = z\n .object({\n ignore: z.array(z.string().min(1)).optional(),\n ignoreProjects: z.array(z.string().min(1)).optional(),\n facts: z.boolean().optional(),\n tests: z.array(z.string().min(1)).optional(),\n })\n .strict();\n\nexport const MaskingConfigSchema = z\n .object({\n /**\n * Glob patterns matched against community cluster labels. Matching labels\n * are replaced with `[masked-cluster-N]` in MCP tool responses so\n * org-sensitive folder names (e.g. `src/payment-vault`, `internal-*`)\n * never surface to AI agents over the stdio channel.\n */\n clusterLabels: z.array(z.string().min(1)).optional(),\n })\n .strict();\n\nexport const WatchConfigSchema = z\n .object({\n /**\n * Minimum number of files that must be re-indexed since the last\n * community snapshot before `ctxo watch` recomputes one. Default: 5.\n * On large monorepos Louvain + PageRank cost ~1 s of blocking CPU;\n * bumping this (e.g. 25) avoids recomputing on every save.\n */\n snapshotMinFileChanges: z.number().int().min(1).optional(),\n })\n .strict();\n\nexport const GateConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n sensitivity: z.enum(['strict', 'balanced', 'lenient']).optional(),\n /** Absolute floor: minimum confirmed+likely dependents before the gate can fire. */\n minDependents: z.number().int().min(0).optional(),\n /** Repo-relative significance: symbol must be in the top `percentile`% by PageRank. */\n percentile: z.number().int().min(1).max(100).optional(),\n })\n .strict();\n\nexport const CoChangeConfigSchema = z\n .object({\n /**\n * Size of the co-change window: the last N non-merge commits from HEAD, or \"all\".\n * Default: 2000. Independent of `ctxo index --max-history`, which only sets how much\n * per-file commit history is kept.\n */\n maxCommits: z\n .union([z.number().int().min(1), z.literal('all')], { error: 'must be a positive integer or \"all\"' })\n .optional(),\n /**\n * Narrows the window to a duration counted back from HEAD's committer date (never the wall\n * clock): `30d`, `2w`, `12m` or `1y`. Applies together with `maxCommits`.\n */\n since: z\n .string()\n .refine((value) => parseCoChangeSince(value) !== undefined, {\n error: 'must be a duration such as 30d, 2w, 12m or 1y',\n })\n .optional(),\n /** Commits touching more analysed files than this are skipped as mass changes. Default: 30. */\n maxFilesPerCommit: z.number().int().min(1).optional(),\n /** A file pair becomes an entry only after this many shared commits. Default: 2. */\n minShared: z.number().int().min(1).optional(),\n /** Extra tracked files to analyse that no language plugin indexes, such as SQL scripts (globs). */\n include: z.array(z.string().min(1)).optional(),\n /** Indexed files to leave out of the analysis (globs). */\n exclude: z.array(z.string().min(1)).optional(),\n })\n .strict();\n\nexport const CommunitiesConfigSchema = z\n .object({\n /** Louvain resolution: higher gives more, smaller communities. Default: 1.0. */\n resolution: z.number().positive().optional(),\n /** Minimum number of communities a symbol must bridge to count as a god node. Default: 3. */\n godNodeMinBridged: z.number().int().min(1).optional(),\n /** Maximum number of god nodes reported. Default: 25. */\n godNodeMaxResults: z.number().int().min(0).optional(),\n })\n .strict();\n\nexport const ContextsViewConfigSchema = z\n .object({\n /** Blend of the file-edge signals. Defaults: dependency 1.0, coChange 1.0, data 0 (shared tables are a finding, not a clustering weight). */\n weights: z\n .object({\n dependency: z.number().min(0).optional(),\n coChange: z.number().min(0).optional(),\n data: z.number().min(0).optional(),\n })\n .strict()\n .optional(),\n /** Files to leave out of the file graph (globs). */\n exclude: z.array(z.string().min(1)).optional(),\n hubs: z\n .object({\n /** Detect hubs automatically (fan-in and god nodes). Default: true. */\n auto: z.boolean().optional(),\n /** A file is a fan-in hub when it is in the top `topPercent`% by dependents. Default: 1. */\n topPercent: z.number().positive().max(100).optional(),\n /** Minimum distinct dependent files for a fan-in hub. Default: 10. */\n minDependents: z.number().int().min(1).optional(),\n /** Files that lose all their edges (globs). */\n paths: z.array(z.string().min(1)).optional(),\n })\n .strict()\n .optional(),\n /** Louvain resolution for the file graph. Default: 1.0. */\n resolution: z.number().positive().optional(),\n /** Candidates with fewer files fold into a neighbour. Default: 3. */\n minCandidateFiles: z.number().int().min(1).optional(),\n /** Words dropped from candidate terms; replaces the default list. */\n technicalTerms: z.array(z.string().min(1)).optional(),\n })\n .strict();\n\nexport const CtxoConfigSchema = z\n .object({\n version: z.union([z.string(), z.number()]).optional(),\n stats: StatsConfigSchema.optional(),\n index: IndexConfigSchema.optional(),\n masking: MaskingConfigSchema.optional(),\n watch: WatchConfigSchema.optional(),\n gate: GateConfigSchema.optional(),\n cochange: CoChangeConfigSchema.optional(),\n communities: CommunitiesConfigSchema.optional(),\n contextsView: ContextsViewConfigSchema.optional(),\n })\n .passthrough();\n\nexport type StatsConfig = z.infer<typeof StatsConfigSchema>;\nexport type IndexConfig = z.infer<typeof IndexConfigSchema>;\nexport type MaskingConfig = z.infer<typeof MaskingConfigSchema>;\nexport type WatchConfig = z.infer<typeof WatchConfigSchema>;\nexport type GateConfig = z.infer<typeof GateConfigSchema>;\nexport type CoChangeConfig = z.infer<typeof CoChangeConfigSchema>;\nexport type CommunitiesConfig = z.infer<typeof CommunitiesConfigSchema>;\nexport type ContextsViewConfig = z.infer<typeof ContextsViewConfigSchema>;\nexport type CtxoConfig = z.infer<typeof CtxoConfigSchema>;\n\nexport const DEFAULT_WATCH_SNAPSHOT_MIN_FILE_CHANGES = 5;\nexport const DEFAULT_COCHANGE_MAX_COMMITS = 2000;\nexport const DEFAULT_COCHANGE_MAX_FILES_PER_COMMIT = 30;\nexport const DEFAULT_COCHANGE_MIN_SHARED = 2;\nexport const DEFAULT_TECHNICAL_TERMS: readonly string[] = [\n 'Service',\n 'Repository',\n 'Controller',\n 'Handler',\n 'Manager',\n 'Dto',\n 'Impl',\n 'Async',\n];\n\nexport const DEFAULT_CONFIG: CtxoConfig = {\n version: '1.0',\n stats: { enabled: true },\n index: { ignore: [], ignoreProjects: [] },\n masking: { clusterLabels: [] },\n watch: { snapshotMinFileChanges: DEFAULT_WATCH_SNAPSHOT_MIN_FILE_CHANGES },\n gate: { enabled: true, sensitivity: 'balanced' },\n cochange: {\n maxCommits: DEFAULT_COCHANGE_MAX_COMMITS,\n maxFilesPerCommit: DEFAULT_COCHANGE_MAX_FILES_PER_COMMIT,\n minShared: DEFAULT_COCHANGE_MIN_SHARED,\n include: [],\n exclude: [],\n },\n};\n","import { makeGlobMatcher } from '../config/load-config.js';\nimport type { FileIndex } from '../types.js';\n\n/** Globs that mark a repo-relative path as a test file, in spec 7.1 order. */\nexport const DEFAULT_TEST_GLOBS: readonly string[] = [\n '**/__tests__/**',\n '**/*.test.*',\n '**/*.spec.*',\n '**/test/**',\n '**/tests/**',\n '**/*_test.go',\n '**/src/test/**',\n '**/*.Tests/**',\n '**/*.Test/**',\n '**/*Tests.cs',\n '**/*Test.cs',\n '**/test_*.py',\n '**/*_test.py',\n];\n\n/**\n * The default globs that decide by file name alone. With annotation evidence (see `annotationAware`) a path\n * matched only by one of these is a test file only when a test-framework annotation marks it.\n */\nexport const NAME_ONLY_TEST_GLOBS: readonly string[] = ['**/*Tests.cs', '**/*Test.cs'];\n\n/**\n * Short names of the attributes that mark a test, as the C# plugin reports them (`Fact`, not\n * `Xunit.FactAttribute`): xUnit, NUnit and MSTest. Matching is exact and case-sensitive.\n */\nexport const TEST_FRAMEWORK_ANNOTATIONS: readonly string[] = [\n 'Fact',\n 'Theory',\n 'SkippableFact',\n 'SkippableTheory',\n 'Test',\n 'TestCase',\n 'TestCaseSource',\n 'TestFixture',\n 'TestMethod',\n 'DataTestMethod',\n 'TestClass',\n];\n\n/** Takes a repo-relative path with either separator. */\nexport type TestFileClassifier = (path: string) => boolean;\n\nexport interface TestFileClassifierOptions {\n /**\n * True when annotation evidence exists (the plugin that reports annotations ran). A path matched only by\n * a name-only glob is then a test file only if `annotatedFiles` holds it. Default false: name globs decide.\n */\n readonly annotationAware?: boolean;\n}\n\nfunction normalisePath(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\n/** The files (forward-slash paths) that hold a symbol carrying a test-framework annotation. */\nexport function filesWithTestAnnotations(indices: readonly Pick<FileIndex, 'file' | 'symbols'>[]): Set<string> {\n const names = new Set(TEST_FRAMEWORK_ANNOTATIONS);\n const files = new Set<string>();\n for (const index of indices) {\n const marked = index.symbols.some((symbol) => symbol.annotations?.some((annotation) => names.has(annotation.name)));\n if (marked) files.add(normalisePath(index.file));\n }\n return files;\n}\n\n/**\n * Whether any `.cs` index entry carries an annotation on any symbol. That is the evidence that the v2 C#\n * plugin ran, so a `.cs` file without a test annotation really has none: pass it as `annotationAware`. The\n * contexts view and the module host's test-file check both decide this way.\n */\nexport function hasCSharpAnnotations(indices: readonly FileIndex[]): boolean {\n return indices.some(\n (index) =>\n index.file.endsWith('.cs') && index.symbols.some((symbol) => symbol.annotations && symbol.annotations.length > 0),\n );\n}\n\n/**\n * Classifies test files by the default globs plus any extra (`index.tests`) globs. A path in `annotatedFiles`\n * is always a test file. With `options.annotationAware`, the name-only globs no longer decide on their own.\n */\nexport function createTestFileClassifier(\n extraGlobs: readonly string[] = [],\n annotatedFiles?: ReadonlySet<string>,\n options: TestFileClassifierOptions = {},\n): TestFileClassifier {\n const nameOnly = new Set(NAME_ONLY_TEST_GLOBS);\n const defaults = options.annotationAware === true\n ? DEFAULT_TEST_GLOBS.filter((glob) => !nameOnly.has(glob))\n : DEFAULT_TEST_GLOBS;\n const matchesGlob = makeGlobMatcher([...defaults, ...extraGlobs]);\n return (path) => annotatedFiles?.has(normalisePath(path)) === true || matchesGlob(path);\n}\n","import { spawnSync } from 'node:child_process';\nimport { simpleGit, type SimpleGit } from 'simple-git';\nimport type { IGitPort } from '../../ports/i-git-port.js';\nimport type {\n CommitRecord,\n ChurnData,\n ChangeLog,\n CommitChangeSet,\n FileChange,\n FileChangeStatus,\n GitRange,\n GitRefs,\n RangeCommit,\n} from '../../core/types.js';\nimport { compareCodepoint } from '../../core/compare-codepoint.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:git');\n\nconst NUL = '\\x00';\nconst RECORD_SEPARATOR = '\\x1e';\nconst FIELD_SEPARATOR = '\\x1f';\n\n/**\n * One record per commit: a record separator, then hash, author date (ISO), committer unix\n * time, author name and subject, joined by field separators.\n */\nconst CHANGE_LOG_FORMAT = '--format=%x1e%H%x1f%aI%x1f%ct%x1f%an%x1f%s';\n\n/**\n * Config overrides that keep git's output parseable whatever the user has configured.\n * - core.quotepath=off: paths come out verbatim instead of quoted and octal-escaped.\n * - log.showSignature=false: when on, gpg verification text (CRLF on Windows) is written\n * into the log stream ahead of each commit.\n */\nconst STABLE_OUTPUT_CONFIG: readonly string[] = [\n '-c', 'core.quotepath=off',\n '-c', 'log.showSignature=false',\n];\n\nfunction toForwardSlashes(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\nfunction isFileChangeStatus(value: string): value is FileChangeStatus {\n return value === 'A' || value === 'M' || value === 'D' || value === 'R' || value === 'C' || value === 'T';\n}\n\n/**\n * Parses the NUL-separated tokens of `--name-status -z`: `<status> <path>`, or for renames and\n * copies `<status><score> <old path> <new path>` (for example `R100`). Only status tokens are\n * trimmed, because git puts a newline between a commit header and its first status; paths are\n * never altered beyond separator normalisation. Statuses outside FileChangeStatus (for example\n * U) are skipped, and a truncated trailing entry is dropped.\n */\nfunction parseNameStatus(tokens: readonly string[]): FileChange[] {\n const changes: FileChange[] = [];\n let i = 0;\n while (i < tokens.length) {\n const kind = (tokens[i] ?? '').trim().charAt(0);\n if (kind === '') {\n i += 1; // terminator after the last path, or a bare newline\n } else if (kind === 'R' || kind === 'C') {\n const oldPath = tokens[i + 1];\n const path = tokens[i + 2];\n i += 3;\n if (oldPath !== undefined && path !== undefined) {\n changes.push({ status: kind, path: toForwardSlashes(path), oldPath: toForwardSlashes(oldPath) });\n }\n } else {\n const path = tokens[i + 1];\n i += 2;\n if (path !== undefined && isFileChangeStatus(kind)) {\n changes.push({ status: kind, path: toForwardSlashes(path) });\n }\n }\n }\n return changes;\n}\n\n/**\n * Parses `git ls-files -t -z`: records of `<tag> <path>`, where the tag is H for an ordinary tracked file\n * and S for a skip-worktree one (others mark unmerged, removed, modified or untracked paths). Returns the\n * paths tagged S.\n */\nfunction parseSkipWorktree(raw: string): Set<string> {\n const paths = new Set<string>();\n for (const record of raw.split(NUL)) {\n if (record.length > 2 && record.charAt(0) === 'S' && record.charAt(1) === ' ') {\n paths.add(toForwardSlashes(record.slice(2)));\n }\n }\n return paths;\n}\n\n/** Parses `git log` output produced with CHANGE_LOG_FORMAT, `--name-status` and `-z`. */\nfunction parseChangeLog(raw: string): CommitChangeSet[] {\n const commits: CommitChangeSet[] = [];\n for (const record of raw.split(RECORD_SEPARATOR)) {\n // \"<header>\\0\\n<status>\\0<path>\\0...\": the header ends at the first NUL. Text before the\n // first separator, or a record without a complete header, is skipped.\n const [header = '', ...tokens] = record.split(NUL);\n const [hash, date, time, author, ...subject] = header.split(FIELD_SEPARATOR);\n const committedAt = Number.parseInt(time ?? '', 10);\n if (!hash || date === undefined || author === undefined || Number.isNaN(committedAt)) continue;\n\n commits.push({\n hash,\n message: subject.join(FIELD_SEPARATOR).trim(),\n date,\n author,\n committedAt,\n changes: parseNameStatus(tokens),\n });\n }\n return commits;\n}\n\n/** One record per commit: hash, parents, subject and the requested trailer values, then the name-status tokens. */\nfunction rangeLogFormat(trailerKey: string | undefined): string {\n const trailers = trailerKey === undefined ? '' : `%(trailers:key=${trailerKey},valueonly)`;\n return `--format=%x1e%H%x1f%P%x1f%s%x1f${trailers}%x1f`;\n}\n\n/** Parses `git log` output produced with rangeLogFormat, `--name-status` and `-z`. */\nfunction parseRangeLog(raw: string): RangeCommit[] {\n const commits: RangeCommit[] = [];\n for (const record of raw.split(RECORD_SEPARATOR)) {\n const [header = '', ...tokens] = record.split(NUL);\n const [hash, parents, subject, trailerText] = header.split(FIELD_SEPARATOR);\n if (!hash || parents === undefined || subject === undefined) continue;\n\n const isMerge = parents.trim().split(' ').filter((p) => p.length > 0).length > 1;\n commits.push({\n hash,\n subject,\n isMerge,\n trailers: (trailerText ?? '').split('\\n').map((t) => t.trim()).filter((t) => t.length > 0),\n changes: isMerge ? [] : parseNameStatus(tokens),\n });\n }\n return commits;\n}\n\n/**\n * Parses `git status --porcelain=v1 -z`: records of `XY <path>`, where a rename or copy in either column\n * is followed by its source path as a separate token.\n */\nfunction parseStatus(raw: string): FileChange[] {\n const tokens = raw.split(NUL);\n const changes: FileChange[] = [];\n for (let i = 0; i < tokens.length; i++) {\n const entry = tokens[i] ?? '';\n if (entry.length < 4) continue;\n const x = entry.charAt(0);\n const y = entry.charAt(1);\n const path = toForwardSlashes(entry.slice(3));\n if (x === 'R' || x === 'C' || y === 'R' || y === 'C') {\n const oldPath = tokens[++i];\n if (oldPath === undefined) continue;\n const status: FileChangeStatus = x === 'R' || y === 'R' ? 'R' : 'C';\n changes.push({ status, path, oldPath: toForwardSlashes(oldPath) });\n } else if (x === '?') {\n changes.push({ status: 'A', path });\n } else if (x === 'D' || y === 'D') {\n changes.push({ status: 'D', path });\n } else if (x === 'A' || y === 'A') {\n changes.push({ status: 'A', path });\n } else {\n changes.push({ status: 'M', path });\n }\n }\n return changes;\n}\n\n/** Short ref names in `for-each-ref --format=%(refname)` output, local and remote-tracking apart (codepoint order). */\nfunction parseBranchNames(raw: string): { local: string[]; remote: string[]; all: string[] } {\n const local = new Set<string>();\n const remote = new Set<string>();\n for (const line of raw.split('\\n')) {\n const ref = line.trim();\n if (ref.startsWith('refs/heads/')) {\n local.add(ref.slice('refs/heads/'.length));\n } else if (ref.startsWith('refs/remotes/') && !/^refs\\/remotes\\/[^/]+\\/HEAD$/.test(ref)) {\n remote.add(ref.slice('refs/remotes/'.length));\n }\n }\n const sorted = (set: Set<string>): string[] => [...set].sort(compareCodepoint);\n return { local: sorted(local), remote: sorted(remote), all: sorted(new Set([...local, ...remote])) };\n}\n\nconst TRAILER_KEY = /^[A-Za-z0-9][A-Za-z0-9-]*$/;\n\nexport class SimpleGitAdapter implements IGitPort {\n private readonly git: SimpleGit;\n private readonly projectRoot: string;\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot;\n this.git = simpleGit(projectRoot);\n log.debug('Initialized with root: %s', projectRoot);\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n await this.git.version();\n return true;\n } catch {\n return false;\n }\n }\n\n async getCommitHistory(filePath: string, maxCount?: number): Promise<CommitRecord[]> {\n try {\n const log = await this.git.log({\n file: filePath,\n '--follow': null,\n ...(maxCount ? { maxCount } : {}),\n });\n\n return log.all.map((entry) => ({\n hash: entry.hash,\n message: entry.message,\n date: entry.date,\n author: entry.author_name,\n }));\n } catch (err) {\n log.error(` Failed to get history for ${filePath}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getChangedFiles(since: string): Promise<string[]> {\n try {\n const diff = await this.git.diffSummary([since]);\n return diff.files.map((f) => f.file.replace(/\\\\/g, '/'));\n } catch (err) {\n log.error(` Failed to get changed files since ${since}: ${(err as Error).message}`);\n return [];\n }\n }\n\n async getFileChurn(filePath: string): Promise<ChurnData> {\n try {\n const log = await this.git.log({ file: filePath, '--follow': null });\n\n return {\n filePath,\n commitCount: log.total,\n };\n } catch (err) {\n log.error(` Failed to get churn for ${filePath}: ${(err as Error).message}`);\n return { filePath, commitCount: 0 };\n }\n }\n\n async getHeadSha(): Promise<string | undefined> {\n try {\n const raw = await this.git.revparse(['--short', 'HEAD']);\n const sha = raw.trim();\n return sha.length > 0 ? sha : undefined;\n } catch (err) {\n log.error(`Failed to resolve HEAD: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async countCommitsBetween(base: string): Promise<number | undefined> {\n try {\n const raw = await this.git.raw(['rev-list', '--count', `${base}..HEAD`]);\n const n = parseInt(raw.trim(), 10);\n return Number.isFinite(n) && n >= 0 ? n : undefined;\n } catch (err) {\n log.error(`Failed to count commits since ${base}: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async getChangeLog(maxCommits: number | 'all'): Promise<ChangeLog | undefined> {\n try {\n // HEAD gets its own call because the log below skips merge commits and HEAD may be one.\n // It is read first on purpose: if a commit lands between the two calls, `head` can only\n // end up older than the log (it looks stale), never newer (it would look current while\n // missing a commit).\n const headRaw = await this.git.raw([...STABLE_OUTPUT_CONFIG, 'log', '-1', '--format=%h%x1f%ct', 'HEAD']);\n const [head, headTime] = headRaw.trim().split(FIELD_SEPARATOR);\n const headCommittedAt = Number.parseInt(headTime ?? '', 10);\n if (!head || Number.isNaN(headCommittedAt)) {\n throw new Error(`unexpected HEAD description ${JSON.stringify(headRaw)}`);\n }\n\n const args = [...STABLE_OUTPUT_CONFIG, 'log', '--no-merges', '-M', '--name-status', '-z', CHANGE_LOG_FORMAT];\n if (maxCommits !== 'all') args.push(`--max-count=${maxCommits}`);\n args.push('HEAD');\n\n return { head, headCommittedAt, commits: parseChangeLog(await this.git.raw(args)) };\n } catch (err) {\n log.error(`Failed to read change log: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async getSkipWorktreePaths(): Promise<ReadonlySet<string> | undefined> {\n try {\n // One listing of the index with status tags; -z keeps unusual names verbatim.\n return parseSkipWorktree(await this.git.raw([...STABLE_OUTPUT_CONFIG, 'ls-files', '-t', '-z']));\n } catch (err) {\n log.error(`Failed to list skip-worktree paths: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async getIgnoredPaths(paths: readonly string[]): Promise<ReadonlySet<string> | undefined> {\n if (paths.length === 0) return new Set();\n\n // The paths go in on stdin, NUL separated, so a long list cannot overflow the command line and unusual\n // names arrive verbatim. Exit code 0: some of them are ignored; 1: none is; anything else is a failure.\n const result = spawnSync('git', [...STABLE_OUTPUT_CONFIG, 'check-ignore', '-z', '--stdin'], {\n cwd: this.projectRoot,\n input: paths.map((path) => `${path}${NUL}`).join(''),\n encoding: 'utf-8',\n maxBuffer: 64 * 1024 * 1024,\n });\n if (result.error === undefined && result.status === 1) return new Set();\n if (result.error !== undefined || result.status !== 0) {\n const reason = (result.error?.message ?? result.stderr ?? '').replace(/\\s+/g, ' ').trim();\n log.error(`Failed to check ignored paths: ${reason}`);\n return undefined;\n }\n return new Set(\n result.stdout\n .split(NUL)\n .filter((path) => path.length > 0)\n .map(toForwardSlashes),\n );\n }\n\n async resolveCommit(ref: string): Promise<string | undefined> {\n if (this.refuseOptionLike('resolve', [ref])) return undefined;\n try {\n const sha = (await this.git.raw([...STABLE_OUTPUT_CONFIG, 'rev-parse', '--verify', '--quiet', `${ref}^{commit}`])).trim();\n return sha.length > 0 ? sha : undefined;\n } catch (err) {\n log.debug(`Cannot resolve ${ref}: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async mergeBase(a: string, b: string): Promise<string | undefined> {\n if (this.refuseOptionLike('merge-base', [a, b])) return undefined;\n try {\n const sha = (await this.git.raw([...STABLE_OUTPUT_CONFIG, 'merge-base', a, b])).trim();\n return sha.length > 0 ? sha : undefined;\n } catch (err) {\n log.debug(`No merge base for ${a} and ${b}: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async isAncestor(ancestor: string, descendant: string): Promise<boolean | undefined> {\n if (this.refuseOptionLike('is-ancestor', [ancestor, descendant])) return undefined;\n // Exit code 0: an ancestor; 1: not; anything else is a failure.\n const result = spawnSync('git', [...STABLE_OUTPUT_CONFIG, 'merge-base', '--is-ancestor', ancestor, descendant], {\n cwd: this.projectRoot,\n encoding: 'utf-8',\n });\n if (result.error === undefined && result.status === 0) return true;\n if (result.error === undefined && result.status === 1) return false;\n log.debug(`Cannot tell whether ${ancestor} is an ancestor of ${descendant}`);\n return undefined;\n }\n\n async getRangeLog(\n range: GitRange,\n options?: { readonly trailerKey?: string },\n ): Promise<RangeCommit[] | undefined> {\n if (this.refuseOptionLike('range log', [range.head, ...range.exclude])) return undefined;\n const trailerKey = options?.trailerKey;\n if (trailerKey !== undefined && !TRAILER_KEY.test(trailerKey)) {\n log.error(`Refusing to read trailers for invalid key ${JSON.stringify(trailerKey)}`);\n return undefined;\n }\n\n try {\n const raw = await this.git.raw([\n ...STABLE_OUTPUT_CONFIG,\n 'log', '-M', '--name-status', '-z', rangeLogFormat(trailerKey),\n range.head, ...range.exclude.map((ref) => `^${ref}`), '--',\n ]);\n return parseRangeLog(raw);\n } catch (err) {\n log.error(`Failed to read range log of ${range.head}: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async getWorkingTreeChanges(scope: 'all' | 'staged'): Promise<FileChange[] | undefined> {\n try {\n if (scope === 'staged') {\n const raw = await this.git.raw([...STABLE_OUTPUT_CONFIG, 'diff', '--cached', '-M', '--name-status', '-z']);\n return parseNameStatus(raw.split(NUL));\n }\n // status.renames=true: rename detection must not depend on the user's diff.renames setting.\n const raw = await this.git.raw([...STABLE_OUTPUT_CONFIG, '-c', 'status.renames=true', 'status','--porcelain=v1', '-z', '--untracked-files=all']);\n return parseStatus(raw);\n } catch (err) {\n log.error(`Failed to read ${scope} working-tree changes: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n async getRefs(): Promise<GitRefs | undefined> {\n let names: ReturnType<typeof parseBranchNames>;\n try {\n names = parseBranchNames(\n await this.git.raw([...STABLE_OUTPUT_CONFIG, 'for-each-ref', '--format=%(refname)', 'refs/heads', 'refs/remotes']),\n );\n } catch (err) {\n log.error(`Failed to list branches: ${(err as Error).message}`);\n return undefined;\n }\n\n const currentBranch = await this.tryRaw(['symbolic-ref', '--quiet', '--short', 'HEAD']);\n const upstream = await this.tryRaw(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);\n const originHead = await this.tryRaw(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD']);\n return {\n ...(currentBranch !== undefined ? { currentBranch } : {}),\n ...(upstream !== undefined ? { upstream } : {}),\n ...(originHead !== undefined ? { originHead } : {}),\n branches: names.all,\n localBranches: names.local,\n remoteBranches: names.remote,\n };\n }\n\n async listTrackedFiles(): Promise<string[] | undefined> {\n try {\n const raw = await this.git.raw([...STABLE_OUTPUT_CONFIG, 'ls-files', '-z']);\n return raw.split(NUL).filter((path) => path.length > 0).map(toForwardSlashes);\n } catch (err) {\n log.error(`Failed to list tracked files: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n /** Runs one git call whose failure only means \"no such fact\"; logged at debug, not error. */\n private async tryRaw(args: string[]): Promise<string | undefined> {\n try {\n const out = (await this.git.raw([...STABLE_OUTPUT_CONFIG, ...args])).trim();\n return out.length > 0 ? out : undefined;\n } catch (err) {\n log.debug(`git ${args[0]} unavailable: ${(err as Error).message}`);\n return undefined;\n }\n }\n\n /** A ref is never an option; true (and logged) when one would be parsed as such. */\n private refuseOptionLike(what: string, refs: readonly string[]): boolean {\n const bad = refs.find((ref) => ref.startsWith('-'));\n if (bad === undefined) return false;\n log.error(`Refusing ${what} for option-like ref ${bad}`);\n return true;\n }\n\n async getCommitFileChanges(rev: string): Promise<FileChange[] | undefined> {\n // A revision is never an option; refuse anything that git would parse as one.\n if (rev.startsWith('-')) {\n log.error(`Refusing to read file changes for option-like revision ${rev}`);\n return undefined;\n }\n\n try {\n const raw = await this.git.raw([\n ...STABLE_OUTPUT_CONFIG,\n 'diff-tree', '--root', '--no-commit-id', '-r', '-M', '--name-status', '-z', rev,\n ]);\n return parseNameStatus(raw.split(NUL));\n } catch (err) {\n log.error(`Failed to read file changes of ${rev}: ${(err as Error).message}`);\n return undefined;\n }\n }\n}\n","import { DATA_RESOURCE_KINDS, type DataResourceKind } from '@ctxo/plugin-api';\nimport type { EdgeKind } from '../types.js';\n\n/** Edge kinds that couple code to data resources (protocol v2). */\nexport const DATA_EDGE_KINDS: ReadonlySet<EdgeKind> = new Set<EdgeKind>(['reads', 'writes', 'executes']);\n\nconst PROCEDURE_KINDS: readonly string[] = ['procedure'];\n\nexport function isDataResourceKind(kind: string): kind is DataResourceKind {\n return (DATA_RESOURCE_KINDS as readonly string[]).includes(kind);\n}\n\n/**\n * The definition kinds a data reference of `kind` matches, or undefined when `kind` names no data object. A\n * table or view reference matches both, because a 2-part `::table` reference may name a view; a procedure\n * reference matches procedures only.\n */\nexport function dataReferenceKinds(kind: string): readonly string[] | undefined {\n if (isDataResourceKind(kind)) return DATA_RESOURCE_KINDS;\n if (kind === 'procedure') return PROCEDURE_KINDS;\n return undefined;\n}\n","import { compareCodepoint } from '../compare-codepoint.js';\nimport { dataReferenceKinds } from '../data-coupling/data-kinds.js';\nimport { createTestFileClassifier } from '../test-files/test-file-classifier.js';\nimport type { SymbolNode, GraphEdge } from '../types.js';\n\n/**\n * Test files by `DEFAULT_TEST_GLOBS` only. The graph has no config, so `index.tests` extras do not count\n * here: a copy in a test folder that only those extras name ranks as production.\n */\nconst isDefaultTestFile = createTestFileClassifier();\n\nconst DOCUMENTATION_FOLDERS: ReadonlySet<string> = new Set(['docs', 'doc', 'documentation']);\n\n/** ASCII A-Z lowercased, with no locale or Unicode folding. */\nfunction asciiLower(text: string): string {\n return text.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));\n}\n\n/** True when a folder of the path is `docs`, `doc` or `documentation`, compared after ASCII lowercasing. */\nfunction isUnderDocumentationFolder(file: string): boolean {\n const folders = file.replace(/\\\\/g, '/').split('/').slice(0, -1);\n return folders.some((folder) => DOCUMENTATION_FOLDERS.has(asciiLower(folder)));\n}\n\n/**\n * The rank of a data definition among several of the same name, smaller first: not in a test file comes\n * first, then not under a documentation folder. A definition is never dropped; the rank only orders them.\n */\nfunction definitionRank(symbolId: string): number {\n const at = symbolId.indexOf('::');\n const file = at < 0 ? symbolId : symbolId.slice(0, at);\n return (isDefaultTestFile(file) ? 2 : 0) + (isUnderDocumentationFolder(file) ? 1 : 0);\n}\n\n/** A table, view or procedure as one resource across every definition of its canonical name. */\nexport interface DataResourceIdentity {\n /** `<name>::<kind>` with the winner's kind, for example `dbo.loans::table`. */\n readonly canonicalId: string;\n /** The definition every data reference to the name resolves to (the `definitionRank` tie-break). */\n readonly winner: SymbolNode;\n /** Every definition of the name, once each, in tie-break order: the winner comes first. */\n readonly definitions: readonly SymbolNode[];\n /**\n * The definitions in neither a test file nor a documentation folder (the best `definitionRank`), in\n * tie-break order. Empty when every definition is a docs or test copy.\n */\n readonly production: readonly SymbolNode[];\n}\n\n/**\n * The symbol graph of an index.\n *\n * Add all nodes before any edge: `addEdge` resolves each endpoint against the nodes present at that moment,\n * and an endpoint it cannot resolve keeps its raw id for good.\n *\n * A table, view or procedure is one resource per canonical name, whatever the number of files that define\n * it (`dataResourceIdentity`). Every data reference resolves to the same definition, the winner, so only the\n * winner has incoming edges. The impact tools read dependents through `getDependentEdges`, which gives every\n * definition those edges, and forward edges stay on each definition: a procedure body in a later migration\n * keeps its own accesses.\n */\nexport class SymbolGraph {\n private readonly nodes = new Map<string, SymbolNode>();\n private readonly nodesByFileAndName = new Map<string, SymbolNode>();\n private readonly nodesByNameAndKind = new Map<string, SymbolNode[]>();\n private readonly forwardEdges = new Map<string, GraphEdge[]>();\n private readonly reverseEdges = new Map<string, GraphEdge[]>();\n private readonly definitionRanks = new Map<string, number>();\n private edgeSet = new Set<string>();\n\n addNode(node: SymbolNode): void {\n this.nodes.set(node.symbolId, node);\n // Index by file::name (ignoring kind) for fuzzy edge resolution\n const parts = node.symbolId.split('::');\n const [p0, p1] = parts;\n if (p0 !== undefined && p1 !== undefined) {\n this.nodesByFileAndName.set(`${p0}::${p1}`, node);\n }\n // Index by name::kind for name-reference targets (e.g. Java \"Bar::class\",\n // \"fixture.Bar::helper::method\") that carry name+kind but not the file path.\n if (parts.length === 3) {\n const nk = `${parts[1]}::${parts[2]}`;\n const list = this.nodesByNameAndKind.get(nk);\n if (list) list.push(node); else this.nodesByNameAndKind.set(nk, [node]);\n }\n }\n\n addEdge(edge: GraphEdge): void {\n // Resolve edge endpoints: if exact ID not found, try fuzzy match by file::name\n const resolvedFrom = this.resolveNodeId(edge.from);\n const resolvedTo = this.resolveNodeId(edge.to);\n const resolvedEdge: GraphEdge = { from: resolvedFrom, to: resolvedTo, kind: edge.kind };\n\n const edgeKey = `${resolvedEdge.from}|${resolvedEdge.to}|${resolvedEdge.kind}`;\n if (this.edgeSet.has(edgeKey)) return;\n this.edgeSet.add(edgeKey);\n\n const forward = this.forwardEdges.get(resolvedEdge.from) ?? [];\n forward.push(resolvedEdge);\n this.forwardEdges.set(resolvedEdge.from, forward);\n\n const reverse = this.reverseEdges.get(resolvedEdge.to) ?? [];\n reverse.push(resolvedEdge);\n this.reverseEdges.set(resolvedEdge.to, reverse);\n }\n\n getNode(symbolId: string): SymbolNode | undefined {\n return this.nodes.get(symbolId) ?? this.resolveNodeFuzzy(symbolId);\n }\n\n private resolveNodeId(id: string): string {\n if (this.nodes.has(id)) return id;\n const segs = id.split('::');\n const [p0, p1] = segs;\n if (p0 !== undefined && p1 !== undefined) {\n const fuzzyKey = `${p0}::${p1}`;\n const match = this.nodesByFileAndName.get(fuzzyKey);\n if (match) return match.symbolId;\n\n // Try .js → .ts extension swap (handles unresolved module specifiers)\n const jsToTs = p0.replace(/\\.js$/, '.ts');\n if (jsToTs !== p0) {\n const altKey = `${jsToTs}::${p1}`;\n const altMatch = this.nodesByFileAndName.get(altKey);\n if (altMatch) return altMatch.symbolId;\n }\n }\n\n // Final fallback: name-reference targets (e.g. Java \"Bar::class\", \"pkg.Bar::helper::method\" or SQL\n // \"dbo.loans::table\") carry name+kind but not the file path. A data reference resolves even when\n // several definitions share the name, by the tie-break in resolveDataReference; any other reference\n // resolves by name+kind ONLY when unambiguous (exactly one node matches).\n const dataMatch = this.resolveDataReference(segs);\n if (dataMatch) return dataMatch.symbolId;\n const nameKind = this.extractNameKind(segs);\n if (nameKind) {\n const candidates = this.nodesByNameAndKind.get(nameKind);\n if (candidates && candidates.length === 1) return candidates[0]!.symbolId;\n }\n\n return id;\n }\n\n private resolveNodeFuzzy(id: string): SymbolNode | undefined {\n const segs = id.split('::');\n const [p0, p1] = segs;\n if (p0 !== undefined && p1 !== undefined) {\n const match = this.nodesByFileAndName.get(`${p0}::${p1}`);\n if (match) return match;\n\n // Try .js → .ts extension swap (consistent with resolveNodeId)\n const jsToTs = p0.replace(/\\.js$/, '.ts');\n if (jsToTs !== p0) {\n const tsMatch = this.nodesByFileAndName.get(`${jsToTs}::${p1}`);\n if (tsMatch) return tsMatch;\n }\n }\n\n // Final fallback, as in resolveNodeId: a data reference by its tie-break, anything else by name+kind\n // only when unambiguous.\n const dataMatch = this.resolveDataReference(segs);\n if (dataMatch) return dataMatch;\n const nameKind = this.extractNameKind(segs);\n if (nameKind) {\n const candidates = this.nodesByNameAndKind.get(nameKind);\n if (candidates && candidates.length === 1) return candidates[0];\n }\n\n return undefined;\n }\n\n /**\n * Resolves a 2-part data reference (`dbo.loans::table`). A table or view reference is kind-agnostic\n * across data resource kinds, a procedure reference matches procedures only. Several definitions\n * resolve by `definitionRank` (a production copy before a documentation copy before a test copy), then\n * to the codepoint-smallest symbolId, which is the first file in sorted path order. The result does\n * not depend on insertion order.\n */\n private resolveDataReference(segs: string[]): SymbolNode | undefined {\n if (segs.length !== 2) return undefined;\n const [name, kind] = segs as [string, string];\n const kinds = dataReferenceKinds(kind);\n if (!kinds) return undefined;\n let best: SymbolNode | undefined;\n for (const k of kinds) {\n for (const node of this.nodesByNameAndKind.get(`${name}::${k}`) ?? []) {\n if (!best || this.compareDefinitions(node, best) < 0) best = node;\n }\n }\n return best;\n }\n\n /** The tie-break order of same-name definitions: `definitionRank`, then the codepoint-smaller symbolId. */\n private readonly compareDefinitions = (a: SymbolNode, b: SymbolNode): number =>\n this.definitionRankOf(a.symbolId) - this.definitionRankOf(b.symbolId) || compareCodepoint(a.symbolId, b.symbolId);\n\n /**\n * The identity of the table, view or procedure that `id` names: a full id of any of its definitions, or a\n * 2-part reference such as `dbo.loans::table`. Undefined when `id` names no node, or a node of another\n * kind. A table or view identity spans both kinds, as a 2-part `::table` reference does.\n */\n dataResourceIdentity(id: string): DataResourceIdentity | undefined {\n const node = this.getNode(id);\n return node ? this.identityOf(node) : undefined;\n }\n\n private identityOf(node: SymbolNode): DataResourceIdentity | undefined {\n const segs = node.symbolId.split('::');\n if (segs.length !== 3) return undefined;\n const [, name, kind] = segs as [string, string, string];\n const kinds = dataReferenceKinds(kind);\n if (!kinds) return undefined;\n const byId = new Map<string, SymbolNode>();\n for (const k of kinds) {\n for (const definition of this.nodesByNameAndKind.get(`${name}::${k}`) ?? []) {\n byId.set(definition.symbolId, this.nodes.get(definition.symbolId) ?? definition);\n }\n }\n const definitions = [...byId.values()].sort(this.compareDefinitions);\n const winner = definitions[0] ?? node;\n const production = definitions.filter((definition) => this.definitionRankOf(definition.symbolId) === 0);\n return { canonicalId: `${name}::${winner.kind}`, winner, definitions, production };\n }\n\n /** `definitionRank`, cached per symbolId (the glob match runs once per definition). */\n private definitionRankOf(symbolId: string): number {\n let rank = this.definitionRanks.get(symbolId);\n if (rank === undefined) {\n rank = definitionRank(symbolId);\n this.definitionRanks.set(symbolId, rank);\n }\n return rank;\n }\n\n /**\n * Extracts a `name::kind` key from the segments of a reference ID, supporting:\n * - 2-part: `Bar::class` → `Bar::class`\n * - 3-part: `pkg.Bar::helper::method` → `helper::method`\n * Returns undefined for 1-part IDs (no kind information available).\n */\n private extractNameKind(segs: string[]): string | undefined {\n if (segs.length === 3) return `${segs[1]}::${segs[2]}`; // pkg.Type::member::kind → member::kind\n if (segs.length === 2) return `${segs[0]}::${segs[1]}`; // Type::kind\n return undefined;\n }\n\n getForwardEdges(symbolId: string): GraphEdge[] {\n return this.forwardEdges.get(symbolId) ?? [];\n }\n\n getReverseEdges(symbolId: string): GraphEdge[] {\n return this.reverseEdges.get(symbolId) ?? [];\n }\n\n /**\n * The dependents of a resolved symbolId, as the impact tools read them. For a definition of a table, view\n * or procedure these are the incoming edges of every same-name definition, which in practice are the\n * winner's: any copy then reports the dependents of the resource. For every other id they are its own\n * incoming edges, as `getReverseEdges` gives them.\n */\n getDependentEdges(symbolId: string): GraphEdge[] {\n const node = this.nodes.get(symbolId);\n const definitions = node ? this.identityOf(node)?.definitions : undefined;\n if (!definitions || definitions.length < 2) return this.getReverseEdges(symbolId);\n return definitions.flatMap((definition) => this.getReverseEdges(definition.symbolId));\n }\n\n hasNode(symbolId: string): boolean {\n return this.nodes.has(symbolId) || this.resolveNodeFuzzy(symbolId) !== undefined;\n }\n\n get nodeCount(): number {\n return this.nodes.size;\n }\n\n get edgeCount(): number {\n return this.edgeSet.size;\n }\n\n allNodes(): SymbolNode[] {\n return [...this.nodes.values()];\n }\n\n allEdges(): GraphEdge[] {\n const edges: GraphEdge[] = [];\n for (const list of this.forwardEdges.values()) {\n edges.push(...list);\n }\n return edges;\n }\n}\n"],"mappings":"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","names":["log","raw"]}
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildGraphFromIndices
4
+ } from "./chunk-L2CAUER6.js";
5
+ import {
6
+ SimpleGitAdapter,
7
+ TEST_FRAMEWORK_ANNOTATIONS,
8
+ createTestFileClassifier,
9
+ filesWithTestAnnotations,
10
+ hasCSharpAnnotations,
11
+ indexTestPatterns,
12
+ loadConfig,
13
+ makeGlobMatcher,
14
+ resolveCoChangeConfig,
15
+ selectWindow,
16
+ windowCutoff
17
+ } from "./chunk-7HSA5JVP.js";
18
+ import {
19
+ getVersion,
20
+ locateGitRoot
21
+ } from "./chunk-72CEQEQD.js";
22
+ import {
23
+ JsonIndexReader
24
+ } from "./chunk-X4TVFJ4C.js";
25
+ import {
26
+ createLogger
27
+ } from "./chunk-5DSKZJNN.js";
28
+
29
+ // src/cli/module-host.ts
30
+ import { join } from "path";
31
+ var log = createLogger("ctxo:module-host");
32
+ async function readCoChangeWindow(projectRoot, config) {
33
+ try {
34
+ const coChange = resolveCoChangeConfig(config);
35
+ const changeLog = await new SimpleGitAdapter(projectRoot).getChangeLog(coChange.maxCommits);
36
+ if (changeLog === void 0) return void 0;
37
+ return {
38
+ head: changeLog.head,
39
+ headCommittedAt: changeLog.headCommittedAt,
40
+ commits: selectWindow(changeLog.commits, coChange.maxCommits, windowCutoff(coChange, changeLog.headCommittedAt))
41
+ };
42
+ } catch (err) {
43
+ log.warn(`Co-change window unavailable: ${err.message}`);
44
+ return void 0;
45
+ }
46
+ }
47
+ var HOST_SERVICES = Object.freeze({
48
+ createLogger,
49
+ loadConfig,
50
+ // The CLI matcher takes a mutable array; the module API hands out readonly ones.
51
+ makeGlobMatcher: (patterns) => makeGlobMatcher([...patterns]),
52
+ // Decided exactly as the contexts view decides it (index-contexts-view.ts), so both agree on every file.
53
+ createTestFileCheck: (config, indices) => createTestFileClassifier(indexTestPatterns(config), filesWithTestAnnotations(indices), {
54
+ annotationAware: hasCSharpAnnotations(indices)
55
+ }),
56
+ createGit: (projectRoot) => new SimpleGitAdapter(projectRoot),
57
+ readIndex: (ctxoRoot) => new JsonIndexReader(ctxoRoot).readAll(),
58
+ buildGraph: buildGraphFromIndices,
59
+ readCoChangeWindow,
60
+ findGitRoot: (dir) => locateGitRoot(dir) ?? void 0,
61
+ testFrameworkAnnotations: TEST_FRAMEWORK_ANNOTATIONS
62
+ });
63
+ function createModuleHost(options) {
64
+ return {
65
+ cliVersion: options.cliVersion ?? getVersion(),
66
+ mode: options.mode,
67
+ projectRoot: options.projectRoot,
68
+ ctxoRoot: join(options.projectRoot, ".ctxo"),
69
+ services: HOST_SERVICES
70
+ };
71
+ }
72
+
73
+ export {
74
+ createModuleHost
75
+ };
76
+ //# sourceMappingURL=chunk-G4BMKCKG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/module-host.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { CoChangeWindowCommits, CtxoHost, CtxoHostServices } from '../ports/i-ctxo-module.js';\nimport type { CtxoConfig } from '../core/config/config-schema.js';\nimport type { FileIndex } from '../core/types.js';\nimport { indexTestPatterns, loadConfig, makeGlobMatcher, resolveCoChangeConfig } from '../core/config/load-config.js';\nimport { selectWindow, windowCutoff } from '../core/co-change/co-change-window.js';\nimport { createLogger } from '../core/logger.js';\nimport {\n TEST_FRAMEWORK_ANNOTATIONS,\n createTestFileClassifier,\n filesWithTestAnnotations,\n hasCSharpAnnotations,\n} from '../core/test-files/test-file-classifier.js';\nimport { SimpleGitAdapter } from '../adapters/git/simple-git-adapter.js';\nimport { JsonIndexReader } from '../adapters/storage/json-index-reader.js';\nimport { buildGraphFromIndices } from '../adapters/mcp/get-logic-slice.js';\nimport { getVersion, locateGitRoot } from './cli-paths.js';\n\nconst log = createLogger('ctxo:module-host');\n\n/**\n * The configured co-change window: `getChangeLog` with `cochange.maxCommits`, cut with the same\n * `windowCutoff` and `selectWindow` that `ctxo index` uses for `co-changes.json`. The commits stay raw\n * (renames carry `oldPath`). Undefined when git fails, the directory is not a repository or it has no\n * commits.\n */\nasync function readCoChangeWindow(projectRoot: string, config: CtxoConfig): Promise<CoChangeWindowCommits | undefined> {\n try {\n const coChange = resolveCoChangeConfig(config);\n const changeLog = await new SimpleGitAdapter(projectRoot).getChangeLog(coChange.maxCommits);\n if (changeLog === undefined) return undefined;\n return {\n head: changeLog.head,\n headCommittedAt: changeLog.headCommittedAt,\n commits: selectWindow(changeLog.commits, coChange.maxCommits, windowCutoff(coChange, changeLog.headCommittedAt)),\n };\n } catch (err) {\n // simple-git refuses a directory that does not exist when the adapter is built.\n log.warn(`Co-change window unavailable: ${(err as Error).message}`);\n return undefined;\n }\n}\n\n/** The host's services: thin bindings to the CLI's own config, glob, test-file, git, index and graph code. */\nconst HOST_SERVICES: CtxoHostServices = Object.freeze({\n createLogger,\n loadConfig,\n // The CLI matcher takes a mutable array; the module API hands out readonly ones.\n makeGlobMatcher: (patterns: readonly string[]) => makeGlobMatcher([...patterns]),\n // Decided exactly as the contexts view decides it (index-contexts-view.ts), so both agree on every file.\n createTestFileCheck: (config: CtxoConfig, indices: readonly FileIndex[]) =>\n createTestFileClassifier(indexTestPatterns(config), filesWithTestAnnotations(indices), {\n annotationAware: hasCSharpAnnotations(indices),\n }),\n createGit: (projectRoot: string) => new SimpleGitAdapter(projectRoot),\n readIndex: (ctxoRoot: string) => new JsonIndexReader(ctxoRoot).readAll(),\n buildGraph: buildGraphFromIndices,\n readCoChangeWindow,\n findGitRoot: (dir: string) => locateGitRoot(dir) ?? undefined,\n testFrameworkAnnotations: TEST_FRAMEWORK_ANNOTATIONS,\n});\n\nexport interface CreateModuleHostOptions {\n readonly projectRoot: string;\n readonly mode: CtxoHost['mode'];\n /** Defaults to the running CLI's version. */\n readonly cliVersion?: string;\n}\n\n/**\n * The host a module's `register(host)` receives: the CLI version, the run mode, the project paths and the\n * shared services. Imports neither the CLI router nor the MCP server, so the hook route can build one.\n */\nexport function createModuleHost(options: CreateModuleHostOptions): CtxoHost {\n return {\n cliVersion: options.cliVersion ?? getVersion(),\n mode: options.mode,\n projectRoot: options.projectRoot,\n ctxoRoot: join(options.projectRoot, '.ctxo'),\n services: HOST_SERVICES,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAkBrB,IAAM,MAAM,aAAa,kBAAkB;AAQ3C,eAAe,mBAAmB,aAAqB,QAAgE;AACrH,MAAI;AACF,UAAM,WAAW,sBAAsB,MAAM;AAC7C,UAAM,YAAY,MAAM,IAAI,iBAAiB,WAAW,EAAE,aAAa,SAAS,UAAU;AAC1F,QAAI,cAAc,OAAW,QAAO;AACpC,WAAO;AAAA,MACL,MAAM,UAAU;AAAA,MAChB,iBAAiB,UAAU;AAAA,MAC3B,SAAS,aAAa,UAAU,SAAS,SAAS,YAAY,aAAa,UAAU,UAAU,eAAe,CAAC;AAAA,IACjH;AAAA,EACF,SAAS,KAAK;AAEZ,QAAI,KAAK,iCAAkC,IAAc,OAAO,EAAE;AAClE,WAAO;AAAA,EACT;AACF;AAGA,IAAM,gBAAkC,OAAO,OAAO;AAAA,EACpD;AAAA,EACA;AAAA;AAAA,EAEA,iBAAiB,CAAC,aAAgC,gBAAgB,CAAC,GAAG,QAAQ,CAAC;AAAA;AAAA,EAE/E,qBAAqB,CAAC,QAAoB,YACxC,yBAAyB,kBAAkB,MAAM,GAAG,yBAAyB,OAAO,GAAG;AAAA,IACrF,iBAAiB,qBAAqB,OAAO;AAAA,EAC/C,CAAC;AAAA,EACH,WAAW,CAAC,gBAAwB,IAAI,iBAAiB,WAAW;AAAA,EACpE,WAAW,CAAC,aAAqB,IAAI,gBAAgB,QAAQ,EAAE,QAAQ;AAAA,EACvE,YAAY;AAAA,EACZ;AAAA,EACA,aAAa,CAAC,QAAgB,cAAc,GAAG,KAAK;AAAA,EACpD,0BAA0B;AAC5B,CAAC;AAaM,SAAS,iBAAiB,SAA4C;AAC3E,SAAO;AAAA,IACL,YAAY,QAAQ,cAAc,WAAW;AAAA,IAC7C,MAAM,QAAQ;AAAA,IACd,aAAa,QAAQ;AAAA,IACrB,UAAU,KAAK,QAAQ,aAAa,OAAO;AAAA,IAC3C,UAAU;AAAA,EACZ;AACF;","names":[]}