Compare commits

..

20 Commits

Author SHA1 Message Date
85eb20c4cb stuff 2026-04-10 15:41:24 +02:00
276378b57c flk 2026-04-09 08:33:04 +00:00
03b968513b flk 2026-04-08 08:07:51 +00:00
e545d38314 sm 2026-04-08 08:07:51 +00:00
564ccd2559 keys 2026-04-07 11:43:58 +00:00
f32f51970b up 2026-04-07 11:41:55 +00:00
0e50839ce0 up 2026-04-07 11:02:37 +00:00
ed995a1edd flk 2026-04-07 11:02:37 +00:00
2424b87b46 flk 2026-04-07 11:02:37 +00:00
37ef245374 flk 2026-04-02 14:46:44 +00:00
ac40abe696 flk 2026-04-02 14:45:05 +00:00
a611d7fb99 clean 2026-04-02 13:01:08 +00:00
89c430b940 review more 2026-04-02 12:57:50 +00:00
1dd7d8a2d8 fix review 2026-04-02 12:57:50 +00:00
69697c822c model 2026-04-02 11:12:13 +00:00
8def00f368 review 2026-04-02 10:58:32 +00:00
c907354a4f flk 2026-04-02 10:09:06 +00:00
80ff1f8b03 fuck 2026-04-01 16:11:02 +00:00
9b5069693a fix opensrc mcp command 2026-04-01 16:11:02 +00:00
2cad84ff26 rm direnv 2026-04-01 16:11:02 +00:00
27 changed files with 703 additions and 1659 deletions

View File

