design-rover 0.2.0 → 0.3.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/dist/bin.js CHANGED
@@ -27,7 +27,7 @@ var init_constants = __esm({
27
27
  MODEL_VERSION = 1;
28
28
  PRODUCT_NAME = "Design Rover";
29
29
  CLI_PACKAGE = "design-rover";
30
- CLI_VERSION = "0.2.0";
30
+ CLI_VERSION = "0.3.0";
31
31
  SPEC_DIR = ".design-rover";
32
32
  MANAGED_START = "<!-- design-rover:start -->";
33
33
  MANAGED_END = "<!-- design-rover:end -->";
@@ -99,7 +99,7 @@ var init_ids = __esm({
99
99
 
100
100
  // ../model/src/schema.ts
101
101
  import { z } from "zod";
102
- var KnowledgeStatusSchema, ConfidenceSchema, SourceKindSchema, TokenCategorySchema, EntityKindSchema, DiagnosticSeveritySchema, SyncStateSchema, SourceLocationSchema, EvidenceSchema, KnowledgeMetaSchema, TokenRecordSchema, ComponentPropSchema, ComponentUsageSchema, ComponentRecordSchema, PatternOccurrenceSchema, PatternRecordSchema, ConventionRecordSchema, IntentRecordSchema, ExceptionRecordSchema, RejectionRecordSchema, BaselineFindingSchema, BaselineRecordSchema, ExternalSourceSchema, SourceDiscrepancySchema, GeneratedFileSchema, HistoryEventSchema, FrameworkInfoSchema, StylingInfoSchema, EnforcementInfoSchema, SystemRecordSchema, CanonicalModelSchema, ObservedSnapshotSchema, ChangeKindSchema, ModelChangeSchema, SystemDiffSchema, DiagnosticSchema, CheckReportSchema, StatusReportSchema;
102
+ var KnowledgeStatusSchema, ConfidenceSchema, SourceKindSchema, TokenCategorySchema, EntityKindSchema, DiagnosticSeveritySchema, SyncStateSchema, SourceLocationSchema, EvidenceSchema, KnowledgeMetaSchema, TokenRecordSchema, ComponentPropSchema, ComponentUsageSchema, ComponentPropsResolutionSchema, ComponentRecordSchema, PatternOccurrenceSchema, PatternRecordSchema, ConventionRecordSchema, IntentRecordSchema, ExceptionRecordSchema, RejectionRecordSchema, BaselineFindingSchema, BaselineRecordSchema, ExternalSourceSchema, SourceDiscrepancySchema, GeneratedFileSchema, HistoryEventSchema, FrameworkInfoSchema, StylingInfoSchema, EnforcementInfoSchema, SystemRecordSchema, CanonicalModelSchema, ObservedSnapshotSchema, ChangeKindSchema, ModelChangeSchema, SystemDiffSchema, DiagnosticSchema, CheckReportSchema, StatusReportSchema;
103
103
  var init_schema = __esm({
104
104
  "../model/src/schema.ts"() {
105
105
  "use strict";
@@ -160,6 +160,10 @@ var init_schema = __esm({
160
160
  file: z.string(),
161
161
  count: z.number().int()
162
162
  });
163
+ ComponentPropsResolutionSchema = z.object({
164
+ status: z.enum(["resolved", "partial", "unresolved", "untyped"]),
165
+ issues: z.array(z.string()).default([])
166
+ });
163
167
  ComponentRecordSchema = KnowledgeMetaSchema.extend({
164
168
  id: z.string(),
165
169
  name: z.string(),
@@ -167,6 +171,7 @@ var init_schema = __esm({
167
171
  importPath: z.string(),
168
172
  exportName: z.string(),
169
173
  props: z.array(ComponentPropSchema).default([]),
174
+ propsResolution: ComponentPropsResolutionSchema.optional(),
170
175
  variants: z.array(z.string()).default([]),
171
176
  sizes: z.array(z.string()).default([]),
172
177
  composition: z.array(z.string()).default([]),
@@ -502,10 +507,189 @@ var init_hash = __esm({
502
507
  }
503
508
  });
504
509
 
505
- // ../core/src/component-resolution.ts
510
+ // ../core/src/local-modules.ts
506
511
  import path2 from "path";
512
+ import ts from "typescript";
513
+ function scriptKind(file) {
514
+ if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
515
+ if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
516
+ return /\.(?:js|mjs|cjs)$/.test(file) ? ts.ScriptKind.JS : ts.ScriptKind.TS;
517
+ }
518
+ function localModules(evidence) {
519
+ let graph = graphs.get(evidence);
520
+ if (!graph) {
521
+ graph = new LocalModules(evidence);
522
+ graphs.set(evidence, graph);
523
+ }
524
+ return graph;
525
+ }
526
+ var graphs, extensions, normalize, LocalModules;
527
+ var init_local_modules = __esm({
528
+ "../core/src/local-modules.ts"() {
529
+ "use strict";
530
+ init_esm_shims();
531
+ graphs = /* @__PURE__ */ new WeakMap();
532
+ extensions = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx"];
533
+ normalize = (file) => path2.posix.normalize(file.replaceAll("\\", "/"));
534
+ LocalModules = class {
535
+ constructor(evidence) {
536
+ this.evidence = evidence;
537
+ for (const file of evidence.files) if (file.kind === "script") this.files.set(normalize(file.relative), file.content);
538
+ const config = evidence.files.find((file) => file.relative === "tsconfig.json");
539
+ const options = config ? ts.parseConfigFileTextToJson("tsconfig.json", config.content).config?.compilerOptions : void 0;
540
+ this.baseUrl = typeof options?.baseUrl === "string" ? normalize(options.baseUrl) : ".";
541
+ }
542
+ evidence;
543
+ files = /* @__PURE__ */ new Map();
544
+ modules = /* @__PURE__ */ new Map();
545
+ resolutions = /* @__PURE__ */ new Map();
546
+ baseUrl;
547
+ source(file) {
548
+ return this.module(file)?.source;
549
+ }
550
+ resolveModule(from, specifier) {
551
+ const candidates = [];
552
+ if (specifier.startsWith(".")) candidates.push(path2.posix.join(path2.posix.dirname(from), specifier));
553
+ else {
554
+ const aliases = Object.entries(this.evidence.tsconfigPaths).sort(([a], [b]) => Number(a.includes("*")) - Number(b.includes("*")) || b.indexOf("*") - a.indexOf("*") || a.localeCompare(b));
555
+ for (const [alias, values] of aliases) {
556
+ const star = alias.indexOf("*");
557
+ if (star < 0) {
558
+ if (alias === specifier) {
559
+ candidates.push(...values.map((value) => path2.posix.join(this.baseUrl, value)));
560
+ break;
561
+ }
562
+ } else {
563
+ const prefix = alias.slice(0, star);
564
+ const suffix = alias.slice(star + 1);
565
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) {
566
+ const rest = specifier.slice(prefix.length, suffix ? -suffix.length : void 0);
567
+ candidates.push(...values.map((value) => path2.posix.join(this.baseUrl, value.replace("*", rest))));
568
+ break;
569
+ }
570
+ }
571
+ }
572
+ if (this.baseUrl !== ".") candidates.push(path2.posix.join(this.baseUrl, specifier));
573
+ }
574
+ for (const candidate of candidates) {
575
+ const normalized = normalize(candidate);
576
+ if (normalized === ".." || normalized.startsWith("../") || path2.posix.isAbsolute(normalized)) continue;
577
+ const direct = [];
578
+ if (/\.jsx?$/.test(normalized)) direct.push(normalized.replace(/\.jsx?$/, ".ts"), normalized.replace(/\.jsx?$/, ".tsx"));
579
+ if (normalized.endsWith(".mjs")) direct.push(normalized.replace(/\.mjs$/, ".mts"));
580
+ if (normalized.endsWith(".cjs")) direct.push(normalized.replace(/\.cjs$/, ".cts"));
581
+ direct.push(normalized);
582
+ direct.push(...extensions.map((extension) => `${normalized}${extension}`), ...extensions.map((extension) => `${normalized}/index${extension}`));
583
+ const found = direct.find((file) => this.files.has(file));
584
+ if (found) return found;
585
+ }
586
+ return void 0;
587
+ }
588
+ resolveExport(from, specifier, name, kind) {
589
+ const module = this.resolveModule(from, specifier);
590
+ if (!module) return { targets: [], issues: [`Cannot resolve local ${kind} import ${JSON.stringify(specifier)} from ${from}. External package declarations are not loaded.`] };
591
+ const key = JSON.stringify([module, name, kind]);
592
+ const cached = this.resolutions.get(key);
593
+ if (cached) return cached;
594
+ const result = this.exported(module, name, kind, /* @__PURE__ */ new Set());
595
+ const distinct = [...new Map(result.targets.map((target) => [JSON.stringify(target), target])).values()];
596
+ const issues = [...new Set(result.issues)];
597
+ if (distinct.length > 1) issues.push(`Ambiguous ${kind} export ${name} in ${module}; multiple declarations match.`);
598
+ const resolution = { module, targets: distinct, issues };
599
+ this.resolutions.set(key, resolution);
600
+ return resolution;
601
+ }
602
+ module(file) {
603
+ file = normalize(file);
604
+ const cached = this.modules.get(file);
605
+ if (cached) return cached;
606
+ const content = this.files.get(file);
607
+ if (content === void 0) return void 0;
608
+ const source = ts.createSourceFile(path2.resolve(this.evidence.root || ".", file), content, ts.ScriptTarget.Latest, true, scriptKind(file));
609
+ const module = { source, locals: /* @__PURE__ */ new Map(), imports: /* @__PURE__ */ new Map(), exports: /* @__PURE__ */ new Map(), stars: [] };
610
+ this.modules.set(file, module);
611
+ const addExport = (name, binding) => module.exports.set(name, [...module.exports.get(name) ?? [], binding]);
612
+ const addLocal = (name, declaration) => module.locals.set(name, [...module.locals.get(name) ?? [], declaration]);
613
+ for (const statement of source.statements) {
614
+ if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
615
+ const clause = statement.importClause;
616
+ const specifier = statement.moduleSpecifier.text;
617
+ if (clause?.name) module.imports.set(clause.name.text, { source: specifier, imported: "default", typeOnly: clause.isTypeOnly });
618
+ const bindings = clause?.namedBindings;
619
+ if (bindings && ts.isNamedImports(bindings)) for (const binding of bindings.elements) {
620
+ module.imports.set(binding.name.text, { source: specifier, imported: binding.propertyName?.text ?? binding.name.text, typeOnly: Boolean(clause?.isTypeOnly || binding.isTypeOnly) });
621
+ }
622
+ if (bindings && ts.isNamespaceImport(bindings)) module.imports.set(bindings.name.text, { source: specifier, imported: "*", typeOnly: Boolean(clause?.isTypeOnly) });
623
+ continue;
624
+ }
625
+ if (ts.isExportDeclaration(statement)) {
626
+ const specifier = statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : void 0;
627
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
628
+ for (const binding of statement.exportClause.elements) addExport(binding.name.text, { source: specifier, imported: binding.propertyName?.text ?? binding.name.text, typeOnly: statement.isTypeOnly || binding.isTypeOnly });
629
+ } else if (!statement.exportClause && specifier) module.stars.push({ source: specifier, typeOnly: statement.isTypeOnly });
630
+ continue;
631
+ }
632
+ if (ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) {
633
+ addExport("default", { imported: statement.expression.text, typeOnly: false });
634
+ continue;
635
+ }
636
+ const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : void 0;
637
+ const exported = modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
638
+ const isDefault = modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
639
+ if (ts.isVariableStatement(statement)) {
640
+ for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name)) {
641
+ addLocal(declaration.name.text, { type: false, value: true });
642
+ if (exported) addExport(declaration.name.text, { imported: declaration.name.text, typeOnly: false });
643
+ }
644
+ } else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement)) && statement.name) {
645
+ const name = statement.name.text;
646
+ const type = !ts.isFunctionDeclaration(statement);
647
+ const value = !ts.isInterfaceDeclaration(statement) && !ts.isTypeAliasDeclaration(statement);
648
+ addLocal(name, { type, value });
649
+ if (exported) addExport(isDefault ? "default" : name, { imported: name, typeOnly: !value });
650
+ }
651
+ }
652
+ return module;
653
+ }
654
+ local(file, name, kind, seen) {
655
+ const module = this.module(file);
656
+ if (!module) return { targets: [], issues: [] };
657
+ if (module.locals.get(name)?.some((declaration) => declaration[kind])) return { targets: [{ file, name }], issues: [] };
658
+ const binding = module.imports.get(name);
659
+ if (binding?.source && !(kind === "value" && binding.typeOnly)) return this.binding(file, binding, kind, seen);
660
+ return { targets: [], issues: [] };
661
+ }
662
+ binding(file, binding, kind, seen) {
663
+ if (kind === "value" && binding.typeOnly) return { targets: [], issues: [] };
664
+ if (!binding.source) return this.local(file, binding.imported, kind, seen);
665
+ const target = this.resolveModule(file, binding.source);
666
+ if (!target) return { targets: [], issues: [`Cannot resolve local ${kind} re-export ${JSON.stringify(binding.source)} from ${file}.`] };
667
+ if (binding.imported === "*") return { targets: [], issues: [`Namespace re-export in ${file} requires an explicit exported member.`] };
668
+ return this.exported(target, binding.imported, kind, seen);
669
+ }
670
+ exported(file, name, kind, seen) {
671
+ const key = JSON.stringify([file, name, kind]);
672
+ if (seen.has(key)) return { targets: [], issues: [] };
673
+ const next = new Set(seen).add(key);
674
+ const module = this.module(file);
675
+ if (!module) return { targets: [], issues: [] };
676
+ const explicit = module.exports.get(name)?.filter((binding) => !(kind === "value" && binding.typeOnly));
677
+ if (explicit?.length) {
678
+ const results2 = explicit.map((binding) => this.binding(file, binding, kind, next));
679
+ return { targets: results2.flatMap((result) => result.targets), issues: results2.flatMap((result) => result.issues) };
680
+ }
681
+ if (name === "default") return { targets: [], issues: [] };
682
+ const results = module.stars.filter((star) => !(kind === "value" && star.typeOnly)).map((star) => this.binding(file, { source: star.source, imported: name, typeOnly: star.typeOnly }, kind, next));
683
+ return { targets: results.flatMap((result) => result.targets), issues: results.flatMap((result) => result.issues) };
684
+ }
685
+ };
686
+ }
687
+ });
688
+
689
+ // ../core/src/component-resolution.ts
690
+ import path3 from "path";
507
691
  function targets(evidence, file, source) {
508
- if (source.startsWith(".")) return [path2.posix.normalize(path2.posix.join(path2.posix.dirname(file), source))];
692
+ if (source.startsWith(".")) return [path3.posix.normalize(path3.posix.join(path3.posix.dirname(file), source))];
509
693
  const found = [];
510
694
  for (const [alias, values] of Object.entries(evidence.tsconfigPaths)) {
511
695
  const star = alias.indexOf("*");
@@ -520,7 +704,7 @@ function targets(evidence, file, source) {
520
704
  }
521
705
  }
522
706
  }
523
- return found.map((value) => path2.posix.normalize(value.replace(/^\.\//, "")));
707
+ return found.map((value) => path3.posix.normalize(value.replace(/^\.\//, "")));
524
708
  }
525
709
  function resolveComponent(evidence, file, tag, components, options = {}) {
526
710
  if (tag.split(".").length > 2) return void 0;
@@ -528,6 +712,7 @@ function resolveComponent(evidence, file, tag, components, options = {}) {
528
712
  const imports = file.imports.filter((item) => !item.isTypeOnly);
529
713
  let bound = false;
530
714
  const matches = /* @__PURE__ */ new Set();
715
+ const graph = localModules(evidence);
531
716
  for (const item of imports) {
532
717
  const bindings = item.bindings ?? [
533
718
  ...item.names.map((name) => ({ local: name, imported: name })),
@@ -536,8 +721,16 @@ function resolveComponent(evidence, file, tag, components, options = {}) {
536
721
  const binding = bindings.find((value) => value.local === local);
537
722
  if (!binding) continue;
538
723
  bound = true;
724
+ if (binding.isTypeOnly) continue;
539
725
  if (binding.imported === "*" !== Boolean(member)) continue;
540
726
  const exported = member ?? binding.imported;
727
+ const resolution = graph.resolveExport(file.relative, item.source, exported, "value");
728
+ if (resolution.module) {
729
+ if (resolution.issues.length || resolution.targets.length !== 1) continue;
730
+ const target = resolution.targets[0];
731
+ for (const component of components) if (component.source === target.file && component.name === target.name) matches.add(component);
732
+ continue;
733
+ }
541
734
  const modules = targets(evidence, file.relative, item.source);
542
735
  for (const component of components) {
543
736
  const moduleMatch = !item.source.startsWith(".") && component.importPath === item.source || modules.some((target) => {
@@ -561,12 +754,307 @@ var init_component_resolution = __esm({
561
754
  "../core/src/component-resolution.ts"() {
562
755
  "use strict";
563
756
  init_esm_shims();
757
+ init_local_modules();
564
758
  withoutExtension = (file) => file.replace(/\.(?:tsx?|jsx?|mts|cts|mjs|cjs)$/, "");
565
759
  }
566
760
  });
567
761
 
762
+ // ../core/src/type-resolution.ts
763
+ import path4 from "path";
764
+ import ts2 from "typescript";
765
+ function unwrap(node) {
766
+ if (ts2.isArrowFunction(node) || ts2.isFunctionExpression(node)) return { fn: node };
767
+ if (ts2.isParenthesizedExpression(node) || ts2.isAsExpression(node) || ts2.isSatisfiesExpression(node)) return unwrap(node.expression);
768
+ if (ts2.isCallExpression(node)) {
769
+ const name = ts2.isIdentifier(node.expression) ? node.expression.text : ts2.isPropertyAccessExpression(node.expression) ? node.expression.name.text : "";
770
+ if (name === "memo" || name === "forwardRef") {
771
+ const first = node.arguments[0];
772
+ if (first) return { ...unwrap(first), type: name === "forwardRef" ? node.typeArguments?.[1] : node.typeArguments?.[0] };
773
+ }
774
+ }
775
+ return {};
776
+ }
777
+ function componentInput(source, component) {
778
+ let input;
779
+ const visit = (node) => {
780
+ if (input) return;
781
+ const line2 = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
782
+ if (ts2.isFunctionDeclaration(node) && node.name?.text === component.name && line2 === component.startLine) {
783
+ input = { fn: node, type: node.parameters[0]?.type };
784
+ return;
785
+ }
786
+ if (ts2.isVariableDeclaration(node) && ts2.isIdentifier(node.name) && node.name.text === component.name && line2 === component.startLine && node.initializer) {
787
+ const unwrapped = unwrap(node.initializer);
788
+ if (!unwrapped.fn) return;
789
+ let annotation;
790
+ if (node.type && ts2.isTypeReferenceNode(node.type) && /(?:^|\.)(?:FC|FunctionComponent|ComponentType)$/.test(node.type.typeName.getText())) annotation = node.type.typeArguments?.[0];
791
+ if (node.type && ts2.isFunctionTypeNode(node.type)) annotation = node.type.parameters[0]?.type;
792
+ input = { fn: unwrapped.fn, type: unwrapped.fn.parameters[0]?.type ?? unwrapped.type ?? annotation };
793
+ return;
794
+ }
795
+ ts2.forEachChild(node, visit);
796
+ };
797
+ visit(source);
798
+ return input;
799
+ }
800
+ function resolveComponentProps(evidence, component) {
801
+ let resolver = resolvers.get(evidence);
802
+ if (!resolver) {
803
+ resolver = new TypeResolver(evidence);
804
+ resolvers.set(evidence, resolver);
805
+ }
806
+ return resolver.props(component);
807
+ }
808
+ var resolvers, MAX_PROPS, normalize2, nameOf, TypeResolver;
809
+ var init_type_resolution = __esm({
810
+ "../core/src/type-resolution.ts"() {
811
+ "use strict";
812
+ init_esm_shims();
813
+ init_local_modules();
814
+ resolvers = /* @__PURE__ */ new WeakMap();
815
+ MAX_PROPS = 256;
816
+ normalize2 = (file) => path4.resolve(file).replaceAll("\\", "/");
817
+ nameOf = (name) => ts2.isIdentifier(name) || ts2.isStringLiteral(name) || ts2.isNumericLiteral(name) ? name.text : name.getText();
818
+ TypeResolver = class {
819
+ constructor(evidence) {
820
+ this.evidence = evidence;
821
+ this.graph = localModules(evidence);
822
+ this.root = path4.resolve(evidence.root || ".");
823
+ this.libDirectory = normalize2(path4.dirname(ts2.getDefaultLibFilePath({})));
824
+ }
825
+ evidence;
826
+ program;
827
+ graph;
828
+ root;
829
+ libDirectory;
830
+ semantic = /* @__PURE__ */ new Map();
831
+ relative(file) {
832
+ return path4.relative(this.root, file).replaceAll("\\", "/");
833
+ }
834
+ isLibrary(file) {
835
+ return normalize2(file).startsWith(`${this.libDirectory}/`) && /\/lib\.[^/]+\.d\.ts$/.test(normalize2(file));
836
+ }
837
+ compiler() {
838
+ if (this.program) return this.program;
839
+ const options = {
840
+ target: ts2.ScriptTarget.ES2022,
841
+ module: ts2.ModuleKind.ESNext,
842
+ moduleResolution: ts2.ModuleResolutionKind.Bundler,
843
+ jsx: ts2.JsxEmit.Preserve,
844
+ strict: true,
845
+ noEmit: true,
846
+ allowJs: true,
847
+ checkJs: false,
848
+ skipLibCheck: true,
849
+ types: [],
850
+ lib: ["lib.es2022.d.ts"]
851
+ };
852
+ const original = ts2.createCompilerHost(options, true);
853
+ const read = (file) => this.graph.files.get(this.relative(file)) ?? (this.isLibrary(file) ? ts2.sys.readFile(file) : void 0);
854
+ const host = {
855
+ ...original,
856
+ getCurrentDirectory: () => this.root,
857
+ getCanonicalFileName: normalize2,
858
+ useCaseSensitiveFileNames: () => true,
859
+ readFile: read,
860
+ fileExists: (file) => read(file) !== void 0,
861
+ getSourceFile: (file, languageVersion) => {
862
+ const source = this.graph.source(this.relative(file));
863
+ if (source) return source;
864
+ const text = this.isLibrary(file) ? ts2.sys.readFile(file) : void 0;
865
+ return text === void 0 ? void 0 : ts2.createSourceFile(file, text, languageVersion, true);
866
+ },
867
+ writeFile: () => {
868
+ throw new Error("Design Rover's type resolver never emits application code.");
869
+ },
870
+ resolveModuleNames: (names, containingFile) => names.map((name) => {
871
+ const target = this.graph.resolveModule(this.relative(containingFile), name);
872
+ if (!target) return void 0;
873
+ return { resolvedFileName: path4.resolve(this.root, target), isExternalLibraryImport: false };
874
+ }),
875
+ resolveTypeReferenceDirectives: (names) => names.map(() => void 0)
876
+ };
877
+ this.program = ts2.createProgram([...this.graph.files.keys()].map((file) => path4.resolve(this.root, file)), options, host);
878
+ return this.program;
879
+ }
880
+ props(component) {
881
+ const source = this.graph.source(component.file);
882
+ const input = source && componentInput(source, component);
883
+ if (!input) return { props: component.props, resolution: { status: "unresolved", issues: ["The component's function declaration could not be matched to its source location."] } };
884
+ const parameter = input.fn.parameters[0];
885
+ if (!parameter && !input.type) return { props: [], resolution: { status: "resolved", issues: [] } };
886
+ const defaults = /* @__PURE__ */ new Map();
887
+ const untyped = [];
888
+ if (parameter && ts2.isObjectBindingPattern(parameter.name)) {
889
+ for (const binding of parameter.name.elements) {
890
+ if (binding.dotDotDotToken) continue;
891
+ const name = binding.propertyName ? nameOf(binding.propertyName) : binding.name.getText();
892
+ if (binding.initializer) defaults.set(name, binding.initializer.getText());
893
+ untyped.push({ name, required: !binding.initializer, defaultValue: binding.initializer?.getText() });
894
+ }
895
+ }
896
+ if (!input.type) return { props: untyped, resolution: { status: "untyped", issues: ["The props parameter has no explicit supported type annotation. Destructured bindings are not a complete component API."] } };
897
+ const program = this.compiler();
898
+ const checker = program.getTypeChecker();
899
+ const issues = /* @__PURE__ */ new Set();
900
+ const visited = /* @__PURE__ */ new Set();
901
+ let unsafeDependency = false;
902
+ const diagnostics = (node) => {
903
+ const source2 = node.getSourceFile();
904
+ let all = this.semantic.get(source2.fileName);
905
+ if (!all) {
906
+ all = [...program.getSyntacticDiagnostics(source2), ...program.getSemanticDiagnostics(source2)];
907
+ this.semantic.set(source2.fileName, all);
908
+ }
909
+ const start = node.getStart(source2);
910
+ const end = node.getEnd();
911
+ for (const diagnostic of all) if (diagnostic.category === ts2.DiagnosticCategory.Error && diagnostic.start !== void 0 && diagnostic.start >= start && diagnostic.start < end) {
912
+ issues.add(`${this.relative(source2.fileName)}: ${ts2.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`);
913
+ }
914
+ };
915
+ const importType = (node) => {
916
+ const argument = node.argument;
917
+ if (!ts2.isLiteralTypeNode(argument) || !ts2.isStringLiteral(argument.literal) || !node.qualifier || !ts2.isIdentifier(node.qualifier)) {
918
+ return { targets: [], issues: ["An import type must identify one explicit local exported member; computed or nested import-type members are unsupported."] };
919
+ }
920
+ return this.graph.resolveExport(this.relative(node.getSourceFile().fileName), argument.literal.text, node.qualifier.text, node.isTypeOf ? "value" : "type");
921
+ };
922
+ const reference = (name) => {
923
+ const current = name.getSourceFile();
924
+ const file = this.relative(current.fileName);
925
+ const parts = name.getText(current).split(".");
926
+ const first = parts[0];
927
+ for (const statement of current.statements) {
928
+ if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier)) continue;
929
+ const clause = statement.importClause;
930
+ const bindings = clause?.namedBindings;
931
+ let imported;
932
+ if (parts.length === 1 && clause?.name?.text === first) imported = "default";
933
+ if (parts.length === 1 && bindings && ts2.isNamedImports(bindings)) {
934
+ const binding = bindings.elements.find((item) => item.name.text === first);
935
+ if (binding) imported = binding.propertyName?.text ?? binding.name.text;
936
+ }
937
+ if (parts.length === 2 && bindings && ts2.isNamespaceImport(bindings) && bindings.name.text === first) imported = parts[1];
938
+ if (imported) {
939
+ const resolution = this.graph.resolveExport(file, statement.moduleSpecifier.text, imported, "type");
940
+ for (const issue of resolution.issues) issues.add(issue);
941
+ if (resolution.targets.length !== 1 && !resolution.issues.length) issues.add(`Cannot resolve a unique local type export ${imported} from ${statement.moduleSpecifier.text}.`);
942
+ if (resolution.issues.length || resolution.targets.length !== 1) unsafeDependency = true;
943
+ }
944
+ }
945
+ let symbol = checker.getSymbolAtLocation(name);
946
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
947
+ if (!symbol || !symbol.declarations?.length) {
948
+ issues.add(`Cannot resolve type ${name.getText(current)} in ${file}.`);
949
+ unsafeDependency = true;
950
+ return;
951
+ }
952
+ for (const declaration of symbol.declarations) {
953
+ if (this.isLibrary(declaration.getSourceFile().fileName)) continue;
954
+ if (!this.graph.files.has(this.relative(declaration.getSourceFile().fileName))) {
955
+ issues.add(`Type ${name.getText(current)} is declared outside scanned repository files.`);
956
+ continue;
957
+ }
958
+ if (ts2.isTypeAliasDeclaration(declaration) || ts2.isInterfaceDeclaration(declaration) || ts2.isClassDeclaration(declaration)) walk(declaration);
959
+ }
960
+ };
961
+ const walk = (node) => {
962
+ if (visited.has(node)) return;
963
+ visited.add(node);
964
+ diagnostics(node);
965
+ if (ts2.isClassDeclaration(node)) issues.add("Class-based props contracts are only partially supported; inherited and runtime-initialized members require inspection.");
966
+ if (ts2.isTypeReferenceNode(node)) reference(node.typeName);
967
+ if (ts2.isExpressionWithTypeArguments(node)) reference(node.expression);
968
+ if (ts2.isImportTypeNode(node)) {
969
+ const resolution = importType(node);
970
+ for (const issue of resolution.issues) issues.add(issue);
971
+ if (resolution.issues.length || resolution.targets.length !== 1) {
972
+ unsafeDependency = true;
973
+ if (!resolution.issues.length) issues.add("The import type does not identify a unique local exported declaration.");
974
+ } else if (node.qualifier) {
975
+ let symbol = checker.getSymbolAtLocation(node.qualifier);
976
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
977
+ for (const declaration of symbol?.declarations ?? []) if (ts2.isTypeAliasDeclaration(declaration) || ts2.isInterfaceDeclaration(declaration) || ts2.isClassDeclaration(declaration)) walk(declaration);
978
+ }
979
+ }
980
+ ts2.forEachChild(node, walk);
981
+ };
982
+ walk(input.type);
983
+ const type = checker.getTypeFromTypeNode(input.type);
984
+ const primitive = ts2.TypeFlags.StringLike | ts2.TypeFlags.NumberLike | ts2.TypeFlags.BooleanLike | ts2.TypeFlags.BigIntLike | ts2.TypeFlags.ESSymbolLike | ts2.TypeFlags.Null | ts2.TypeFlags.Undefined | ts2.TypeFlags.Void;
985
+ const nonObject = Boolean(type.flags & primitive) || type.isUnion() && type.types.some((member) => Boolean(member.flags & primitive));
986
+ if (nonObject) issues.add("The props annotation includes a non-object type; primitive members are not a component property API.");
987
+ if (type.flags & (ts2.TypeFlags.Any | ts2.TypeFlags.Unknown | ts2.TypeFlags.TypeParameter | ts2.TypeFlags.Conditional | ts2.TypeFlags.Never)) issues.add("The props object type could not be resolved to a concrete local contract.");
988
+ if (type.isUnion()) issues.add("Union props expose only common properties; branch-specific contracts remain unresolved.");
989
+ if (checker.getIndexInfosOfType(type).length) issues.add("The props type has an index signature; arbitrary property names are not enumerated.");
990
+ const proven = (node, seen) => {
991
+ if (seen.has(node)) return [];
992
+ const next = new Set(seen).add(node);
993
+ if (ts2.isParenthesizedTypeNode(node)) return proven(node.type, next);
994
+ if (ts2.isIntersectionTypeNode(node)) return node.types.flatMap((member) => proven(member, next));
995
+ if (ts2.isTypeLiteralNode(node)) return checker.getPropertiesOfType(checker.getTypeFromTypeNode(node));
996
+ if (ts2.isInterfaceDeclaration(node) || ts2.isClassDeclaration(node)) {
997
+ const own = node.members.flatMap((member) => member.name && !(ts2.canHaveModifiers(member) && ts2.getModifiers(member)?.some((modifier) => modifier.kind === ts2.SyntaxKind.StaticKeyword)) ? [checker.getSymbolAtLocation(member.name)].filter((symbol) => Boolean(symbol)) : []);
998
+ return [...own, ...(node.heritageClauses ?? []).flatMap((clause) => clause.types.flatMap((base) => proven(base, next)))];
999
+ }
1000
+ if (ts2.isTypeAliasDeclaration(node)) return proven(node.type, next);
1001
+ if (ts2.isImportTypeNode(node)) {
1002
+ const resolution = importType(node);
1003
+ if (resolution.issues.length || resolution.targets.length !== 1) return [];
1004
+ let symbol = node.qualifier && checker.getSymbolAtLocation(node.qualifier);
1005
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
1006
+ return (symbol?.declarations ?? []).filter((declaration) => !this.isLibrary(declaration.getSourceFile().fileName)).flatMap((declaration) => proven(declaration, next));
1007
+ }
1008
+ if (ts2.isTypeReferenceNode(node) || ts2.isExpressionWithTypeArguments(node)) {
1009
+ const name = ts2.isTypeReferenceNode(node) ? node.typeName : node.expression;
1010
+ const file = this.relative(node.getSourceFile().fileName);
1011
+ const parts = name.getText().split(".");
1012
+ for (const statement of node.getSourceFile().statements) {
1013
+ if (!ts2.isImportDeclaration(statement) || !ts2.isStringLiteral(statement.moduleSpecifier)) continue;
1014
+ const clause = statement.importClause;
1015
+ const bindings = clause?.namedBindings;
1016
+ let imported = parts.length === 1 && clause?.name?.text === parts[0] ? "default" : void 0;
1017
+ if (parts.length === 1 && bindings && ts2.isNamedImports(bindings)) {
1018
+ const binding = bindings.elements.find((member) => member.name.text === parts[0]);
1019
+ if (binding) imported = binding.propertyName?.text ?? binding.name.text;
1020
+ }
1021
+ if (parts.length === 2 && bindings && ts2.isNamespaceImport(bindings) && bindings.name.text === parts[0]) imported = parts[1];
1022
+ if (imported) {
1023
+ const resolution = this.graph.resolveExport(file, statement.moduleSpecifier.text, imported, "type");
1024
+ if (resolution.issues.length || resolution.targets.length !== 1) return [];
1025
+ }
1026
+ }
1027
+ let symbol = checker.getSymbolAtLocation(name);
1028
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
1029
+ return (symbol?.declarations ?? []).filter((declaration) => !this.isLibrary(declaration.getSourceFile().fileName)).flatMap((declaration) => proven(declaration, next));
1030
+ }
1031
+ return [];
1032
+ };
1033
+ const candidates = nonObject ? [] : unsafeDependency || type.flags & (ts2.TypeFlags.Any | ts2.TypeFlags.Unknown) ? proven(input.type, /* @__PURE__ */ new Set()) : checker.getPropertiesOfType(type);
1034
+ const symbols = [...new Map(candidates.map((symbol) => [symbol.name, symbol])).values()];
1035
+ if (symbols.length > MAX_PROPS) issues.add(`The props contract exceeds ${MAX_PROPS} properties; only the first ${MAX_PROPS} are included.`);
1036
+ const props = symbols.slice(0, MAX_PROPS).map((symbol) => {
1037
+ const propertyType = checker.getTypeOfSymbolAtLocation(symbol, input.type);
1038
+ const required = !(symbol.flags & ts2.SymbolFlags.Optional);
1039
+ const members = propertyType.isUnion() ? propertyType.types.filter((member) => required || !(member.flags & ts2.TypeFlags.Undefined)) : [propertyType];
1040
+ if (members.some((member) => member.flags & (ts2.TypeFlags.TypeParameter | ts2.TypeFlags.Conditional | ts2.TypeFlags.IndexedAccess))) issues.add(`Property ${symbol.name} depends on unresolved generic type information.`);
1041
+ const values = members.length && members.every((member) => member.isStringLiteral()) ? members.map((member) => member.value).sort() : void 0;
1042
+ return {
1043
+ name: symbol.name,
1044
+ type: members.map((member) => checker.typeToString(member, input.type, ts2.TypeFormatFlags.NoTruncation | ts2.TypeFormatFlags.InTypeAlias)).join(" | ") || "undefined",
1045
+ required,
1046
+ values,
1047
+ defaultValue: defaults.get(symbol.name)
1048
+ };
1049
+ }).sort((a, b) => a.name.localeCompare(b.name));
1050
+ return { props, resolution: { status: issues.size ? props.length ? "partial" : "unresolved" : "resolved", issues: [...issues].sort() } };
1051
+ }
1052
+ };
1053
+ }
1054
+ });
1055
+
568
1056
  // ../core/src/analyzers/components.ts
569
- import path3 from "path";
1057
+ import path5 from "path";
570
1058
  function isUiPath(file) {
571
1059
  return /(^|\/)(components\/ui|ui\/|components\/primitives)\//.test(file);
572
1060
  }
@@ -579,7 +1067,7 @@ function isPageFile(file) {
579
1067
  function importPathFor(evidence, file, name) {
580
1068
  const aliases = evidence.componentsJson?.aliases ?? {};
581
1069
  if (aliases.ui && isUiPath(file)) {
582
- const base = path3.posix.basename(file).replace(/\.(tsx|jsx|ts|js)$/, "");
1070
+ const base = path5.posix.basename(file).replace(/\.(tsx|jsx|ts|js)$/, "");
583
1071
  return `${aliases.ui}/${base}`;
584
1072
  }
585
1073
  if (aliases.components && file.includes("components/")) {
@@ -646,18 +1134,21 @@ function analyzeComponents(evidence, tokenNames) {
646
1134
  return records.sort((a, b) => a.id.localeCompare(b.id));
647
1135
  }
648
1136
  function toRecord(evidence, component, usage, architecture, local, id, tokenNames) {
1137
+ const api = resolveComponentProps(evidence, component);
649
1138
  return {
650
1139
  id,
651
1140
  name: component.name,
652
1141
  source: component.file,
653
1142
  importPath: importPathFor(evidence, component.file, component.name),
654
1143
  exportName: component.isDefault ? "default" : component.exportName,
655
- props: component.props.map((prop) => ({
1144
+ props: api.props.map((prop) => ({
656
1145
  name: prop.name,
657
1146
  type: prop.type,
658
1147
  required: prop.required,
659
- values: prop.values
1148
+ values: prop.values,
1149
+ defaultValue: prop.defaultValue
660
1150
  })),
1151
+ propsResolution: api.resolution,
661
1152
  variants: component.variants,
662
1153
  sizes: component.sizes,
663
1154
  composition: component.composition,
@@ -668,7 +1159,7 @@ function toRecord(evidence, component, usage, architecture, local, id, tokenName
668
1159
  architecture,
669
1160
  local,
670
1161
  status: "observed",
671
- confidence: "high",
1162
+ confidence: api.resolution.status === "resolved" ? "high" : api.resolution.status === "partial" ? "medium" : "low",
672
1163
  originKind: "implementation",
673
1164
  evidence: [
674
1165
  {
@@ -677,7 +1168,13 @@ function toRecord(evidence, component, usage, architecture, local, id, tokenName
677
1168
  description: `${component.name} declared in ${component.file}`,
678
1169
  location: { file: component.file, startLine: component.startLine },
679
1170
  count: usage.length
680
- }
1171
+ },
1172
+ ...api.resolution.issues.map((description, index) => ({
1173
+ id: `${id}:api-${index}`,
1174
+ kind: "component-api-resolution",
1175
+ description,
1176
+ location: { file: component.file, startLine: component.startLine }
1177
+ }))
681
1178
  ]
682
1179
  };
683
1180
  }
@@ -688,6 +1185,7 @@ var init_components = __esm({
688
1185
  init_src();
689
1186
  init_hash();
690
1187
  init_component_resolution();
1188
+ init_type_resolution();
691
1189
  }
692
1190
  });
693
1191
 
@@ -1444,37 +1942,37 @@ var init_tokens = __esm({
1444
1942
  });
1445
1943
 
1446
1944
  // ../core/src/paths.ts
1447
- import path4 from "path";
1945
+ import path6 from "path";
1448
1946
  function specDir(root) {
1449
- return path4.join(root, SPEC_DIR);
1947
+ return path6.join(root, SPEC_DIR);
1450
1948
  }
1451
1949
  function specPaths(root) {
1452
1950
  const dir = specDir(root);
1453
1951
  return {
1454
1952
  dir,
1455
- system: path4.join(dir, "system.json"),
1456
- tokens: path4.join(dir, "tokens"),
1457
- components: path4.join(dir, "components"),
1458
- patterns: path4.join(dir, "patterns"),
1459
- intents: path4.join(dir, "intents"),
1460
- conventions: path4.join(dir, "conventions"),
1461
- exceptions: path4.join(dir, "exceptions"),
1462
- generated: path4.join(dir, "generated"),
1463
- history: path4.join(dir, "history"),
1464
- historyLog: path4.join(dir, "history", "events.jsonl"),
1465
- rejections: path4.join(dir, "rejections.json"),
1466
- baseline: path4.join(dir, "baseline.json"),
1467
- sources: path4.join(dir, "sources.json"),
1468
- discrepancies: path4.join(dir, "discrepancies.json"),
1469
- cache: path4.join(dir, "cache"),
1470
- gitignore: path4.join(dir, ".gitignore")
1953
+ system: path6.join(dir, "system.json"),
1954
+ tokens: path6.join(dir, "tokens"),
1955
+ components: path6.join(dir, "components"),
1956
+ patterns: path6.join(dir, "patterns"),
1957
+ intents: path6.join(dir, "intents"),
1958
+ conventions: path6.join(dir, "conventions"),
1959
+ exceptions: path6.join(dir, "exceptions"),
1960
+ generated: path6.join(dir, "generated"),
1961
+ history: path6.join(dir, "history"),
1962
+ historyLog: path6.join(dir, "history", "events.jsonl"),
1963
+ rejections: path6.join(dir, "rejections.json"),
1964
+ baseline: path6.join(dir, "baseline.json"),
1965
+ sources: path6.join(dir, "sources.json"),
1966
+ discrepancies: path6.join(dir, "discrepancies.json"),
1967
+ cache: path6.join(dir, "cache"),
1968
+ gitignore: path6.join(dir, ".gitignore")
1471
1969
  };
1472
1970
  }
1473
1971
  function toPosix(filePath) {
1474
- return filePath.split(path4.sep).join("/");
1972
+ return filePath.split(path6.sep).join("/");
1475
1973
  }
1476
1974
  function relativePosix(root, filePath) {
1477
- return toPosix(path4.relative(root, filePath));
1975
+ return toPosix(path6.relative(root, filePath));
1478
1976
  }
1479
1977
  var init_paths = __esm({
1480
1978
  "../core/src/paths.ts"() {
@@ -1499,7 +1997,7 @@ __export(fs_exports, {
1499
1997
  });
1500
1998
  import { execFile } from "child_process";
1501
1999
  import { promisify } from "util";
1502
- import path5 from "path";
2000
+ import path7 from "path";
1503
2001
  import fs from "fs/promises";
1504
2002
  async function isGitRepo(root) {
1505
2003
  try {
@@ -1592,7 +2090,7 @@ async function readText(filePath) {
1592
2090
  }
1593
2091
  }
1594
2092
  async function writeText(filePath, content) {
1595
- await fs.mkdir(path5.dirname(filePath), { recursive: true });
2093
+ await fs.mkdir(path7.dirname(filePath), { recursive: true });
1596
2094
  await fs.writeFile(filePath, content, "utf8");
1597
2095
  }
1598
2096
  async function readJsonFile(filePath, fallback) {
@@ -1607,7 +2105,7 @@ async function readJsonFile(filePath, fallback) {
1607
2105
  async function listJsonFiles(dir) {
1608
2106
  try {
1609
2107
  const entries = await fs.readdir(dir, { withFileTypes: true });
1610
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path5.join(dir, entry.name)).sort();
2108
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path7.join(dir, entry.name)).sort();
1611
2109
  } catch {
1612
2110
  return [];
1613
2111
  }
@@ -1624,37 +2122,37 @@ var init_fs = __esm({
1624
2122
  });
1625
2123
 
1626
2124
  // ../core/src/parse.ts
1627
- import ts from "typescript";
2125
+ import ts3 from "typescript";
1628
2126
  import postcss from "postcss";
1629
- function scriptKind(file) {
1630
- if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
1631
- if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
2127
+ function scriptKind2(file) {
2128
+ if (file.endsWith(".tsx")) return ts3.ScriptKind.TSX;
2129
+ if (file.endsWith(".jsx")) return ts3.ScriptKind.JSX;
1632
2130
  if (file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs")) {
1633
- return ts.ScriptKind.JS;
2131
+ return ts3.ScriptKind.JS;
1634
2132
  }
1635
- return ts.ScriptKind.TS;
2133
+ return ts3.ScriptKind.TS;
1636
2134
  }
1637
2135
  function lineOf(source, node) {
1638
2136
  return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
1639
2137
  }
1640
2138
  function isJsx(node) {
1641
- return ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node);
2139
+ return ts3.isJsxElement(node) || ts3.isJsxSelfClosingElement(node) || ts3.isJsxFragment(node);
1642
2140
  }
1643
2141
  function nodeReturnsJsx(node) {
1644
2142
  let found = false;
1645
2143
  const visit = (child) => {
1646
2144
  if (found) return;
1647
- if (ts.isReturnStatement(child) && child.expression && containsJsx(child.expression)) {
2145
+ if (ts3.isReturnStatement(child) && child.expression && containsJsx(child.expression)) {
1648
2146
  found = true;
1649
2147
  return;
1650
2148
  }
1651
- if (ts.isArrowFunction(child) || ts.isFunctionExpression(child) || ts.isFunctionDeclaration(child)) {
2149
+ if (ts3.isArrowFunction(child) || ts3.isFunctionExpression(child) || ts3.isFunctionDeclaration(child)) {
1652
2150
  return;
1653
2151
  }
1654
- ts.forEachChild(child, visit);
2152
+ ts3.forEachChild(child, visit);
1655
2153
  };
1656
- ts.forEachChild(node, visit);
1657
- if (ts.isArrowFunction(node) && node.body && containsJsx(node.body)) return true;
2154
+ ts3.forEachChild(node, visit);
2155
+ if (ts3.isArrowFunction(node) && node.body && containsJsx(node.body)) return true;
1658
2156
  return found;
1659
2157
  }
1660
2158
  function containsJsx(node) {
@@ -1666,9 +2164,9 @@ function containsJsx(node) {
1666
2164
  found = true;
1667
2165
  return;
1668
2166
  }
1669
- ts.forEachChild(child, visit);
2167
+ ts3.forEachChild(child, visit);
1670
2168
  };
1671
- ts.forEachChild(node, visit);
2169
+ ts3.forEachChild(node, visit);
1672
2170
  return found;
1673
2171
  }
1674
2172
  function isComponentName(name) {
@@ -1676,18 +2174,18 @@ function isComponentName(name) {
1676
2174
  }
1677
2175
  function collectClassesFromExpr(expr) {
1678
2176
  if (!expr) return { classes: [], dynamic: false };
1679
- if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
2177
+ if (ts3.isStringLiteral(expr) || ts3.isNoSubstitutionTemplateLiteral(expr)) {
1680
2178
  return { classes: splitClasses(expr.text), dynamic: false };
1681
2179
  }
1682
- if (ts.isTemplateExpression(expr)) {
2180
+ if (ts3.isTemplateExpression(expr)) {
1683
2181
  const parts = [expr.head.text, ...expr.templateSpans.map((span) => span.literal.text)];
1684
2182
  return { classes: splitClasses(parts.join(" ")), dynamic: true };
1685
2183
  }
1686
- if (ts.isCallExpression(expr)) {
2184
+ if (ts3.isCallExpression(expr)) {
1687
2185
  const name = callName(expr);
1688
2186
  if (name && CLASS_FNS.has(name)) {
1689
2187
  const merged = expr.arguments.map(
1690
- (arg) => ts.isExpression(arg) ? collectClassesFromExpr(arg) : { classes: [], dynamic: false }
2188
+ (arg) => ts3.isExpression(arg) ? collectClassesFromExpr(arg) : { classes: [], dynamic: false }
1691
2189
  );
1692
2190
  return {
1693
2191
  classes: merged.flatMap((item) => item.classes),
@@ -1696,7 +2194,7 @@ function collectClassesFromExpr(expr) {
1696
2194
  }
1697
2195
  return { classes: [], dynamic: true };
1698
2196
  }
1699
- if (ts.isConditionalExpression(expr)) {
2197
+ if (ts3.isConditionalExpression(expr)) {
1700
2198
  const left = collectClassesFromExpr(expr.whenTrue);
1701
2199
  const right = collectClassesFromExpr(expr.whenFalse);
1702
2200
  return {
@@ -1704,7 +2202,7 @@ function collectClassesFromExpr(expr) {
1704
2202
  dynamic: true
1705
2203
  };
1706
2204
  }
1707
- if (ts.isBinaryExpression(expr)) {
2205
+ if (ts3.isBinaryExpression(expr)) {
1708
2206
  const left = collectClassesFromExpr(expr.left);
1709
2207
  const right = collectClassesFromExpr(expr.right);
1710
2208
  return {
@@ -1712,13 +2210,13 @@ function collectClassesFromExpr(expr) {
1712
2210
  dynamic: left.dynamic || right.dynamic
1713
2211
  };
1714
2212
  }
1715
- if (ts.isParenthesizedExpression(expr)) {
2213
+ if (ts3.isParenthesizedExpression(expr)) {
1716
2214
  return collectClassesFromExpr(expr.expression);
1717
2215
  }
1718
- if (ts.isObjectLiteralExpression(expr)) {
2216
+ if (ts3.isObjectLiteralExpression(expr)) {
1719
2217
  const classes = [];
1720
2218
  for (const prop of expr.properties) {
1721
- if (ts.isPropertyAssignment(prop) && ts.isStringLiteral(prop.initializer)) {
2219
+ if (ts3.isPropertyAssignment(prop) && ts3.isStringLiteral(prop.initializer)) {
1722
2220
  classes.push(...splitClasses(prop.initializer.text));
1723
2221
  }
1724
2222
  }
@@ -1727,17 +2225,17 @@ function collectClassesFromExpr(expr) {
1727
2225
  return { classes: [], dynamic: true };
1728
2226
  }
1729
2227
  function callName(expr) {
1730
- if (ts.isIdentifier(expr.expression)) return expr.expression.text;
1731
- if (ts.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
2228
+ if (ts3.isIdentifier(expr.expression)) return expr.expression.text;
2229
+ if (ts3.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
1732
2230
  return void 0;
1733
2231
  }
1734
2232
  function unionValues(typeNode) {
1735
2233
  if (!typeNode) return void 0;
1736
- if (ts.isUnionTypeNode(typeNode)) {
1737
- const values = typeNode.types.map((item) => ts.isLiteralTypeNode(item) && ts.isStringLiteral(item.literal) ? item.literal.text : void 0).filter((item) => Boolean(item));
2234
+ if (ts3.isUnionTypeNode(typeNode)) {
2235
+ const values = typeNode.types.map((item) => ts3.isLiteralTypeNode(item) && ts3.isStringLiteral(item.literal) ? item.literal.text : void 0).filter((item) => Boolean(item));
1738
2236
  return values.length ? values : void 0;
1739
2237
  }
1740
- if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
2238
+ if (ts3.isLiteralTypeNode(typeNode) && ts3.isStringLiteral(typeNode.literal)) {
1741
2239
  return [typeNode.literal.text];
1742
2240
  }
1743
2241
  return void 0;
@@ -1745,7 +2243,7 @@ function unionValues(typeNode) {
1745
2243
  function propsFromTypeLiteral(literal) {
1746
2244
  const props = [];
1747
2245
  for (const member of literal.members) {
1748
- if (!ts.isPropertySignature(member) || !member.name) continue;
2246
+ if (!ts3.isPropertySignature(member) || !member.name) continue;
1749
2247
  const name = member.name.getText();
1750
2248
  props.push({
1751
2249
  name,
@@ -1758,22 +2256,22 @@ function propsFromTypeLiteral(literal) {
1758
2256
  }
1759
2257
  function findInterface(source, name) {
1760
2258
  for (const stmt of source.statements) {
1761
- if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === name) return stmt;
1762
- if (ts.isTypeAliasDeclaration(stmt) && stmt.name.text === name) return stmt;
2259
+ if (ts3.isInterfaceDeclaration(stmt) && stmt.name.text === name) return stmt;
2260
+ if (ts3.isTypeAliasDeclaration(stmt) && stmt.name.text === name) return stmt;
1763
2261
  }
1764
2262
  return void 0;
1765
2263
  }
1766
2264
  function propsFromType(source, typeNode) {
1767
2265
  if (!typeNode) return [];
1768
- if (ts.isTypeLiteralNode(typeNode)) return propsFromTypeLiteral(typeNode);
1769
- if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
2266
+ if (ts3.isTypeLiteralNode(typeNode)) return propsFromTypeLiteral(typeNode);
2267
+ if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
1770
2268
  const decl = findInterface(source, typeNode.typeName.text);
1771
2269
  if (!decl) return [];
1772
- if (ts.isInterfaceDeclaration(decl)) {
1773
- const fake = ts.factory.createTypeLiteralNode(decl.members);
2270
+ if (ts3.isInterfaceDeclaration(decl)) {
2271
+ const fake = ts3.factory.createTypeLiteralNode(decl.members);
1774
2272
  return propsFromTypeLiteral(fake);
1775
2273
  }
1776
- if (ts.isTypeAliasDeclaration(decl) && decl.type && ts.isTypeLiteralNode(decl.type)) {
2274
+ if (ts3.isTypeAliasDeclaration(decl) && decl.type && ts3.isTypeLiteralNode(decl.type)) {
1777
2275
  return propsFromTypeLiteral(decl.type);
1778
2276
  }
1779
2277
  }
@@ -1783,7 +2281,7 @@ function propsFromFunction(source, fn) {
1783
2281
  const first = fn.parameters[0];
1784
2282
  if (!first) return [];
1785
2283
  if (first.type) return propsFromType(source, first.type);
1786
- if (ts.isObjectBindingPattern(first.name)) {
2284
+ if (ts3.isObjectBindingPattern(first.name)) {
1787
2285
  return first.name.elements.map((el) => ({
1788
2286
  name: el.name.getText(),
1789
2287
  required: !el.dotDotDotToken
@@ -1791,15 +2289,15 @@ function propsFromFunction(source, fn) {
1791
2289
  }
1792
2290
  return [];
1793
2291
  }
1794
- function unwrap(node) {
1795
- if (ts.isCallExpression(node)) {
2292
+ function unwrap2(node) {
2293
+ if (ts3.isCallExpression(node)) {
1796
2294
  const name = callName(node);
1797
2295
  if (name === "forwardRef" || name === "memo") {
1798
2296
  const arg = node.arguments[0];
1799
- if (arg && ts.isExpression(arg)) return unwrap(arg);
2297
+ if (arg && ts3.isExpression(arg)) return unwrap2(arg);
1800
2298
  }
1801
2299
  }
1802
- if (ts.isParenthesizedExpression(node)) return unwrap(node.expression);
2300
+ if (ts3.isParenthesizedExpression(node)) return unwrap2(node.expression);
1803
2301
  return node;
1804
2302
  }
1805
2303
  function extractCva(node) {
@@ -1807,24 +2305,24 @@ function extractCva(node) {
1807
2305
  const sizes = [];
1808
2306
  const classes = [];
1809
2307
  const base = node.arguments[0];
1810
- if (base && ts.isExpression(base)) {
2308
+ if (base && ts3.isExpression(base)) {
1811
2309
  classes.push(...collectClassesFromExpr(base).classes);
1812
2310
  }
1813
2311
  const config = node.arguments[1];
1814
- if (config && ts.isObjectLiteralExpression(config)) {
2312
+ if (config && ts3.isObjectLiteralExpression(config)) {
1815
2313
  for (const prop of config.properties) {
1816
- if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
1817
- if (prop.name.text !== "variants" || !ts.isObjectLiteralExpression(prop.initializer)) continue;
2314
+ if (!ts3.isPropertyAssignment(prop) || !ts3.isIdentifier(prop.name)) continue;
2315
+ if (prop.name.text !== "variants" || !ts3.isObjectLiteralExpression(prop.initializer)) continue;
1818
2316
  for (const group of prop.initializer.properties) {
1819
- if (!ts.isPropertyAssignment(group) || !ts.isIdentifier(group.name)) continue;
2317
+ if (!ts3.isPropertyAssignment(group) || !ts3.isIdentifier(group.name)) continue;
1820
2318
  const groupName = group.name.text;
1821
- if (!ts.isObjectLiteralExpression(group.initializer)) continue;
1822
- const keys = group.initializer.properties.map((item) => ts.isPropertyAssignment(item) ? item.name.getText().replace(/['"]/g, "") : void 0).filter((item) => Boolean(item));
2319
+ if (!ts3.isObjectLiteralExpression(group.initializer)) continue;
2320
+ const keys = group.initializer.properties.map((item) => ts3.isPropertyAssignment(item) ? item.name.getText().replace(/['"]/g, "") : void 0).filter((item) => Boolean(item));
1823
2321
  if (groupName === "size") sizes.push(...keys);
1824
2322
  else if (groupName === "variant") variants.push(...keys);
1825
2323
  else variants.push(...keys.map((key) => `${groupName}:${key}`));
1826
2324
  for (const item of group.initializer.properties) {
1827
- if (ts.isPropertyAssignment(item) && ts.isExpression(item.initializer)) {
2325
+ if (ts3.isPropertyAssignment(item) && ts3.isExpression(item.initializer)) {
1828
2326
  classes.push(...collectClassesFromExpr(item.initializer).classes);
1829
2327
  }
1830
2328
  }
@@ -1839,11 +2337,11 @@ function tagName(node) {
1839
2337
  function jsxAttributes(node) {
1840
2338
  const attrs = {};
1841
2339
  for (const attr of node.properties) {
1842
- if (!ts.isJsxAttribute(attr) || !attr.initializer) continue;
2340
+ if (!ts3.isJsxAttribute(attr) || !attr.initializer) continue;
1843
2341
  const name = attr.name.getText();
1844
- if (ts.isStringLiteral(attr.initializer)) attrs[name] = attr.initializer.text;
1845
- else if (ts.isJsxExpression(attr.initializer) && attr.initializer.expression) {
1846
- if (ts.isStringLiteral(attr.initializer.expression)) {
2342
+ if (ts3.isStringLiteral(attr.initializer)) attrs[name] = attr.initializer.text;
2343
+ else if (ts3.isJsxExpression(attr.initializer) && attr.initializer.expression) {
2344
+ if (ts3.isStringLiteral(attr.initializer.expression)) {
1847
2345
  attrs[name] = attr.initializer.expression.text;
1848
2346
  }
1849
2347
  }
@@ -1851,28 +2349,28 @@ function jsxAttributes(node) {
1851
2349
  return attrs;
1852
2350
  }
1853
2351
  function opening(node) {
1854
- return ts.isJsxElement(node) ? node.openingElement : node;
2352
+ return ts3.isJsxElement(node) ? node.openingElement : node;
1855
2353
  }
1856
2354
  function walkJsxTree(node, tags, components, families, depth) {
1857
2355
  let childCount = 0;
1858
2356
  let maxDepth = depth;
1859
- const children = ts.isJsxFragment(node) ? node.children : ts.isJsxElement(node) ? node.children : [];
1860
- if (!ts.isJsxFragment(node)) {
2357
+ const children = ts3.isJsxFragment(node) ? node.children : ts3.isJsxElement(node) ? node.children : [];
2358
+ if (!ts3.isJsxFragment(node)) {
1861
2359
  const open = opening(node);
1862
2360
  const name = tagName(open.tagName);
1863
2361
  if (isComponentName(name)) components.push(name);
1864
2362
  else tags.push(name);
1865
2363
  const classAttr = open.attributes.properties.find(
1866
- (prop) => ts.isJsxAttribute(prop) && prop.name.getText() === "className"
2364
+ (prop) => ts3.isJsxAttribute(prop) && prop.name.getText() === "className"
1867
2365
  );
1868
- if (classAttr && ts.isJsxAttribute(classAttr)) {
2366
+ if (classAttr && ts3.isJsxAttribute(classAttr)) {
1869
2367
  const expr = classAttr.initializer;
1870
- const collected = expr && ts.isJsxExpression(expr) ? collectClassesFromExpr(expr.expression) : expr && ts.isStringLiteral(expr) ? { classes: splitClasses(expr.text), dynamic: false } : { classes: [], dynamic: false };
2368
+ const collected = expr && ts3.isJsxExpression(expr) ? collectClassesFromExpr(expr.expression) : expr && ts3.isStringLiteral(expr) ? { classes: splitClasses(expr.text), dynamic: false } : { classes: [], dynamic: false };
1871
2369
  for (const cls of collected.classes) families.add(classFamily(cls));
1872
2370
  }
1873
2371
  }
1874
2372
  for (const child of children) {
1875
- if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
2373
+ if (ts3.isJsxElement(child) || ts3.isJsxSelfClosingElement(child) || ts3.isJsxFragment(child)) {
1876
2374
  childCount += 1;
1877
2375
  const nested = walkJsxTree(child, tags, components, families, depth + 1);
1878
2376
  childCount += nested.childCount;
@@ -1887,25 +2385,25 @@ function variantsOwnerName(name) {
1887
2385
  return `${base[0].toUpperCase()}${base.slice(1)}`;
1888
2386
  }
1889
2387
  function findComponent(components, name) {
1890
- const owner = variantsOwnerName(name);
2388
+ const owner2 = variantsOwnerName(name);
1891
2389
  return components.find(
1892
- (item) => item.name === name || item.name === owner || item.name.toLowerCase() === name.toLowerCase()
2390
+ (item) => item.name === name || item.name === owner2 || item.name.toLowerCase() === name.toLowerCase()
1893
2391
  );
1894
2392
  }
1895
2393
  function headingFrom(node) {
1896
2394
  let heading;
1897
2395
  const visit = (child) => {
1898
2396
  if (heading) return;
1899
- if (ts.isJsxElement(child)) {
2397
+ if (ts3.isJsxElement(child)) {
1900
2398
  const name = tagName(child.openingElement.tagName);
1901
2399
  if (/^h[1-3]$/.test(name)) {
1902
- heading = child.children.map((item) => ts.isJsxText(item) ? item.getText() : "").join("").trim();
2400
+ heading = child.children.map((item) => ts3.isJsxText(item) ? item.getText() : "").join("").trim();
1903
2401
  return;
1904
2402
  }
1905
2403
  }
1906
- ts.forEachChild(child, visit);
2404
+ ts3.forEachChild(child, visit);
1907
2405
  };
1908
- ts.forEachChild(node, visit);
2406
+ ts3.forEachChild(node, visit);
1909
2407
  return heading || void 0;
1910
2408
  }
1911
2409
  function isPlaceholderSrc(src) {
@@ -1931,14 +2429,14 @@ function cssVariables(content, file) {
1931
2429
  }
1932
2430
  function commentsFromSource(source, relative) {
1933
2431
  const hits = [];
1934
- const trivia = ts.getLeadingCommentRanges(source.text, 0) ?? [];
2432
+ const trivia = ts3.getLeadingCommentRanges(source.text, 0) ?? [];
1935
2433
  const ranges = [...trivia];
1936
2434
  const visit = (node) => {
1937
- const leading = ts.getLeadingCommentRanges(source.text, node.pos) ?? [];
2435
+ const leading = ts3.getLeadingCommentRanges(source.text, node.pos) ?? [];
1938
2436
  ranges.push(...leading);
1939
- ts.forEachChild(node, visit);
2437
+ ts3.forEachChild(node, visit);
1940
2438
  };
1941
- ts.forEachChild(source, visit);
2439
+ ts3.forEachChild(source, visit);
1942
2440
  const seen = /* @__PURE__ */ new Set();
1943
2441
  for (const range of ranges) {
1944
2442
  if (seen.has(range.pos)) continue;
@@ -1973,12 +2471,12 @@ function extractFileEvidence(input) {
1973
2471
  empty.comments = input.content.split("\n").map((text, index) => ({ file: input.relative, text, startLine: index + 1 })).filter((item) => /TODO|FIXME|placeholder/i.test(item.text));
1974
2472
  return empty;
1975
2473
  }
1976
- const source = ts.createSourceFile(
2474
+ const source = ts3.createSourceFile(
1977
2475
  input.relative,
1978
2476
  input.content,
1979
- ts.ScriptTarget.Latest,
2477
+ ts3.ScriptTarget.Latest,
1980
2478
  true,
1981
- scriptKind(input.relative)
2479
+ scriptKind2(input.relative)
1982
2480
  );
1983
2481
  const components = [];
1984
2482
  const classes = [];
@@ -1989,29 +2487,29 @@ function extractFileEvidence(input) {
1989
2487
  const exportedClassConstants = [];
1990
2488
  const interfaces = /* @__PURE__ */ new Map();
1991
2489
  for (const stmt of source.statements) {
1992
- if (ts.isInterfaceDeclaration(stmt)) {
1993
- interfaces.set(stmt.name.text, propsFromTypeLiteral(ts.factory.createTypeLiteralNode(stmt.members)));
2490
+ if (ts3.isInterfaceDeclaration(stmt)) {
2491
+ interfaces.set(stmt.name.text, propsFromTypeLiteral(ts3.factory.createTypeLiteralNode(stmt.members)));
1994
2492
  }
1995
2493
  }
1996
2494
  function maybeComponent(name, node, exported, isDefault, start) {
1997
2495
  if (!isComponentName(name)) return;
1998
- const fn = ts.isFunctionLike(node) ? node : void 0;
1999
- const expr = !fn && ts.isExpression(node) ? unwrap(node) : node;
2496
+ const fn = ts3.isFunctionLike(node) ? node : void 0;
2497
+ const expr = !fn && ts3.isExpression(node) ? unwrap2(node) : node;
2000
2498
  const bodyNode = fn ?? expr;
2001
- if (!nodeReturnsJsx(bodyNode) && !(ts.isFunctionLike(bodyNode) && bodyNode.body && containsJsx(bodyNode.body))) {
2002
- if (!(ts.isArrowFunction(expr) && expr.body && containsJsx(expr.body))) return;
2499
+ if (!nodeReturnsJsx(bodyNode) && !(ts3.isFunctionLike(bodyNode) && bodyNode.body && containsJsx(bodyNode.body))) {
2500
+ if (!(ts3.isArrowFunction(expr) && expr.body && containsJsx(expr.body))) return;
2003
2501
  }
2004
- const props = fn ? propsFromFunction(source, fn) : ts.isFunctionLike(expr) ? propsFromFunction(source, expr) : [];
2502
+ const props = fn ? propsFromFunction(source, fn) : ts3.isFunctionLike(expr) ? propsFromFunction(source, expr) : [];
2005
2503
  const composition = /* @__PURE__ */ new Set();
2006
2504
  const classNames = [];
2007
2505
  const visitComp = (child) => {
2008
- if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child)) {
2506
+ if (ts3.isJsxOpeningElement(child) || ts3.isJsxSelfClosingElement(child)) {
2009
2507
  const tag = tagName(child.tagName);
2010
2508
  if (isComponentName(tag)) composition.add(tag);
2011
2509
  }
2012
- ts.forEachChild(child, visitComp);
2510
+ ts3.forEachChild(child, visitComp);
2013
2511
  };
2014
- ts.forEachChild(bodyNode, visitComp);
2512
+ ts3.forEachChild(bodyNode, visitComp);
2015
2513
  components.push({
2016
2514
  name,
2017
2515
  file: input.relative,
@@ -2028,7 +2526,7 @@ function extractFileEvidence(input) {
2028
2526
  }
2029
2527
  const cvaPending = /* @__PURE__ */ new Map();
2030
2528
  function visit(node) {
2031
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
2529
+ if (ts3.isImportDeclaration(node) && ts3.isStringLiteral(node.moduleSpecifier)) {
2032
2530
  const names = [];
2033
2531
  const importedBindings = [];
2034
2532
  let defaultName;
@@ -2038,13 +2536,13 @@ function extractFileEvidence(input) {
2038
2536
  importedBindings.push({ local: defaultName, imported: "default" });
2039
2537
  }
2040
2538
  const bindings = clause?.namedBindings;
2041
- if (bindings && ts.isNamedImports(bindings)) {
2539
+ if (bindings && ts3.isNamedImports(bindings)) {
2042
2540
  for (const el of bindings.elements) {
2043
2541
  names.push(el.name.text);
2044
- importedBindings.push({ local: el.name.text, imported: el.propertyName?.text ?? el.name.text });
2542
+ importedBindings.push({ local: el.name.text, imported: el.propertyName?.text ?? el.name.text, isTypeOnly: el.isTypeOnly || void 0 });
2045
2543
  }
2046
2544
  }
2047
- if (bindings && ts.isNamespaceImport(bindings)) importedBindings.push({ local: bindings.name.text, imported: "*" });
2545
+ if (bindings && ts3.isNamespaceImport(bindings)) importedBindings.push({ local: bindings.name.text, imported: "*" });
2048
2546
  imports.push({
2049
2547
  file: input.relative,
2050
2548
  source: node.moduleSpecifier.text,
@@ -2054,16 +2552,16 @@ function extractFileEvidence(input) {
2054
2552
  isTypeOnly: Boolean(clause?.isTypeOnly)
2055
2553
  });
2056
2554
  }
2057
- if (ts.isFunctionDeclaration(node) && node.name) {
2058
- const exported = Boolean(node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword));
2059
- maybeComponent(node.name.text, node, exported, Boolean(node.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)), node);
2555
+ if (ts3.isFunctionDeclaration(node) && node.name) {
2556
+ const exported = Boolean(node.modifiers?.some((m) => m.kind === ts3.SyntaxKind.ExportKeyword));
2557
+ maybeComponent(node.name.text, node, exported, Boolean(node.modifiers?.some((m) => m.kind === ts3.SyntaxKind.DefaultKeyword)), node);
2060
2558
  }
2061
- if (ts.isVariableStatement(node)) {
2062
- const exported = Boolean(node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword));
2559
+ if (ts3.isVariableStatement(node)) {
2560
+ const exported = Boolean(node.modifiers?.some((m) => m.kind === ts3.SyntaxKind.ExportKeyword));
2063
2561
  for (const decl of node.declarationList.declarations) {
2064
- if (!ts.isIdentifier(decl.name) || !decl.initializer) continue;
2562
+ if (!ts3.isIdentifier(decl.name) || !decl.initializer) continue;
2065
2563
  const name = decl.name.text;
2066
- if (/class|classes|className/i.test(name) && (ts.isStringLiteral(decl.initializer) || ts.isNoSubstitutionTemplateLiteral(decl.initializer))) {
2564
+ if (/class|classes|className/i.test(name) && (ts3.isStringLiteral(decl.initializer) || ts3.isNoSubstitutionTemplateLiteral(decl.initializer))) {
2067
2565
  exportedClassConstants.push(name);
2068
2566
  classes.push({
2069
2567
  file: input.relative,
@@ -2074,39 +2572,39 @@ function extractFileEvidence(input) {
2074
2572
  startLine: lineOf(source, decl)
2075
2573
  });
2076
2574
  }
2077
- const init = unwrap(decl.initializer);
2078
- if (ts.isCallExpression(decl.initializer) && callName(decl.initializer) && CVA_NAMES.has(callName(decl.initializer))) {
2575
+ const init = unwrap2(decl.initializer);
2576
+ if (ts3.isCallExpression(decl.initializer) && callName(decl.initializer) && CVA_NAMES.has(callName(decl.initializer))) {
2079
2577
  const extracted = extractCva(decl.initializer);
2080
- const owner = variantsOwnerName(name);
2578
+ const owner2 = variantsOwnerName(name);
2081
2579
  classes.push({
2082
2580
  file: input.relative,
2083
2581
  classes: extracted.classes,
2084
2582
  dynamic: false,
2085
2583
  exported,
2086
2584
  kind: "cva",
2087
- componentName: owner,
2585
+ componentName: owner2,
2088
2586
  startLine: lineOf(source, decl)
2089
2587
  });
2090
- const existing = findComponent(components, owner);
2588
+ const existing = findComponent(components, owner2);
2091
2589
  if (existing) {
2092
2590
  existing.variants = extracted.variants;
2093
2591
  existing.sizes = extracted.sizes;
2094
2592
  existing.classNames.push(...extracted.classes);
2095
2593
  } else {
2096
- cvaPending.set(owner, extracted);
2594
+ cvaPending.set(owner2, extracted);
2097
2595
  }
2098
2596
  }
2099
2597
  maybeComponent(name, init, exported, false, decl);
2100
2598
  }
2101
2599
  }
2102
- if (ts.isExportAssignment(node) && ts.isIdentifier(node.expression)) {
2600
+ if (ts3.isExportAssignment(node) && ts3.isIdentifier(node.expression)) {
2103
2601
  const target = components.find((item) => item.name === node.expression.getText());
2104
2602
  if (target) {
2105
2603
  target.isDefault = true;
2106
2604
  target.exported = true;
2107
2605
  }
2108
2606
  }
2109
- if (ts.isCallExpression(node) && callName(node) && CVA_NAMES.has(callName(node))) {
2607
+ if (ts3.isCallExpression(node) && callName(node) && CVA_NAMES.has(callName(node))) {
2110
2608
  const extracted = extractCva(node);
2111
2609
  classes.push({
2112
2610
  file: input.relative,
@@ -2117,8 +2615,8 @@ function extractFileEvidence(input) {
2117
2615
  startLine: lineOf(source, node)
2118
2616
  });
2119
2617
  }
2120
- if (ts.isJsxAttribute(node) && node.name.getText() === "className") {
2121
- const collected = node.initializer && ts.isJsxExpression(node.initializer) ? collectClassesFromExpr(node.initializer.expression) : node.initializer && ts.isStringLiteral(node.initializer) ? { classes: splitClasses(node.initializer.text), dynamic: false } : { classes: [], dynamic: false };
2618
+ if (ts3.isJsxAttribute(node) && node.name.getText() === "className") {
2619
+ const collected = node.initializer && ts3.isJsxExpression(node.initializer) ? collectClassesFromExpr(node.initializer.expression) : node.initializer && ts3.isStringLiteral(node.initializer) ? { classes: splitClasses(node.initializer.text), dynamic: false } : { classes: [], dynamic: false };
2122
2620
  if (collected.classes.length) {
2123
2621
  classes.push({
2124
2622
  file: input.relative,
@@ -2130,14 +2628,14 @@ function extractFileEvidence(input) {
2130
2628
  });
2131
2629
  }
2132
2630
  }
2133
- if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
2631
+ if (ts3.isJsxElement(node) || ts3.isJsxSelfClosingElement(node)) {
2134
2632
  const open = opening(node);
2135
2633
  const tag = tagName(open.tagName);
2136
2634
  const attrs = jsxAttributes(open.attributes);
2137
2635
  const classAttr = open.attributes.properties.find(
2138
- (prop) => ts.isJsxAttribute(prop) && prop.name.getText() === "className"
2636
+ (prop) => ts3.isJsxAttribute(prop) && prop.name.getText() === "className"
2139
2637
  );
2140
- const collected = classAttr && ts.isJsxAttribute(classAttr) ? classAttr.initializer && ts.isJsxExpression(classAttr.initializer) ? collectClassesFromExpr(classAttr.initializer.expression) : classAttr.initializer && ts.isStringLiteral(classAttr.initializer) ? { classes: splitClasses(classAttr.initializer.text), dynamic: false } : { classes: [], dynamic: false } : { classes: [], dynamic: false };
2638
+ const collected = classAttr && ts3.isJsxAttribute(classAttr) ? classAttr.initializer && ts3.isJsxExpression(classAttr.initializer) ? collectClassesFromExpr(classAttr.initializer.expression) : classAttr.initializer && ts3.isStringLiteral(classAttr.initializer) ? { classes: splitClasses(classAttr.initializer.text), dynamic: false } : { classes: [], dynamic: false } : { classes: [], dynamic: false };
2141
2639
  jsx.push({
2142
2640
  file: input.relative,
2143
2641
  tag,
@@ -2145,7 +2643,7 @@ function extractFileEvidence(input) {
2145
2643
  classes: collected.classes,
2146
2644
  attributes: attrs,
2147
2645
  startLine: lineOf(source, node),
2148
- selfClosing: ts.isJsxSelfClosingElement(node)
2646
+ selfClosing: ts3.isJsxSelfClosingElement(node)
2149
2647
  });
2150
2648
  if (tag === "img" || tag === "Image") {
2151
2649
  images.push({
@@ -2168,7 +2666,7 @@ function extractFileEvidence(input) {
2168
2666
  });
2169
2667
  }
2170
2668
  const structural = !isComponentName(tag) && (tag === "section" || tag === "article" || tag === "header" || tag === "aside" || tag === "main" || tag === "div" && collected.classes.some((cls) => /^(flex|grid|space-y|rounded|border)/.test(cls)));
2171
- if (structural && ts.isJsxElement(node)) {
2669
+ if (structural && ts3.isJsxElement(node)) {
2172
2670
  const tags = [];
2173
2671
  const comps = [];
2174
2672
  const families = /* @__PURE__ */ new Set();
@@ -2187,7 +2685,7 @@ function extractFileEvidence(input) {
2187
2685
  }
2188
2686
  }
2189
2687
  }
2190
- ts.forEachChild(node, visit);
2688
+ ts3.forEachChild(node, visit);
2191
2689
  }
2192
2690
  visit(source);
2193
2691
  for (const [name, extracted] of cvaPending) {
@@ -2199,8 +2697,8 @@ function extractFileEvidence(input) {
2199
2697
  }
2200
2698
  }
2201
2699
  for (const stmt of source.statements) {
2202
- if (ts.isFunctionDeclaration(stmt) && stmt.name && isComponentName(stmt.name.text) && nodeReturnsJsx(stmt)) {
2203
- const exported = Boolean(stmt.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword));
2700
+ if (ts3.isFunctionDeclaration(stmt) && stmt.name && isComponentName(stmt.name.text) && nodeReturnsJsx(stmt)) {
2701
+ const exported = Boolean(stmt.modifiers?.some((m) => m.kind === ts3.SyntaxKind.ExportKeyword));
2204
2702
  if (!exported && /page|layout|route/i.test(input.relative)) {
2205
2703
  const tags = [];
2206
2704
  const comps = [];
@@ -2237,13 +2735,13 @@ function extractFileEvidence(input) {
2237
2735
  }
2238
2736
  function walkJsxTreeFromBody(body, tags, components, families) {
2239
2737
  const visit = (node) => {
2240
- if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) {
2738
+ if (ts3.isJsxElement(node) || ts3.isJsxSelfClosingElement(node) || ts3.isJsxFragment(node)) {
2241
2739
  walkJsxTree(node, tags, components, families, 1);
2242
2740
  return;
2243
2741
  }
2244
- ts.forEachChild(node, visit);
2742
+ ts3.forEachChild(node, visit);
2245
2743
  };
2246
- ts.forEachChild(body, visit);
2744
+ ts3.forEachChild(body, visit);
2247
2745
  }
2248
2746
  var CVA_NAMES, CLASS_FNS, PASCAL;
2249
2747
  var init_parse = __esm({
@@ -2259,13 +2757,14 @@ var init_parse = __esm({
2259
2757
 
2260
2758
  // ../core/src/scan.ts
2261
2759
  import fs2 from "fs/promises";
2262
- import path6 from "path";
2760
+ import path8 from "path";
2263
2761
  import fg from "fast-glob";
2264
2762
  import ignore from "ignore";
2763
+ import ts4 from "typescript";
2265
2764
  async function discoverFiles(root) {
2266
2765
  const ig = ignore();
2267
2766
  ig.add(DEFAULT_IGNORE);
2268
- const gitignore = await readText(path6.join(root, ".gitignore"));
2767
+ const gitignore = await readText(path8.join(root, ".gitignore"));
2269
2768
  if (gitignore) ig.add(gitignore);
2270
2769
  const found = await fg(
2271
2770
  [
@@ -2291,7 +2790,7 @@ async function discoverFiles(root) {
2291
2790
  return found.map((file) => toPosix(file)).filter((file) => !ig.ignores(file)).sort();
2292
2791
  }
2293
2792
  async function loadPackageJson(root) {
2294
- const raw = await readText(path6.join(root, "package.json"));
2793
+ const raw = await readText(path8.join(root, "package.json"));
2295
2794
  if (!raw) return void 0;
2296
2795
  try {
2297
2796
  return JSON.parse(raw);
@@ -2300,7 +2799,7 @@ async function loadPackageJson(root) {
2300
2799
  }
2301
2800
  }
2302
2801
  async function loadComponentsJson(root) {
2303
- const raw = await readText(path6.join(root, "components.json"));
2802
+ const raw = await readText(path8.join(root, "components.json"));
2304
2803
  if (!raw) return void 0;
2305
2804
  try {
2306
2805
  return JSON.parse(raw);
@@ -2309,11 +2808,13 @@ async function loadComponentsJson(root) {
2309
2808
  }
2310
2809
  }
2311
2810
  async function loadTsconfigPaths(root) {
2312
- const raw = await readText(path6.join(root, "tsconfig.json"));
2811
+ const raw = await readText(path8.join(root, "tsconfig.json"));
2313
2812
  if (!raw) return {};
2314
2813
  try {
2315
- const parsed = JSON.parse(raw);
2316
- return parsed.compilerOptions?.paths ?? {};
2814
+ const parsed = ts4.parseConfigFileTextToJson("tsconfig.json", raw).config;
2815
+ const paths = parsed?.compilerOptions?.paths;
2816
+ if (!paths || typeof paths !== "object" || Array.isArray(paths)) return {};
2817
+ return Object.fromEntries(Object.entries(paths).filter((entry) => Array.isArray(entry[1]) && entry[1].every((value) => typeof value === "string")));
2317
2818
  } catch {
2318
2819
  return {};
2319
2820
  }
@@ -2329,9 +2830,9 @@ async function collectEvidence(root, options = {}) {
2329
2830
  const files = [];
2330
2831
  const excluded = ignore().add(DEFAULT_IGNORE);
2331
2832
  for (const relative of relativeFiles) {
2332
- const abs = path6.join(root, relative);
2333
- const fromRoot = path6.relative(path6.resolve(root), path6.resolve(abs));
2334
- if (fromRoot === ".." || fromRoot.startsWith(`..${path6.sep}`) || path6.isAbsolute(fromRoot)) {
2833
+ const abs = path8.join(root, relative);
2834
+ const fromRoot = path8.relative(path8.resolve(root), path8.resolve(abs));
2835
+ if (fromRoot === ".." || fromRoot.startsWith(`..${path8.sep}`) || path8.isAbsolute(fromRoot)) {
2335
2836
  throw new Error(`Source path is outside the repository: ${relative}`);
2336
2837
  }
2337
2838
  if (excluded.ignores(relative)) continue;
@@ -2346,7 +2847,7 @@ async function collectEvidence(root, options = {}) {
2346
2847
  content = content.replace(/<!-- design-rover:start -->[\s\S]*?<!-- design-rover:end -->/g, (block) => "\n".repeat(block.split("\n").length - 1));
2347
2848
  if (!content.trim()) continue;
2348
2849
  }
2349
- const ext = path6.extname(relative);
2850
+ const ext = path8.extname(relative);
2350
2851
  const kind = SCRIPT_EXT.has(ext) ? "script" : STYLE_EXT.has(ext) || relative.endsWith(".module.css") ? "style" : relative.endsWith(".json") || relative.endsWith(".md") || relative.endsWith(".mdc") ? "json" : "other";
2351
2852
  const normalizedKind = kind === "script" || kind === "style" ? kind : "other";
2352
2853
  const hash = sha256(content);
@@ -2404,12 +2905,12 @@ var init_scan = __esm({
2404
2905
  });
2405
2906
 
2406
2907
  // ../core/src/cache.ts
2407
- import path7 from "path";
2908
+ import path9 from "path";
2408
2909
  import fs3 from "fs/promises";
2409
2910
  import { randomUUID } from "crypto";
2410
2911
  import { z as z2 } from "zod";
2411
2912
  async function loadCache(root) {
2412
- const file = path7.join(specPaths(root).cache, "evidence.json");
2913
+ const file = path9.join(specPaths(root).cache, "evidence.json");
2413
2914
  const raw = await readText(file);
2414
2915
  if (!raw) return void 0;
2415
2916
  try {
@@ -2417,7 +2918,7 @@ async function loadCache(root) {
2417
2918
  if (!parsed.success) return void 0;
2418
2919
  const paths = parsed.data.files.map((item) => item.relative);
2419
2920
  if (new Set(paths).size !== paths.length) return void 0;
2420
- if (paths.some((relative) => path7.isAbsolute(relative) || relative.split(/[\\/]/).includes(".."))) return void 0;
2921
+ if (paths.some((relative) => path9.isAbsolute(relative) || relative.split(/[\\/]/).includes(".."))) return void 0;
2421
2922
  if (parsed.data.files.some((item) => item.hash !== sha256(item.content))) return void 0;
2422
2923
  if (parsed.data.hash !== sha256(parsed.data.files.map((item) => `${item.relative}\0${item.hash}`).join("\0"))) return void 0;
2423
2924
  return parsed.data;
@@ -2427,9 +2928,9 @@ async function loadCache(root) {
2427
2928
  }
2428
2929
  async function saveCache(root, cache) {
2429
2930
  const directory = specPaths(root).cache;
2430
- const file = path7.join(directory, "evidence.json");
2931
+ const file = path9.join(directory, "evidence.json");
2431
2932
  await fs3.mkdir(directory, { recursive: true });
2432
- const temporary = path7.join(directory, `evidence-${randomUUID()}.tmp`);
2933
+ const temporary = path9.join(directory, `evidence-${randomUUID()}.tmp`);
2433
2934
  try {
2434
2935
  await fs3.writeFile(temporary, `${JSON.stringify(cache)}
2435
2936
  `, "utf8");
@@ -2476,7 +2977,7 @@ var init_cache = __esm({
2476
2977
  cssVariables: z2.array(z2.object({ name: z2.string(), value: z2.string(), line: z2.number().int().optional(), scope: z2.string().optional(), mode: z2.string().optional(), important: z2.boolean().optional() }).passthrough()),
2477
2978
  exportedClassConstants: strings
2478
2979
  }).passthrough();
2479
- cacheSchema = z2.object({ version: z2.literal(3), hash: z2.string(), files: z2.array(evidenceSchema) });
2980
+ cacheSchema = z2.object({ version: z2.literal(4), hash: z2.string(), files: z2.array(evidenceSchema) });
2480
2981
  }
2481
2982
  });
2482
2983
 
@@ -2521,7 +3022,7 @@ async function analyzeRepository(root, options = {}) {
2521
3022
  documentation: framework.documentation
2522
3023
  };
2523
3024
  const nextCache = {
2524
- version: 3,
3025
+ version: 4,
2525
3026
  hash: sha256(merged.files.map((file) => `${file.relative}\0${file.hash}`).join("\0")),
2526
3027
  files: merged.files
2527
3028
  };
@@ -2548,7 +3049,7 @@ var init_analyze = __esm({
2548
3049
  });
2549
3050
 
2550
3051
  // ../core/src/context.ts
2551
- import path8 from "path";
3052
+ import path10 from "path";
2552
3053
  function renderContext(model, options = {}) {
2553
3054
  const directory = options.directory ?? SPEC_DIR;
2554
3055
  const body = [
@@ -2556,7 +3057,7 @@ function renderContext(model, options = {}) {
2556
3057
  "",
2557
3058
  "Before changing UI:",
2558
3059
  "1. Run `design-rover status --json` to inspect freshness and pending review.",
2559
- "2. Use `design-rover query <name> --json` to find components, tokens, conventions and exceptions. Use `design-rover explain <id>` for details and source locations.",
3060
+ "2. Use `design-rover context <task> --path <file-or-directory> --json` for bounded, relevant components, tokens and approved decisions. Inspect coverage and omission warnings; raise --max-chars or narrow the request when approved decisions are omitted. Use `design-rover query <id> --json` for whole records or `design-rover explain <id>` for readable details and source locations.",
2560
3061
  "3. Read the relevant implementation before using its imports, props and variants. A component name can exist in several packages; choose by ID and source.",
2561
3062
  "4. Use approved conventions and exceptions in their documented scope. Observed code is evidence; inferred or suggested entries need review before becoming policy.",
2562
3063
  "5. Match the relevant token scope and theme. Missing or unresolved information is not permission to invent an API or claim a rule was checked.",
@@ -2651,7 +3152,7 @@ async function writeGenerated(root, model, options = {}) {
2651
3152
  const writes = [];
2652
3153
  for (const entry of entries) {
2653
3154
  if (options.writeRepoFiles === false && !entry.rel.startsWith(`${directory}/generated/`)) continue;
2654
- const current = await readText(path8.join(root, entry.rel));
3155
+ const current = await readText(path10.join(root, entry.rel));
2655
3156
  if (entry.wrap) {
2656
3157
  writes.push({ rel: entry.rel, content: wrapManaged(current, entry.content) });
2657
3158
  continue;
@@ -2664,10 +3165,10 @@ async function writeGenerated(root, model, options = {}) {
2664
3165
  }
2665
3166
  const files = [];
2666
3167
  for (const write of writes) {
2667
- if (!options.dryRun) await writeText(path8.join(root, write.rel), write.content);
3168
+ if (!options.dryRun) await writeText(path10.join(root, write.rel), write.content);
2668
3169
  files.push({ path: write.rel, hash: sha256(write.content), managed: true });
2669
3170
  }
2670
- if (!options.dryRun) await writeText(path8.join(specPaths(root).generated, "projections.json"), `${JSON.stringify({ files }, null, 2)}
3171
+ if (!options.dryRun) await writeText(path10.join(specPaths(root).generated, "projections.json"), `${JSON.stringify({ files }, null, 2)}
2671
3172
  `);
2672
3173
  return { ...rendered, files };
2673
3174
  }
@@ -2681,7 +3182,7 @@ async function generatedHashes(root, model) {
2681
3182
  stale.push(entry.rel);
2682
3183
  continue;
2683
3184
  }
2684
- const current = await readText(path8.join(root, entry.rel));
3185
+ const current = await readText(path10.join(root, entry.rel));
2685
3186
  if (current === void 0) {
2686
3187
  stale.push(entry.rel);
2687
3188
  continue;
@@ -2732,7 +3233,8 @@ function componentSig(record) {
2732
3233
  })).sort((a, b) => a.name.localeCompare(b.name)),
2733
3234
  source: record.source,
2734
3235
  importPath: record.importPath,
2735
- exportName: record.exportName
3236
+ exportName: record.exportName,
3237
+ propsResolution: record.propsResolution
2736
3238
  };
2737
3239
  }
2738
3240
  function tokenSig(record) {
@@ -3197,6 +3699,221 @@ var init_check = __esm({
3197
3699
  }
3198
3700
  });
3199
3701
 
3702
+ // ../core/src/retrieval.ts
3703
+ import path11 from "path";
3704
+ import ignore3 from "ignore";
3705
+ function normalizePath(value) {
3706
+ const input = value.trim().replace(/\\/g, "/");
3707
+ if (!input || input.length > 512 || input.includes("\0") || input.startsWith("/") || /^[a-z]:/i.test(input)) throw new Error("Context path must be a repository-relative file or directory (at most 512 characters).");
3708
+ const normalized = path11.posix.normalize(input).replace(/\/$/, "");
3709
+ if (normalized === ".." || normalized.startsWith("../")) throw new Error("Context path cannot leave the repository.");
3710
+ return normalized;
3711
+ }
3712
+ function owner(file) {
3713
+ const parts = file.split("/");
3714
+ return (parts[0] === "apps" || parts[0] === "packages") && parts[1] ? `${parts[0]}/${parts[1]}` : void 0;
3715
+ }
3716
+ function under(file, selected) {
3717
+ return selected === "." || file === selected || file.startsWith(`${selected}/`);
3718
+ }
3719
+ function sourceFiles(record) {
3720
+ const files = record.evidence.flatMap((item) => item.location?.file ? [item.location.file] : []);
3721
+ if ("source" in record) files.push(record.source);
3722
+ if ("origin" in record) files.push(record.origin);
3723
+ if ("occurrences" in record) files.push(...record.occurrences.map((item) => item.file));
3724
+ if ("sourceRefs" in record) files.push(...record.sourceRefs.flatMap((item) => item.location?.file ? [item.location.file] : []));
3725
+ return [...new Set(files.map((file) => file.replace(/\\/g, "/")).filter(Boolean))].sort(compare);
3726
+ }
3727
+ function lexicalScore(record, query, words) {
3728
+ const name = "name" in record ? record.name : record.subject;
3729
+ if (query.toLowerCase() === record.id.toLowerCase()) return 300;
3730
+ const title = normalizeText(`${record.id} ${name}`);
3731
+ const text = normalizeText(JSON.stringify(record));
3732
+ let score = query.toLowerCase() === name.toLowerCase() ? 150 : 0;
3733
+ for (const word of words) {
3734
+ if (title.includes(word)) score += 20;
3735
+ else if (text.includes(word)) score += 4;
3736
+ }
3737
+ return score;
3738
+ }
3739
+ function approvedDecision(match) {
3740
+ return match.record.status === "approved" && match.kind !== "component" && match.kind !== "token";
3741
+ }
3742
+ function visible(kind, record, includePending) {
3743
+ return record.status === "approved" || kind !== "exception" && record.status === "observed" || Boolean(includePending && (record.status === "inferred" || record.status === "suggested"));
3744
+ }
3745
+ function pathScopeMatches(patterns, selected, allowDescendants = true) {
3746
+ if (selected === ".") return "potential";
3747
+ const result = ignore3().add(patterns).test(selected);
3748
+ if (result.ignored) return "direct";
3749
+ if (result.unignored) return void 0;
3750
+ if (allowDescendants && patterns.some((pattern) => {
3751
+ if (pattern.startsWith("!")) return false;
3752
+ const rawPrefix = pattern.replace(/^\//, "").split(/[?*\[]/, 1)[0];
3753
+ const prefix = rawPrefix.replace(/\/$/, "");
3754
+ return !prefix || under(prefix, selected) || under(selected, prefix) || !rawPrefix.endsWith("/") && selected.startsWith(rawPrefix);
3755
+ })) return "potential";
3756
+ return void 0;
3757
+ }
3758
+ function retrieveContext(model, options = {}) {
3759
+ const query = options.query?.trim() ?? "";
3760
+ if (query.length > 512) throw new Error("Context query must be at most 512 characters.");
3761
+ const selected = options.path === void 0 ? void 0 : normalizePath(options.path);
3762
+ if (!query && selected === void 0) throw new Error("Provide a context query or a repository-relative path.");
3763
+ const limit = options.limit ?? 20;
3764
+ const maxChars = options.maxChars ?? 12e3;
3765
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("Context limit must be an integer from 1 to 100.");
3766
+ if (!Number.isInteger(maxChars) || maxChars < 1024 || maxChars > 1e5) throw new Error("Context maxChars must be an integer from 1024 to 100000.");
3767
+ const includePending = options.includePending ?? false;
3768
+ const words = [...new Set(lexicalWords(query))].slice(0, 32);
3769
+ const matches = [];
3770
+ const relevantComponents = /* @__PURE__ */ new Set();
3771
+ const relevantPaths = /* @__PURE__ */ new Set();
3772
+ const relevantOwners = /* @__PURE__ */ new Set();
3773
+ const relevantComponentSources = /* @__PURE__ */ new Set();
3774
+ const tokenNames = /* @__PURE__ */ new Set();
3775
+ function relevance(kind, record) {
3776
+ if (!visible(kind, record, includePending)) return void 0;
3777
+ const files = sourceFiles(record);
3778
+ const lexical = query ? lexicalScore(record, query, words) : 0;
3779
+ let pathScore = selected !== void 0 && files.some((file) => under(file, selected)) ? 100 : 0;
3780
+ const reasons = [];
3781
+ if (kind === "component" && selected !== void 0 && "usage" in record && record.usage.some((item) => under(item.file, selected))) {
3782
+ pathScore = Math.max(pathScore, 120);
3783
+ reasons.push("used-in-path");
3784
+ }
3785
+ const connected = kind === "token" && "origin" in record && (tokenNames.has(record.id) || tokenNames.has(record.name) && (owner(record.origin) === void 0 || relevantOwners.has(owner(record.origin)) || selected !== void 0 && owner(record.origin) === owner(selected)));
3786
+ if (connected) {
3787
+ pathScore = Math.max(pathScore, 80);
3788
+ reasons.push("component-token");
3789
+ }
3790
+ const globalDecision = (kind === "convention" || kind === "pattern" || kind === "intent") && !files.length;
3791
+ if (kind !== "component" && kind !== "token" && files.some((file) => relevantComponentSources.has(file))) {
3792
+ pathScore = Math.max(pathScore, 110);
3793
+ reasons.push("component-source-decision");
3794
+ }
3795
+ let applicable = false;
3796
+ if (kind === "exception" && "scope" in record && typeof record.scope === "object") {
3797
+ const scope = record.scope;
3798
+ const componentsMatch = !scope.components.length || scope.components.some((item) => relevantComponents.has(item));
3799
+ let pathsMatch;
3800
+ let potentialScope = false;
3801
+ try {
3802
+ const matched = selected !== void 0 ? pathScopeMatches(scope.paths, selected) : [...relevantPaths].map((file) => pathScopeMatches(scope.paths, file, false)).find(Boolean);
3803
+ pathsMatch = !scope.paths.length || Boolean(matched);
3804
+ potentialScope = scope.paths.length > 0 && matched === "potential";
3805
+ } catch (error) {
3806
+ throw new Error(`Cannot safely resolve exception scope ${record.id}: ${String(error)}`);
3807
+ }
3808
+ const pathRelevant = scope.paths.length > 0 && pathsMatch;
3809
+ applicable = Boolean((scope.paths.length || scope.components.length) && componentsMatch && pathsMatch);
3810
+ if (selected !== void 0 && !applicable && !pathRelevant) return void 0;
3811
+ if (applicable || pathRelevant) {
3812
+ pathScore = Math.max(pathScore, 140);
3813
+ reasons.push(potentialScope ? "path-exception-may-apply" : applicable ? "applicable-exception" : "path-exception-component-scope-unresolved");
3814
+ }
3815
+ }
3816
+ if (selected !== void 0) {
3817
+ const selectedOwner = owner(selected);
3818
+ const foreign = files.length > 0 && files.every((file) => owner(file) !== void 0 && owner(file) !== selectedOwner);
3819
+ if (foreign && !pathScore && !applicable) return void 0;
3820
+ if (files.length && !pathScore && lexical < 20 && !globalDecision && !applicable) return void 0;
3821
+ }
3822
+ if (!lexical && !pathScore && !globalDecision) return void 0;
3823
+ if (lexical) reasons.push("query-match");
3824
+ if (pathScore && !reasons.length) reasons.push("source-in-path");
3825
+ if (globalDecision) reasons.push("repository-decision");
3826
+ const name = "name" in record ? record.name : record.subject;
3827
+ const explicitMatch = query.toLowerCase() === record.id.toLowerCase() || query.toLowerCase() === name.toLowerCase();
3828
+ const nameWords = lexicalWords(name);
3829
+ const requestedComponent = kind === "component" && nameWords.length > 0 && nameWords.every((word) => words.includes(word));
3830
+ const requestBoost = explicitMatch ? 3e3 : requestedComponent ? 2e3 : 0;
3831
+ if (requestBoost) reasons.push("requested-record");
3832
+ const decisionBoost = record.status === "approved" && kind !== "component" && kind !== "token" ? 1e3 : 0;
3833
+ return { kind, score: requestBoost + decisionBoost + pathScore + lexical, reasons, sourceRefs: files, record };
3834
+ }
3835
+ for (const component of model.components) {
3836
+ const match = relevance("component", component);
3837
+ if (!match) continue;
3838
+ matches.push(match);
3839
+ relevantComponents.add(component.id);
3840
+ relevantComponents.add(component.name);
3841
+ relevantPaths.add(component.source);
3842
+ relevantOwners.add(owner(component.source));
3843
+ relevantComponentSources.add(component.source);
3844
+ for (const usage of component.usage) relevantPaths.add(usage.file);
3845
+ for (const token of component.relatedTokens) tokenNames.add(token);
3846
+ }
3847
+ const collections = { token: model.tokens, pattern: model.patterns, convention: model.conventions, intent: model.intents, exception: model.exceptions };
3848
+ for (const kind of ["token", "pattern", "convention", "intent", "exception"]) {
3849
+ for (const record of collections[kind]) {
3850
+ const match = relevance(kind, record);
3851
+ if (match) matches.push(match);
3852
+ }
3853
+ }
3854
+ matches.sort((left, right) => right.score - left.score || compare(left.kind, right.kind) || compare(left.record.id, right.record.id));
3855
+ const decisionCount = matches.filter(approvedDecision).length;
3856
+ function packet(items, omittedByBudget2, omittedByLimit2) {
3857
+ const returnedDecisions = items.filter(approvedDecision).length;
3858
+ const omittedDecisions = decisionCount - returnedDecisions;
3859
+ const result2 = {
3860
+ version: 1,
3861
+ repository: model.system.repository.name,
3862
+ scope: { query: query || null, path: selected ?? null, includePending },
3863
+ limits: { limit, maxChars, outputChars: 0, unit: "UTF-16 code units including JSON formatting and final newline" },
3864
+ freshness: { checked: false, canonicalUpdatedAt: model.system.updatedAt, lastAnalyzedAt: model.system.lastAnalyzedAt ?? null, recordedSyncStatus: model.system.syncStatus },
3865
+ coverage: { matched: matches.length, returned: items.length, complete: items.length === matches.length, omittedByLimit: omittedByLimit2, omittedByBudget: omittedByBudget2, approvedDecisions: { matched: decisionCount, returned: returnedDecisions, omitted: omittedDecisions } },
3866
+ warnings: ["Canonical snapshot only; run status to check source freshness."],
3867
+ matches: items
3868
+ };
3869
+ if (omittedDecisions) result2.warnings.push("Applicable approved decisions were omitted. Retrieve them before treating this context as complete.");
3870
+ if (items.length < matches.length) result2.warnings.push("Whole records were omitted by the stated limits; raise the bounds or narrow the query/path. No record was sliced.");
3871
+ if (items.some((item) => item.reasons.some((reason) => reason === "path-exception-may-apply" || reason === "path-exception-component-scope-unresolved"))) result2.warnings.push("Some exception scopes may apply to descendants or unresolved components. Inspect their recorded scope before applying them.");
3872
+ for (let pass = 0; pass < 4; pass += 1) {
3873
+ const length = serializeContext(result2).length;
3874
+ if (length === result2.limits.outputChars) break;
3875
+ result2.limits.outputChars = length;
3876
+ }
3877
+ return result2;
3878
+ }
3879
+ const selectedMatches = [];
3880
+ let omittedByBudget = 0;
3881
+ let omittedByLimit = 0;
3882
+ if (serializeContext(packet([], matches.length, 0)).length > maxChars) throw new Error("Context metadata exceeds maxChars. Increase the bound or shorten the query/path.");
3883
+ for (const match of matches) {
3884
+ if (selectedMatches.length >= limit) {
3885
+ omittedByLimit += 1;
3886
+ continue;
3887
+ }
3888
+ const candidate = packet([...selectedMatches, match], omittedByBudget, matches.length - selectedMatches.length - 1 - omittedByBudget);
3889
+ if (serializeContext(candidate).length <= maxChars) selectedMatches.push(match);
3890
+ else omittedByBudget += 1;
3891
+ }
3892
+ let result = packet(selectedMatches, omittedByBudget, omittedByLimit);
3893
+ while (serializeContext(result).length > maxChars && selectedMatches.length) {
3894
+ selectedMatches.pop();
3895
+ omittedByBudget += 1;
3896
+ result = packet(selectedMatches, omittedByBudget, omittedByLimit);
3897
+ }
3898
+ if (serializeContext(result).length > maxChars) throw new Error("Context metadata exceeds maxChars. Increase the bound.");
3899
+ return result;
3900
+ }
3901
+ function serializeContext(packet) {
3902
+ return `${JSON.stringify(packet, null, 2)}
3903
+ `;
3904
+ }
3905
+ var STOP_WORDS, normalizeText, lexicalWords, compare;
3906
+ var init_retrieval = __esm({
3907
+ "../core/src/retrieval.ts"() {
3908
+ "use strict";
3909
+ init_esm_shims();
3910
+ STOP_WORDS = /* @__PURE__ */ new Set(["a", "an", "the", "to", "for", "with", "on", "in", "and", "or", "of", "this", "that", "please", "build", "create", "edit", "update", "change", "add", "implement", "make", "use", "using"]);
3911
+ normalizeText = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
3912
+ lexicalWords = (value) => normalizeText(value).split(/[^a-z0-9_-]+/).filter((word) => word && !STOP_WORDS.has(word));
3913
+ compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
3914
+ }
3915
+ });
3916
+
3200
3917
  // ../core/src/explain.ts
3201
3918
  function explain(model, query) {
3202
3919
  const q = query.trim().toLowerCase();
@@ -3221,6 +3938,8 @@ ${componentMatches.map((item) => `${item.id} \u2014 ${item.source}`).join("\n")}
3221
3938
  `Variants: ${component.variants.join(", ") || "none"}`,
3222
3939
  `Sizes: ${component.sizes.join(", ") || "none"}`,
3223
3940
  `Props: ${component.props.map((prop) => `${prop.name}${prop.required ? "" : "?"}: ${prop.type ?? "unresolved"}${prop.defaultValue ? ` = ${prop.defaultValue}` : ""}`).join(", ") || "none"}`,
3941
+ `Props resolution: ${component.propsResolution?.status ?? "not recorded"}`,
3942
+ ...component.propsResolution?.issues.map((issue) => ` ${issue}`) ?? [],
3224
3943
  `Architecture: ${component.architecture}`,
3225
3944
  `Usage: ${component.usage.reduce((sum, item) => sum + item.count, 0)} call sites in ${component.usage.length} files`,
3226
3945
  `Status: ${component.status}`
@@ -3310,11 +4029,11 @@ var init_explain = __esm({
3310
4029
 
3311
4030
  // ../core/src/store.ts
3312
4031
  import fs4 from "fs/promises";
3313
- import path9 from "path";
4032
+ import path12 from "path";
3314
4033
  async function listJsonFiles2(dir) {
3315
4034
  try {
3316
4035
  const entries = await fs4.readdir(dir, { withFileTypes: true });
3317
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path9.join(dir, entry.name)).sort();
4036
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path12.join(dir, entry.name)).sort();
3318
4037
  } catch (error) {
3319
4038
  if (error.code === "ENOENT") return [];
3320
4039
  throw new Error(`Cannot read canonical directory ${dir}: ${String(error)}`);
@@ -3394,7 +4113,7 @@ async function isInitialized(root) {
3394
4113
  }
3395
4114
  async function loadCanonicalUnlocked(root) {
3396
4115
  const paths = specPaths(root);
3397
- const recoveryMarker = path9.join(paths.dir, ".recovery-required.json");
4116
+ const recoveryMarker = path12.join(paths.dir, ".recovery-required.json");
3398
4117
  if (await pathExists(recoveryMarker)) throw new Error(`Canonical storage needs recovery. Inspect ${recoveryMarker} before continuing.`);
3399
4118
  if (!await pathExists(paths.system)) return void 0;
3400
4119
  const system = await readJsonStrict(paths.system, void 0);
@@ -3423,7 +4142,7 @@ async function loadCanonicalUnlocked(root) {
3423
4142
  async function loadCanonical(root) {
3424
4143
  const paths = specPaths(root);
3425
4144
  if (!await pathExists(paths.dir)) return void 0;
3426
- const lockFile = path9.join(paths.dir, ".write.lock");
4145
+ const lockFile = path12.join(paths.dir, ".write.lock");
3427
4146
  let lock;
3428
4147
  try {
3429
4148
  lock = await fs4.open(lockFile, "wx");
@@ -3444,7 +4163,7 @@ async function saveCanonical(root, model) {
3444
4163
  model = validateCanonical(model);
3445
4164
  const paths = specPaths(root);
3446
4165
  await fs4.mkdir(paths.dir, { recursive: true });
3447
- const lockFile = path9.join(paths.dir, ".write.lock");
4166
+ const lockFile = path12.join(paths.dir, ".write.lock");
3448
4167
  let lock;
3449
4168
  try {
3450
4169
  lock = await fs4.open(lockFile, "wx");
@@ -3476,9 +4195,9 @@ async function saveCanonical(root, model) {
3476
4195
  [paths.intents, model.intents],
3477
4196
  [paths.exceptions, model.exceptions]
3478
4197
  ]) {
3479
- const keep = new Set(records.map((record) => path9.join(dir, recordFileName(record.id))));
4198
+ const keep = new Set(records.map((record) => path12.join(dir, recordFileName(record.id))));
3480
4199
  for (const file of await listJsonFiles2(dir)) if (!keep.has(file)) obsolete.push(file);
3481
- for (const record of records) writes.set(path9.join(dir, recordFileName(record.id)), stableStringify(record));
4200
+ for (const record of records) writes.set(path12.join(dir, recordFileName(record.id)), stableStringify(record));
3482
4201
  }
3483
4202
  let currentIgnore = "";
3484
4203
  try {
@@ -3505,16 +4224,16 @@ async function saveCanonical(root, model) {
3505
4224
  }
3506
4225
  writes.set(paths.historyLog, history2.map((event) => JSON.stringify(event)).join("\n") + (history2.length ? "\n" : ""));
3507
4226
  writes.set(paths.system, stableStringify(model.system));
3508
- staging = await fs4.mkdtemp(path9.join(paths.dir, ".write-"));
4227
+ staging = await fs4.mkdtemp(path12.join(paths.dir, ".write-"));
3509
4228
  const staged = [];
3510
4229
  for (const [destination, content] of writes) {
3511
- const temporary = path9.join(staging, `new-${staged.length}`);
4230
+ const temporary = path12.join(staging, `new-${staged.length}`);
3512
4231
  await fs4.writeFile(temporary, content, "utf8");
3513
4232
  staged.push([temporary, destination]);
3514
4233
  }
3515
4234
  const recovery = [];
3516
4235
  for (const destination of /* @__PURE__ */ new Set([...writes.keys(), ...obsolete])) {
3517
- const backup = path9.join(staging, `old-${recovery.length}`);
4236
+ const backup = path12.join(staging, `old-${recovery.length}`);
3518
4237
  try {
3519
4238
  await fs4.copyFile(destination, backup);
3520
4239
  recovery.push({ destination, backup });
@@ -3523,13 +4242,13 @@ async function saveCanonical(root, model) {
3523
4242
  recovery.push({ destination, backup: null });
3524
4243
  }
3525
4244
  }
3526
- await fs4.writeFile(path9.join(staging, "recovery.json"), JSON.stringify({
4245
+ await fs4.writeFile(path12.join(staging, "recovery.json"), JSON.stringify({
3527
4246
  canonicalDirectory: paths.dir,
3528
- files: recovery.map((item) => ({ path: path9.relative(paths.dir, item.destination), backup: item.backup ? path9.basename(item.backup) : null }))
4247
+ files: recovery.map((item) => ({ path: path12.relative(paths.dir, item.destination), backup: item.backup ? path12.basename(item.backup) : null }))
3529
4248
  }, null, 2));
3530
4249
  try {
3531
4250
  for (const [temporary, destination] of staged) {
3532
- await fs4.mkdir(path9.dirname(destination), { recursive: true });
4251
+ await fs4.mkdir(path12.dirname(destination), { recursive: true });
3533
4252
  await fs4.rename(temporary, destination);
3534
4253
  }
3535
4254
  for (const file of obsolete) await fs4.rm(file);
@@ -3540,9 +4259,9 @@ async function saveCanonical(root, model) {
3540
4259
  try {
3541
4260
  if (item.backup === null) await fs4.rm(item.destination, { force: true });
3542
4261
  else {
3543
- const temporary = path9.join(staging, `restore-${index}`);
4262
+ const temporary = path12.join(staging, `restore-${index}`);
3544
4263
  await fs4.copyFile(item.backup, temporary);
3545
- await fs4.mkdir(path9.dirname(item.destination), { recursive: true });
4264
+ await fs4.mkdir(path12.dirname(item.destination), { recursive: true });
3546
4265
  await fs4.rename(temporary, item.destination);
3547
4266
  }
3548
4267
  } catch (rollbackError) {
@@ -3551,8 +4270,8 @@ async function saveCanonical(root, model) {
3551
4270
  }
3552
4271
  if (failures.length) {
3553
4272
  retainRecovery = true;
3554
- const marker = path9.join(paths.dir, ".recovery-required.json");
3555
- await fs4.writeFile(marker, JSON.stringify({ backup: staging, manifest: path9.join(staging, "recovery.json") }, null, 2));
4273
+ const marker = path12.join(paths.dir, ".recovery-required.json");
4274
+ await fs4.writeFile(marker, JSON.stringify({ backup: staging, manifest: path12.join(staging, "recovery.json") }, null, 2));
3556
4275
  throw new AggregateError([error, ...failures], `Canonical save and rollback failed. Retained recovery backup at ${staging}; inspect ${marker}.`);
3557
4276
  }
3558
4277
  throw error;
@@ -3564,12 +4283,12 @@ async function saveCanonical(root, model) {
3564
4283
  }
3565
4284
  }
3566
4285
  function emptyCanonical(root, name) {
3567
- const ts2 = nowIso();
4286
+ const ts5 = nowIso();
3568
4287
  return {
3569
4288
  system: {
3570
4289
  version: 1,
3571
- createdAt: ts2,
3572
- updatedAt: ts2,
4290
+ createdAt: ts5,
4291
+ updatedAt: ts5,
3573
4292
  repository: { name, root },
3574
4293
  framework: { name: "unknown" },
3575
4294
  styling: { systems: [] },
@@ -3609,7 +4328,7 @@ var init_store = __esm({
3609
4328
  });
3610
4329
 
3611
4330
  // ../core/src/integrations-local.ts
3612
- import path10 from "path";
4331
+ import path13 from "path";
3613
4332
  import fs5 from "fs/promises";
3614
4333
  import { execFile as execFile2 } from "child_process";
3615
4334
  import { promisify as promisify2 } from "util";
@@ -3651,7 +4370,7 @@ async function installGitHook(root) {
3651
4370
  } catch {
3652
4371
  throw new Error("Cannot install a Git hook outside a Git repository.");
3653
4372
  }
3654
- const dest = path10.resolve(root, hooksPath);
4373
+ const dest = path13.resolve(root, hooksPath);
3655
4374
  const existing = await fs5.readFile(dest, "utf8").catch((error) => {
3656
4375
  if (error.code === "ENOENT") return void 0;
3657
4376
  throw error;
@@ -3667,7 +4386,7 @@ async function installGitHook(root) {
3667
4386
  if (error.code !== "ENOENT") throw error;
3668
4387
  }
3669
4388
  }
3670
- await fs5.mkdir(path10.dirname(dest), { recursive: true });
4389
+ await fs5.mkdir(path13.dirname(dest), { recursive: true });
3671
4390
  const temporary = `${dest}.${randomUUID2()}.tmp`;
3672
4391
  let moved = false;
3673
4392
  try {
@@ -3714,7 +4433,7 @@ jobs:
3714
4433
  }
3715
4434
  async function installGithubWorkflow(root) {
3716
4435
  const { writeText: writeText2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
3717
- const current = path10.join(root, ".github/workflows/design-rover.yml");
4436
+ const current = path13.join(root, ".github/workflows/design-rover.yml");
3718
4437
  const read = async (file) => fs5.readFile(file, "utf8").catch((error) => {
3719
4438
  if (error.code === "ENOENT") return void 0;
3720
4439
  throw error;
@@ -3905,9 +4624,9 @@ var init_sync = __esm({
3905
4624
  });
3906
4625
 
3907
4626
  // ../core/src/engine.ts
3908
- import path11 from "path";
4627
+ import path14 from "path";
3909
4628
  function repoName(root) {
3910
- return path11.basename(root);
4629
+ return path14.basename(root);
3911
4630
  }
3912
4631
  async function createEngine(root) {
3913
4632
  const initialized = await isInitialized(root);
@@ -4050,6 +4769,11 @@ async function createEngine(root) {
4050
4769
  if (!model) return void 0;
4051
4770
  return searchModel(model, query);
4052
4771
  },
4772
+ context: async (options = {}) => {
4773
+ const model = await loadCanonical(root);
4774
+ if (!model) throw new Error("Design Rover is not initialized. Run `design-rover init` before retrieving context.");
4775
+ return retrieveContext(model, options);
4776
+ },
4053
4777
  history: () => history(root),
4054
4778
  checkSnippet: async (code, fileName) => {
4055
4779
  const model = await loadCanonical(root);
@@ -4071,6 +4795,7 @@ var init_engine = __esm({
4071
4795
  "use strict";
4072
4796
  init_esm_shims();
4073
4797
  init_src();
4798
+ init_retrieval();
4074
4799
  init_analyze();
4075
4800
  init_check();
4076
4801
  init_usage_check();
@@ -4111,9 +4836,11 @@ __export(src_exports, {
4111
4836
  pathExists: () => pathExists,
4112
4837
  readText: () => readText,
4113
4838
  renderContext: () => renderContext,
4839
+ retrieveContext: () => retrieveContext,
4114
4840
  saveCanonical: () => saveCanonical,
4115
4841
  searchModel: () => searchModel,
4116
4842
  seedCanonical: () => seedCanonical,
4843
+ serializeContext: () => serializeContext,
4117
4844
  specDir: () => specDir,
4118
4845
  specPaths: () => specPaths,
4119
4846
  usageDiagnostics: () => usageDiagnostics,
@@ -4127,6 +4854,7 @@ var init_src2 = __esm({
4127
4854
  init_analyze();
4128
4855
  init_check();
4129
4856
  init_context();
4857
+ init_retrieval();
4130
4858
  init_diff();
4131
4859
  init_engine();
4132
4860
  init_explain();
@@ -4213,7 +4941,7 @@ async function createMcpServer(root) {
4213
4941
  async ({ includePending }) => {
4214
4942
  const model = await requireModel();
4215
4943
  return {
4216
- content: [{ type: "text", text: JSON.stringify(model.conventions.filter((item) => visible(item, includePending)), null, 2) }]
4944
+ content: [{ type: "text", text: JSON.stringify(model.conventions.filter((item) => visible2(item, includePending)), null, 2) }]
4217
4945
  };
4218
4946
  }
4219
4947
  );
@@ -4224,7 +4952,7 @@ async function createMcpServer(root) {
4224
4952
  async ({ includePending }) => {
4225
4953
  const model = await requireModel();
4226
4954
  return {
4227
- content: [{ type: "text", text: JSON.stringify(model.intents.filter((item) => visible(item, includePending)), null, 2) }]
4955
+ content: [{ type: "text", text: JSON.stringify(model.intents.filter((item) => visible2(item, includePending)), null, 2) }]
4228
4956
  };
4229
4957
  }
4230
4958
  );
@@ -4246,7 +4974,7 @@ async function createMcpServer(root) {
4246
4974
  async ({ query, includePending }) => {
4247
4975
  await requireModel();
4248
4976
  const result = await engine.search(query);
4249
- const filtered = Object.fromEntries(Object.entries(result ?? {}).map(([kind, records]) => [kind, records.filter((item) => visible(item, includePending))]));
4977
+ const filtered = Object.fromEntries(Object.entries(result ?? {}).map(([kind, records]) => [kind, records.filter((item) => visible2(item, includePending))]));
4250
4978
  return {
4251
4979
  content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }]
4252
4980
  };
@@ -4264,6 +4992,25 @@ async function createMcpServer(root) {
4264
4992
  };
4265
4993
  }
4266
4994
  );
4995
+ server.registerTool(
4996
+ "get_context",
4997
+ {
4998
+ description: "Retrieve bounded canonical design context for a task/query and/or repository-relative path. Whole records are ranked deterministically; inspect coverage and omission warnings before relying on it. Does not scan source or check freshness.",
4999
+ inputSchema: {
5000
+ query: z3.string().max(512).optional(),
5001
+ path: z3.string().max(512).optional(),
5002
+ includePending: z3.boolean().optional(),
5003
+ limit: z3.number().int().min(1).max(100).optional(),
5004
+ maxChars: z3.number().int().min(1024).max(1e5).optional()
5005
+ },
5006
+ annotations: { readOnlyHint: true }
5007
+ },
5008
+ async (options) => {
5009
+ const model = await requireModel();
5010
+ const packet = retrieveContext(model, options);
5011
+ return { content: [{ type: "text", text: serializeContext(packet) }] };
5012
+ }
5013
+ );
4267
5014
  server.tool(
4268
5015
  "explain_violation",
4269
5016
  "Explain a component, pattern, convention, or diagnostic in context.",
@@ -4279,7 +5026,7 @@ async function createMcpServer(root) {
4279
5026
  function trusted(item) {
4280
5027
  return item.status === "approved" || item.status === "observed";
4281
5028
  }
4282
- function visible(item, includePending) {
5029
+ function visible2(item, includePending) {
4283
5030
  return trusted(item) || Boolean(includePending && (item.status === "inferred" || item.status === "suggested"));
4284
5031
  }
4285
5032
  function approvedModel(model) {
@@ -4312,7 +5059,7 @@ init_esm_shims();
4312
5059
  init_src2();
4313
5060
  init_src();
4314
5061
  import { parseArgs } from "util";
4315
- import path12 from "path";
5062
+ import path15 from "path";
4316
5063
 
4317
5064
  // ../integrations/src/index.ts
4318
5065
  init_esm_shims();
@@ -4762,6 +5509,7 @@ Usage
4762
5509
  design-rover diff Show canonical system changes
4763
5510
  design-rover explain <name> Explain a component, pattern, convention, or intent
4764
5511
  design-rover query <q> Search the canonical model
5512
+ design-rover context [task] Retrieve bounded design context for a task and/or path
4765
5513
  design-rover generate Write AGENTS.md / CLAUDE.md / Cursor rules / skill
4766
5514
  design-rover history Show how the canonical system changed
4767
5515
  design-rover baseline Capture current findings as existing debt
@@ -4783,6 +5531,10 @@ Options
4783
5531
  --version Print the CLI version
4784
5532
  --cwd <path> Repository root
4785
5533
  --base <ref> Git base for incremental / PR / CI
5534
+ --path <path> Context: repository-relative file or directory
5535
+ --limit <n> Context: maximum whole records (default 20, maximum 100)
5536
+ --max-chars <n> Context: maximum formatted JSON characters (default 12000)
5537
+ --include-pending Context: include inferences and suggestions for review
4786
5538
  --fail-on <level> CI: error (default) | warning | none
4787
5539
  `;
4788
5540
  async function main() {
@@ -4796,6 +5548,10 @@ async function main() {
4796
5548
  version: { type: "boolean", short: "v", default: false },
4797
5549
  cwd: { type: "string" },
4798
5550
  base: { type: "string" },
5551
+ path: { type: "string" },
5552
+ limit: { type: "string" },
5553
+ "max-chars": { type: "string" },
5554
+ "include-pending": { type: "boolean", default: false },
4799
5555
  "fail-on": { type: "string", default: "error" },
4800
5556
  approve: { type: "boolean", default: false },
4801
5557
  generate: { type: "boolean", default: false },
@@ -4814,7 +5570,7 @@ async function main() {
4814
5570
  `);
4815
5571
  return;
4816
5572
  }
4817
- const root = path12.resolve(parsed.values.cwd ?? process.cwd());
5573
+ const root = path15.resolve(parsed.values.cwd ?? process.cwd());
4818
5574
  const command = parsed.positionals[0] ?? "status";
4819
5575
  const rest = parsed.positionals.slice(1);
4820
5576
  const engine = await createEngine(root);
@@ -4830,7 +5586,7 @@ async function main() {
4830
5586
  generate: Boolean(parsed.values.generate)
4831
5587
  });
4832
5588
  printInitSummary({
4833
- directory: path12.relative(root, engine.specDir),
5589
+ directory: path15.relative(root, engine.specDir),
4834
5590
  framework: result.snapshot.framework.name,
4835
5591
  styling: result.snapshot.styling.systems,
4836
5592
  counts: {
@@ -4953,6 +5709,17 @@ async function main() {
4953
5709
  }
4954
5710
  return;
4955
5711
  }
5712
+ if (command === "context") {
5713
+ const packet = await engine.context({
5714
+ query: rest.join(" ") || void 0,
5715
+ path: parsed.values.path,
5716
+ includePending: Boolean(parsed.values["include-pending"]),
5717
+ limit: parsed.values.limit === void 0 ? void 0 : Number(parsed.values.limit),
5718
+ maxChars: parsed.values["max-chars"] === void 0 ? void 0 : Number(parsed.values["max-chars"])
5719
+ });
5720
+ process.stdout.write(serializeContext(packet));
5721
+ return;
5722
+ }
4956
5723
  if (command === "query") {
4957
5724
  const query = rest.join(" ");
4958
5725
  if (!query) fail("Usage: design-rover query <text>");
@@ -5131,7 +5898,7 @@ async function main() {
5131
5898
  const mapping = mapShadcnLint(model);
5132
5899
  if (rest[0] === "install") {
5133
5900
  const { writeText: writeText2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
5134
- const dest = path12.join(engine.specDir, "generated/oxlintrc.shadcn.json");
5901
+ const dest = path15.join(engine.specDir, "generated/oxlintrc.shadcn.json");
5135
5902
  await writeText2(dest, oxlintFromMap(mapping));
5136
5903
  header();
5137
5904
  line(` Wrote ${dest}`);