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_timestamp],
  "Freshness" => %i[stale],
  "Provenance" => %i[uncited_external broken_citation],
  "Hygiene" => %i[duplicate_title unused_reference_def undefined_reference self_link]
}.freeze

Constants inherited from Command

Command::DUCK_TYPE

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



23
24
25
# File 'lib/okf/cli/lint.rb', line 23

def self.group
  :judge
end

.help_rowsObject



27
28
29
30
31
# File 'lib/okf/cli/lint.rb', line 27

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

.idObject



19
20
21
# File 'lib/okf/cli/lint.rb', line 19

def self.id
  :lint
end

Instance Method Details

#call(argv) ⇒ Object



33
34
35
36
37
38
39
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
# File 'lib/okf/cli/lint.rb', line 33

def call(argv)
  options = { json: false, min_body: OKF::Bundle::Linter::DEFAULT_MIN_BODY, stale_after: 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] [--only a,b] [--except a,b] [--fail-on 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)") { |v| options[:stale_after] = 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 warn], "exit 1 when a finding at LEVEL exists (never | 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

  folder = OKF::Bundle::Folder.load(dir)
  report = folder.lint(min_body: options[:min_body], stale_before: stale_before, only: options[:only], except: options[:except])
  note_skipped(report.stats[:skipped])
  options[:json] ? print_lint_json(dir, report) : print_lint(dir, report)
  options[:fail_on] == :warn && report.warnings.any? ? 1 : 0
end