@@ -1,195 +0,0 @@
---
description: Reviews code changes for quality, bugs, security, and best practices
mode: subagent
temperature: 0.1
tools:
write: false
permission:
edit: deny
bash: allow
---
You are a code reviewer for proposed code changes made by another engineer.
## Version Control
This project uses `jj` (Jujutsu) for version control. Use jj commands to inspect changes.
## Review Modes
Parse the user's request to determine the review mode. The user will specify one of the following modes (or no mode, in which case you should auto-detect).
### Auto-detect (no mode specified)
If no mode is specified:
1. Check for working-copy changes with `jj diff --summary`
2. If there are working-copy changes, review those (working-copy mode)
3. Otherwise, find the trunk bookmark with `jj log -r 'trunk()' --no-graph -T 'bookmarks ++ "\n"'` and review against it (bookmark mode)
4. If no trunk bookmark exists, review the current change
### working-copy
Review the current working-copy changes (including new files).
Commands to inspect:
- `jj status` - overview of changed files
- `jj diff --summary` - summary of changes
- `jj diff` - full diff of all changes
### bookmark <name>
Review code changes against a base bookmark (PR-style review).
Steps:
1. Resolve the bookmark. If the name contains `@`, split into `name@remote`. Otherwise, look for a local bookmark first, then remote bookmarks.
2. Find the merge-base: `jj log -r 'heads(::@ & ::<bookmark_revset>)' --no-graph -T 'change_id.shortest(8) ++ "\n"'`
- For local bookmarks: `<bookmark_revset>` = `bookmarks(exact:"<name>")`
- For remote bookmarks: `<bookmark_revset>` = `remote_bookmarks(exact:"<name>", exact:"<remote>")`
3. Inspect the diff: `jj diff --from <merge_base> --to @`
Also check for local working-copy changes on top with `jj diff --summary` and include those in the review.
### change <id>
Review a specific change by its change ID.
Commands to inspect:
- `jj show <id>` - show the change details and diff
- `jj log -r <id>` - show change metadata
### pr <number-or-url>
Review a GitHub pull request by materializing it locally.
Use the `review_materialize_pr` tool to materialize the PR. It returns the PR title, base bookmark, and remote used. Then review as a bookmark-style review against the base bookmark.
If the `review_materialize_pr` tool is not available, do it manually:
1. Get PR info: `gh pr view <number> --json baseRefName,title,headRefName,isCrossRepository,headRepository,headRepositoryOwner`
2. Fetch the PR branch: `jj git fetch --remote origin --branch <headRefName>`
3. Save current position: `jj log -r @ --no-graph -T 'change_id.shortest(8)'`
4. Create a new change on the PR: `jj new 'remote_bookmarks(exact:"<headRefName>", exact:"origin")'`
5. Find merge-base and review as bookmark mode against `<baseRefName>`
6. After the review, restore position: `jj edit <saved_change_id>`
For cross-repository (forked) PRs:
1. Add a temporary remote: `jj git remote add <temp_name> <fork_url>`
2. Fetch from that remote instead
3. After the review, remove the temporary remote: `jj git remote remove <temp_name>`
Parse PR references as either a number (e.g. `123`) or a GitHub URL (e.g. `https://github.com/owner/repo/pull/123`).
### folder <paths...>
Snapshot review (not a diff) of specific folders or files.
Read the files directly in the specified paths. Do not compare against any previous state.
## Extra Instructions
If the user's request contains `--extra "..."` or `--extra=...`, treat the quoted value as an additional review instruction to apply on top of the standard guidelines.
## Project-Specific Review Guidelines
Before starting the review, check if a `REVIEW_GUIDELINES.md` file exists in the project root. If it does, read it and incorporate those guidelines into this review. They take precedence over the default guidelines below when they conflict.
## Review Guidelines
Below are default guidelines for determining what to flag. If you encounter more specific guidelines in the project's REVIEW_GUIDELINES.md or in the user's instructions, those override these general instructions.
### Determining what to flag
Flag issues that:
1. Meaningfully impact the accuracy, performance, security, or maintainability of the code.
2. Are discrete and actionable (not general issues or multiple combined issues).
3. Don't demand rigor inconsistent with the rest of the codebase.
4. Were introduced in the changes being reviewed (not pre-existing bugs).
5. The author would likely fix if aware of them.
6. Don't rely on unstated assumptions about the codebase or author's intent.
7. Have provable impact on other parts of the code -- it is not enough to speculate that a change may disrupt another part, you must identify the parts that are provably affected.
8. Are clearly not intentional changes by the author.
9. Be particularly careful with untrusted user input and follow the specific guidelines to review.
10. Treat silent local error recovery (especially parsing/IO/network fallbacks) as high-signal review candidates unless there is explicit boundary-level justification.
### Untrusted User Input
1. Be careful with open redirects, they must always be checked to only go to trusted domains (?next_page=...)
2. Always flag SQL that is not parametrized
3. In systems with user supplied URL input, http fetches always need to be protected against access to local resources (intercept DNS resolver!)
4. Escape, don't sanitize if you have the option (eg: HTML escaping)
### Comment guidelines
1. Be clear about why the issue is a problem.
2. Communicate severity appropriately - don't exaggerate.
3. Be brief - at most 1 paragraph.
4. Keep code snippets under 3 lines, wrapped in inline code or code blocks.
5. Use ```suggestion blocks ONLY for concrete replacement code (minimal lines; no commentary inside the block). Preserve the exact leading whitespace of the replaced lines.
6. Explicitly state scenarios/environments where the issue arises.
7. Use a matter-of-fact tone - helpful AI assistant, not accusatory.
8. Write for quick comprehension without close reading.
9. Avoid excessive flattery or unhelpful phrases like "Great job...".
### Review priorities
1. Surface critical non-blocking human callouts (migrations, dependency churn, auth/permissions, compatibility, destructive operations) at the end.
2. Prefer simple, direct solutions over wrappers or abstractions without clear value.
3. Treat back pressure handling as critical to system stability.
4. Apply system-level thinking; flag changes that increase operational risk or on-call wakeups.
5. Ensure that errors are always checked against codes or stable identifiers, never error messages.
### Fail-fast error handling (strict)
When reviewing added or modified error handling, default to fail-fast behavior.
1. Evaluate every new or changed `try/catch`: identify what can fail and why local handling is correct at that exact layer.
2. Prefer propagation over local recovery. If the current scope cannot fully recover while preserving correctness, rethrow (optionally with context) instead of returning fallbacks.
3. Flag catch blocks that hide failure signals (e.g. returning `null`/`[]`/`false`, swallowing JSON parse failures, logging-and-continue, or "best effort" silent recovery).
4. JSON parsing/decoding should fail loudly by default. Quiet fallback parsing is only acceptable with an explicit compatibility requirement and clear tested behavior.
5. Boundary handlers (HTTP routes, CLI entrypoints, supervisors) may translate errors, but must not pretend success or silently degrade.
6. If a catch exists only to satisfy lint/style without real handling, treat it as a bug.
7. When uncertain, prefer crashing fast over silent degradation.
### Priority levels
Tag each finding with a priority level in the title:
- [P0] - Drop everything to fix. Blocking release/operations. Only for universal issues that do not depend on assumptions about inputs.
- [P1] - Urgent. Should be addressed in the next cycle.
- [P2] - Normal. To be fixed eventually.
- [P3] - Low. Nice to have.
## Output Format
Provide your findings in a clear, structured format:
1. List each finding with its priority tag, file location, and explanation.
2. Findings must reference locations that overlap with the actual diff -- don't flag pre-existing code.
3. Keep line references as short as possible (avoid ranges over 5-10 lines; pick the most suitable subrange).
4. Provide an overall verdict: "correct" (no blocking issues) or "needs attention" (has blocking issues).
5. Ignore trivial style issues unless they obscure meaning or violate documented standards.
6. Do not generate a full PR fix -- only flag issues and optionally provide short suggestion blocks.
7. End with the required "Human Reviewer Callouts (Non-Blocking)" section and all applicable bold callouts (no yes/no).
Output all findings the author would fix if they knew about them. If there are no qualifying findings, explicitly state the code looks good. Don't stop at the first finding - list every qualifying issue. Then append the required non-blocking callouts section.
### Required Human Reviewer Callouts (Non-Blocking)
After findings/verdict, you MUST append this final section:
## Human Reviewer Callouts (Non-Blocking)
Include only applicable callouts (no yes/no lines):
- **This change adds a database migration:** <files/details>
- **This change introduces a new dependency:** <package(s)/details>
- **This change changes a dependency (or the lockfile):** <files/package(s)/details>
- **This change modifies auth/permission behavior:** <what changed and where>
- **This change introduces backwards-incompatible public schema/API/contract changes:** <what changed and where>
- **This change includes irreversible or destructive operations:** <operation and scope>
Rules for this section:
1. These are informational callouts for the human reviewer, not fix items.
2. Do not include them in Findings unless there is an independent defect.
3. These callouts alone must not change the verdict.
4. Only include callouts that apply to the reviewed change.
5. Keep each emitted callout bold exactly as written.
6. If none apply, write "- (none)".

View File

@@ -2,7 +2,7 @@ keys:
- &user_cschmatzler age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7
- &host_tahani age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm
- &host_michael age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j
- &host_jason age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2
- &host_janet age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8
- &host_chidi age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3
creation_rules:
- path_regex: secrets/[^/]+$
@@ -11,5 +11,5 @@ creation_rules:
- *user_cschmatzler
- *host_tahani
- *host_michael
- *host_jason
- *host_janet
- *host_chidi

114
flake.lock generated
View File

@@ -114,11 +114,11 @@
]
},
"locked": {
"lastModified": 1775023938,
"narHash": "sha256-0/aPuEXIIaehfP/t9icDJUTCmAu13dfS+RNKWdMV5P0=",
"lastModified": 1775037210,
"narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "5176e2f4b45de02f1c90133854634a6c675ef41b",
"rev": "06648f4902343228ce2de79f291dd5a58ee12146",
"type": "github"
},
"original": {
@@ -130,11 +130,11 @@
},
"den": {
"locked": {
"lastModified": 1775034229,
"narHash": "sha256-BZPqamTWnWdKA+tSjt5y57EDYZnSRQYNZWQNFtqn9rw=",
"lastModified": 1775702491,
"narHash": "sha256-5BCNtE/zCLSheltliy4hTdwsq0Boj/W1XRIX8n89nqA=",
"owner": "vic",
"repo": "den",
"rev": "88533ec7ac8ddda4a59243387de4b9d24d3932ae",
"rev": "d267c458e384b57317d06d45f7c65f7fb03fae4b",
"type": "github"
},
"original": {
@@ -191,11 +191,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1775029908,
"narHash": "sha256-QuPn+EN/097aBLeSqbQ7vOwc5TSOb68bAxg1+mknfmw=",
"lastModified": 1775721346,
"narHash": "sha256-ogqjruvVBYEj8sWM3viOucSo1Pna9c147EKQOfA+p3I=",
"owner": "nix-community",
"repo": "fenix",
"rev": "380f1969f440e683333af5746caac76811b4a1a8",
"rev": "99fde43dfee2a672e4e37ef211e0844337e5b725",
"type": "github"
},
"original": {
@@ -302,11 +302,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -323,11 +323,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -344,11 +344,11 @@
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github"
},
"original": {
@@ -441,11 +441,11 @@
]
},
"locked": {
"lastModified": 1774991950,
"narHash": "sha256-kScKj3qJDIWuN9/6PMmgy5esrTUkYinrO5VvILik/zw=",
"lastModified": 1775683737,
"narHash": "sha256-oBYyowo6yfgb95Z78s3uTnAd9KkpJpwzjJbfnpLaM2Y=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "f2d3e04e278422c7379e067e323734f3e8c585a7",
"rev": "7ba4ee4228ed36123c7cb75d50524b43514ef992",
"type": "github"
},
"original": {
@@ -457,11 +457,11 @@
"homebrew-cask": {
"flake": false,
"locked": {
"lastModified": 1775034103,
"narHash": "sha256-poo46muSZsDLcnN8wY/30YeLAdRCxIwzr2s1Z12aC28=",
"lastModified": 1775700724,
"narHash": "sha256-qQm9uIF+tI7gamLMa7DSXSQQzLQalEtOa7PHPxNkbr8=",
"owner": "homebrew",
"repo": "homebrew-cask",
"rev": "0285f9dcb1dfaacde1fb6218ebe92540d9a3762d",
"rev": "c622bff3b88557e3c870104db0426b93e0767a8f",
"type": "github"
},
"original": {
@@ -473,11 +473,11 @@
"homebrew-core": {
"flake": false,
"locked": {
"lastModified": 1775034425,
"narHash": "sha256-nTdPP63yUkmUsx/ksOvfRs6MjXztPh6GEv6FQU5IFGA=",
"lastModified": 1775721921,
"narHash": "sha256-s6K2QbKa4OJlblFp3zMSh0/2PM2zpWpAd4ZnREirj/I=",
"owner": "homebrew",
"repo": "homebrew-core",
"rev": "da66ad06774537e48644d117e6300ad9c2db25a0",
"rev": "70028a68b515145bbeccb2961240275ab6eb9e82",
"type": "github"
},
"original": {
@@ -535,11 +535,11 @@
"jj-nvim": {
"flake": false,
"locked": {
"lastModified": 1773914813,
"narHash": "sha256-UuNcOfgsWuHu9hx6NT/FbQ0E8T6nRY1X6O6CDRtH8Sk=",
"lastModified": 1775551442,
"narHash": "sha256-hoU+DenrgxNwvLNmDtIsJ5yB5fhRjDRPOOL8WW9bpZM=",
"owner": "NicolasGB",
"repo": "jj.nvim",
"rev": "a6e163bcc3a6b75e5b6d4190b64ed4b39f8ddb0c",
"rev": "2dbe2c73c599a29e86e4123b42e430828b1f01d9",
"type": "github"
},
"original": {
@@ -593,11 +593,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1775013753,
"narHash": "sha256-uIEYD2rwgV9EFO5x0SQ34Yj50r/4Abj28OibW404eCw=",
"lastModified": 1775705124,
"narHash": "sha256-OUtgrn0k7DYnAP9skY2rOJSWJyn4w5tnUcF3lSJdfME=",
"owner": "numtide",
"repo": "llm-agents.nix",
"rev": "5a192c61b052a7713ea8eb5490a64087a996afa7",
"rev": "ca76524952b00135dba57da62ce2dd123a1ba4be",
"type": "github"
},
"original": {
@@ -637,11 +637,11 @@
]
},
"locked": {
"lastModified": 1774915815,
"narHash": "sha256-LocQzkSjVS4G0AKMBiEIVdBKCNTMZXQFjQMWFId4Jpg=",
"lastModified": 1775693082,
"narHash": "sha256-nnhkpfWsRutQh//KmVoIV7e9Gk90tBezjcoRr775BfU=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "9001416dc5d0ca24c8e4b5a44bfe7cd6fbeb1dd1",
"rev": "21b2795e6aeb4a0110bdc7bd81bad59c022c9986",
"type": "github"
},
"original": {
@@ -653,11 +653,11 @@
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1774915197,
"narHash": "sha256-yor+eo8CVi7wBp7CjAMQnVoK+m197gsl7MvUzaqicns=",
"lastModified": 1775689880,
"narHash": "sha256-savZYhFAaBm3BQUdTrPOv7i5K18JFANJvyHv0uuvaWM=",
"owner": "neovim",
"repo": "neovim",
"rev": "dbc4800dda2b0dc3290dc79955f857256e0694e2",
"rev": "eefb50e352a689ec1a0a55d6827abea79960cd3d",
"type": "github"
},
"original": {
@@ -734,11 +734,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1774855581,
"narHash": "sha256-YkreHeMgTCYvJ5fESV0YyqQK49bHGe2B51tH6claUh4=",
"lastModified": 1775639890,
"narHash": "sha256-9O9gNidrdzcb7vgKGtff7QiLtr0IsVaCi0pAXm8anhQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "15c6719d8c604779cf59e03c245ea61d3d7ab69b",
"rev": "456e8a9468b9d46bd8c9524425026c00745bc4d2",
"type": "github"
},
"original": {
@@ -750,11 +750,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1775036421,
"narHash": "sha256-kOAGXAqmmCmXpTJ0ZC/v0pUlyTFgwj31hEfJbcf0l70=",
"lastModified": 1775722436,
"narHash": "sha256-Z7QmfL80jmUPoSQkMlCc+1MGfkugf7bG47H3UTsyi7Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f16ce1b999cc00aa1222578a740e74b5fbfa0284",
"rev": "e73a61d035ee91f95bb0a6b95ce0b9d2866bd332",
"type": "github"
},
"original": {
@@ -803,11 +803,11 @@
"systems": "systems_4"
},
"locked": {
"lastModified": 1774802402,
"narHash": "sha256-L1UJ/zxKTyyaGGmytH6OYlgQ0HGSMhvPkvU+iz4Mkb8=",
"lastModified": 1775307257,
"narHash": "sha256-y9hEecHH4ennFwIcw1n480YCGh73DkEmizmQnyXuvgg=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "cbd8536a05d1aae2593cb5c9ace1010c8c5845cb",
"rev": "2e008bb941f72379d5b935d5bfe70ed8b7c793ff",
"type": "github"
},
"original": {
@@ -868,11 +868,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1774948198,
"narHash": "sha256-oVPo0/3CXM/5uFKu1ZwP7osSV2tiQIFU09Y3UzNbm7g=",
"lastModified": 1775663707,
"narHash": "sha256-3cSvpBETRa8aDSrUCX1jGc6FSse3OWB7cXACIZW8BYI=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "63b3eff38ef1c216480147dd53b0e4365d55f269",
"rev": "8c5af725817905e462052d91a8d229b85ffa83a5",
"type": "github"
},
"original": {
@@ -944,11 +944,11 @@
]
},
"locked": {
"lastModified": 1774910634,
"narHash": "sha256-B+rZDPyktGEjOMt8PcHKYmgmKoF+GaNAFJhguktXAo0=",
"lastModified": 1775682595,
"narHash": "sha256-0E9PohY/VuESLq0LR4doaH7hTag513sDDW5n5qmHd1Q=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "19bf3d8678fbbfbc173beaa0b5b37d37938db301",
"rev": "d2e8438d5886e92bc5e7c40c035ab6cae0c41f76",
"type": "github"
},
"original": {
@@ -1040,11 +1040,11 @@
]
},
"locked": {
"lastModified": 1773297127,
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
"lastModified": 1775636079,
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
"type": "github"
},
"original": {

View File

@@ -1,85 +0,0 @@
---
description: Autonomous deep worker — explores thoroughly, acts decisively, finishes the job
mode: primary
model: openai/gpt-5.4
temperature: 0.3
color: "#D97706"
reasoningEffort: xhigh
---
You are an autonomous deep worker for software engineering.
Build context by examining the codebase first. Do not assume. Think through the nuances of the code you encounter. Complete tasks end-to-end within the current turn. Persevere when tool calls fail. Only end your turn when the problem is solved and verified.
When blocked: try a different approach, decompose the problem, challenge assumptions, explore how others solved it. Asking the user is the last resort after exhausting alternatives.
## Do Not Ask — Just Do
FORBIDDEN:
- Asking permission ("Should I proceed?", "Would you like me to...?") — JUST DO IT
- "Do you want me to run tests?" — RUN THEM
- "I noticed Y, should I fix it?" — FIX IT
- Stopping after partial implementation — finish or don't start
- Answering a question then stopping — questions imply action, DO THE ACTION
- "I'll do X" then ending turn — you committed to X, DO X NOW
- Explaining findings without acting on them — ACT immediately
CORRECT:
- Keep going until COMPLETELY done
- Run verification without asking
- Make decisions; course-correct on concrete failure
- Note assumptions in your final message, not as questions mid-work
## Intent Extraction
Every message has surface form and true intent. Extract true intent BEFORE doing anything:
- "Did you do X?" (and you didn't) → Acknowledge, DO X immediately
- "How does X work?" → Explore, then implement/fix
- "Can you look into Y?" → Investigate AND resolve
- "What's the best way to do Z?" → Decide, then implement
- "Why is A broken?" → Diagnose, then fix
A message is pure question ONLY when the user explicitly says "just explain" or "don't change anything". Default: message implies action.
## Task Classification
Classify before acting:
- **Trivial**: Single file, known location, <10 lines — use tools directly, no exploration needed
- **Explicit**: Specific file/line given, clear instruction — execute directly
- **Exploratory**: "How does X work?", "Find Y" — fire parallel searches, then act on findings
- **Open-ended**: "Improve", "Refactor", "Add feature" — full execution loop required
- **Ambiguous**: Unclear scope, multiple interpretations — explore first (search, read, grep), ask only if exploration fails
Default bias: explore before asking. Exhaust tools before asking a clarifying question.
## Execution
1. **EXPLORE**: Search the codebase in parallel — fire multiple reads and searches simultaneously
2. **PLAN**: Identify files to modify, specific changes, dependencies
3. **EXECUTE**: Make the changes
4. **VERIFY**: Check diagnostics on all modified files, run tests, run build
If verification fails, return to step 1. After 3 failed approaches, stop edits, revert to last working state, and explain what you tried.
## Verification Is Mandatory
Before ending your turn, you MUST have:
- All requested functionality fully implemented
- Diagnostics clean on all modified files
- Build passing (if applicable)
- Tests passing (or pre-existing failures documented)
- Evidence for each verification step — "it should work" is not evidence
## Progress
Report what you're doing every ~30 seconds. One or two sentences with a concrete detail — a file path, a pattern found, a decision made.
## Self-Check Before Ending
1. Did the user's message imply action you haven't taken?
2. Did you commit to something ("I'll do X") without doing it?
3. Did you offer to do something instead of doing it?
4. Did you answer a question and stop when work was implied?
If any of these are true, you are not done. Continue working.

View File

@@ -1,17 +0,0 @@
---
description: Add AI session summary to GitHub PR or GitLab MR description
---
Update the PR/MR description with an AI session export summary.
First, invoke the skill tool to load the session-export skill:
```
skill({ name: 'session-export' })
```
Then follow the skill instructions to export the session summary.
<user-request>
$ARGUMENTS
</user-request>

View File

@@ -0,0 +1,150 @@
---
description: Initialize Supermemory with comprehensive codebase knowledge
---
# Initializing Supermemory
You are initializing persistent memory for this codebase. This is not just data collection - you're building context that will make you significantly more effective across all future sessions.
## Understanding Context
You are a **stateful** coding agent. Users expect to work with you over extended periods - potentially the entire lifecycle of a project. Your memory is how you get better over time and maintain continuity.
## What to Remember
### 1. Procedures (Rules & Workflows)
Explicit rules that should always be followed:
- "Never commit directly to main - always use feature branches"
- "Always run lint before tests"
- "Use conventional commits format"
### 2. Preferences (Style & Conventions)
Project and user coding style:
- "Prefer functional components over class components"
- "Use early returns instead of nested conditionals"
- "Always add JSDoc to exported functions"
### 3. Architecture & Context
How the codebase works and why:
- "Auth system was refactored in v2.0 - old patterns deprecated"
- "The monorepo used to have 3 modules before consolidation"
- "This pagination bug was fixed before - similar to PR #234"
## Memory Scopes
**Project-scoped** (\`scope: "project"\`):
- Build/test/lint commands
- Architecture and key directories
- Team conventions specific to this codebase
- Technology stack and framework choices
- Known issues and their solutions
**User-scoped** (\`scope: "user"\`):
- Personal coding preferences across all projects
- Communication style preferences
- General workflow habits
## Research Approach
This is a **deep research** initialization. Take your time and be thorough (~50+ tool calls). The goal is to genuinely understand the project, not just collect surface-level facts.
**What to uncover:**
- Tech stack and dependencies (explicit and implicit)
- Project structure and architecture
- Build/test/deploy commands and workflows
- Contributors & team dynamics (who works on what?)
- Commit conventions and branching strategy
- Code evolution (major refactors, architecture changes)
- Pain points (areas with lots of bug fixes)
- Implicit conventions not documented anywhere
## Research Techniques
### File-based
- README.md, CONTRIBUTING.md, AGENTS.md, CLAUDE.md
- Package manifests (package.json, Cargo.toml, pyproject.toml, go.mod)
- Config files (.eslintrc, tsconfig.json, .prettierrc)
- CI/CD configs (.github/workflows/)
### Git-based
- \`git log --oneline -20\` - Recent history
- \`git branch -a\` - Branching strategy
- \`git log --format="%s" -50\` - Commit conventions
- \`git shortlog -sn --all | head -10\` - Main contributors
### Explore Agent
Fire parallel explore queries for broad understanding:
\`\`\`
Task(explore, "What is the tech stack and key dependencies?")
Task(explore, "What is the project structure? Key directories?")
Task(explore, "How do you build, test, and run this project?")
Task(explore, "What are the main architectural patterns?")
Task(explore, "What conventions or patterns are used?")
\`\`\`
## How to Do Thorough Research
**Don't just collect data - analyze and cross-reference.**
Bad (shallow):
- Run commands, copy output
- List facts without understanding
Good (thorough):
- Cross-reference findings (if inconsistent, dig deeper)
- Resolve ambiguities (don't leave questions unanswered)
- Read actual file content, not just names
- Look for patterns (what do commits tell you about workflow?)
- Think like a new team member - what would you want to know?
## Saving Memories
Use the \`supermemory\` tool for each distinct insight:
\`\`\`
supermemory(mode: "add", content: "...", type: "...", scope: "project")
\`\`\`
**Types:**
- \`project-config\` - tech stack, commands, tooling
- \`architecture\` - codebase structure, key components, data flow
- \`learned-pattern\` - conventions specific to this codebase
- \`error-solution\` - known issues and their fixes
- \`preference\` - coding style preferences (use with user scope)
**Guidelines:**
- Save each distinct insight as a separate memory
- Be concise but include enough context to be useful
- Include the "why" not just the "what" when relevant
- Update memories incrementally as you research (don't wait until the end)
**Good memories:**
- "Uses Bun runtime and package manager. Commands: bun install, bun run dev, bun test"
- "API routes in src/routes/, handlers in src/handlers/. Hono framework."
- "Auth uses Redis sessions, not JWT. Implementation in src/lib/auth.ts"
- "Never use \`any\` type - strict TypeScript. Use \`unknown\` and narrow."
- "Database migrations must be backward compatible - we do rolling deploys"
## Upfront Questions
Before diving in, ask:
1. "Any specific rules I should always follow?"
2. "Preferences for how I communicate? (terse/detailed)"
## Reflection Phase
Before finishing, reflect:
1. **Completeness**: Did you cover commands, architecture, conventions, gotchas?
2. **Quality**: Are memories concise and searchable?
3. **Scope**: Did you correctly separate project vs user knowledge?
Then ask: "I've initialized memory with X insights. Want me to continue refining, or is this good?"
## Your Task
1. Ask upfront questions (research depth, rules, preferences)
2. Check existing memories: \`supermemory(mode: "list", scope: "project")\`
3. Research based on chosen depth
4. Save memories incrementally as you discover insights
5. Reflect and verify completeness
6. Summarize what was learned and ask if user wants refinement

View File

@@ -1,18 +0,0 @@
import type { Plugin } from "@opencode-ai/plugin";
export const DirenvPlugin: Plugin = async ({ $ }) => {
return {
"shell.env": async (input, output) => {
try {
const exported = await $`direnv export json`
.cwd(input.cwd)
.quiet()
.json();
Object.assign(output.env, exported);
} catch (error) {
console.warn("[direnv] failed to export env:", error);
}
},
};
};

View File

@@ -1,3 +1,9 @@
// Substantially modified from:
// https://github.com/mitsuhiko/agent-stuff/blob/80e1e96/pi-extensions/review.ts
// Copyright Armin Ronacher and contributors
// Licensed under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
import type {
TuiPlugin,
TuiPluginModule,
@@ -8,6 +14,17 @@ import path from "node:path"
type BookmarkRef = { name: string; remote?: string }
type Change = { changeId: string; title: string }
type JjRemote = { name: string; url: string }
type PullRequestListItem = {
prNumber: number
title: string
updatedAt: string
reviewRequested: boolean
author?: string
baseRefName?: string
headRefName?: string
isManualEntry?: boolean
}
type ReviewTarget =
| { type: "workingCopy" }
@@ -25,6 +42,9 @@ type ReviewTarget =
type ReviewSelectorValue = ReviewTarget["type"] | "toggleCustomInstructions"
const CUSTOM_INSTRUCTIONS_KEY = "review.customInstructions"
const MIN_CHANGE_REVIEW_OPTIONS = 10
const RECENT_PULL_REQUEST_LIMIT = 5
const PULL_REQUEST_MAX_AGE_DAYS = 7
const WORKING_COPY_PROMPT =
"Review the current working-copy changes (including new files) and provide prioritized findings."
@@ -156,6 +176,13 @@ Provide your findings in a clear, structured format:
Output all findings the author would fix if they knew about them. If there are no qualifying findings, explicitly state the code looks good. Don't stop at the first finding - list every qualifying issue. Then append the required non-blocking callouts section.`
function normalizeCustomInstructions(
value: string | undefined,
): string | undefined {
const normalized = value?.trim()
return normalized ? normalized : undefined
}
function bookmarkLabel(b: BookmarkRef): string {
return b.remote ? `${b.name}@${b.remote}` : b.name
}
@@ -205,19 +232,139 @@ function parseChanges(stdout: string): Change[] {
function parsePrRef(ref: string): number | null {
const trimmed = ref.trim()
const num = parseInt(trimmed, 10)
if (!isNaN(num) && num > 0) return num
const urlMatch = trimmed.match(/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/)
if (urlMatch) return parseInt(urlMatch[1], 10)
if (/^\d+$/.test(trimmed)) {
const num = Number(trimmed)
return Number.isSafeInteger(num) && num > 0 ? num : null
}
const urlMatch = trimmed.match(
/^(?:https?:\/\/)?github\.com\/[^/]+\/[^/]+\/pull\/(\d+)(?:[/?#].*)?$/i,
)
if (urlMatch) {
const num = Number(urlMatch[1])
return Number.isSafeInteger(num) && num > 0 ? num : null
}
return null
}
function formatRelativeTime(value: string): string | null {
const timestamp = Date.parse(value)
if (!Number.isFinite(timestamp)) return null
const deltaMs = Date.now() - timestamp
const future = deltaMs < 0
const absoluteSeconds = Math.round(Math.abs(deltaMs) / 1000)
if (absoluteSeconds < 60) return future ? "in <1m" : "just now"
const units = [
{ label: "y", seconds: 60 * 60 * 24 * 365 },
{ label: "mo", seconds: 60 * 60 * 24 * 30 },
{ label: "d", seconds: 60 * 60 * 24 },
{ label: "h", seconds: 60 * 60 },
{ label: "m", seconds: 60 },
]
for (const unit of units) {
if (absoluteSeconds >= unit.seconds) {
const count = Math.floor(absoluteSeconds / unit.seconds)
return future ? `in ${count}${unit.label}` : `${count}${unit.label} ago`
}
}
return future ? "soon" : "just now"
}
function getPullRequestUpdatedSinceDate(days: number): string {
const timestamp = Date.now() - days * 24 * 60 * 60 * 1000
return new Date(timestamp).toISOString().slice(0, 10)
}
function parsePullRequests(stdout: string): PullRequestListItem[] {
let parsed: unknown
try {
parsed = JSON.parse(stdout)
} catch {
return []
}
if (!Array.isArray(parsed)) return []
return parsed.flatMap((entry) => {
if (!entry || typeof entry !== "object") return []
const prNumber =
typeof entry.number === "number" && Number.isSafeInteger(entry.number)
? entry.number
: null
const title = typeof entry.title === "string" ? entry.title.trim() : ""
const updatedAt =
typeof entry.updatedAt === "string" ? entry.updatedAt.trim() : ""
if (!prNumber || !title || !updatedAt) return []
return [
{
prNumber,
title,
updatedAt,
reviewRequested: entry.reviewRequested === true,
author:
entry.author &&
typeof entry.author === "object" &&
typeof entry.author.login === "string"
? entry.author.login
: undefined,
baseRefName:
typeof entry.baseRefName === "string"
? entry.baseRefName.trim() || undefined
: undefined,
headRefName:
typeof entry.headRefName === "string"
? entry.headRefName.trim() || undefined
: undefined,
},
]
})
}
function dedupePullRequests(
pullRequests: PullRequestListItem[],
): PullRequestListItem[] {
const seen = new Set<number>()
const result: PullRequestListItem[] = []
for (const pullRequest of pullRequests) {
if (seen.has(pullRequest.prNumber)) continue
seen.add(pullRequest.prNumber)
result.push(pullRequest)
}
return result
}
function buildPullRequestOptionDescription(pr: PullRequestListItem): string {
if (pr.isManualEntry) return "(enter PR number or URL)"
const parts = [pr.reviewRequested ? "review requested" : "recent"]
const relativeTime = formatRelativeTime(pr.updatedAt)
if (relativeTime) parts.push(`updated ${relativeTime}`)
if (pr.author) parts.push(`@${pr.author}`)
if (pr.baseRefName && pr.headRefName) {
parts.push(`${pr.headRefName}${pr.baseRefName}`)
}
return `(${parts.join(" · ")})`
}
function normalizeRemoteUrl(value: string): string {
return value
.trim()
.replace(/^git@github\.com:/, "https://github.com/")
.replace(/^ssh:\/\/git@github\.com\//, "https://github.com/")
.replace(/^git@([^:]+):/, "https://$1/")
.replace(/^ssh:\/\/git@([^/]+)\//, "https://$1/")
.replace(/^(https?:\/\/)[^/@]+@/i, "$1")
.replace(/\.git$/, "")
.replace(/\/+$/, "")
.toLowerCase()
}
@@ -228,18 +375,14 @@ function sanitizeRemoteName(value: string): string {
)
}
function getRepositoryUrl(value: string): string | null {
const match = normalizeRemoteUrl(value).match(/^https?:\/\/[^/]+\/[^/]+\/[^/]+/)
if (!match) return null
return match[0]
}
const plugin: TuiPlugin = async (api) => {
const cwd = api.state.path.directory
let reviewCustomInstructions = normalizeCustomInstructions(
api.kv.get<string | undefined>(CUSTOM_INSTRUCTIONS_KEY, undefined),
)
function setReviewCustomInstructions(value?: string): void {
reviewCustomInstructions = normalizeCustomInstructions(value)
api.kv.set(CUSTOM_INSTRUCTIONS_KEY, reviewCustomInstructions)
}
// -- shell helpers -------------------------------------------------------
async function exec(
cmd: string,
@@ -276,72 +419,33 @@ const plugin: TuiPlugin = async (api) => {
return { stdout: r.stdout, ok: r.exitCode === 0, stderr: r.stderr }
}
// -- jj helpers ----------------------------------------------------------
async function isJjRepo(): Promise<boolean> {
return (await jj("root")).ok
}
const reviewCustomInstructionsKey = `${CUSTOM_INSTRUCTIONS_KEY}-${cwd}`
let reviewCustomInstructions = normalizeCustomInstructions(
api.kv.get<string | undefined>(reviewCustomInstructionsKey, undefined),
)
function setReviewCustomInstructions(value?: string): void {
reviewCustomInstructions = normalizeCustomInstructions(value)
api.kv.set(reviewCustomInstructionsKey, reviewCustomInstructions)
}
async function hasWorkingCopyChanges(): Promise<boolean> {
const r = await jj("diff", "--summary")
return r.ok && r.stdout.trim().length > 0
}
async function getBookmarks(): Promise<BookmarkRef[]> {
const r = await jj(
"bookmark",
"list",
"--all-remotes",
"-T",
'name ++ "\\t" ++ remote ++ "\\n"',
)
if (!r.ok) return []
return parseBookmarks(r.stdout)
}
async function getCurrentBookmarks(): Promise<BookmarkRef[]> {
const headRevset = (await hasWorkingCopyChanges()) ? "@" : "@-"
const r = await jj(
"bookmark",
"list",
"--all-remotes",
"-r",
headRevset,
"-T",
'name ++ "\\t" ++ remote ++ "\\n"',
)
if (!r.ok) return []
return parseBookmarks(r.stdout)
}
async function getDefaultBookmark(): Promise<BookmarkRef | null> {
const trunkR = await jj(
"bookmark",
"list",
"--all-remotes",
"-r",
"trunk()",
"-T",
'name ++ "\\t" ++ remote ++ "\\n"',
)
if (trunkR.ok) {
const bookmarks = parseBookmarks(trunkR.stdout)
if (bookmarks.length > 0) return bookmarks[0]
}
const all = await getBookmarks()
return (
all.find((b) => !b.remote && b.name === "main") ??
all.find((b) => !b.remote && b.name === "master") ??
all[0] ??
null
)
}
async function getRecentChanges(limit = 20): Promise<Change[]> {
const effectiveLimit = Math.max(limit, MIN_CHANGE_REVIEW_OPTIONS)
const r = await jj(
"log",
"-r",
"all()",
"-n",
String(limit),
String(effectiveLimit),
"--no-graph",
"-T",
'change_id.shortest(8) ++ "\\t" ++ description.first_line() ++ "\\n"',
@@ -350,6 +454,53 @@ const plugin: TuiPlugin = async (api) => {
return parseChanges(r.stdout)
}
async function getPullRequests(
args: string[],
reviewRequested: boolean,
): Promise<PullRequestListItem[]> {
const response = await gh(
"pr",
"list",
...args,
"--json",
"number,title,updatedAt,author,baseRefName,headRefName",
)
if (!response.ok) return []
return parsePullRequests(response.stdout).map((pr) => ({
...pr,
reviewRequested,
}))
}
async function getSelectablePullRequests(): Promise<PullRequestListItem[]> {
const updatedSince = getPullRequestUpdatedSinceDate(
PULL_REQUEST_MAX_AGE_DAYS,
)
const [reviewRequested, recent] = await Promise.all([
getPullRequests(
[
"--search",
`review-requested:@me updated:>=${updatedSince} sort:updated-desc`,
"--limit",
"50",
],
true,
),
getPullRequests(
[
"--search",
`updated:>=${updatedSince} sort:updated-desc`,
"--limit",
String(RECENT_PULL_REQUEST_LIMIT),
],
false,
),
])
return dedupePullRequests([...reviewRequested, ...recent])
}
async function getMergeBase(
bookmark: string,
remote?: string,
@@ -372,8 +523,6 @@ const plugin: TuiPlugin = async (api) => {
return lines.length === 1 ? lines[0].trim() : null
}
// -- PR materialization --------------------------------------------------
async function materializePr(prNumber: number): Promise<
| {
ok: true
@@ -393,24 +542,19 @@ const plugin: TuiPlugin = async (api) => {
}
}
const savedR = await jj(
"log",
"-r",
"@",
"--no-graph",
"-T",
"change_id.shortest(8)",
)
const savedChangeId = savedR.stdout.trim()
const savedChangeId = await getSingleChangeId("@")
if (!savedChangeId) {
return { ok: false, error: "Failed to determine the current change" }
}
const prR = await gh(
const prResponse = await gh(
"pr",
"view",
String(prNumber),
"--json",
"baseRefName,title,headRefName,isCrossRepository,headRepository,headRepositoryOwner",
"baseRefName,title,headRefName,isCrossRepository,headRepository,headRepositoryOwner,url",
)
if (!prR.ok) {
if (!prResponse.ok) {
return {
ok: false,
error: `Could not find PR #${prNumber}. Check gh auth and that the PR exists.`,
@@ -422,54 +566,36 @@ const plugin: TuiPlugin = async (api) => {
title: string
headRefName: string
isCrossRepository: boolean
url: string
headRepository?: { name: string; url: string }
headRepositoryOwner?: { login: string }
}
try {
prInfo = JSON.parse(prR.stdout)
prInfo = JSON.parse(prResponse.stdout)
} catch {
return { ok: false, error: "Failed to parse PR info" }
}
const remotesR = await jj("git", "remote", "list")
const remotes = remotesR.stdout
.trim()
.split("\n")
.filter(Boolean)
.map((line) => {
const [name, ...urlParts] = line.split(/\s+/)
return { name, url: urlParts.join(" ") }
})
.filter((r) => r.name && r.url)
const defaultRemote =
remotes.find((r) => r.name === "origin") ?? remotes[0]
const remotes = await getJjRemotes()
const defaultRemote = getDefaultRemote(remotes)
if (!defaultRemote) {
return { ok: false, error: "No jj remotes configured" }
}
const baseRepoUrl = getRepositoryUrl(prInfo.url)
const baseRemote = baseRepoUrl
? remotes.find((remote) => getRepositoryUrl(remote.url) === baseRepoUrl)
: undefined
let remoteName = defaultRemote.name
let addedTempRemote = false
if (prInfo.isCrossRepository) {
const repoSlug =
prInfo.headRepositoryOwner?.login && prInfo.headRepository?.name
? `${prInfo.headRepositoryOwner.login}/${prInfo.headRepository.name}`.toLowerCase()
: undefined
const forkUrl = prInfo.headRepository?.url
const existingRemote = remotes.find((r) => {
if (
forkUrl &&
normalizeRemoteUrl(r.url) === normalizeRemoteUrl(forkUrl)
)
return true
return repoSlug
? normalizeRemoteUrl(r.url).includes(
`github.com/${repoSlug}`,
)
: false
})
const forkRepoUrl = forkUrl ? getRepositoryUrl(forkUrl) : null
const existingRemote = forkRepoUrl
? remotes.find((remote) => getRepositoryUrl(remote.url) === forkRepoUrl)
: undefined
if (existingRemote) {
remoteName = existingRemote.name
@@ -483,21 +609,23 @@ const plugin: TuiPlugin = async (api) => {
while (names.has(remoteName)) {
remoteName = `${baseName}-${suffix++}`
}
const addR = await jj(
const addRemoteResult = await jj(
"git",
"remote",
"add",
remoteName,
forkUrl,
)
if (!addR.ok) return { ok: false, error: "Failed to add PR remote" }
if (!addRemoteResult.ok) {
return { ok: false, error: "Failed to add PR remote" }
}
addedTempRemote = true
} else {
return { ok: false, error: "PR fork URL is unavailable" }
}
}
const fetchR = await jj(
const fetchHeadResult = await jj(
"git",
"fetch",
"--remote",
@@ -505,15 +633,15 @@ const plugin: TuiPlugin = async (api) => {
"--branch",
prInfo.headRefName,
)
if (!fetchR.ok) {
if (!fetchHeadResult.ok) {
if (addedTempRemote)
await jj("git", "remote", "remove", remoteName)
return { ok: false, error: "Failed to fetch PR branch" }
}
const revset = `remote_bookmarks(exact:${JSON.stringify(prInfo.headRefName)}, exact:${JSON.stringify(remoteName)})`
const newR = await jj("new", revset)
if (!newR.ok) {
const createChangeResult = await jj("new", revset)
if (!createChangeResult.ok) {
if (addedTempRemote)
await jj("git", "remote", "remove", remoteName)
return { ok: false, error: "Failed to create change on PR branch" }
@@ -521,27 +649,17 @@ const plugin: TuiPlugin = async (api) => {
if (addedTempRemote) await jj("git", "remote", "remove", remoteName)
// Resolve base bookmark remote
const baseRef = await resolveBookmarkRef(prInfo.baseRefName)
return {
ok: true,
title: prInfo.title,
baseBookmark: prInfo.baseRefName,
baseRemote: baseRef?.remote,
baseRemote: baseRemote?.name,
headBookmark: prInfo.headRefName,
remote: remoteName,
savedChangeId,
}
}
function normalizeCustomInstructions(
value: string | undefined,
): string | undefined {
const normalized = value?.trim()
return normalized ? normalized : undefined
}
function parseNonEmptyLines(stdout: string): string[] {
return stdout
.trim()
@@ -554,19 +672,6 @@ const plugin: TuiPlugin = async (api) => {
return left.name === right.name && left.remote === right.remote
}
function parseBookmarkReference(value: string): BookmarkRef {
const trimmed = value.trim()
const separatorIndex = trimmed.lastIndexOf("@")
if (separatorIndex <= 0 || separatorIndex === trimmed.length - 1) {
return { name: trimmed }
}
return {
name: trimmed.slice(0, separatorIndex),
remote: trimmed.slice(separatorIndex + 1),
}
}
function dedupeBookmarkRefs(bookmarks: BookmarkRef[]): BookmarkRef[] {
const seen = new Set<string>()
const result: BookmarkRef[] = []
@@ -625,7 +730,7 @@ const plugin: TuiPlugin = async (api) => {
return revisions.length === 1 ? revisions[0] : null
}
async function getJjRemotes(): Promise<Array<{ name: string; url: string }>> {
async function getJjRemotes(): Promise<JjRemote[]> {
const r = await jj("git", "remote", "list")
if (!r.ok) return []
@@ -637,10 +742,16 @@ const plugin: TuiPlugin = async (api) => {
.filter((remote) => remote.name && remote.url)
}
function getDefaultRemote(remotes: JjRemote[]): JjRemote | null {
return (
remotes.find((remote) => remote.name === "origin") ??
remotes[0] ??
null
)
}
async function getDefaultRemoteName(): Promise<string | null> {
const remotes = await getJjRemotes()
if (remotes.length === 0) return null
return remotes.find((remote) => remote.name === "origin")?.name ?? remotes[0].name
return getDefaultRemote(await getJjRemotes())?.name ?? null
}
function preferBookmarkRef(
@@ -759,8 +870,6 @@ const plugin: TuiPlugin = async (api) => {
}
}
// -- prompt building -----------------------------------------------------
async function buildTargetReviewPrompt(
target: ReviewTarget,
options?: { includeLocalChanges?: boolean },
@@ -841,15 +950,15 @@ const plugin: TuiPlugin = async (api) => {
}
async function buildReviewPrompt(target: ReviewTarget): Promise<string> {
const prompt = await buildTargetReviewPrompt(target)
const prompt = await buildTargetReviewPrompt(target, {
includeLocalChanges:
target.type !== "workingCopy" && (await hasWorkingCopyChanges()),
})
const projectGuidelines = await loadProjectReviewGuidelines()
const sharedInstructions = normalizeCustomInstructions(
reviewCustomInstructions,
)
let fullPrompt = `${REVIEW_RUBRIC}\n\n---\n\nPlease perform a code review with the following focus:\n\n${prompt}`
if (sharedInstructions) {
fullPrompt += `\n\nShared custom review instructions (applies to all reviews):\n\n${sharedInstructions}`
if (reviewCustomInstructions) {
fullPrompt += `\n\nCustom review instructions for this working directory (applies to all review modes here):\n\n${reviewCustomInstructions}`
}
if (projectGuidelines) {
@@ -910,17 +1019,13 @@ const plugin: TuiPlugin = async (api) => {
}
}
// -- review execution ----------------------------------------------------
async function startReview(target: ReviewTarget): Promise<void> {
async function startReview(target: ReviewTarget): Promise<boolean> {
const prompt = await buildReviewPrompt(target)
const hint = getUserFacingHint(target)
const cleared = await api.client.tui.clearPrompt()
const appended = await api.client.tui.appendPrompt({
text: prompt,
})
// `prompt.submit` is ignored unless the prompt input is focused.
// When this runs from a dialog, focus returns on the next tick.
await sleep(50)
const submitted = await api.client.tui.submitPrompt()
@@ -929,16 +1034,16 @@ const plugin: TuiPlugin = async (api) => {
message: "Failed to start review prompt automatically",
variant: "error",
})
return
return false
}
api.ui.toast({
message: `Starting review: ${hint}`,
variant: "info",
})
}
// -- dialogs -------------------------------------------------------------
return true
}
async function showReviewSelector(): Promise<void> {
const smartDefault = await getSmartDefault()
@@ -972,8 +1077,8 @@ const plugin: TuiPlugin = async (api) => {
: "Add custom review instructions",
value: "toggleCustomInstructions",
description: reviewCustomInstructions
? "(currently set)"
: "(applies to all review modes)",
? "(set for this directory)"
: "(this directory, all review modes)",
},
]
@@ -996,7 +1101,7 @@ const plugin: TuiPlugin = async (api) => {
void showChangeSelector()
break
case "pullRequest":
void showPrInput()
void showPrSelector()
break
case "folder":
showFolderInput()
@@ -1005,7 +1110,8 @@ const plugin: TuiPlugin = async (api) => {
if (reviewCustomInstructions) {
setReviewCustomInstructions(undefined)
api.ui.toast({
message: "Custom review instructions removed",
message:
"Custom review instructions removed for this directory",
variant: "info",
})
void showReviewSelector()
@@ -1040,7 +1146,8 @@ const plugin: TuiPlugin = async (api) => {
setReviewCustomInstructions(next)
api.ui.toast({
message: "Custom review instructions saved",
message:
"Custom review instructions saved for this directory",
variant: "success",
})
void showReviewSelector()
@@ -1150,16 +1257,7 @@ const plugin: TuiPlugin = async (api) => {
)
}
async function showPrInput(): Promise<void> {
if (await hasWorkingCopyChanges()) {
api.ui.toast({
message:
"Cannot materialize PR: you have local jj changes. Please snapshot or discard them first.",
variant: "error",
})
return
}
function showPrManualInput(): void {
api.ui.dialog.replace(
() =>
api.ui.DialogPrompt({
@@ -1179,6 +1277,69 @@ const plugin: TuiPlugin = async (api) => {
api.ui.dialog.clear()
void handlePrReview(prNumber)
},
onCancel: () => {
api.ui.dialog.clear()
void showPrSelector()
},
}),
)
}
async function showPrSelector(): Promise<void> {
if (await hasWorkingCopyChanges()) {
api.ui.toast({
message:
"Cannot materialize PR: you have local jj changes. Please snapshot or discard them first.",
variant: "error",
})
return
}
api.ui.toast({ message: "Loading pull requests...", variant: "info" })
const pullRequests = await getSelectablePullRequests()
const options: TuiDialogSelectOption<PullRequestListItem>[] = [
{
title: "Enter a PR number or URL",
value: {
prNumber: -1,
title: "Manual entry",
updatedAt: "",
reviewRequested: false,
isManualEntry: true,
},
description: "(override the list)",
},
...pullRequests.map((pr) => ({
title: `#${pr.prNumber} ${pr.title}`,
value: pr,
description: buildPullRequestOptionDescription(pr),
})),
]
if (pullRequests.length === 0) {
api.ui.toast({
message:
"No pull requests found from GitHub; you can still enter a PR number or URL.",
variant: "info",
})
}
api.ui.dialog.replace(
() =>
api.ui.DialogSelect({
title: "Select pull request to review",
placeholder: "Filter pull requests...",
options,
onSelect: (option) => {
api.ui.dialog.clear()
if (option.value.isManualEntry) {
showPrManualInput()
return
}
void handlePrReview(option.value.prNumber)
},
}),
)
}
@@ -1206,13 +1367,22 @@ const plugin: TuiPlugin = async (api) => {
variant: "info",
})
await startReview({
const started = await startReview({
type: "pullRequest",
prNumber,
baseBookmark: result.baseBookmark,
baseRemote: result.baseRemote,
title: result.title,
})
if (started) return
const restored = await jj("edit", result.savedChangeId)
api.ui.toast({
message: restored.ok
? "Restored the previous change after the review prompt failed"
: `Review prompt failed and restoring the previous change also failed (${result.savedChangeId})`,
variant: restored.ok ? "info" : "error",
})
}
function showFolderInput(): void {
@@ -1240,12 +1410,8 @@ const plugin: TuiPlugin = async (api) => {
)
}
// -- jj repo check -------------------------------------------------------
const inJjRepo = await isJjRepo()
// -- command registration ------------------------------------------------
api.command.register(() =>
inJjRepo
? [

View File

@@ -1,123 +0,0 @@
---
name: librarian
description: Multi-repository codebase exploration. Research library internals, find code patterns, understand architecture, compare implementations across GitHub/npm/PyPI/crates. Use when needing deep understanding of how libraries work, finding implementations across open source, or exploring remote repository structure.
references:
- references/tool-routing.md
- references/opensrc-api.md
- references/opensrc-examples.md
- references/linking.md
- references/diagrams.md
---
# Librarian Skill
Deep codebase exploration across remote repositories.
## How to Use This Skill
### Reference Structure
| File | Purpose | When to Read |
|------|---------|--------------|
| `tool-routing.md` | Tool selection decision trees | **Always read first** |
| `opensrc-api.md` | API reference, types | Writing opensrc code |
| `opensrc-examples.md` | JavaScript patterns, workflows | Implementation examples |
| `linking.md` | GitHub URL patterns | Formatting responses |
| `diagrams.md` | Mermaid patterns | Visualizing architecture |
### Reading Order
1. **Start** with `tool-routing.md` → choose tool strategy
2. **If using opensrc:**
- Read `opensrc-api.md` for API details
- Read `opensrc-examples.md` for patterns
3. **Before responding:** `linking.md` + `diagrams.md` for output formatting
## Tool Arsenal
| Tool | Best For | Limitations |
|------|----------|-------------|
| **grep_app** | Find patterns across ALL public GitHub | Literal search only |
| **context7** | Library docs, API examples, usage | Known libraries only |
| **opensrc** | Fetch full source for deep exploration | Must fetch before read |
## Quick Decision Trees
### "How does X work?"
```
Known library?
├─ Yes → context7.resolve-library-id → context7.query-docs
│ └─ Need internals? → opensrc.fetch → read source
└─ No → grep_app search → opensrc.fetch top result
```
### "Find pattern X"
```
Specific repo?
├─ Yes → opensrc.fetch → opensrc.grep → read matches
└─ No → grep_app (broad) → opensrc.fetch interesting repos
```
### "Explore repo structure"
```
1. opensrc.fetch(target)
2. opensrc.tree(source.name) → quick overview
3. opensrc.files(source.name, "**/*.ts") → detailed listing
4. Read: README, package.json, src/index.*
5. Create architecture diagram (see diagrams.md)
```
### "Compare X vs Y"
```
1. opensrc.fetch(["X", "Y"])
2. Use source.name from results for subsequent calls
3. opensrc.grep(pattern, { sources: [nameX, nameY] })
4. Read comparable files, synthesize differences
```
## Critical: Source Naming Convention
**After fetching, always use `source.name` for subsequent calls:**
```javascript
const [{ source }] = await opensrc.fetch("vercel/ai");
const files = await opensrc.files(source.name, "**/*.ts");
```
| Type | Fetch Spec | Source Name |
|------|------------|-------------|
| npm | `"zod"` | `"zod"` |
| npm scoped | `"@tanstack/react-query"` | `"@tanstack/react-query"` |
| pypi | `"pypi:requests"` | `"requests"` |
| crates | `"crates:serde"` | `"serde"` |
| GitHub | `"vercel/ai"` | `"github.com/vercel/ai"` |
| GitLab | `"gitlab:org/repo"` | `"gitlab.com/org/repo"` |
## When NOT to Use opensrc
| Scenario | Use Instead |
|----------|-------------|
| Simple library API questions | context7 |
| Finding examples across many repos | grep_app |
| Very large monorepos (>10GB) | Clone locally |
| Private repositories | Direct access |
## Output Guidelines
1. **Comprehensive final message** - only last message returns to main agent
2. **Parallel tool calls** - maximize efficiency
3. **Link every file reference** - see `linking.md`
4. **Diagram complex relationships** - see `diagrams.md`
5. **Never mention tool names** - say "I'll search" not "I'll use opensrc"
## References
- [Tool Routing Decision Trees](references/tool-routing.md)
- [opensrc API Reference](references/opensrc-api.md)
- [opensrc Code Examples](references/opensrc-examples.md)
- [GitHub Linking Patterns](references/linking.md)
- [Mermaid Diagram Patterns](references/diagrams.md)

View File

@@ -1,51 +0,0 @@
# Mermaid Diagram Patterns
Create diagrams for:
- Architecture (component relationships)
- Data flow (request → response)
- Dependencies (import graph)
- Sequences (step-by-step processes)
## Architecture
```mermaid
graph TD
A[Client] --> B[API Gateway]
B --> C[Auth Service]
B --> D[Data Service]
D --> E[(Database)]
```
## Flow
```mermaid
flowchart LR
Input --> Parse --> Validate --> Transform --> Output
```
## Sequence
```mermaid
sequenceDiagram
Client->>+Server: Request
Server->>+DB: Query
DB-->>-Server: Result
Server-->>-Client: Response
```
## When to Use
| Type | Use For |
|------|---------|
| `graph TD` | Component hierarchy, dependencies |
| `flowchart LR` | Data transformation, pipelines |
| `sequenceDiagram` | Request/response, multi-party interaction |
| `classDiagram` | Type relationships, inheritance |
| `stateDiagram` | State machines, lifecycle |
## Tips
- Keep nodes short (3-4 words max)
- Use subgraphs for grouping related components
- Arrow labels for relationship types
- Prefer LR (left-right) for flows, TD (top-down) for hierarchies

View File

@@ -1,61 +0,0 @@
# GitHub Linking Patterns
All file/dir/code refs → fluent markdown links. Never raw URLs.
## URL Formats
### File
```
https://github.com/{owner}/{repo}/blob/{ref}/{path}
```
### File + Lines
```
https://github.com/{owner}/{repo}/blob/{ref}/{path}#L{start}-L{end}
```
### Directory
```
https://github.com/{owner}/{repo}/tree/{ref}/{path}
```
### GitLab (note `/-/blob/`)
```
https://gitlab.com/{owner}/{repo}/-/blob/{ref}/{path}
```
## Ref Resolution
| Source | Use as ref |
|--------|------------|
| Known version | `v{version}` |
| Default branch | `main` or `master` |
| opensrc fetch | ref from result |
| Specific commit | full SHA |
## Examples
### Correct
```markdown
The [`parseAsync`](https://github.com/colinhacks/zod/blob/main/src/types.ts#L450-L480) method handles...
```
### Wrong
```markdown
See https://github.com/colinhacks/zod/blob/main/src/types.ts#L100
The parseAsync method in src/types.ts handles...
```
## Line Numbers
- Single: `#L42`
- Range: `#L42-L50`
- Prefer ranges for context (2-5 lines around key code)
## Registry → GitHub
| Registry | Find repo in |
|----------|--------------|
| npm | `package.json``repository` |
| PyPI | `pyproject.toml` or setup.py |
| crates | `Cargo.toml` |

View File

@@ -1,235 +0,0 @@
# opensrc API Reference
## Tool
Use the **opensrc MCP server** via single tool:
| Tool | Purpose |
|------|---------|
| `opensrc_execute` | All operations (fetch, read, grep, files, remove, etc.) |
Takes a `code` parameter: JavaScript async arrow function executed server-side. Source trees stay on server, only results return.
## API Surface
### Read Operations
```typescript
// List all fetched sources
opensrc.list(): Source[]
// Check if source exists
opensrc.has(name: string, version?: string): boolean
// Get source metadata
opensrc.get(name: string): Source | undefined
// List files with optional glob
opensrc.files(sourceName: string, glob?: string): Promise<FileEntry[]>
// Get directory tree structure (default depth: 3)
opensrc.tree(sourceName: string, options?: { depth?: number }): Promise<TreeNode>
// Regex search file contents
opensrc.grep(pattern: string, options?: GrepOptions): Promise<GrepResult[]>
// AST-based semantic code search
opensrc.astGrep(sourceName: string, pattern: string, options?: AstGrepOptions): Promise<AstGrepMatch[]>
// Read single file
opensrc.read(sourceName: string, filePath: string): Promise<string>
// Batch read multiple files (supports globs!)
opensrc.readMany(sourceName: string, paths: string[]): Promise<Record<string, string>>
// Parse fetch spec
opensrc.resolve(spec: string): Promise<ParsedSpec>
```
### Mutation Operations
```typescript
// Fetch packages/repos
opensrc.fetch(specs: string | string[], options?: { modify?: boolean }): Promise<FetchedSource[]>
// Remove sources
opensrc.remove(names: string[]): Promise<RemoveResult>
// Clean by type
opensrc.clean(options?: CleanOptions): Promise<RemoveResult>
```
## Types
### Source
```typescript
interface Source {
type: "npm" | "pypi" | "crates" | "repo";
name: string; // Use this for all subsequent calls
version?: string;
ref?: string;
path: string;
fetchedAt: string;
repository: string;
}
```
### FetchedSource
```typescript
interface FetchedSource {
source: Source; // IMPORTANT: use source.name for subsequent calls
alreadyExists: boolean;
}
```
### GrepOptions
```typescript
interface GrepOptions {
sources?: string[]; // Filter to specific sources
include?: string; // File glob pattern (e.g., "*.ts")
maxResults?: number; // Limit results (default: 100)
}
```
### GrepResult
```typescript
interface GrepResult {
source: string;
file: string;
line: number;
content: string;
}
```
### AstGrepOptions
```typescript
interface AstGrepOptions {
glob?: string; // File glob pattern (e.g., "**/*.ts")
lang?: string | string[]; // Language(s): "js", "ts", "tsx", "html", "css"
limit?: number; // Max results (default: 1000)
}
```
### AstGrepMatch
```typescript
interface AstGrepMatch {
file: string;
line: number;
column: number;
endLine: number;
endColumn: number;
text: string; // Matched code text
metavars: Record<string, string>; // Captured $VAR → text
}
```
#### AST Pattern Syntax
| Pattern | Matches |
|---------|---------|
| `$NAME` | Single node, captures to metavars |
| `$$$ARGS` | Zero or more nodes (variadic), captures |
| `$_` | Single node, no capture |
| `$$$` | Zero or more nodes, no capture |
### FileEntry
```typescript
interface FileEntry {
path: string;
size: number;
isDirectory: boolean;
}
```
### TreeNode
```typescript
interface TreeNode {
name: string;
type: "file" | "dir";
children?: TreeNode[]; // only for dirs
}
```
### CleanOptions
```typescript
interface CleanOptions {
packages?: boolean;
repos?: boolean;
npm?: boolean;
pypi?: boolean;
crates?: boolean;
}
```
### RemoveResult
```typescript
interface RemoveResult {
success: boolean;
removed: string[];
}
```
## Error Handling
Operations throw on errors. Wrap in try/catch if needed:
```javascript
async () => {
try {
const content = await opensrc.read("zod", "missing.ts");
return content;
} catch (e) {
return { error: e.message };
}
}
```
`readMany` returns errors as string values prefixed with `[Error:`:
```javascript
const files = await opensrc.readMany("zod", ["exists.ts", "missing.ts"]);
// { "exists.ts": "content...", "missing.ts": "[Error: ENOENT...]" }
// Filter successful reads
const successful = Object.entries(files)
.filter(([_, content]) => !content.startsWith("[Error:"));
```
## Package Spec Formats
| Format | Example | Source Name After Fetch |
|--------|---------|------------------------|
| `<name>` | `"zod"` | `"zod"` |
| `<name>@<version>` | `"zod@3.22.0"` | `"zod"` |
| `pypi:<name>` | `"pypi:requests"` | `"requests"` |
| `crates:<name>` | `"crates:serde"` | `"serde"` |
| `owner/repo` | `"vercel/ai"` | `"github.com/vercel/ai"` |
| `owner/repo@ref` | `"vercel/ai@v1.0.0"` | `"github.com/vercel/ai"` |
| `gitlab:owner/repo` | `"gitlab:org/repo"` | `"gitlab.com/org/repo"` |
## Critical Pattern
**Always capture `source.name` from fetch results:**
```javascript
async () => {
const [{ source }] = await opensrc.fetch("vercel/ai");
// GitHub repos: "vercel/ai" → "github.com/vercel/ai"
const sourceName = source.name;
// Use sourceName for ALL subsequent calls
const files = await opensrc.files(sourceName, "src/**/*.ts");
return files;
}
```

View File

@@ -1,336 +0,0 @@
# opensrc Code Examples
## Workflow: Fetch → Explore
### Basic Fetch and Explore with tree()
```javascript
async () => {
const [{ source }] = await opensrc.fetch("vercel/ai");
// Get directory structure first
const tree = await opensrc.tree(source.name, { depth: 2 });
return tree;
}
```
### Fetch and Read Key Files
```javascript
async () => {
const [{ source }] = await opensrc.fetch("vercel/ai");
const sourceName = source.name; // "github.com/vercel/ai"
const files = await opensrc.readMany(sourceName, [
"package.json",
"README.md",
"src/index.ts"
]);
return { sourceName, files };
}
```
### readMany with Globs
```javascript
async () => {
const [{ source }] = await opensrc.fetch("zod");
// Read all package.json files in monorepo
const files = await opensrc.readMany(source.name, [
"packages/*/package.json" // globs supported!
]);
return Object.keys(files);
}
```
### Batch Fetch Multiple Packages
```javascript
async () => {
const results = await opensrc.fetch(["zod", "valibot", "yup"]);
const names = results.map(r => r.source.name);
// Compare how each handles string validation
const comparisons = {};
for (const name of names) {
const matches = await opensrc.grep("string.*validate|validateString", {
sources: [name],
include: "*.ts",
maxResults: 10
});
comparisons[name] = matches.map(m => `${m.file}:${m.line}`);
}
return comparisons;
}
```
## Search Patterns
### Grep → Read Context
```javascript
async () => {
const matches = await opensrc.grep("export function parse\\(", {
sources: ["zod"],
include: "*.ts"
});
if (matches.length === 0) return "No matches";
const match = matches[0];
const content = await opensrc.read(match.source, match.file);
const lines = content.split("\n");
// Return 40 lines starting from match
return {
file: match.file,
code: lines.slice(match.line - 1, match.line + 39).join("\n")
};
}
```
### Search Across All Fetched Sources
```javascript
async () => {
const sources = opensrc.list();
const results = {};
for (const source of sources) {
const errorHandling = await opensrc.grep("throw new|catch \\(|\\.catch\\(", {
sources: [source.name],
include: "*.ts",
maxResults: 20
});
results[source.name] = {
type: source.type,
errorPatterns: errorHandling.length
};
}
return results;
}
```
## AST-Based Search
Use `astGrep` for semantic code search with pattern matching.
### Find Function Declarations
```javascript
async () => {
const [{ source }] = await opensrc.fetch("lodash");
const fns = await opensrc.astGrep(source.name, "function $NAME($$$ARGS) { $$$BODY }", {
lang: "js",
limit: 20
});
return fns.map(m => ({
file: m.file,
line: m.line,
name: m.metavars.NAME
}));
}
```
### Find React Hooks Usage
```javascript
async () => {
const [{ source }] = await opensrc.fetch("vercel/ai");
const stateHooks = await opensrc.astGrep(
source.name,
"const [$STATE, $SETTER] = useState($$$INIT)",
{ lang: ["ts", "tsx"], limit: 50 }
);
return stateHooks.map(m => ({
file: m.file,
state: m.metavars.STATE,
setter: m.metavars.SETTER
}));
}
```
### Find Class Definitions with Context
```javascript
async () => {
const [{ source }] = await opensrc.fetch("zod");
const classes = await opensrc.astGrep(source.name, "class $NAME", {
glob: "**/*.ts"
});
const details = [];
for (const cls of classes.slice(0, 5)) {
const content = await opensrc.read(source.name, cls.file);
const lines = content.split("\n");
details.push({
name: cls.metavars.NAME,
file: cls.file,
preview: lines.slice(cls.line - 1, cls.line + 9).join("\n")
});
}
return details;
}
```
### Compare Export Patterns Across Libraries
```javascript
async () => {
const results = await opensrc.fetch(["zod", "valibot"]);
const names = results.map(r => r.source.name);
const exports = {};
for (const name of names) {
const matches = await opensrc.astGrep(name, "export const $NAME = $_", {
lang: "ts",
limit: 30
});
exports[name] = matches.map(m => m.metavars.NAME);
}
return exports;
}
```
### grep vs astGrep
| Use Case | Tool |
|----------|------|
| Text/regex pattern | `grep` |
| Function declarations | `astGrep`: `function $NAME($$$) { $$$ }` |
| Arrow functions | `astGrep`: `const $N = ($$$) => $_` |
| Class definitions | `astGrep`: `class $NAME extends $PARENT` |
| Import statements | `astGrep`: `import { $$$IMPORTS } from "$MOD"` |
| JSX components | `astGrep`: `<$COMP $$$PROPS />` |
## Repository Exploration
### Find Entry Points
```javascript
async () => {
const name = "github.com/vercel/ai";
const allFiles = await opensrc.files(name, "**/*.{ts,js}");
const entryPoints = allFiles.filter(f =>
f.path.match(/^(src\/)?(index|main|mod)\.(ts|js)$/) ||
f.path.includes("/index.ts")
);
// Read all entry points
const contents = {};
for (const ep of entryPoints.slice(0, 5)) {
contents[ep.path] = await opensrc.read(name, ep.path);
}
return {
totalFiles: allFiles.length,
entryPoints: entryPoints.map(f => f.path),
contents
};
}
```
### Explore Package Structure
```javascript
async () => {
const name = "zod";
// Get all TypeScript files
const tsFiles = await opensrc.files(name, "**/*.ts");
// Group by directory
const byDir = {};
for (const f of tsFiles) {
const dir = f.path.split("/").slice(0, -1).join("/") || ".";
byDir[dir] = (byDir[dir] || 0) + 1;
}
// Read key files
const pkg = await opensrc.read(name, "package.json");
const readme = await opensrc.read(name, "README.md");
return {
structure: byDir,
package: JSON.parse(pkg),
readmePreview: readme.slice(0, 500)
};
}
```
## Batch Operations
### Read Many with Error Handling
```javascript
async () => {
const files = await opensrc.readMany("zod", [
"src/index.ts",
"src/types.ts",
"src/ZodError.ts",
"src/helpers/parseUtil.ts"
]);
// files is Record<string, string> - errors start with "[Error:"
const successful = Object.entries(files)
.filter(([_, content]) => !content.startsWith("[Error:"))
.map(([path, content]) => ({ path, lines: content.split("\n").length }));
return successful;
}
```
### Parallel Grep Across Multiple Sources
```javascript
async () => {
const targets = ["zod", "valibot"];
const pattern = "export (type|interface)";
const results = await Promise.all(
targets.map(async (name) => {
const matches = await opensrc.grep(pattern, {
sources: [name],
include: "*.ts",
maxResults: 50
});
return { name, count: matches.length, matches };
})
);
return results;
}
```
## Workflow Checklist
### Comprehensive Repository Analysis
```
Repository Analysis Progress:
- [ ] 1. Fetch repository
- [ ] 2. Read package.json + README
- [ ] 3. Identify entry points (src/index.*)
- [ ] 4. Read main entry file
- [ ] 5. Map exports and public API
- [ ] 6. Trace key functionality
- [ ] 7. Create architecture diagram
```
### Library Comparison
```
Comparison Progress:
- [ ] 1. Fetch all libraries
- [ ] 2. Grep for target pattern in each
- [ ] 3. Read matching implementations
- [ ] 4. Create comparison table
- [ ] 5. Synthesize findings
```

View File

@@ -1,109 +0,0 @@
# Tool Routing
## Decision Flowchart
```mermaid
graph TD
Q[User Query] --> T{Query Type?}
T -->|Understand/Explain| U[UNDERSTAND]
T -->|Find/Search| F[FIND]
T -->|Explore/Architecture| E[EXPLORE]
T -->|Compare| C[COMPARE]
U --> U1{Known library?}
U1 -->|Yes| U2[context7.resolve-library-id]
U2 --> U3[context7.query-docs]
U3 --> U4{Need source?}
U4 -->|Yes| U5[opensrc.fetch → read]
U1 -->|No| U6[grep_app → opensrc.fetch]
F --> F1{Specific repo?}
F1 -->|Yes| F2[opensrc.fetch → grep → read]
F1 -->|No| F3[grep_app broad search]
F3 --> F4[opensrc.fetch interesting repos]
E --> E1[opensrc.fetch]
E1 --> E2[opensrc.files]
E2 --> E3[Read entry points]
E3 --> E4[Create diagram]
C --> C1["opensrc.fetch([X, Y])"]
C1 --> C2[grep same pattern]
C2 --> C3[Read comparable files]
C3 --> C4[Synthesize comparison]
```
## Query Type Detection
| Keywords | Query Type | Start With |
|----------|------------|------------|
| "how does", "why does", "explain", "purpose of" | UNDERSTAND | context7 |
| "find", "where is", "implementations of", "examples of" | FIND | grep_app |
| "explore", "walk through", "architecture", "structure" | EXPLORE | opensrc |
| "compare", "vs", "difference between" | COMPARE | opensrc |
## UNDERSTAND Queries
```
Known library? → context7.resolve-library-id → context7.query-docs
└─ Need source? → opensrc.fetch → read
Unknown? → grep_app search → opensrc.fetch top result → read
```
**When to transition context7 → opensrc:**
- Need implementation details (not just API docs)
- Question about internals/private methods
- Tracing code flow through library
## FIND Queries
```
Specific repo? → opensrc.fetch → opensrc.grep → read matches
Broad search? → grep_app → analyze → opensrc.fetch interesting repos
```
**grep_app query tips:**
- Use literal code patterns: `useState(` not "react hooks"
- Filter by language: `language: ["TypeScript"]`
- Narrow by repo: `repo: "vercel/"` for org
## EXPLORE Queries
```
1. opensrc.fetch(target)
2. opensrc.files → understand structure
3. Identify entry points: README, package.json, src/index.*
4. Read entry → internals
5. Create architecture diagram
```
## COMPARE Queries
```
1. opensrc.fetch([X, Y])
2. Extract source.name from each result
3. opensrc.grep same pattern in both
4. Read comparable files
5. Synthesize → comparison table
```
## Tool Capabilities
| Tool | Best For | Not For |
|------|----------|---------|
| **grep_app** | Broad search, unknown scope, finding repos | Semantic queries |
| **context7** | Library APIs, best practices, common patterns | Library internals |
| **opensrc** | Deep exploration, reading internals, tracing flow | Initial discovery |
## Anti-patterns
| Don't | Do |
|-------|-----|
| grep_app for known library docs | context7 first |
| opensrc.fetch before knowing target | grep_app to discover |
| Multiple small reads | opensrc.readMany batch |
| Describe without linking | Link every file ref |
| Text for complex relationships | Mermaid diagram |
| Use tool names in responses | "I'll search..." not "I'll use opensrc" |

View File

@@ -1,122 +0,0 @@
---
name: session-export
description: Update GitHub PR descriptions with AI session export summaries. Use when user asks to add session summary to PR/MR, document AI assistance in PR/MR, or export conversation summary to PR/MR description.
---
# Session Export
Update PR/MR descriptions with a structured summary of the AI-assisted conversation.
## Output Format
```markdown
> [!NOTE]
> This PR was written with AI assistance.
<details><summary>AI Session Export</summary>
<p>
```json
{
"info": {
"title": "<brief task description>",
"agent": "opencode",
"models": ["<model(s) used>"]
},
"summary": [
"<action 1>",
"<action 2>",
...
]
}
```
</p>
</details>
```
## Workflow
### 1. Export Session Data
Get session data using OpenCode CLI:
```bash
opencode export [sessionID]
```
Returns JSON with session info including models used. Use current session if no sessionID provided.
### 2. Generate Summary JSON
From exported data and conversation context, create summary:
- **title**: 2-5 word task description (lowercase)
- **agent**: always "opencode"
- **models**: array from export data
- **summary**: array of terse action statements
- Use past tense ("added", "fixed", "created")
- Start with "user requested..." or "user asked..."
- Chronological order
- Attempt to keep the summary to a max of 25 turns ("user requested", "agent did")
- **NEVER include sensitive data**: API keys, credentials, secrets, tokens, passwords, env vars
### 3. Update PR/MR Description
**GitHub:**
```bash
gh pr edit <PR_NUMBER> --body "$(cat <<'EOF'
<existing description>
> [!NOTE]
> This PR was written with AI assistance.
<details><summary>AI Session Export</summary>
...
</details>
EOF
)"
```
### 4. Preserve Existing Content
Always fetch and preserve existing PR/MR description:
```bash
# GitHub
gh pr view <PR_NUMBER> --json body -q '.body'
Append session export after existing content with blank line separator.
## Example Summary
For a session where user asked to add dark mode:
```json
{
"info": {
"title": "dark mode implementation",
"agent": "opencode",
"models": ["claude sonnet 4"]
},
"summary": [
"user requested dark mode toggle in settings",
"agent explored existing theme system",
"agent created ThemeContext for state management",
"agent added DarkModeToggle component",
"agent updated CSS variables for dark theme",
"agent ran tests and fixed 2 failures",
"agent committed changes"
]
}
```
## Security
**NEVER include in summary:**
- API keys, tokens, secrets
- Passwords, credentials
- Environment variable values
- Private URLs with auth tokens
- Personal identifiable information
- Internal hostnames/IPs

View File

@@ -1,4 +0,0 @@
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["./plugin/review.ts"]
}

View File

@@ -24,26 +24,106 @@ in {
programs.opencode = {
enable = true;
package = inputs'.llm-agents.packages.opencode;
settings = {
model = "anthropic/claude-opus-4-6";
small_model = "anthropic/claude-haiku-4-5";
tui = {
theme = "rosepine";
plugin = ["opencode-claude-auth"];
plugin = ["./plugin/review.ts"];
};
settings = {
model = "openai/gpt-5.4";
small_model = "openai/gpt-5.1-codex-mini";
plugin = [
"opencode-claude-auth"
"opencode-supermemory"
];
permission = {
external_directory = {
"*" = "allow";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
bash = {
"*" = "allow";
env = "deny";
"env *" = "deny";
printenv = "deny";
"printenv *" = "deny";
"export *" = "deny";
"gh auth *" = "deny";
ssh = "ask";
"ssh *" = "ask";
mosh = "ask";
"mosh *" = "ask";
"cat *.env" = "deny";
"cat *.env.*" = "deny";
"cat **/.env" = "deny";
"cat **/.env.*" = "deny";
"cat *.envrc" = "deny";
"cat **/.envrc" = "deny";
"cat .dev.vars" = "deny";
"cat **/.dev.vars" = "deny";
"cat *.pem" = "deny";
"cat *.key" = "deny";
"cat **/.gnupg/**" = "deny";
"cat **/.ssh/**" = "deny";
"cat ~/.config/gh/hosts.yml" = "deny";
"cat ~/.config/sops/age/keys.txt" = "deny";
"cat ~/.local/share/opencode/mcp-auth.json" = "deny";
"cat /etc/ssh/ssh_host_*" = "deny";
"cat /run/secrets/*" = "deny";
};
edit = {
"*" = "allow";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"**/secrets/**" = "deny";
"secrets/*" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
glob = "allow";
grep = "allow";
list = "allow";
lsp = "allow";
question = "allow";
read = {
"*" = "allow";
"*.env" = "deny";
"*.env.*" = "deny";
"*.envrc" = "deny";
"**/.env" = "deny";
"**/.env.*" = "deny";
"**/.envrc" = "deny";
".dev.vars" = "deny";
"**/.dev.vars" = "deny";
"**/.gnupg/**" = "deny";
"**/.ssh/**" = "deny";
"*.key" = "deny";
"*.pem" = "deny";
"**/secrets/**" = "deny";
"secrets/*" = "deny";
"~/.config/gh/hosts.yml" = "deny";
"~/.config/sops/age/keys.txt" = "deny";
"~/.local/share/opencode/mcp-auth.json" = "deny";
"/etc/ssh/ssh_host_*" = "deny";
"/run/secrets/*" = "deny";
};
skill = "allow";
task = "allow";
webfetch = "allow";
websearch = "allow";
codesearch = "allow";
};
agent = {
plan = {
model = "anthropic/claude-opus-4-6";
};
explore = {
model = "anthropic/claude-haiku-4-5";
model = "openai/gpt-5.1-codex-mini";
};
};
instructions = [
@@ -61,7 +141,7 @@ in {
opensrc = {
enabled = true;
type = "local";
command = ["opensrc-mcp"];
command = ["node" "/home/cschmatzler/.bun/bin/opensrc-mcp"];
};
context7 = {
enabled = true;
@@ -78,10 +158,10 @@ in {
};
xdg.configFile = {
"opencode/agent" = {
source = ./_opencode/agent;
recursive = true;
};
# "opencode/agent" = {
# source = ./_opencode/agent;
# recursive = true;
# };
"opencode/command" = {
source = ./_opencode/command;
recursive = true;
@@ -90,16 +170,11 @@ in {
source = ./_opencode/skill;
recursive = true;
};
"opencode/tool" = {
source = ./_opencode/tool;
recursive = true;
};
"opencode/plugin" = {
source = ./_opencode/plugin;
recursive = true;
};
"opencode/AGENTS.md".source = ./_opencode/AGENTS.md;
"opencode/tui.json".source = ./_opencode/tui.json;
};
};
}

View File

@@ -157,6 +157,8 @@ in {
"ghostty@tip"
"raycast"
"spotify"
"tailscale"
"whatsapp"
];
};
};

View File

@@ -2,6 +2,11 @@
local = import ./_lib/local.nix;
in {
den.aspects.email.homeManager = {pkgs, ...}: {
programs.aerc = {
enable = true;
extraConfig.general.unsafe-accounts-conf = true;
};
programs.himalaya = {
enable = true;
package =
@@ -50,6 +55,7 @@ in {
port = 993;
tls.enable = true;
};
aerc.enable = true;
};
};
};

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKWXVhTEw0em1CZVB3d2U3\nSWRjMlZpTlFYSXZJZVE5SWttSFV2K3l3VFJjCjZZbG05a1RsUlRORDNaRXduVHBC\ndHZ2aG1FRnJhbFQxNkNxTEwzV0NRdHMKLS0tIFplbXhERjFhRGh0YUtuVG5IZWVh\ndEV6SkhTWnUrVkFmR0NYRS8xZ05zRTQK8qcapJ2Z2AukAUSFagChNGt7BTnIXchr\nociqhWE+BVPROduihwpsQlAWxEARJP/5sqhOAZpNrnna9Psg4m1MFA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0cmFCRDVFcFFORVpsK2Fo\nYWg5S3VzSGlCYVp1S1Q3NGF4SDZqMVFmTFc0CkZQUDJwQ3AvUDNpMUZNaUlRTkNt\nQVR2UzI0Snp3SVZqN2phRzE1K0ErZ3cKLS0tIDE4dndRWXVycitxSVNEN28vN29s\nWnRIT24yT3Z6R1ZiYURYd1BiSzBIOU0Kn7jrvq06CxRxn7XbOIMoEIjMDIr6A089\ns1ymMJO8DmeKdNaL5s3EwG7BZBAimdmcjANST1kXFy7oc4eSk4ETwg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJTzlPdWcyQlFPSWdhVnVB\nR08yVmdaeFRNYUo2clA4ellicU9rVnlZMVFBCnRjOTZGdHBOMW94OGRyaE9HMktq\nQ1V0WmQycndnSHh4K1JiZllqSG5oRVUKLS0tIGZ6VTNUd2xmR1N1bmtDU1k2Q0ox\nRDAwQytvL3JQUjh0MHBFV09kdVpJdkUKC1CigRs4k/uSXC4zMWL60xaHFoyvzYlu\nP2olBA4CerGEoMc0ZIBEEr78hB0j//06se68BreZcD4FcDC77IWNXQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqZFVRMDVVaEJWUWQ1bkRD\nWmQ1dTRMK2FpMmZLTERuOHc1OHUramFTZkFzCnlSUTUzUktrdFJyVnk0SnpFOURT\nME1wckdyUHFmeE0rN1BneUhIR3JuZHMKLS0tIGJCTHZuWTdGQVhoU3JnSmxIcm83\nc0hwNGUvVEljWWVwOWpCS1BJckhjRUkKDD22XytVN58yR22InpsUEN6y1r2AQqLn\nu1fmWC7R4AJcisHqVSEOBW3qQVQdFWIptBN/5urkb2yAKNtP7WUUbg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRcVJ0WFoxN2dQSy8xN2ox\nU09GbUd0UG03Wlc0RnpTNkpYbUo2ZFg1Q1hRCmZyUTZiYklMbnBFczhkSTNFaTNi\nSy9vb1VXOFBqQVMySHRVUng0c083VFEKLS0tIFplV3JVSmV4TmJHSkRPMFMzZkZl\ncmVZWjRCZWxlamxUdnp6ZnA3bXNaV1EK4ZXEJBXHGTaEbNq81RRm+GfElyJekrX+\nxrWHGTkJ4golaI39p3j/F8sF40Sa036ZJmQU8uX9fq1cnuSRv4lb1g==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlc0NibDBuUVJYLzRQY0dP\nWlRzWVo0Rk5NVXh0SGZjYnNza2g3M3E0U0cwCmc5K2d5UzZvR1c2QmJickg3Uk5X\nQm9GMkZnYUVGS1lPRklrcDJRdmMvM2MKLS0tIHBiclZyTVU5emVka05xNXJTTnhF\nN3plRmN2eEl0Y012eGNBbXBsc054Wm8KIB6pNlSM63UvoaPsHNYhSuhZsfM/VQfw\nTxITGnK0Lkjw91SDVzuCGPTtZxuUTFR2q0SyGbZU9IrLQWmBTaENhw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5a3gxLzQzQXl6OThJekYr\nVE5zQ0tHd2l4K1NnZEJvSkw0WVptdE9VQ3g0CjcrdkgyUGczZTY5SFdQNFdneTVR\naTdjTUhSSzZvQnJmc2g1NGJhN3Y3VjQKLS0tIDk4ckszblk5QUFkNzVVZ3czYnpk\ncVZlTlJxK3ZLRmxZRmtaZzNkME9UZWcK+GVFoiY5qKX8DdKbGxUoLxF3gnnG3Cbl\nk/57ZH1xVJT4jCCqdploZCSwSLdGrUDGs8I4FixKVMKGT6Ce2xaPcg==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEUUhvblozMyt0UDJpZ2dx\nNWJBZG5wQzFEM2RFTTJjb3FtcE1ISkd3cEhFCjJOdDJwOTJrdWc2c0NYWGQwWk9y\nREM0Qm1iK3FHUE54ZVI5L2k0RmtESHMKLS0tIEtRdTQvY1AxZHhSZWlhSThFV2FN\nQ0wyUTRaRnh1cDVlVmNjekNuNUN2bUUKOnTcpcaN7cuQXceJmPtKJj2pyIxC3Lj7\npedLV126Q07i3aRrQgm92O8NW2lMaM2Z3FldooTsQgIieQeMCJ6G9w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxUHJtZ3VuRVZnU0RXeStx\nNUQrakgxWm5oOFlmbXZGL1UxQnhvYU1sUkZNCmlucWQyeFJDSFJZYWlSbkd2eCt6\nS2liQ2g3VXpING81OHRPUGRocnNqa1UKLS0tIG9tdXdna3RhYm8xRHRsS2tQYlUx\najVwbndFMDUyVG0weGtGdWxpQnpsSVkKdTnOnyKDkDfDtDA4jdF1JZ6wXdf/Rucv\nT4YYvjbh7JTnOl6wMg37uy29GVlI0hjOCMyX0j2BmKis/awCJ4bxUA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqUVhZanBIR294ek1keWtZ\nM3puaC8zWGE3UHpmZy9vbzh3YkRyTW5rcG5zCmQ1SnR5NzZLcXNZZVUzTWV1YkFn\ncE5CaHpSeEZnWlNkak1MbWM2UUJJT0kKLS0tIEl1T2lsYndYeHkxalpBYUtwY1Yv\nMGJLWWEwVW5lNnNwNEZhTnRMTVdJdmMK3mg0hzN6KjmR288U09USPmyoQd5ixmCY\nOZb6qjx1O9TUmuho6j+zR4BmMcZ9kIv6uEuURKxGFwzZy+l5a3e3Cg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:45:21Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2Ly9XQlRDZzgrY2tiTmRi\nNmhBNDQ2TDV4eGZsUUFvTmlHR2tCN29IUGljCnBvb2RKNUdHTnEwQ1RzS05uOTVK\neElUUmdRdnRaYUpIWEljdlN6REgwUm8KLS0tIGpJRVNKTkR3bEcrcks3ek5LNVV3\nYzhOUFRvY1RPYkM5V0hXenFHd2NSdWsKokyuyPXjMAJIfyeWsBHGWaLID7Oorc9d\nQE9Veh2aramAH1xb30BVLa40Dpu0jI/Wgxoo0iDf5OypNXBiBH+FMA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4WmNWMnQ2NEg1MnFvOS9n\neHBnMHpvWllvM2taeFM5SXRVT0wvMTRaMUJFCnk5MVpGRjN1cUxiYkdLSlZ1aTB0\nTkduQkFZcjkxQzUyWHhlQ0tFRnZFTWsKLS0tIDVLdXdySWxqb20zZ3RKSE1qc2Nh\nSC82UmxGN2VUY2pZY3pnWDlKTkRKSEUKFSQqZIzGXpCzpWVWko7J3hxglRJmOuF4\nmE/lC55uekxcochjgPT9uDJfG1/kskWyJTBb5Ndx3YFkUcnfVOZc4Q==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNUVHeWg1N1ZPKzBqMnBn\nY2VaTlp0K3ppbFdmN0JuQmRnVUlzQ0hMZTI4CkxORVJ0NUIxVXZMcHVRcXFSV01l\naFFVOEtJSFY0MUVWOXYxbFlqRmNuVHMKLS0tIEYydDB1ZHUvQS8yTENmZkZHeXFV\nU1N3ZC9zNEtZSnE2Wk9TREN3Y3JwNEkKyFqj+1kGjqKyslllunUl8dVQwFAe/MdL\n5PNKIfVErxbnPtlc+yfRSWsfOQPdNV9GVM9rtOxA+51QStiGlLU12Q==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5U2plZGxEVy90NUMzellJ\nYVVsTDUvL1o5OXZ4YjR3c0tPY2RjQWpVd0M4Ci9kUFA3OEFoRjlUa1g4Nnl3K2Rq\nNHA1NkRtVzdXM21QNTI5ZFJsNFVCMncKLS0tIFRQbDdXeWxIdzJpNng0QVc3bStT\nQ2VPNFA4NDlmU3hscjJ0MjdGRGQxakUKM9H04Ezw/KoAJVjFB56v0vjIHJiO+aW+\nWWdBlsZoeTYChkMGcQ74SaLkPMEkD3GU8Ua9RZBIqFzOZwsOdAkWBA==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTREx3SU02RU1adU5Ielkz\nVUFrdUZkQzJwZG1aQlZEcDdxWXVFM1JtRHk0CmNTamFhcjJEMklYbkpxWHFzekhY\nQ1E1STZ3U0RMNkNnVnVvWXJkZUhxRm8KLS0tIE1ZaFBORVRMZkYwNnBkTjFxSWNG\nVWJnYjgzM3IyMDJ3Z3o1QkV0RGdjL0EKQ1G/uJ8HcVDMUk8NXBC0MRcJOyLzQRlT\nL5Sf6Es6UYrTp5tIWleR6u48aUGi8HvcOOeCrK2S5utj5xPl6E+bqQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSSTNhc0xrOXRXOWZVdWNh\nNGQxNFBXUHNrL2NsMnBkcDdSVHVYc1RPM25zClh0L2FrMEhZcWt3RGtHa0ErNjUz\ndDlZaC9kZVVHSkFOK3dBWGwwZStQeUkKLS0tIE1VRnJCbTFWUkhoQ0c0dkppbVRL\nSDZldkdXV2U5Z3ZycmEySTJ5WDFTbVEKgHq5Xa86l+1e87p2KodJN+8/IOz2y0w7\n3xHjoZ8LaP7aQ5PWBTHUk6KeOjJQnTMzimQC+YOs4XkmeItBt+6Gjg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBT25zTE9Na21GbHJOeVZw\nU3Uzck0yN0J3Und4bExsb1VGSkNleFdZbFdZCkx1TG5VU2RyZGlWT29wd1QxV1Rs\nM20rd2hWN09wQkc2K2dWS1JUenMwUW8KLS0tIGlLQ3FNL1NrODVmNENpd3F1OGlt\nVDBhNThoR015N01UTGp6MDUxdHJFWnMKvuFBvzz1k8Tg1J2GrhEO2yvJEfwxYrmX\nDQ1/qcoSOWde7N1Mvx9ih2mFlK4JiD51tWF351C0VOKNdnBBzO46VA==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBybnprUGZPREdpZzllYjMy\nTU5DVnRrTkxEREZQUFpiM0E4SUtsSlpqOGdnCktuaGtSSHpzTnl3c2ZuMlV6MzBo\nLzdzYWhPR1RtNnFmdVg2cTUwNE5CNEUKLS0tIHRHaXlnTlFIU1lVT1dkVUdOUnNG\nWnl1OXFpVmo0WkVlSVpKYlZaMC96encKNBnnwC2/5n2ZWYvRRoCQjOPg9b3+fFJj\nhJA45nhoO38zg0Wdv4OPA+Zrw0K0lgllk02BNXMDSv8+IthlzN5F0Q==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTYysrSjZSZDYycmtNa0h6\nZHM4VGpzdDQ3ZXlYWG9ia2F2RXpOOTVnMlcwCmpTazNzeWt0RVdPT1d5b0hqQ2pp\nd3BiU0poRm53MGFFakJvYkhxVklhWGcKLS0tIGdncmxRZVB4dGVzOVMxdVM5MkN1\nTkVXRVhqbXIxa1FERjFBYnhlTkJsVmcKOUwliiZ/tMzelHQGCYi3dW/Kq0rKqW7k\nz6kSVA6vTZGFTqiK6uLqYL56BToBY1iWM4skUYTctrcXO1eEGwnbfQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTeDllU1RoL3VndEpGMG8z\neFl0TEtPL0VXUXFBVDhwRTV2TkdyaDlRS3k4Cm1hZHRDUXRFT1RPajY0Vm9oZ1BC\nbWhla2swcHdLdFdRK2NTZ1B3SGgrVjQKLS0tIGhpNE0yV2JTeHV4emdmSU5PM2ZV\nWkh5ZzY1eW5jSVF2N0Rndk0vQzlpam8K9++sBqLwmDs4WMhHADbSwlUd1te4t3/S\nDk9ENUmvu3xRX8cpT0VELjfXRf3ne6HTbG7K7Lxj5jbmfX6aSRpIsg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:47:06Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5SVUveHVXUDVTakJ1QUlj\nOWlmakVlY3JucVJGaVljVUwrL25MaU5PSGxzCk02akVDWHhjUWs0aFZrdVpjUER3\nVXFsYnJCWXFSZFNrR2tLSzRBek9mRXMKLS0tIGFJZzFFV3hkeTdmYWFKWVpnWXl2\nbm9naXJiRkdSL1UzTE9xQXBXR0xENGcKxYnDk+n4u94whP0mNNSxK3KsY8RAq+w4\nbVaxdcomXo6NJq1izMdVkQFIp1hBZniaktPGsDUMWLpJ4mSrVwPbPw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQRlFyYjFaNHJ0RDB1azRW\nY1ZvdnV5MEdWb3UwQnZuZU9sT2JleElkNmdZCm9CWVB6V0JUTE4xQWtYTU9hb0N2\nVUVtdXFVcHlTaXJBNlM0SXJac096dmsKLS0tIFdEdDZlTndXVmtTbjV3eEVsb2xG\nbzkrbU5jY3l1d3VFZExsVnB3dnFUdU0KKpmLWWgxnUfH62N0ibnqtGjvtRC6/2q7\nD/h4VozD7oZh1lC1S9SDl6J7j54frJ//AtsDdbIviVNTg4JzJ50kwQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmWGNtM0RGTHR3M2c5RnFt\nVjd1MFM1Q3hvK0xid3BnZ3VWWmpySlN0WkRvCmM3WFlUajBaN1B4cEVUVWRUOS9M\ncXVQZHZ6a21xaG53UzdKdHR5Y1JJeU0KLS0tIHRTRHpZbW1IQ2FUMDBldUtxRi8x\nN202KzhjQ3MxSERhZEsrUm00RUlGZG8Kz0BdZXtPOGe/lwiSrecSho9zPn68TZ5P\nkH3YOpAHmQZrm3P876fg8ChMv5Rgd3F679d6h334fwZDfiu2h9mFBA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3WWNCR04zV0FhT3NhNUQ2\ndVN3am4xY1oxa0tXOGlLdjFMU0cwQWo4MWlnCklPRGl5YVYyMTRVcGhhNVRSaUdo\nYjZmbTRTaGZ2SFB6d0tUeEdEVFFBU1EKLS0tIHZDOFRDTkhqbG41MGNYU2lkc2NV\nWjNYV3kwRVFrWkdNZitYZlpnUlF3NGsKne7cflZE3HpP2TbtsSSQfOLucOq16Pof\n5vQ7M8lIqi0jqL/k+siWIspilbhpkknTgUzcBsui4FxKCfEPxLFMHg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLVmRUczBEQ3RLbXByeWpU\nN3EwUm9aTXRwTlp5dG1weEpKOENXTkhrMW1FCmllRUFTZGdhWXRhWUp0dG03TnFu\nN1ZjYUJ6ZGVYNnVJZTBlcTFGUkNXZ2cKLS0tIENLZmo0aHIvbjNxbllZa2tkbHR5\neHZiRjcwd2VGbEovYm5HcEdHRXRxV0EKx6A57Usvle5ItD3XysZI41M/9s9l719i\n1fpP9W/5i+dDCgE6/ui8WLVpoj62eaArFBEN7OH1Xt04DkJ/G8Cytg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvYkxnekd6VnJ4MEo2d0tx\nSlhmeGhPenQzSkJLSkNzWnB1d2pvTHI1QVJVCkJRNTVZeWpUV0dzNXdMclNCUlVW\nQWFoa0oxMkNBUERVMVFGdG4ySlV6akUKLS0tIFhRdkZ6ZWdqVS91U3JPd2p6OENj\neHpCc3UydXF4SFZCSTl5SzcwL2dwVUEKLT37+0UdWfe26cyQ5gRwubqN8nBv0iwp\nBRDwEBw5H6rgzUEDaAv3fRy/AxT1TvtiavuKhlJdCsJqDpJmBjgIRw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiMEo1NFhoei9vV3hlaDVo\nZjNteStQTzl2VU5WaVZFaEpmdWpzUEtVNlFFCjVUREVBZ3ZxdlBBdHplRzRxUGph\nYldyY0tpNUVqclh1TE1xemlZYm5QeGsKLS0tIE1oZ2VYSDFqZTNvK25EclRPTUZT\nbVIwdjFBQmRZTEhISTcxM1BRSk9iSWsK5/xZkLijy4p/N9WygTCIxlH0SdIiIz5W\nkGqvG27RZGqR3qwNn3vSoUBH5yOzhJAP9RAvAu5EET0jwuuG/3L0Vw==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyU1pZT01HdkhZQ0FSbXdS\nQVdlTlhlcDcvdlZZcnJJTVNSQ0Z2NTZueVVVCmpRVWR4aTF1NDZ3NUV0RkpRMjMx\nY3EzYUZRSUtMVWtsVzlhQ0MvVmgyN2sKLS0tIHIzOTUrdEVWQ1l4RXQvV01IUy95\na1pvK0pLSFRYMGFyQjhCSDJmaERhSGsK1vg2a05yGfgp1Uo12+1nCv6cN4oU1s6h\nwvrCtejM1z79f5ZGJaAK8Tz/KOKQIA402QKp9eTWf5Y4Ix6OUd1t1w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOMExJbWh4MldLckw5YjJK\nbGJyLzgxU1RUQldGVXZIcDRZL3BaODRDSEhFCkQ3TUszSWZHbkRuV0RCNWFaZDFl\nUmQvVlVydDMwL3JKVXpoNG1sdGtFMVkKLS0tIFJGK1AvZlYybVJIS0d0TlNpVU9o\nVkFXK25Ub015YWp6V3VBaStkRDM1RjgKrP91fgB60MlnXjKs9jvSnkUlSHIU6niJ\n4VveJwqCXO9zrAcS2tZaQFQ7uwga1r+iKflzLJxMPsbvshj4sfLsrg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5a2YrMjR3SjFGZ3dtb1JN\nNGxCcDBTOVVVcHVyYzlXblBKR1F2blNVelc4ClBDZHdqNTNKckpXYzlwVFhZVktk\ncExnNTNka09ZK1lWclRudnU3N3ZDQlkKLS0tIENBR3NwSk9BdFMwejNybXYvN0tV\nU3VEVC9lTkxJbFZXWUZDRG9QMGdab3cKRYgccR5zCKEZW08FHb8/PTibpTPChG7o\nHT23XeVAf524+QpExJKsQX4Qpr+2UEG3/hUoRaisuS3TMyRUtKRoMw==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-01-04T19:44:05Z",

View File

@@ -4,27 +4,28 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1SktQSmxuSG5MY2ErNUEx\neEd0Q1RsSzhaUjgyQnViQ2hhd09UNDFzcVJBCjg0R0M4ZnB1UFBEcDNJQXFjWnlO\nQVhSeURHVnorcUVnczBtdU04WDhRODgKLS0tIEJxNm9teWIyUXhzbU1EY2l1WVBk\nZG9xUlh1cDhiQmdsYnZpNVNOTUY5ajAKPyt8ZIKTfu0azAFezj7rtSJX8X4rO712\n0w8MAvnLM8k5ij6nJtR3HylwLmZ9AfMSq4Aikl+oRu7rXs26JvPbZA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJdjZ2ZEcvM3RFbThNL3VV\ncHlOek8zS05sOGJGYlJ5U0J2VDJqaHA4Vm5vClB1b2NNZk5Lc2ZscDdnd0k3TGZS\nWHllM0l4TTFuWWdtUWtidDVaRU91M28KLS0tIHh5UDZiVXEyV2lYSTQ0Rk5peGx3\nNG00OFhPUWZQamdEOWVGZVpFSkc2MFkKSPQ+6c1AyWObFZQcoH5ImtFNl8lILz59\nao3ZQ23yIiSYAZ2pAeOPh4WuNtguZ5S6dnbRDr4e/8Vzw/1qgU994w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSWU5qSDBNY2d2YnNvVHU4\ncElNT0s0R0N3U3pjYW9kVGxFV2thY3QzYTB3CkdGRUhVMUdvR1dwdVdnZ3o0M0ZH\neXV0VUZyaFRBbEN1RXR6RGJ6RmIySjgKLS0tIGZGc3Voa09CNDFoMXVyZTJmME1Z\neldyMVAyd3pTZzB4RVhTRzZVOGs3NVkKyP8sIk/Oy1GXxG0tw8Ocjerfze+eIrNW\n5XYA96ct/2M2jiPdTxg2yEI5a9wycDkzNIzE95Xyfl3LkY8864wAMQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6Q29GRkdZdzNjUDAzUWU3\nTlRvUm5URWd5a3MrMDE0WmFsWFRZS2NkQ2s0CktHZHpOaUFnWUhHUDdWK3FPMi9L\nMkhweU84OFp6bFhnME1hbk92M0pGMUUKLS0tIGdkYjN5VHl5L2hlL29wa2p6VU9t\nVzluZzdFNnRuL0dNUmtjZ3lOQTRCL1EKvvrXH1IxL4PVIZam2k9XRNBTLKahNCUh\nhMdfb4shNBtUs1bAiicJRPT1eAAWk4CWO5xpS3uJ0fODabrOozXdnw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVHF0N2hHTHprQlpSYW8r\nMmpIZmZIT2QzNXYxTE4vc05wcXhTQzQ5clg0CktzRGFpeEVYMXA4RnV4TVdJNk04\nM3ltL2ZyczloR3NNWm01cE16NmJNemsKLS0tIDBHczRiUVhnZHlrTmdocmNQY2NK\naU5VYWZ4QWFuK0h4cUZGOGxUL3QzQ0kKtsuW7yl1/t7q9kUhTtK0G5G950Bi5n5w\n7cxX/pfMtgPhOh3NMoeuTxc9sH4pTIthRmaLVJ+GzEc4KsMJhOp+rA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLalFBanFCMWtaakVmQWNh\nc3Q2UmZ1OG1MOTNaTzFmcy9lbEFZdDZMSEVzCisrV3hMZndQekZtR3ZORjRkWHdm\nZG85M2Y1L2tPcWc1ZHpkd2R6TDJ2VXMKLS0tIEZ3Zmk0eWVsUTJwNHZJbWJhY3VJ\neVdxMVdqcTdxeUxhU0UzYThhOWhnc3MKOkedT3Tifi+6Q+C0ur1+TQuVwq75fAUa\n0cdXexCI2UIyLv+Ggbv1YIYcUiNeJNiF8JttpGAyOoVnUfIUcLsY9g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLK0Z5UHpwdTFRM3dNL2FC\najdScHp1R1pHM0ZyMk1reENnTFlnTldVNTJJCmxVcVY1OHlrWExIeSs4bTBwQklE\nU0NkUnVmdlVuOHcrWjJpRFU4WTc5ZlEKLS0tIFhheExxWHBIcDBqL290WEpJK0sw\nRTZKbzRWMmJhVVVGT3A1UWJQUE1QS3cKp+jmuHUvZKbPx+/gxQUSz7QV1jLuIzP5\ne1jkJ2rJT2i8snAvihd8bsjRSFmoUnEg6kV8f0OteezNbkZoNhd30Q==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4RDZ1K2RUaU4vTnFsbnpM\nRTRCUkg3RUFGS0pnK0NJSWxXTWVsaTZ3ZVhrCldDWkd4L0Y3Sm5uM0FZZnZqclJq\nUEpNRElGbEUwM0ZHRzJWNG0xT1ozZzAKLS0tIDBiOTRJSlRRbkxuRm9aTGVoZEoy\nU25iVjhxTXhvSTVqV2kwb2doMXNBeWcKK1wDpzC6aYSVJ/z0wGvSDjgXDiPeW+t7\ncgD9a1nCB/N1QZQn2IbnnkL6EsCuIQpRlzlLL9FhdRdLP+3ij+i8eg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzRmpkdloxdU1DRmF1Tzg4\nYSt3bks0Y21OQW5DS2JRU0ZMMHZJV2xLc0dRCmRORFliS3A0QTR1Uzc0ckd3cHA4\nRnNrdVBISG5NcjhrNDRoUnl4c2dPL1UKLS0tIHA5aW9GQkdXU0VNRHd6aEpoSzhJ\nSmd5OU1ESGJqMFVZdGhBMkdYTmlsRWsKLN36pDsdf06Rn9RLxfh46nX5u0dfyoe8\n/VvQiaWoj2/pv8NmwdFzdJQ0mTKkvEdxxY/Jk0YK+GQA/NGIVIIoWQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkN2kxR1BSUVBHSDhuYVVu\nREZGeHFNT1lUVGljMUpmV05FcHROdEcxK1hRCjhYYkJ1U0JaUkdRZG1tMXFudlRM\nSllqZVBsd3JDemFITVVlWkZ4Uis0NnMKLS0tIHRjWHFzZEgwSmlnWFVpQk1tcnJC\naytMVk92U3lGbCtWdXA2UDFXY29Ndk0KPmSHHP4Nyb4dkyz9a60OP5Lc8MlbPGxo\nLReOVEJ86yGbL4ARFptqrpcJ6ILEagWSYLEkqoZ1xiG1BSvs6J7dHg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-23T09:09:07Z",
"mac": "ENC[AES256_GCM,data:phFpHUzJ/7rd1k1fr9YFD2FplXV3Qv5zFni00fAgG2VtVoIdFYeNRE0EEh2ulnKcIXjB/5lZuMss2bIoBt4i46BB2ZHTpnWksbeHowdgkHL+eXT1F7b11S1y9NEKc/ug3jarPwyj3usmVQJlllAzANCQHGrYQdBrFXvFae3cH40=,iv:4v3k4q0SxyTvHoqr2Abf6OhAcANCT9oWTa5Kwlb5GCs=,tag:Hn+fUEmOu7fWc7SSBe5yfA==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.12.2"
}
}

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhVVBSMm5hUmlnUUhrSmxL\nNVVZY2dZajlKSWMxZ3dMVlBORjNUaHBOTEZnCmNpNFRodDFFb2xNdnlYVHRiK2ZT\namV4YXFOd3FCK1ZiK0NIbW56Sm1wNm8KLS0tIHk5TVlxZHB4NzFiQTFQeThUY0JG\nV0huc2FzQU9WWUFJWUFNS3JoTzFaTjQK/T7aFSWnkpv1Qbx47LFp85bkaCOt5vrs\nLjiAGimUOVkr7ZcJz3540JWvBBUNNRKmM3QDNlgPg5luI0fa7+pNRQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3NmNaR1dvaXA4QUVqbkNP\nbFlpTzlHMXd0SGNsd0wyZ2FwZDFBR0tuaWxnClZuNzZma29BWURibzVUZndUc2hV\nZUpUdE94TC9Pa1dLQitLbE1qRXZJNVEKLS0tIG00ZGJyNHp0eEtQeDNsemErNVlo\nclBid2Y0TysyZ1RtK3F2RS92QjJJWDAKUruZmiTB5tkBF5VOO7dv+GijgAO6AVNe\n6t/xyot/Gc3WFOeNkYUk1K7fo0oLFfRG1tZBW+4cwFi/FMUpyjCxig==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSU3ZFZkZqUjcycmtYalRw\nYXlSQlFvcXFwS0xYQ3BVUXlPeDBHeUl0R1J3CkRGRHNaZWxQN1hISmh0VzNwZnlM\nbUFYVWw3dU5jNVorOVFKYUtyWHl5Q28KLS0tIGU1MkhrNHptZUZldHM1Y1FZTytV\nY0VvQjRmUmpIbW12WDlkLzVwSE9uZkkKMczm0x03kPRAD+pdXyDRTqaQckU4MKxg\nfIJAAwRAbskLF37SUzvefa0DCdKpyK876km7nOyIZunTE1j7xIwMNQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKcW5Ld3BjdnYvanJYeHM4\nNk1vS2o4WEg5Ni9aUlBuNmRKMHJpOHh3dTJnCnlhTFRDdXoyS3kyVEM4Ukc5N0pB\nazBwQlIycHliaEt0Unk3WUZtcURJRXcKLS0tIEpoVHlFLzBCSDdGRVZSeCs3SW1n\nK3k4T0VkeGhBd3NSYlNYT3JudXRQVWMKPu0HGvtYlgdih3vNrqvEvke0Fg7hr9xm\n1f3G7GpeRBLZlab6UKKzbFFgf0NADnGEBmWMP7e9taVYqYy1FFf7lQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnVE9tNEhSRUFydEhvL2tu\nbWVuVXhVYlhTVVFnMTZrOEFBOEZKZ1A5TFRjCnNSSGVjNmtYUnVsdGZRcjg5SmNW\nbys5alQxWVRRNS9rV1V5aU9nNEF5ZHMKLS0tIFl2MjgwclFZOEtvVmR5TDFzL0lm\nblBnekRPRzhqem1CSEgyM2YzTnJvck0KwITv/a+Z0XQ0FeI7rPti9IGZPuILWVhC\nTC0dWuJ3uSflAxgRvGpaQyvsKMLgkXCRf58kx17cHjPT+Z8EsXeBSQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQUFgzR1hFemRNeHorN0Fm\nNld4eWhwMWJHdWhBYVQxNUtGemlEeU5jcUcwCldlSENWSXBxYjN6TXZUM2Nuc1Mr\nN1Zlc0lWY29KcGI0bWVwYzVnS3ZBM2MKLS0tIE1rVnhhNFJkeFZjeDh0RVM3eHU5\ndkJPZm1xQTdWZTdXNVJrdHpXV1dIWVEKZlyGzrce2W4M8LVDbYGXIpK+cVqqB86t\nqibOGTNdSAhd2Y+IWb2FdPr0uxoixC4j6Mk+Nw2bXhDKXp+VAAlWog==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxdEpHdzlVbDdBcWJyNDJC\nQnpTaFZLMno1Q0ZJcXVjMUIvcTZ0K3lDY0RzClJlbHhqbWNVemFpRFh2WXRGV1VZ\nRENTV1ppN0J5SVFDdUR1aDRRdTFKUUUKLS0tIGRFMGIwVkliRitaeXdHTm55QzQy\neGdzYStQalVFb3ZPdkp2VFJ5SUJ6RkkKeeOJ1MjTKqevHHl+5dXL8n5o05KV6HvS\nMf5yO3rxYDLzO5ore63G3KjqCS9i36mTzLoyJgYIKQK4IAh/AKcI/A==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKN0x6QnJCZklFTis0QVJE\nbWdJZmxJWmxRYUtNdmFmaHM4bTFQOGZENVY0CnkxbW5Qd3lQTHlNV3dEWHJVS0Qr\nNUhDMzBmdzI2dzNGY3J2dVJwd0wzYXcKLS0tIEVjb3JNODY2ckNUZDVtN0VYaTNm\nSnZWRUpFQkFXWWRBVVNVcUVNYjRxb2MKGa0tBfwyEWrXmkCIupwHID2j357UMiSe\nsg70OdmszMZ+MDjcLIXVkjQfrLTbtIKU+u3jA7MDzECZTm+nAk8cTg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxZE1lU0UzUFQ5Z1ZQRXBj\nbktnN0lWNXZ0QVhPS1Z5cFNPeDJlVCtLdWlvCmFPSHdkeUlONHptbE0xcjg5SUQ2\ncnZKY0JWY0haZFEzcWNnUHdnN250WmMKLS0tIEhlbG91K0tabmVVSDhBeStoUU5j\ncFRlUk5xZjRRVC93UzNJUUhhTVQySlEKB2uBSMXOJd1ufB3i66ldhXlnbquWcXgi\nXjuJb5ud4Nz97wlSnqAADoY8V9aYJM30byL4VJeXiMq1oppDptbQzQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4YTdqUXp3eVdmV1drQ00v\nMkxFUEIrdTBoRDREbndMc2hkekdWZDlSSFZnCndmMmtvTDRpRjNvUENQaVY3S29k\nRGFaUStJRmlSSEJxMG82Nm1Oc0puOU0KLS0tIFdUdEZNR2tZdlBoTFhjK0pNNnNW\nVGZIRytNNnFOQXZMTlhHMDRMUnJ6MG8KDaPfk8dcE3ijfwPhbeN909I6zPdjxX1M\nFnQiSGQuKvWuVzn2qjl8AAFxw1nVO/3l+7eu/PN2yeKIb3rMUvWpDQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-01T17:47:46Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2UDczc281UGU2aTlHeEp5\naEdiZGgzK01VODE4enMrRTIzNmNwa0srRm1rCnd3K1NnOEpXVDJHWGp4VXhFTUJr\nM1c5cXVza3NQOTZjTElTcFRhY0ZDTE0KLS0tIGp1Y3d0dXg3NnBnQmpmeXI4a2NQ\nZ1lIU1hCMUJMR3lidkxRNWV6SzJkeGsK93sF6uCKKFh12hnlSZ9DMxBv8/j0LIp5\nMGeUbbX3sWdk4I04QjXl2Yi9ER5d9W3zDfrQ1u5CqM3IqSPHQ7VgpA==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2UGRKeHloRTVjcHUyOGJ3\nM3FKajJTZXE1d2pnUXEwa25BMytSZjlZV0YwCjFBUjhtUDFRckcxK20zcmxpOVdX\neGhVaC9jaHJuSm9tRktZVlFLTk5yS1EKLS0tIG1CRkdhVW9LSU9sY3dRUS85eHFU\nRnpxdVg3dzM1bldQS0JQY2hYbkhXSVEKREVnO8OsmWfrJ19Vr7KY3O97XG2LXrSP\nxvGOZIDMI2UIfTFWm5TcFDOOD34HV0fWtzjn9GYSx9XN5fQX1YK5vQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPcE55WTAxSERXdUZudmxJ\nODQzaWFBRXl4Ly93MjM3K211OXBtL3A1SUZRCjBjUnRmL0FVdXgvWExsM1E5bUhs\neHAvcEwvUmxDeWFoMWJ4RDQ2VEpzcGMKLS0tIHJUNWE3Z3drMVYwSkV4Tll5emxN\nc3VnZFllaTJLckE1M2ZYaE4vdFNTSGsKvQhxtlSjH9+tjaT77ZiuMQeb0RPotSeS\ncoYf0R8TCvy8c2CP0YYs99bIJP1zx4RirlC/80Ji9eXBYkJaoDRkew==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3bzdOOWRzMm9BOVNoaTkz\nbW1YZWZjVkF1MHhMcy9nTFhPUjdpamRLZmprCmtIK0VxNVNSR1MrbGlqakZzV3Jw\ndE8yZ2lyOE92MStpRWExUENDSGNCbHcKLS0tIGMyS05FUlk1a2N3WmFQcGdpZytD\nRWxQc0hucmxCK2psSzMrWmt4cTlxMGsK95lvBRK4daCbixLEJkEToAg7/yL2iJ+j\newP0caUe3E9UyhuFmZCiN9VsYaQy0q12KcSTq/KMybj6UmDThaOMRw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMS3dYenBFalBySUJKcDZl\nQXRBMTFXN0tCQTM1bFl2SDNma0xRZnBJbG5nCm5hTXlIZVVpREpLUUoyRitnWWZJ\nZ2VPSFBBK2h1aFZldGRNZm9JVDRvd2cKLS0tIEg5cHgrRE1sS0lFZkxSNHdweXJz\nb3p3Y1lZUjBxYWUxZjNBbDlFUkd6VVUKLaUOi7OESO8yTZyBcgOt0LhjM6QKMwlm\nl6MSVJYBtmcp4wdZdaCsiC28dlHg98qXauLppgnkda8LyhDUliP5lQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByTnVqbkNSbW1nNUxpNlNQ\nekRWWEJBeHdmMG1jYk0xeGNUMnBlSk5xNVRnCmNhTlZEcGtUbnlNaU12MGpVVDhp\nOHRRbkVBZXg1bVZiNmtXVHlxU0dDL28KLS0tIDRhV2R3akMzZHFZcytwSHJ3QU9I\namhWREJHZGcvMk9DeUdsb3FtOW1wUzQKhRDjz4lv9BPjOl0ZUstNlMVAw5x2Dgql\nQ06h5a1qm6YqktDvJLiKiUA64ZqDFHvB61x5qVn0Wc913vLayoEIwQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkUTJEcFp4dWxzR3BuRnJw\nNy9vcStGT3ZnOHBYczZSUHgvN2Fzc0MzaG1nCjdCNXRnTHN0YUZZKzBPVmI0UTBn\nbnRNZGxyM0tMSW9HVG1yMmxQeUFXUHMKLS0tIEYrOTJMWlZndHNxeHA4U0ZFajJM\nclRPdVAzWThwNGRpSzJkU0pxY2llbnMKpMQuw76xRJ162EJ91ui6jLNeBY6+XCiG\npKJB9YCTnh+JlxFzm9LU5s8bI4XzsywdrdKMYck1G4A8NW4MoQCrig==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFdmIzR1pjNnBnckpEMzcx\naXhyZHNXNUZ6SFdnSFRoWWIzUVFFNExvSXdFCkNXQXhDY0h3UURPRlJSeGFrVC9B\nblFrTitZVllxYmVwbEZRSk8xelpYY3cKLS0tIExkeVlMNWtjZ2VkL1AreTRHUXNj\nYS9LNTNLd3BhUlpNdWp3UUdNQmhLYkkKpYAbuft1WSROAm+3iGvU1TRhBP3HgiB8\nuDOdv9NTw5AhhTK2VIixhJHZI3gIAvCwndmQWV41PDgoDqmeVwxrsg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMWnYzbWVNM081WnJ2MHh2\nbE56TUZDU1JqdkJHczVaUi9ReTFsNENRdlFnCnArZEYrcmtIMFZEZVhUcG9MdWNO\ndVRNS0w3S2FqMzNkOGVqcTJwTFQyK0EKLS0tIDRnSjBaRC9xd0ZsUy9CWm13MWZu\nTXQ1WEdZR2h0UGM0cmZOMFZwdzdzd28Kv+KkGEfWvQVgOtiJMqEwbIgQcQI9U4fC\ngh+9QrN8blwbt0OVqyu9tPWrP9bPOEhM6U13wj4Q8BqzrNVsLix4Lg==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByVDBtZzJibHNpV01WUTF3\nWm1OajJnVjF6TFluK05UWXptdWJFekY2Wng0CnliUGhmTHZZUHNRY0piM0ZXT3J6\nc2puem5VbVczOVlKZ3RqM084N3JCMEEKLS0tIHppbHdvYk1LTXRMeXp1eEZYeGd1\nT0FJZlNJNnAvQWNGcnBCdnBNTTg4UzAKTwMtaAQk5/8qGO4M3kmjfP8yJmrDiWiJ\nsD23c6S7wwAZdltK1spIRMkSS2AhJBN+ePXBiAGWjatSWsBKlDZqww==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-03-13T17:29:46Z",

View File

@@ -4,23 +4,23 @@
"age": [
{
"recipient": "age1xate984yhl9qk9d4q99pyxmzz48sq56nfhu8weyzkgum4ed5tc5shjmrs7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqMHVFbUFpOGdXc0t4Q2dj\na1lxeWFBR3ROTDN6RVlwYlgxZmdhd1ozNmdvCnVpZFFnaGFqWGs3UmRtZytlUEZK\nK05lM2ttWmNOSGU5ZlBTYWZ6K0dCbzgKLS0tIEpHQXRmaVVhbXJRRUI4REdCZWI5\nTmFDY1gvWVVkUExpeWYxTjlCb3MwUUUKnHzWgaUvDXH1I3TPyY38h9Pbjqk4Whma\n8ctECOpg1obtMr+a9Bdw11IPwAe3R9K0ZfE681HroETCHRxw2+38yw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDMWRWUElvQ3ZRTjFKRVQ3\nYmZINCt2eWJBbWcvLzQvWWlURlpzMldSWkZFCnhidzFMZjliUkt0MUVML0pLQ21L\nQ0pwL2pteEFlaW0wRFJBZk9BSHRXa3cKLS0tIEc1dXlNdDQxUFZrVUc1Z0VBMHQ1\ncXNaYnd5dVVvTU41bTRnRUdGVjEzczAKjfqq9yciDRBW+N2M+5mRRztlW5+JRL0n\nRL5aZlsDVo3SJAxjaaGx4zgwN7dUX6JPrTGW6sYd5LCGuHZWX8phfQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1njjegjjdqzfnrr54f536yl4lduqgna3wuv7ef6vtl9jw5cju0grsgy62tm",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFRU43QXpWb1JDUVJCck9z\nNkFXWXdWY3pJZmx0Mjc5SDgwcVFXWkRua3pVCjAxQkJBUXF5T3hxek00ZzhRcmF2\nb2tWQlYwVXpiM1dMRFdqNXIyQjRhb1UKLS0tIDB2Q003YjlCTXUxa2k5RmMrWVds\neXhLSzViMHl6Smo3dDljRnRsT3lRb1EKKxaXLegA0V6vTnYF7l7MsJD42XWidB/h\nGiOojZ6r3JVV4Wx4Xl/MPk7lYvYN9VH//PUlA7h0Q9iVaQS+HA+aZw==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNXBjRGdmWGFEMmY3T0F3\nS2lFcXRKWnl5T0JnZkRxTzJUK0FlWlIvRVNvClVESkhEdFgzZnhKTTdaZEd3N3Yr\nWWJYTWI2M2Mwd3F3ZWdJVkdLMWwrRlkKLS0tIFFwcEdQUVA1dUFRRUtkUDN5RUZt\nTjlQNEx1ei9UeVpRTjcxL2t5NVBUajAKaZOQ1GQ3vtgZdxxoXQKU5Q3jSUnakW9F\n6bHKbhBGSFwCb3u9+mgVaefBU11vT1ue1kA8sCM03MkYBmM+8cPv7g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age187jl7e4k9n4guygkmpuqzeh0wenefwrfkpvuyhvwjrjwxqpzassqq3x67j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBob0MwUkJvRDRnQ05XMVRh\nODY5WE8wVlBkTW8xWUlLWUwxNHV6Y0FJTW5zCkdVV1dJTGlxREdGbWowak1HL3pH\nejNKV0VvejRadTVCeVdxdFkyMk0yOG8KLS0tIEllWldkaXZ1TzYzSmFoQ21nMkV4\nZlFxN0wxWlpvSHdreTdxQ1NZcit0dGsK+23q1QS5L1JzqFcKUygqfmYc64qVOO1R\nSiGa7O7ZoRMnn0Zfjas3K+byJ4PzNitEEU64s1518L/e78oHKQ9vVQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2a01mMmNPckZTbmJvQUVC\ncDBrVCtlRmpJMVByeE5zWFMrUCtHSkdDRFNBCmpZazA4Q0duejNYcmoxWDVMR3A5\nclh5OXZNNWd0VjI3cGlWbk5JTUw5NHcKLS0tIEVma1VsQ3cwSzdGZTlpanA2ZWNF\nQ2xLSjNkdjJyby95aTdTYk9HSHFvM1EK29MPNiB5GKqBFPgLcIp1giWzYRpPZ7ur\nDSKqvZ4kbEyxgdLxJ/i2P2iv4/4bb14LWQno9iEXsVeZ/iKf1mDigg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1ez6j3r5wdp0tjy7n5qzv5vfakdc2nh2zeu388zu7a80l0thv052syxq5e2",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaakJHOXRxeGRLZldZVmVi\nNHpFNmtYU1BCUnVKR1I3ZC9CSW5ORXlCbG5JCnpyUFA5NDNzbExob0s2R1EybzVJ\nb2tqSWtmZk4zQ1RZQmhKUmFvTFhTUjgKLS0tIG1DV04zMzVXdk9yQ1FFQzZsakM1\nTXQ0dCsrN0Z5RUVRamJUZEo5UmRiZWsK/av25ulEhPih+AK2rnktJsK6/SYS4IDx\nBuI2hBRZTdw1m4bBcCwMkLn/F1Rxd37xI1NZKKE3+CgnIs5G9XxhAw==\n-----END AGE ENCRYPTED FILE-----\n"
"recipient": "age1f9h725ewwwwwkelnrvdvrurg6fcsn3zxrxdt0v6v8ys0nzngcsvqu77nc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBteGpDZkpLcFIyVzNMbXFV\nMlY4SExrTThkdEZJczdLdlVwUWpVUWVuTUJvCmFXVG1ESHlIOExSdFJJOXhtb1JZ\nSVBocEtJTjRkckNwYktOdEdBQVQ5TVkKLS0tIE9iZ1pzMGhVc3F4THJwb2pFK2dm\nWnZ0SkhyNFMyQ1VVa0tWbDlXR1ZwK0EKPJq69mAeq3ghUpY+VuS+xCKeJVwmYnGm\nQ8mtmk/5skqbeOSKAbIiEEY5avPRy+Uy3YTD7g6vpSCqnpNSLEc/qg==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1tlymdmaukhwupzrhszspp26lgd8s64rw4vu9lwc7gsgrjm78095s9fe9l3",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEV1dtckt6Wi9HQ3Fxd0Ev\nRnRzcWs5SWRmNEpYbmZQTnEzbnliVDRkZ3pnCjUyTEFOYjd5SC9STVd2V1o0NGVR\nelphZE1ISGlkemdXSTBlcURDYUh3OGsKLS0tIHUxZFRKbVFsM0FWZ2ZEa0FaYjl5\nTWhTMnptYU4xNmp4aVNYMDRORkk5NVUKiwwbLVe3mtVe9sgeSA/FUhkowfFeirbA\nXXL9ct+lizSNXsFG7w3xpZsEaNGbHF28maZHWOpZotKRbdx4w6UJGQ==\n-----END AGE ENCRYPTED FILE-----\n"
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIZENwNHhLdzNYYmEvOHNW\ndEZJbVpUMGJyaVU5WkR0bUc0NzlsMWtmdjN3ClMyM2dGa3BlVTFqeDB6ZHRGSTNu\nREZSN0JXUkU4ektnWis0L3lCOHdpTmMKLS0tIGQxZTFRYjYvMS9uM2lYUThwYmtp\nbkYrTlNQeEd6aVNTY25aMHFVbnJXSWMKulwDyWezqDUIlv/aHMMGzGqOFU3VGaw3\nYvm/e2wFWPenFH0gfALkdC8upRghE8r9jkXcj1pSmDBbfjNghz+2oQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-12-20T21:46:17Z",