jekyll-theme-zer0 1.22.0 → 1.24.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/_data/ai.yml +10 -0
- data/_data/backlog.yml +65 -37
- data/_data/features.yml +847 -34
- data/_data/navigation/docs.yml +4 -0
- data/_data/theme_backgrounds.yml +24 -0
- data/_includes/components/component-showcase.html +77 -16
- data/_includes/components/feature-card.html +15 -0
- data/_includes/components/search-modal.html +28 -4
- data/_includes/components/zer0-env-var.html +7 -1
- data/_includes/core/color-mode-init.html +35 -0
- data/_includes/core/head.html +9 -0
- data/_includes/core/tokens-inline.html +5 -0
- data/_layouts/author.html +22 -1
- data/_layouts/root.html +15 -1
- data/scripts/bin/validate +18 -1
- data/scripts/ci/classify_changes.py +129 -0
- data/scripts/issues/dispatch.py +478 -0
- data/scripts/issues/test_verify_close.py +118 -0
- data/scripts/issues/triage.py +1046 -0
- data/scripts/issues/verify_close.py +179 -0
- data/scripts/validate-features.rb +142 -0
- metadata +10 -2
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
verify_close.py — the deterministic gate of the Issue Autopilot verify-and-close lane.
|
|
4
|
+
|
|
5
|
+
The issue-verifier (an LLM, read-only) inspects each `verify_candidate` issue and
|
|
6
|
+
writes its verdicts to `.issues/verify.json`. This script is the DETERMINISTIC half:
|
|
7
|
+
it selects which of those verdicts may actually close an issue, and it does so ONLY
|
|
8
|
+
when `main`'s full CI/CD gate suite is green. The autopilot never closes a human
|
|
9
|
+
issue on an LLM's say-so alone — the green-CI gate here is the hard backstop.
|
|
10
|
+
|
|
11
|
+
A verdict closes its issue iff ALL of:
|
|
12
|
+
1. verdict.resolved is true,
|
|
13
|
+
2. verdict.confidence == "high" and verdict.evidence is non-empty,
|
|
14
|
+
3. the issue is a `verify_candidate` in plan.json (defense in depth — never a
|
|
15
|
+
protected/backlog-managed/bot/epic issue, even if the LLM names one),
|
|
16
|
+
4. `main`'s combined commit status + every check-run on its HEAD are green.
|
|
17
|
+
|
|
18
|
+
`select` prints `<number>\t<evidence>` TSV for closable issues (the workflow does
|
|
19
|
+
the actual `gh issue close`, keeping the irreversible mutation in an auditable
|
|
20
|
+
step). If the CI/CD gate is not green, it prints NOTHING and the whole batch is
|
|
21
|
+
held — "close only if they pass all CI/CD gates" is all-or-nothing per run.
|
|
22
|
+
|
|
23
|
+
READ-ONLY against GitHub: this script only ever calls `gh api` GET endpoints. It
|
|
24
|
+
never closes, comments, or labels. Verdict text is treated strictly as DATA.
|
|
25
|
+
|
|
26
|
+
Subcommands:
|
|
27
|
+
select print closable `<number>\t<evidence>` lines (gated on green CI)
|
|
28
|
+
gate print "green" / "not-green: <why>" for main's CI suite, then exit 0/1
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import json
|
|
34
|
+
import subprocess
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any, Optional
|
|
38
|
+
|
|
39
|
+
ISSUES_DIR = Path(".issues")
|
|
40
|
+
PLAN_PATH = ISSUES_DIR / "plan.json"
|
|
41
|
+
VERIFY_PATH = ISSUES_DIR / "verify.json"
|
|
42
|
+
|
|
43
|
+
# check-run conclusions that do NOT block a close (everything else does, and any
|
|
44
|
+
# check-run that has not `completed` blocks too — something is still running).
|
|
45
|
+
OK_CONCLUSIONS = {"success", "neutral", "skipped"}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def warn(msg: str) -> None:
|
|
49
|
+
print(f"::warning::verify_close: {msg}", file=sys.stderr)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _gh_api(path: str) -> Optional[Any]:
|
|
53
|
+
"""GET a gh api endpoint, returning parsed JSON or None on any failure."""
|
|
54
|
+
try:
|
|
55
|
+
out = subprocess.run(
|
|
56
|
+
["gh", "api", "-H", "Accept: application/vnd.github+json", path],
|
|
57
|
+
capture_output=True, text=True, timeout=60,
|
|
58
|
+
)
|
|
59
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
60
|
+
warn(f"gh api {path} failed to run: {exc}")
|
|
61
|
+
return None
|
|
62
|
+
if out.returncode != 0:
|
|
63
|
+
warn(f"gh api {path} exited {out.returncode}: {out.stderr.strip()[:200]}")
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
return json.loads(out.stdout or "null")
|
|
67
|
+
except json.JSONDecodeError as exc:
|
|
68
|
+
warn(f"gh api {path} returned non-JSON: {exc}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def ci_gate(repo: str, ref: str = "main") -> tuple[bool, str]:
|
|
73
|
+
"""
|
|
74
|
+
True iff `main`'s combined commit status AND every check-run on its HEAD are
|
|
75
|
+
green. Fails CLOSED: any API error, pending run, or failure -> (False, why).
|
|
76
|
+
"""
|
|
77
|
+
if not repo:
|
|
78
|
+
return False, "no repo configured"
|
|
79
|
+
|
|
80
|
+
status = _gh_api(f"repos/{repo}/commits/{ref}/status")
|
|
81
|
+
if status is None:
|
|
82
|
+
return False, "could not read commit status (failing closed)"
|
|
83
|
+
state = str(status.get("state") or "")
|
|
84
|
+
total = int(status.get("total_count") or 0)
|
|
85
|
+
# A commit with zero classic statuses reports state=pending; that's only a
|
|
86
|
+
# blocker if there are actually statuses. With statuses, require success.
|
|
87
|
+
if total > 0 and state != "success":
|
|
88
|
+
return False, f"combined commit status is '{state}' ({total} status(es))"
|
|
89
|
+
|
|
90
|
+
runs = _gh_api(f"repos/{repo}/commits/{ref}/check-runs?per_page=100")
|
|
91
|
+
if runs is None:
|
|
92
|
+
return False, "could not read check-runs (failing closed)"
|
|
93
|
+
for run in runs.get("check_runs") or []:
|
|
94
|
+
name = str(run.get("name") or "?")
|
|
95
|
+
if str(run.get("status")) != "completed":
|
|
96
|
+
return False, f"check-run '{name}' is not completed (status={run.get('status')})"
|
|
97
|
+
if str(run.get("conclusion")) not in OK_CONCLUSIONS:
|
|
98
|
+
return False, f"check-run '{name}' concluded '{run.get('conclusion')}'"
|
|
99
|
+
return True, f"green (state={state or 'none'}, {total} status(es), all check-runs passed)"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _load(path: Path) -> Optional[Any]:
|
|
103
|
+
if not path.exists():
|
|
104
|
+
warn(f"{path} not found")
|
|
105
|
+
return None
|
|
106
|
+
try:
|
|
107
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
108
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
109
|
+
warn(f"{path} unreadable: {exc}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def closable(plan: dict[str, Any], verify: dict[str, Any]) -> list[dict[str, Any]]:
|
|
114
|
+
"""Verdicts that pass the policy filter (steps 1-3), before the CI gate."""
|
|
115
|
+
candidates = {
|
|
116
|
+
int(r["number"]): r
|
|
117
|
+
for r in (plan.get("issues") or [])
|
|
118
|
+
if r.get("verify_candidate") and r.get("number") is not None
|
|
119
|
+
}
|
|
120
|
+
out: list[dict[str, Any]] = []
|
|
121
|
+
for v in (verify.get("verdicts") or []):
|
|
122
|
+
try:
|
|
123
|
+
num = int(v.get("number"))
|
|
124
|
+
except (TypeError, ValueError):
|
|
125
|
+
continue
|
|
126
|
+
if not v.get("resolved"):
|
|
127
|
+
continue
|
|
128
|
+
if str(v.get("confidence") or "").lower() != "high":
|
|
129
|
+
continue
|
|
130
|
+
evidence = str(v.get("evidence") or "").strip()
|
|
131
|
+
if not evidence:
|
|
132
|
+
continue
|
|
133
|
+
if num not in candidates: # defense in depth — never close a non-candidate
|
|
134
|
+
warn(f"#{num} verdict resolved but not a verify_candidate — skipping")
|
|
135
|
+
continue
|
|
136
|
+
out.append({"number": num, "evidence": evidence})
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
141
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
142
|
+
ap.add_argument("command", choices=["select", "gate"])
|
|
143
|
+
ap.add_argument("--repo", default=None, help="owner/name (else from plan.json)")
|
|
144
|
+
ap.add_argument("--ref", default="main", help="git ref to gate on (default: main)")
|
|
145
|
+
args = ap.parse_args(argv)
|
|
146
|
+
|
|
147
|
+
plan = _load(PLAN_PATH) or {}
|
|
148
|
+
repo = args.repo or str(plan.get("repo") or "")
|
|
149
|
+
|
|
150
|
+
if args.command == "gate":
|
|
151
|
+
green, why = ci_gate(repo, args.ref)
|
|
152
|
+
print("green" if green else f"not-green: {why}")
|
|
153
|
+
return 0 if green else 1
|
|
154
|
+
|
|
155
|
+
# select
|
|
156
|
+
verify = _load(VERIFY_PATH)
|
|
157
|
+
if not isinstance(verify, dict):
|
|
158
|
+
print("no verdicts to act on.", file=sys.stderr)
|
|
159
|
+
return 0
|
|
160
|
+
picks = closable(plan, verify)
|
|
161
|
+
if not picks:
|
|
162
|
+
print("no resolved+high-confidence verdicts for a verify_candidate.", file=sys.stderr)
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
green, why = ci_gate(repo, args.ref)
|
|
166
|
+
if not green:
|
|
167
|
+
warn(f"CI/CD gate not green — holding {len(picks)} close(s) this run: {why}")
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
print(f"CI/CD gate {why}", file=sys.stderr)
|
|
171
|
+
for p in picks:
|
|
172
|
+
# TSV: the workflow closes these; evidence goes into the close comment.
|
|
173
|
+
evidence = p["evidence"].replace("\t", " ").replace("\n", " ")
|
|
174
|
+
print(f"{p['number']}\t{evidence}")
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# ---------------------------------------------------------------------------
|
|
5
|
+
# validate-features.rb — canonical integrity checker for the feature registry.
|
|
6
|
+
#
|
|
7
|
+
# Single source of truth shared by `scripts/bin/validate` (preflight) and
|
|
8
|
+
# `test/test_features.sh` (the `features` test suite). Governance:
|
|
9
|
+
# .github/instructions/features.instructions.md.
|
|
10
|
+
#
|
|
11
|
+
# HARD failures (exit 1):
|
|
12
|
+
# - features/features.yml and _data/features.yml are not byte-identical
|
|
13
|
+
# - the header is missing a `# Last Updated:` line
|
|
14
|
+
# - schema violations (missing required field, bad id/version, duplicate id)
|
|
15
|
+
# - implemented:false without `removed_in:`
|
|
16
|
+
# - a reference path on an ACTIVE feature does not exist in the repo
|
|
17
|
+
# - missing/malformed `provenance:` block on an active feature (since PR B)
|
|
18
|
+
# - missing/unresolvable `tests:` linkage on an active feature (since PR C):
|
|
19
|
+
# every entry must be a real test path or `{na: reason}`
|
|
20
|
+
#
|
|
21
|
+
# WARNINGS (non-fatal unless FEATURES_STRICT=1):
|
|
22
|
+
# - header `# Version:` not tracking the gem version
|
|
23
|
+
# - id gaps (IDs are never reused, but gaps are worth a human glance)
|
|
24
|
+
#
|
|
25
|
+
# Portable across Ruby 2.6 (macOS system) and 3.x (CI/Docker).
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
require 'date'
|
|
29
|
+
require 'yaml'
|
|
30
|
+
|
|
31
|
+
MASTER = 'features/features.yml'
|
|
32
|
+
MIRROR = '_data/features.yml'
|
|
33
|
+
REQUIRED = %w[id title description implemented version link docs tags date].freeze
|
|
34
|
+
STRICT = ENV['FEATURES_STRICT'] == '1'
|
|
35
|
+
|
|
36
|
+
def die(msg)
|
|
37
|
+
warn " \e[31m✗\e[0m #{msg}"
|
|
38
|
+
exit 1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Ruby 3.1+ defaults YAML.load_file to safe loading (rejects Date); 2.6 does
|
|
42
|
+
# not accept the keywords. Try the strict form, fall back for old Ruby.
|
|
43
|
+
def load_yaml(path)
|
|
44
|
+
YAML.load_file(path, permitted_classes: [Date, Time], aliases: true)
|
|
45
|
+
rescue ArgumentError
|
|
46
|
+
YAML.load_file(path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ref_paths(refs)
|
|
50
|
+
return [] unless refs.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
refs.values.flat_map { |v| v.is_a?(Array) ? v : [v] }.select { |v| v.is_a?(String) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[MASTER, MIRROR].each { |f| die "#{f} is missing" unless File.file?(f) }
|
|
56
|
+
|
|
57
|
+
# 1. Sync contract — the two registries must be byte-identical.
|
|
58
|
+
if File.binread(MASTER) != File.binread(MIRROR)
|
|
59
|
+
die "#{MASTER} and #{MIRROR} differ — run `cp #{MASTER} #{MIRROR}` (must be byte-identical)"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
data = load_yaml(MIRROR)
|
|
63
|
+
feats = data && data['features']
|
|
64
|
+
die 'top-level `features:` list missing' unless feats.is_a?(Array)
|
|
65
|
+
|
|
66
|
+
warnings = []
|
|
67
|
+
|
|
68
|
+
# Header sanity.
|
|
69
|
+
header = File.foreach(MIRROR).first(8).join
|
|
70
|
+
gem_version = File.read('lib/jekyll-theme-zer0/version.rb')[/VERSION\s*=\s*"([^"]+)"/, 1]
|
|
71
|
+
warnings << "header `# Version:` should match gem version #{gem_version}" unless header.include?("# Version: #{gem_version}")
|
|
72
|
+
die 'header missing `# Last Updated: YYYY-MM-DD` line' unless header =~ /# Last Updated: \d{4}-\d{2}-\d{2}/
|
|
73
|
+
|
|
74
|
+
seen = {}
|
|
75
|
+
feats.each_with_index do |f, i|
|
|
76
|
+
die "entry ##{i} is not a mapping" unless f.is_a?(Hash)
|
|
77
|
+
id = f['id'] || "(index #{i})"
|
|
78
|
+
|
|
79
|
+
REQUIRED.each do |key|
|
|
80
|
+
val = f[key]
|
|
81
|
+
die "#{id}: required field `#{key}` is missing/empty" if val.nil? || (val.respond_to?(:empty?) && val.empty?)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
die "#{id}: id must match ZER0-NNN" unless f['id'] =~ /\AZER0-\d{3}\z/
|
|
85
|
+
die "#{id}: duplicate id" if seen[f['id']]
|
|
86
|
+
seen[f['id']] = true
|
|
87
|
+
die "#{id}: version must be X.Y.Z" unless f['version'].to_s =~ /\A\d+\.\d+\.\d+/
|
|
88
|
+
die "#{id}: tags must be a non-empty list" unless f['tags'].is_a?(Array) && !f['tags'].empty?
|
|
89
|
+
|
|
90
|
+
if f['implemented'] == false
|
|
91
|
+
# Removed features keep their (now-absent) references for history but must
|
|
92
|
+
# record when they went away.
|
|
93
|
+
die "#{id}: implemented:false requires `removed_in:`" unless f['removed_in']
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# 2. Every reference path on an active feature must exist.
|
|
98
|
+
ref_paths(f['references']).each do |p|
|
|
99
|
+
ok = p.end_with?('/') ? File.directory?(p) : File.exist?(p)
|
|
100
|
+
die "#{id}: reference path does not exist: #{p}" unless ok
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# 3a. Provenance is REQUIRED on every active feature (backfilled in PR B).
|
|
104
|
+
prov = f['provenance']
|
|
105
|
+
die "#{id}: missing `provenance:` block" unless prov.is_a?(Hash)
|
|
106
|
+
die "#{id}: provenance.introduced_in must be a version string" unless prov['introduced_in'].is_a?(String) && prov['introduced_in'] =~ /\A\d+\.\d+\.\d+/
|
|
107
|
+
die "#{id}: provenance.commit must be a short git hash" unless prov['commit'].is_a?(String) && prov['commit'] =~ /\A[0-9a-f]{7,40}\z/
|
|
108
|
+
die "#{id}: provenance.pr must be an integer or null" unless prov['pr'].nil? || prov['pr'].is_a?(Integer)
|
|
109
|
+
die "#{id}: provenance.issue must be an integer or null" unless prov['issue'].nil? || prov['issue'].is_a?(Integer)
|
|
110
|
+
|
|
111
|
+
# 3b. Test linkage is REQUIRED on every active feature (backfilled in PR C).
|
|
112
|
+
tests = f['tests']
|
|
113
|
+
die "#{id}: missing/empty `tests:` (a real test path or `{na: reason}`)" unless tests.is_a?(Array) && !tests.empty?
|
|
114
|
+
tests.each do |t|
|
|
115
|
+
if t.is_a?(String)
|
|
116
|
+
die "#{id}: tests entry does not exist: #{t}" unless File.exist?(t)
|
|
117
|
+
elsif t.is_a?(Hash)
|
|
118
|
+
die "#{id}: tests `na:` must carry a non-empty reason" unless t['na'].is_a?(String) && !t['na'].strip.empty?
|
|
119
|
+
else
|
|
120
|
+
die "#{id}: tests entries must be a path string or `{na: reason}`"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Sequential-id sanity (gaps allowed — never reuse an ID — but flagged).
|
|
126
|
+
nums = seen.keys.map { |k| k.split('-').last.to_i }.sort
|
|
127
|
+
gaps = (1..nums.last).to_a - nums
|
|
128
|
+
warnings << "id gaps (verify intentional): #{gaps.map { |n| format('ZER0-%03d', n) }.join(', ')}" unless gaps.empty?
|
|
129
|
+
|
|
130
|
+
unless warnings.empty?
|
|
131
|
+
verbose = STRICT || ENV['FEATURES_VERBOSE'] == '1'
|
|
132
|
+
prov = warnings.count { |w| w.include?('provenance') }
|
|
133
|
+
tests = warnings.count { |w| w.include?('`tests:`') }
|
|
134
|
+
other = warnings.length - prov - tests
|
|
135
|
+
warn " \e[33m⚠\e[0m feature registry: #{warnings.length} warning(s) — " \
|
|
136
|
+
"provenance:#{prov} tests:#{tests} other:#{other} (FEATURES_VERBOSE=1 for the full list)"
|
|
137
|
+
warnings.each { |w| warn " - #{w}" } if verbose
|
|
138
|
+
die "#{warnings.length} warning(s) treated as errors (FEATURES_STRICT=1)" if STRICT
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
active = feats.count { |f| f['implemented'] != false }
|
|
142
|
+
puts "Feature registry valid: #{feats.length} entries (#{active} active), refs resolved, master/_data in sync"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -78,6 +78,7 @@ files:
|
|
|
78
78
|
- LICENSE
|
|
79
79
|
- README.md
|
|
80
80
|
- _data/README.md
|
|
81
|
+
- _data/ai.yml
|
|
81
82
|
- _data/authors.yml
|
|
82
83
|
- _data/backlog.yml
|
|
83
84
|
- _data/content_statistics.yml
|
|
@@ -171,6 +172,7 @@ files:
|
|
|
171
172
|
- _includes/content/toc.html
|
|
172
173
|
- _includes/content/transclude.html
|
|
173
174
|
- _includes/core/branding.html
|
|
175
|
+
- _includes/core/color-mode-init.html
|
|
174
176
|
- _includes/core/footer-fabs.html
|
|
175
177
|
- _includes/core/footer.html
|
|
176
178
|
- _includes/core/head.html
|
|
@@ -384,6 +386,7 @@ files:
|
|
|
384
386
|
- scripts/bin/test
|
|
385
387
|
- scripts/bin/validate
|
|
386
388
|
- scripts/build
|
|
389
|
+
- scripts/ci/classify_changes.py
|
|
387
390
|
- scripts/content-review.rb
|
|
388
391
|
- scripts/convert-notebooks.sh
|
|
389
392
|
- scripts/dev/css-diff.sh
|
|
@@ -449,6 +452,10 @@ files:
|
|
|
449
452
|
- scripts/install/template.sh
|
|
450
453
|
- scripts/install/tui.sh
|
|
451
454
|
- scripts/install/upgrade.sh
|
|
455
|
+
- scripts/issues/dispatch.py
|
|
456
|
+
- scripts/issues/test_verify_close.py
|
|
457
|
+
- scripts/issues/triage.py
|
|
458
|
+
- scripts/issues/verify_close.py
|
|
452
459
|
- scripts/lib/README.md
|
|
453
460
|
- scripts/lib/audit.sh
|
|
454
461
|
- scripts/lib/changelog.sh
|
|
@@ -519,6 +526,7 @@ files:
|
|
|
519
526
|
- scripts/utils/fix-markdown
|
|
520
527
|
- scripts/utils/setup
|
|
521
528
|
- scripts/validate
|
|
529
|
+
- scripts/validate-features.rb
|
|
522
530
|
- scripts/vendor-install.sh
|
|
523
531
|
homepage: https://github.com/bamr87/zer0-mistakes
|
|
524
532
|
licenses:
|