@runcontext/cli 0.3.0 → 0.3.1

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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command12 } from "commander";
4
+ import { Command as Command15 } from "commander";
5
5
 
6
6
  // src/commands/lint.ts
7
7
  import { Command } from "commander";
@@ -508,25 +508,352 @@ var explainCommand = new Command4("explain").description("Look up models, terms,
508
508
  });
509
509
 
510
510
  // src/commands/fix.ts
511
+ import { Command as Command7 } from "commander";
512
+ import chalk8 from "chalk";
513
+ import path7 from "path";
514
+ import fs2 from "fs";
515
+ import {
516
+ compile as compile6,
517
+ loadConfig as loadConfig6,
518
+ LintEngine as LintEngine3,
519
+ ALL_RULES as ALL_RULES3,
520
+ applyFixes as applyFixes2,
521
+ createAdapter as createAdapter3
522
+ } from "@runcontext/core";
523
+
524
+ // src/commands/introspect.ts
511
525
  import { Command as Command5 } from "commander";
512
526
  import chalk6 from "chalk";
513
527
  import path5 from "path";
514
- import fs2 from "fs";
528
+ import { mkdirSync, writeFileSync as writeFileSync2, existsSync } from "fs";
515
529
  import {
516
- compile as compile5,
517
530
  loadConfig as loadConfig4,
531
+ createAdapter,
532
+ scaffoldFromSchema
533
+ } from "@runcontext/core";
534
+ function parseDbUrl(db) {
535
+ if (db.startsWith("duckdb://")) {
536
+ return { adapter: "duckdb", path: db.slice("duckdb://".length) };
537
+ }
538
+ if (db.startsWith("postgres://") || db.startsWith("postgresql://")) {
539
+ return { adapter: "postgres", connection: db };
540
+ }
541
+ if (db.endsWith(".duckdb") || db.endsWith(".db")) {
542
+ return { adapter: "duckdb", path: db };
543
+ }
544
+ throw new Error(
545
+ `Cannot determine adapter from "${db}". Use duckdb:// or postgres:// prefix.`
546
+ );
547
+ }
548
+ var introspectCommand = new Command5("introspect").description("Introspect a database and scaffold Bronze-level OSI metadata").option(
549
+ "--db <url>",
550
+ "Database URL (e.g., duckdb://path.duckdb or postgres://...)"
551
+ ).option(
552
+ "--source <name>",
553
+ "Use a named data_source from contextkit.config.yaml"
554
+ ).option("--tables <glob>", 'Filter tables by glob pattern (e.g., "vw_*")').option(
555
+ "--model-name <name>",
556
+ "Name for the generated model (default: derived from source)"
557
+ ).action(async (opts) => {
558
+ try {
559
+ const config = loadConfig4(process.cwd());
560
+ const contextDir = path5.resolve(config.context_dir);
561
+ let dsConfig;
562
+ let dsName;
563
+ if (opts.db) {
564
+ dsConfig = parseDbUrl(opts.db);
565
+ dsName = opts.source ?? "default";
566
+ } else if (opts.source) {
567
+ if (!config.data_sources?.[opts.source]) {
568
+ console.error(
569
+ chalk6.red(
570
+ `Data source "${opts.source}" not found in config`
571
+ )
572
+ );
573
+ process.exit(1);
574
+ }
575
+ dsConfig = config.data_sources[opts.source];
576
+ dsName = opts.source;
577
+ } else {
578
+ const sources = config.data_sources;
579
+ if (!sources || Object.keys(sources).length === 0) {
580
+ console.error(
581
+ chalk6.red(
582
+ "No data source specified. Use --db <url> or configure data_sources in config"
583
+ )
584
+ );
585
+ process.exit(1);
586
+ }
587
+ const firstName = Object.keys(sources)[0];
588
+ dsConfig = sources[firstName];
589
+ dsName = firstName;
590
+ }
591
+ const adapter = await createAdapter(dsConfig);
592
+ await adapter.connect();
593
+ console.log(
594
+ chalk6.green(
595
+ `Connected to ${dsConfig.adapter}: ${dsConfig.path ?? dsConfig.connection}`
596
+ )
597
+ );
598
+ let tables = await adapter.listTables();
599
+ if (opts.tables) {
600
+ const pattern = opts.tables.replace(/\*/g, ".*");
601
+ const regex = new RegExp(`^${pattern}$`, "i");
602
+ tables = tables.filter((t) => regex.test(t.name));
603
+ }
604
+ console.log(`Discovered ${tables.length} tables/views`);
605
+ const columns = {};
606
+ for (const table of tables) {
607
+ columns[table.name] = await adapter.listColumns(table.name);
608
+ }
609
+ const totalCols = Object.values(columns).reduce(
610
+ (sum, cols) => sum + cols.length,
611
+ 0
612
+ );
613
+ console.log(`Found ${totalCols} columns total`);
614
+ await adapter.disconnect();
615
+ const modelName = opts.modelName ?? dsName.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
616
+ const result = scaffoldFromSchema({
617
+ modelName,
618
+ dataSourceName: dsName,
619
+ tables,
620
+ columns
621
+ });
622
+ for (const dir of ["models", "governance", "owners"]) {
623
+ const dirPath = path5.join(contextDir, dir);
624
+ if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
625
+ }
626
+ const osiPath = path5.join(contextDir, "models", result.files.osi);
627
+ const govPath = path5.join(
628
+ contextDir,
629
+ "governance",
630
+ result.files.governance
631
+ );
632
+ const ownerPath = path5.join(contextDir, "owners", result.files.owner);
633
+ writeFileSync2(osiPath, result.osiYaml, "utf-8");
634
+ writeFileSync2(govPath, result.governanceYaml, "utf-8");
635
+ if (!existsSync(ownerPath)) {
636
+ writeFileSync2(ownerPath, result.ownerYaml, "utf-8");
637
+ }
638
+ console.log("");
639
+ console.log(chalk6.green("Scaffolded:"));
640
+ console.log(` ${path5.relative(process.cwd(), osiPath)}`);
641
+ console.log(` ${path5.relative(process.cwd(), govPath)}`);
642
+ console.log(` ${path5.relative(process.cwd(), ownerPath)}`);
643
+ console.log("");
644
+ console.log(chalk6.cyan("Run `context tier` to check your tier score."));
645
+ console.log(
646
+ chalk6.cyan("Run `context verify` to validate against data.")
647
+ );
648
+ } catch (err) {
649
+ console.error(
650
+ chalk6.red(`Introspect failed: ${err.message}`)
651
+ );
652
+ process.exit(1);
653
+ }
654
+ });
655
+
656
+ // src/commands/verify.ts
657
+ import { Command as Command6 } from "commander";
658
+ import chalk7 from "chalk";
659
+ import path6 from "path";
660
+ import {
661
+ compile as compile5,
662
+ loadConfig as loadConfig5,
518
663
  LintEngine as LintEngine2,
519
664
  ALL_RULES as ALL_RULES2,
520
- applyFixes as applyFixes2
665
+ createAdapter as createAdapter2
521
666
  } from "@runcontext/core";
