Skip to content

Initial scaffold: empty Eldunarya and Arif scaffolds with a stdlib-only bootstrap CLI - #1

Merged
ahrazzle merged 1 commit into
mainfrom
feat/initial-scaffold
Sep 22, 2026
Merged

ahrazzle merged 1 commit into
mainfrom
feat/initial-scaffold

Conversation

@ahrazzle

Copy link
Copy Markdown
Contributor

PR body: protean-context implementation (v0.1.0)

Target: aska-digital/protean-context, base main.

What this adds

The full v0.1.0 ingredient surface, per the locked architecture (decision record D1 through D9 of the protean-context lock (2026-09-22)) and the locked wording record (STABLE 2026-09-22):

  • README with the contract sentence as line 1 (byte-identical to protean-ingredient.json contract), the locked skeleton sections, and the license and attribution line.
  • skills/protean-context/SKILL.md: discoverability and operator instructions only, no runtime code.
  • scripts/protean-context/bootstrap.py: stdlib-only CLI with plan, init, verify, ingest, remove.
  • templates/protean-context/: Eldunarya templates (eldunarya.json, ROUTER.template.md, modules/README.template.md), Arif templates (arif.json, adapters/README.md, empty records/, content/, index/), four schema documents, and docs/rehoming.md.
  • gates/protean-context/: eight gate scripts, fresh-install-test.py, and check-post-install.py.
  • fixtures/ (synthetic only), tests/ (unittest suites for steps 9 to 13 and the CLI suite), build/verify-all.py (14-step ordered runner), protean-ingredient.json, install.sh, CHANGELOG.md, MIT LICENSE, and one minimal .github/workflows/verify.yml.

Decisions implemented (D1 to D9)

  • D1 Composition: one ingredient repository with one minimal skill, the bootstrap CLI as ingredient payload, no plugin substrate, no second installer model. Absence of plugin.js and mcp.json is proven by read-back.
  • D2 Name and metadata: name protean-context; contract line adopted verbatim from the wording record section 1; description and the six topics are set on the repository before announcement (metadata change is part of the release checklist, not this PR).
  • D3 Eldunarya contract: both contract sentences verbatim in README; scaffold trees exactly per the locked layout; all machine-readable files carry schema + schemaVersion; naming regex and reserved names (eldunari, eldunarya, arif, exit 1); ROUTER.md router-only semantics with Purpose, Rules, and Pointers; one-fact-one-home invariant in the modules/ stub; gate 6 allowlist entries for eldunari, eldunarya, and arif cite this decision record.
  • D4 Arif contract: default store file-v1, metadata-first arif-record-v1, adapter interface (ADAPTER_NAME, ADAPTER_VERSION, collect) passed by explicit path, fail-closed all-or-nothing validation, locked negative contract quoted verbatim in adapters/README.md, embedding slot default disabled, forward-only additive migration rules with backups and exit 2 on downgrade, empty migration registry in v0.1.0.
  • D5 CLI contract: entry point scripts/protean-context/bootstrap.py; explicit --target on every command; --dry-run and --offline everywhere; --name repeatable and required on init; ensure-exist rerun semantics; init receipt schema protean-context-receipt-v1 with hash-scoped remove; exit codes 0 to 6; errors to stderr, summary to stdout, no partial-success zero.
  • D6 Gates: build/verify-all.py docstring is authoritative for the 14 locked steps in order, 300-second timeout per step, fail closed, gate protocol 0/1/2 with SKIPPED: missing <path> counted as NOT PASS; check-post-install.py for the composer; one fresh minimal workflow running only python3 build/verify-all.py (implementer freedom F4).
  • D7 Re-homing: templates/protean-context/docs/rehoming.md with the seven locked sections, the token table, EXAMPLE-marked blocks, one env override, hermes -p {PROFILE_NAME} config set <key> <value> guidance with the comment-strip warning, and backup, upgrade, and removal semantics.
  • D8 Kit integration: not touched by this PR. Tag v0.1.0 (annotated, visible prerelease) and the protean-kit lock-entry PR follow the ordered gate sequence G-1 to G-5.
  • D9 Open items: the blocklist collision is handled by the companion protean-team identifiers PR; the file-store decision resolves the storage question; the fresh workflow resolves the unverified-.github item; composer lock hygiene, the handoff contract quirk, and the team manifest drift are out of scope and routed elsewhere.

Verification

Commands run on this branch. Outputs pasted verbatim.

$ python3 build/verify-all.py

