Class: OKF::CLI::Lint

Inherits:
Command show all
Defined in:
lib/okf/cli/lint.rb

Overview

The curation judge: is this bundle good? Advisory by design — findings never change the exit code unless --fail-on says so, because a stub or a loose leaf can be deliberate. Freshness is off unless --stale-after asks.

Constant Summary collapse

LINT_CATEGORIES =

Lint findings grouped for display, in category order.

{
  "Reachability" => %i[orphan not_in_index disconnected_component unlinked],
  "Backlog" => %i[missing_concept broken_index_entry],
  "Completeness" => %i[stub missing_title missing_description missing_generated],
  "Freshness" => %i[expired stale],
  "Provenance" => %i[uncited_external broken_source unattributed_claim unused_source unprefixed_actor],
  "Attestation" => %i[incomplete_computation broken_attestation_ref],
  "Migration" => %i[legacy_timestamp legacy_citations],
  "Hygiene" => %i[duplicate_title unused_reference_def undefined_reference self_link log_order]
}.freeze

Constants inherited from Command

Command::DUCK_TYPE, Command::FILTER_KEYS

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Command

hidden?, #initialize

Constructor Details

This class inherits a constructor from OKF::CLI::Command

Class Method Details

.groupObject



30
31
32
# File 'lib/okf/cli/lint.rb', line 30

def self.group
  :judge
end

.help_rowsObject



34
35
36
37
38
# File 'lib/okf/cli/lint.rb', line 34

def self.help_rows
  [
    [ "lint      <dir|@slug> [--json] [--fail-on LEVEL] [...]", "report curation-quality issues" ]
  ]
end

.idObject

--today reason for existing, in one line: a CI consumer wanting a reproducible expired report needs a fixed clock. Semver-stable API, documented in the skill's cli.md; absent, the CLI reads the wall clock — the library default (Linter.call with no today:) reads none at all.



26
27
28
# File 'lib/okf/cli/lint.rb', line 26

def self.id
  :lint
end

Instance Method Details

#call(argv) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/okf/cli/lint.rb', line 40

def call(argv)
  options = { json: false, min_body: OKF::Bundle::Linter::DEFAULT_MIN_BODY, stale_after: nil,
              today: nil, only: nil, except: nil, fail_on: :never }
  parser = OptionParser.new do |o|
    o.banner = "Usage: okf lint <dir|@slug> [--json] [--min-body N] [--stale-after DUR] [--today DATE] " \
               "[--only a,b] [--except a,b] [--fail-on never|info|warn]"
    json_flags(o, options, "emit a JSON report")
    o.on("--min-body N", Integer, "stub threshold in body characters (default #{OKF::Bundle::Linter::DEFAULT_MIN_BODY})") { |v| options[:min_body] = v }
    o.on("--stale-after DUR", "flag concepts older than DUR (e.g. 90d, 12w, 2026-01-01,",
      "or a full 2026-01-01T09:00:00Z timestamp)") { |v| options[:stale_after] = v }
    o.on("--today DATE", "the day `expired` compares stale_after against (default: today)") { |v| options[:today] = v }
    o.on("--only LIST", Array, "run only these checks (comma-separated)") { |v| options[:only] = v.map(&:to_sym) }
    o.on("--except LIST", Array, "skip these checks (comma-separated)") { |v| options[:except] = v.map(&:to_sym) }
    o.on("--fail-on LEVEL", %w[never info warn], "exit 1 when a finding at LEVEL exists (never | info | warn)") { |v| options[:fail_on] = v.to_sym }
    help_flag(o)
  end
  dir = positional_dir(parser, argv) or return 2

  unknown = ((options[:only] || []) + (options[:except] || [])) - OKF::Bundle::Linter::CHECKS
  unless unknown.empty?
    @err.puts "error: unknown check(s): #{unknown.uniq.join(", ")}"
    return 2
  end

  stale_before = parse_stale_after(options[:stale_after])
  if stale_before == :invalid
    @err.puts "error: invalid --stale-after `#{options[:stale_after]}` (use 90d, 12w, or an ISO date like 2026-01-01)"
    return 2
  end

  today = parse_today(options[:today])
  if today == :invalid
    @err.puts "error: invalid --today `#{options[:today]}` (use YYYY-MM-DD)"
    return 2
  end

  folder = OKF::Bundle::Folder.load(dir)
  report = folder.lint(min_body: options[:min_body], stale_before: stale_before, today: today,
    only: options[:only], except: options[:except])
  note_skipped(report.stats[:skipped])
  options[:json] ? print_lint_json(dir, report) : print_lint(dir, report)
  lint_exit(options[:fail_on], report)
end