522
- var fixCommand = new Command5("fix").description("Auto-fix lint issues").option("--context-dir <path>", "Path to context directory").option("--format <type>", "Output format: pretty or json", "pretty").option("--dry-run", "Show what would be fixed without writing files").action(async (opts) => {
667
+ function findTable(dsName, graph, existingTables) {
668
+ if (existingTables.has(dsName)) return dsName;
669
+ for (const [, model] of graph.models) {
670
+ const ds = model.datasets.find((d) => d.name === dsName);
671
+ if (ds?.source) {
672
+ const tableName = ds.source.split(".").pop();
673
+ if (existingTables.has(tableName)) return tableName;
674
+ }
675
+ }
676
+ return void 0;
677
+ }
678
+ async function collectDataValidation(adapter, graph) {
679
+ const validation = {
680
+ existingTables: /* @__PURE__ */ new Map(),
681
+ existingColumns: /* @__PURE__ */ new Map(),
682
+ actualSampleValues: /* @__PURE__ */ new Map(),
683
+ goldenQueryResults: /* @__PURE__ */ new Map(),
684
+ guardrailResults: /* @__PURE__ */ new Map()
685
+ };
686
+ const tables = await adapter.listTables();
687
+ for (const t of tables) {
688
+ validation.existingTables.set(t.name, t.row_count);
689
+ }
690
+ for (const t of tables) {
691
+ const cols = await adapter.listColumns(t.name);
692
+ const colMap = new Map(cols.map((c) => [c.name, c.data_type]));
693
+ validation.existingColumns.set(t.name, colMap);
694
+ }
695
+ for (const [, gov] of graph.governance) {
696
+ if (!gov.fields) continue;
697
+ for (const [fieldKey, fieldGov] of Object.entries(gov.fields)) {
698
+ if (!fieldGov.sample_values || fieldGov.sample_values.length === 0)
699
+ continue;
700
+ const dotIdx = fieldKey.indexOf(".");
701
+ if (dotIdx < 0) continue;
702
+ const dsName = fieldKey.substring(0, dotIdx);
703
+ const fieldName = fieldKey.substring(dotIdx + 1);
704
+ const tableName = findTable(dsName, graph, validation.existingTables);
705
+ if (!tableName) continue;
706
+ try {
707
+ const result = await adapter.query(
708
+ `SELECT DISTINCT CAST("${fieldName}" AS VARCHAR) AS val FROM "${tableName}" WHERE "${fieldName}" IS NOT NULL LIMIT 50`
709
+ );
710
+ validation.actualSampleValues.set(
711
+ fieldKey,
712
+ result.rows.map((r) => String(r.val))
713
+ );
714
+ } catch {
715
+ }
716
+ }
717
+ }
718
+ for (const [, rules] of graph.rules) {
719
+ if (!rules.golden_queries) continue;
720
+ for (let i = 0; i < rules.golden_queries.length; i++) {
721
+ const gq = rules.golden_queries[i];
722
+ try {
723
+ const result = await adapter.query(gq.sql);
724
+ validation.goldenQueryResults.set(i, {
725
+ success: true,
726
+ rowCount: result.row_count
727
+ });
728
+ } catch (err) {
729
+ validation.goldenQueryResults.set(i, {
730
+ success: false,
731
+ error: err.message
732
+ });
733
+ }
734
+ }
735
+ }
736
+ for (const [, rules] of graph.rules) {
737
+ if (!rules.guardrail_filters) continue;
738
+ for (let i = 0; i < rules.guardrail_filters.length; i++) {
739
+ const gf = rules.guardrail_filters[i];
740
+ const testTable = gf.tables?.[0] ?? "unknown";
741
+ const tableName = findTable(testTable, graph, validation.existingTables);
742
+ if (!tableName) {
743
+ validation.guardrailResults.set(i, {
744
+ valid: false,
745
+ error: `Table "${testTable}" not found`
746
+ });
747
+ continue;
748
+ }
749
+ try {
750
+ await adapter.query(
751
+ `SELECT 1 FROM "${tableName}" WHERE ${gf.filter} LIMIT 1`
752
+ );
753
+ validation.guardrailResults.set(i, { valid: true });
754
+ } catch (err) {
755
+ validation.guardrailResults.set(i, {
756
+ valid: false,
757
+ error: err.message
758
+ });
759
+ }
760
+ }
761
+ }
762
+ return validation;
763
+ }
764
+ var verifyCommand = new Command6("verify").description("Validate metadata accuracy against a live database").option("--source <name>", "Use a specific data_source from config").option("--db <url>", "Database URL override (postgres:// or path.duckdb)").option("--context-dir <path>", "Path to context directory").option("--format <type>", "Output format: pretty or json", "pretty").action(async (opts) => {
523
765
  try {
524
- const config = loadConfig4(process.cwd());
525
- const contextDir = opts.contextDir ? path5.resolve(opts.contextDir) : path5.resolve(config.context_dir);
526
- const { graph } = await compile5({ contextDir, config, rootDir: process.cwd() });
527
- const overrides = config.lint?.severity_overrides;
528
- const engine = new LintEngine2(overrides);
766
+ const config = loadConfig5(process.cwd());
767
+ const contextDir = opts.contextDir ? path6.resolve(opts.contextDir) : path6.resolve(config.context_dir);
768
+ const { graph, diagnostics: compileDiags } = await compile5({
769
+ contextDir,
770
+ config
771
+ });
772
+ let dsConfig;
773
+ if (opts.db) {
774
+ dsConfig = parseDbUrl(opts.db);
775
+ } else {
776
+ const sources = config.data_sources;
777
+ if (!sources || Object.keys(sources).length === 0) {
778
+ console.error(
779
+ chalk7.red(
780
+ "No data source configured. Add data_sources to contextkit.config.yaml or use --db."
781
+ )
782
+ );
783
+ process.exit(1);
784
+ }
785
+ const name = opts.source ?? Object.keys(sources)[0];
786
+ const resolved = sources[name];
787
+ if (!resolved) {
788
+ console.error(
789
+ chalk7.red(
790
+ `Data source "${name}" not found. Available: ${Object.keys(sources).join(", ")}`
791
+ )
792
+ );
793
+ process.exit(1);
794
+ return;
795
+ }
796
+ dsConfig = resolved;
797
+ }
798
+ const adapter = await createAdapter2(dsConfig);
799
+ await adapter.connect();
800
+ console.log(chalk7.green(`Connected to ${dsConfig.adapter}`));
801
+ console.log("Collecting validation data...\n");
802
+ graph.dataValidation = await collectDataValidation(adapter, graph);
803
+ await adapter.disconnect();
804
+ const engine = new LintEngine2();
529
805
  for (const rule of ALL_RULES2) {
806
+ if (rule.id.startsWith("data/")) {
807
+ engine.register(rule);
808
+ }
809
+ }
810
+ const dataDiags = engine.run(graph);
811
+ const allDiags = [...dataDiags];
812
+ if (allDiags.length === 0) {
813
+ const tableCount = graph.dataValidation.existingTables.size;
814
+ const totalRows = [
815
+ ...graph.dataValidation.existingTables.values()
816
+ ].reduce((a, b) => a + b, 0);
817
+ console.log(chalk7.green("All data validation checks passed.\n"));
818
+ console.log(
819
+ `Verified against ${tableCount} table(s) (${totalRows.toLocaleString()} total rows)`
820
+ );
821
+ } else {
822
+ console.log(formatDiagnostics(allDiags));
823
+ }
824
+ const hasErrors = allDiags.some((d) => d.severity === "error");
825
+ if (hasErrors) process.exit(1);
826
+ } catch (err) {
827
+ console.error(chalk7.red(`Verify failed: ${err.message}`));
828
+ process.exit(1);
829
+ }
830
+ });
831
+
832
+ // src/commands/fix.ts
833
+ var fixCommand = new Command7("fix").description("Auto-fix lint issues").option("--context-dir <path>", "Path to context directory").option("--format <type>", "Output format: pretty or json", "pretty").option("--dry-run", "Show what would be fixed without writing files").option("--db <url>", "Database URL for data-aware fixes (postgres:// or path.duckdb)").option("--source <name>", "Use a specific data_source from config").action(async (opts) => {
834
+ try {
835
+ const config = loadConfig6(process.cwd());
836
+ const contextDir = opts.contextDir ? path7.resolve(opts.contextDir) : path7.resolve(config.context_dir);
837
+ const { graph } = await compile6({ contextDir, config, rootDir: process.cwd() });
838
+ let dsConfig;
839
+ if (opts.db) {
840
+ dsConfig = parseDbUrl(opts.db);
841
+ } else {
842
+ const sources = config.data_sources;
843
+ if (sources && Object.keys(sources).length > 0) {
844
+ const name = opts.source ?? Object.keys(sources)[0];
845
+ dsConfig = sources[name];
846
+ }
847
+ }
848
+ if (dsConfig) {
849
+ const adapter = await createAdapter3(dsConfig);
850
+ await adapter.connect();
851
+ graph.dataValidation = await collectDataValidation(adapter, graph);
852
+ await adapter.disconnect();
853
+ }
854
+ const overrides = config.lint?.severity_overrides;
855
+ const engine = new LintEngine3(overrides);
856
+ for (const rule of ALL_RULES3) {
530
857
  engine.register(rule);
531
858
  }
532
859
  const diagnostics = engine.run(graph);
@@ -535,7 +862,7 @@ var fixCommand = new Command5("fix").description("Auto-fix lint issues").option(
535
862
  if (opts.format === "json") {
536
863
  console.log(formatJson({ fixedFiles: [], fixCount: 0 }));
537
864
  } else {
538
- console.log(chalk6.green("No fixable issues found."));
865
+ console.log(chalk8.green("No fixable issues found."));
539
866
  }
540
867
  return;
541
868
  }
@@ -552,10 +879,10 @@ var fixCommand = new Command5("fix").description("Auto-fix lint issues").option(
552
879
  );
553
880
  } else {
554
881
  console.log(
555
- chalk6.yellow(`Dry run: ${fixable.length} issue(s) would be fixed in ${fixedFiles.size} file(s):`)
882
+ chalk8.yellow(`Dry run: ${fixable.length} issue(s) would be fixed in ${fixedFiles.size} file(s):`)
556
883
  );
557
884
  for (const file of fixedFiles.keys()) {
558
- console.log(chalk6.gray(` ${file}`));
885
+ console.log(chalk8.gray(` ${file}`));
559
886
  }
560
887
  }
561
888
  return;
@@ -584,15 +911,15 @@ var fixCommand = new Command5("fix").description("Auto-fix lint issues").option(
584
911
  });
585
912
 
586
913
  // src/commands/dev.ts
587
- import { Command as Command6 } from "commander";
588
- import chalk7 from "chalk";
589
- import path6 from "path";
590
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
914
+ import { Command as Command8 } from "commander";
915
+ import chalk9 from "chalk";
916
+ import path8 from "path";
917
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
591
918
  import {
592
- compile as compile6,
593
- loadConfig as loadConfig5,
594
- LintEngine as LintEngine3,
595
- ALL_RULES as ALL_RULES3,
919
+ compile as compile7,
920
+ loadConfig as loadConfig7,
921
+ LintEngine as LintEngine4,
922
+ ALL_RULES as ALL_RULES4,
596
923
  filterByDirectives as filterByDirectives2,
597
924
  applyFixes as applyFixes3
598
925
  } from "@runcontext/core";
@@ -601,15 +928,15 @@ function diagKey(d) {
601
928
  }
602
929
  var previousDiags = /* @__PURE__ */ new Map();
603
930
  async function runLint(contextDir, fix) {
604
- const config = loadConfig5(process.cwd());
605
- const { graph, diagnostics: compileDiags, directives } = await compile6({
931
+ const config = loadConfig7(process.cwd());
932
+ const { graph, diagnostics: compileDiags, directives } = await compile7({
606
933
  contextDir,
607
934
  config,
608
935
  rootDir: process.cwd()
609
936
  });
610
937
  const overrides = config.lint?.severity_overrides;
611
- const engine = new LintEngine3(overrides);
612
- for (const rule of ALL_RULES3) {
938
+ const engine = new LintEngine4(overrides);
939
+ for (const rule of ALL_RULES4) {
613
940
  engine.register(rule);
614
941
  }
615
942
  const lintDiags = engine.run(graph);
@@ -619,20 +946,20 @@ async function runLint(contextDir, fix) {
619
946
  if (fixable.length > 0) {
620
947
  const fixes = applyFixes3(fixable, (filePath) => readFileSync2(filePath, "utf-8"));
621
948
  for (const [file, content] of fixes) {
622
- writeFileSync2(file, content, "utf-8");
949
+ writeFileSync3(file, content, "utf-8");
623
950
  }
624
- const { graph: reGraph, diagnostics: reCompileDiags, directives: reDirs } = await compile6({
951
+ const { graph: reGraph, diagnostics: reCompileDiags, directives: reDirs } = await compile7({
625
952
  contextDir,
626
953
  config,
627
954
  rootDir: process.cwd()
628
955
  });
629
- const reEngine = new LintEngine3(overrides);
630
- for (const rule of ALL_RULES3) {
956
+ const reEngine = new LintEngine4(overrides);
957
+ for (const rule of ALL_RULES4) {
631
958
  reEngine.register(rule);
632
959
  }
633
960
  allDiags = filterByDirectives2([...reCompileDiags, ...reEngine.run(reGraph)], reDirs);
634
961
  if (fixable.length > 0) {
635
- console.log(chalk7.green(` Auto-fixed ${fixable.length} issue(s).`));
962
+ console.log(chalk9.green(` Auto-fixed ${fixable.length} issue(s).`));
636
963
  }
637
964
  }
638
965
  }
@@ -649,16 +976,16 @@ async function runLint(contextDir, fix) {
649
976
  if (!currentDiags.has(key)) resolved.push(d);
650
977
  }
651
978
  console.clear();
652
- console.log(chalk7.gray(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Linting...`));
979
+ console.log(chalk9.gray(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Linting...`));
653
980
  if (previousDiags.size > 0) {
654
981
  if (resolved.length > 0) {
655
- console.log(chalk7.green(` ${resolved.length} issue(s) resolved`));
982
+ console.log(chalk9.green(` ${resolved.length} issue(s) resolved`));
656
983
  }
657
984
  if (newIssues.length > 0) {
658
- console.log(chalk7.red(` ${newIssues.length} new issue(s)`));
985
+ console.log(chalk9.red(` ${newIssues.length} new issue(s)`));
659
986
  }
660
987
  if (resolved.length === 0 && newIssues.length === 0) {
661
- console.log(chalk7.gray(" No changes"));
988
+ console.log(chalk9.gray(" No changes"));
662
989
  }
663
990
  console.log("");
664
991
  }
@@ -666,14 +993,14 @@ async function runLint(contextDir, fix) {
666
993
  console.log("");
667
994
  previousDiags = currentDiags;
668
995
  }
669
- var devCommand = new Command6("dev").description("Watch mode \u2014 re-run lint on file changes").option("--context-dir <path>", "Path to context directory").option("--fix", "Auto-fix problems on each re-lint").action(async (opts) => {
996
+ var devCommand = new Command8("dev").description("Watch mode \u2014 re-run lint on file changes").option("--context-dir <path>", "Path to context directory").option("--fix", "Auto-fix problems on each re-lint").action(async (opts) => {
670
997
  try {
671
- const config = loadConfig5(process.cwd());
672
- const contextDir = opts.contextDir ? path6.resolve(opts.contextDir) : path6.resolve(config.context_dir);
998
+ const config = loadConfig7(process.cwd());
999
+ const contextDir = opts.contextDir ? path8.resolve(opts.contextDir) : path8.resolve(config.context_dir);
673
1000
  const fix = opts.fix === true;
674
- console.log(chalk7.blue(`Watching ${contextDir} for changes...`));
675
- if (fix) console.log(chalk7.blue("Auto-fix enabled."));
676
- console.log(chalk7.gray("Press Ctrl+C to stop.\n"));
1001
+ console.log(chalk9.blue(`Watching ${contextDir} for changes...`));
1002
+ if (fix) console.log(chalk9.blue("Auto-fix enabled."));
1003
+ console.log(chalk9.gray("Press Ctrl+C to stop.\n"));
677
1004
  await runLint(contextDir, fix);
678
1005
  const { watch } = await import("chokidar");
679
1006
  let debounceTimer = null;
@@ -690,21 +1017,21 @@ var devCommand = new Command6("dev").description("Watch mode \u2014 re-run lint
690
1017
  await runLint(contextDir, fix);
691
1018
  } catch (err) {
692
1019
  console.error(
693
- chalk7.red(`Lint error: ${err.message}`)
1020
+ chalk9.red(`Lint error: ${err.message}`)
694
1021
  );
695
1022
  }
696
1023
  }, 300);
697
1024
  });
698
1025
  } catch (err) {
699
- console.error(chalk7.red(`Dev mode failed: ${err.message}`));
1026
+ console.error(chalk9.red(`Dev mode failed: ${err.message}`));
700
1027
  process.exit(1);
701
1028
  }
702
1029
  });
703
1030
 
704
1031
  // src/commands/init.ts
705
- import { Command as Command7 } from "commander";
706
- import chalk8 from "chalk";
707
- import path7 from "path";
1032
+ import { Command as Command9 } from "commander";
1033
+ import chalk10 from "chalk";
1034
+ import path9 from "path";
708
1035
  import fs3 from "fs";
709
1036
  var EXAMPLE_OSI = `version: "1.0"
710
1037
 
@@ -769,26 +1096,26 @@ var EXAMPLE_CONFIG = `context_dir: context
769
1096
  output_dir: dist
770
1097
  minimum_tier: bronze
771
1098
  `;
772
- var initCommand = new Command7("init").description("Scaffold a v0.2 ContextKit project structure").option("--dir <path>", "Root directory for the project", ".").action(async (opts) => {
1099
+ var initCommand = new Command9("init").description("Scaffold a v0.2 ContextKit project structure").option("--dir <path>", "Root directory for the project", ".").action(async (opts) => {
773
1100
  try {
774
- const rootDir = path7.resolve(opts.dir);
775
- const contextDir = path7.join(rootDir, "context");
1101
+ const rootDir = path9.resolve(opts.dir);
1102
+ const contextDir = path9.join(rootDir, "context");
776
1103
  const dirs = [
777
- path7.join(contextDir, "models"),
778
- path7.join(contextDir, "governance"),
779
- path7.join(contextDir, "glossary"),
780
- path7.join(contextDir, "owners")
1104
+ path9.join(contextDir, "models"),
1105
+ path9.join(contextDir, "governance"),
1106
+ path9.join(contextDir, "glossary"),
1107
+ path9.join(contextDir, "owners")
781
1108
  ];
782
1109
  for (const dir of dirs) {
783
1110
  fs3.mkdirSync(dir, { recursive: true });
784
1111
  }
785
1112
  const files = [
786
1113
  {
787
- path: path7.join(contextDir, "models", "example-model.osi.yaml"),
1114
+ path: path9.join(contextDir, "models", "example-model.osi.yaml"),
788
1115
  content: EXAMPLE_OSI
789
1116
  },
790
1117
  {
791
- path: path7.join(
1118
+ path: path9.join(
792
1119
  contextDir,
793
1120
  "governance",
794
1121
  "example-model.governance.yaml"
@@ -796,15 +1123,15 @@ var initCommand = new Command7("init").description("Scaffold a v0.2 ContextKit p
796
1123
  content: EXAMPLE_GOVERNANCE
797
1124
  },
798
1125
  {
799
- path: path7.join(contextDir, "glossary", "glossary.term.yaml"),
1126
+ path: path9.join(contextDir, "glossary", "glossary.term.yaml"),
800
1127
  content: EXAMPLE_TERM
801
1128
  },
802
1129
  {
803
- path: path7.join(contextDir, "owners", "data-team.owner.yaml"),
1130
+ path: path9.join(contextDir, "owners", "data-team.owner.yaml"),
804
1131
  content: EXAMPLE_OWNER
805
1132
  },
806
1133
  {
807
- path: path7.join(rootDir, "contextkit.config.yaml"),
1134
+ path: path9.join(rootDir, "contextkit.config.yaml"),
808
1135
  content: EXAMPLE_CONFIG
809
1136
  }
810
1137
  ];
@@ -812,11 +1139,11 @@ var initCommand = new Command7("init").description("Scaffold a v0.2 ContextKit p
812
1139
  let skipped = 0;
813
1140
  for (const file of files) {
814
1141
  if (fs3.existsSync(file.path)) {
815
- console.log(chalk8.gray(` skip ${path7.relative(rootDir, file.path)} (exists)`));
1142
+ console.log(chalk10.gray(` skip ${path9.relative(rootDir, file.path)} (exists)`));
816
1143
  skipped++;
817
1144
  } else {
818
1145
  fs3.writeFileSync(file.path, file.content, "utf-8");
819
- console.log(chalk8.green(` create ${path7.relative(rootDir, file.path)}`));
1146
+ console.log(chalk10.green(` create ${path9.relative(rootDir, file.path)}`));
820
1147
  created++;
821
1148
  }
822
1149
  }
@@ -827,10 +1154,10 @@ var initCommand = new Command7("init").description("Scaffold a v0.2 ContextKit p
827
1154
  )
828
1155
  );
829
1156
  console.log("");
830
- console.log(chalk8.gray("Next steps:"));
831
- console.log(chalk8.gray(" 1. Edit the example files in context/"));
832
- console.log(chalk8.gray(" 2. Run: context lint"));
833
- console.log(chalk8.gray(" 3. Run: context build"));
1157
+ console.log(chalk10.gray("Next steps:"));
1158
+ console.log(chalk10.gray(" 1. Edit the example files in context/"));
1159
+ console.log(chalk10.gray(" 2. Run: context lint"));
1160
+ console.log(chalk10.gray(" 3. Run: context build"));
834
1161
  } catch (err) {
835
1162
  console.error(formatError(err.message));
836
1163
  process.exit(1);
@@ -838,15 +1165,15 @@ var initCommand = new Command7("init").description("Scaffold a v0.2 ContextKit p
838
1165
  });
839
1166
 
840
1167
  // src/commands/site.ts
841
- import { Command as Command8 } from "commander";
842
- import chalk9 from "chalk";
843
- import path8 from "path";
844
- import { compile as compile7, loadConfig as loadConfig6, emitManifest as emitManifest2 } from "@runcontext/core";
845
- var siteCommand = new Command8("site").description("Build a static documentation site from compiled context").option("--context-dir <path>", "Path to context directory").option("--output-dir <path>", "Path to site output directory").action(async (opts) => {
1168
+ import { Command as Command10 } from "commander";
1169
+ import chalk11 from "chalk";
1170
+ import path10 from "path";
1171
+ import { compile as compile8, loadConfig as loadConfig8, emitManifest as emitManifest2 } from "@runcontext/core";
1172
+ var siteCommand = new Command10("site").description("Build a static documentation site from compiled context").option("--context-dir <path>", "Path to context directory").option("--output-dir <path>", "Path to site output directory").action(async (opts) => {
846
1173
  try {
847
- const config = loadConfig6(process.cwd());
848
- const contextDir = opts.contextDir ? path8.resolve(opts.contextDir) : path8.resolve(config.context_dir);
849
- const { graph } = await compile7({ contextDir, config, rootDir: process.cwd() });
1174
+ const config = loadConfig8(process.cwd());
1175
+ const contextDir = opts.contextDir ? path10.resolve(opts.contextDir) : path10.resolve(config.context_dir);
1176
+ const { graph } = await compile8({ contextDir, config, rootDir: process.cwd() });
850
1177
  const manifest = emitManifest2(graph, config);
851
1178
  let buildSite;
852
1179
  try {
@@ -856,15 +1183,15 @@ var siteCommand = new Command8("site").description("Build a static documentation
856
1183
  }
857
1184
  if (!buildSite) {
858
1185
  console.log(
859
- chalk9.yellow(
1186
+ chalk11.yellow(
860
1187
  "Site generator is not yet available. Install @runcontext/site to enable this command."
861
1188
  )
862
1189
  );
863
1190
  process.exit(0);
864
1191
  }
865
- const outputDir = opts.outputDir ? path8.resolve(opts.outputDir) : path8.resolve(config.site?.base_path ?? "site");
1192
+ const outputDir = opts.outputDir ? path10.resolve(opts.outputDir) : path10.resolve(config.site?.base_path ?? "site");
866
1193
  await buildSite(manifest, config, outputDir);
867
- console.log(chalk9.green(`Site built to ${outputDir}`));
1194
+ console.log(chalk11.green(`Site built to ${outputDir}`));
868
1195
  } catch (err) {
869
1196
  console.error(formatError(err.message));
870
1197
  process.exit(1);
@@ -872,9 +1199,9 @@ var siteCommand = new Command8("site").description("Build a static documentation
872
1199
  });
873
1200
 
874
1201
  // src/commands/serve.ts
875
- import { Command as Command9 } from "commander";
876
- import chalk10 from "chalk";
877
- var serveCommand = new Command9("serve").description("Start the MCP server (stdio transport)").option("--context-dir <path>", "Path to context directory").action(async (opts) => {
1202
+ import { Command as Command11 } from "commander";
1203
+ import chalk12 from "chalk";
1204
+ var serveCommand = new Command11("serve").description("Start the MCP server (stdio transport)").option("--context-dir <path>", "Path to context directory").action(async (opts) => {
878
1205
  try {
879
1206
  let startServer;
880
1207
  try {
@@ -884,13 +1211,13 @@ var serveCommand = new Command9("serve").description("Start the MCP server (stdi
884
1211
  }
885
1212
  if (!startServer) {
886
1213
  console.log(
887
- chalk10.yellow(
1214
+ chalk12.yellow(
888
1215
  "MCP server is not available. Install @runcontext/mcp to enable this command."
889
1216
  )
890
1217
  );
891
1218
  process.exit(1);
892
1219
  }
893
- console.log(chalk10.blue("Starting MCP server (stdio transport)..."));
1220
+ console.log(chalk12.blue("Starting MCP server (stdio transport)..."));
894
1221
  await startServer({
895
1222
  contextDir: opts.contextDir,
896
1223
  rootDir: process.cwd()
@@ -902,13 +1229,13 @@ var serveCommand = new Command9("serve").description("Start the MCP server (stdi
902
1229
  });
903
1230
 
904
1231
  // src/commands/validate-osi.ts
905
- import { Command as Command10 } from "commander";
906
- import chalk11 from "chalk";
907
- import path9 from "path";
1232
+ import { Command as Command12 } from "commander";
1233
+ import chalk13 from "chalk";
1234
+ import path11 from "path";
908
1235
  import { parseFile, osiDocumentSchema } from "@runcontext/core";
909
- var validateOsiCommand = new Command10("validate-osi").description("Validate a single OSI file against the schema").argument("<file>", "Path to the OSI YAML file").option("--format <type>", "Output format: pretty or json", "pretty").action(async (file, opts) => {
1236
+ var validateOsiCommand = new Command12("validate-osi").description("Validate a single OSI file against the schema").argument("<file>", "Path to the OSI YAML file").option("--format <type>", "Output format: pretty or json", "pretty").action(async (file, opts) => {
910
1237
  try {
911
- const filePath = path9.resolve(file);
1238
+ const filePath = path11.resolve(file);
912
1239
  const parsed = await parseFile(filePath, "model");
913
1240
  const result = osiDocumentSchema.safeParse(parsed.data);
914
1241
  if (result.success) {
@@ -937,9 +1264,9 @@ var validateOsiCommand = new Command10("validate-osi").description("Validate a s
937
1264
  })
938
1265
  );
939
1266
  } else {
940
- console.error(chalk11.red(`Validation failed for ${filePath}:`));
1267
+ console.error(chalk13.red(`Validation failed for ${filePath}:`));
941
1268
  for (const issue of issues) {
942
- console.error(chalk11.red(` ${issue.path}: ${issue.message}`));
1269
+ console.error(chalk13.red(` ${issue.path}: ${issue.message}`));
943
1270
  }
944
1271
  }
945
1272
  process.exit(1);
@@ -950,46 +1277,268 @@ var validateOsiCommand = new Command10("validate-osi").description("Validate a s
950
1277
  }
951
1278
  });
952
1279
 
1280
+ // src/commands/enrich.ts
1281
+ import { Command as Command13 } from "commander";
1282
+ import chalk14 from "chalk";
1283
+ import path12 from "path";
1284
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync2, readdirSync } from "fs";
1285
+ import * as yaml from "yaml";
1286
+ import {
1287
+ compile as compile9,
1288
+ loadConfig as loadConfig9,
1289
+ computeTier as computeTier2,
1290
+ createAdapter as createAdapter4,
1291
+ suggestEnrichments,
1292
+ inferSemanticRole,
1293
+ inferAggregation
1294
+ } from "@runcontext/core";
1295
+ function findFileRecursive(dir, suffix) {
1296
+ if (!existsSync2(dir)) return void 0;
1297
+ const entries = readdirSync(dir, { withFileTypes: true });
1298
+ for (const entry of entries) {
1299
+ const fullPath = path12.join(dir, entry.name);
1300
+ if (entry.isDirectory()) {
1301
+ const found = findFileRecursive(fullPath, suffix);
1302
+ if (found) return found;
1303
+ } else if (entry.name.endsWith(suffix)) {
1304
+ return fullPath;
1305
+ }
1306
+ }
1307
+ return void 0;
1308
+ }
1309
+ var enrichCommand = new Command13("enrich").description("Suggest or apply metadata enrichments to reach a target tier").option("--target <tier>", "Target tier: silver or gold", "silver").option("--apply", "Write suggestions to YAML files").option("--source <name>", "Data source for sample values").option("--db <url>", "Database URL for sample values").option("--context-dir <path>", "Path to context directory").action(async (opts) => {
1310
+ try {
1311
+ const config = loadConfig9(process.cwd());
1312
+ const contextDir = opts.contextDir ? path12.resolve(opts.contextDir) : path12.resolve(config.context_dir);
1313
+ const target = opts.target;
1314
+ if (!["silver", "gold"].includes(target)) {
1315
+ console.error(chalk14.red('--target must be "silver" or "gold"'));
1316
+ process.exit(1);
1317
+ }
1318
+ const { graph } = await compile9({ contextDir, config });
1319
+ for (const [modelName] of graph.models) {
1320
+ const tierScore = computeTier2(modelName, graph);
1321
+ console.log(chalk14.bold(`${modelName}: ${tierScore.tier.toUpperCase()}`));
1322
+ if (tierScore.tier === target || target === "silver" && tierScore.tier === "gold") {
1323
+ console.log(chalk14.green(` Already at ${target} or above.
1324
+ `));
1325
+ continue;
1326
+ }
1327
+ const model = graph.models.get(modelName);
1328
+ const datasetNames = model.datasets.map((d) => d.name);
1329
+ const suggestions = suggestEnrichments(target, tierScore, datasetNames);
1330
+ if (!suggestions.governance && !suggestions.lineage && !suggestions.glossaryTerms && !suggestions.needsRulesFile && !suggestions.needsSampleValues && !suggestions.needsSemanticRoles) {
1331
+ console.log(chalk14.green(" No suggestions needed.\n"));
1332
+ continue;
1333
+ }
1334
+ if (suggestions.governance?.trust) {
1335
+ console.log(chalk14.yellow(` + Add trust: ${suggestions.governance.trust}`));
1336
+ }
1337
+ if (suggestions.governance?.tags) {
1338
+ console.log(chalk14.yellow(` + Add tags: [${suggestions.governance.tags.join(", ")}]`));
1339
+ }
1340
+ if (suggestions.governance?.refreshAll) {
1341
+ console.log(chalk14.yellow(` + Add refresh: ${suggestions.governance.refreshAll}`));
1342
+ }
1343
+ if (suggestions.lineage) {
1344
+ console.log(chalk14.yellow(` + Add lineage with ${suggestions.lineage.upstream?.length ?? 0} upstream sources`));
1345
+ }
1346
+ if (suggestions.glossaryTerms) {
1347
+ console.log(chalk14.yellow(` + Generate ${suggestions.glossaryTerms.length} glossary term(s)`));
1348
+ }
1349
+ if (suggestions.needsSampleValues) {
1350
+ console.log(chalk14.yellow(" + Populate sample_values from database"));
1351
+ }
1352
+ if (suggestions.needsSemanticRoles) {
1353
+ console.log(chalk14.yellow(" + Infer semantic_role for all fields"));
1354
+ }
1355
+ if (suggestions.needsRulesFile) {
1356
+ console.log(chalk14.yellow(" + Generate rules file"));
1357
+ }
1358
+ if (!opts.apply) {
1359
+ console.log(chalk14.cyan("\n Run with --apply to write these changes.\n"));
1360
+ continue;
1361
+ }
1362
+ const govFilePath = findFileRecursive(contextDir, `${modelName}.governance.yaml`);
1363
+ if (govFilePath) {
1364
+ const govContent = readFileSync3(govFilePath, "utf-8");
1365
+ const govDoc = yaml.parse(govContent) ?? {};
1366
+ if (suggestions.governance?.trust) {
1367
+ govDoc.trust = suggestions.governance.trust;
1368
+ }
1369
+ if (suggestions.governance?.tags) {
1370
+ govDoc.tags = suggestions.governance.tags;
1371
+ }
1372
+ if (suggestions.governance?.refreshAll) {
1373
+ for (const dsName of Object.keys(govDoc.datasets ?? {})) {
1374
+ govDoc.datasets[dsName].refresh = suggestions.governance.refreshAll;
1375
+ }
1376
+ }
1377
+ if (suggestions.needsSemanticRoles) {
1378
+ govDoc.fields = govDoc.fields ?? {};
1379
+ let adapter = null;
1380
+ const dsConfig = opts.db ? parseDbUrl(opts.db) : config.data_sources?.[opts.source ?? Object.keys(config.data_sources ?? {})[0]];
1381
+ if (dsConfig) {
1382
+ adapter = await createAdapter4(dsConfig);
1383
+ await adapter.connect();
1384
+ }
1385
+ for (const ds of model.datasets) {
1386
+ let columns = [];
1387
+ if (adapter) {
1388
+ const tableName = ds.source?.split(".").pop() ?? ds.name;
1389
+ try {
1390
+ columns = await adapter.listColumns(tableName);
1391
+ } catch {
1392
+ }
1393
+ }
1394
+ for (const field of ds.fields ?? []) {
1395
+ const fieldKey = `${ds.name}.${field.name}`;
1396
+ if (govDoc.fields[fieldKey]?.semantic_role) continue;
1397
+ const col = columns.find((c) => c.name === field.name);
1398
+ const isPK = col?.is_primary_key ?? field.name.endsWith("_id");
1399
+ const dataType = col?.data_type ?? "VARCHAR";
1400
+ govDoc.fields[fieldKey] = govDoc.fields[fieldKey] ?? {};
1401
+ const role = inferSemanticRole(field.name, dataType, isPK);
1402
+ govDoc.fields[fieldKey].semantic_role = role;
1403
+ if (role === "metric") {
1404
+ govDoc.fields[fieldKey].default_aggregation = inferAggregation(field.name);
1405
+ govDoc.fields[fieldKey].additive = govDoc.fields[fieldKey].default_aggregation === "SUM";
1406
+ }
1407
+ }
1408
+ }
1409
+ if (adapter) await adapter.disconnect();
1410
+ }
1411
+ if (suggestions.needsSampleValues) {
1412
+ govDoc.fields = govDoc.fields ?? {};
1413
+ const dsConfig2 = opts.db ? parseDbUrl(opts.db) : config.data_sources?.[opts.source ?? Object.keys(config.data_sources ?? {})[0]];
1414
+ if (dsConfig2) {
1415
+ const adapter2 = await createAdapter4(dsConfig2);
1416
+ await adapter2.connect();
1417
+ let count = 0;
1418
+ for (const ds of model.datasets) {
1419
+ if (count >= 2) break;
1420
+ const tableName = ds.source?.split(".").pop() ?? ds.name;
1421
+ for (const field of ds.fields ?? []) {
1422
+ if (count >= 2) break;
1423
+ const fieldKey = `${ds.name}.${field.name}`;
1424
+ if (govDoc.fields[fieldKey]?.sample_values?.length > 0) continue;
1425
+ try {
1426
+ const result = await adapter2.query(
1427
+ `SELECT DISTINCT CAST("${field.name}" AS VARCHAR) AS val FROM "${tableName}" WHERE "${field.name}" IS NOT NULL LIMIT 5`
1428
+ );
1429
+ if (result.rows.length > 0) {
1430
+ govDoc.fields[fieldKey] = govDoc.fields[fieldKey] ?? {};
1431
+ govDoc.fields[fieldKey].sample_values = result.rows.map((r) => String(r.val));
1432
+ count++;
1433
+ }
1434
+ } catch {
1435
+ }
1436
+ }
1437
+ }
1438
+ await adapter2.disconnect();
1439
+ }
1440
+ }
1441
+ writeFileSync4(govFilePath, yaml.stringify(govDoc, { lineWidth: 120 }), "utf-8");
1442
+ console.log(chalk14.green(` Updated: ${path12.relative(process.cwd(), govFilePath)}`));
1443
+ }
1444
+ if (suggestions.lineage) {
1445
+ const lineageDir = path12.join(contextDir, "lineage");
1446
+ if (!existsSync2(lineageDir)) mkdirSync2(lineageDir, { recursive: true });
1447
+ const lineagePath = path12.join(lineageDir, `${modelName}.lineage.yaml`);
1448
+ if (!existsSync2(lineagePath)) {
1449
+ const lineageDoc = {
1450
+ model: modelName,
1451
+ upstream: suggestions.lineage.upstream
1452
+ };
1453
+ writeFileSync4(lineagePath, yaml.stringify(lineageDoc, { lineWidth: 120 }), "utf-8");
1454
+ console.log(chalk14.green(` Created: ${path12.relative(process.cwd(), lineagePath)}`));
1455
+ }
1456
+ }
1457
+ if (suggestions.glossaryTerms) {
1458
+ const glossaryDir = path12.join(contextDir, "glossary");
1459
+ if (!existsSync2(glossaryDir)) mkdirSync2(glossaryDir, { recursive: true });
1460
+ for (const term of suggestions.glossaryTerms) {
1461
+ const termPath = path12.join(glossaryDir, `${term.id}.term.yaml`);
1462
+ if (!existsSync2(termPath)) {
1463
+ writeFileSync4(termPath, yaml.stringify(term, { lineWidth: 120 }), "utf-8");
1464
+ console.log(chalk14.green(` Created: ${path12.relative(process.cwd(), termPath)}`));
1465
+ }
1466
+ }
1467
+ }
1468
+ if (suggestions.needsRulesFile) {
1469
+ const rulesDir = path12.join(contextDir, "rules");
1470
+ if (!existsSync2(rulesDir)) mkdirSync2(rulesDir, { recursive: true });
1471
+ const rulesPath = path12.join(rulesDir, `${modelName}.rules.yaml`);
1472
+ if (!existsSync2(rulesPath)) {
1473
+ const rulesDoc = {
1474
+ model: modelName,
1475
+ golden_queries: [
1476
+ { question: "TODO: What is the total count?", sql: "SELECT COUNT(*) FROM table_name" },
1477
+ { question: "TODO: What are the top records?", sql: "SELECT * FROM table_name LIMIT 10" },
1478
+ { question: "TODO: What is the distribution?", sql: "SELECT column, COUNT(*) FROM table_name GROUP BY column" }
1479
+ ],
1480
+ business_rules: [
1481
+ { name: "TODO: rule-name", definition: "TODO: describe the business rule" }
1482
+ ],
1483
+ guardrail_filters: [
1484
+ { name: "TODO: filter-name", filter: "column IS NOT NULL", reason: "TODO: explain why" }
1485
+ ],
1486
+ hierarchies: [
1487
+ { name: "TODO: hierarchy-name", levels: ["level1", "level2"], dataset: datasetNames[0] ?? "dataset" }
1488
+ ]
1489
+ };
1490
+ writeFileSync4(rulesPath, yaml.stringify(rulesDoc, { lineWidth: 120 }), "utf-8");
1491
+ console.log(chalk14.green(` Created: ${path12.relative(process.cwd(), rulesPath)} (with TODOs)`));
1492
+ }
1493
+ }
1494
+ console.log("");
1495
+ }
1496
+ } catch (err) {
1497
+ console.error(chalk14.red(`Enrich failed: ${err.message}`));
1498
+ process.exit(1);
1499
+ }
1500
+ });
1501
+
953
1502
  // src/commands/rules.ts
954
- import { Command as Command11 } from "commander";
955
- import chalk12 from "chalk";
956
- import { ALL_RULES as ALL_RULES4 } from "@runcontext/core";
1503
+ import { Command as Command14 } from "commander";
1504
+ import chalk15 from "chalk";
1505
+ import { ALL_RULES as ALL_RULES5 } from "@runcontext/core";
957
1506
  function formatRuleTable(rules) {
958
1507
  if (rules.length === 0) {
959
- return chalk12.gray("No rules match the filters.");
1508
+ return chalk15.gray("No rules match the filters.");
960
1509
  }
961
1510
  const lines = [];
962
1511
  const header = `${"ID".padEnd(40)} ${"Tier".padEnd(8)} ${"Severity".padEnd(10)} ${"Fix".padEnd(5)} Description`;
963
- lines.push(chalk12.bold(header));
964
- lines.push(chalk12.gray("\u2500".repeat(100)));
1512
+ lines.push(chalk15.bold(header));
1513
+ lines.push(chalk15.gray("\u2500".repeat(100)));
965
1514
  for (const rule of rules) {
966
1515
  const tier = rule.tier ?? "\u2014";
967
1516
  const tierCol = colorTier(tier);
968
- const fixCol = rule.fixable ? chalk12.green("yes") : chalk12.gray("no");
969
- const sevCol = rule.defaultSeverity === "error" ? chalk12.red(rule.defaultSeverity) : chalk12.yellow(rule.defaultSeverity);
970
- const deprecated = rule.deprecated ? chalk12.gray(" (deprecated)") : "";
1517
+ const fixCol = rule.fixable ? chalk15.green("yes") : chalk15.gray("no");
1518
+ const sevCol = rule.defaultSeverity === "error" ? chalk15.red(rule.defaultSeverity) : chalk15.yellow(rule.defaultSeverity);
1519
+ const deprecated = rule.deprecated ? chalk15.gray(" (deprecated)") : "";
971
1520
  lines.push(
972
1521
  `${rule.id.padEnd(40)} ${tierCol.padEnd(8 + (tierCol.length - tier.length))} ${sevCol.padEnd(10 + (sevCol.length - rule.defaultSeverity.length))} ${fixCol.padEnd(5 + (fixCol.length - (rule.fixable ? 3 : 2)))} ${rule.description}${deprecated}`
973
1522
  );
974
1523
  }
975
1524
  lines.push("");
976
- lines.push(chalk12.gray(`${rules.length} rule(s) total`));
1525
+ lines.push(chalk15.gray(`${rules.length} rule(s) total`));
977
1526
  return lines.join("\n");
978
1527
  }
979
1528
  function colorTier(tier) {
980
1529
  switch (tier) {
981
1530
  case "gold":
982
- return chalk12.yellow(tier);
1531
+ return chalk15.yellow(tier);
983
1532
  case "silver":
984
- return chalk12.white(tier);
1533
+ return chalk15.white(tier);
985
1534
  case "bronze":
986
- return chalk12.hex("#CD7F32")(tier);
1535
+ return chalk15.hex("#CD7F32")(tier);
987
1536
  default:
988
- return chalk12.gray(tier);
1537
+ return chalk15.gray(tier);
989
1538
  }
990
1539
  }
991
- var rulesCommand = new Command11("rules").description("List all lint rules with metadata").option("--tier <tier>", "Filter by tier: bronze, silver, gold").option("--fixable", "Show only fixable rules").option("--format <type>", "Output format: pretty or json", "pretty").action((opts) => {
992
- let rules = [...ALL_RULES4];
1540
+ var rulesCommand = new Command14("rules").description("List all lint rules with metadata").option("--tier <tier>", "Filter by tier: bronze, silver, gold").option("--fixable", "Show only fixable rules").option("--format <type>", "Output format: pretty or json", "pretty").action((opts) => {
1541
+ let rules = [...ALL_RULES5];
993
1542
  if (opts.tier) {
994
1543
  const tier = opts.tier;
995
1544
  rules = rules.filter((r) => r.tier === tier);
@@ -1014,8 +1563,8 @@ var rulesCommand = new Command11("rules").description("List all lint rules with
1014
1563
  });
1015
1564
 
1016
1565
  // src/index.ts
1017
- var program = new Command12();
1018
- program.name("context").description("ContextKit \u2014 AI-ready metadata governance over OSI").version("0.2.1");
1566
+ var program = new Command15();
1567
+ program.name("context").description("ContextKit \u2014 AI-ready metadata governance over OSI").version("0.3.1");
1019
1568
  program.addCommand(lintCommand);
1020
1569
  program.addCommand(buildCommand);
1021
1570
  program.addCommand(tierCommand);
@@ -1026,6 +1575,9 @@ program.addCommand(initCommand);
1026
1575
  program.addCommand(siteCommand);
1027
1576
  program.addCommand(serveCommand);
1028
1577
  program.addCommand(validateOsiCommand);
1578
+ program.addCommand(introspectCommand);
1579
+ program.addCommand(verifyCommand);
1580
+ program.addCommand(enrichCommand);
1029
1581
  program.addCommand(rulesCommand);
1030
1582
  program.parse();
1031
1583
  //# sourceMappingURL=index.js.map