protean-context gate runner
root: /private/tmp/pcrepo-1
steps: 14, timeout per step: 300s
========================================================================
STEP 1/14: contract and descriptor parity
========================================================================
check-manifest-parity: PASS
  contract carriers agree: 4
  install targets: 4
PASS: contract and descriptor parity
========================================================================
STEP 2/14: shipped JSON, schemas, ingest
========================================================================
check-schema: PASS
  json files parsed: 11
  schema documents: 4
PASS: shipped JSON, schemas, ingest
========================================================================
STEP 3/14: no bundled content
========================================================================
check-emptiness: PASS
  shipped template files: 13
  created tree: exactly 6 files, records/content/index empty
PASS: no bundled content
========================================================================
STEP 4/14: privacy scan
========================================================================
check-leak-scan: PASS
  files scanned: 59
  surfaces: S1 content, S2 filenames, S4 script paths, S5 config defaults
  hits: 0
PASS: privacy scan
========================================================================
STEP 5/14: path portability
========================================================================
check-path-portability: PASS
  files scanned: 58
  allowed tokens: {HOME}, {TARGET}, {HERMES_HOME}, {PROFILE_NAME}, {ELDUNARI_NAME}
PASS: path portability
========================================================================
STEP 6/14: identifiers
========================================================================
check-identifiers: PASS
  files scanned: 57
  blocklist terms: 5
  allowlisted public system terms: eldunari, eldunarya, arif
PASS: identifiers
========================================================================
STEP 7/14: stdlib-only imports
========================================================================
check-stdlib-imports: PASS
  python files: 30
  import statements checked: 135
  modules seen: argparse, ast, bootstrap, hashlib, json, os, pc_common, pc_ingest, pc_init, pc_migrate, pc_paths, pc_receipt, pc_remove, pc_schema, pc_support, pc_verify, re, shutil, subprocess, sys, tempfile, typing, unittest
PASS: stdlib-only imports
========================================================================
STEP 8/14: synthetic fixtures
========================================================================
check-synthetic-fixtures: PASS
  fixtures checked: 7
  synthetic vocabulary terms: 41
PASS: synthetic fixtures
========================================================================
STEP 9/14: no live state (unit test)
========================================================================
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
command: plan (init --dry-run)
names: demo
would create: arif/adapters/README.md
would create: arif/arif.json
would create: eldunarya/eldunari/demo/ROUTER.md
would create: eldunarya/eldunari/demo/modules/README.md
would create: eldunarya/eldunarya.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: dry-run, no files written
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
command: init
names: demo
create: arif/adapters/README.md
create: arif/arif.json
create: eldunarya/eldunari/demo/ROUTER.md
create: eldunarya/eldunari/demo/modules/README.md
create: eldunarya/eldunarya.json
wrote: arif/adapters/README.md
wrote: arif/arif.json
wrote: eldunarya/eldunari/demo/ROUTER.md
wrote: eldunarya/eldunari/demo/modules/README.md
wrote: eldunarya/eldunarya.json
wrote: .protean-context-receipt.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
command: init
names: demo
unchanged: arif/adapters/README.md
unchanged: arif/arif.json
unchanged: eldunarya/eldunari/demo/ROUTER.md
unchanged: eldunarya/eldunari/demo/modules/README.md
unchanged: eldunarya/eldunarya.json
summary: create=0 replace=0 unchanged=5 conflict=0 migrations=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
check receipt: ok
check eldunarya-registry: ok
check arif-store: ok
checks: 3 failed=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
adapter: synthetic-adapter 0.0.1
source: /private/tmp/pcrepo-1/tests/fixtures/source
records: 1
write: arif: content/240fe5658a6f3db606e850939963bd8fd903c3b850b467f18439534ef0ed8f5c
write: arif: records/demo-record-1.json
summary: records=1 blobs=1
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
check receipt: ok
check eldunarya-registry: ok
check arif-store: ok
checks: 3 failed=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created
command: remove
receipt: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-gkxg6xop/created/.protean-context-receipt.json
delete: eldunarya/eldunari/demo/modules/README.md
delete: eldunarya/eldunari/demo/ROUTER.md
delete: arif/adapters/README.md
delete: arif/arif.json
delete: eldunarya/eldunarya.json
deleted: eldunarya/eldunari/demo/modules/README.md
deleted: eldunarya/eldunari/demo/ROUTER.md
deleted: arif/adapters/README.md
deleted: arif/arif.json
deleted: eldunarya/eldunarya.json
removed directory: eldunarya/eldunari/demo/modules
removed directory: eldunarya/eldunari/demo
removed directory: arif/adapters
kept directory: arif/content (not empty)
removed directory: arif/index
kept directory: arif/records (not empty)
removed directory: eldunarya/eldunari
kept directory: arif (not empty)
removed directory: eldunarya
deleted: .protean-context-receipt.json
summary: delete=5 skipped=0 absent=0 directories=6
result: ok
PASS: no live state (unit test)
========================================================================
STEP 10/14: idempotent rerun (unit test)
========================================================================
PASS: idempotent rerun (unit test)
========================================================================
STEP 11/14: deterministic init (unit test)
========================================================================
PASS: deterministic init (unit test)
========================================================================
STEP 12/14: removal (unit test)
========================================================================
PASS: removal (unit test)
========================================================================
STEP 13/14: migration rules (unit test)
========================================================================
PASS: migration rules (unit test)
========================================================================
STEP 14/14: fresh install end to end
========================================================================
fresh-install-test: /private/tmp/pcrepo-1
  step: install.sh --target (dry-run) -> exit 0
  step: install.sh --target -> exit 0
  step: installed bootstrap.py plan -> exit 0
  step: installed bootstrap.py init -> exit 0
  step: installed bootstrap.py verify -> exit 0
  step: check-emptiness.py against the installed copy -> exit 0
  step: check-schema.py against the committed tree -> exit 0
