Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
1b31796
Add skill-vetting skill + advisory delta-detector hook (Workstream B)
F-e-u-e-r Jul 24, 2026
32c6929
Rebuild skill-vetting advisory hook on a hardened snapshot primitive
F-e-u-e-r Jul 24, 2026
56a9c10
Harden skill-vetting snapshot layer per round-3 cross-family gate
F-e-u-e-r Jul 24, 2026
5320c0b
Fold round-4 gate: fix top-level enumeration regression + trust-bound…
F-e-u-e-r Jul 24, 2026
d744181
Fold round-5 gate: CLI/binding hardening + bounded walker fds
F-e-u-e-r Jul 24, 2026
d2ea586
Remove dead _fmt_path helper (replaced by _redacted_path)
F-e-u-e-r Jul 24, 2026
550689d
Fold round-6 gate: 12 must-fixes across five review lenses
F-e-u-e-r Jul 25, 2026
b427bf8
Correct a false claim in 550689d; fold round-7 mechanical findings
F-e-u-e-r Jul 25, 2026
f5e20d9
Correct the threat model's G3 claim to match the reverted control
F-e-u-e-r Jul 25, 2026
fee4a67
Add the round-8 design for the round-7 residue (DESIGN ONLY, nothing …
F-e-u-e-r Jul 25, 2026
f83925c
Fold the round-8 screen and the first design-gate pass
F-e-u-e-r Jul 25, 2026
54d0f4e
Fold screen pass 2: 15 claims, five of them created by the pass-1 fixes
F-e-u-e-r Jul 25, 2026
4d1378f
Fold screen pass 3: three siblings, and a method change
F-e-u-e-r Jul 25, 2026
944916a
Fold screen pass 4: cross-abstraction restatements the grep method ca…
F-e-u-e-r Jul 25, 2026
5050209
Fold screen pass 5: two more incomplete twins, two goal/invariant dis…
F-e-u-e-r Jul 25, 2026
a7ef071
Fold screen pass 6: a retired invariant still quoted in a test failur…
F-e-u-e-r Jul 25, 2026
2976dba
Fix a silent-miss regression I shipped in b427bf8, and the coverage g…
F-e-u-e-r Jul 25, 2026
e9c453a
Fold grok pass 8, including three findings I gathered at pass 7 and d…
F-e-u-e-r Jul 25, 2026
7b16f18
Fix the residual half of the round-6 budget poisoner (Opus pass 8)
F-e-u-e-r Jul 25, 2026
7d77f92
Fold the third screening family (Fable 5 max): three claims the other…
F-e-u-e-r Jul 25, 2026
86da3d3
Fold grok pass 9: a wrong silent-case attribution, and my own contrad…
F-e-u-e-r Jul 25, 2026
9c73954
Fold pass 9: a livelock I introduced at pass 8, and an underclaim cor…
F-e-u-e-r Jul 25, 2026
36123b3
Fold grok pass 10: four incomplete twins, all in text from the last t…
F-e-u-e-r Jul 25, 2026
2416d96
Fold pass 10: my dot-path fix laundered my own badname gate
F-e-u-e-r Jul 25, 2026
7ede0f0
Fold grok pass 11: the twin of the pass-10 dot-path fix, unpaid
F-e-u-e-r Jul 25, 2026
e8e3159
Fold Opus pass 11: two more record guards, and my own quoting rule as…
F-e-u-e-r Jul 25, 2026
a826527
Fold Fable pass 11: two peak-fd counts that disagreed, both mine
F-e-u-e-r Jul 25, 2026
c670479
Fold pass 12: the measurement I added to stop asserting a number was …
F-e-u-e-r Jul 25, 2026
38862c7
Remove a dead duplicate test, land a missed design twin, and make bot…
F-e-u-e-r Jul 25, 2026
e77096c
Correct a false verification claim in 38862c7; the duplicate check no…
F-e-u-e-r Jul 25, 2026
579e109
Fold grok pass 13: eight descriptions of a line I had already de-perf…
F-e-u-e-r Jul 25, 2026
74bc418
Fold Opus pass 13: three FALSE, two of them instruments I had just built
F-e-u-e-r Jul 25, 2026
af70238
Fold pass 13 in full: seven Opus findings, not the three I first read
F-e-u-e-r Jul 25, 2026
98d767c
Fold grok pass 14: three comments describing mechanisms the same fold…
F-e-u-e-r Jul 25, 2026
4c21179
Fold pass 14: the symlink guard I added at pass 13 was wrong three ways
F-e-u-e-r Jul 25, 2026
7c39c73
Fold pass 15: the mutation harness reported a result it never measured
F-e-u-e-r Jul 25, 2026
5042727
Fold round 8 P0: three more ways through the dot branch, none of them…
F-e-u-e-r Jul 25, 2026
d86fc0e
Fold round 8, batch 2: six defects the suite could not see
F-e-u-e-r Jul 25, 2026
d540fe2
Fold round 8, batch 3: the last five findings, and two equivalent mut…
F-e-u-e-r Jul 25, 2026
bcf1001
Fold the harness review: an equivalence claim that was simply false
F-e-u-e-r Jul 25, 2026
7459904
Name the dirty-tree override for what it does, and make the gate test…
F-e-u-e-r Jul 25, 2026
6a0bb01
Make the authoritative mode a gate instead of a sentence
F-e-u-e-r Jul 25, 2026
1b8ae20
Separate completeness from result, and stop losing the per-mutant record
F-e-u-e-r Jul 25, 2026
27e455c
A partial run could overwrite an authoritative run's evidence, and did
F-e-u-e-r Jul 26, 2026
7ad4e48
An authoritative run that records nothing must not exit 0
F-e-u-e-r Jul 26, 2026
e478480
Run identity by nonce, and prove main() consumes a writer failure
F-e-u-e-r Jul 26, 2026
498c786
Close every file the tools open, so a warning stream cannot hide a re…
F-e-u-e-r Jul 26, 2026
c545ee3
Name the unmet precondition when a critical test skips
F-e-u-e-r Jul 26, 2026
b28522c
Porcelain is not the definition of a clean tree (cross-family BLOCK)
F-e-u-e-r Jul 26, 2026
1301b0a
Every kill was measured without a control; the oracle also ignored ex…
F-e-u-e-r Jul 26, 2026
6dfec41
Bind the control to the evidence, and require each mutant to start clean
F-e-u-e-r Jul 26, 2026
7ee196e
Rebuild the worktree between mutants instead of inspecting it
F-e-u-e-r Jul 26, 2026
074d52a
State the isolation scope, and make "restored each time" re-derivable
F-e-u-e-r Jul 26, 2026
c26603e
The summary a human reads disagreed with the evidence a machine checks
F-e-u-e-r Jul 26, 2026
1de44b8
Two families, four BLOCKs: the checks were incomplete in four differe…
F-e-u-e-r Jul 26, 2026
8f625bd
CI red: the control refused, and could not say why
F-e-u-e-r Jul 26, 2026
320748a
A test that passed by luck of the filesystem
F-e-u-e-r Jul 26, 2026
ededb73
The assertion contradicted the comment directly above it
F-e-u-e-r Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
{
"name": "opus-pack",
"source": "./",
"description": "Nine agent-discipline skills: operational-rigor, delegation-and-review, ground-truth-gates, cross-model-review, skill-authoring, security-architect, product-roadmap, personal-goal-planning, domain-evidence-discipline. Hooks stay manual-install by design (per-user consent).",
"version": "0.1.15",
"description": "Ten agent-discipline skills: operational-rigor, delegation-and-review, ground-truth-gates, cross-model-review, skill-authoring, security-architect, product-roadmap, personal-goal-planning, domain-evidence-discipline, skill-vetting. Hooks stay manual-install by design (per-user consent).",
"version": "0.1.16",
"author": {
"name": "F-e-u-e-r"
}
Expand Down
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "opus-pack",
"description": "Nine agent-discipline skills for daily-driver Claude models: operational-rigor, delegation-and-review, ground-truth-gates, cross-model-review, skill-authoring, security-architect, product-roadmap, personal-goal-planning, domain-evidence-discipline. Few dense rules, executable gates over long prose; measured against its own doctrine, nulls included. The repo's hooks are NOT registered or enabled by this plugin - they change harness behavior and need per-user consent; see the README's hooks section for manual install.",
"version": "0.1.15",
"description": "Ten agent-discipline skills for daily-driver Claude models: operational-rigor, delegation-and-review, ground-truth-gates, cross-model-review, skill-authoring, security-architect, product-roadmap, personal-goal-planning, domain-evidence-discipline, skill-vetting. Few dense rules, executable gates over long prose; measured against its own doctrine, nulls included. The repo's hooks are NOT registered or enabled by this plugin - they change harness behavior and need per-user consent; see the README's hooks section for manual install.",
"version": "0.1.16",
"author": {
"name": "F-e-u-e-r"
},
Expand Down
66 changes: 64 additions & 2 deletions .github/checks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#!/usr/bin/env python3
"""Repo consistency checks. Run locally or in CI: python3 .github/checks.py

Scope: what the published repo carries, enumerated via `git ls-files` (the
Scope: what the published repo carries. NOTE which checks actually use that
enumeration: only the plugin-reachability check and the hidden-directive sweep
read the `tracked` list. Skill discovery and hook-entry discovery walk the
WORKING TREE with os.listdir, so an untracked directory under a skills root, or
an untracked script under hooks/, is seen by those and invisible to these
(round-8 screen, pass 13). Tracked-file enumeration is via `git ls-files` (the
.claude/ live-install copy and the private evals are gitignored - keeping
those in sync is a local concern, not a repo one). Fail direction: every
check fails CLOSED on what it claims to cover - a tracked file the sweep
Expand Down Expand Up @@ -40,7 +45,11 @@ def read(rel):

# 1. Every skill has a CLOSED frontmatter block (byte-exact fences) whose
# interior carries exactly one name: (== directory) and exactly one
# single-line description: long enough to be a load trigger. A trailing
# description: long enough to be a load trigger. NOTE what this does NOT
# establish: it collects lines matching ^description:(\s|$) and measures the
# remainder of THAT line, so a YAML continuation on the following lines is
# neither seen nor rejected - "single-line" describes what is measured, not
# a property that is enforced (round-8 screen). A trailing
# " #" comment is stripped before judging the value (YAML semantics);
# a bare "name:" counts as a duplicate entry, not nothing. Single-line
# description is a deliberate house-style tripwire (skill-authoring:
Expand Down Expand Up @@ -418,6 +427,59 @@ def nonempty_str(d, key):
if isinstance(plugin, dict) and "hooks" not in plugin and not os.path.exists(os.path.join(ROOT, "hooks", "hooks.json")):
ok("plugin registers no hooks (standing invariant holds)")

# 5. No test function is defined twice in a suite. Python keeps only the last
# definition, so a duplicate silently shadows an earlier one - the earlier
# body stops running while the suite still reports it as present. Found live
# at the round-8 screen: one hook test had been added twice by two folds of
# the same finding, and the shadowed copy was dead for several commits.
import collections as _collections
for _suite in ("hooks/test-skill_snapshot.py", "hooks/test-skill-vetting-advisory.py"):
_src = open(_suite).read()
_names = re.findall(r"^ def (test_\w+)\(", _src, re.M)
# A duplicate CLASS name shadows exactly as silently as a duplicate method:
# Python keeps the last, and every test method on the earlier class stops
# running while the file still shows them. Same failure, same invisibility
# (round-8 screen, pass 13).
_classes = re.findall(r"^class (\w+)\(", _src, re.M)
_dupes = sorted(n for n, c in _collections.Counter(_names).items() if c > 1)
_dupes += sorted("class " + n for n, c in _collections.Counter(_classes).items()
if c > 1)
if _dupes:
fail("%s defines these tests more than once, so the earlier copy is "
"dead code Python never runs: %s" % (_suite, ", ".join(_dupes)))
else:
# len(set(...)) - the definitions that actually RUN. len(_names) counts
# the shadowed copy too, and this ok() used to print in the same run as
# its own fail() because it was not in an else: the check contradicted
# itself one line apart, and reported 93 tests where 91 ran. Verifying
# the exit code is not verifying the output (round-8 screen, pass 13).
ok("%s has %d tests, no shadowed duplicates"
% (_suite, len(set(_names))))

# The mutation matrix's authoritative gate works by comparing the parsed
# argparse namespace against the parser's own defaults, which is sound ONLY
# while every input that can change a measurement is a command-line option. A
# later environment variable or config read would sit outside that comparison
# and could silently re-enable an override inside an "authoritative" run. That
# premise lives in the tool's docstring; this makes it a rule that has to be
# broken deliberately.
_matrix = os.path.join(ROOT, "hooks", "mutation_matrix.py")
if os.path.isfile(_matrix):
with open(_matrix, encoding="utf-8") as _fh:
_msrc = _fh.read()
_env_reads = [tok for tok in ("os.environ", "os.getenv", "configparser",
"tomllib", "json.load(open")
if tok in _msrc]
if _env_reads:
fail("hooks/mutation_matrix.py reads outside the command line (%s). "
"The authoritative gate compares the parsed namespace against "
"argparse defaults, so any other input source escapes it - add it "
"to that gate first, then update this check."
% ", ".join(_env_reads))
else:
ok("mutation_matrix.py takes no env/config input, so the authoritative "
"gate covers every measurement-changing option")

print()
if failures:
print(f"{len(failures)} check(s) failed")
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,26 @@ jobs:
- name: Run every hook test suite (discovered, not hard-coded)
run: |
set -euo pipefail
# Discovery order is the glob's, which is alphabetical - and that put
# test-mutation_matrix.sh ahead of the two suites its own pristine
# control runs. On the first Linux run those suites were red inside
# the matrix's worktree, the harness aborted, `set -e` stopped the
# job, and their standalone output never appeared. A tool whose
# control DEPENDS on other suites must not be able to mask them, so
# the harness runs last while discovery stays automatic.
found=0
last=""
for t in hooks/test-*.sh; do
case "$t" in *test-mutation_matrix.sh) last="$t"; continue;; esac
echo "== $t"
bash "$t"
found=1
done
if [ -n "$last" ]; then
echo "== $last (runs last: its control depends on the suites above)"
bash "$last"
found=1
fi
[ "$found" = "1" ] || { echo "no hook test suites discovered"; exit 1; }

gate-template:
Expand Down
76 changes: 65 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<p align="center">
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg"></a>
<img alt="Version alpha-0.1.15" src="https://img.shields.io/badge/version-alpha--0.1.15-orange.svg">
<img alt="Version alpha-0.1.16" src="https://img.shields.io/badge/version-alpha--0.1.16-orange.svg">
<img alt="For Claude Code" src="https://img.shields.io/badge/for-Claude%20Code-8A2BE2.svg">
<a href="https://github.com/F-e-u-e-r/opus-pack/issues"><img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"></a>
<a href="https://github.com/F-e-u-e-r/opus-pack/actions/workflows/checks.yml"><img alt="checks" src="https://github.com/F-e-u-e-r/opus-pack/actions/workflows/checks.yml/badge.svg"></a>
Expand All @@ -17,15 +17,15 @@
---

**Opus Pack is a Claude Code plugin marketplace** — one repo, two plugins you
install independently, 12 skills in total, for the daily-driver models that
install independently, 13 skills in total, for the daily-driver models that
remain after Fable 5's window closes (Opus 4.8 / Sonnet 5 / Haiku):

| Plugin | Focus | Installs |
|---|---|---|
| **`opus-pack`** | Agent discipline — how work gets done: rigor, delegation, verification, evidence | 9 skills |
| **`opus-pack`** | Agent discipline — how work gets done: rigor, delegation, verification, evidence | 10 skills |
| **`design-pack`** | Design-craft — visual/UI judgment in the same style: layout, motion, review | 3 skills |

Plus **three optional hooks** — repo-level, installed by hand; neither plugin
Plus **four optional hooks** — repo-level, installed by hand; neither plugin
registers them (see [Enforcement: hooks](#enforcement-setting-up-hooks)). All of
it encodes one bet: the judgment strong models already have improves less from
**more prose** than from **gates that fail loudly when the work is wrong.**
Expand All @@ -35,7 +35,7 @@ opus-pack simply resolve when opus-pack is present; see
[`design-pack`](#design-pack-the-design-skills)).

> [!NOTE]
> **Early alpha (`alpha-0.1.15`).** Rules change as real sessions expose misses,
> **Early alpha (`alpha-0.1.16`).** Rules change as real sessions expose misses,
> and the pack is [measured against its own doctrine](#evals-testing-the-pack-itself)
> — honest null result included. Issues and PRs with concrete failure cases are welcome.

Expand All @@ -60,7 +60,7 @@ want. Install targets use `plugin@marketplace`, and the marketplace ID is
/plugin install design-pack@opus-pack
```

`opus-pack@opus-pack` installs the discipline plugin (9 skills);
`opus-pack@opus-pack` installs the discipline plugin (10 skills);
`design-pack@opus-pack` installs the design plugin (3 skills). Install either,
or both. Skills arrive namespaced (`opus-pack:operational-rigor`,
`design-pack:ui-design-craft`, …) and update via `/plugin marketplace update`.
Expand Down Expand Up @@ -96,6 +96,7 @@ occupies context until triggered.
| `product-roadmap` | Product-owner lens: evidence before opinion, riskiest assumption first, Now/Next/Later/Not-now, milestones, adjacent-repo mining, three-way task split (agent/human/needs-info) | user-supplied roadmap reference draft — ceremony cut, judgment added |
| `personal-goal-planning` | Coach-style five steps: minimal intake, tiered goals (2–4w / 2–3m / 6–12m) with one mainline, executable tasks with observable done-criteria, realistic weekly rhythm, weekly review with a stuck rule | @pro_ai.news goal-coaching protocol (Threads) + this pack's house rules |
| `domain-evidence-discipline` | Evidence discipline for non-code deliverables (marketing / research / data / ops): per-domain minimum evidence set, authority order, what verification-by-observation means, and the fraud table a reviewer hunts; red-line professional judgment refused and routed to a qualified human | Sahir619/fable-method's domain-adapter schema (MIT, ideas only) condensed into one pattern skill; the worked instances are this pack's own compressions |
| `skill-vetting` | Vet a third-party skill / plugin / hook / instruction file for trojan patterns before it runs: turns operational-rigor §2's install gate into a runnable procedure, with a trojan-shape checklist and a fail-closed verdict (a clean scan is never "safe"); ships an opt-in advisory session-start tripwire (`hooks/skill-vetting-advisory.py`) | operational-rigor §2 (canonical) + the 2026-07 twelve-source community-security-skill audit and a 2026-07-24 starred-repo mining pass that caught a 4th live trojan — see Acknowledgements |
| `cross-model-review` | Adversarial review from a *different model family* before a load-bearing merge: session-time reviewer discovery (no hard-coded lineup), self-contained packet, findings-are-claims, bounded review-and-fix loop (merge only when every reviewer returned a confirmed verdict — each one PROCEED, or a FIX whose every remaining item is a recorded, justified gap; a timeout/empty body is not a verdict), exit-code≠pass. Doctrine only — concrete CLIs stay out of the pack | promoted from the owner's private cross-model-review CLI notes; doctrine generalized, machine recipes kept personal |

`ground-truth-gates/template/` was verified by execution (Node v23, 2026-07-06):
Expand Down Expand Up @@ -285,15 +286,19 @@ silently allowing one):
mkdir -p .claude/hooks
cp hooks/gate-before-commit.sh hooks/parse-commit-command.py .claude/hooks/
cp hooks/verify-before-stop.py hooks/gate-credential-destruction.py .claude/hooks/
cp hooks/skill-vetting-advisory.py hooks/skill_snapshot.py .claude/hooks/
```

Maintainers can regression-test every hook (each suite covers both the
allow path and the block path):
Maintainers can regression-test every hook (each suite covers both sides of
its hook's behavior — allow and block for the gating hooks, silent and
advisory for the advisory hook):

```bash
bash hooks/test-gate-before-commit.sh
bash hooks/test-verify-before-stop.sh
bash hooks/test-gate-credential-destruction.sh
bash hooks/test-skill-vetting-advisory.sh
bash hooks/test-skill_snapshot.sh
```

Then add to `.claude/settings.json`:
Expand Down Expand Up @@ -373,9 +378,58 @@ second command under the same `PreToolUse`/`Bash` matcher:
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/gate-credential-destruction.py" }
```

All three hooks append audit events to `~/.claude/hooks/hooks.log`, so
"how often does the gate fire, and what happened after a block" is auditable
instead of invisible.
**Fourth (optional) hook — an advisory tripwire for unvetted or changed
third-party skills.** `hooks/skill-vetting-advisory.py` (Python 3 stdlib, tested)
is a **pure-advisory** `SessionStart` hook, the companion to the `skill-vetting`
skill; its whole observation layer lives in the sibling module
`hooks/skill_snapshot.py` — install both files into the same directory (design
record: `reviews/2026-07-25-skill-vetting-snapshot-threat-model.md`).
**Signature scanning is not a security boundary and has been removed**: the
primitive snapshots every file under each entry of the watched skills roots
(`$CLAUDE_CONFIG_DIR/skills`, default `~/.claude/skills`, plus the project's
`.claude/skills` via `$CLAUDE_PROJECT_DIR`), so an add / modify / delete /
rename / symlink / filetype change anywhere inside a skill — not just in its
`SKILL.md` — counts (one carve-out, stated under G1 in the threat model: a loose
regular file sitting directly in the skills root is not a candidate at all,
because it is not loadable as a skill),
and whatever cannot be fully observed (a read error, an oversize file, a scan-budget
breach and everything enumerated after it, any
symlink, a special file, a hostile TOP-LEVEL skill name — nested names are not
gated, a corrupt or version-stale baseline)
is an **anomaly that always advises and can never be certified unchanged**. For
a new, changed, removed, or anomalous skill it injects one line routing to the
`skill-vetting` skill. It **never blocks and never emits a "safe" line** — a
`SessionStart` hook cannot deny, and a green-lighting scanner is the
false-assurance trap `skill-vetting` exists to avoid; a clean, unchanged run is
silent — as is a first run that found nothing to record — while a first run
that HAS something to baseline emits one labelled line naming how many
installed skills it is BASELINING without reviewing them (the line is emitted
before the write and says so; a write that then fails is not announced
separately - it cannot be, under the one-message rule - and does not need to be,
because nothing was written and the next session says the same thing again) — a count that
includes candidates whose observation was COMPLETE but adverse (a symlink, an
unreadable directory, a special file, a hostile name), and excludes only those
lost to a resource-budget short-circuit, whose digest would be a placeholder;
each excluded one still advises through its own anomaly line; the advisory prints
**before** the baseline
(`<config>/skill-vetting/baseline.json`) advances — a failed delivery
re-advises next session — and skill names reach the model only through a strict
ASCII allowlist or an opaque id. The baseline is not tamper-evident (it shares
a trust level with the skills themselves); that limit is documented, not
defended. It is a tripwire that routes to the full vetting read, never a
substitute for it. Wire it with:

```json
"SessionStart": [
{ "matcher": "", "hooks": [ { "type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/skill-vetting-advisory.py" } ] }
]
```

The three gating hooks append audit events to `~/.claude/hooks/hooks.log` and the
advisory vetting hook to `<CLAUDE_CONFIG_DIR>/skill-vetting/advisory.log`
(default `~/.claude/skill-vetting/advisory.log`), so gate activity — and the
vetting hook's advisories — is auditable instead of invisible.

**Two cautions.** Hooks run arbitrary shell with your permissions: read any
hook script before enabling it, and prefer committing hooks so they are
Expand Down
Loading
Loading