fresh-install-test: PASS
  installed targets: 4
  plan, init, verify: exit 0 offline
PASS: fresh install end to end
========================================================================
SUMMARY
========================================================================
   1 PASS      contract and descriptor parity
   2 PASS      shipped JSON, schemas, ingest
   3 PASS      no bundled content
   4 PASS      privacy scan
   5 PASS      path portability
   6 PASS      identifiers
   7 PASS      stdlib-only imports
   8 PASS      synthetic fixtures
   9 PASS      no live state (unit test)
  10 PASS      idempotent rerun (unit test)
  11 PASS      deterministic init (unit test)
  12 PASS      removal (unit test)
  13 PASS      migration rules (unit test)
  14 PASS      fresh install end to end
  steps=14 pass=14 not-pass=0 skipped=0
RESULT: all 14 steps PASS
exit=0
$ bash install.sh --target /tmp/pctarget-1/dry --dry-run
installer: protean-context 0.1.0
mode: dry-run
target: /tmp/pctarget-1/dry
network calls: 0
install targets:
  skills/protean-context
  scripts/protean-context
  templates/protean-context
  gates/protean-context
planned writes (target-relative):
  /tmp/pctarget-1/dry/skills/protean-context/SKILL.md
  /tmp/pctarget-1/dry/scripts/protean-context/bootstrap.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_common.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_ingest.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_init.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_migrate.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_paths.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_receipt.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_remove.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_schema.py
  /tmp/pctarget-1/dry/scripts/protean-context/pc_verify.py
  /tmp/pctarget-1/dry/templates/protean-context/arif/adapters/README.md
  /tmp/pctarget-1/dry/templates/protean-context/arif/arif.json
  /tmp/pctarget-1/dry/templates/protean-context/arif/content/.gitkeep
  /tmp/pctarget-1/dry/templates/protean-context/arif/index/.gitkeep
  /tmp/pctarget-1/dry/templates/protean-context/arif/records/.gitkeep
  /tmp/pctarget-1/dry/templates/protean-context/docs/rehoming.md
  /tmp/pctarget-1/dry/templates/protean-context/eldunarya/eldunari/ROUTER.template.md
  /tmp/pctarget-1/dry/templates/protean-context/eldunarya/eldunari/modules/README.template.md
  /tmp/pctarget-1/dry/templates/protean-context/eldunarya/eldunarya.json
  /tmp/pctarget-1/dry/templates/protean-context/schema/arif-record.schema.json
  /tmp/pctarget-1/dry/templates/protean-context/schema/arif-store.schema.json
  /tmp/pctarget-1/dry/templates/protean-context/schema/eldunari.schema.json
  /tmp/pctarget-1/dry/templates/protean-context/schema/eldunarya.schema.json
  /tmp/pctarget-1/dry/gates/protean-context/check-emptiness.py
  /tmp/pctarget-1/dry/gates/protean-context/check-identifiers.py
  /tmp/pctarget-1/dry/gates/protean-context/check-leak-scan.py
  /tmp/pctarget-1/dry/gates/protean-context/check-manifest-parity.py
  /tmp/pctarget-1/dry/gates/protean-context/check-path-portability.py
  /tmp/pctarget-1/dry/gates/protean-context/check-post-install.py
  /tmp/pctarget-1/dry/gates/protean-context/check-schema.py
  /tmp/pctarget-1/dry/gates/protean-context/check-stdlib-imports.py
  /tmp/pctarget-1/dry/gates/protean-context/check-synthetic-fixtures.py
  /tmp/pctarget-1/dry/gates/protean-context/fresh-install-test.py
result: dry-run, no files written
exit=0
$ python3 scripts/protean-context/bootstrap.py plan --target <TARGET> --name demo
target: /tmp/pctarget-1/created
command: plan (init --dry-run)
names: demo
would create: arif/adapters/README.md
would create: arif/arif.json
would create: eldunarya/eldunari/demo/ROUTER.md
would create: eldunarya/eldunari/demo/modules/README.md
would create: eldunarya/eldunarya.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: dry-run, no files written
exit=0
$ python3 scripts/protean-context/bootstrap.py init --target <TARGET> --name demo
target: /tmp/pctarget-1/created
command: init
names: demo
create: arif/adapters/README.md
create: arif/arif.json
create: eldunarya/eldunari/demo/ROUTER.md
create: eldunarya/eldunari/demo/modules/README.md
create: eldunarya/eldunarya.json
wrote: arif/adapters/README.md
wrote: arif/arif.json
wrote: eldunarya/eldunari/demo/ROUTER.md
wrote: eldunarya/eldunari/demo/modules/README.md
wrote: eldunarya/eldunarya.json
wrote: .protean-context-receipt.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: ok
exit=0
$ python3 scripts/protean-context/bootstrap.py init --target <TARGET> --name demo   # second run, zero writes
target: /tmp/pctarget-1/created
command: init
names: demo
unchanged: arif/adapters/README.md
unchanged: arif/arif.json
unchanged: eldunarya/eldunari/demo/ROUTER.md
unchanged: eldunarya/eldunari/demo/modules/README.md
unchanged: eldunarya/eldunarya.json
summary: create=0 replace=0 unchanged=5 conflict=0 migrations=0
result: ok
exit=0
$ python3 scripts/protean-context/bootstrap.py verify --target <TARGET>
target: /tmp/pctarget-1/created
check receipt: ok
check eldunarya-registry: ok
check arif-store: ok
checks: 3 failed=0
result: ok
exit=0
$ python3 scripts/protean-context/bootstrap.py init --target <TARGET> --name 'Bad Name'
error: bad --name: 'Bad Name' does not match ^[a-z]([a-z0-9-]{0,46}[a-z0-9])?$
exit=1
$ python3 scripts/protean-context/bootstrap.py init --target <TARGET> --name eldunari
error: reserved --name: 'eldunari' is part of the tree vocabulary
exit=1
$ find . -name 'plugin.js' -o -name 'mcp.json' | wc -l
       0
$ python3 gates/protean-context/check-identifiers.py .
check-identifiers: PASS
  files scanned: 56
  blocklist terms: 5
  allowlisted public system terms: eldunari, eldunarya, arif
exit=0
$ grep -rniE 'pgvector|chroma|postgres' .
./templates/protean-context/docs/rehoming.md:51:Upgrade path: external engines (for example pgvector or chroma) are user-side adapters behind the ingestion and store boundary. You configure them on your side of that boundary; they are never dependencies of this scaffold, which bundles no database, no server, and no index.
exit=0
$ python3 gates/protean-context/check-leak-scan.py .
check-leak-scan: PASS
  files scanned: 58
  surfaces: S1 content, S2 filenames, S4 script paths, S5 config defaults
  hits: 0
exit=0

$ python3 gates/protean-context/check-path-portability.py .
check-path-portability: PASS
  files scanned: 57
  allowed tokens: {HOME}, {TARGET}, {HERMES_HOME}, {PROFILE_NAME}, {ELDUNARI_NAME}
exit=0
$ python3 -m unittest tests.test_no_live_state
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
command: plan (init --dry-run)
names: demo
would create: arif/adapters/README.md
would create: arif/arif.json
would create: eldunarya/eldunari/demo/ROUTER.md
would create: eldunarya/eldunari/demo/modules/README.md
would create: eldunarya/eldunarya.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: dry-run, no files written
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
command: init
names: demo
create: arif/adapters/README.md
create: arif/arif.json
create: eldunarya/eldunari/demo/ROUTER.md
create: eldunarya/eldunari/demo/modules/README.md
create: eldunarya/eldunarya.json
wrote: arif/adapters/README.md
wrote: arif/arif.json
wrote: eldunarya/eldunari/demo/ROUTER.md
wrote: eldunarya/eldunari/demo/modules/README.md
wrote: eldunarya/eldunarya.json
wrote: .protean-context-receipt.json
summary: create=5 replace=0 unchanged=0 conflict=0 migrations=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
command: init
names: demo
unchanged: arif/adapters/README.md
unchanged: arif/arif.json
unchanged: eldunarya/eldunari/demo/ROUTER.md
unchanged: eldunarya/eldunari/demo/modules/README.md
unchanged: eldunarya/eldunarya.json
summary: create=0 replace=0 unchanged=5 conflict=0 migrations=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
check receipt: ok
check eldunarya-registry: ok
check arif-store: ok
checks: 3 failed=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
adapter: synthetic-adapter 0.0.1
source: /private/tmp/pcrepo-1/tests/fixtures/source
records: 1
write: arif: content/240fe5658a6f3db606e850939963bd8fd903c3b850b467f18439534ef0ed8f5c
write: arif: records/demo-record-1.json
summary: records=1 blobs=1
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
check receipt: ok
check eldunarya-registry: ok
check arif-store: ok
checks: 3 failed=0
result: ok
network: none (offline requested; no network path exists)
target: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created
command: remove
receipt: /var/folders/26/tt22z54s45b9wx307z93pmjw0000gn/T/no-live-state-8np8de0s/created/.protean-context-receipt.json
delete: eldunarya/eldunari/demo/modules/README.md
delete: eldunarya/eldunari/demo/ROUTER.md
delete: arif/adapters/README.md
delete: arif/arif.json
delete: eldunarya/eldunarya.json
deleted: eldunarya/eldunari/demo/modules/README.md
deleted: eldunarya/eldunari/demo/ROUTER.md
deleted: arif/adapters/README.md
deleted: arif/arif.json
deleted: eldunarya/eldunarya.json
removed directory: eldunarya/eldunari/demo/modules
removed directory: eldunarya/eldunari/demo
removed directory: arif/adapters
kept directory: arif/content (not empty)
removed directory: arif/index
kept directory: arif/records (not empty)
removed directory: eldunarya/eldunari
kept directory: arif (not empty)
removed directory: eldunarya
deleted: .protean-context-receipt.json
summary: delete=5 skipped=0 absent=0 directories=6
result: ok
.
----------------------------------------------------------------------
Ran 1 test in 0.022s

OK
exit=0
$ python3 gates/protean-context/fresh-install-test.py
fresh-install-test: /private/tmp/pcrepo-1
  step: install.sh --target (dry-run) -> exit 0
  step: install.sh --target -> exit 0
  step: installed bootstrap.py plan -> exit 0
  step: installed bootstrap.py init -> exit 0
  step: installed bootstrap.py verify -> exit 0
  step: check-emptiness.py against the installed copy -> exit 0
  step: check-schema.py against the committed tree -> exit 0
fresh-install-test: PASS
  installed targets: 4
  plan, init, verify: exit 0 offline
exit=0

Boundary self-check

  • No absolute home paths; only {HOME}, {TARGET}, {HERMES_HOME}, {PROFILE_NAME} (plus template placeholders).
  • No credential values; key names only.
  • No owner content, no internal identities, no instance tokens; the identifiers allowlist cites the decision record, not a silent baseline.
  • Synthetic fixtures only; every fixture carries the SYNTHETIC TEST DATA marker.
  • Stdlib-only imports proven by AST; network modules forbidden.

Out of scope for this PR

  • protean-kit lock entry and generated README rows (separate PR, opens only after G-3 and G-4).
  • protean-team identifiers amendment (separate companion PR).
  • Repository description, topics, annotated tag, and visible prerelease (release checklist, before announcement).

Implements the locked architecture (D1 to D9) and the locked wording of the
protean-context component: the ingredient surface (README with the contract
sentence as line 1, discoverability skill, bootstrap CLI, Eldunarya and Arif
templates, the four schema documents, the eight gates plus the fresh-install
and post-install gates, synthetic fixtures, the behavioral and CLI test suites,
the standalone installer, the descriptor, and an MIT license), the ordered
14-step gate runner in build/verify-all.py, and one minimal workflow that runs
it on push and on pull requests.
@ahrazzle
ahrazzle merged commit 999d8fd into main Sep 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant