diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 743dc283b2..192dada787 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -26,4 +26,4 @@ body: - label: This is backend-only. - label: This does not require storing user PATs. - label: This does not expose wallet details, raw trust scores, or private rankings publicly. - - label: This does not auto-label, auto-close, or auto-merge contributor work. + - label: This does not auto-close, auto-merge, rewrite contributor work, or label PRs outside the confirmed-miner policy. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c933af3c17..3a56a879c4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,6 +5,7 @@ ## Validation - [ ] `npm run test:ci` +- [ ] `npm run changelog:check` ## Safety @@ -12,3 +13,4 @@ - [ ] No secrets, wallet details, user PATs, raw trust scores, or private rankings exposed - [ ] Public text avoids compensation-seeking or optimization-tactic language - [ ] OpenAPI/MCP behavior updated where needed +- [ ] Public docs/changelogs updated where needed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9146df9e57..03f49774f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} - name: Setup Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 @@ -30,6 +33,12 @@ jobs: - name: Install dependencies run: npm ci + - name: Check whitespace + run: git diff --check + + - name: Lint workflows + run: npm run actionlint + - name: Typecheck run: npm run typecheck @@ -42,5 +51,11 @@ jobs: - name: MCP package check run: npm run build:mcp && npm run test:mcp-pack + - name: Docs check + run: npm run docs:check && npm run docs:build + + - name: Changelog check + run: npm run changelog:check + - name: Audit dependencies run: npm audit --audit-level=moderate diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index b2b523e7cb..cefeb3dedb 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -20,6 +20,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + fetch-depth: 0 - name: Setup Node uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 @@ -47,10 +49,20 @@ jobs: set -euo pipefail PACK_JSON="$(npm pack --workspace @jsonbored/gittensory-mcp --json)" TARBALL="$(node -e 'const fs=require("fs"); const input=fs.readFileSync(0,"utf8"); process.stdout.write(JSON.parse(input)[0].filename)' <<< "$PACK_JSON")" + UNEXPECTED_FILES="$(tar -tzf "$TARBALL" | grep -Ev '^(package/(bin|lib)/|package/(package.json|README.md|CHANGELOG.md|LICENSE))$' || true)" + if [ -n "$UNEXPECTED_FILES" ]; then + printf '%s\n' "$UNEXPECTED_FILES" + echo "Unexpected file in package tarball" + exit 1 + fi + if tar -xOf "$TARBALL" | grep -E '(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|github_pat_|gh[pousr]_|gts_[0-9a-f]{64}|[A-Z0-9_]*(TOKEN|SECRET|PRIVATE_KEY)=)'; then + echo "Secret-like content found in package tarball" + exit 1 + fi TMP="$(mktemp -d)" npm --prefix "$TMP" init -y >/dev/null npm --prefix "$TMP" install "$PWD/$TARBALL" >/dev/null "$TMP/node_modules/.bin/gittensory-mcp" --help >/dev/null - name: Publish with npm trusted publishing - run: npx -y npm@11.15.0 publish --workspace @jsonbored/gittensory-mcp --access public + run: npx -y npm@11.15.0 publish --workspace @jsonbored/gittensory-mcp --access public --provenance diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..1e3ccfcf85 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +## Unreleased + + +### CI + +- Trigger pages deploy + +- Retrigger github pages deploy + + + +### Chores + +- Configure gittensory app id + +- Prepare public gittensory launch + + + +### Docs + +- Align public docs with gittensor aesthetic + +- Remove worker preview URLs from public docs + +- Publish static github pages artifact + + + +### Features + +- Scaffold gittensory advisory API + +- Add backend intelligence APIs + +- Expose MCP and PR intelligence + +- Add install site and mcp diagnostics + +- Add situational score projections + +- Add public registration polish gates + + + +### Fixes + +- Gate public PR visibility for confirmed miners + + + +### Tests + +- Raise backend regression coverage + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c17f811d59..6fa96ea2a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ Out of scope: - frontend UI work - public leaderboards - public wallet or raw trust-score exposure -- auto-labeling, auto-closing, auto-merging, or mutating contributor work +- auto-closing, auto-merging, rewriting contributor work, or applying labels outside the explicit confirmed-miner GitHub App policy - storing contributor PATs - public text that implies compensation estimates or optimization tactics @@ -29,6 +29,7 @@ Out of scope: - Add or update tests for behavior changes. - Keep API and MCP responses structured and machine-readable. - Keep public GitHub comments advisory, sanitized, and non-spammy. +- Keep GitHub App labels limited to configured labels for officially confirmed Gittensor miner PRs. - Prefer deterministic, evidence-based rules over opaque scoring. - Use Conventional Commit style for release-quality changelog output. diff --git a/LICENSE b/LICENSE index 5b40a7def0..be3f7b28e5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,661 @@ -SPDX-License-Identifier: AGPL-3.0-only + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 -Gittensory is licensed under the GNU Affero General Public License v3.0 only. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -The full license text is available at: -https://www.gnu.org/licenses/agpl-3.0.txt + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 2e0f678305..71ae621a75 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Gittensory is a backend-only intelligence layer for Gittensor registered reposit It helps miners and contributors make better decisions before they open work, and it helps maintainers review Gittensor-driven PRs with less noise. The product is the signal: role-aware contributor context, official Gittensor stats, local MCP preflight, queue health, collision risk, reviewability, and repo configuration quality. -Gittensory is not a Gittensor frontend, not a public leaderboard, and not an auto-label/auto-close bot. +Gittensory is not a Gittensor frontend, not a public leaderboard, and not an auto-close or auto-merge bot. ## What It Does @@ -12,14 +12,14 @@ Gittensory is not a Gittensor frontend, not a public leaderboard, and not an aut - Analyzes local branches through the MCP wrapper without uploading source contents. - Explains private reward/risk context: score blockers, open PR pressure, lane fit, duplicate risk, credibility assumptions, and maintainer friction. - Generates public-safe PR packets that help contributors write cleaner submissions. -- Gives maintainers private PR reviewability packets and advisory check runs. +- Gives maintainers private PR reviewability packets through the API/MCP, while the GitHub App stays public-safe. - Tracks repository intelligence: lane correctness, registry changes, queue health, label/config quality, collisions, bounties, and sync fidelity. ## Surfaces - Worker API: Cloudflare Workers + Hono + D1 + Queues. - MCP package: `@jsonbored/gittensory-mcp`, a local stdio wrapper for coding agents. -- GitHub App: check runs and optional sanitized sticky PR comments. +- GitHub App: quiet PR inspection, public-safe sticky comments, and maintainer-configured labels only for officially confirmed Gittensor miners. - Docs site: VitePress under `site/`, deployed at `https://gittensory.aethereal.dev/`. ## MCP Install @@ -108,6 +108,8 @@ Protected endpoints use `Authorization: Bearer ` or a Gitt - `GET /v1/repos` - `GET /v1/repos/:owner/:repo` - `GET /v1/repos/:owner/:repo/intelligence` +- `GET /v1/repos/:owner/:repo/registration-readiness` +- `GET /v1/repos/:owner/:repo/gittensor-config-recommendation` - `GET /v1/repos/:owner/:repo/pulls/:number/maintainer-packet` - `GET /v1/repos/:owner/:repo/pulls/:number/reviewability` - `GET /v1/contributors/:login/profile` @@ -128,13 +130,12 @@ Internal job routes are protected by `INTERNAL_JOB_TOKEN`. Required repository permissions: - Metadata: read -- Checks: write - Pull requests: read -- Issues: read +- Issues: write Optional repository permission: -- Issues: write, only when public-safe sticky PR comments are enabled. +- Checks: write, only when minimal check runs are explicitly enabled. Required events: @@ -144,6 +145,8 @@ Required events: If GitHub shows `Installation target`, select it. Gittensory should not block install health on event names that GitHub does not show in the app UI. +Default GitHub App behavior is low-noise: non-miner, bot, and maintainer-associated PR authors produce no public output. Confirmed Gittensor miners get one sticky public-safe PR comment and the configured label, defaulting to `gittensor`. Private reviewability, scoring, wallet, hotkey, and reward/risk context never appears in public GitHub comments or checks. + ## Docs ```sh @@ -154,8 +157,27 @@ npm run docs:preview The Pages workflow builds the docs on `main` for `https://gittensory.aethereal.dev/`, but deploys only when the repository variable `GITTENSORY_DOCS_DEPLOY` is set to `true`. +## Changelog And Releases + +```sh +npm run changelog +npm run changelog:check +``` + +- `CHANGELOG.md` tracks backend/API/GitHub App changes. +- `packages/gittensory-mcp/CHANGELOG.md` tracks npm-visible MCP package changes. +- Root releases use `vX.Y.Z` tags. +- MCP package releases use `mcp-vX.Y.Z` tags and publish through trusted publishing with provenance. + ## Validation ```sh npm run test:ci ``` + +## Support And Security + +- Public support: `SUPPORT.md` +- Security policy: `SECURITY.md` +- Privacy posture: `site/security/privacy.md` +- Terms: `site/security/terms.md` diff --git a/SECURITY.md b/SECURITY.md index 630f3d7d54..51af71e784 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,20 +1,22 @@ # Security -Gittensory is private beta software. Please do not include secrets, API tokens, private keys, -wallet details, webhook payload secrets, or sensitive contributor evidence in public issues, -pull requests, screenshots, or logs. +Please do not include secrets, API tokens, private keys, wallet details, webhook payload secrets, +or sensitive contributor evidence in public issues, pull requests, screenshots, or logs. ## Reporting -Until GitHub private vulnerability reporting is enabled for the public repository, report security -issues directly to the repository owner through a private channel. Once public vulnerability -reporting is enabled, use GitHub Security Advisories. +Use GitHub private vulnerability reporting if it is available on the repository. If it is not +available for your account context, contact the repository owner through a private channel and +include only the minimum reproduction details needed to verify the issue. ## Privacy Posture - Gittensory does not store user GitHub PATs. -- Public PR comments are opt-in and sanitized. -- Detailed contributor evidence belongs in private API responses and GitHub check runs. +- Public PR comments are sanitized and only posted for officially confirmed Gittensor miners when + the installed repository settings allow them. +- Detailed contributor evidence belongs in authenticated API and MCP responses only. +- GitHub check runs are public repository surfaces; when enabled, they must stay minimal and + exclude private reviewability, scoring, wallet, hotkey, and reward/risk context. - Wallet details, raw trust scores, private rankings, and negative labels must not be published in public comments or public issue templates. - GitHub App private keys, webhook secrets, MCP tokens, API tokens, and internal job tokens must be @@ -22,4 +24,4 @@ reporting is enabled, use GitHub Security Advisories. ## Supported Version -Private beta support tracks the current `main` branch. +Public support tracks the current `main` branch and the latest published MCP package. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..537fdd733a --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,27 @@ +# Support + +Use GitHub issues for public, non-sensitive support: + +- MCP install, auth, or local branch-analysis problems +- GitHub App install health, permissions, or comment/label behavior +- API contract, data freshness, or signal-quality bugs +- documentation gaps + +Do not post secrets, private keys, webhook payload secrets, wallet details, hotkeys, coldkeys, +raw session tokens, private maintainer evidence, or private scoring output in public issues. + +For security issues, use the guidance in `SECURITY.md`. + +## Useful Issue Details + +- Gittensory MCP version from `gittensory-mcp status` +- command used, with tokens and local paths removed +- sanitized error message +- repository owner/name when relevant +- whether the problem is MCP, API, GitHub App, docs, or data freshness + +## Expected Response Posture + +Gittensory is maintained as a backend and agent-integration project. Support should keep the same +boundaries as the product: no public wallet data, no raw trust scores, no public reward estimates, +and no source-code upload by default. diff --git a/cliff.mcp.toml b/cliff.mcp.toml new file mode 100644 index 0000000000..36d33a01a5 --- /dev/null +++ b/cliff.mcp.toml @@ -0,0 +1,40 @@ +[changelog] +header = "# Changelog\n\n" +body = """ +{% if version %}\ +## {{ version }} - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ +## Unreleased +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + +### {{ group | striptags | trim | upper_first }} +{% for commit in commits %} +- {{ commit.message | upper_first }} +{% endfor %} +{% endfor %}\n +""" +trim = true +footer = "" + +[git] +conventional_commits = true +filter_unconventional = true +split_commits = false +tag_pattern = "^mcp-v[0-9].*" +sort_commits = "oldest" + +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Fixes" }, + { message = "^test", group = "Tests" }, + { message = "^docs\\(changelog\\)", skip = true }, + { message = "^ci\\(changelog\\)", skip = true }, + { message = "^ci", group = "CI" }, + { message = "^build", group = "Build" }, + { message = "^docs", group = "Docs" }, + { message = "^refactor", group = "Refactors" }, + { message = "^chore\\(deps\\)", group = "Dependencies" }, + { message = "^chore", group = "Chores" }, + { message = "^revert", group = "Reverts" }, +] diff --git a/cliff.toml b/cliff.toml index b338a7d29d..15ad6d8453 100644 --- a/cliff.toml +++ b/cliff.toml @@ -10,7 +10,7 @@ body = """ ### {{ group | striptags | trim | upper_first }} {% for commit in commits %} -- {{ commit.message | upper_first }}{% if commit.remote.pr_number %} ([#{{ commit.remote.pr_number }}]({{ commit.remote.pr_url }})){% endif %} +- {{ commit.message | upper_first }} {% endfor %} {% endfor %}\n """ @@ -21,13 +21,15 @@ footer = "" conventional_commits = true filter_unconventional = true split_commits = false -tag_pattern = "v[0-9]*" +tag_pattern = "^v[0-9].*" sort_commits = "oldest" commit_parsers = [ { message = "^feat", group = "Features" }, { message = "^fix", group = "Fixes" }, { message = "^test", group = "Tests" }, + { message = "^docs\\(changelog\\)", skip = true }, + { message = "^ci\\(changelog\\)", skip = true }, { message = "^ci", group = "CI" }, { message = "^build", group = "Build" }, { message = "^docs", group = "Docs" }, diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index fb9fd49543..0000000000 --- a/docs/404.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - 404 | Gittensory - - - - - - - - - - - - - - - - - -
- - - - \ No newline at end of file diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index ccf3f210e9..0000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -gittensory.aethereal.dev diff --git a/docs/assets/app.DeJiVmBc.js b/docs/assets/app.DeJiVmBc.js deleted file mode 100644 index fe925665c6..0000000000 --- a/docs/assets/app.DeJiVmBc.js +++ /dev/null @@ -1 +0,0 @@ -import{t as p}from"./chunks/theme.BfZ7hXNa.js";import{v as o,aq as i,R as u,w as c,p as l,a as f,C as d,a3 as m,k as h,U as g,l as A,r as v,ae as w,O as C,aw as y,aj as P,ad as R,ac as b,u as E}from"./chunks/framework.BXgR76oC.js";function r(e){if(e.extends){const t=r(e.extends);return{...t,...e,async enhanceApp(a){t.enhanceApp&&await t.enhanceApp(a),e.enhanceApp&&await e.enhanceApp(a)}}}return e}const n=r(p),S=v({name:"VitePressApp",setup(){const{site:e,lang:t,dir:a}=w();return C(()=>{y(()=>{document.documentElement.lang=t.value,document.documentElement.dir=a.value})}),e.value.router.prefetchLinks&&P(),R(),b(),n.setup&&n.setup(),()=>E(n.Layout)}});async function T(){globalThis.__VITEPRESS__=!0;const e=D(),t=j();t.provide(u,e);const a=c(e.route);return t.provide(l,a),t.component("Content",f),t.component("ClientOnly",d),Object.defineProperties(t.config.globalProperties,{$frontmatter:{get(){return a.frontmatter.value}},$params:{get(){return a.page.value.params}}}),n.enhanceApp&&await n.enhanceApp({app:t,router:e,siteData:m}),{app:t,router:e,data:a}}function j(){return A(S)}function D(){let e=o;return h(t=>{let a=g(t),s=null;return a&&(e&&(a=a.replace(/\.js$/,".lean.js")),s=import(a)),o&&(e=!1),s},n.NotFound)}o&&T().then(({app:e,router:t,data:a})=>{t.go(location.href,{initialLoad:!0}).then(()=>{i(t.route,a.site),e.mount("#app")})});export{T as createApp}; diff --git a/docs/assets/chunks/@localSearchIndexroot.DQCfcpak.js b/docs/assets/chunks/@localSearchIndexroot.DQCfcpak.js deleted file mode 100644 index 77e5670a91..0000000000 --- a/docs/assets/chunks/@localSearchIndexroot.DQCfcpak.js +++ /dev/null @@ -1 +0,0 @@ -const e='{"documentCount":54,"nextId":54,"documentIds":{"0":"/guide/auth#auth","1":"/guide/auth#mcp-user-auth","2":"/guide/auth#non-interactive-bootstrap","3":"/guide/auth#check-auth-state","4":"/guide/auth#logout","5":"/guide/auth#static-tokens","6":"/guide/github-app-setup#github-app-setup","7":"/guide/github-app-setup#basic-fields","8":"/guide/github-app-setup#required-repository-permissions","9":"/guide/github-app-setup#required-events","10":"/guide/github-app-setup#install-or-repair","11":"/guide/github-app-setup#marketplace-readiness","12":"/guide/install#install","13":"/guide/install#public-npm","14":"/guide/install#local-checkout","15":"/guide/install#verify-the-install","16":"/guide/install#privacy-defaults","17":"/guide/maintainers#for-maintainers","18":"/guide/maintainers#github-app-surface","19":"/guide/maintainers#reviewability-actions","20":"/guide/maintainers#public-comments","21":"/guide/maintainers#repo-owner-signals","22":"/guide/mcp#mcp","23":"/guide/mcp#generate-client-config","24":"/guide/mcp#codex","25":"/guide/mcp#claude-desktop","26":"/guide/mcp#cursor","27":"/guide/mcp#tools","28":"/guide/mcp#runtime-rules","29":"/guide/miners#for-miners","30":"/guide/miners#what-it-answers","31":"/guide/miners#branch-analysis","32":"/guide/miners#preflight","33":"/guide/miners#how-this-helps","34":"/reference/api#api-reference","35":"/reference/api#contributor-apis","36":"/reference/api#repo-apis","37":"/reference/api#local-and-preflight-apis","38":"/reference/api#ops-apis","39":"/reference/api#mcp-endpoint","40":"/security/privacy#privacy-and-security","41":"/security/privacy#no-pat-storage","42":"/security/privacy#no-source-upload","43":"/security/privacy#public-output-boundaries","44":"/security/privacy#rate-limiting","45":"/security/privacy#github-app-boundaries","46":"/troubleshooting#troubleshooting","47":"/troubleshooting#gittensory-mcp-command-not-found","48":"/troubleshooting#login-fails","49":"/troubleshooting#session-expired","50":"/troubleshooting#source-upload-error","51":"/troubleshooting#github-app-installation-needs-attention","52":"/troubleshooting#rate-limited","53":"/troubleshooting#stale-decision-pack"},"fieldIds":{"title":0,"titles":1,"text":2},"fieldLength":{"0":[1,1,14],"1":[3,1,41],"2":[3,1,31],"3":[3,1,7],"4":[1,1,16],"5":[2,1,23],"6":[3,1,13],"7":[2,3,56],"8":[3,3,53],"9":[2,3,39],"10":[3,3,48],"11":[2,3,31],"12":[1,1,15],"13":[2,1,41],"14":[2,1,24],"15":[3,1,35],"16":[2,1,38],"17":[2,1,12],"18":[3,2,41],"19":[2,2,31],"20":[2,2,40],"21":[3,2,28],"22":[1,1,26],"23":[3,1,31],"24":[1,1,9],"25":[2,1,8],"26":[1,1,8],"27":[1,1,36],"28":[2,1,37],"29":[2,1,14],"30":[3,2,51],"31":[2,2,97],"32":[1,2,39],"33":[3,2,53],"34":[2,1,45],"35":[2,2,36],"36":[2,2,30],"37":[4,2,19],"38":[2,2,33],"39":[2,2,21],"40":[3,1,11],"41":[3,3,23],"42":[3,3,39],"43":[3,3,36],"44":[2,3,31],"45":[3,3,18],"46":[1,1,1],"47":[5,1,32],"48":[2,1,22],"49":[2,1,10],"50":[3,1,21],"51":[5,1,41],"52":[2,1,20],"53":[3,1,18]},"averageFieldLength":[2.3333333333333335,1.6111111111111112,29.500000000000004],"storedFields":{"0":{"title":"Auth","titles":[]},"1":{"title":"MCP User Auth","titles":["Auth"]},"2":{"title":"Non-Interactive Bootstrap","titles":["Auth"]},"3":{"title":"Check Auth State","titles":["Auth"]},"4":{"title":"Logout","titles":["Auth"]},"5":{"title":"Static Tokens","titles":["Auth"]},"6":{"title":"GitHub App Setup","titles":[]},"7":{"title":"Basic Fields","titles":["GitHub App Setup"]},"8":{"title":"Required Repository Permissions","titles":["GitHub App Setup"]},"9":{"title":"Required Events","titles":["GitHub App Setup"]},"10":{"title":"Install Or Repair","titles":["GitHub App Setup"]},"11":{"title":"Marketplace Readiness","titles":["GitHub App Setup"]},"12":{"title":"Install","titles":[]},"13":{"title":"Public npm","titles":["Install"]},"14":{"title":"Local checkout","titles":["Install"]},"15":{"title":"Verify The Install","titles":["Install"]},"16":{"title":"Privacy Defaults","titles":["Install"]},"17":{"title":"For Maintainers","titles":[]},"18":{"title":"GitHub App Surface","titles":["For Maintainers"]},"19":{"title":"Reviewability Actions","titles":["For Maintainers"]},"20":{"title":"Public Comments","titles":["For Maintainers"]},"21":{"title":"Repo Owner Signals","titles":["For Maintainers"]},"22":{"title":"MCP","titles":[]},"23":{"title":"Generate Client Config","titles":["MCP"]},"24":{"title":"Codex","titles":["MCP"]},"25":{"title":"Claude Desktop","titles":["MCP"]},"26":{"title":"Cursor","titles":["MCP"]},"27":{"title":"Tools","titles":["MCP"]},"28":{"title":"Runtime Rules","titles":["MCP"]},"29":{"title":"For Miners","titles":[]},"30":{"title":"What It Answers","titles":["For Miners"]},"31":{"title":"Branch Analysis","titles":["For Miners"]},"32":{"title":"Preflight","titles":["For Miners"]},"33":{"title":"How This Helps","titles":["For Miners"]},"34":{"title":"API Reference","titles":[]},"35":{"title":"Contributor APIs","titles":["API Reference"]},"36":{"title":"Repo APIs","titles":["API Reference"]},"37":{"title":"Local And Preflight APIs","titles":["API Reference"]},"38":{"title":"Ops APIs","titles":["API Reference"]},"39":{"title":"MCP Endpoint","titles":["API Reference"]},"40":{"title":"Privacy And Security","titles":[]},"41":{"title":"No PAT Storage","titles":["Privacy And Security"]},"42":{"title":"No Source Upload","titles":["Privacy And Security"]},"43":{"title":"Public Output Boundaries","titles":["Privacy And Security"]},"44":{"title":"Rate Limiting","titles":["Privacy And Security"]},"45":{"title":"GitHub App Boundaries","titles":["Privacy And Security"]},"46":{"title":"Troubleshooting","titles":[]},"47":{"title":"gittensory-mcp: command not found","titles":["Troubleshooting"]},"48":{"title":"Login Fails","titles":["Troubleshooting"]},"49":{"title":"Session Expired","titles":["Troubleshooting"]},"50":{"title":"Source Upload Error","titles":["Troubleshooting"]},"51":{"title":"GitHub App Installation Needs Attention","titles":["Troubleshooting"]},"52":{"title":"Rate Limited","titles":["Troubleshooting"]},"53":{"title":"Stale Decision Pack","titles":["Troubleshooting"]}},"dirtCount":0,"index":[["429",{"2":{"44":1,"52":1}}],["known",{"2":{"36":1}}],["know",{"2":{"32":1}}],["8",{"2":{"31":1}}],["0",{"2":{"31":2}}],["3",{"2":{"31":1}}],["=",{"2":{"24":2}}],["queue",{"2":{"18":1,"21":1,"31":1,"53":1}}],["json",{"2":{"15":1,"25":1,"26":1,"31":2,"32":1,"34":1}}],["jsonbored",{"2":{"13":1,"14":2,"47":1}}],["job",{"2":{"10":1}}],["jobs",{"2":{"5":1,"10":1}}],["x",{"2":{"10":1}}],["number",{"2":{"36":2}}],["next",{"2":{"19":1,"20":1,"27":1,"29":1,"31":1}}],["need",{"2":{"32":1}}],["needs",{"0":{"51":1},"2":{"19":1,"53":1}}],["needing",{"2":{"10":1}}],["needed",{"2":{"8":2,"11":1}}],["name",{"2":{"16":1,"42":2}}],["names",{"2":{"9":1}}],["npm",{"0":{"13":1},"2":{"12":1,"14":2,"39":1}}],["no",{"0":{"41":1,"42":1},"2":{"28":1}}],["now",{"2":{"19":1,"30":1}}],["noisy",{"2":{"17":1}}],["normal",{"2":{"5":1,"9":1,"12":1,"30":1,"44":1,"52":1}}],["non",{"0":{"2":1}}],["note",{"2":{"31":1}}],["notes",{"2":{"20":1,"31":1}}],["not",{"0":{"47":1},"2":{"1":1,"2":1,"7":1,"9":2,"11":1,"16":1,"19":1,"20":1,"23":2,"28":1,"33":1,"40":1,"42":1,"43":1,"45":1,"47":2}}],["vs",{"2":{"31":1,"33":1}}],["var",{"2":{"50":1}}],["variant",{"2":{"27":1}}],["validation",{"2":{"16":1,"33":1,"42":1}}],["valid",{"2":{"11":1}}],["value",{"2":{"7":2,"52":1}}],["values",{"2":{"7":1}}],["visible",{"2":{"15":1}}],["verify",{"0":{"15":1}}],["verification",{"2":{"7":1}}],["v1",{"2":{"7":1,"10":2,"16":1,"28":1,"35":3,"36":5,"37":3,"38":4,"42":1,"50":1,"51":1}}],["using",{"2":{"35":1}}],["useful",{"2":{"27":1,"32":1}}],["uses",{"2":{"13":1,"41":1,"44":1}}],["use",{"2":{"5":1,"7":3,"12":1,"13":1,"14":1,"21":1,"23":1,"32":1,"34":1,"47":1}}],["user",{"0":{"1":1},"2":{"1":2,"6":1,"31":1,"39":1}}],["users",{"2":{"0":1,"5":1}}],["underlying",{"2":{"31":1}}],["unknown",{"2":{"30":1}}],["until",{"2":{"11":1}}],["up",{"2":{"30":1}}],["uploaded",{"2":{"42":1}}],["upload",{"0":{"42":1,"50":1},"2":{"15":1,"16":2,"28":1,"42":1,"50":3}}],["update",{"2":{"8":1,"10":1,"51":1}}],["ui",{"2":{"9":1}}],["url=",{"2":{"34":1,"51":1}}],["url",{"2":{"7":4,"10":2,"11":1,"34":1,"51":1}}],["have",{"2":{"52":1}}],["handles",{"2":{"40":1}}],["hashed",{"2":{"1":1,"41":1}}],["has",{"2":{"0":1,"12":1,"53":1}}],["http",{"2":{"39":1}}],["https",{"2":{"7":1,"14":1,"34":1,"51":1}}],["history",{"2":{"33":1}}],["hidden",{"2":{"9":1}}],["head",{"2":{"42":1}}],["healthy",{"2":{"10":1}}],["health",{"2":{"9":1,"10":3,"15":1,"21":3,"34":1,"38":4,"51":3}}],["helps",{"0":{"33":1},"2":{"29":1}}],["how",{"0":{"33":1}}],["hotkey",{"2":{"28":1}}],["hotkeys",{"2":{"20":1,"43":1}}],["homepage",{"2":{"7":1}}],["hygiene",{"2":{"20":1}}],["h",{"2":{"10":2,"34":1,"51":1}}],["boundaries",{"0":{"43":1,"45":1}}],["bootstrap",{"0":{"2":1},"2":{"5":1}}],["bundle",{"2":{"36":1}}],["burden",{"2":{"32":1}}],["by",{"2":{"20":1,"31":1}}],["binary",{"2":{"15":1}}],["branch",{"0":{"31":1},"2":{"15":1,"16":1,"27":3,"30":1,"31":2,"32":1,"37":2,"42":2,"44":1}}],["blocked",{"2":{"31":1,"33":1}}],["blockers",{"2":{"27":2,"31":1}}],["blocks",{"2":{"30":1}}],["block",{"2":{"9":1}}],["backfill",{"2":{"21":1}}],["backend",{"2":{"5":1,"7":1,"41":1}}],["base",{"2":{"16":1,"31":1,"42":1}}],["basic",{"0":{"7":1}}],["because",{"2":{"43":1}}],["be",{"2":{"31":1,"48":1}}],["before",{"2":{"11":1,"30":1,"32":1}}],["beta",{"2":{"5":1}}],["bearer",{"2":{"5":1,"10":2,"34":2,"51":1}}],["planned",{"2":{"37":1}}],["pick",{"2":{"33":1}}],["pending",{"2":{"31":1}}],["permission",{"2":{"8":2,"38":1,"51":1}}],["permissions",{"0":{"8":1},"2":{"10":2,"51":1}}],["poll",{"2":{"34":1}}],["policy",{"2":{"11":1}}],["potential",{"2":{"31":1}}],["point",{"2":{"19":2}}],["post",{"2":{"10":1,"37":3,"39":1}}],["possible",{"2":{"4":1}}],["purpose",{"2":{"35":1,"36":1,"37":1,"38":1}}],["public",{"0":{"13":1,"20":1,"43":1},"2":{"8":1,"11":2,"12":1,"18":2,"20":3,"28":2,"30":1,"31":1,"34":1,"43":5}}],["pulls",{"2":{"36":2}}],["pull",{"2":{"8":1,"9":1}}],["profile",{"2":{"35":2}}],["protected",{"2":{"34":1}}],["promise",{"2":{"33":1}}],["projected",{"2":{"31":2}}],["preferred",{"2":{"39":1}}],["preflight",{"0":{"32":1,"37":1},"2":{"27":2,"31":1,"32":1,"37":4}}],["presence",{"2":{"38":1}}],["pressure",{"2":{"33":1}}],["prepare",{"2":{"27":1}}],["preview",{"2":{"27":1,"44":1}}],["print",{"2":{"23":4}}],["privacy",{"0":{"16":1,"40":1},"1":{"41":1,"42":1,"43":1,"44":1,"45":1},"2":{"11":2}}],["private",{"2":{"5":1,"7":1,"22":1,"28":1,"31":1,"35":1,"36":1,"40":1,"43":2}}],["prs",{"2":{"18":1,"19":1,"30":1,"31":3,"33":1}}],["pr",{"2":{"8":2,"18":1,"20":1,"27":2,"28":1,"30":2,"31":2,"32":1,"33":2,"36":2,"37":2,"43":1}}],["payload",{"2":{"35":1}}],["payouts",{"2":{"33":1}}],["packet",{"2":{"27":1,"30":1,"31":1,"36":2}}],["packets",{"2":{"27":1,"28":1,"43":1}}],["pack",{"0":{"53":1},"2":{"27":1,"35":2,"44":1,"53":1}}],["packs",{"2":{"27":1}}],["package",{"2":{"22":1}}],["pat",{"0":{"41":1}}],["path",{"2":{"23":2,"47":2}}],["paths",{"2":{"12":1,"16":1,"42":1}}],["pats",{"2":{"1":1}}],["page",{"2":{"11":1}}],["pages",{"2":{"7":2}}],["pass",{"2":{"2":1,"31":1}}],["work",{"2":{"30":1,"32":1,"33":2,"45":1}}],["workspace",{"2":{"14":1,"47":1}}],["withdraw",{"2":{"33":1}}],["with",{"2":{"22":1,"29":1,"32":1,"44":1}}],["wip",{"2":{"20":1,"30":1}}],["wallets",{"2":{"40":1,"43":1}}],["wallet",{"2":{"20":1,"28":1}}],["watch",{"2":{"19":1}}],["warnings",{"2":{"31":1}}],["warning",{"2":{"10":1}}],["write",{"2":{"8":2}}],["wrapper",{"2":{"2":1,"27":1,"28":1,"39":1}}],["webhooks",{"2":{"7":1}}],["webhook",{"2":{"7":5,"8":1,"11":1,"34":1}}],["what",{"0":{"30":1},"2":{"29":1,"30":2,"31":1}}],["where",{"2":{"33":1}}],["whether",{"2":{"15":1,"32":1}}],["when",{"2":{"4":1,"14":1,"20":1,"31":2,"32":1,"34":1,"35":1}}],["why",{"2":{"8":2}}],["whoami",{"2":{"3":1,"15":1}}],["globally",{"2":{"47":1}}],["gates",{"2":{"31":1}}],["give",{"2":{"30":1}}],["gittensor",{"2":{"17":1,"35":1}}],["gittensory",{"0":{"47":1},"2":{"0":1,"1":3,"2":2,"3":2,"7":3,"8":1,"9":1,"10":1,"12":1,"13":5,"14":6,"15":2,"16":2,"17":1,"18":1,"19":1,"21":1,"22":2,"23":2,"24":2,"25":2,"26":2,"27":8,"29":1,"31":1,"33":1,"34":2,"40":1,"41":2,"42":1,"44":1,"47":1,"48":2,"50":2,"51":2,"53":1}}],["git",{"2":{"14":1,"15":1,"31":1}}],["github",{"0":{"6":1,"18":1,"45":1,"51":1},"1":{"7":1,"8":1,"9":1,"10":1,"11":1},"2":{"0":2,"1":4,"2":3,"5":1,"6":2,"7":3,"9":1,"10":2,"13":1,"14":1,"15":1,"18":1,"21":1,"31":2,"32":1,"34":1,"38":2,"41":2,"45":1,"48":2,"51":1}}],["guesswork",{"2":{"29":1}}],["get",{"2":{"27":1,"35":3,"36":5,"38":4}}],["generate",{"0":{"23":1}}],["generated",{"2":{"7":1}}],["g",{"2":{"13":1}}],["gh",{"2":{"2":1}}],["$internal",{"2":{"10":1}}],["$gittensory",{"2":{"10":3,"34":2,"51":2}}],["$",{"2":{"2":1,"7":1}}],["error",{"0":{"50":1},"2":{"50":1}}],["eligibility",{"2":{"33":1}}],["especially",{"2":{"32":1}}],["estimates",{"2":{"20":1,"28":1,"43":2}}],["effective",{"2":{"31":1}}],["evidence",{"2":{"29":1,"33":1,"35":1}}],["event",{"2":{"9":2,"38":1}}],["events",{"0":{"9":1},"2":{"8":2,"9":1,"10":1,"51":1}}],["edit",{"2":{"23":1}}],["enqueued",{"2":{"53":1}}],["env",{"2":{"50":1}}],["endpoint",{"0":{"39":1},"2":{"35":1,"36":1,"37":1,"38":1,"51":1}}],["endpoints",{"2":{"34":1}}],["enabled",{"2":{"7":3,"8":1,"20":1,"48":1}}],["enough",{"2":{"2":1}}],["exact",{"2":{"38":1}}],["example",{"2":{"34":1,"51":1}}],["extracted",{"2":{"35":1}}],["except",{"2":{"34":1}}],["exchanged",{"2":{"2":1}}],["exchanges",{"2":{"1":1,"41":1}}],["expired",{"0":{"49":1}}],["expiry",{"2":{"1":1}}],["expensive",{"2":{"44":1,"52":1}}],["expected",{"2":{"31":2}}],["explicitly",{"2":{"31":1}}],["explains",{"2":{"33":1}}],["explain",{"2":{"27":2}}],["exposes",{"2":{"27":1,"39":1}}],["drains",{"2":{"53":1}}],["driven",{"2":{"17":1}}],["data",{"2":{"20":1}}],["diff",{"2":{"31":1,"37":2}}],["direct",{"2":{"30":1,"33":2}}],["directory",{"2":{"13":1}}],["directly",{"2":{"8":1}}],["diagnostics",{"2":{"11":2}}],["discovery",{"2":{"8":1,"30":1,"33":2}}],["duplicate",{"2":{"8":1,"18":1,"19":1,"20":1,"30":1,"33":1}}],["deletions",{"2":{"42":1}}],["delivery",{"2":{"34":1}}],["decide",{"2":{"29":1}}],["decision",{"0":{"53":1},"2":{"27":3,"35":5,"44":1,"53":1}}],["degraded",{"2":{"21":1}}],["default",{"2":{"20":1}}],["defaults",{"0":{"16":1},"2":{"15":1}}],["detailed",{"2":{"18":2}}],["desktop",{"0":{"25":1},"2":{"13":1}}],["deployed",{"2":{"7":1}}],["developing",{"2":{"14":1}}],["development",{"2":{"12":1}}],["dev",{"2":{"7":1}}],["device",{"2":{"1":1,"7":1,"13":1,"41":1,"48":1}}],["domain",{"2":{"7":1}}],["document",{"2":{"34":1}}],["docs",{"2":{"7":2,"11":1}}],["doctor",{"2":{"3":1,"15":2,"47":1,"48":1}}],["do",{"2":{"7":1,"11":1,"23":1,"28":1,"29":1}}],["does",{"2":{"1":1,"16":1,"23":1,"30":1,"33":1,"45":1,"47":1}}],["mode",{"2":{"50":1}}],["more",{"2":{"30":1}}],["missing",{"2":{"32":2,"51":1}}],["miner",{"2":{"35":1}}],["miners",{"0":{"29":1},"1":{"30":1,"31":1,"32":1,"33":1},"2":{"29":1}}],["mint",{"2":{"2":1}}],["my",{"2":{"30":1}}],["must",{"2":{"20":1,"43":1,"48":1}}],["merge",{"2":{"31":1,"45":1}}],["merged",{"2":{"31":1}}],["meant",{"2":{"17":1}}],["messages",{"2":{"16":1,"42":1}}],["metadata",{"2":{"1":1,"8":2,"15":1,"16":1,"36":1,"37":3,"42":1,"44":1}}],["may",{"2":{"31":1}}],["maps",{"2":{"19":1}}],["makes",{"2":{"33":1}}],["make",{"2":{"17":1}}],["marketplace",{"0":{"11":1},"2":{"11":1}}],["maintainers",{"0":{"17":1},"1":{"18":1,"19":1,"20":1,"21":1}}],["maintainer",{"2":{"6":1,"18":1,"19":2,"20":1,"21":1,"30":2,"31":1,"32":1,"36":3}}],["mcpservers",{"2":{"25":1,"26":1}}],["mcp",{"0":{"1":1,"22":1,"39":1,"47":1},"1":{"23":1,"24":1,"25":1,"26":1,"27":1,"28":1},"2":{"0":1,"1":1,"2":2,"3":3,"4":1,"5":1,"6":1,"13":5,"14":3,"15":4,"16":1,"22":1,"23":3,"24":2,"25":1,"26":1,"28":1,"31":2,"32":1,"35":1,"37":1,"39":3,"41":1,"42":1,"43":1,"44":1,"47":3,"48":2,"49":1}}],["improve",{"2":{"33":1}}],["immediately",{"2":{"2":1}}],["i",{"2":{"30":3}}],["itself",{"2":{"14":1}}],["it",{"0":{"30":1},"2":{"9":1,"16":1,"22":1,"31":1,"32":1,"33":1,"45":1}}],["if",{"2":{"8":2,"9":1,"11":1,"23":1,"31":1,"47":2,"50":1,"52":1,"53":1}}],["issue",{"2":{"8":2,"16":1,"18":1,"30":1,"32":1,"33":2,"42":1}}],["issues",{"2":{"8":2,"9":1}}],["is",{"2":{"2":1,"6":2,"7":1,"15":1,"16":1,"17":1,"18":1,"19":2,"22":1,"30":2,"31":1,"32":2,"34":1,"39":1,"45":1,"47":1}}],["increase",{"2":{"32":1}}],["includes",{"2":{"31":1}}],["include",{"2":{"20":2,"28":1,"43":2}}],["inflated",{"2":{"31":1}}],["inactive",{"2":{"30":1}}],["inherit",{"2":{"23":1,"47":1}}],["init",{"2":{"23":3}}],["intentionally",{"2":{"49":1}}],["intelligence",{"2":{"36":2,"40":1}}],["internal",{"2":{"5":1,"10":1,"35":1}}],["interactive",{"0":{"2":1}}],["intake",{"2":{"21":1}}],["instead",{"2":{"29":1}}],["installed",{"2":{"36":1,"47":1}}],["install",{"0":{"10":1,"12":1,"15":1},"1":{"13":1,"14":1,"15":1,"16":1},"2":{"6":1,"11":2,"12":1,"13":1,"14":1}}],["installations",{"2":{"10":1,"38":3,"51":1}}],["installation",{"0":{"51":1},"2":{"0":1,"9":2,"10":3,"21":1,"38":2,"51":3}}],["inspect",{"2":{"10":1,"21":1}}],["in",{"2":{"7":1,"9":1,"13":1,"18":1,"28":2,"30":1,"33":1,"42":1,"47":1,"50":1,"51":1}}],["identify",{"2":{"19":1}}],["identity",{"2":{"8":1}}],["id",{"2":{"1":1,"38":1,"51":1}}],["labels",{"2":{"31":1}}],["label",{"2":{"21":1,"45":1}}],["land",{"2":{"33":1}}],["language",{"2":{"20":1,"43":1}}],["lane",{"2":{"18":1,"19":1,"21":1,"30":7,"31":1,"33":2}}],["less",{"2":{"17":1}}],["limits",{"2":{"52":1}}],["limited",{"0":{"52":1},"2":{"44":1}}],["limiting",{"0":{"44":1}}],["limit",{"2":{"38":1}}],["live",{"2":{"34":1}}],["lived",{"2":{"1":1,"13":1,"49":1}}],["likely",{"2":{"15":1,"19":1,"32":1}}],["linked",{"2":{"16":1,"18":1,"32":1,"42":1}}],["link",{"2":{"14":1,"47":1}}],["linkage",{"2":{"8":1}}],["look",{"2":{"30":1}}],["lowest",{"2":{"19":1}}],["logout",{"0":{"4":1},"2":{"4":2}}],["login",{"0":{"48":1},"2":{"1":2,"2":1,"13":2,"14":1,"15":2,"31":4,"32":2,"35":3,"41":1,"49":1}}],["long",{"2":{"2":1}}],["locally",{"2":{"22":1}}],["local",{"0":{"14":1,"37":1},"2":{"2":1,"4":1,"12":1,"13":1,"15":1,"27":5,"28":1,"31":1,"37":4,"39":1,"42":1}}],["of",{"2":{"29":1}}],["official",{"2":{"35":1}}],["off",{"2":{"20":1}}],["outputs",{"2":{"43":1}}],["output",{"0":{"43":1},"2":{"28":1}}],["over",{"2":{"22":1,"39":1}}],["owners",{"2":{"21":1}}],["owner",{"0":{"21":1},"2":{"35":1,"36":4}}],["other",{"2":{"13":1}}],["oriented",{"2":{"37":1}}],["origin",{"2":{"7":1,"34":1,"51":1}}],["or",{"0":{"10":1},"2":{"10":1,"13":1,"19":1,"20":2,"21":1,"28":1,"30":3,"32":1,"33":2,"40":1,"45":1,"48":1}}],["ops",{"0":{"38":1}}],["openapi",{"2":{"34":2}}],["opening",{"2":{"30":1,"32":1}}],["open",{"2":{"30":1,"31":2,"33":1}}],["operations",{"2":{"5":1}}],["optimization",{"2":{"20":1}}],["optional",{"2":{"8":1,"18":1}}],["opt",{"2":{"18":1}}],["only",{"2":{"2":1,"5":1,"7":1,"8":2,"16":1,"28":1,"33":1,"41":1,"42":1,"45":1}}],["on",{"2":{"1":1,"9":1,"18":1,"48":1}}],["oauth",{"2":{"0":1,"5":1,"6":1,"41":1,"48":1}}],["credibility",{"2":{"31":2,"33":1}}],["creates",{"2":{"18":1}}],["create",{"2":{"8":1}}],["calls",{"2":{"22":1}}],["can",{"2":{"20":1,"21":1,"43":1}}],["canonical",{"2":{"18":1,"35":1,"36":1}}],["curl",{"2":{"34":1,"51":1}}],["current",{"2":{"27":2,"30":1,"31":3}}],["cursor",{"0":{"26":1},"2":{"13":1,"23":1}}],["cut",{"2":{"21":1}}],["cd",{"2":{"14":1}}],["changes",{"2":{"31":1}}],["change",{"2":{"27":1}}],["changed",{"2":{"10":1,"16":1,"42":1}}],["checkout",{"0":{"14":1},"2":{"12":1}}],["checks",{"2":{"8":1,"15":1}}],["check",{"0":{"3":1},"2":{"8":1,"10":1,"18":2,"48":1,"51":1}}],["clusters",{"2":{"33":1}}],["clean",{"2":{"30":1}}],["clear",{"2":{"11":1,"31":1}}],["classes",{"2":{"44":1}}],["clarity",{"2":{"21":1}}],["claude",{"0":{"25":1},"2":{"13":1,"23":1}}],["closed",{"2":{"42":1}}],["close",{"2":{"19":1,"45":1}}],["clone",{"2":{"14":1}}],["cloudflare",{"2":{"7":1}}],["clients",{"2":{"15":1,"35":1}}],["client",{"0":{"23":1},"2":{"13":1,"23":4,"47":2}}],["cli",{"2":{"1":1}}],["colliding",{"2":{"32":1}}],["collision",{"2":{"30":1}}],["coding",{"2":{"22":1}}],["codex",{"0":{"24":1},"2":{"13":1,"23":1}}],["code",{"2":{"1":1,"40":1}}],["counts",{"2":{"16":1}}],["comparison",{"2":{"27":1}}],["complete",{"2":{"11":1}}],["com",{"2":{"14":1}}],["commit",{"2":{"16":1,"42":1}}],["commands",{"2":{"23":1}}],["command",{"0":{"47":1},"2":{"13":1,"16":1,"23":1,"24":1,"25":1,"26":1,"47":2,"52":1}}],["comments",{"0":{"20":1},"2":{"8":1,"18":2,"20":3,"43":1}}],["contributors",{"2":{"19":1,"35":3}}],["contributor",{"0":{"35":1},"2":{"18":1,"20":1,"21":1,"22":1,"27":1,"30":1,"35":1,"45":1}}],["contribution",{"2":{"17":1,"20":1,"40":1}}],["contact",{"2":{"11":1}}],["contents",{"2":{"8":1,"16":1,"42":1}}],["context",{"2":{"8":1,"18":3,"20":1,"27":1,"31":2,"38":1}}],["configured",{"2":{"48":1}}],["configuration",{"2":{"21":1}}],["config",{"0":{"23":1},"2":{"4":1,"13":1,"23":1,"47":1}}],["your",{"2":{"13":1,"15":1,"22":1,"23":3,"31":2,"32":1,"33":1,"34":1,"47":2,"51":1}}],["you",{"2":{"1":1,"32":1,"50":1}}],["found",{"0":{"47":1}}],["for",{"0":{"17":1,"29":1},"1":{"18":1,"19":1,"20":1,"21":1,"30":1,"31":1,"32":1,"33":1},"2":{"0":2,"1":1,"2":1,"5":1,"7":2,"8":3,"12":2,"13":1,"22":1,"28":1,"30":1,"34":1,"35":1,"41":1,"48":1}}],["farming",{"2":{"43":1}}],["fails",{"0":{"48":1},"2":{"42":1}}],["facing",{"2":{"22":1}}],["freshness",{"2":{"31":1}}],["from",{"2":{"31":1,"35":1,"37":1}}],["friction",{"2":{"19":1,"33":1}}],["friendly",{"2":{"19":1}}],["full",{"2":{"16":1,"42":1}}],["future",{"2":{"8":1}}],["feature",{"2":{"8":1}}],["fix",{"2":{"51":1}}],["fidelity",{"2":{"38":2}}],["first",{"2":{"35":1}}],["fit",{"2":{"31":1,"33":2}}],["findings",{"2":{"31":1}}],["field",{"2":{"7":1}}],["fields",{"0":{"7":1}}],["files",{"2":{"8":1,"23":1}}],["file",{"2":{"4":1,"16":1,"42":1}}],["flow",{"2":{"1":1,"7":1,"11":2,"13":1,"17":1,"41":1,"48":1}}],["troubleshooting",{"0":{"46":1},"1":{"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1}}],["trust",{"2":{"20":1,"28":1,"43":1}}],["trigger",{"2":{"10":1}}],["tests",{"2":{"32":1}}],["terms",{"2":{"11":2}}],["temporary",{"2":{"5":1,"31":1}}],["target",{"2":{"9":1}}],["than",{"2":{"52":1}}],["that",{"2":{"9":1,"31":1,"33":1,"47":1}}],["this",{"0":{"33":1},"2":{"13":1,"14":1,"30":2,"32":1,"50":1}}],["through",{"2":{"8":1}}],["they",{"2":{"43":1}}],["there",{"2":{"30":1}}],["these",{"2":{"7":1,"23":1}}],["then",{"2":{"1":1,"47":1,"51":1}}],["the",{"0":{"15":1},"2":{"1":4,"2":4,"4":2,"6":3,"7":4,"8":2,"9":1,"10":5,"11":1,"13":1,"15":1,"18":4,"19":3,"22":3,"27":1,"28":1,"31":7,"33":1,"34":2,"35":1,"39":2,"41":3,"45":1,"47":1,"48":1,"51":3,"52":1,"53":1}}],["totals",{"2":{"38":1}}],["tools",{"0":{"27":1},"2":{"27":2,"44":1}}],["toml",{"2":{"24":1}}],["tokens",{"0":{"5":1},"2":{"5":1}}],["token",{"2":{"1":3,"2":6,"10":2,"13":1,"22":1,"34":2,"41":3,"51":1}}],["to",{"2":{"1":1,"2":1,"8":1,"9":1,"15":1,"17":1,"19":3,"21":1,"29":1,"31":1,"32":2}}],["two",{"2":{"0":1,"12":1}}],["snapshot",{"2":{"53":1}}],["snippet",{"2":{"23":1}}],["sync",{"2":{"38":2}}],["specific",{"2":{"35":1,"36":1}}],["split",{"2":{"30":1}}],["scenario",{"2":{"31":2}}],["scenarios",{"2":{"31":1}}],["scoring",{"2":{"44":1}}],["scoreability",{"2":{"30":1,"31":1,"33":1,"43":1}}],["score",{"2":{"20":1,"27":2,"28":3,"31":4,"36":1,"43":1}}],["scores",{"2":{"20":1,"43":1}}],["scopes",{"2":{"1":1}}],["source=true",{"2":{"16":1,"42":1}}],["source",{"0":{"42":1,"50":1},"2":{"15":1,"16":1,"28":1,"40":1,"42":1,"50":3}}],["some",{"2":{"9":1}}],["summary",{"2":{"38":1}}],["summaries",{"2":{"16":1,"42":1}}],["supplied",{"2":{"31":1}}],["support",{"2":{"11":2}}],["submit",{"2":{"11":1}}],["submission",{"2":{"11":1}}],["subscribe",{"2":{"9":1}}],["surface",{"0":{"18":1},"2":{"6":2,"18":1,"22":1,"39":1}}],["surfaces",{"2":{"0":1}}],["sanitized",{"2":{"18":1,"28":1}}],["safe",{"2":{"8":1,"18":1,"30":1,"31":1,"43":1}}],["same",{"2":{"7":1}}],["signal",{"2":{"38":2,"44":1}}],["signals",{"0":{"21":1},"2":{"8":1,"18":1}}],["side",{"2":{"1":2,"41":1}}],["ssl",{"2":{"7":1}}],["storage",{"0":{"41":1}}],["store",{"2":{"1":1}}],["stores",{"2":{"1":1,"2":1,"13":1,"41":1}}],["still",{"2":{"39":1}}],["sticky",{"2":{"8":1,"18":1}}],["style",{"2":{"39":1}}],["stricter",{"2":{"52":1}}],["strict",{"2":{"44":1}}],["streamable",{"2":{"39":1}}],["structured",{"2":{"16":1,"37":1}}],["steps",{"2":{"20":1}}],["step",{"2":{"19":1}}],["stdio",{"2":{"13":2,"14":1,"22":1,"24":1,"25":1,"26":1,"28":1}}],["start",{"2":{"34":1}}],["starts",{"2":{"1":1}}],["stale",{"0":{"53":1},"2":{"21":1,"33":1}}],["stay",{"2":{"20":1,"28":1}}],["stats",{"2":{"35":1}}],["static",{"0":{"5":1},"2":{"5":1,"7":1}}],["status",{"2":{"3":1,"13":1,"27":1,"38":1,"48":1}}],["state",{"0":{"3":1},"2":{"10":1,"15":1,"21":1,"31":1,"38":1}}],["see",{"2":{"50":1}}],["security",{"0":{"40":1},"1":{"41":1,"42":1,"43":1,"44":1,"45":1}}],["secrets",{"2":{"38":1}}],["secret",{"2":{"7":2}}],["segments",{"2":{"38":1}}],["service",{"2":{"38":1}}],["serves",{"2":{"7":1}}],["servers",{"2":{"24":1}}],["server",{"2":{"1":2,"41":1}}],["sends",{"2":{"16":1,"42":1}}],["selectable",{"2":{"9":1}}],["select",{"2":{"9":1,"10":1}}],["set",{"2":{"7":1}}],["setup",{"0":{"6":1},"1":{"7":1,"8":1,"9":1,"10":1,"11":1},"2":{"11":2}}],["sessions",{"2":{"49":1}}],["session",{"0":{"49":1},"2":{"1":3,"2":2,"4":1,"13":1,"22":1,"41":1}}],["shunset",{"2":{"50":1}}],["shaming",{"2":{"43":1}}],["shame",{"2":{"19":1}}],["shexport",{"2":{"34":1,"51":1}}],["shell",{"2":{"23":1,"47":1}}],["shgit",{"2":{"14":1}}],["shgittensory",{"2":{"1":1,"2":1,"3":1,"4":1,"15":1,"23":1,"31":2,"32":1,"47":1,"48":1,"49":1}}],["shnpm",{"2":{"13":1,"47":1}}],["shcurl",{"2":{"10":2}}],["shown",{"2":{"9":1}}],["shows",{"2":{"9":1,"31":1}}],["should",{"2":{"5":1,"9":1,"10":2,"30":2}}],["short",{"2":{"1":1,"13":1,"49":1}}],["right",{"2":{"30":1}}],["risk",{"2":{"20":1,"28":1,"30":1,"31":1,"33":1,"43":1}}],["rules",{"0":{"28":1}}],["runtime",{"0":{"28":1}}],["runs",{"2":{"18":1,"22":1}}],["run",{"2":{"1":1,"8":1,"10":1,"15":1,"18":1,"31":1,"49":1}}],["rate",{"0":{"44":1,"52":1},"2":{"38":1,"44":1}}],["ranked",{"2":{"31":1}}],["rank",{"2":{"27":1}}],["raw",{"2":{"20":1,"28":1,"43":1}}],["routes",{"2":{"44":1,"52":2}}],["route",{"2":{"44":1}}],["role",{"2":{"18":1,"31":1}}],["rows",{"2":{"9":1}}],["rebuild",{"2":{"53":1}}],["rejects",{"2":{"50":1}}],["rejected",{"2":{"16":1}}],["rewrite",{"2":{"45":1}}],["reward",{"2":{"20":1,"28":1,"31":1,"43":2}}],["retry",{"2":{"44":1,"47":1,"52":2,"53":1}}],["returns",{"2":{"52":1,"53":1}}],["return",{"2":{"44":1}}],["records",{"2":{"38":1}}],["recommendations",{"2":{"33":1}}],["registered",{"2":{"36":1}}],["registry",{"2":{"27":1}}],["rest",{"2":{"34":1}}],["responses",{"2":{"43":1,"44":1}}],["response",{"2":{"31":1}}],["reasoning",{"2":{"28":1,"31":1,"43":1}}],["readiness",{"0":{"11":1},"2":{"10":2,"21":1,"38":1}}],["reads",{"2":{"8":1}}],["read",{"2":{"8":4,"44":1,"52":1}}],["redirect",{"2":{"19":1}}],["reference",{"0":{"34":1},"1":{"35":1,"36":1,"37":1,"38":1,"39":1}}],["references",{"2":{"16":1,"42":1}}],["refs",{"2":{"16":1,"42":1}}],["refresh",{"2":{"10":2,"44":2,"51":1,"53":1}}],["reinstall",{"2":{"10":1}}],["reported",{"2":{"51":1,"52":1}}],["report",{"2":{"18":1}}],["repo",{"0":{"21":1,"36":1},"2":{"18":1,"21":2,"27":2,"30":2,"31":1,"33":1,"35":2,"36":4,"38":1,"42":1}}],["repos",{"2":{"10":1,"33":1,"35":1,"36":5}}],["repository",{"0":{"8":1},"2":{"8":3,"9":1,"16":1,"36":2}}],["repositories",{"2":{"0":1,"36":1}}],["repair",{"0":{"10":1}}],["related",{"2":{"9":1}}],["reviewable",{"2":{"30":1}}],["reviewability",{"0":{"19":1},"2":{"8":1,"18":1,"36":2}}],["review",{"2":{"19":1,"20":1,"33":1,"36":1}}],["revokes",{"2":{"4":1}}],["request",{"2":{"9":1}}],["requests",{"2":{"8":1,"39":1}}],["required",{"0":{"8":1,"9":1},"2":{"8":4}}],["remediation",{"2":{"38":1}}],["remain",{"2":{"5":1}}],["remove",{"2":{"10":1,"50":1}}],["removes",{"2":{"4":1}}],["remote",{"2":{"4":1,"39":1}}],["after",{"2":{"52":2,"53":1}}],["advisory",{"2":{"45":1}}],["additions",{"2":{"42":1}}],["add",{"2":{"11":1}}],["available",{"2":{"35":1}}],["avoid",{"2":{"33":1}}],["am",{"2":{"30":1}}],["args",{"2":{"24":1,"25":1,"26":1}}],["are",{"2":{"8":1,"9":2,"11":1,"18":1,"28":1,"42":1,"43":1,"49":1}}],["absolute",{"2":{"23":1,"47":1}}],["about",{"2":{"10":1}}],["agents",{"2":{"22":1}}],["analysis",{"0":{"31":1},"2":{"37":2,"42":1,"44":1,"52":1}}],["analyze",{"2":{"15":1,"31":2}}],["answers",{"0":{"30":1}}],["an",{"2":{"23":1,"47":1}}],["any",{"2":{"13":1}}],["and",{"0":{"37":1,"40":1},"1":{"41":1,"42":1,"43":1,"44":1,"45":1},"2":{"1":2,"4":1,"5":1,"7":1,"8":4,"10":1,"11":2,"12":1,"13":1,"15":1,"16":2,"18":3,"22":1,"27":1,"28":2,"31":2,"33":2,"34":1,"35":1,"36":2,"38":3,"41":1,"42":2,"43":3,"44":1,"51":1}}],["attention",{"0":{"51":1},"2":{"10":1}}],["always",{"2":{"9":1}}],["actually",{"2":{"33":1}}],["action",{"2":{"36":1}}],["actionable",{"2":{"33":1}}],["actions",{"0":{"19":1},"2":{"19":1,"27":1,"31":1}}],["active",{"2":{"7":1,"32":1}}],["account",{"2":{"31":1}}],["access",{"2":{"8":2}}],["assumptions",{"2":{"31":1,"33":1}}],["as",{"2":{"7":1,"9":1,"31":1}}],["asks",{"2":{"1":1}}],["apis",{"0":{"35":1,"36":1,"37":1,"38":1},"2":{"44":1}}],["api",{"0":{"34":1},"1":{"35":1,"36":1,"37":1,"38":1,"39":1},"2":{"7":3,"10":3,"15":1,"22":1,"28":1,"34":5,"43":1,"51":4}}],["approved",{"2":{"31":1}}],["approve",{"2":{"1":1,"10":1,"51":1}}],["app",{"0":{"6":1,"18":1,"45":1,"51":1},"1":{"7":1,"8":1,"9":1,"10":1,"11":1},"2":{"0":1,"6":1,"8":1,"9":1,"10":4,"18":1,"21":1,"38":1,"45":1,"48":2,"51":1}}],["aethereal",{"2":{"7":1}}],["auto",{"2":{"45":1}}],["automation",{"2":{"2":1}}],["authenticated",{"2":{"34":1,"43":1}}],["author",{"2":{"19":1}}],["authorization",{"2":{"10":2,"34":1,"51":1}}],["auth",{"0":{"0":1,"1":1,"3":1},"1":{"1":1,"2":1,"3":1,"4":1,"5":1},"2":{"0":2,"2":1,"6":1,"15":1,"34":1,"44":1}}],["a",{"2":{"1":2,"2":2,"7":1,"8":1,"13":1,"23":1,"30":4,"31":2,"32":3,"33":1,"41":1,"50":1,"52":1,"53":1}}]],"serializationVersion":2}';export{e as default}; diff --git a/docs/assets/chunks/VPLocalSearchBox.DIqEGPXK.js b/docs/assets/chunks/VPLocalSearchBox.DIqEGPXK.js deleted file mode 100644 index 793568d099..0000000000 --- a/docs/assets/chunks/VPLocalSearchBox.DIqEGPXK.js +++ /dev/null @@ -1,3 +0,0 @@ -import{_ as _t,au as Le,d as Se,a4 as Tt,a7 as qe,ab as Et,K as It,a9 as kt,a2 as ne,r as Nt,e as He,X as ue,an as Ft,ag as Rt,aw as Ot,av as At,O as Ne,H as de,N as me,al as Ct,af as Mt,am as Lt,v as Dt,L as Pt,S as B,h as zt,g as k,aC as Vt,aa as C,aA as jt,at as $t,B as Bt,j as q,I as Ge,i as ge,Y as Qe,F as Ye,n as fe,a5 as he,T as Wt,D as Ze,f as Kt,p as Jt,U as Ut,s as qt,c as Ht}from"./framework.BXgR76oC.js";import{u as lt}from"./theme.BfZ7hXNa.js";const Gt={root:()=>_t(()=>import("./@localSearchIndexroot.DQCfcpak.js"),[])};var ct=["input:not([inert]):not([inert] *)","select:not([inert]):not([inert] *)","textarea:not([inert]):not([inert] *)","a[href]:not([inert]):not([inert] *)","button:not([inert]):not([inert] *)","[tabindex]:not(slot):not([inert]):not([inert] *)","audio[controls]:not([inert]):not([inert] *)","video[controls]:not([inert]):not([inert] *)",'[contenteditable]:not([contenteditable="false"]):not([inert]):not([inert] *)',"details>summary:first-of-type:not([inert]):not([inert] *)","details:not([inert]):not([inert] *)"],xe=ct.join(","),ut=typeof Element>"u",Z=ut?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,_e=!ut&&Element.prototype.getRootNode?function(a){var e;return a==null||(e=a.getRootNode)===null||e===void 0?void 0:e.call(a)}:function(a){return a?.ownerDocument},Te=function(e,t){var n;t===void 0&&(t=!0);var s=e==null||(n=e.getAttribute)===null||n===void 0?void 0:n.call(e,"inert"),i=s===""||s==="true",r=i||t&&e&&(typeof e.closest=="function"?e.closest("[inert]"):Te(e.parentNode));return r},Qt=function(e){var t,n=e==null||(t=e.getAttribute)===null||t===void 0?void 0:t.call(e,"contenteditable");return n===""||n==="true"},dt=function(e,t,n){if(Te(e))return[];var s=Array.prototype.slice.apply(e.querySelectorAll(xe));return t&&Z.call(e,xe)&&s.unshift(e),s=s.filter(n),s},Ee=function(e,t,n){for(var s=[],i=Array.from(e);i.length;){var r=i.shift();if(!Te(r,!1))if(r.tagName==="SLOT"){var o=r.assignedElements(),l=o.length?o:r.children,c=Ee(l,!0,n);n.flatten?s.push.apply(s,c):s.push({scopeParent:r,candidates:c})}else{var f=Z.call(r,xe);f&&n.filter(r)&&(t||!e.includes(r))&&s.push(r);var v=r.shadowRoot||typeof n.getShadowRoot=="function"&&n.getShadowRoot(r),h=!Te(v,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(r));if(v&&h){var y=Ee(v===!0?r.children:v.children,!0,n);n.flatten?s.push.apply(s,y):s.push({scopeParent:r,candidates:y})}else i.unshift.apply(i,r.children)}}return s},ft=function(e){return!isNaN(parseInt(e.getAttribute("tabindex"),10))},Y=function(e){if(!e)throw new Error("No node provided");return e.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(e.tagName)||Qt(e))&&!ft(e)?0:e.tabIndex},Yt=function(e,t){var n=Y(e);return n<0&&t&&!ft(e)?0:n},Zt=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},ht=function(e){return e.tagName==="INPUT"},Xt=function(e){return ht(e)&&e.type==="hidden"},en=function(e){var t=e.tagName==="DETAILS"&&Array.prototype.slice.apply(e.children).some(function(n){return n.tagName==="SUMMARY"});return t},tn=function(e,t){for(var n=0;nsummary:first-of-type"),o=r?e.parentElement:e;if(Z.call(o,"details:not([open]) *"))return!0;if(!n||n==="full"||n==="full-native"||n==="legacy-full"){if(typeof s=="function"){for(var l=e;e;){var c=e.parentElement,f=_e(e);if(c&&!c.shadowRoot&&s(c)===!0)return Xe(e);e.assignedSlot?e=e.assignedSlot:!c&&f!==e.ownerDocument?e=f.host:e=c}e=l}if(an(e))return!e.getClientRects().length;if(n!=="legacy-full")return!0}else if(n==="non-zero-area")return Xe(e);return!1},ln=function(e){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(e.tagName))for(var t=e.parentElement;t;){if(t.tagName==="FIELDSET"&&t.disabled){for(var n=0;n=0)},pt=function(e){var t=[],n=[];return e.forEach(function(s,i){var r=!!s.scopeParent,o=r?s.scopeParent:s,l=Yt(o,r),c=r?pt(s.candidates):o;l===0?r?t.push.apply(t,c):t.push(o):n.push({documentOrder:i,tabIndex:l,item:s,isScope:r,content:c})}),n.sort(Zt).reduce(function(s,i){return i.isScope?s.push.apply(s,i.content):s.push(i.content),s},[]).concat(t)},un=function(e,t){t=t||{};var n;return t.getShadowRoot?n=Ee([e],t.includeContainer,{filter:De.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot,shadowRootFilter:cn}):n=dt(e,t.includeContainer,De.bind(null,t)),pt(n)},dn=function(e,t){t=t||{};var n;return t.getShadowRoot?n=Ee([e],t.includeContainer,{filter:Ie.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):n=dt(e,t.includeContainer,Ie.bind(null,t)),n},ee=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return Z.call(e,xe)===!1?!1:De(t,e)},fn=ct.concat("iframe:not([inert]):not([inert] *)").join(","),Fe=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return Z.call(e,fn)===!1?!1:Ie(t,e)};function Pe(a,e){(e==null||e>a.length)&&(e=a.length);for(var t=0,n=Array(e);t=a.length?{done:!0}:{done:!1,value:a[n++]}},e:function(l){throw l},f:s}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. -In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var i,r=!0,o=!1;return{s:function(){t=t.call(a)},n:function(){var l=t.next();return r=l.done,l},e:function(l){o=!0,i=l},f:function(){try{r||t.return==null||t.return()}finally{if(o)throw i}}}}function pn(a,e,t){return(e=yn(e))in a?Object.defineProperty(a,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):a[e]=t,a}function vn(a){if(typeof Symbol<"u"&&a[Symbol.iterator]!=null||a["@@iterator"]!=null)return Array.from(a)}function mn(){throw new TypeError(`Invalid attempt to spread non-iterable instance. -In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function tt(a,e){var t=Object.keys(a);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(a);e&&(n=n.filter(function(s){return Object.getOwnPropertyDescriptor(a,s).enumerable})),t.push.apply(t,n)}return t}function nt(a){for(var e=1;e0?e[e.length-1]:null},activateTrap:function(e,t){var n=W.getActiveTrap(e);t!==n&&W.pauseTrap(e);var s=e.indexOf(t);s===-1||e.splice(s,1),e.push(t)},deactivateTrap:function(e,t){var n=e.indexOf(t);n!==-1&&e.splice(n,1),W.unpauseTrap(e)},pauseTrap:function(e){var t=W.getActiveTrap(e);t?._setPausedState(!0)},unpauseTrap:function(e){var t=W.getActiveTrap(e);t&&!t._isManuallyPaused()&&t._setPausedState(!1)}},wn=function(e){return e.tagName&&e.tagName.toLowerCase()==="input"&&typeof e.select=="function"},Sn=function(e){return e?.key==="Escape"||e?.key==="Esc"||e?.keyCode===27},ve=function(e){return e?.key==="Tab"||e?.keyCode===9},xn=function(e){return ve(e)&&!e.shiftKey},_n=function(e){return ve(e)&&e.shiftKey},rt=function(e){return setTimeout(e,0)},pe=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),s=1;s1&&arguments[1]!==void 0?arguments[1]:{},b=d.hasFallback,I=b===void 0?!1:b,m=d.params,p=m===void 0?[]:m,g=i[u];if(typeof g=="function"&&(g=g.apply(void 0,gn(p))),g===!0&&(g=void 0),!g){if(g===void 0||g===!1)return g;throw new Error("`".concat(u,"` was specified but was not a node, or did not return a node"))}var _=g;if(typeof g=="string"){try{_=n.querySelector(g)}catch(x){throw new Error("`".concat(u,'` appears to be an invalid selector; error="').concat(x.message,'"'))}if(!_&&!I)throw new Error("`".concat(u,"` as selector refers to no known node"))}return _},v=function(u){var d=u.activeElement;return d?d.shadowRoot&&d.shadowRoot.activeElement!==null?v(d.shadowRoot):d:null},h=function(){var u=f("initialFocus",{hasFallback:!0});if(u===!1)return!1;if(u===void 0||u&&!Fe(u,i.tabbableOptions)){var d=v(n);if(c(d)>=0)u=d;else{var b=r.tabbableGroups[0],I=b&&b.firstTabbableNode;u=I||f("fallbackFocus")}}else u===null&&(u=f("fallbackFocus"));if(!u)throw new Error("Your focus-trap needs to have at least one focusable element");return u},y=function(){if(r.containerGroups=r.containers.map(function(u){var d=un(u,i.tabbableOptions),b=dn(u,i.tabbableOptions),I=d.length>0?d[0]:void 0,m=d.length>0?d[d.length-1]:void 0,p=b.find(function(x){return ee(x)}),g=b.slice().reverse().find(function(x){return ee(x)}),_=!!d.find(function(x){return Y(x)>0});return{container:u,tabbableNodes:d,focusableNodes:b,posTabIndexesFound:_,firstTabbableNode:I,lastTabbableNode:m,firstDomTabbableNode:p,lastDomTabbableNode:g,nextTabbableNode:function(F){var D=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,M=d.indexOf(F);return M<0?D?b.slice(b.indexOf(F)+1).find(function(j){return ee(j)}):b.slice(0,b.indexOf(F)).reverse().find(function(j){return ee(j)}):d[M+(D?1:-1)]}}}),r.tabbableGroups=r.containerGroups.filter(function(u){return u.tabbableNodes.length>0}),r.tabbableGroups.length<=0&&!f("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(r.containerGroups.find(function(u){return u.posTabIndexesFound})&&r.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},S=function(u){if(u!==!1&&u!==v(document)){if(!u||!u.focus){S(h());return}u.focus({preventScroll:!!i.preventScroll}),r.mostRecentlyFocusedNode=u,wn(u)&&u.select()}},w=function(u){var d=f("setReturnFocus",{params:[u]});return d||(d===!1?!1:u)},T=function(u){var d=u.target,b=u.event,I=u.isBackward,m=I===void 0?!1:I;d=d||be(b),y();var p=null;if(r.tabbableGroups.length>0){var g=c(d,b),_=g>=0?r.containerGroups[g]:void 0;if(g<0)m?p=r.tabbableGroups[r.tabbableGroups.length-1].lastTabbableNode:p=r.tabbableGroups[0].firstTabbableNode;else if(m){var x=r.tabbableGroups.findIndex(function(J){var U=J.firstTabbableNode;return d===U});if(x<0&&(_.container===d||Fe(d,i.tabbableOptions)&&!ee(d,i.tabbableOptions)&&!_.nextTabbableNode(d,!1))&&(x=g),x>=0){var F=x===0?r.tabbableGroups.length-1:x-1,D=r.tabbableGroups[F];p=Y(d)>=0?D.lastTabbableNode:D.lastDomTabbableNode}else ve(b)||(p=_.nextTabbableNode(d,!1))}else{var M=r.tabbableGroups.findIndex(function(J){var U=J.lastTabbableNode;return d===U});if(M<0&&(_.container===d||Fe(d,i.tabbableOptions)&&!ee(d,i.tabbableOptions)&&!_.nextTabbableNode(d))&&(M=g),M>=0){var j=M===r.tabbableGroups.length-1?0:M+1,O=r.tabbableGroups[j];p=Y(d)>=0?O.firstTabbableNode:O.firstDomTabbableNode}else ve(b)||(p=_.nextTabbableNode(d))}}else p=f("fallbackFocus");return p},N=function(u){var d=be(u);if(!(c(d,u)>=0)){if(pe(i.clickOutsideDeactivates,u)){o.deactivate({returnFocus:i.returnFocusOnDeactivate});return}pe(i.allowOutsideClick,u)||u.preventDefault()}},R=function(u){var d=be(u),b=c(d,u)>=0;if(b||d instanceof Document)b&&(r.mostRecentlyFocusedNode=d);else{u.stopImmediatePropagation();var I,m=!0;if(r.mostRecentlyFocusedNode)if(Y(r.mostRecentlyFocusedNode)>0){var p=c(r.mostRecentlyFocusedNode),g=r.containerGroups[p].tabbableNodes;if(g.length>0){var _=g.findIndex(function(x){return x===r.mostRecentlyFocusedNode});_>=0&&(i.isKeyForward(r.recentNavEvent)?_+1=0&&(I=g[_-1],m=!1))}}else r.containerGroups.some(function(x){return x.tabbableNodes.some(function(F){return Y(F)>0})})||(m=!1);else m=!1;m&&(I=T({target:r.mostRecentlyFocusedNode,isBackward:i.isKeyBackward(r.recentNavEvent)})),S(I||r.mostRecentlyFocusedNode||h())}r.recentNavEvent=void 0},G=function(u){var d=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;r.recentNavEvent=u;var b=T({event:u,isBackward:d});b&&(ve(u)&&u.preventDefault(),S(b))},z=function(u){(i.isKeyForward(u)||i.isKeyBackward(u))&&G(u,i.isKeyBackward(u))},$=function(u){Sn(u)&&pe(i.escapeDeactivates,u)!==!1&&(u.preventDefault(),o.deactivate())},P=function(u){var d=be(u);c(d,u)>=0||pe(i.clickOutsideDeactivates,u)||pe(i.allowOutsideClick,u)||(u.preventDefault(),u.stopImmediatePropagation())},V=function(){if(r.active){W.activateTrap(s,o);var u;return i.delayInitialFocus?u=new Promise(function(d){r.delayInitialFocusTimer=rt(function(){S(h()),d()})}):S(h()),n.addEventListener("focusin",R,!0),n.addEventListener("mousedown",N,{capture:!0,passive:!1}),n.addEventListener("touchstart",N,{capture:!0,passive:!1}),n.addEventListener("click",P,{capture:!0,passive:!1}),n.addEventListener("keydown",z,{capture:!0,passive:!1}),n.addEventListener("keydown",$),u}},A=function(u){r.active&&!r.paused&&o._setSubtreeIsolation(!1),r.adjacentElements.clear(),r.alreadySilent.clear();var d=new Set,b=new Set,I=et(u),m;try{for(I.s();!(m=I.n()).done;){var p=m.value;d.add(p);for(var g=typeof ShadowRoot<"u"&&p.getRootNode()instanceof ShadowRoot,_=p;_;){d.add(_);var x=_.parentElement,F=[];x?F=x.children:!x&&g&&(F=_.getRootNode().children,x=_.getRootNode().host,g=typeof ShadowRoot<"u"&&x.getRootNode()instanceof ShadowRoot);var D=et(F),M;try{for(D.s();!(M=D.n()).done;){var j=M.value;b.add(j)}}catch(O){D.e(O)}finally{D.f()}_=x}}}catch(O){I.e(O)}finally{I.f()}d.forEach(function(O){b.delete(O)}),r.adjacentElements=b},K=function(){if(r.active)return n.removeEventListener("focusin",R,!0),n.removeEventListener("mousedown",N,!0),n.removeEventListener("touchstart",N,!0),n.removeEventListener("click",P,!0),n.removeEventListener("keydown",z,!0),n.removeEventListener("keydown",$),o},ie=function(u){var d=r.mostRecentlyFocusedNode;if(d){var b=u.some(function(m){var p=Array.from(m.removedNodes);return p.some(function(g){return g===d||typeof g.contains=="function"&&g.contains(d)})});if(b&&r.containers.some(function(m){return m?.isConnected})){y();var I=h();S(I)}}},ae=typeof window<"u"&&"MutationObserver"in window?new MutationObserver(ie):void 0,X=function(){ae&&(ae.disconnect(),r.active&&!r.paused&&r.containers.map(function(u){ae.observe(u,{subtree:!0,childList:!0})}))};return o={get active(){return r.active},get paused(){return r.paused},activate:function(u){if(r.active)return this;var d=l(u,"onActivate"),b=l(u,"onPostActivate"),I=l(u,"checkCanFocusTrap"),m=W.getActiveTrap(s),p=!1;if(m&&!m.paused){var g;(g=m._setSubtreeIsolation)===null||g===void 0||g.call(m,!1),p=!0}try{I||y(),r.active=!0,r.paused=!1,r.nodeFocusedBeforeActivation=v(n),d?.({trap:o});var _=function(){I&&y();var D=function(){o._setSubtreeIsolation(!0),X(),b?.({trap:o})},M=V();M?M.then(D):D()};if(I)return I(r.containers.concat()).then(_,_),this;_()}catch(F){if(m===W.getActiveTrap(s)&&p){var x;(x=m._setSubtreeIsolation)===null||x===void 0||x.call(m,!0)}throw F}return this},deactivate:function(u){if(!r.active)return this;var d=nt({onDeactivate:i.onDeactivate,onPostDeactivate:i.onPostDeactivate,checkCanReturnFocus:i.checkCanReturnFocus},u);clearTimeout(r.delayInitialFocusTimer),r.delayInitialFocusTimer=void 0,r.paused||o._setSubtreeIsolation(!1),r.alreadySilent.clear(),K(),r.active=!1,r.paused=!1,X(),W.deactivateTrap(s,o);var b=l(d,"onDeactivate"),I=l(d,"onPostDeactivate"),m=l(d,"checkCanReturnFocus"),p=l(d,"delayReturnFocus"),g=l(d,"returnFocus","returnFocusOnDeactivate");b?.({trap:o});var _=function(){g&&S(w(r.nodeFocusedBeforeActivation)),I?.({trap:o})},x=function(){p&&g?rt(_):_()};return g&&m?(m(w(r.nodeFocusedBeforeActivation)).then(x,x),this):(x(),this)},pause:function(u){return r.active?(r.manuallyPaused=!0,this._setPausedState(!0,u)):this},unpause:function(u){return r.active?(r.manuallyPaused=!1,s[s.length-1]!==this?this:this._setPausedState(!1,u)):this},updateContainerElements:function(u){var d=[].concat(u).filter(Boolean);return r.containers=d.map(function(b){return typeof b=="string"?n.querySelector(b):b}),i.isolateSubtrees&&A(r.containers),r.active&&(y(),r.paused||o._setSubtreeIsolation(!0)),X(),this}},Object.defineProperties(o,{_isManuallyPaused:{value:function(){return r.manuallyPaused}},_setPausedState:{value:function(u,d){if(r.paused===u)return this;if(r.paused=u,u){var b=l(d,"onPause"),I=l(d,"onPostPause");b?.({trap:o}),K(),o._setSubtreeIsolation(!1),X(),I?.({trap:o})}else{var m=l(d,"onUnpause"),p=l(d,"onPostUnpause");m?.({trap:o});var g=function(){y();var x=function(){o._setSubtreeIsolation(!0),X(),p?.({trap:o})},F=V();F?F.then(x):x()};g()}return this}},_setSubtreeIsolation:{value:function(u){i.isolateSubtrees&&r.adjacentElements.forEach(function(d){var b;if(u)switch(i.isolateSubtrees){case"aria-hidden":(d.ariaHidden==="true"||((b=d.getAttribute("aria-hidden"))===null||b===void 0?void 0:b.toLowerCase())==="true")&&r.alreadySilent.add(d),d.setAttribute("aria-hidden","true");break;default:(d.inert||d.hasAttribute("inert"))&&r.alreadySilent.add(d),d.setAttribute("inert",!0);break}else if(!r.alreadySilent.has(d))switch(i.isolateSubtrees){case"aria-hidden":d.removeAttribute("aria-hidden");break;default:d.removeAttribute("inert");break}})}}}),o.updateContainerElements(e),o};function In(a,e={}){let t;const{immediate:n,...s}=e,i=ne(!1),r=ne(!1),o=v=>t&&t.activate(v),l=v=>t&&t.deactivate(v),c=()=>{t&&(t.pause(),r.value=!0)},f=()=>{t&&(t.unpause(),r.value=!1)};return Le(Se(()=>Tt(qe(a)).map(v=>{const h=qe(v);return typeof h=="string"?h:Et(h)}).filter(It)),v=>{if(v.length)if(!t)t=En(v,{...s,onActivate(h){i.value=!0,e.onActivate&&e.onActivate(h)},onDeactivate(h){i.value=!1,e.onDeactivate&&e.onDeactivate(h)}}),n&&o();else{const h=t?.active;t?.updateContainerElements(v),!h&&n&&o()}},{flush:"post"}),kt(()=>l()),{hasFocus:i,isPaused:r,activate:o,deactivate:l,pause:c,unpause:f}}class re{constructor(e,t=!0,n=[],s=5e3){this.ctx=e,this.iframes=t,this.exclude=n,this.iframesTimeout=s}static matches(e,t){const n=typeof t=="string"?[t]:t,s=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(s){let i=!1;return n.every(r=>s.call(e,r)?(i=!0,!1):!0),i}else return!1}getContexts(){let e,t=[];return typeof this.ctx>"u"||!this.ctx?e=[]:NodeList.prototype.isPrototypeOf(this.ctx)?e=Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?e=this.ctx:typeof this.ctx=="string"?e=Array.prototype.slice.call(document.querySelectorAll(this.ctx)):e=[this.ctx],e.forEach(n=>{const s=t.filter(i=>i.contains(n)).length>0;t.indexOf(n)===-1&&!s&&t.push(n)}),t}getIframeContents(e,t,n=()=>{}){let s;try{const i=e.contentWindow;if(s=i.document,!i||!s)throw new Error("iframe inaccessible")}catch{n()}s&&t(s)}isIframeBlank(e){const t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}observeIframeLoad(e,t,n){let s=!1,i=null;const r=()=>{if(!s){s=!0,clearTimeout(i);try{this.isIframeBlank(e)||(e.removeEventListener("load",r),this.getIframeContents(e,t,n))}catch{n()}}};e.addEventListener("load",r),i=setTimeout(r,this.iframesTimeout)}onIframeReady(e,t,n){try{e.contentWindow.document.readyState==="complete"?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch{n()}}waitForIframes(e,t){let n=0;this.forEachIframe(e,()=>!0,s=>{n++,this.waitForIframes(s.querySelector("html"),()=>{--n||t()})},s=>{s||t()})}forEachIframe(e,t,n,s=()=>{}){let i=e.querySelectorAll("iframe"),r=i.length,o=0;i=Array.prototype.slice.call(i);const l=()=>{--r<=0&&s(o)};r||l(),i.forEach(c=>{re.matches(c,this.exclude)?l():this.onIframeReady(c,f=>{t(c)&&(o++,n(f)),l()},l)})}createIterator(e,t,n){return document.createNodeIterator(e,t,n,!1)}createInstanceOnIframe(e){return new re(e.querySelector("html"),this.iframes)}compareNodeIframe(e,t,n){const s=e.compareDocumentPosition(n),i=Node.DOCUMENT_POSITION_PRECEDING;if(s&i)if(t!==null){const r=t.compareDocumentPosition(n),o=Node.DOCUMENT_POSITION_FOLLOWING;if(r&o)return!0}else return!0;return!1}getIteratorNode(e){const t=e.previousNode();let n;return t===null?n=e.nextNode():n=e.nextNode()&&e.nextNode(),{prevNode:t,node:n}}checkIframeFilter(e,t,n,s){let i=!1,r=!1;return s.forEach((o,l)=>{o.val===n&&(i=l,r=o.handled)}),this.compareNodeIframe(e,t,n)?(i===!1&&!r?s.push({val:n,handled:!0}):i!==!1&&!r&&(s[i].handled=!0),!0):(i===!1&&s.push({val:n,handled:!1}),!1)}handleOpenIframes(e,t,n,s){e.forEach(i=>{i.handled||this.getIframeContents(i.val,r=>{this.createInstanceOnIframe(r).forEachNode(t,n,s)})})}iterateThroughNodes(e,t,n,s,i){const r=this.createIterator(t,e,s);let o=[],l=[],c,f,v=()=>({prevNode:f,node:c}=this.getIteratorNode(r),c);for(;v();)this.iframes&&this.forEachIframe(t,h=>this.checkIframeFilter(c,f,h,o),h=>{this.createInstanceOnIframe(h).forEachNode(e,y=>l.push(y),s)}),l.push(c);l.forEach(h=>{n(h)}),this.iframes&&this.handleOpenIframes(o,e,n,s),i()}forEachNode(e,t,n,s=()=>{}){const i=this.getContexts();let r=i.length;r||s(),i.forEach(o=>{const l=()=>{this.iterateThroughNodes(e,o,t,n,()=>{--r<=0&&s()})};this.iframes?this.waitForIframes(o,l):l()})}}let kn=class{constructor(e){this.ctx=e,this.ie=!1;const t=window.navigator.userAgent;(t.indexOf("MSIE")>-1||t.indexOf("Trident")>-1)&&(this.ie=!0)}set opt(e){this._opt=Object.assign({},{element:"",className:"",exclude:[],iframes:!1,iframesTimeout:5e3,separateWordSearch:!0,diacritics:!0,synonyms:{},accuracy:"partially",acrossElements:!1,caseSensitive:!1,ignoreJoiners:!1,ignoreGroups:0,ignorePunctuation:[],wildcards:"disabled",each:()=>{},noMatch:()=>{},filter:()=>!0,done:()=>{},debug:!1,log:window.console},e)}get opt(){return this._opt}get iterator(){return new re(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(e,t="debug"){const n=this.opt.log;this.opt.debug&&typeof n=="object"&&typeof n[t]=="function"&&n[t](`mark.js: ${e}`)}escapeStr(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}createRegExp(e){return this.opt.wildcards!=="disabled"&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),this.opt.wildcards!=="disabled"&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),e}createSynonymsRegExp(e){const t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",s=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(let i in t)if(t.hasOwnProperty(i)){const r=t[i],o=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(i):this.escapeStr(i),l=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(r):this.escapeStr(r);o!==""&&l!==""&&(e=e.replace(new RegExp(`(${this.escapeStr(o)}|${this.escapeStr(l)})`,`gm${n}`),s+`(${this.processSynomyms(o)}|${this.processSynomyms(l)})`+s))}return e}processSynomyms(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}setupWildcardsRegExp(e){return e=e.replace(/(?:\\)*\?/g,t=>t.charAt(0)==="\\"?"?":""),e.replace(/(?:\\)*\*/g,t=>t.charAt(0)==="\\"?"*":"")}createWildcardsRegExp(e){let t=this.opt.wildcards==="withSpaces";return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}setupIgnoreJoinersRegExp(e){return e.replace(/[^(|)\\]/g,(t,n,s)=>{let i=s.charAt(n+1);return/[(|)\\]/.test(i)||i===""?t:t+"\0"})}createJoinersRegExp(e){let t=[];const n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join(`[${t.join("")}]*`):e}createDiacriticsRegExp(e){const t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"];let s=[];return e.split("").forEach(i=>{n.every(r=>{if(r.indexOf(i)!==-1){if(s.indexOf(r)>-1)return!1;e=e.replace(new RegExp(`[${r}]`,`gm${t}`),`[${r}]`),s.push(r)}return!0})}),e}createMergedBlanksRegExp(e){return e.replace(/[\s]+/gmi,"[\\s]+")}createAccuracyRegExp(e){const t="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿";let n=this.opt.accuracy,s=typeof n=="string"?n:n.value,i=typeof n=="string"?[]:n.limiters,r="";switch(i.forEach(o=>{r+=`|${this.escapeStr(o)}`}),s){case"partially":default:return`()(${e})`;case"complementary":return r="\\s"+(r||this.escapeStr(t)),`()([^${r}]*${e}[^${r}]*)`;case"exactly":return`(^|\\s${r})(${e})(?=$|\\s${r})`}}getSeparatedKeywords(e){let t=[];return e.forEach(n=>{this.opt.separateWordSearch?n.split(" ").forEach(s=>{s.trim()&&t.indexOf(s)===-1&&t.push(s)}):n.trim()&&t.indexOf(n)===-1&&t.push(n)}),{keywords:t.sort((n,s)=>s.length-n.length),length:t.length}}isNumeric(e){return Number(parseFloat(e))==e}checkRanges(e){if(!Array.isArray(e)||Object.prototype.toString.call(e[0])!=="[object Object]")return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];const t=[];let n=0;return e.sort((s,i)=>s.start-i.start).forEach(s=>{let{start:i,end:r,valid:o}=this.callNoMatchOnInvalidRanges(s,n);o&&(s.start=i,s.length=r-i,t.push(s),n=r)}),t}callNoMatchOnInvalidRanges(e,t){let n,s,i=!1;return e&&typeof e.start<"u"?(n=parseInt(e.start,10),s=n+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&s-t>0&&s-n>0?i=!0:(this.log(`Ignoring invalid or overlapping range: ${JSON.stringify(e)}`),this.opt.noMatch(e))):(this.log(`Ignoring invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)),{start:n,end:s,valid:i}}checkWhitespaceRanges(e,t,n){let s,i=!0,r=n.length,o=t-r,l=parseInt(e.start,10)-o;return l=l>r?r:l,s=l+parseInt(e.length,10),s>r&&(s=r,this.log(`End range automatically set to the max value of ${r}`)),l<0||s-l<0||l>r||s>r?(i=!1,this.log(`Invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)):n.substring(l,s).replace(/\s+/g,"")===""&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:l,end:s,valid:i}}getTextNodes(e){let t="",n=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,s=>{n.push({start:t.length,end:(t+=s.textContent).length,node:s})},s=>this.matchesExclude(s.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT,()=>{e({value:t,nodes:n})})}matchesExclude(e){return re.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapRangeInTextNode(e,t,n){const s=this.opt.element?this.opt.element:"mark",i=e.splitText(t),r=i.splitText(n-t);let o=document.createElement(s);return o.setAttribute("data-markjs","true"),this.opt.className&&o.setAttribute("class",this.opt.className),o.textContent=i.textContent,i.parentNode.replaceChild(o,i),r}wrapRangeInMappedTextNode(e,t,n,s,i){e.nodes.every((r,o)=>{const l=e.nodes[o+1];if(typeof l>"u"||l.start>t){if(!s(r.node))return!1;const c=t-r.start,f=(n>r.end?r.end:n)-r.start,v=e.value.substr(0,r.start),h=e.value.substr(f+r.start);if(r.node=this.wrapRangeInTextNode(r.node,c,f),e.value=v+h,e.nodes.forEach((y,S)=>{S>=o&&(e.nodes[S].start>0&&S!==o&&(e.nodes[S].start-=f),e.nodes[S].end-=f)}),n-=f,i(r.node.previousSibling,r.start),n>r.end)t=r.end;else return!1}return!0})}wrapMatches(e,t,n,s,i){const r=t===0?0:t+1;this.getTextNodes(o=>{o.nodes.forEach(l=>{l=l.node;let c;for(;(c=e.exec(l.textContent))!==null&&c[r]!=="";){if(!n(c[r],l))continue;let f=c.index;if(r!==0)for(let v=1;v{let l;for(;(l=e.exec(o.value))!==null&&l[r]!=="";){let c=l.index;if(r!==0)for(let v=1;vn(l[r],v),(v,h)=>{e.lastIndex=h,s(v)})}i()})}wrapRangeFromIndex(e,t,n,s){this.getTextNodes(i=>{const r=i.value.length;e.forEach((o,l)=>{let{start:c,end:f,valid:v}=this.checkWhitespaceRanges(o,r,i.value);v&&this.wrapRangeInMappedTextNode(i,c,f,h=>t(h,o,i.value.substring(c,f),l),h=>{n(h,o)})}),s()})}unwrapMatches(e){const t=e.parentNode;let n=document.createDocumentFragment();for(;e.firstChild;)n.appendChild(e.removeChild(e.firstChild));t.replaceChild(n,e),this.ie?this.normalizeTextNode(t):t.normalize()}normalizeTextNode(e){if(e){if(e.nodeType===3)for(;e.nextSibling&&e.nextSibling.nodeType===3;)e.nodeValue+=e.nextSibling.nodeValue,e.parentNode.removeChild(e.nextSibling);else this.normalizeTextNode(e.firstChild);this.normalizeTextNode(e.nextSibling)}}markRegExp(e,t){this.opt=t,this.log(`Searching with expression "${e}"`);let n=0,s="wrapMatches";const i=r=>{n++,this.opt.each(r)};this.opt.acrossElements&&(s="wrapMatchesAcrossElements"),this[s](e,this.opt.ignoreGroups,(r,o)=>this.opt.filter(o,r,n),i,()=>{n===0&&this.opt.noMatch(e),this.opt.done(n)})}mark(e,t){this.opt=t;let n=0,s="wrapMatches";const{keywords:i,length:r}=this.getSeparatedKeywords(typeof e=="string"?[e]:e),o=this.opt.caseSensitive?"":"i",l=c=>{let f=new RegExp(this.createRegExp(c),`gm${o}`),v=0;this.log(`Searching with expression "${f}"`),this[s](f,1,(h,y)=>this.opt.filter(y,c,n,v),h=>{v++,n++,this.opt.each(h)},()=>{v===0&&this.opt.noMatch(c),i[r-1]===c?this.opt.done(n):l(i[i.indexOf(c)+1])})};this.opt.acrossElements&&(s="wrapMatchesAcrossElements"),r===0?this.opt.done(n):l(i[0])}markRanges(e,t){this.opt=t;let n=0,s=this.checkRanges(e);s&&s.length?(this.log("Starting to mark with the following ranges: "+JSON.stringify(s)),this.wrapRangeFromIndex(s,(i,r,o,l)=>this.opt.filter(i,r,o,l),(i,r)=>{n++,this.opt.each(i,r)},()=>{this.opt.done(n)})):this.opt.done(n)}unmark(e){this.opt=e;let t=this.opt.element?this.opt.element:"*";t+="[data-markjs]",this.opt.className&&(t+=`.${this.opt.className}`),this.log(`Removal selector "${t}"`),this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,n=>{this.unwrapMatches(n)},n=>{const s=re.matches(n,t),i=this.matchesExclude(n);return!s||i?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},this.opt.done)}};function Nn(a){const e=new kn(a);return this.mark=(t,n)=>(e.mark(t,n),this),this.markRegExp=(t,n)=>(e.markRegExp(t,n),this),this.markRanges=(t,n)=>(e.markRanges(t,n),this),this.unmark=t=>(e.unmark(t),this),this}const Fn="ENTRIES",mt="KEYS",gt="VALUES",L="";class Re{constructor(e,t){const n=e._tree,s=Array.from(n.keys());this.set=e,this._type=t,this._path=s.length>0?[{node:n,keys:s}]:[]}next(){const e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return{done:!0,value:void 0};const{node:e,keys:t}=te(this._path);if(te(t)===L)return{done:!1,value:this.result()};const n=e.get(te(t));return this._path.push({node:n,keys:Array.from(n.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const e=te(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map(({keys:e})=>te(e)).filter(e=>e!==L).join("")}value(){return te(this._path).node.get(L)}result(){switch(this._type){case gt:return this.value();case mt:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}}const te=a=>a[a.length-1],Rn=(a,e,t)=>{const n=new Map;if(e===void 0)return n;const s=e.length+1,i=s+t,r=new Uint8Array(i*s).fill(t+1);for(let o=0;o{const l=i*r;e:for(const c of a.keys())if(c===L){const f=s[l-1];f<=t&&n.set(o,[a.get(c),f])}else{let f=i;for(let v=0;vt)continue e}bt(a.get(c),e,t,n,s,f,r,o+c)}};class H{constructor(e=new Map,t=""){this._size=void 0,this._tree=e,this._prefix=t}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[t,n]=ke(this._tree,e.slice(this._prefix.length));if(t===void 0){const[s,i]=$e(n);for(const r of s.keys())if(r!==L&&r.startsWith(i)){const o=new Map;return o.set(r.slice(i.length),s.get(r)),new H(o,e)}}return new H(t,e)}clear(){this._size=void 0,this._tree.clear()}delete(e){return this._size=void 0,On(this._tree,e)}entries(){return new Re(this,Fn)}forEach(e){for(const[t,n]of this)e(t,n,this)}fuzzyGet(e,t){return Rn(this._tree,e,t)}get(e){const t=ze(this._tree,e);return t!==void 0?t.get(L):void 0}has(e){const t=ze(this._tree,e);return t!==void 0&&t.has(L)}keys(){return new Re(this,mt)}set(e,t){if(typeof e!="string")throw new Error("key must be a string");return this._size=void 0,Oe(this._tree,e).set(L,t),this}get size(){if(this._size)return this._size;this._size=0;const e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const n=Oe(this._tree,e);return n.set(L,t(n.get(L))),this}fetch(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const n=Oe(this._tree,e);let s=n.get(L);return s===void 0&&n.set(L,s=t()),s}values(){return new Re(this,gt)}[Symbol.iterator](){return this.entries()}static from(e){const t=new H;for(const[n,s]of e)t.set(n,s);return t}static fromObject(e){return H.from(Object.entries(e))}}const ke=(a,e,t=[])=>{if(e.length===0||a==null)return[a,t];for(const n of a.keys())if(n!==L&&e.startsWith(n))return t.push([a,n]),ke(a.get(n),e.slice(n.length),t);return t.push([a,e]),ke(void 0,"",t)},ze=(a,e)=>{if(e.length===0||a==null)return a;for(const t of a.keys())if(t!==L&&e.startsWith(t))return ze(a.get(t),e.slice(t.length))},Oe=(a,e)=>{const t=e.length;e:for(let n=0;a&&n{const[t,n]=ke(a,e);if(t!==void 0){if(t.delete(L),t.size===0)yt(n);else if(t.size===1){const[s,i]=t.entries().next().value;wt(n,s,i)}}},yt=a=>{if(a.length===0)return;const[e,t]=$e(a);if(e.delete(t),e.size===0)yt(a.slice(0,-1));else if(e.size===1){const[n,s]=e.entries().next().value;n!==L&&wt(a.slice(0,-1),n,s)}},wt=(a,e,t)=>{if(a.length===0)return;const[n,s]=$e(a);n.set(s+e,t),n.delete(s)},$e=a=>a[a.length-1],Be="or",St="and",An="and_not";class se{constructor(e){if(e?.fields==null)throw new Error('MiniSearch: option "fields" must be provided');const t=e.autoVacuum==null||e.autoVacuum===!0?Me:e.autoVacuum;this._options={...Ce,...e,autoVacuum:t,searchOptions:{...st,...e.searchOptions||{}},autoSuggestOptions:{...Pn,...e.autoSuggestOptions||{}}},this._index=new H,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=je,this.addFields(this._options.fields)}add(e){const{extractField:t,stringifyField:n,tokenize:s,processTerm:i,fields:r,idField:o}=this._options,l=t(e,o);if(l==null)throw new Error(`MiniSearch: document does not have ID field "${o}"`);if(this._idToShortId.has(l))throw new Error(`MiniSearch: duplicate ID ${l}`);const c=this.addDocumentId(l);this.saveStoredFields(c,e);for(const f of r){const v=t(e,f);if(v==null)continue;const h=s(n(v,f),f),y=this._fieldIds[f],S=new Set(h).size;this.addFieldLength(c,y,this._documentCount-1,S);for(const w of h){const T=i(w,f);if(Array.isArray(T))for(const N of T)this.addTerm(y,c,N);else T&&this.addTerm(y,c,T)}}}addAll(e){for(const t of e)this.add(t)}addAllAsync(e,t={}){const{chunkSize:n=10}=t,s={chunk:[],promise:Promise.resolve()},{chunk:i,promise:r}=e.reduce(({chunk:o,promise:l},c,f)=>(o.push(c),(f+1)%n===0?{chunk:[],promise:l.then(()=>new Promise(v=>setTimeout(v,0))).then(()=>this.addAll(o))}:{chunk:o,promise:l}),s);return r.then(()=>this.addAll(i))}remove(e){const{tokenize:t,processTerm:n,extractField:s,stringifyField:i,fields:r,idField:o}=this._options,l=s(e,o);if(l==null)throw new Error(`MiniSearch: document does not have ID field "${o}"`);const c=this._idToShortId.get(l);if(c==null)throw new Error(`MiniSearch: cannot remove document with ID ${l}: it is not in the index`);for(const f of r){const v=s(e,f);if(v==null)continue;const h=t(i(v,f),f),y=this._fieldIds[f],S=new Set(h).size;this.removeFieldLength(c,y,this._documentCount,S);for(const w of h){const T=n(w,f);if(Array.isArray(T))for(const N of T)this.removeTerm(y,c,N);else T&&this.removeTerm(y,c,T)}}this._storedFields.delete(c),this._documentIds.delete(c),this._idToShortId.delete(l),this._fieldLength.delete(c),this._documentCount-=1}removeAll(e){if(e)for(const t of e)this.remove(t);else{if(arguments.length>0)throw new Error("Expected documents to be present. Omit the argument to remove all documents.");this._index=new H,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldLength=new Map,this._avgFieldLength=[],this._storedFields=new Map,this._nextId=0}}discard(e){const t=this._idToShortId.get(e);if(t==null)throw new Error(`MiniSearch: cannot discard document with ID ${e}: it is not in the index`);this._idToShortId.delete(e),this._documentIds.delete(t),this._storedFields.delete(t),(this._fieldLength.get(t)||[]).forEach((n,s)=>{this.removeFieldLength(t,s,this._documentCount,n)}),this._fieldLength.delete(t),this._documentCount-=1,this._dirtCount+=1,this.maybeAutoVacuum()}maybeAutoVacuum(){if(this._options.autoVacuum===!1)return;const{minDirtFactor:e,minDirtCount:t,batchSize:n,batchWait:s}=this._options.autoVacuum;this.conditionalVacuum({batchSize:n,batchWait:s},{minDirtCount:t,minDirtFactor:e})}discardAll(e){const t=this._options.autoVacuum;try{this._options.autoVacuum=!1;for(const n of e)this.discard(n)}finally{this._options.autoVacuum=t}this.maybeAutoVacuum()}replace(e){const{idField:t,extractField:n}=this._options,s=n(e,t);this.discard(s),this.add(e)}vacuum(e={}){return this.conditionalVacuum(e)}conditionalVacuum(e,t){return this._currentVacuum?(this._enqueuedVacuumConditions=this._enqueuedVacuumConditions&&t,this._enqueuedVacuum!=null?this._enqueuedVacuum:(this._enqueuedVacuum=this._currentVacuum.then(()=>{const n=this._enqueuedVacuumConditions;return this._enqueuedVacuumConditions=je,this.performVacuuming(e,n)}),this._enqueuedVacuum)):this.vacuumConditionsMet(t)===!1?Promise.resolve():(this._currentVacuum=this.performVacuuming(e),this._currentVacuum)}async performVacuuming(e,t){const n=this._dirtCount;if(this.vacuumConditionsMet(t)){const s=e.batchSize||Ve.batchSize,i=e.batchWait||Ve.batchWait;let r=1;for(const[o,l]of this._index){for(const[c,f]of l)for(const[v]of f)this._documentIds.has(v)||(f.size<=1?l.delete(c):f.delete(v));this._index.get(o).size===0&&this._index.delete(o),r%s===0&&await new Promise(c=>setTimeout(c,i)),r+=1}this._dirtCount-=n}await null,this._currentVacuum=this._enqueuedVacuum,this._enqueuedVacuum=null}vacuumConditionsMet(e){if(e==null)return!0;let{minDirtCount:t,minDirtFactor:n}=e;return t=t||Me.minDirtCount,n=n||Me.minDirtFactor,this.dirtCount>=t&&this.dirtFactor>=n}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}has(e){return this._idToShortId.has(e)}getStoredFields(e){const t=this._idToShortId.get(e);if(t!=null)return this._storedFields.get(t)}search(e,t={}){const{searchOptions:n}=this._options,s={...n,...t},i=this.executeQuery(e,t),r=[];for(const[o,{score:l,terms:c,match:f}]of i){const v=c.length||1,h={id:this._documentIds.get(o),score:l*v,terms:Object.keys(f),queryTerms:c,match:f};Object.assign(h,this._storedFields.get(o)),(s.filter==null||s.filter(h))&&r.push(h)}return e===se.wildcard&&s.boostDocument==null||r.sort(at),r}autoSuggest(e,t={}){t={...this._options.autoSuggestOptions,...t};const n=new Map;for(const{score:i,terms:r}of this.search(e,t)){const o=r.join(" "),l=n.get(o);l!=null?(l.score+=i,l.count+=1):n.set(o,{score:i,terms:r,count:1})}const s=[];for(const[i,{score:r,terms:o,count:l}]of n)s.push({suggestion:i,terms:o,score:r/l});return s.sort(at),s}get documentCount(){return this._documentCount}get termCount(){return this._index.size}static loadJSON(e,t){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJS(JSON.parse(e),t)}static async loadJSONAsync(e,t){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJSAsync(JSON.parse(e),t)}static getDefault(e){if(Ce.hasOwnProperty(e))return Ae(Ce,e);throw new Error(`MiniSearch: unknown option "${e}"`)}static loadJS(e,t){const{index:n,documentIds:s,fieldLength:i,storedFields:r,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=ye(s),l._fieldLength=ye(i),l._storedFields=ye(r);for(const[c,f]of l._documentIds)l._idToShortId.set(f,c);for(const[c,f]of n){const v=new Map;for(const h of Object.keys(f)){let y=f[h];o===1&&(y=y.ds),v.set(parseInt(h,10),ye(y))}l._index.set(c,v)}return l}static async loadJSAsync(e,t){const{index:n,documentIds:s,fieldLength:i,storedFields:r,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=await we(s),l._fieldLength=await we(i),l._storedFields=await we(r);for(const[f,v]of l._documentIds)l._idToShortId.set(v,f);let c=0;for(const[f,v]of n){const h=new Map;for(const y of Object.keys(v)){let S=v[y];o===1&&(S=S.ds),h.set(parseInt(y,10),await we(S))}++c%1e3===0&&await xt(0),l._index.set(f,h)}return l}static instantiateMiniSearch(e,t){const{documentCount:n,nextId:s,fieldIds:i,averageFieldLength:r,dirtCount:o,serializationVersion:l}=e;if(l!==1&&l!==2)throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version");const c=new se(t);return c._documentCount=n,c._nextId=s,c._idToShortId=new Map,c._fieldIds=i,c._avgFieldLength=r,c._dirtCount=o||0,c._index=new H,c}executeQuery(e,t={}){if(e===se.wildcard)return this.executeWildcardQuery(t);if(typeof e!="string"){const h={...t,...e,queries:void 0},y=e.queries.map(S=>this.executeQuery(S,h));return this.combineResults(y,h.combineWith)}const{tokenize:n,processTerm:s,searchOptions:i}=this._options,r={tokenize:n,processTerm:s,...i,...t},{tokenize:o,processTerm:l}=r,v=o(e).flatMap(h=>l(h)).filter(h=>!!h).map(Dn(r)).map(h=>this.executeQuerySpec(h,r));return this.combineResults(v,r.combineWith)}executeQuerySpec(e,t){const n={...this._options.searchOptions,...t},s=(n.fields||this._options.fields).reduce((w,T)=>({...w,[T]:Ae(n.boost,T)||1}),{}),{boostDocument:i,weights:r,maxFuzzy:o,bm25:l}=n,{fuzzy:c,prefix:f}={...st.weights,...r},v=this._index.get(e.term),h=this.termResults(e.term,e.term,1,e.termBoost,v,s,i,l);let y,S;if(e.prefix&&(y=this._index.atPrefix(e.term)),e.fuzzy){const w=e.fuzzy===!0?.2:e.fuzzy,T=w<1?Math.min(o,Math.round(e.term.length*w)):w;T&&(S=this._index.fuzzyGet(e.term,T))}if(y)for(const[w,T]of y){const N=w.length-e.term.length;if(!N)continue;S?.delete(w);const R=f*w.length/(w.length+.3*N);this.termResults(e.term,w,R,e.termBoost,T,s,i,l,h)}if(S)for(const w of S.keys()){const[T,N]=S.get(w);if(!N)continue;const R=c*w.length/(w.length+N);this.termResults(e.term,w,R,e.termBoost,T,s,i,l,h)}return h}executeWildcardQuery(e){const t=new Map,n={...this._options.searchOptions,...e};for(const[s,i]of this._documentIds){const r=n.boostDocument?n.boostDocument(i,"",this._storedFields.get(s)):1;t.set(s,{score:r,terms:[],match:{}})}return t}combineResults(e,t=Be){if(e.length===0)return new Map;const n=t.toLowerCase(),s=Cn[n];if(!s)throw new Error(`Invalid combination operator: ${t}`);return e.reduce(s)||new Map}toJSON(){const e=[];for(const[t,n]of this._index){const s={};for(const[i,r]of n)s[i]=Object.fromEntries(r);e.push([t,s])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,serializationVersion:2}}termResults(e,t,n,s,i,r,o,l,c=new Map){if(i==null)return c;for(const f of Object.keys(r)){const v=r[f],h=this._fieldIds[f],y=i.get(h);if(y==null)continue;let S=y.size;const w=this._avgFieldLength[h];for(const T of y.keys()){if(!this._documentIds.has(T)){this.removeTerm(h,T,t),S-=1;continue}const N=o?o(this._documentIds.get(T),t,this._storedFields.get(T)):1;if(!N)continue;const R=y.get(T),G=this._fieldLength.get(T)[h],z=Ln(R,S,this._documentCount,G,w,l),$=n*s*v*N*z,P=c.get(T);if(P){P.score+=$,zn(P.terms,e);const V=Ae(P.match,t);V?V.push(f):P.match[t]=[f]}else c.set(T,{score:$,terms:[e],match:{[t]:[f]}})}}return c}addTerm(e,t,n){const s=this._index.fetch(n,ot);let i=s.get(e);if(i==null)i=new Map,i.set(t,1),s.set(e,i);else{const r=i.get(t);i.set(t,(r||0)+1)}}removeTerm(e,t,n){if(!this._index.has(n)){this.warnDocumentChanged(t,e,n);return}const s=this._index.fetch(n,ot),i=s.get(e);i==null||i.get(t)==null?this.warnDocumentChanged(t,e,n):i.get(t)<=1?i.size<=1?s.delete(e):i.delete(t):i.set(t,i.get(t)-1),this._index.get(n).size===0&&this._index.delete(n)}warnDocumentChanged(e,t,n){for(const s of Object.keys(this._fieldIds))if(this._fieldIds[s]===t){this._options.logger("warn",`MiniSearch: document with ID ${this._documentIds.get(e)} has changed before removal: term "${n}" was not present in field "${s}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}}addDocumentId(e){const t=this._nextId;return this._idToShortId.set(e,t),this._documentIds.set(t,e),this._documentCount+=1,this._nextId+=1,t}addFields(e){for(let t=0;tObject.prototype.hasOwnProperty.call(a,e)?a[e]:void 0,Cn={[Be]:(a,e)=>{for(const t of e.keys()){const n=a.get(t);if(n==null)a.set(t,e.get(t));else{const{score:s,terms:i,match:r}=e.get(t);n.score=n.score+s,n.match=Object.assign(n.match,r),it(n.terms,i)}}return a},[St]:(a,e)=>{const t=new Map;for(const n of e.keys()){const s=a.get(n);if(s==null)continue;const{score:i,terms:r,match:o}=e.get(n);it(s.terms,r),t.set(n,{score:s.score+i,terms:s.terms,match:Object.assign(s.match,o)})}return t},[An]:(a,e)=>{for(const t of e.keys())a.delete(t);return a}},Mn={k:1.2,b:.7,d:.5},Ln=(a,e,t,n,s,i)=>{const{k:r,b:o,d:l}=i;return Math.log(1+(t-e+.5)/(e+.5))*(l+a*(r+1)/(a+r*(1-o+o*n/s)))},Dn=a=>(e,t,n)=>{const s=typeof a.fuzzy=="function"?a.fuzzy(e,t,n):a.fuzzy||!1,i=typeof a.prefix=="function"?a.prefix(e,t,n):a.prefix===!0,r=typeof a.boostTerm=="function"?a.boostTerm(e,t,n):1;return{term:e,fuzzy:s,prefix:i,termBoost:r}},Ce={idField:"id",extractField:(a,e)=>a[e],stringifyField:(a,e)=>a.toString(),tokenize:a=>a.split(Vn),processTerm:a=>a.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(a,e)=>{typeof console?.[a]=="function"&&console[a](e)},autoVacuum:!0},st={combineWith:Be,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:Mn},Pn={combineWith:St,prefix:(a,e,t)=>e===t.length-1},Ve={batchSize:1e3,batchWait:10},je={minDirtFactor:.1,minDirtCount:20},Me={...Ve,...je},zn=(a,e)=>{a.includes(e)||a.push(e)},it=(a,e)=>{for(const t of e)a.includes(t)||a.push(t)},at=({score:a},{score:e})=>e-a,ot=()=>new Map,ye=a=>{const e=new Map;for(const t of Object.keys(a))e.set(parseInt(t,10),a[t]);return e},we=async a=>{const e=new Map;let t=0;for(const n of Object.keys(a))e.set(parseInt(n,10),a[n]),++t%1e3===0&&await xt(0);return e},xt=a=>new Promise(e=>setTimeout(e,a)),Vn=/[\n\r\p{Z}\p{P}]+/u;class jn{max;cache;constructor(e=10){this.max=e,this.cache=new Map}get(e){let t=this.cache.get(e);return t!==void 0&&(this.cache.delete(e),this.cache.set(e,t)),t}set(e,t){this.cache.has(e)?this.cache.delete(e):this.cache.size===this.max&&this.cache.delete(this.first()),this.cache.set(e,t)}first(){return this.cache.keys().next().value}clear(){this.cache.clear()}}function $n(a){const{localeIndex:e,theme:t}=lt();function n(s){const i=s.split("."),r=t.value.search?.options,o=r&&typeof r=="object",l=o&&r.locales?.[e.value]?.translations||null,c=o&&r.translations||null;let f=l,v=c,h=a;const y=i.pop();for(const S of i){let w=null;const T=h?.[S];T&&(w=h=T);const N=v?.[S];N&&(w=v=N);const R=f?.[S];R&&(w=f=R),T||(h=w),N||(v=w),R||(f=w)}return f?.[y]??v?.[y]??h?.[y]??""}return n}const Bn=["aria-owns"],Wn={class:"shell"},Kn=["title"],Jn={class:"search-actions before"},Un=["title"],qn=["aria-activedescendant","aria-controls","placeholder"],Hn={class:"search-actions"},Gn=["title"],Qn=["disabled","title"],Yn=["id","role","aria-labelledby"],Zn=["id","aria-selected"],Xn=["href","aria-label","onMouseenter","onFocusin","data-index"],er={class:"titles"},tr=["innerHTML"],nr={class:"title main"},rr=["innerHTML"],sr={key:0,class:"excerpt-wrapper"},ir={key:0,class:"excerpt",inert:""},ar=["innerHTML"],or={key:0,class:"no-results"},lr={class:"search-keyboard-shortcuts"},cr=["aria-label"],ur=["aria-label"],dr=["aria-label"],fr=["aria-label"],hr=Nt({__name:"VPLocalSearchBox",emits:["close"],setup(a,{emit:e}){const t=e,n=ne(),s=ne(),i=ne(Gt),r=lt(),{activate:o}=In(n,{immediate:!0,allowOutsideClick:!0,clickOutsideDeactivates:!0,escapeDeactivates:!0}),{localeIndex:l,theme:c}=r,f=He(async()=>Ze(se.loadJSON((await i.value[l.value]?.())?.default,{fields:["title","titles","text"],storeFields:["title","titles"],searchOptions:{fuzzy:.2,prefix:!0,boost:{title:4,text:2,titles:1},...c.value.search?.provider==="local"&&c.value.search.options?.miniSearch?.searchOptions},...c.value.search?.provider==="local"&&c.value.search.options?.miniSearch?.options}))),h=Se(()=>c.value.search?.provider==="local"&&c.value.search.options?.disableQueryPersistence===!0).value?ue(""):Ft("vitepress:local-search-filter",""),y=Rt("vitepress:local-search-detailed-list",c.value.search?.provider==="local"&&c.value.search.options?.detailedView===!0),S=Se(()=>c.value.search?.provider==="local"&&(c.value.search.options?.disableDetailedView===!0||c.value.search.options?.detailedView===!1));Ot(()=>{S.value&&(y.value=!1)});const w=ne([]),T=ue(!1);Le(h,()=>{T.value=!1});const N=He(async()=>{if(s.value)return Ze(new Nn(s.value))},null),R=new jn(16);At(()=>[f.value,h.value,y.value],async([m,p,g],_,x)=>{_?.[0]!==m&&R.clear();let F=!1;if(x(()=>{F=!0}),!m)return;w.value=m.search(p).slice(0,16),T.value=!0;const D=g?await Promise.all(w.value.map(O=>G(O.id))):[];if(F)return;for(const{id:O,mod:J}of D){const U=O.slice(0,O.indexOf("#"));let oe=R.get(U);if(oe)continue;oe=new Map,R.set(U,oe);const le=J.default??J;if(le?.render||le?.setup){const Q=Kt(le);Q.config.warnHandler=()=>{},Q.provide(Jt,r),Object.defineProperties(Q.config.globalProperties,{$frontmatter:{get(){return r.frontmatter.value}},$params:{get(){return r.page.value.params}}});const We=document.createElement("div");Q.mount(We),We.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach(ce=>{const Ke=ce.querySelector("a")?.getAttribute("href"),Je=Ke?.startsWith("#")&&Ke.slice(1);if(!Je)return;let Ue="";for(;(ce=ce.nextElementSibling)&&!/^h[1-6]$/i.test(ce.tagName);)Ue+=ce.outerHTML;oe.set(Je,Ue)}),Q.unmount()}if(F)return}const M=new Set;if(w.value=w.value.map(O=>{const[J,U]=O.id.split("#"),le=R.get(J)?.get(U)??"";for(const Q in O.match)M.add(Q);return{...O,text:le}}),await de(),F)return;await new Promise(O=>{N.value?.unmark({done:()=>{N.value?.markRegExp(b(M),{done:O})}})});const j=n.value?.querySelectorAll(".result .excerpt")??[];for(const O of j)O.querySelector('mark[data-markjs="true"]')?.scrollIntoView({block:"center"});s.value?.firstElementChild?.scrollIntoView({block:"start"})},{debounce:200,immediate:!0});async function G(m){const p=Ut(m.slice(0,m.indexOf("#")));try{if(!p)throw new Error(`Cannot find file for id: ${m}`);return{id:m,mod:await import(p)}}catch(g){return console.error(g),{id:m,mod:{}}}}const z=ue(),$=Se(()=>h.value?.length<=0);function P(m=!0){z.value?.focus(),m&&z.value?.select()}Ne(()=>{P()});function V(m){m.pointerType==="mouse"&&P()}const A=ue(-1),K=ue(!0);Le(w,m=>{A.value=m.length?0:-1,ie()});function ie(){de(()=>{document.querySelector(".result.selected")?.scrollIntoView({block:"nearest"})})}me("ArrowUp",m=>{m.preventDefault(),A.value--,A.value<0&&(A.value=w.value.length-1),K.value=!0,ie()}),me("ArrowDown",m=>{m.preventDefault(),A.value++,A.value>=w.value.length&&(A.value=0),K.value=!0,ie()});const ae=Ct();me("Enter",m=>{if(m.isComposing||m.target instanceof HTMLButtonElement&&m.target.type!=="submit")return;const p=w.value[A.value];if(m.target instanceof HTMLInputElement&&!p){m.preventDefault();return}p&&(ae.go(p.id),t("close"))}),me("Escape",()=>{t("close")});const E=$n({button:{buttonText:"Search"},modal:{displayDetails:"Display detailed list",resetButtonTitle:"Reset search",backButtonTitle:"Close search",noResultsText:"No results for",footer:{selectText:"to select",selectKeyAriaLabel:"enter",navigateText:"to navigate",navigateUpKeyAriaLabel:"up arrow",navigateDownKeyAriaLabel:"down arrow",closeText:"to close",closeKeyAriaLabel:"escape"}}});Ne(()=>{window.history.pushState(null,"",null)}),Mt("popstate",m=>{m.preventDefault(),t("close")});const u=Lt(Dt?document.body:null);Ne(()=>{de(()=>{u.value=!0,de().then(()=>o())})}),Pt(()=>{u.value=!1});function d(){h.value="",de().then(()=>P(!1))}function b(m){return new RegExp([...m].sort((p,g)=>g.length-p.length).map(p=>`(${qt(p)})`).join("|"),"gi")}function I(m){if(!K.value)return;const p=m.target?.closest(".result"),g=Number.parseInt(p?.dataset.index);g>=0&&g!==A.value&&(A.value=g),K.value=!1}return(m,p)=>(B(),zt(Wt,{to:"body"},[k("div",{ref_key:"el",ref:n,role:"button","aria-owns":w.value?.length?"localsearch-list":void 0,"aria-expanded":"true","aria-haspopup":"listbox","aria-labelledby":"localsearch-label",class:"VPLocalSearchBox"},[k("div",{class:"backdrop",onClick:p[0]||(p[0]=g=>m.$emit("close"))}),k("div",Wn,[k("form",{class:"search-bar",onPointerup:p[4]||(p[4]=g=>V(g)),onSubmit:p[5]||(p[5]=Vt(()=>{},["prevent"]))},[k("label",{title:C(E)("button.buttonText"),id:"localsearch-label",for:"localsearch-input"},[...p[7]||(p[7]=[k("span",{"aria-hidden":"true",class:"vpi-search search-icon local-search-icon"},null,-1)])],8,Kn),k("div",Jn,[k("button",{class:"back-button",title:C(E)("modal.backButtonTitle"),onClick:p[1]||(p[1]=g=>m.$emit("close"))},[...p[8]||(p[8]=[k("span",{class:"vpi-arrow-left local-search-icon"},null,-1)])],8,Un)]),jt(k("input",{ref_key:"searchInput",ref:z,"onUpdate:modelValue":p[2]||(p[2]=g=>Bt(h)?h.value=g:null),"aria-activedescendant":A.value>-1?"localsearch-item-"+A.value:void 0,"aria-autocomplete":"both","aria-controls":w.value?.length?"localsearch-list":void 0,"aria-labelledby":"localsearch-label",autocapitalize:"off",autocomplete:"off",autocorrect:"off",class:"search-input",id:"localsearch-input",enterkeyhint:"go",maxlength:"64",placeholder:C(E)("button.buttonText"),spellcheck:"false",type:"search"},null,8,qn),[[$t,C(h)]]),k("div",Hn,[S.value?ge("",!0):(B(),q("button",{key:0,class:Ge(["toggle-layout-button",{"detailed-list":C(y)}]),type:"button",title:C(E)("modal.displayDetails"),onClick:p[3]||(p[3]=g=>A.value>-1&&(y.value=!C(y)))},[...p[9]||(p[9]=[k("span",{class:"vpi-layout-list local-search-icon"},null,-1)])],10,Gn)),k("button",{class:"clear-button",type:"reset",disabled:$.value,title:C(E)("modal.resetButtonTitle"),onClick:d},[...p[10]||(p[10]=[k("span",{class:"vpi-delete local-search-icon"},null,-1)])],8,Qn)])],32),k("ul",{ref_key:"resultsEl",ref:s,id:w.value?.length?"localsearch-list":void 0,role:w.value?.length?"listbox":void 0,"aria-labelledby":w.value?.length?"localsearch-label":void 0,class:"results",onMousemove:I},[(B(!0),q(Ye,null,Qe(w.value,(g,_)=>(B(),q("li",{key:g.id,id:"localsearch-item-"+_,"aria-selected":A.value===_?"true":"false",role:"option"},[k("a",{href:g.id,class:Ge(["result",{selected:A.value===_}]),"aria-label":[...g.titles,g.title].join(" > "),onMouseenter:x=>!K.value&&(A.value=_),onFocusin:x=>A.value=_,onClick:p[6]||(p[6]=x=>m.$emit("close")),"data-index":_},[k("div",null,[k("div",er,[p[12]||(p[12]=k("span",{class:"title-icon"},"#",-1)),(B(!0),q(Ye,null,Qe(g.titles,(x,F)=>(B(),q("span",{key:F,class:"title"},[k("span",{class:"text",innerHTML:x},null,8,tr),p[11]||(p[11]=k("span",{class:"vpi-chevron-right local-search-icon"},null,-1))]))),128)),k("span",nr,[k("span",{class:"text",innerHTML:g.title},null,8,rr)])]),C(y)?(B(),q("div",sr,[g.text?(B(),q("div",ir,[k("div",{class:"vp-doc",innerHTML:g.text},null,8,ar)])):ge("",!0),p[13]||(p[13]=k("div",{class:"excerpt-gradient-bottom"},null,-1)),p[14]||(p[14]=k("div",{class:"excerpt-gradient-top"},null,-1))])):ge("",!0)])],42,Xn)],8,Zn))),128)),C(h)&&!w.value.length&&T.value?(B(),q("li",or,[fe(he(C(E)("modal.noResultsText"))+' "',1),k("strong",null,he(C(h)),1),p[15]||(p[15]=fe('" ',-1))])):ge("",!0)],40,Yn),k("div",lr,[k("span",null,[k("kbd",{"aria-label":C(E)("modal.footer.navigateUpKeyAriaLabel")},[...p[16]||(p[16]=[k("span",{class:"vpi-arrow-up navigate-icon"},null,-1)])],8,cr),k("kbd",{"aria-label":C(E)("modal.footer.navigateDownKeyAriaLabel")},[...p[17]||(p[17]=[k("span",{class:"vpi-arrow-down navigate-icon"},null,-1)])],8,ur),fe(" "+he(C(E)("modal.footer.navigateText")),1)]),k("span",null,[k("kbd",{"aria-label":C(E)("modal.footer.selectKeyAriaLabel")},[...p[18]||(p[18]=[k("span",{class:"vpi-corner-down-left navigate-icon"},null,-1)])],8,dr),fe(" "+he(C(E)("modal.footer.selectText")),1)]),k("span",null,[k("kbd",{"aria-label":C(E)("modal.footer.closeKeyAriaLabel")},"esc",8,fr),fe(" "+he(C(E)("modal.footer.closeText")),1)])])])],8,Bn)]))}}),br=Ht(hr,[["__scopeId","data-v-8c9d8b44"]]);export{br as default}; diff --git a/docs/assets/chunks/framework.BXgR76oC.js b/docs/assets/chunks/framework.BXgR76oC.js deleted file mode 100644 index c052ce05b2..0000000000 --- a/docs/assets/chunks/framework.BXgR76oC.js +++ /dev/null @@ -1,4 +0,0 @@ -function wl(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Sl={},El=()=>{},Ri=Object.assign,Tl=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},xl=Object.prototype.hasOwnProperty,Vn=(e,t)=>xl.call(e,t),dt=Array.isArray,Qt=e=>Yn(e)==="[object Map]",Cl=e=>Yn(e)==="[object Set]",on=e=>typeof e=="function",Al=e=>typeof e=="string",Mt=e=>typeof e=="symbol",Rt=e=>e!==null&&typeof e=="object",Ol=Object.prototype.toString,Yn=e=>Ol.call(e),Ml=e=>Yn(e).slice(8,-1),Rl=e=>Yn(e)==="[object Object]",Jn=e=>Al(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Ge=(e,t)=>!Object.is(e,t),Pl=(e,t,n,s=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})};let de;class Ll{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.__v_skip=!0,this.parent=de,!t&&de&&(this.index=(de.scopes||(de.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0&&--this._on===0){if(de===this)de=this.prevScope;else{let t=de;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,s;for(n=0,s=this.effects.length;n0)return;if(en){let t=en;for(en=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Zt;){let t=Zt;for(Zt=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Ii(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Ni(e){let t,n=e.depsTail,s=n;for(;s;){const r=s.prevDep;s.version===-1?(s===n&&(n=r),ir(s),Il(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=r}e.deps=t,e.depsTail=n}function js(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Fi(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Fi(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===ln)||(e.globalVersion=ln,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!js(e))))return;e.flags|=2;const t=e.dep,n=se,s=Ne;se=e,Ne=!0;try{Ii(e);const r=e.fn(e._value);(t.version===0||Ge(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{se=n,Ne=s,Ni(e),e.flags&=-3}}function ir(e,t=!1){const{dep:n,prevSub:s,nextSub:r}=e;if(s&&(s.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let i=n.computed.deps;i;i=i.nextDep)ir(i,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function Il(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Ne=!0;const Di=[];function nt(){Di.push(Ne),Ne=!1}function st(){const e=Di.pop();Ne=e===void 0?!0:e}function Lr(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=se;se=void 0;try{t()}finally{se=n}}}let ln=0;class Nl{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Qn{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!se||!Ne||se===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==se)n=this.activeLink=new Nl(se,this),se.deps?(n.prevDep=se.depsTail,se.depsTail.nextDep=n,se.depsTail=n):se.deps=se.depsTail=n,ji(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=se.depsTail,n.nextDep=void 0,se.depsTail.nextDep=n,se.depsTail=n,se.deps===n&&(se.deps=s)}return n}trigger(t){this.version++,ln++,this.notify(t)}notify(t){sr();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{rr()}}}function ji(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)ji(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const kn=new WeakMap,Et=Symbol(""),Hs=Symbol(""),cn=Symbol("");function ve(e,t,n){if(Ne&&se){let s=kn.get(e);s||kn.set(e,s=new Map);let r=s.get(n);r||(s.set(n,r=new Qn),r.map=s,r.key=n),r.track()}}function Ze(e,t,n,s,r,i){const o=kn.get(e);if(!o){ln++;return}const l=c=>{c&&c.trigger()};if(sr(),t==="clear")o.forEach(l);else{const c=dt(e),u=c&&Jn(n);if(c&&n==="length"){const a=Number(s);o.forEach((d,m)=>{(m==="length"||m===cn||!Mt(m)&&m>=a)&&l(d)})}else switch((n!==void 0||o.has(void 0))&&l(o.get(n)),u&&l(o.get(cn)),t){case"add":c?u&&l(o.get("length")):(l(o.get(Et)),Qt(e)&&l(o.get(Hs)));break;case"delete":c||(l(o.get(Et)),Qt(e)&&l(o.get(Hs)));break;case"set":Qt(e)&&l(o.get(Et));break}}rr()}function Fl(e,t){const n=kn.get(e);return n&&n.get(t)}function Lt(e){const t=Z(e);return t===e?t:(ve(t,"iterate",cn),Me(e)?t:t.map(De))}function Zn(e){return ve(e=Z(e),"iterate",cn),e}function qe(e,t){return rt(e)?kt(ut(e)?De(t):t):De(t)}const Dl={__proto__:null,[Symbol.iterator](){return _s(this,Symbol.iterator,e=>qe(this,e))},concat(...e){return Lt(this).concat(...e.map(t=>dt(t)?Lt(t):t))},entries(){return _s(this,"entries",e=>(e[1]=qe(this,e[1]),e))},every(e,t){return Xe(this,"every",e,t,void 0,arguments)},filter(e,t){return Xe(this,"filter",e,t,n=>n.map(s=>qe(this,s)),arguments)},find(e,t){return Xe(this,"find",e,t,n=>qe(this,n),arguments)},findIndex(e,t){return Xe(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Xe(this,"findLast",e,t,n=>qe(this,n),arguments)},findLastIndex(e,t){return Xe(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Xe(this,"forEach",e,t,void 0,arguments)},includes(...e){return ws(this,"includes",e)},indexOf(...e){return ws(this,"indexOf",e)},join(e){return Lt(this).join(e)},lastIndexOf(...e){return ws(this,"lastIndexOf",e)},map(e,t){return Xe(this,"map",e,t,void 0,arguments)},pop(){return qt(this,"pop")},push(...e){return qt(this,"push",e)},reduce(e,...t){return $r(this,"reduce",e,t)},reduceRight(e,...t){return $r(this,"reduceRight",e,t)},shift(){return qt(this,"shift")},some(e,t){return Xe(this,"some",e,t,void 0,arguments)},splice(...e){return qt(this,"splice",e)},toReversed(){return Lt(this).toReversed()},toSorted(e){return Lt(this).toSorted(e)},toSpliced(...e){return Lt(this).toSpliced(...e)},unshift(...e){return qt(this,"unshift",e)},values(){return _s(this,"values",e=>qe(this,e))}};function _s(e,t,n){const s=Zn(e),r=s[t]();return s!==e&&!Me(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.done||(i.value=n(i.value)),i}),r}const jl=Array.prototype;function Xe(e,t,n,s,r,i){const o=Zn(e),l=o!==e&&!Me(e),c=o[t];if(c!==jl[t]){const d=c.apply(e,i);return l?De(d):d}let u=n;o!==e&&(l?u=function(d,m){return n.call(this,qe(e,d),m,e)}:n.length>2&&(u=function(d,m){return n.call(this,d,m,e)}));const a=c.call(o,u,s);return l&&r?r(a):a}function $r(e,t,n,s){const r=Zn(e),i=r!==e&&!Me(e);let o=n,l=!1;r!==e&&(i?(l=s.length===0,o=function(u,a,d){return l&&(l=!1,u=qe(e,u)),n.call(this,u,qe(e,a),d,e)}):n.length>3&&(o=function(u,a,d){return n.call(this,u,a,d,e)}));const c=r[t](o,...s);return l?qe(e,c):c}function ws(e,t,n){const s=Z(e);ve(s,"iterate",cn);const r=s[t](...n);return(r===-1||r===!1)&&ns(n[0])?(n[0]=Z(n[0]),s[t](...n)):r}function qt(e,t,n=[]){nt(),sr();const s=Z(e)[t].apply(e,n);return rr(),st(),s}const Hl=wl("__proto__,__v_isRef,__isVue"),Hi=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Mt));function Vl(e){Mt(e)||(e=String(e));const t=Z(this);return ve(t,"has",e),t.hasOwnProperty(e)}class Vi{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){if(n==="__v_skip")return t.__v_skip;const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?qi:Ki:i?Bi:Wi).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=dt(t);if(!r){let c;if(o&&(c=Dl[n]))return c;if(n==="hasOwnProperty")return Vl}const l=Reflect.get(t,n,ce(t)?t:s);if((Mt(n)?Hi.has(n):Hl(n))||(r||ve(t,"get",n),i))return l;if(ce(l)){const c=o&&Jn(n)?l:l.value;return r&&Rt(c)?an(c):c}return Rt(l)?r?an(l):Vt(l):l}}class ki extends Vi{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];const o=dt(t)&&Jn(n);if(!this._isShallow){const u=rt(i);if(!Me(s)&&!rt(s)&&(i=Z(i),s=Z(s)),!o&&ce(i)&&!ce(s))return u||(i.value=s),!0}const l=o?Number(n)e,En=e=>Reflect.getPrototypeOf(e);function Kl(e,t,n){return function(...s){const r=this.__v_raw,i=Z(r),o=Qt(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,u=r[e](...s),a=n?Vs:t?kt:De;return!t&&ve(i,"iterate",c?Hs:Et),Ri(Object.create(u),{next(){const{value:d,done:m}=u.next();return m?{value:d,done:m}:{value:l?[a(d[0]),a(d[1])]:a(d),done:m}}})}}function Tn(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function ql(e,t){const n={get(r){const i=this.__v_raw,o=Z(i),l=Z(r);e||(Ge(r,l)&&ve(o,"get",r),ve(o,"get",l));const{has:c}=En(o),u=t?Vs:e?kt:De;if(c.call(o,r))return u(i.get(r));if(c.call(o,l))return u(i.get(l));i!==o&&i.get(r)},get size(){const r=this.__v_raw;return!e&&ve(Z(r),"iterate",Et),r.size},has(r){const i=this.__v_raw,o=Z(i),l=Z(r);return e||(Ge(r,l)&&ve(o,"has",r),ve(o,"has",l)),r===l?i.has(r):i.has(r)||i.has(l)},forEach(r,i){const o=this,l=o.__v_raw,c=Z(l),u=t?Vs:e?kt:De;return!e&&ve(c,"iterate",Et),l.forEach((a,d)=>r.call(i,u(a),u(d),o))}};return Ri(n,e?{add:Tn("add"),set:Tn("set"),delete:Tn("delete"),clear:Tn("clear")}:{add(r){const i=Z(this),o=En(i),l=Z(r),c=!t&&!Me(r)&&!rt(r)?l:r;return o.has.call(i,c)||Ge(r,c)&&o.has.call(i,r)||Ge(l,c)&&o.has.call(i,l)||(i.add(c),Ze(i,"add",c,c)),this},set(r,i){!t&&!Me(i)&&!rt(i)&&(i=Z(i));const o=Z(this),{has:l,get:c}=En(o);let u=l.call(o,r);u||(r=Z(r),u=l.call(o,r));const a=c.call(o,r);return o.set(r,i),u?Ge(i,a)&&Ze(o,"set",r,i):Ze(o,"add",r,i),this},delete(r){const i=Z(this),{has:o,get:l}=En(i);let c=o.call(i,r);c||(r=Z(r),c=o.call(i,r)),l&&l.call(i,r);const u=i.delete(r);return c&&Ze(i,"delete",r,void 0),u},clear(){const r=Z(this),i=r.size!==0,o=r.clear();return i&&Ze(r,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=Kl(r,e,t)}),n}function es(e,t){const n=ql(e,t);return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(Vn(n,r)&&r in s?n:s,r,i)}const Gl={get:es(!1,!1)},zl={get:es(!1,!0)},Xl={get:es(!0,!1)},Yl={get:es(!0,!0)},Wi=new WeakMap,Bi=new WeakMap,Ki=new WeakMap,qi=new WeakMap;function Jl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Ql(e){return e.__v_skip||!Object.isExtensible(e)?0:Jl(Ml(e))}function Vt(e){return rt(e)?e:ts(e,!1,kl,Gl,Wi)}function Zl(e){return ts(e,!1,Wl,zl,Bi)}function an(e){return ts(e,!0,Ul,Xl,Ki)}function ec(e){return ts(e,!0,Bl,Yl,qi)}function ts(e,t,n,s,r){if(!Rt(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=Ql(e);if(i===0)return e;const o=r.get(e);if(o)return o;const l=new Proxy(e,i===2?s:n);return r.set(e,l),l}function ut(e){return rt(e)?ut(e.__v_raw):!!(e&&e.__v_isReactive)}function rt(e){return!!(e&&e.__v_isReadonly)}function Me(e){return!!(e&&e.__v_isShallow)}function ns(e){return e?!!e.__v_raw:!1}function Z(e){const t=e&&e.__v_raw;return t?Z(t):e}function In(e){return!Vn(e,"__v_skip")&&Object.isExtensible(e)&&Pl(e,"__v_skip",!0),e}const De=e=>Rt(e)?Vt(e):e,kt=e=>Rt(e)?an(e):e;function ce(e){return e?e.__v_isRef===!0:!1}function Fe(e){return Gi(e,!1)}function ye(e){return Gi(e,!0)}function Gi(e,t){return ce(e)?e:new tc(e,t)}class tc{constructor(t,n){this.dep=new Qn,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:Z(t),this._value=n?t:De(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||Me(t)||rt(t);t=s?t:Z(t),Ge(t,n)&&(this._rawValue=t,this._value=s?t:De(t),this.dep.trigger())}}function ss(e){return ce(e)?e.value:e}function oe(e){return on(e)?e():ss(e)}const nc={get:(e,t,n)=>t==="__v_raw"?e:ss(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return ce(r)&&!ce(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function zi(e){return ut(e)?e:new Proxy(e,nc)}class sc{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new Qn,{get:s,set:r}=t(n.track.bind(n),n.trigger.bind(n));this._get=s,this._set=r}get value(){return this._value=this._get()}set value(t){this._set(t)}}function rc(e){return new sc(e)}class ic{constructor(t,n,s){this._object=t,this._defaultValue=s,this.__v_isRef=!0,this._value=void 0,this._key=Mt(n)?n:String(n),this._raw=Z(t);let r=!0,i=t;if(!dt(t)||Mt(this._key)||!Jn(this._key))do r=!ns(i)||Me(i);while(r&&(i=i.__v_raw));this._shallow=r}get value(){let t=this._object[this._key];return this._shallow&&(t=ss(t)),this._value=t===void 0?this._defaultValue:t}set value(t){if(this._shallow&&ce(this._raw[this._key])){const n=this._object[this._key];if(ce(n)){n.value=t;return}}this._object[this._key]=t}get dep(){return Fl(this._raw,this._key)}}class oc{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function lc(e,t,n){return ce(e)?e:on(e)?new oc(e):Rt(e)&&arguments.length>1?cc(e,t,n):Fe(e)}function cc(e,t,n){return new ic(e,t,n)}class ac{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new Qn(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=ln-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&se!==this)return $i(this,!0),!0}get value(){const t=this.dep.track();return Fi(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function uc(e,t,n=!1){let s,r;return on(e)?s=e:(s=e.get,r=e.set),new ac(s,r,n)}const xn={},Un=new WeakMap;let _t;function fc(e,t=!1,n=_t){if(n){let s=Un.get(n);s||Un.set(n,s=[]),s.push(e)}}function dc(e,t,n=Sl){const{immediate:s,deep:r,once:i,scheduler:o,augmentJob:l,call:c}=n,u=g=>r?g:Me(g)||r===!1||r===0?et(g,1):et(g);let a,d,m,v,w=!1,_=!1;if(ce(e)?(d=()=>e.value,w=Me(e)):ut(e)?(d=()=>u(e),w=!0):dt(e)?(_=!0,w=e.some(g=>ut(g)||Me(g)),d=()=>e.map(g=>{if(ce(g))return g.value;if(ut(g))return u(g);if(on(g))return c?c(g,2):g()})):on(e)?t?d=c?()=>c(e,2):e:d=()=>{if(m){nt();try{m()}finally{st()}}const g=_t;_t=a;try{return c?c(e,3,[v]):e(v)}finally{_t=g}}:d=El,t&&r){const g=d,O=r===!0?1/0:r;d=()=>et(g(),O)}const R=nr(),D=()=>{a.stop(),R&&R.active&&Tl(R.effects,a)};if(i&&t){const g=t;t=(...O)=>{g(...O),D()}}let $=_?new Array(e.length).fill(xn):xn;const p=g=>{if(!(!(a.flags&1)||!a.dirty&&!g))if(t){const O=a.run();if(r||w||(_?O.some((B,P)=>Ge(B,$[P])):Ge(O,$))){m&&m();const B=_t;_t=a;try{const P=[O,$===xn?void 0:_&&$[0]===xn?[]:$,v];$=O,c?c(t,3,P):t(...P)}finally{_t=B}}}else a.run()};return l&&l(p),a=new Pi(d),a.scheduler=o?()=>o(p,!1):p,v=g=>fc(g,!1,a),m=a.onStop=()=>{const g=Un.get(a);if(g){if(c)c(g,4);else for(const O of g)O();Un.delete(a)}},t?s?p(!0):$=a.run():o?o(p.bind(null,!0),!0):a.run(),D.pause=a.pause.bind(a),D.resume=a.resume.bind(a),D.stop=D,D}function et(e,t=1/0,n){if(t<=0||!Rt(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,ce(e))et(e.value,t,n);else if(dt(e))for(let s=0;s{et(s,t,n)});else if(Rl(e)){for(const s in e)et(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&et(e[s],t,n)}return e}function hc(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const te={},Dt=[],tt=()=>{},Xi=()=>!1,rs=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),or=e=>e.startsWith("onUpdate:"),xe=Object.assign,Yi=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},pc=Object.prototype.hasOwnProperty,re=(e,t)=>pc.call(e,t),Y=Array.isArray,gc=e=>is(e)==="[object Map]",mc=e=>is(e)==="[object Set]",Ir=e=>is(e)==="[object Date]",z=e=>typeof e=="function",be=e=>typeof e=="string",un=e=>typeof e=="symbol",le=e=>e!==null&&typeof e=="object",Ji=e=>(le(e)||z(e))&&z(e.then)&&z(e.catch),Qi=Object.prototype.toString,is=e=>Qi.call(e),vc=e=>is(e)==="[object Object]",Tt=hc(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),os=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},yc=/-\w/g,je=os(e=>e.replace(yc,t=>t.slice(1).toUpperCase())),bc=/\B([A-Z])/g,mn=os(e=>e.replace(bc,"-$1").toLowerCase()),lr=os(e=>e.charAt(0).toUpperCase()+e.slice(1)),Nn=os(e=>e?`on${lr(e)}`:""),Ss=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},wc=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Nr;const ls=()=>Nr||(Nr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function cr(e){if(Y(e)){const t={};for(let n=0;n{if(n){const s=n.split(Ec);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function ar(e){let t="";if(be(e))t=e;else if(Y(e))for(let n=0;n!!(e&&e.__v_isRef===!0),Ac=e=>be(e)?e:e==null?"":Y(e)||le(e)&&(e.toString===Qi||!z(e.toString))?Zi(e)?Ac(e.value):JSON.stringify(e,eo,2):String(e),eo=(e,t)=>Zi(t)?eo(e,t.value):gc(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[Es(s,i)+" =>"]=r,n),{})}:mc(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Es(n))}:un(t)?Es(t):le(t)&&!Y(t)&&!vc(t)?String(t):t,Es=(e,t="")=>{var n;return un(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};function vn(e,t,n,s){try{return s?e(...s):e()}catch(r){yn(r,t,n)}}function He(e,t,n,s){if(z(e)){const r=vn(e,t,n,s);return r&&Ji(r)&&r.catch(i=>{yn(i,t,n)}),r}if(Y(e)){const r=[];for(let i=0;i>>1,r=Se[s],i=fn(r);i=fn(n)?Se.push(e):Se.splice(Mc(t),0,e),e.flags|=1,no()}}function no(){Wn||(Wn=to.then(so))}function Rc(e){Y(e)?jt.push(...e):ct&&e.id===-1?ct.splice(It+1,0,e):e.flags&1||(jt.push(e),e.flags|=1),no()}function Fr(e,t,n=We+1){for(;nfn(n)-fn(s));if(jt.length=0,ct){ct.push(...t);return}for(ct=t,It=0;Ite.id==null?e.flags&2?-1:1/0:e.id;function so(e){try{for(We=0;We{s._d&&zn(-1);const i=Kn(t);let o;try{o=e(...r)}finally{Kn(i),s._d&&zn(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function Uf(e,t){if(me===null)return e;const n=hs(me),s=e.dirs||(e.dirs=[]);for(let r=0;r1)return n&&z(t)?t.call(s&&s.proxy):t}}function io(){return!!(gt()||At)}const $c=Symbol.for("v-scx"),Ic=()=>xt($c);function dr(e,t){return as(e,null,t)}function Wf(e,t){return as(e,null,{flush:"post"})}function Re(e,t,n){return as(e,t,n)}function as(e,t,n=te){const{immediate:s,deep:r,flush:i,once:o}=n,l=xe({},n),c=t&&s||!t&&i!=="post";let u;if(Ut){if(i==="sync"){const v=Ic();u=v.__watcherHandles||(v.__watcherHandles=[])}else if(!c){const v=()=>{};return v.stop=tt,v.resume=tt,v.pause=tt,v}}const a=ge;l.call=(v,w,_)=>He(v,a,w,_);let d=!1;i==="post"?l.scheduler=v=>{we(v,a&&a.suspense)}:i!=="sync"&&(d=!0,l.scheduler=(v,w)=>{w?v():fr(v)}),l.augmentJob=v=>{t&&(v.flags|=4),d&&(v.flags|=2,a&&(v.id=a.uid,v.i=a))};const m=dc(e,t,l);return Ut&&(u?u.push(m):c&&m()),m}function Nc(e,t,n){const s=this.proxy,r=be(e)?e.includes(".")?oo(s,e):()=>s[e]:e.bind(s,s);let i;z(t)?i=t:(i=t.handler,n=t);const o=_n(this),l=as(r,i.bind(s),n);return o(),l}function oo(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;re.__isTeleport,wt=e=>e&&(e.disabled||e.disabled===""),Fc=e=>e&&(e.defer||e.defer===""),Dr=e=>typeof SVGElement<"u"&&e instanceof SVGElement,jr=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,ks=(e,t)=>{const n=e&&e.to;return be(n)?t?t(n):null:n},Dc={name:"Teleport",__isTeleport:!0,process(e,t,n,s,r,i,o,l,c,u){const{mc:a,pc:d,pbc:m,o:{insert:v,querySelector:w,createText:_,createComment:R,parentNode:D}}=u,$=wt(t.props);let{dynamicChildren:p}=t;const g=(P,U,b)=>{P.shapeFlag&16&&a(P.children,U,b,r,i,o,l,c)},O=(P=t)=>{const U=wt(P.props),b=P.target=ks(P.props,w),A=Us(b,P,_,v);b&&(o!=="svg"&&Dr(b)?o="svg":o!=="mathml"&&jr(b)&&(o="mathml"),r&&r.isCE&&(r.ce._teleportTargets||(r.ce._teleportTargets=new Set)).add(b),U||(g(P,b,A),Xt(P,!1)))},B=P=>{const U=()=>{if(lt.get(P)===U){if(lt.delete(P),wt(P.props)){const b=D(P.el)||n;g(P,b,P.anchor),Xt(P,!0)}O(P)}};lt.set(P,U),we(U,i)};if(e==null){const P=t.el=_(""),U=t.anchor=_("");if(v(P,n,s),v(U,n,s),Fc(t.props)||i&&i.pendingBranch){B(t);return}$&&(g(t,n,U),Xt(t,!0)),O()}else{t.el=e.el;const P=t.anchor=e.anchor,U=lt.get(e);if(U){U.flags|=8,lt.delete(e),B(t);return}t.targetStart=e.targetStart;const b=t.target=e.target,A=t.targetAnchor=e.targetAnchor,H=wt(e.props),S=H?n:b,k=H?P:A;if(o==="svg"||Dr(b)?o="svg":(o==="mathml"||jr(b))&&(o="mathml"),p?(m(e.dynamicChildren,p,S,r,i,o,l),vr(e,t,!0)):c||d(e,t,S,k,r,i,o,l,!1),$)H?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Cn(t,n,P,u,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const j=t.target=ks(t.props,w);j&&Cn(t,j,null,u,0)}else H&&Cn(t,b,A,u,1);Xt(t,$)}},remove(e,t,n,{um:s,o:{remove:r}},i){const{shapeFlag:o,children:l,anchor:c,targetStart:u,targetAnchor:a,target:d,props:m}=e;let v=i||!wt(m);const w=lt.get(e);if(w&&(w.flags|=8,lt.delete(e),v=!1),d&&(r(u),r(a)),i&&r(c),o&16)for(let _=0;_{e.isMounted=!0}),mo(()=>{e.isUnmounting=!0}),e}const Pe=[Function,Array],ao={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Pe,onEnter:Pe,onAfterEnter:Pe,onEnterCancelled:Pe,onBeforeLeave:Pe,onLeave:Pe,onAfterLeave:Pe,onLeaveCancelled:Pe,onBeforeAppear:Pe,onAppear:Pe,onAfterAppear:Pe,onAppearCancelled:Pe},uo=e=>{const t=e.subTree;return t.component?uo(t.component):t},Vc={name:"BaseTransition",props:ao,setup(e,{slots:t}){const n=gt(),s=Hc();return()=>{const r=t.default&&po(t.default(),!0),i=r&&r.length?fo(r):n.subTree?Ra():void 0;if(!i)return;const o=Z(e),{mode:l}=o;if(s.isLeaving)return Ts(i);const c=Hr(i);if(!c)return Ts(i);let u=Ws(c,o,s,n,d=>u=d);c.type!==pe&&dn(c,u);let a=n.subTree&&Hr(n.subTree);if(a&&a.type!==pe&&!St(a,c)&&uo(n).type!==pe){let d=Ws(a,o,s,n);if(dn(a,d),l==="out-in"&&c.type!==pe)return s.isLeaving=!0,d.afterLeave=()=>{s.isLeaving=!1,n.job.flags&8||n.update(),delete d.afterLeave,a=void 0},Ts(i);l==="in-out"&&c.type!==pe?d.delayLeave=(m,v,w)=>{const _=ho(s,a);_[String(a.key)]=a,m[Ke]=()=>{v(),m[Ke]=void 0,delete u.delayedLeave,a=void 0},u.delayedLeave=()=>{w(),delete u.delayedLeave,a=void 0}}:a=void 0}else a&&(a=void 0);return i}}};function fo(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==pe){t=n;break}}return t}const kc=Vc;function ho(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function Ws(e,t,n,s,r){const{appear:i,mode:o,persisted:l=!1,onBeforeEnter:c,onEnter:u,onAfterEnter:a,onEnterCancelled:d,onBeforeLeave:m,onLeave:v,onAfterLeave:w,onLeaveCancelled:_,onBeforeAppear:R,onAppear:D,onAfterAppear:$,onAppearCancelled:p}=t,g=String(e.key),O=ho(n,e),B=(b,A)=>{b&&He(b,s,9,A)},P=(b,A)=>{const H=A[1];B(b,A),Y(b)?b.every(S=>S.length<=1)&&H():b.length<=1&&H()},U={mode:o,persisted:l,beforeEnter(b){let A=c;if(!n.isMounted)if(i)A=R||c;else return;b[Ke]&&b[Ke](!0);const H=O[g];H&&St(e,H)&&H.el[Ke]&&H.el[Ke](),B(A,[b])},enter(b){if(O[g]===e)return;let A=u,H=a,S=d;if(!n.isMounted)if(i)A=D||u,H=$||a,S=p||d;else return;let k=!1;b[Gt]=K=>{k||(k=!0,K?B(S,[b]):B(H,[b]),U.delayedLeave&&U.delayedLeave(),b[Gt]=void 0)};const j=b[Gt].bind(null,!1);A?P(A,[b,j]):j()},leave(b,A){const H=String(e.key);if(b[Gt]&&b[Gt](!0),n.isUnmounting)return A();B(m,[b]);let S=!1;b[Ke]=j=>{S||(S=!0,A(),j?B(_,[b]):B(w,[b]),b[Ke]=void 0,O[H]===e&&delete O[H])};const k=b[Ke].bind(null,!1);O[H]=e,v?P(v,[b,k]):k()},clone(b){const A=Ws(b,t,n,s,r);return r&&r(A),A}};return U}function Ts(e){if(bn(e))return e=ht(e),e.children=null,e}function Hr(e){if(!bn(e))return co(e.type)&&e.children?fo(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&z(n.default))return n.default()}}function dn(e,t){e.shapeFlag&6&&e.component?(e.transition=t,dn(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function po(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;in.value,set:i=>n.value=i})}return n}function Vr(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const qn=new WeakMap;function Ht(e,t,n,s,r=!1){if(Y(e)){e.forEach((_,R)=>Ht(_,t&&(Y(t)?t[R]:t),n,s,r));return}if(Ct(s)&&!r){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Ht(e,t,n,s.component.subTree);return}const i=s.shapeFlag&4?hs(s.component):s.el,o=r?null:i,{i:l,r:c}=e,u=t&&t.r,a=l.refs===te?l.refs={}:l.refs,d=l.setupState,m=Z(d),v=d===te?Xi:_=>Vr(a,_)?!1:re(m,_),w=(_,R)=>!(R&&Vr(a,R));if(u!=null&&u!==c){if(kr(t),be(u))a[u]=null,v(u)&&(d[u]=null);else if(ce(u)){const _=t;w(u,_.k)&&(u.value=null),_.k&&(a[_.k]=null)}}if(z(c))vn(c,l,12,[o,a]);else{const _=be(c),R=ce(c);if(_||R){const D=()=>{if(e.f){const $=_?v(c)?d[c]:a[c]:w()||!e.k?c.value:a[e.k];if(r)Y($)&&Yi($,i);else if(Y($))$.includes(i)||$.push(i);else if(_)a[c]=[i],v(c)&&(d[c]=a[c]);else{const p=[i];w(c,e.k)&&(c.value=p),e.k&&(a[e.k]=p)}}else _?(a[c]=o,v(c)&&(d[c]=o)):R&&(w(c,e.k)&&(c.value=o),e.k&&(a[e.k]=o))};if(o){const $=()=>{D(),qn.delete(e)};$.id=-1,qn.set(e,$),we($,n)}else kr(e),D()}}}function kr(e){const t=qn.get(e);t&&(t.flags|=8,qn.delete(e))}let Ur=!1;const $t=()=>{Ur||(console.error("Hydration completed but contains mismatches."),Ur=!0)},Uc=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Wc=e=>e.namespaceURI.includes("MathML"),An=e=>{if(e.nodeType===1){if(Uc(e))return"svg";if(Wc(e))return"mathml"}},Ft=e=>e.nodeType===8;function Bc(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:l,insert:c,createComment:u}}=e,a=(p,g)=>{if(!g.hasChildNodes()){n(null,p,g),Bn(),g._vnode=p;return}d(g.firstChild,p,null,null,null),Bn(),g._vnode=p},d=(p,g,O,B,P,U=!1)=>{U=U||!!g.dynamicChildren;const b=Ft(p)&&p.data==="[",A=()=>_(p,g,O,B,P,b),{type:H,ref:S,shapeFlag:k,patchFlag:j}=g;let K=p.nodeType;g.el=p,j===-2&&(U=!1,g.dynamicChildren=null);let I=null;switch(H){case Ot:K!==3?g.children===""?(c(g.el=r(""),o(p),p),I=p):I=A():(p.data!==g.children&&($t(),p.data=g.children),I=i(p));break;case pe:$(p)?(I=i(p),D(g.el=p.content.firstChild,p,O)):K!==8||b?I=A():I=i(p);break;case nn:if(b&&(p=i(p),K=p.nodeType),K===1||K===3){I=p;const X=!g.children.length;for(let V=0;V{U=U||!!g.dynamicChildren;const{type:b,props:A,patchFlag:H,shapeFlag:S,dirs:k,transition:j}=g,K=b==="input"||b==="option";if(K||H!==-1){k&&Be(g,null,O,"created");let I=!1;if($(p)){I=Fo(null,j)&&O&&O.vnode.props&&O.vnode.props.appear;const V=p.content.firstChild;if(I){const J=V.getAttribute("class");J&&(V.$cls=J),j.beforeEnter(V)}D(V,p,O),g.el=p=V}if(S&16&&!(A&&(A.innerHTML||A.textContent))){let V=v(p.firstChild,g,p,O,B,P,U);for(;V;){On(p,1)||$t();const J=V;V=V.nextSibling,l(J)}}else if(S&8){let V=g.children;V[0]===` -`&&(p.tagName==="PRE"||p.tagName==="TEXTAREA")&&(V=V.slice(1));const{textContent:J}=p;J!==V&&J!==V.replace(/\r\n|\r/g,` -`)&&(On(p,0)||$t(),p.textContent=g.children)}if(A){if(K||!U||H&48){const V=p.tagName.includes("-");for(const J in A)(K&&(J.endsWith("value")||J==="indeterminate")||rs(J)&&!Tt(J)||J[0]==="."||V&&!Tt(J))&&s(p,J,null,A[J],void 0,O)}else if(A.onClick)s(p,"onClick",null,A.onClick,void 0,O);else if(H&4&&ut(A.style))for(const V in A.style)A.style[V]}let X;(X=A&&A.onVnodeBeforeMount)&&Le(X,O,g),k&&Be(g,null,O,"beforeMount"),((X=A&&A.onVnodeMounted)||k||I)&&Vo(()=>{X&&Le(X,O,g),I&&j.enter(p),k&&Be(g,null,O,"mounted")},B)}return p.nextSibling},v=(p,g,O,B,P,U,b)=>{b=b||!!g.dynamicChildren;const A=g.children,H=A.length;for(let S=0;S{const{slotScopeIds:b}=g;b&&(P=P?P.concat(b):b);const A=o(p),H=v(i(p),g,A,O,B,P,U);return H&&Ft(H)&&H.data==="]"?i(g.anchor=H):($t(),c(g.anchor=u("]"),A,H),H)},_=(p,g,O,B,P,U)=>{if(On(p.parentElement,1)||$t(),g.el=null,U){const H=R(p);for(;;){const S=i(p);if(S&&S!==H)l(S);else break}}const b=i(p),A=o(p);return l(p),n(null,g,A,b,O,B,An(A),P),O&&(O.vnode.el=g.el,Co(O,g.el)),b},R=(p,g="[",O="]")=>{let B=0;for(;p;)if(p=i(p),p&&Ft(p)&&(p.data===g&&B++,p.data===O)){if(B===0)return i(p);B--}return p},D=(p,g,O)=>{const B=g.parentNode;B&&B.replaceChild(p,g);let P=O;for(;P;)P.vnode.el===g&&(P.vnode.el=P.subTree.el=p),P=P.parent},$=p=>p.nodeType===1&&p.tagName==="TEMPLATE";return[a,d]}const Wr="data-allow-mismatch",Kc={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function On(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(Wr);)e=e.parentElement;const n=e&&e.getAttribute(Wr);if(n==null)return!1;if(n==="")return!0;{const s=n.split(",");return t===0&&s.includes("children")?!0:s.includes(Kc[t])}}ls().requestIdleCallback;ls().cancelIdleCallback;function qc(e,t){if(Ft(e)&&e.data==="["){let n=1,s=e.nextSibling;for(;s;){if(s.nodeType===1){if(t(s)===!1)break}else if(Ft(s))if(s.data==="]"){if(--n===0)break}else s.data==="["&&n++;s=s.nextSibling}}else t(e)}const Ct=e=>!!e.type.__asyncLoader;function qf(e){z(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:s,delay:r=200,hydrate:i,timeout:o,suspensible:l=!0,onError:c}=e;let u=null,a,d=0;const m=()=>(d++,u=null,v()),v=()=>{let w;return u||(w=u=t().catch(_=>{if(_=_ instanceof Error?_:new Error(String(_)),c)return new Promise((R,D)=>{c(_,()=>R(m()),()=>D(_),d+1)});throw _}).then(_=>w!==u&&u?u:(_&&(_.__esModule||_[Symbol.toStringTag]==="Module")&&(_=_.default),a=_,_)))};return hr({name:"AsyncComponentWrapper",__asyncLoader:v,__asyncHydrate(w,_,R){let D=!1;(_.bu||(_.bu=[])).push(()=>D=!0);const $=()=>{D||R()},p=i?()=>{const g=i($,O=>qc(w,O));g&&(_.bum||(_.bum=[])).push(g)}:$;a?p():v().then(()=>!_.isUnmounted&&p())},get __asyncResolved(){return a},setup(){const w=ge;if(pr(w),a)return()=>Mn(a,w);const _=p=>{u=null,yn(p,w,13,!s)};if(l&&w.suspense||Ut)return v().then(p=>()=>Mn(p,w)).catch(p=>(_(p),()=>s?ue(s,{error:p}):null));const R=Fe(!1),D=Fe(),$=Fe(!!r);return r&&setTimeout(()=>{$.value=!1},r),o!=null&&setTimeout(()=>{if(!R.value&&!D.value){const p=new Error(`Async component timed out after ${o}ms.`);_(p),D.value=p}},o),v().then(()=>{R.value=!0,w.parent&&bn(w.parent.vnode)&&w.parent.update()}).catch(p=>{_(p),D.value=p}),()=>{if(R.value&&a)return Mn(a,w);if(D.value&&s)return ue(s,{error:D.value});if(n&&!$.value)return Mn(n,w)}}})}function Mn(e,t){const{ref:n,props:s,children:r,ce:i}=t.vnode,o=ue(e,s,r);return o.ref=n,o.ce=i,delete t.vnode.ce,o}const bn=e=>e.type.__isKeepAlive;function Gc(e,t){go(e,"a",t)}function zc(e,t){go(e,"da",t)}function go(e,t,n=ge){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(us(t,s,n),n){let r=n.parent;for(;r&&r.parent;)bn(r.parent.vnode)&&Xc(s,t,n,r),r=r.parent}}function Xc(e,t,n,s){const r=us(t,e,s,!0);fs(()=>{Yi(s[t],r)},n)}function us(e,t,n=ge,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{nt();const l=_n(n),c=He(t,n,e,o);return l(),st(),c});return s?r.unshift(i):r.push(i),i}}const it=e=>(t,n=ge)=>{(!Ut||e==="sp")&&us(e,(...s)=>t(...s),n)},Yc=it("bm"),Wt=it("m"),Jc=it("bu"),Qc=it("u"),mo=it("bum"),fs=it("um"),Zc=it("sp"),ea=it("rtg"),ta=it("rtc");function na(e,t=ge){us("ec",e,t)}const vo="components";function Gf(e,t){return bo(vo,e,!0,t)||e}const yo=Symbol.for("v-ndc");function zf(e){return be(e)?bo(vo,e,!1)||e:e||yo}function bo(e,t,n=!0,s=!1){const r=me||ge;if(r){const i=r.type;{const l=ja(i,!1);if(l&&(l===t||l===je(t)||l===lr(je(t))))return i}const o=Br(r[e]||i[e],t)||Br(r.appContext[e],t);return!o&&s?i:o}}function Br(e,t){return e&&(e[t]||e[je(t)]||e[lr(je(t))])}function Xf(e,t,n,s){let r;const i=n,o=Y(e);if(o||be(e)){const l=o&&ut(e);let c=!1,u=!1;l&&(c=!Me(e),u=rt(e),e=Zn(e)),r=new Array(e.length);for(let a=0,d=e.length;at(l,c,void 0,i));else{const l=Object.keys(e);r=new Array(l.length);for(let c=0,u=l.length;c0;return t!=="default"&&(n.name=t),zs(),Xs(Ee,null,[ue("slot",n,s&&s())],u?-2:64)}let i=e[t];i&&i._c&&(i._d=!1),zs();const o=i&&_o(i(n)),l=n.key||o&&o.key,c=Xs(Ee,{key:(l&&!un(l)?l:`_${t}`)+(!o&&s?"_fb":"")},o||(s?s():[]),o&&e._===1?64:-2);return!r&&c.scopeId&&(c.slotScopeIds=[c.scopeId+"-s"]),i&&i._c&&(i._d=!0),c}function _o(e){return e.some(t=>pn(t)?!(t.type===pe||t.type===Ee&&!_o(t.children)):!0)?e:null}function Jf(e,t){const n={};for(const s in e)n[/[A-Z]/.test(s)?`on:${s}`:Nn(s)]=e[s];return n}const Bs=e=>e?Ko(e)?hs(e):Bs(e.parent):null,tn=xe(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Bs(e.parent),$root:e=>Bs(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>So(e),$forceUpdate:e=>e.f||(e.f=()=>{fr(e.update)}),$nextTick:e=>e.n||(e.n=cs.bind(e.proxy)),$watch:e=>Nc.bind(e)}),xs=(e,t)=>e!==te&&!e.__isScriptSetup&&re(e,t),sa={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:l,appContext:c}=e;if(t[0]!=="$"){const m=o[t];if(m!==void 0)switch(m){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(xs(s,t))return o[t]=1,s[t];if(r!==te&&re(r,t))return o[t]=2,r[t];if(re(i,t))return o[t]=3,i[t];if(n!==te&&re(n,t))return o[t]=4,n[t];Ks&&(o[t]=0)}}const u=tn[t];let a,d;if(u)return t==="$attrs"&&ve(e.attrs,"get",""),u(e);if((a=l.__cssModules)&&(a=a[t]))return a;if(n!==te&&re(n,t))return o[t]=4,n[t];if(d=c.config.globalProperties,re(d,t))return d[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return xs(r,t)?(r[t]=n,!0):s!==te&&re(s,t)?(s[t]=n,!0):re(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,props:i,type:o}},l){let c;return!!(n[l]||e!==te&&l[0]!=="$"&&re(e,l)||xs(t,l)||re(i,l)||re(s,l)||re(tn,l)||re(r.config.globalProperties,l)||(c=o.__cssModules)&&c[l])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:re(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Qf(){return ra().slots}function ra(e){const t=gt();return t.setupContext||(t.setupContext=Go(t))}function Kr(e){return Y(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Ks=!0;function ia(e){const t=So(e),n=e.proxy,s=e.ctx;Ks=!1,t.beforeCreate&&qr(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:u,created:a,beforeMount:d,mounted:m,beforeUpdate:v,updated:w,activated:_,deactivated:R,beforeDestroy:D,beforeUnmount:$,destroyed:p,unmounted:g,render:O,renderTracked:B,renderTriggered:P,errorCaptured:U,serverPrefetch:b,expose:A,inheritAttrs:H,components:S,directives:k,filters:j}=t;if(u&&oa(u,s,null),o)for(const X in o){const V=o[X];z(V)&&(s[X]=V.bind(n))}if(r){const X=r.call(n,n);le(X)&&(e.data=Vt(X))}if(Ks=!0,i)for(const X in i){const V=i[X],J=z(V)?V.bind(n,n):z(V.get)?V.get.bind(n,n):tt,mt=!z(V)&&z(V.set)?V.set.bind(n):tt,Ve=ie({get:J,set:mt});Object.defineProperty(s,X,{enumerable:!0,configurable:!0,get:()=>Ve.value,set:ke=>Ve.value=ke})}if(l)for(const X in l)wo(l[X],s,n,X);if(c){const X=z(c)?c.call(n):c;Reflect.ownKeys(X).forEach(V=>{Lc(V,X[V])})}a&&qr(a,e,"c");function I(X,V){Y(V)?V.forEach(J=>X(J.bind(n))):V&&X(V.bind(n))}if(I(Yc,d),I(Wt,m),I(Jc,v),I(Qc,w),I(Gc,_),I(zc,R),I(na,U),I(ta,B),I(ea,P),I(mo,$),I(fs,g),I(Zc,b),Y(A))if(A.length){const X=e.exposed||(e.exposed={});A.forEach(V=>{Object.defineProperty(X,V,{get:()=>n[V],set:J=>n[V]=J,enumerable:!0})})}else e.exposed||(e.exposed={});O&&e.render===tt&&(e.render=O),H!=null&&(e.inheritAttrs=H),S&&(e.components=S),k&&(e.directives=k),b&&pr(e)}function oa(e,t,n=tt){Y(e)&&(e=qs(e));for(const s in e){const r=e[s];let i;le(r)?"default"in r?i=xt(r.from||s,r.default,!0):i=xt(r.from||s):i=xt(r),ce(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function qr(e,t,n){He(Y(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function wo(e,t,n,s){let r=s.includes(".")?oo(n,s):()=>n[s];if(be(e)){const i=t[e];z(i)&&Re(r,i)}else if(z(e))Re(r,e.bind(n));else if(le(e))if(Y(e))e.forEach(i=>wo(i,t,n,s));else{const i=z(e.handler)?e.handler.bind(n):t[e.handler];z(i)&&Re(r,i,e)}}function So(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!n&&!s?c=t:(c={},r.length&&r.forEach(u=>Gn(c,u,o,!0)),Gn(c,t,o)),le(t)&&i.set(t,c),c}function Gn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&Gn(e,i,n,!0),r&&r.forEach(o=>Gn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const l=la[o]||n&&n[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const la={data:Gr,props:zr,emits:zr,methods:Yt,computed:Yt,beforeCreate:_e,created:_e,beforeMount:_e,mounted:_e,beforeUpdate:_e,updated:_e,beforeDestroy:_e,beforeUnmount:_e,destroyed:_e,unmounted:_e,activated:_e,deactivated:_e,errorCaptured:_e,serverPrefetch:_e,components:Yt,directives:Yt,watch:aa,provide:Gr,inject:ca};function Gr(e,t){return t?e?function(){return xe(z(e)?e.call(this,this):e,z(t)?t.call(this,this):t)}:t:e}function ca(e,t){return Yt(qs(e),qs(t))}function qs(e){if(Y(e)){const t={};for(let n=0;nt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${je(t)}Modifiers`]||e[`${mn(t)}Modifiers`];function ha(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||te;let r=n;const i=t.startsWith("update:"),o=i&&da(s,t.slice(7));o&&(o.trim&&(r=n.map(a=>be(a)?a.trim():a)),o.number&&(r=n.map(wc)));let l,c=s[l=Nn(t)]||s[l=Nn(je(t))];!c&&i&&(c=s[l=Nn(mn(t))]),c&&He(c,e,6,r);const u=s[l+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,He(u,e,6,r)}}const pa=new WeakMap;function To(e,t,n=!1){const s=n?pa:t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!z(e)){const c=u=>{const a=To(u,t,!0);a&&(l=!0,xe(o,a))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(le(e)&&s.set(e,null),null):(Y(i)?i.forEach(c=>o[c]=null):xe(o,i),le(e)&&s.set(e,o),o)}function ds(e,t){return!e||!rs(t)?!1:(t=t.slice(2).replace(/Once$/,""),re(e,t[0].toLowerCase()+t.slice(1))||re(e,mn(t))||re(e,t))}function Cs(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:u,renderCache:a,props:d,data:m,setupState:v,ctx:w,inheritAttrs:_}=e,R=Kn(e);let D,$;try{if(n.shapeFlag&4){const g=r||s,O=g;D=$e(u.call(O,g,a,d,v,m,w)),$=l}else{const g=t;D=$e(g.length>1?g(d,{attrs:l,slots:o,emit:c}):g(d,null)),$=t.props?l:ga(l)}}catch(g){sn.length=0,yn(g,e,1),D=ue(pe)}let p=D;if($&&_!==!1){const g=Object.keys($),{shapeFlag:O}=p;g.length&&O&7&&(i&&g.some(or)&&($=ma($,i)),p=ht(p,$,!1,!0))}return n.dirs&&(p=ht(p,null,!1,!0),p.dirs=p.dirs?p.dirs.concat(n.dirs):n.dirs),n.transition&&dn(p,n.transition),D=p,Kn(R),D}const ga=e=>{let t;for(const n in e)(n==="class"||n==="style"||rs(n))&&((t||(t={}))[n]=e[n]);return t},ma=(e,t)=>{const n={};for(const s in e)(!or(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function va(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,u=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?Xr(s,o,u):!!o;if(c&8){const a=t.dynamicProps;for(let d=0;dObject.create(Ao),Mo=e=>Object.getPrototypeOf(e)===Ao;function ya(e,t,n,s=!1){const r={},i=Oo();e.propsDefaults=Object.create(null),Ro(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:Zl(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function ba(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=Z(r),[c]=e.propsOptions;let u=!1;if((s||o>0)&&!(o&16)){if(o&8){const a=e.vnode.dynamicProps;for(let d=0;d{c=!0;const[m,v]=Po(d,t,!0);xe(o,m),v&&l.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}if(!i&&!c)return le(e)&&s.set(e,Dt),Dt;if(Y(i))for(let a=0;ae==="_"||e==="_ctx"||e==="$stable",mr=e=>Y(e)?e.map($e):[$e(e)],wa=(e,t,n)=>{if(t._n)return t;const s=Pc((...r)=>mr(t(...r)),n);return s._c=!1,s},Lo=(e,t,n)=>{const s=e._ctx;for(const r in e){if(gr(r))continue;const i=e[r];if(z(i))t[r]=wa(r,i,s);else if(i!=null){const o=mr(i);t[r]=()=>o}}},$o=(e,t)=>{const n=mr(t);e.slots.default=()=>n},Io=(e,t,n)=>{for(const s in t)(n||!gr(s))&&(e[s]=t[s])},Sa=(e,t,n)=>{const s=e.slots=Oo();if(e.vnode.shapeFlag&32){const r=t._;r?(Io(s,t,n),n&&_c(s,"_",r,!0)):Lo(t,s)}else t&&$o(e,t)},Ea=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=te;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:Io(r,t,n):(i=!t.$stable,Lo(t,r)),o=t}else t&&($o(e,t),o={default:1});if(i)for(const l in r)!gr(l)&&o[l]==null&&delete r[l]},we=Vo;function Ta(e){return No(e)}function xa(e){return No(e,Bc)}function No(e,t){const n=ls();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:u,setElementText:a,parentNode:d,nextSibling:m,setScopeId:v=tt,insertStaticContent:w}=e,_=(f,h,y,C=null,E=null,T=null,N=void 0,L=null,M=!!h.dynamicChildren)=>{if(f===h)return;f&&!St(f,h)&&(C=Sn(f),ke(f,E,T,!0),f=null),h.patchFlag===-2&&(M=!1,h.dynamicChildren=null);const{type:x,ref:q,shapeFlag:F}=h;switch(x){case Ot:R(f,h,y,C);break;case pe:D(f,h,y,C);break;case nn:f==null&&$(h,y,C,N);break;case Ee:S(f,h,y,C,E,T,N,L,M);break;default:F&1?O(f,h,y,C,E,T,N,L,M):F&6?k(f,h,y,C,E,T,N,L,M):(F&64||F&128)&&x.process(f,h,y,C,E,T,N,L,M,Pt)}q!=null&&E?Ht(q,f&&f.ref,T,h||f,!h):q==null&&f&&f.ref!=null&&Ht(f.ref,null,T,f,!0)},R=(f,h,y,C)=>{if(f==null)s(h.el=l(h.children),y,C);else{const E=h.el=f.el;h.children!==f.children&&u(E,h.children)}},D=(f,h,y,C)=>{f==null?s(h.el=c(h.children||""),y,C):h.el=f.el},$=(f,h,y,C)=>{[f.el,f.anchor]=w(f.children,h,y,C,f.el,f.anchor)},p=({el:f,anchor:h},y,C)=>{let E;for(;f&&f!==h;)E=m(f),s(f,y,C),f=E;s(h,y,C)},g=({el:f,anchor:h})=>{let y;for(;f&&f!==h;)y=m(f),r(f),f=y;r(h)},O=(f,h,y,C,E,T,N,L,M)=>{if(h.type==="svg"?N="svg":h.type==="math"&&(N="mathml"),f==null)B(h,y,C,E,T,N,L,M);else{const x=f.el&&f.el._isVueCE?f.el:null;try{x&&x._beginPatch(),b(f,h,E,T,N,L,M)}finally{x&&x._endPatch()}}},B=(f,h,y,C,E,T,N,L)=>{let M,x;const{props:q,shapeFlag:F,transition:W,dirs:G}=f;if(M=f.el=o(f.type,T,q&&q.is,q),F&8?a(M,f.children):F&16&&U(f.children,M,null,C,E,As(f,T),N,L),G&&Be(f,null,C,"created"),P(M,f,f.scopeId,N,C),q){for(const ee in q)ee!=="value"&&!Tt(ee)&&i(M,ee,null,q[ee],T,C);"value"in q&&i(M,"value",null,q.value,T),(x=q.onVnodeBeforeMount)&&Le(x,C,f)}G&&Be(f,null,C,"beforeMount");const Q=Fo(E,W);Q&&W.beforeEnter(M),s(M,h,y),((x=q&&q.onVnodeMounted)||Q||G)&&we(()=>{try{x&&Le(x,C,f),Q&&W.enter(M),G&&Be(f,null,C,"mounted")}finally{}},E)},P=(f,h,y,C,E)=>{if(y&&v(f,y),C)for(let T=0;T{for(let x=M;x{const L=h.el=f.el;let{patchFlag:M,dynamicChildren:x,dirs:q}=h;M|=f.patchFlag&16;const F=f.props||te,W=h.props||te;let G;if(y&&vt(y,!1),(G=W.onVnodeBeforeUpdate)&&Le(G,y,h,f),q&&Be(h,f,y,"beforeUpdate"),y&&vt(y,!0),(F.innerHTML&&W.innerHTML==null||F.textContent&&W.textContent==null)&&a(L,""),x?A(f.dynamicChildren,x,L,y,C,As(h,E),T):N||V(f,h,L,null,y,C,As(h,E),T,!1),M>0){if(M&16)H(L,F,W,y,E);else if(M&2&&F.class!==W.class&&i(L,"class",null,W.class,E),M&4&&i(L,"style",F.style,W.style,E),M&8){const Q=h.dynamicProps;for(let ee=0;ee{G&&Le(G,y,h,f),q&&Be(h,f,y,"updated")},C)},A=(f,h,y,C,E,T,N)=>{for(let L=0;L{if(h!==y){if(h!==te)for(const T in h)!Tt(T)&&!(T in y)&&i(f,T,h[T],null,E,C);for(const T in y){if(Tt(T))continue;const N=y[T],L=h[T];N!==L&&T!=="value"&&i(f,T,L,N,E,C)}"value"in y&&i(f,"value",h.value,y.value,E)}},S=(f,h,y,C,E,T,N,L,M)=>{const x=h.el=f?f.el:l(""),q=h.anchor=f?f.anchor:l("");let{patchFlag:F,dynamicChildren:W,slotScopeIds:G}=h;G&&(L=L?L.concat(G):G),f==null?(s(x,y,C),s(q,y,C),U(h.children||[],y,q,E,T,N,L,M)):F>0&&F&64&&W&&f.dynamicChildren&&f.dynamicChildren.length===W.length?(A(f.dynamicChildren,W,y,E,T,N,L),(h.key!=null||E&&h===E.subTree)&&vr(f,h,!0)):V(f,h,y,q,E,T,N,L,M)},k=(f,h,y,C,E,T,N,L,M)=>{h.slotScopeIds=L,f==null?h.shapeFlag&512?E.ctx.activate(h,y,C,N,M):j(h,y,C,E,T,N,M):K(f,h,M)},j=(f,h,y,C,E,T,N)=>{const L=f.component=Ia(f,C,E);if(bn(f)&&(L.ctx.renderer=Pt),Na(L,!1,N),L.asyncDep){if(E&&E.registerDep(L,I,N),!f.el){const M=L.subTree=ue(pe);D(null,M,h,y),f.placeholder=M.el}}else I(L,f,h,y,E,T,N)},K=(f,h,y)=>{const C=h.component=f.component;if(va(f,h,y))if(C.asyncDep&&!C.asyncResolved){X(C,h,y);return}else C.next=h,C.update();else h.el=f.el,C.vnode=h},I=(f,h,y,C,E,T,N)=>{const L=()=>{if(f.isMounted){let{next:F,bu:W,u:G,parent:Q,vnode:ee}=f;{const Ce=Do(f);if(Ce){F&&(F.el=ee.el,X(f,F,N)),Ce.asyncDep.then(()=>{we(()=>{f.isUnmounted||x()},E)});return}}let ne=F,ae;vt(f,!1),F?(F.el=ee.el,X(f,F,N)):F=ee,W&&Ss(W),(ae=F.props&&F.props.onVnodeBeforeUpdate)&&Le(ae,Q,F,ee),vt(f,!0);const fe=Cs(f),Ie=f.subTree;f.subTree=fe,_(Ie,fe,d(Ie.el),Sn(Ie),f,E,T),F.el=fe.el,ne===null&&Co(f,fe.el),G&&we(G,E),(ae=F.props&&F.props.onVnodeUpdated)&&we(()=>Le(ae,Q,F,ee),E)}else{let F;const{el:W,props:G}=h,{bm:Q,m:ee,parent:ne,root:ae,type:fe}=f,Ie=Ct(h);if(vt(f,!1),Q&&Ss(Q),!Ie&&(F=G&&G.onVnodeBeforeMount)&&Le(F,ne,h),vt(f,!0),W&&ys){const Ce=()=>{f.subTree=Cs(f),ys(W,f.subTree,f,E,null)};Ie&&fe.__asyncHydrate?fe.__asyncHydrate(W,f,Ce):Ce()}else{ae.ce&&ae.ce._hasShadowRoot()&&ae.ce._injectChildStyle(fe,f.parent?f.parent.type:void 0);const Ce=f.subTree=Cs(f);_(null,Ce,y,C,f,E,T),h.el=Ce.el}if(ee&&we(ee,E),!Ie&&(F=G&&G.onVnodeMounted)){const Ce=h;we(()=>Le(F,ne,Ce),E)}(h.shapeFlag&256||ne&&Ct(ne.vnode)&&ne.vnode.shapeFlag&256)&&f.a&&we(f.a,E),f.isMounted=!0,h=y=C=null}};f.scope.on();const M=f.effect=new Pi(L);f.scope.off();const x=f.update=M.run.bind(M),q=f.job=M.runIfDirty.bind(M);q.i=f,q.id=f.uid,M.scheduler=()=>fr(q),vt(f,!0),x()},X=(f,h,y)=>{h.component=f;const C=f.vnode.props;f.vnode=h,f.next=null,ba(f,h.props,C,y),Ea(f,h.children,y),nt(),Fr(f),st()},V=(f,h,y,C,E,T,N,L,M=!1)=>{const x=f&&f.children,q=f?f.shapeFlag:0,F=h.children,{patchFlag:W,shapeFlag:G}=h;if(W>0){if(W&128){mt(x,F,y,C,E,T,N,L,M);return}else if(W&256){J(x,F,y,C,E,T,N,L,M);return}}G&8?(q&16&&Bt(x,E,T),F!==x&&a(y,F)):q&16?G&16?mt(x,F,y,C,E,T,N,L,M):Bt(x,E,T,!0):(q&8&&a(y,""),G&16&&U(F,y,C,E,T,N,L,M))},J=(f,h,y,C,E,T,N,L,M)=>{f=f||Dt,h=h||Dt;const x=f.length,q=h.length,F=Math.min(x,q);let W;for(W=0;Wq?Bt(f,E,T,!0,!1,F):U(h,y,C,E,T,N,L,M,F)},mt=(f,h,y,C,E,T,N,L,M)=>{let x=0;const q=h.length;let F=f.length-1,W=q-1;for(;x<=F&&x<=W;){const G=f[x],Q=h[x]=M?Qe(h[x]):$e(h[x]);if(St(G,Q))_(G,Q,y,null,E,T,N,L,M);else break;x++}for(;x<=F&&x<=W;){const G=f[F],Q=h[W]=M?Qe(h[W]):$e(h[W]);if(St(G,Q))_(G,Q,y,null,E,T,N,L,M);else break;F--,W--}if(x>F){if(x<=W){const G=W+1,Q=GW)for(;x<=F;)ke(f[x],E,T,!0),x++;else{const G=x,Q=x,ee=new Map;for(x=Q;x<=W;x++){const Ae=h[x]=M?Qe(h[x]):$e(h[x]);Ae.key!=null&&ee.set(Ae.key,x)}let ne,ae=0;const fe=W-Q+1;let Ie=!1,Ce=0;const Kt=new Array(fe);for(x=0;x=fe){ke(Ae,E,T,!0);continue}let Ue;if(Ae.key!=null)Ue=ee.get(Ae.key);else for(ne=Q;ne<=W;ne++)if(Kt[ne-Q]===0&&St(Ae,h[ne])){Ue=ne;break}Ue===void 0?ke(Ae,E,T,!0):(Kt[Ue-Q]=x+1,Ue>=Ce?Ce=Ue:Ie=!0,_(Ae,h[Ue],y,null,E,T,N,L,M),ae++)}const Mr=Ie?Ca(Kt):Dt;for(ne=Mr.length-1,x=fe-1;x>=0;x--){const Ae=Q+x,Ue=h[Ae],Rr=h[Ae+1],Pr=Ae+1{const{el:T,type:N,transition:L,children:M,shapeFlag:x}=f;if(x&6){Ve(f.component.subTree,h,y,C);return}if(x&128){f.suspense.move(h,y,C);return}if(x&64){N.move(f,h,y,Pt);return}if(N===Ee){s(T,h,y);for(let F=0;FL.enter(T),E);else{const{leave:F,delayLeave:W,afterLeave:G}=L,Q=()=>{f.ctx.isUnmounted?r(T):s(T,h,y)},ee=()=>{T._isLeaving&&T[Ke](!0),F(T,()=>{Q(),G&&G()})};W?W(T,Q,ee):ee()}else s(T,h,y)},ke=(f,h,y,C=!1,E=!1)=>{const{type:T,props:N,ref:L,children:M,dynamicChildren:x,shapeFlag:q,patchFlag:F,dirs:W,cacheIndex:G,memo:Q}=f;if(F===-2&&(E=!1),L!=null&&(nt(),Ht(L,null,y,f,!0),st()),G!=null&&(h.renderCache[G]=void 0),q&256){h.ctx.deactivate(f);return}const ee=q&1&&W,ne=!Ct(f);let ae;if(ne&&(ae=N&&N.onVnodeBeforeUnmount)&&Le(ae,h,f),q&6)_l(f.component,y,C);else{if(q&128){f.suspense.unmount(y,C);return}ee&&Be(f,null,h,"beforeUnmount"),q&64?f.type.remove(f,h,y,Pt,C):x&&!x.hasOnce&&(T!==Ee||F>0&&F&64)?Bt(x,h,y,!1,!0):(T===Ee&&F&384||!E&&q&16)&&Bt(M,h,y),C&&Ar(f)}const fe=Q!=null&&G==null;(ne&&(ae=N&&N.onVnodeUnmounted)||ee||fe)&&we(()=>{ae&&Le(ae,h,f),ee&&Be(f,null,h,"unmounted"),fe&&(f.el=null)},y)},Ar=f=>{const{type:h,el:y,anchor:C,transition:E}=f;if(h===Ee){bl(y,C);return}if(h===nn){g(f);return}const T=()=>{r(y),E&&!E.persisted&&E.afterLeave&&E.afterLeave()};if(f.shapeFlag&1&&E&&!E.persisted){const{leave:N,delayLeave:L}=E,M=()=>N(y,T);L?L(f.el,T,M):M()}else T()},bl=(f,h)=>{let y;for(;f!==h;)y=m(f),r(f),f=y;r(h)},_l=(f,h,y)=>{const{bum:C,scope:E,job:T,subTree:N,um:L,m:M,a:x}=f;Jr(M),Jr(x),C&&Ss(C),E.stop(),T&&(T.flags|=8,ke(N,f,h,y)),L&&we(L,h),we(()=>{f.isUnmounted=!0},h)},Bt=(f,h,y,C=!1,E=!1,T=0)=>{for(let N=T;N{if(f.shapeFlag&6)return Sn(f.component.subTree);if(f.shapeFlag&128)return f.suspense.next();const h=m(f.anchor||f.el),y=h&&h[lo];return y?m(y):h};let ms=!1;const Or=(f,h,y)=>{let C;f==null?h._vnode&&(ke(h._vnode,null,null,!0),C=h._vnode.component):_(h._vnode||null,f,h,null,null,null,y),h._vnode=f,ms||(ms=!0,Fr(C),Bn(),ms=!1)},Pt={p:_,um:ke,m:Ve,r:Ar,mt:j,mc:U,pc:V,pbc:A,n:Sn,o:e};let vs,ys;return t&&([vs,ys]=t(Pt)),{render:Or,hydrate:vs,createApp:fa(Or,vs)}}function As({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function vt({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Fo(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function vr(e,t,n=!1){const s=e.children,r=t.children;if(Y(s)&&Y(r))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function Do(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Do(t)}function Jr(e){if(e)for(let t=0;te.__isSuspense;function Vo(e,t){t&&t.pendingBranch?Y(e)?t.effects.push(...e):t.effects.push(e):Rc(e)}const Ee=Symbol.for("v-fgt"),Ot=Symbol.for("v-txt"),pe=Symbol.for("v-cmt"),nn=Symbol.for("v-stc"),sn=[];let Oe=null;function zs(e=!1){sn.push(Oe=e?null:[])}function Aa(){sn.pop(),Oe=sn[sn.length-1]||null}let hn=1;function zn(e,t=!1){hn+=e,e<0&&Oe&&t&&(Oe.hasOnce=!0)}function ko(e){return e.dynamicChildren=hn>0?Oe||Dt:null,Aa(),hn>0&&Oe&&Oe.push(e),e}function Zf(e,t,n,s,r,i){return ko(Wo(e,t,n,s,r,i,!0))}function Xs(e,t,n,s,r){return ko(ue(e,t,n,s,r,!0))}function pn(e){return e?e.__v_isVNode===!0:!1}function St(e,t){return e.type===t.type&&e.key===t.key}const Uo=({key:e})=>e??null,Fn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?be(e)||ce(e)||z(e)?{i:me,r:e,k:t,f:!!n}:e:null);function Wo(e,t=null,n=null,s=0,r=null,i=e===Ee?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Uo(t),ref:t&&Fn(t),scopeId:ro,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:me};return l?(yr(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=be(n)?8:16),hn>0&&!o&&Oe&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&Oe.push(c),c}const ue=Oa;function Oa(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===yo)&&(e=pe),pn(e)){const l=ht(e,t,!0);return n&&yr(l,n),hn>0&&!i&&Oe&&(l.shapeFlag&6?Oe[Oe.indexOf(e)]=l:Oe.push(l)),l.patchFlag=-2,l}if(Ha(e)&&(e=e.__vccOpts),t){t=Ma(t);let{class:l,style:c}=t;l&&!be(l)&&(t.class=ar(l)),le(c)&&(ns(c)&&!Y(c)&&(c=xe({},c)),t.style=cr(c))}const o=be(e)?1:Ho(e)?128:co(e)?64:le(e)?4:z(e)?2:0;return Wo(e,t,n,s,r,o,i,!0)}function Ma(e){return e?ns(e)||Mo(e)?xe({},e):e:null}function ht(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,u=t?Pa(r||{},t):r,a={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&Uo(u),ref:t&&t.ref?n&&i?Y(i)?i.concat(Fn(t)):[i,Fn(t)]:Fn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Ee?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&ht(e.ssContent),ssFallback:e.ssFallback&&ht(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&s&&dn(a,c.clone(a)),a}function Bo(e=" ",t=0){return ue(Ot,null,e,t)}function ed(e,t){const n=ue(nn,null,e);return n.staticCount=t,n}function Ra(e="",t=!1){return t?(zs(),Xs(pe,null,e)):ue(pe,null,e)}function $e(e){return e==null||typeof e=="boolean"?ue(pe):Y(e)?ue(Ee,null,e.slice()):pn(e)?Qe(e):ue(Ot,null,String(e))}function Qe(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:ht(e)}function yr(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(Y(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),yr(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!Mo(t)?t._ctx=me:r===3&&me&&(me.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else z(t)?(t={default:t,_ctx:me},n=32):(t=String(t),s&64?(n=16,t=[Bo(t)]):n=8);e.children=t,e.shapeFlag|=n}function Pa(...e){const t={};for(let n=0;nge||me;let Xn,Ys;{const e=ls(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};Xn=t("__VUE_INSTANCE_SETTERS__",n=>ge=n),Ys=t("__VUE_SSR_SETTERS__",n=>Ut=n)}const _n=e=>{const t=ge;return Xn(e),e.scope.on(),()=>{e.scope.off(),Xn(t)}},Qr=()=>{ge&&ge.scope.off(),Xn(null)};function Ko(e){return e.vnode.shapeFlag&4}let Ut=!1;function Na(e,t=!1,n=!1){t&&Ys(t);const{props:s,children:r}=e.vnode,i=Ko(e);ya(e,s,i,t),Sa(e,r,n||t);const o=i?Fa(e,t):void 0;return t&&Ys(!1),o}function Fa(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,sa);const{setup:s}=n;if(s){nt();const r=e.setupContext=s.length>1?Go(e):null,i=_n(e),o=vn(s,e,0,[e.props,r]),l=Ji(o);if(st(),i(),(l||e.sp)&&!Ct(e)&&pr(e),l){if(o.then(Qr,Qr),t)return o.then(c=>{Zr(e,c)}).catch(c=>{yn(c,e,0)});e.asyncDep=o}else Zr(e,o)}else qo(e)}function Zr(e,t,n){z(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:le(t)&&(e.setupState=zi(t)),qo(e)}function qo(e,t,n){const s=e.type;e.render||(e.render=s.render||tt);{const r=_n(e);nt();try{ia(e)}finally{st(),r()}}}const Da={get(e,t){return ve(e,"get",""),e[t]}};function Go(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Da),slots:e.slots,emit:e.emit,expose:t}}function hs(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(zi(In(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in tn)return tn[n](e)},has(t,n){return n in t||n in tn}})):e.proxy}function ja(e,t=!0){return z(e)?e.displayName||e.name:e.name||t&&e.__name}function Ha(e){return z(e)&&"__vccOpts"in e}const ie=(e,t)=>uc(e,t,Ut);function Js(e,t,n){try{zn(-1);const s=arguments.length;return s===2?le(t)&&!Y(t)?pn(t)?ue(e,null,[t]):ue(e,t):ue(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&pn(n)&&(n=[n]),ue(e,t,n))}finally{zn(1)}}const Va="3.5.33";function ka(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const Ua=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Wa=e=>e.startsWith("onUpdate:"),br=Object.assign,wn=Array.isArray,zo=e=>typeof e=="function",ft=e=>typeof e=="string",Ba=e=>typeof e=="symbol",Ka=e=>e!==null&&typeof e=="object",_r=e=>{const t=Object.create(null);return(n=>t[n]||(t[n]=e(n)))},qa=/-\w/g,Dn=_r(e=>e.replace(qa,t=>t.slice(1).toUpperCase())),Ga=/\B([A-Z])/g,wr=_r(e=>e.replace(Ga,"-$1").toLowerCase()),za=_r(e=>e.charAt(0).toUpperCase()+e.slice(1)),Xa=(e,...t)=>{for(let n=0;n{const t=parseFloat(e);return isNaN(t)?e:t},Ya=e=>{const t=ft(e)?Number(e):NaN;return isNaN(t)?e:t},Ja="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",Qa=ka(Ja);function Yo(e){return!!e||e===""}let Qs;const ei=typeof window<"u"&&window.trustedTypes;if(ei)try{Qs=ei.createPolicy("vue",{createHTML:e=>e})}catch{}const Jo=Qs?e=>Qs.createHTML(e):e=>e,Za="http://www.w3.org/2000/svg",eu="http://www.w3.org/1998/Math/MathML",Je=typeof document<"u"?document:null,ti=Je&&Je.createElement("template"),tu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?Je.createElementNS(Za,e):t==="mathml"?Je.createElementNS(eu,e):n?Je.createElement(e,{is:n}):Je.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Je.createTextNode(e),createComment:e=>Je.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Je.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{ti.innerHTML=Jo(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const l=ti.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},ot="transition",zt="animation",gn=Symbol("_vtc"),Qo={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},nu=br({},ao,Qo),su=e=>(e.displayName="Transition",e.props=nu,e),td=su((e,{slots:t})=>Js(kc,ru(e),t)),yt=(e,t=[])=>{wn(e)?e.forEach(n=>n(...t)):e&&e(...t)},ni=e=>e?wn(e)?e.some(t=>t.length>1):e.length>1:!1;function ru(e){const t={};for(const S in e)S in Qo||(t[S]=e[S]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:u=o,appearToClass:a=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:m=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,w=iu(r),_=w&&w[0],R=w&&w[1],{onBeforeEnter:D,onEnter:$,onEnterCancelled:p,onLeave:g,onLeaveCancelled:O,onBeforeAppear:B=D,onAppear:P=$,onAppearCancelled:U=p}=t,b=(S,k,j,K)=>{S._enterCancelled=K,bt(S,k?a:l),bt(S,k?u:o),j&&j()},A=(S,k)=>{S._isLeaving=!1,bt(S,d),bt(S,v),bt(S,m),k&&k()},H=S=>(k,j)=>{const K=S?P:$,I=()=>b(k,S,j);yt(K,[k,I]),si(()=>{bt(k,S?c:i),Ye(k,S?a:l),ni(K)||ri(k,s,_,I)})};return br(t,{onBeforeEnter(S){yt(D,[S]),Ye(S,i),Ye(S,o)},onBeforeAppear(S){yt(B,[S]),Ye(S,c),Ye(S,u)},onEnter:H(!1),onAppear:H(!0),onLeave(S,k){S._isLeaving=!0;const j=()=>A(S,k);Ye(S,d),S._enterCancelled?(Ye(S,m),li(S)):(li(S),Ye(S,m)),si(()=>{S._isLeaving&&(bt(S,d),Ye(S,v),ni(g)||ri(S,s,R,j))}),yt(g,[S,j])},onEnterCancelled(S){b(S,!1,void 0,!0),yt(p,[S])},onAppearCancelled(S){b(S,!0,void 0,!0),yt(U,[S])},onLeaveCancelled(S){A(S),yt(O,[S])}})}function iu(e){if(e==null)return null;if(Ka(e))return[Os(e.enter),Os(e.leave)];{const t=Os(e);return[t,t]}}function Os(e){return Ya(e)}function Ye(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[gn]||(e[gn]=new Set)).add(t)}function bt(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[gn];n&&(n.delete(t),n.size||(e[gn]=void 0))}function si(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let ou=0;function ri(e,t,n,s){const r=e._endId=++ou,i=()=>{r===e._endId&&s()};if(n!=null)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=lu(e,t);if(!o)return s();const u=o+"end";let a=0;const d=()=>{e.removeEventListener(u,m),i()},m=v=>{v.target===e&&++a>=c&&d()};setTimeout(()=>{a(n[w]||"").split(", "),r=s(`${ot}Delay`),i=s(`${ot}Duration`),o=ii(r,i),l=s(`${zt}Delay`),c=s(`${zt}Duration`),u=ii(l,c);let a=null,d=0,m=0;t===ot?o>0&&(a=ot,d=o,m=i.length):t===zt?u>0&&(a=zt,d=u,m=c.length):(d=Math.max(o,u),a=d>0?o>u?ot:zt:null,m=a?a===ot?i.length:c.length:0);const v=a===ot&&/\b(?:transform|all)(?:,|$)/.test(s(`${ot}Property`).toString());return{type:a,timeout:d,propCount:m,hasTransform:v}}function ii(e,t){for(;e.lengthoi(n)+oi(e[s])))}function oi(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function li(e){return(e?e.ownerDocument:document).body.offsetHeight}function cu(e,t,n){const s=e[gn];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const ci=Symbol("_vod"),au=Symbol("_vsh"),uu=Symbol(""),fu=/(?:^|;)\s*display\s*:/;function du(e,t,n){const s=e.style,r=ft(n);let i=!1;if(n&&!r){if(t)if(ft(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();n[l]==null&&Jt(s,l,"")}else for(const o in t)n[o]==null&&Jt(s,o,"");for(const o in n){o==="display"&&(i=!0);const l=n[o];l!=null?pu(e,o,!ft(t)&&t?t[o]:void 0,l)||Jt(s,o,l):Jt(s,o,"")}}else if(r){if(t!==n){const o=s[uu];o&&(n+=";"+o),s.cssText=n,i=fu.test(n)}}else t&&e.removeAttribute("style");ci in e&&(e[ci]=i?s.display:"",e[au]&&(s.display="none"))}const ai=/\s*!important$/;function Jt(e,t,n){if(wn(n))n.forEach(s=>Jt(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=hu(e,t);ai.test(n)?e.setProperty(wr(s),n.replace(ai,""),"important"):e[s]=n}}const ui=["Webkit","Moz","ms"],Ms={};function hu(e,t){const n=Ms[t];if(n)return n;let s=je(t);if(s!=="filter"&&s in e)return Ms[t]=s;s=za(s);for(let r=0;rRs||(yu.then(()=>Rs=0),Rs=Date.now());function _u(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;He(wu(s,n.value),t,5,[s])};return n.value=e,n.attached=bu(),n}function wu(e,t){if(wn(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const mi=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Su=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?cu(e,s,o):t==="style"?du(e,n,s):Ua(t)?Wa(t)||mu(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Eu(e,t,s,o))?(hi(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&di(e,t,s,o,i,t!=="value")):e._isVueCE&&(Tu(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!ft(s)))?hi(e,Dn(t),s,i,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),di(e,t,s,o))};function Eu(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&mi(t)&&zo(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return mi(t)&&ft(n)?!1:t in e}function Tu(e,t){const n=e._def.props;if(!n)return!1;const s=Dn(t);return Array.isArray(n)?n.some(r=>Dn(r)===s):Object.keys(n).some(r=>Dn(r)===s)}const vi=e=>{const t=e.props["onUpdate:modelValue"]||!1;return wn(t)?n=>Xa(t,n):t};function xu(e){e.target.composing=!0}function yi(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Ps=Symbol("_assign");function bi(e,t,n){return t&&(e=e.trim()),n&&(e=Xo(e)),e}const nd={created(e,{modifiers:{lazy:t,trim:n,number:s}},r){e[Ps]=vi(r);const i=s||r.props&&r.props.type==="number";Nt(e,t?"change":"input",o=>{o.target.composing||e[Ps](bi(e.value,n,i))}),(n||i)&&Nt(e,"change",()=>{e.value=bi(e.value,n,i)}),t||(Nt(e,"compositionstart",xu),Nt(e,"compositionend",yi),Nt(e,"change",yi))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:r,number:i}},o){if(e[Ps]=vi(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?Xo(e.value):e.value,c=t??"";if(l===c)return;const u=e.getRootNode();(u instanceof Document||u instanceof ShadowRoot)&&u.activeElement===e&&e.type!=="range"&&(s&&t===n||r&&e.value.trim()===c)||(e.value=c)}},Cu=["ctrl","shift","alt","meta"],Au={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Cu.some(n=>e[`${n}Key`]&&!t.includes(n))},sd=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=((r,...i)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=(r=>{if(!("key"in r))return;const i=wr(r.key);if(t.some(o=>o===i||Ou[o]===i))return e(r)}))},Zo=br({patchProp:Su},tu);let rn,_i=!1;function Mu(){return rn||(rn=Ta(Zo))}function Ru(){return rn=_i?rn:xa(Zo),_i=!0,rn}const id=((...e)=>{const t=Mu().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=tl(s);if(!r)return;const i=t._component;!zo(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=n(r,!1,el(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t}),od=((...e)=>{const t=Ru().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=tl(s);if(r)return n(r,!0,el(r))},t});function el(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function tl(e){return ft(e)?document.querySelector(e):e}const ld=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},Pu=window.__VP_SITE_DATA__;function nl(e,t){return nr()?($l(e,t),!0):!1}const Ls=new WeakMap,Lu=(...e)=>{var t;const n=e[0],s=(t=gt())===null||t===void 0?void 0:t.proxy,r=s??nr();if(r==null&&!io())throw new Error("injectLocal must be called in setup");return r&&Ls.has(r)&&n in Ls.get(r)?Ls.get(r)[n]:xt(...e)},sl=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const $u=e=>e!=null,Iu=Object.prototype.toString,Nu=e=>Iu.call(e)==="[object Object]",pt=()=>{},wi=Fu();function Fu(){var e,t,n;return sl&&!!(!((e=window)===null||e===void 0||(e=e.navigator)===null||e===void 0)&&e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window)===null||t===void 0||(t=t.navigator)===null||t===void 0?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test((n=window)===null||n===void 0?void 0:n.navigator.userAgent))}function Sr(...e){if(e.length!==1)return lc(...e);const t=e[0];return typeof t=="function"?an(rc(()=>({get:t,set:pt}))):Fe(t)}function Er(e,t){function n(...s){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(r).catch(i)})}return n}const rl=e=>e();function il(e,t={}){let n,s,r=pt;const i=c=>{clearTimeout(c),r(),r=pt};let o;return c=>{const u=oe(e),a=oe(t.maxWait);return n&&i(n),u<=0||a!==void 0&&a<=0?(s&&(i(s),s=void 0),Promise.resolve(c())):new Promise((d,m)=>{r=t.rejectOnCancel?m:d,o=c,a&&!s&&(s=setTimeout(()=>{n&&i(n),s=void 0,d(o())},a)),n=setTimeout(()=>{s&&i(s),s=void 0,d(c())},u)})}}function Du(...e){let t=0,n,s=!0,r=pt,i,o,l,c,u;!ce(e[0])&&typeof e[0]=="object"?{delay:o,trailing:l=!0,leading:c=!0,rejectOnCancel:u=!1}=e[0]:[o,l=!0,c=!0,u=!1]=e;const a=()=>{n&&(clearTimeout(n),n=void 0,r(),r=pt)};return m=>{const v=oe(o),w=Date.now()-t,_=()=>i=m();return a(),v<=0?(t=Date.now(),_()):(w>v?(t=Date.now(),(c||!s)&&_()):l&&(i=new Promise((R,D)=>{r=u?D:R,n=setTimeout(()=>{t=Date.now(),s=!0,R(_()),a()},Math.max(0,v-w))})),!c&&!n&&(n=setTimeout(()=>s=!0,v)),s=!1,i)}}function ju(e=rl,t={}){const{initialState:n="active"}=t,s=Sr(n==="active");function r(){s.value=!1}function i(){s.value=!0}return{isActive:ec(s),pause:r,resume:i,eventFilter:(...l)=>{s.value&&e(...l)}}}function Si(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function jn(e){return Array.isArray(e)?e:[e]}function ol(e){return gt()}function Hu(e,t=200,n={}){return Er(il(t,n),e)}function Vu(e,t=200,n=!1,s=!0,r=!1){return Er(Du(t,n,s,r),e)}function ll(e,t,n={}){const{eventFilter:s=rl,...r}=n;return Re(e,Er(s,t),r)}function ku(e,t,n={}){const{eventFilter:s,initialState:r="active",...i}=n,{eventFilter:o,pause:l,resume:c,isActive:u}=ju(s,{initialState:r});return{stop:ll(e,t,{...i,eventFilter:o}),pause:l,resume:c,isActive:u}}function ps(e,t=!0,n){ol()?Wt(e,n):t?e():cs(e)}function Uu(e,t){ol()&&fs(e,t)}function cd(e,t,n={}){const{debounce:s=0,maxWait:r=void 0,...i}=n;return ll(e,t,{...i,eventFilter:il(s,{maxWait:r})})}function Wu(e,t,n){return Re(e,t,{...n,immediate:!0})}function ad(e,t,n){var s;let r;ce(n)?r={evaluating:n}:r=n||{};const{lazy:i=!1,flush:o="sync",evaluating:l=void 0,shallow:c=!0,onError:u=(s=globalThis.reportError)!==null&&s!==void 0?s:pt}=r,a=ye(!i),d=c?ye(t):Fe(t);let m=0;return dr(async v=>{if(!a.value)return;m++;const w=m;let _=!1;l&&Promise.resolve().then(()=>{l.value=!0});try{const R=await e(D=>{v(()=>{l&&(l.value=!1),_||D()})});w===m&&(d.value=R)}catch(R){u(R)}finally{l&&w===m&&(l.value=!1),_=!0}},{flush:o}),i?ie(()=>(a.value=!0,d.value)):d}const Te=sl?window:void 0;function gs(e){var t;const n=oe(e);return(t=n?.$el)!==null&&t!==void 0?t:n}function ze(...e){const t=(s,r,i,o)=>(s.addEventListener(r,i,o),()=>s.removeEventListener(r,i,o)),n=ie(()=>{const s=jn(oe(e[0])).filter(r=>r!=null);return s.every(r=>typeof r!="string")?s:void 0});return Wu(()=>{var s,r;return[(s=(r=n.value)===null||r===void 0?void 0:r.map(i=>gs(i)))!==null&&s!==void 0?s:[Te].filter(i=>i!=null),jn(oe(n.value?e[1]:e[0])),jn(ss(n.value?e[2]:e[1])),oe(n.value?e[3]:e[2])]},([s,r,i,o],l,c)=>{if(!s?.length||!r?.length||!i?.length)return;const u=Nu(o)?{...o}:o,a=s.flatMap(d=>r.flatMap(m=>i.map(v=>t(d,m,v,u))));c(()=>{a.forEach(d=>d())})},{flush:"post"})}function Bu(){const e=ye(!1),t=gt();return t&&Wt(()=>{e.value=!0},t),e}function Tr(e){const t=Bu();return ie(()=>(t.value,!!e()))}function Ku(e,t,n={}){const{window:s=Te,...r}=n;let i;const o=Tr(()=>s&&"MutationObserver"in s),l=()=>{i&&(i.disconnect(),i=void 0)},c=Re(ie(()=>{const d=jn(oe(e)).map(gs).filter($u);return new Set(d)}),d=>{l(),o.value&&d.size&&(i=new MutationObserver(t),d.forEach(m=>i.observe(m,r)))},{immediate:!0,flush:"post"}),u=()=>i?.takeRecords(),a=()=>{c(),l()};return nl(a),{isSupported:o,stop:a,takeRecords:u}}function qu(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function ud(...e){let t,n,s={};e.length===3?(t=e[0],n=e[1],s=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,n=e[0],s=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);const{target:r=Te,eventName:i="keydown",passive:o=!1,dedupe:l=!1}=s,c=qu(t);return ze(r,i,a=>{a.repeat&&oe(l)||c(a)&&n(a)},o)}const Gu=Symbol("vueuse-ssr-width");function zu(){const e=io()?Lu(Gu,null):null;return typeof e=="number"?e:void 0}function cl(e,t={}){const{window:n=Te,ssrWidth:s=zu()}=t,r=Tr(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function"),i=ye(typeof s=="number"),o=ye(),l=ye(!1),c=u=>{l.value=u.matches};return dr(()=>{if(i.value){i.value=!r.value,l.value=oe(e).split(",").some(u=>{const a=u.includes("not all"),d=u.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),m=u.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/);let v=!!(d||m);return d&&v&&(v=s>=Si(d[1])),m&&v&&(v=s<=Si(m[1])),a?!v:v});return}r.value&&(o.value=n.matchMedia(oe(e)),l.value=o.value.matches)}),ze(o,"change",c,{passive:!0}),ie(()=>l.value)}const Rn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Pn="__vueuse_ssr_handlers__",Xu=Yu();function Yu(){return Pn in Rn||(Rn[Pn]=Rn[Pn]||{}),Rn[Pn]}function al(e,t){return Xu[e]||t}function ul(e){return cl("(prefers-color-scheme: dark)",e)}function Ju(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Qu={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Ei="vueuse-storage";function xr(e,t,n,s={}){var r;const{flush:i="pre",deep:o=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:u=!1,shallow:a,window:d=Te,eventFilter:m,onError:v=j=>{console.error(j)},initOnMounted:w}=s,_=(a?ye:Fe)(typeof t=="function"?t():t),R=ie(()=>oe(e));if(!n)try{n=al("getDefaultStorage",()=>Te?.localStorage)()}catch(j){v(j)}if(!n)return _;const D=oe(t),$=Ju(D),p=(r=s.serializer)!==null&&r!==void 0?r:Qu[$],{pause:g,resume:O}=ku(_,j=>A(j),{flush:i,deep:o,eventFilter:m});Re(R,()=>S(),{flush:i});let B=!1;const P=j=>{w&&!B||S(j)},U=j=>{w&&!B||k(j)};d&&l&&(n instanceof Storage?ze(d,"storage",P,{passive:!0}):ze(d,Ei,U)),w?ps(()=>{B=!0,S()}):S();function b(j,K){if(d){const I={key:R.value,oldValue:j,newValue:K,storageArea:n};d.dispatchEvent(n instanceof Storage?new StorageEvent("storage",I):new CustomEvent(Ei,{detail:I}))}}function A(j){try{const K=n.getItem(R.value);if(j==null)b(K,null),n.removeItem(R.value);else{const I=p.write(j);K!==I&&(n.setItem(R.value,I),b(K,I))}}catch(K){v(K)}}function H(j){const K=j?j.newValue:n.getItem(R.value);if(K==null)return c&&D!=null&&n.setItem(R.value,p.write(D)),D;if(!j&&u){const I=p.read(K);return typeof u=="function"?u(I,D):$==="object"&&!Array.isArray(I)?{...D,...I}:I}else return typeof K!="string"?K:p.read(K)}function S(j){if(!(j&&j.storageArea!==n)){if(j&&j.key==null){_.value=D;return}if(!(j&&j.key!==R.value)){g();try{const K=p.write(_.value);(j===void 0||j?.newValue!==K)&&(_.value=H(j))}catch(K){v(K)}finally{j?cs(O):O()}}}}function k(j){S(j.detail)}return _}const Zu="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";function ef(e={}){const{selector:t="html",attribute:n="class",initialValue:s="auto",window:r=Te,storage:i,storageKey:o="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:u,disableTransition:a=!0}=e,d={auto:"",light:"light",dark:"dark",...e.modes||{}},m=ul({window:r}),v=ie(()=>m.value?"dark":"light"),w=c||(o==null?Sr(s):xr(o,s,i,{window:r,listenToStorageChanges:l})),_=ie(()=>w.value==="auto"?v.value:w.value),R=al("updateHTMLAttrs",(g,O,B)=>{const P=typeof g=="string"?r?.document.querySelector(g):gs(g);if(!P)return;const U=new Set,b=new Set;let A=null;if(O==="class"){const S=B.split(/\s/g);Object.values(d).flatMap(k=>(k||"").split(/\s/g)).filter(Boolean).forEach(k=>{S.includes(k)?U.add(k):b.add(k)})}else A={key:O,value:B};if(U.size===0&&b.size===0&&A===null)return;let H;a&&(H=r.document.createElement("style"),H.appendChild(document.createTextNode(Zu)),r.document.head.appendChild(H));for(const S of U)P.classList.add(S);for(const S of b)P.classList.remove(S);A&&P.setAttribute(A.key,A.value),a&&(r.getComputedStyle(H).opacity,document.head.removeChild(H))});function D(g){var O;R(t,n,(O=d[g])!==null&&O!==void 0?O:g)}function $(g){e.onChanged?e.onChanged(g,D):D(g)}Re(_,$,{flush:"post",immediate:!0}),ps(()=>$(_.value));const p=ie({get(){return u?w.value:_.value},set(g){w.value=g}});return Object.assign(p,{store:w,system:v,state:_})}function tf(e={}){const{valueDark:t="dark",valueLight:n=""}=e,s=ef({...e,onChanged:(i,o)=>{var l;e.onChanged?(l=e.onChanged)===null||l===void 0||l.call(e,i==="dark",o,i):o(i)},modes:{dark:t,light:n}}),r=ie(()=>s.system.value);return ie({get(){return s.value==="dark"},set(i){const o=i?"dark":"light";r.value===o?s.value="auto":s.value=o}})}function $s(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}const Ti=1;function nf(e,t={}){const{throttle:n=0,idle:s=200,onStop:r=pt,onScroll:i=pt,offset:o={left:0,right:0,top:0,bottom:0},observe:l={mutation:!1},eventListenerOptions:c={capture:!1,passive:!0},behavior:u="auto",window:a=Te,onError:d=b=>{console.error(b)}}=t,m=typeof l=="boolean"?{mutation:l}:l,v=ye(0),w=ye(0),_=ie({get(){return v.value},set(b){D(b,void 0)}}),R=ie({get(){return w.value},set(b){D(void 0,b)}});function D(b,A){var H,S,k,j;if(!a)return;const K=oe(e);if(!K)return;(H=K instanceof Document?a.document.body:K)===null||H===void 0||H.scrollTo({top:(S=oe(A))!==null&&S!==void 0?S:R.value,left:(k=oe(b))!==null&&k!==void 0?k:_.value,behavior:oe(u)});const I=(K==null||(j=K.document)===null||j===void 0?void 0:j.documentElement)||K?.documentElement||K;_!=null&&(v.value=I.scrollLeft),R!=null&&(w.value=I.scrollTop)}const $=ye(!1),p=Vt({left:!0,right:!1,top:!0,bottom:!1}),g=Vt({left:!1,right:!1,top:!1,bottom:!1}),O=b=>{$.value&&($.value=!1,g.left=!1,g.right=!1,g.top=!1,g.bottom=!1,r(b))},B=Hu(O,n+s),P=b=>{var A;if(!a)return;const H=(b==null||(A=b.document)===null||A===void 0?void 0:A.documentElement)||b?.documentElement||gs(b),{display:S,flexDirection:k,direction:j}=a.getComputedStyle(H),K=j==="rtl"?-1:1,I=H.scrollLeft;g.left=Iv.value;const X=Math.abs(I*K)<=(o.left||0),V=Math.abs(I*K)+H.clientWidth>=H.scrollWidth-(o.right||0)-Ti;S==="flex"&&k==="row-reverse"?(p.left=V,p.right=X):(p.left=X,p.right=V),v.value=I;let J=H.scrollTop;b===a.document&&!J&&(J=a.document.body.scrollTop),g.top=Jw.value;const mt=Math.abs(J)<=(o.top||0),Ve=Math.abs(J)+H.clientHeight>=H.scrollHeight-(o.bottom||0)-Ti;S==="flex"&&k==="column-reverse"?(p.top=Ve,p.bottom=mt):(p.top=mt,p.bottom=Ve),w.value=J},U=b=>{var A;a&&(P((A=b.target.documentElement)!==null&&A!==void 0?A:b.target),$.value=!0,B(b),i(b))};return ze(e,"scroll",n?Vu(U,n,!0,!1):U,c),ps(()=>{try{const b=oe(e);if(!b)return;P(b)}catch(b){d(b)}}),m?.mutation&&e!=null&&e!==a&&e!==document&&Ku(e,()=>{const b=oe(e);b&&P(b)},{attributes:!0,childList:!0,subtree:!0}),ze(e,"scrollend",O,c),{x:_,y:R,isScrolling:$,arrivedState:p,directions:g,measure(){const b=oe(e);a&&b&&P(b)}}}function fd(e,t,n={}){const{window:s=Te}=n;return xr(e,t,s?.localStorage,n)}function dd(e={}){const{window:t=Te}=e,n=t?.navigator,s=Tr(()=>n&&"language"in n),r=ye(n?.language);return ze(t,"languagechange",()=>{n&&(r.value=n.language)},{passive:!0}),{isSupported:s,language:r}}function fl(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const Is=new WeakMap;function hd(e,t=!1){const n=ye(t);let s=null,r="";Re(Sr(e),l=>{const c=$s(oe(l));if(c){const u=c;if(Is.get(u)||Is.set(u,u.style.overflow),u.style.overflow!=="hidden"&&(r=u.style.overflow),u.style.overflow==="hidden")return n.value=!0;if(n.value)return u.style.overflow="hidden"}},{immediate:!0});const i=()=>{const l=$s(oe(e));!l||n.value||(wi&&(s=ze(l,"touchmove",c=>{sf(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{const l=$s(oe(e));!l||!n.value||(wi&&s?.(),l.style.overflow=r,Is.delete(l),n.value=!1)};return nl(o),ie({get(){return n.value},set(l){l?i():o()}})}function pd(e,t,n={}){const{window:s=Te}=n;return xr(e,t,s?.sessionStorage,n)}function gd(e={}){const{window:t=Te,...n}=e;return nf(t,n)}function md(e={}){const{window:t=Te,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:s=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:i=!0,type:o="inner"}=e,l=ye(n),c=ye(s),u=()=>{if(t)if(o==="outer")l.value=t.outerWidth,c.value=t.outerHeight;else if(o==="visual"&&t.visualViewport){const{width:d,height:m,scale:v}=t.visualViewport;l.value=Math.round(d*v),c.value=Math.round(m*v)}else i?(l.value=t.innerWidth,c.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,c.value=t.document.documentElement.clientHeight)};u(),ps(u);const a={passive:!0};return ze("resize",u,a),t&&o==="visual"&&t.visualViewport&&ze(t.visualViewport,"resize",u,a),r&&Re(cl("(orientation: portrait)"),()=>u()),{width:l,height:c}}const rf={};var of={};const dl=/^(?:[a-z]+:|\/\/)/i,lf="vitepress-theme-appearance",hl=Symbol("stack-view:unpack"),cf=/#.*?(?=:~:|$)/,af=/[?#].*$/,uf=/(?:(^|\/)index)?\.(?:md|html)$/,he=typeof document<"u",pl={relativePath:"404.md",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function ff(e,t,n=!1){if(t===void 0)return!1;if(e=xi(`/${e}`),n)return new RegExp(t).test(e);if(xi(t)!==e)return!1;const s=t.match(cf);return s?(he?location.hash:"")===s[0]:!0}function xi(e){return decodeURI(e).replace(af,"").replace(uf,"$1")}function df(e){return dl.test(e)}function hf(e,t){return Object.keys(e?.locales||{}).find(n=>n!=="root"&&!df(n)&&ff(t,`^/${n}/`,!0))||"root"}function pf(e,t){const n=hf(e,t),{label:s,link:r,...i}=e.locales[n]??{};Object.assign(i,{localeIndex:n});const o=bf(e,t),l={head:ml(e.head??[],i.head??[],...o.map(c=>c.head??[]).reverse())};return Cr(l,...o,i,e)}function gl(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=gf(e.title,s);return n===r.slice(3)?n:`${n}${r}`}function gf(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function ml(...e){const t=[],n=new Map;for(const s of e)for(const r of s){const[i,o]=r,l=Object.entries(o)[0];if(i!=="meta"||!l){t.push(r);continue}const c=`${l[0]}=${l[1]}`,u=n.get(c);u!=null?t[u]=r:(n.set(c,t.length),t.push(r))}return t}const mf=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,vf=/^[a-z]:/i;function Ci(e){const t=vf.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(mf,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const Ns=new Set;function yf(e){if(Ns.size===0){const n=typeof process=="object"&&of?.VITE_EXTRA_EXTENSIONS||rf?.VITE_EXTRA_EXTENSIONS||"";("3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip"+(n&&typeof n=="string"?","+n:"")).split(",").forEach(s=>Ns.add(s))}const t=e.split(".").pop();return t==null||!Ns.has(t.toLowerCase())}function vd(e){return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}function bf({additionalConfig:e},t){if(e===void 0)return[];if(typeof e=="function")return e(t)??[];const n=[],s=t.split("/").slice(0,-1);for(;s.length;){const r=`/${s.join("/")}/`;n.push(e[r]),s.pop()}return n.push(e["/"]),n.filter(r=>r!==void 0)}function Cr(...e){const t=e.filter(r=>_f(r));if(t.length<=1)return e[0];const n=new Set(t.flatMap(r=>Reflect.ownKeys(r))),s=[...n];return new Proxy({},{get(r,i){return i===hl?t:Cr(...t.map(o=>o[i]).filter(o=>o!==void 0))},set(){throw new Error("StackView is read-only and cannot be mutated.")},has(r,i){return n.has(i)},ownKeys(){return s},getOwnPropertyDescriptor(r,i){for(const o of t){const l=Object.getOwnPropertyDescriptor(o,i);if(l)return l}}})}Cr.unpack=function(e){return e?.[hl]};function _f(e){return Object.prototype.toString.call(e)==="[object Object]"}const wf=["shellscript","shell","bash","sh","zsh"];function Sf(e){return wf.includes(e)}const Ef=Symbol(),at=ye(an(Pu));function yd(e){const t=ie(()=>pf(at.value,e.data.relativePath)),n=t.value.appearance,s=n==="force-dark"?Fe(!0):n==="force-auto"?ul():n?tf({storageKey:lf,initialValue:()=>n==="dark"?"dark":"auto",...typeof n=="object"?n:{}}):Fe(!1),r=Fe(he?location.hash:"");return he&&window.addEventListener("hashchange",()=>{r.value=location.hash}),Re(()=>e.data,()=>{r.value=he?location.hash:""}),{site:t,theme:ie(()=>t.value.themeConfig),page:ie(()=>e.data),frontmatter:ie(()=>e.data.frontmatter),params:ie(()=>e.data.params),lang:ie(()=>t.value.lang),dir:ie(()=>e.data.frontmatter.dir||t.value.dir),localeIndex:ie(()=>t.value.localeIndex||"root"),title:ie(()=>gl(t.value,e.data)),description:ie(()=>e.data.description||t.value.description),isDark:s,hash:ie(()=>r.value)}}function Tf(){const e=xt(Ef);if(!e)throw new Error("vitepress data not properly injected in app");return e}function xf(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Ai(e){return dl.test(e)||!e.startsWith("/")?e:xf(at.value.base,e)}function Cf(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),he){t=Ci(t.slice(1).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];if(s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),!s)return null;t=`/assets/${t}.${s}.js`}else t=`./${Ci(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let Hn=[];function bd(e){Hn.push(e),Uu(()=>{Hn=Hn.filter(t=>t!==e)})}function Af(){let e=at.value.scrollOffset,t=0,n=24;if(typeof e=="object"&&"padding"in e&&(n=e.padding,e=e.selector),typeof e=="number")t=e;else if(typeof e=="string")t=Oi(e,n);else if(Array.isArray(e))for(const s of e){const r=Oi(s,n);if(r){t=r;break}}return t}function Oi(e,t){const n=document.querySelector(e);if(!n)return 0;const s=n.getBoundingClientRect().bottom;return s<0?0:s+t}const Of=Symbol(),Zs="http://a.com",Mf=()=>({path:"/",hash:"",query:"",component:null,data:pl});function _d(e,t){const n=Vt(Mf()),s={route:n,async go(l,c){const{hash:u}=new URL(l,Zs),a=he&&document.fragmentDirective&&u.includes(":~:");l=tr(l),await s.onBeforeRouteChange?.(l)!==!1&&((!he||await Pf(l,{...c,hasTextFragment:a}))&&await i(l,{initialLoad:!!c?.initialLoad}),a&&(location.hash=u),o(),await s.onAfterRouteChange?.(l))}};let r=null;async function i(l,{scrollPosition:c=0,isRetry:u=!1,initialLoad:a=!1}={}){if(await s.onBeforePageLoad?.(l)===!1)return;const d=new URL(l,Zs),m=r=d.pathname;try{let v=await e(m);if(!v)throw new Error(`Page not found: ${m}`);if(r===m){r=null;const{default:w,__pageData:_}=v;if(!w)throw new Error(`Invalid route component: ${w}`);await s.onAfterPageLoad?.(l),n.path=he?m:Ai(m),n.component=In(w),n.data=In(_),o(d),he&&cs(()=>{let R=at.value.base+_.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");!at.value.cleanUrls&&!R.endsWith("/")&&(R+=".html"),R!==d.pathname&&(d.pathname=R,l=R+d.search+d.hash,history.replaceState({},"",l)),a||er(d.hash,!1,c)})}}catch(v){if(!/fetch|Page not found/.test(v.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(v),!u)try{const w=await fetch(at.value.base+"hashmap.json");window.__VP_HASH_MAP__=await w.json(),await i(l,{scrollPosition:c,isRetry:!0,initialLoad:a});return}catch{}if(r===m){r=null,n.path=he?m:Ai(m),n.component=t?In(t):null;const w=he?n.path.replace(/(^|\/)$/,"$1index").replace(/(\.html)?$/,".md").slice(at.value.base.length):"404.md";n.data={...pl,relativePath:w},o(d)}}}function o(l=he?location:{search:"",hash:""}){n.query=l.search,n.hash=decodeURIComponent(l.hash)}return he&&(history.state===null&&history.replaceState({},""),window.addEventListener("click",l=>{if(l.defaultPrevented||!(l.target instanceof Element)||l.target.closest("button")||l.button!==0||l.ctrlKey||l.shiftKey||l.altKey||l.metaKey)return;const c=l.target.closest("a");if(!c||c.closest(".vp-raw")||c.hasAttribute("download")||c.hasAttribute("target"))return;const u=c.getAttribute("href")??(c instanceof SVGAElement?c.getAttribute("xlink:href"):null);if(u==null)return;const{href:a,origin:d,pathname:m}=new URL(u,c.baseURI),v=new URL(location.href);d===v.origin&&yf(m)&&(l.preventDefault(),s.go(a,{smoothScroll:c.classList.contains("header-anchor")}))},{capture:!0}),window.addEventListener("popstate",async l=>{if(l.state===null)return;const c=tr(location.href);await i(c,{scrollPosition:l.state.scrollPosition||0}),o(),await s.onAfterRouteChange?.(c)}),window.addEventListener("hashchange",l=>{l.preventDefault(),o()})),s}function Rf(){const e=xt(Of);if(!e)throw new Error("useRouter() is called without provider.");return e}function vl(){return Rf().route}function er(e,t=!1,n=0){if(!e||n){window.scrollTo(0,n);return}let s=null;try{s=document.getElementById(decodeURIComponent(e).slice(1))}catch(l){console.warn(l)}if(!s)return;const r=window.scrollY+s.getBoundingClientRect().top-Af()+Number.parseInt(window.getComputedStyle(s).paddingTop,10)||0,i=window.matchMedia("(prefers-reduced-motion)").matches?"instant":t&&Math.abs(r-window.scrollY)<=window.innerHeight?"smooth":"auto";requestAnimationFrame(()=>{if(window.scrollTo({left:0,top:r,behavior:i}),s.focus({preventScroll:!0}),document.activeElement===s||s.hasAttribute("tabindex"))return;const l=()=>{s.removeAttribute("tabindex"),s.removeEventListener("blur",l)};s.setAttribute("tabindex","-1"),s.addEventListener("blur",l),s.focus({preventScroll:!0}),document.activeElement!==s&&l()})}function tr(e){const t=new URL(e,Zs);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),at.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash.split(":~:")[0]}async function Pf(e,{smoothScroll:t=!1,initialLoad:n=!1,replace:s=!1,hasTextFragment:r=!1}={}){const i=tr(location.href),o=new URL(e,location.origin),l=new URL(i,location.origin);if(e===i){if(!n)return r||er(o.hash,t),!1}else if(s?history.replaceState({},"",e):(history.replaceState({scrollPosition:window.scrollY},""),history.pushState({},"",e)),o.pathname===l.pathname)return o.hash!==l.hash&&(window.dispatchEvent(new HashChangeEvent("hashchange",{oldURL:l.href,newURL:o.href})),r||er(o.hash,t)),!1;return!0}const Ln=()=>Hn.forEach(e=>e()),wd=hr({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=vl(),{frontmatter:n,site:s}=Tf();return Re(n,Ln,{deep:!0,flush:"post"}),()=>Js(e.as,s.value.contentProps??{style:{position:"relative"}},[t.component?Js(t.component,{onVnodeMounted:Ln,onVnodeUpdated:Ln,onVnodeUnmounted:Ln}):"404 Page Not Found"])}}),Lf="modulepreload",$f=function(e){return"/"+e},Mi={},Sd=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){let c=function(u){return Promise.all(u.map(a=>Promise.resolve(a).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=o?.nonce||o?.getAttribute("nonce");r=c(n.map(u=>{if(u=$f(u),u in Mi)return;Mi[u]=!0;const a=u.endsWith(".css"),d=a?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${u}"]${d}`))return;const m=document.createElement("link");if(m.rel=a?"stylesheet":Lf,a||(m.as="script"),m.crossOrigin="",m.href=u,l&&m.setAttribute("nonce",l),document.head.appendChild(m),a)return new Promise((v,w)=>{m.addEventListener("load",v),m.addEventListener("error",()=>w(new Error(`Unable to preload CSS for ${u}`)))})}))}function i(o){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o}return r.then(o=>{for(const l of o||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})},Ed=hr({setup(e,{slots:t}){const n=Fe(!1);return Wt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function Td(){he&&window.addEventListener("click",e=>{const t=e.target;if(t.matches(".vp-code-group input")){const n=t.parentElement?.parentElement;if(!n)return;const s=Array.from(n.querySelectorAll("input")).indexOf(t);if(s<0)return;const r=n.querySelector(".blocks");if(!r)return;const i=Array.from(r.children).find(c=>c.classList.contains("active"));if(!i)return;const o=r.children[s];if(!o||i===o)return;i.classList.remove("active"),If(o),n?.querySelector(`label[for="${t.id}"]`)?.scrollIntoView({block:"nearest"})}})}function If(e){e.classList.add("active"),window.dispatchEvent(new CustomEvent("vitepress:codeGroupTabActivate",{detail:e}))}const Nf=[".vp-copy-ignore",".diff.remove"].join(", ");function xd(){if(he){const e=new WeakMap;window.addEventListener("click",t=>{const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const s=n.parentElement,r=n.nextElementSibling?.nextElementSibling;if(!s||!r)return;const i=r.cloneNode(!0);i.querySelectorAll(Nf).forEach(c=>c.remove()),i.innerHTML=i.innerHTML.replace(/\n+/g,` -`);let o=i.textContent||"";const l=/language-(\w+)/.exec(s.className)?.[1]||"";Sf(l)&&(o=o.replace(/^ *(\$|>) /gm,"").trim()),Ff(o).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const c=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,c)})}})}}async function Ff(e){try{await navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function Cd(e,t){let n=!0,s=[];const r=i=>{if(n){n=!1,i.forEach(l=>{const c=Fs(l);for(const u of document.head.children)if(u.isEqualNode(c)){s.push(u);return}});return}const o=i.map(Fs);s.forEach((l,c)=>{const u=o.findIndex(a=>a?.isEqualNode(l??null));u!==-1?delete o[u]:(l?.remove(),delete s[c])}),o.forEach(l=>l&&document.head.appendChild(l)),s=[...s,...o].filter(Boolean)};dr(()=>{const i=e.data,o=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[],u=gl(o,i);u!==document.title&&(document.title=u);const a=l||o.description;let d=document.querySelector("meta[name=description]");d?d.getAttribute("content")!==a&&d.setAttribute("content",a):Fs(["meta",{name:"description",content:a}]),r(ml(o.head,jf(c)))})}function Fs([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),e==="script"&&t.async==null&&(s.async=!1),s}function Df(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function jf(e){return e.filter(t=>!Df(t))}const Ds=new Set,yl=()=>document.createElement("link"),Hf=e=>{const t=yl();t.rel="prefetch",t.href=e,document.head.appendChild(t)},Vf=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let $n;const kf=he&&($n=yl())&&$n.relList&&$n.relList.supports&&$n.relList.supports("prefetch")?Hf:Vf;function Ad(){if(!he||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(o=>{if(o.isIntersecting){const l=o.target;n.unobserve(l);const{pathname:c}=l;if(!Ds.has(c)){Ds.add(c);const u=Cf(c);u&&kf(u)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{hostname:o,pathname:l}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||i.target!=="_blank"&&o===location.hostname&&(l!==location.pathname?n.observe(i):Ds.add(l))})})};Wt(s);const r=vl();Re(()=>r.path,s),fs(()=>{n&&n.disconnect()})}export{Gf as $,_f as A,ce as B,Ed as C,In as D,dl as E,Ee as F,Pa as G,cs as H,ar as I,cr as J,$u as K,mo as L,bd as M,ud as N,Wt as O,fs as P,Qc as Q,Of as R,zs as S,Bf as T,Cf as U,Lc as V,an as W,Fe as X,Xf as Y,Yf as Z,Sd as _,wd as a,zf as a0,ec as a1,ye as a2,at as a3,jn as a4,Ac as a5,Jf as a6,oe as a7,yf as a8,nl as a9,Uf as aA,rd as aB,sd as aC,ss as aa,gs as ab,Td as ac,xd as ad,Tf as ae,ze as af,fd as ag,cl as ah,dd as ai,Ad as aj,vl as ak,Rf as al,hd as am,pd as an,Qf as ao,Kf as ap,Cd as aq,gd as ar,md as as,nd as at,Re as au,cd as av,dr as aw,Wf as ax,Ai as ay,Pc as az,td as b,ld as c,ie as d,ad as e,id as f,Wo as g,Xs as h,Ra as i,Zf as j,_d as k,od as l,ed as m,Bo as n,ue as o,Ef as p,qf as q,hr as r,vd as s,Af as t,Js as u,he as v,yd as w,xt as x,ff as y,df as z}; diff --git a/docs/assets/chunks/theme.BfZ7hXNa.js b/docs/assets/chunks/theme.BfZ7hXNa.js deleted file mode 100644 index 727d5fe7f9..0000000000 --- a/docs/assets/chunks/theme.BfZ7hXNa.js +++ /dev/null @@ -1,2 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/chunks/VPLocalSearchBox.DIqEGPXK.js","assets/chunks/framework.BXgR76oC.js"])))=>i.map(i=>d[i]); -import{r as m,j as u,Z as c,I as P,S as o,n as U,a5 as L,h as k,az as v,b as ve,i as h,c as b,ae as Ke,z as Re,a8 as Ue,ay as fe,d as p,g as f,aa as i,y as D,ah as $e,O as R,Q as je,P as he,t as We,aw as te,X as V,au as H,ax as Ve,v as Y,ak as ne,a1 as ye,a2 as ae,M as Se,F as S,Y as w,$ as j,o as _,a0 as B,E as Ne,ap as ze,ai as qe,G as O,x as se,J as Te,as as Je,N as ie,H as Ae,ar as Me,W as Ye,A as Pe,q as Xe,_ as Qe,am as we,V as Ie,a6 as Ze,aB as et,L as tt,aC as nt,ao as at}from"./framework.BXgR76oC.js";const st=m({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(e){return(t,n)=>(o(),u("span",{class:P(["VPBadge",e.type])},[c(t.$slots,"default",{},()=>[U(L(e.text),1)])],2))}}),ot={key:0,class:"VPBackdrop"},rt=m({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(e){return(t,n)=>(o(),k(ve,{name:"fade"},{default:v(()=>[e.show?(o(),u("div",ot)):h("",!0)]),_:1}))}}),it=b(rt,[["__scopeId","data-v-c79a1216"]]),$=Ke;function lt(e,t){let n,a=!1;return()=>{n&&clearTimeout(n),a?n=setTimeout(e,t):(e(),(a=!0)&&setTimeout(()=>a=!1,t))}}function le(e){return e.startsWith("/")?e:`/${e}`}function me(e){const{pathname:t,search:n,hash:a,protocol:s}=new URL(e,"http://a.com");if(Re(e)||e.startsWith("#")||!s.startsWith("http")||!Ue(t))return e;const{site:r}=$(),d=t.endsWith("/")||t.endsWith(".html")?e:e.replace(/(?:(^\.+)\/)?.*$/,`$1${t.replace(/(\.md)?$/,r.value.cleanUrls?"":".html")}${n}${a}`);return fe(d)}function X({correspondingLink:e=!1}={}){const{site:t,localeIndex:n,page:a,theme:s,hash:r}=$(),d=p(()=>({label:t.value.locales[n.value]?.label,link:t.value.locales[n.value]?.link||(n.value==="root"?"/":`/${n.value}/`)}));return{localeLinks:p(()=>Object.entries(t.value.locales).flatMap(([g,y])=>d.value.label===y.label?[]:{text:y.label,link:ct(y.link||(g==="root"?"/":`/${g}/`),s.value.i18nRouting!==!1&&e,a.value.relativePath.slice(d.value.link.length-1),!t.value.cleanUrls)+r.value,lang:y.lang,dir:y.dir})),currentLang:d}}function ct(e,t,n,a){return t?e.replace(/\/$/,"")+le(n.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,a?".html":"")):e}const ut={class:"NotFound"},dt={class:"code"},vt={class:"title"},ft={class:"quote"},ht={class:"action"},mt=["href","aria-label"],pt=m({__name:"NotFound",setup(e){const{theme:t}=$(),{currentLang:n}=X();return(a,s)=>(o(),u("div",ut,[f("p",dt,L(i(t).notFound?.code??"404"),1),f("h1",vt,L(i(t).notFound?.title??"PAGE NOT FOUND"),1),s[0]||(s[0]=f("div",{class:"divider"},null,-1)),f("blockquote",ft,L(i(t).notFound?.quote??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),f("div",ht,[f("a",{class:"link",href:i(fe)(i(t).notFound?.link??i(n).link),"aria-label":i(t).notFound?.linkLabel??"go to home"},L(i(t).notFound?.linkText??"Take me home"),9,mt)])]))}}),kt=b(pt,[["__scopeId","data-v-829df670"]]);function xe(e,t){if(Array.isArray(e))return Q(e);if(e==null)return[];t=le(t);const n=Object.keys(e).sort((s,r)=>r.split("/").length-s.split("/").length).find(s=>t.startsWith(le(s))),a=n?e[n]:[];return Array.isArray(a)?Q(a):Q(a.items,a.base)}function gt(e){const t=[];let n=0;for(const a in e){const s=e[a];if(s.items){n=t.push(s);continue}t[n]||t.push({items:[]}),t[n].items.push(s)}return t}function _t(e){const t=[];function n(a){for(const s of a)s.text&&s.link&&t.push({text:s.text,link:s.link,docFooterText:s.docFooterText}),s.items&&n(s.items)}return n(e),t}function ce(e,t){return Array.isArray(t)?t.some(n=>ce(e,n)):D(e,t.link)?!0:t.items?ce(e,t.items):!1}function Q(e,t){return[...e].map(n=>{const a={...n},s=a.base||t;return s&&a.link&&(a.link=s+a.link.replace(/^\//,s.endsWith("/")?"":"/")),a.items&&(a.items=Q(a.items,s)),a})}function bt(){const{hasSidebar:e}=G(),t=$e("(min-width: 960px)"),n=$e("(min-width: 1280px)");return{isAsideEnabled:p(()=>!n.value&&!t.value?!1:e.value?n.value:t.value)}}const $t=/\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/,ue=[];function He(e){return typeof e.outline=="object"&&!Array.isArray(e.outline)&&e.outline.label||e.outlineTitle||"On this page"}function yt(e){const t=[...document.querySelectorAll(".VPDoc h1, .VPDoc h2, .VPDoc h3, .VPDoc h4, .VPDoc h5, .VPDoc h6")].filter(n=>n.id&&n.hasChildNodes()).map(n=>{const a=Number(n.tagName[1]);return{element:n,title:Pt(n),link:"#"+n.id,level:a}});return Lt(t,e)}function Pt(e){let t="";for(const n of e.childNodes)if(n.nodeType===1){if($t.test(n.className))continue;t+=n.textContent}else n.nodeType===3&&(t+=n.textContent);return t.trim()}function Lt(e,t){if(t===!1)return[];const n=(typeof t=="object"&&!Array.isArray(t)?t.level:t)||2,[a,s]=typeof n=="number"?[n,n]:n==="deep"?[2,6]:n;return Nt(e,a,s)}function Vt(e,t){const{isAsideEnabled:n}=bt(),a=lt(r,100);let s=null;R(()=>{requestAnimationFrame(r),window.addEventListener("scroll",a)}),je(()=>{d(location.hash)}),he(()=>{window.removeEventListener("scroll",a)});function r(){if(!n.value)return;const l=window.scrollY,g=window.innerHeight,y=document.body.offsetHeight,N=Math.abs(l+g-y)<1,A=ue.map(({element:I,link:x})=>({link:x,top:St(I)})).filter(({top:I})=>!Number.isNaN(I)).sort((I,x)=>I.top-x.top);if(!A.length){d(null);return}if(l<1){d(null);return}if(N){d(A[A.length-1].link);return}let E=null;for(const{link:I,top:x}of A){if(x>l+We()+4)break;E=I}d(E)}function d(l){s&&s.classList.remove("active"),l==null?s=null:s=e.value.querySelector(`a[href="${decodeURIComponent(l)}"]`);const g=s;g?(g.classList.add("active"),t.value.style.top=g.offsetTop+39+"px",t.value.style.opacity="1"):(t.value.style.top="33px",t.value.style.opacity="0")}}function St(e){let t=0;for(;e!==document.body;){if(e===null)return NaN;t+=e.offsetTop,e=e.offsetParent}return t}function Nt(e,t,n){ue.length=0;const a=[],s=[];return e.forEach(r=>{const d={...r,children:[]};let l=s[s.length-1];for(;l&&l.level>=d.level;)s.pop(),l=s[s.length-1];if(d.element.classList.contains("ignore-header")||l&&"shouldIgnore"in l){s.push({level:d.level,shouldIgnore:!0});return}d.level>n||d.level{t=q.value?document.activeElement:void 0}),R(()=>{window.addEventListener("keyup",n)}),he(()=>{window.removeEventListener("keyup",n)});function n(a){a.key==="Escape"&&q.value&&(e(),t?.focus())}}function At(){function e(){q.value=!0}function t(){q.value=!1}function n(){q.value?t():e()}return{isOpen:q,open:e,close:t,toggle:n}}function Mt(e){const{page:t,hash:n}=$(),a=V(!1),s=p(()=>e.value.collapsed!=null),r=p(()=>!!e.value.link),d=V(!1),l=()=>{d.value=D(t.value.relativePath,e.value.link)};H([t,e,n],l),R(l);const g=p(()=>d.value?!0:e.value.items?ce(t.value.relativePath,e.value.items):!1),y=p(()=>!!(e.value.items&&e.value.items.length));te(()=>{a.value=!!(s.value&&e.value.collapsed)}),Ve(()=>{(d.value||g.value)&&(a.value=!1)});function N(){s.value&&(a.value=!a.value)}return{collapsed:a,collapsible:s,isLink:r,isActiveLink:d,hasActiveLink:g,hasChildren:y,toggle:N}}const de=ae([]),J=ae([]),Z=ae(!1);function G(){const{frontmatter:e,theme:t}=$(),n=p(()=>!!(e.value.isHome??e.value.layout==="home")),a=p(()=>e.value.sidebar!==!1&&J.value.length>0&&!n.value),s=p(()=>a.value&&Z.value),r=p(()=>a.value?gt(J.value):[]),d=p(()=>n.value?!1:e.value.aside!=null?!!e.value.aside:t.value.aside!==!1),l=p(()=>d.value?e.value.aside==null?t.value.aside==="left":e.value.aside==="left":!1),g=p(()=>de.value.length>0);return{isHome:n,sidebar:ye(J),sidebarGroups:r,hasSidebar:a,isSidebarEnabled:s,hasAside:d,leftAside:l,headers:ye(de),hasLocalNav:g}}function wt({closeSidebar:e}){const{frontmatter:t,page:n,theme:a}=$();H(()=>[n.value.relativePath,a.value.sidebar],([r,d])=>{const l=d?xe(d,r):[];JSON.stringify(l)!==JSON.stringify(J.value)&&(J.value=l)},{immediate:!0,deep:!0,flush:"sync"}),Se(()=>{de.value=yt(t.value.outline??a.value.outline)}),Y&&(Z.value=window.innerWidth>=960,window.addEventListener("resize",()=>{Z.value=window.innerWidth>=960},{passive:!0}));const s=ne();H(()=>s.path,e),H(Z,e),Tt(e)}const Be=Symbol("layout-info"),It=["href","title"],xt=m({__name:"VPDocOutlineItem",props:{headers:{},root:{type:Boolean}},setup(e){return(t,n)=>{const a=j("VPDocOutlineItem",!0);return o(),u("ul",{class:P(["VPDocOutlineItem",e.root?"root":"nested"])},[(o(!0),u(S,null,w(e.headers,({children:s,link:r,title:d})=>(o(),u("li",null,[f("a",{class:"outline-link",href:r,title:d},L(d),9,It),s?.length?(o(),k(a,{key:0,headers:s},null,8,["headers"])):h("",!0)]))),256))],2)}}}),Ce=b(xt,[["__scopeId","data-v-1ce71065"]]),Ht={class:"content"},Bt={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},Ct=m({__name:"VPDocAsideOutline",setup(e){const{theme:t}=$(),n=V(),a=V(),{headers:s,hasLocalNav:r}=G();return Vt(n,a),(d,l)=>(o(),u("nav",{"aria-labelledby":"doc-outline-aria-label",class:P(["VPDocAsideOutline",{"has-outline":i(r)}]),ref_key:"container",ref:n},[f("div",Ht,[f("div",{class:"outline-marker",ref_key:"marker",ref:a},null,512),f("div",Bt,L(i(He)(i(t))),1),_(Ce,{headers:i(s),root:!0},null,8,["headers"])])],2))}}),Et=b(Ct,[["__scopeId","data-v-60d5052e"]]),Ft={class:"VPDocAsideCarbonAds"},Ot=m({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(e){const t=()=>null;return(n,a)=>(o(),u("div",Ft,[_(i(t),{"carbon-ads":e.carbonAds},null,8,["carbon-ads"])]))}}),Dt={class:"VPDocAside"},Gt=m({__name:"VPDocAside",setup(e){const{theme:t}=$();return(n,a)=>(o(),u("div",Dt,[c(n.$slots,"aside-top",{},void 0,!0),c(n.$slots,"aside-outline-before",{},void 0,!0),_(Et),c(n.$slots,"aside-outline-after",{},void 0,!0),a[0]||(a[0]=f("div",{class:"spacer"},null,-1)),c(n.$slots,"aside-ads-before",{},void 0,!0),i(t).carbonAds?(o(),k(Ot,{key:0,"carbon-ads":i(t).carbonAds},null,8,["carbon-ads"])):h("",!0),c(n.$slots,"aside-ads-after",{},void 0,!0),c(n.$slots,"aside-bottom",{},void 0,!0)]))}}),Kt=b(Gt,[["__scopeId","data-v-3f215769"]]);function Rt(){const{theme:e,page:t}=$();return p(()=>{const{text:n="Edit this page",pattern:a=""}=e.value.editLink||{};let s;return typeof a=="function"?s=a(t.value):s=a.replace(/:path/g,t.value.filePath),{url:s,text:n}})}function Ut(){const{page:e,theme:t,frontmatter:n}=$();return p(()=>{const a=xe(t.value.sidebar,e.value.relativePath),s=_t(a),r=jt(s,y=>y.link.replace(/[?#].*$/,"")),d=r.findIndex(y=>D(e.value.relativePath,y.link)),l=t.value.docFooter?.prev===!1&&!n.value.prev||n.value.prev===!1,g=t.value.docFooter?.next===!1&&!n.value.next||n.value.next===!1;return{prev:l?void 0:{text:(typeof n.value.prev=="string"?n.value.prev:typeof n.value.prev=="object"?n.value.prev.text:void 0)??r[d-1]?.docFooterText??r[d-1]?.text,link:(typeof n.value.prev=="object"?n.value.prev.link:void 0)??r[d-1]?.link},next:g?void 0:{text:(typeof n.value.next=="string"?n.value.next:typeof n.value.next=="object"?n.value.next.text:void 0)??r[d+1]?.docFooterText??r[d+1]?.text,link:(typeof n.value.next=="object"?n.value.next.link:void 0)??r[d+1]?.link}}})}function jt(e,t){const n=new Set;return e.filter(a=>{const s=t(a);return n.has(s)?!1:n.add(s)})}const C=m({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(e){const t=e,n=p(()=>t.tag??(t.href?"a":"span")),a=p(()=>t.href&&Ne.test(t.href)||t.target==="_blank");return(s,r)=>(o(),k(B(n.value),{class:P(["VPLink",{link:e.href,"vp-external-link-icon":a.value,"no-icon":e.noIcon}]),href:e.href?i(me)(e.href):void 0,target:e.target??(a.value?"_blank":void 0),rel:e.rel??(a.value?"noreferrer":void 0)},{default:v(()=>[c(s.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Wt={class:"VPLastUpdated"},zt=["datetime"],qt=m({__name:"VPDocFooterLastUpdated",setup(e){const{theme:t,page:n,lang:a}=$(),{language:s}=qe(),r=ze("timeRef"),d=p(()=>new Date(n.value.lastUpdated)),l=p(()=>d.value.toISOString()),g=ae("");return R(()=>{te(()=>{const y=t.value.lastUpdated?.formatOptions?.forceLocale?a.value:s.value;g.value=new Intl.DateTimeFormat(y,t.value.lastUpdated?.formatOptions??{dateStyle:"medium",timeStyle:"medium"}).format(d.value),y&&a.value!==y?r.value?.setAttribute("lang",y):r.value?.removeAttribute("lang")})}),(y,N)=>(o(),u("p",Wt,[U(L(i(t).lastUpdated?.text||i(t).lastUpdatedText||"Last updated")+": ",1),f("time",{ref_key:"timeRef",ref:r,datetime:l.value},L(g.value),9,zt)]))}}),Jt=b(qt,[["__scopeId","data-v-3c637f39"]]),Yt={key:0,class:"VPDocFooter"},Xt={key:0,class:"edit-info"},Qt={key:0,class:"edit-link"},Zt={key:1,class:"last-updated"},en={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},tn={class:"pager"},nn=["innerHTML"],an=["innerHTML"],sn={class:"pager"},on=["innerHTML"],rn=["innerHTML"],ln=m({__name:"VPDocFooter",setup(e){const{theme:t,page:n,frontmatter:a}=$(),s=Rt(),r=Ut(),d=p(()=>t.value.editLink&&a.value.editLink!==!1),l=p(()=>n.value.lastUpdated),g=p(()=>d.value||l.value||r.value.prev||r.value.next);return(y,N)=>g.value?(o(),u("footer",Yt,[c(y.$slots,"doc-footer-before",{},void 0,!0),d.value||l.value?(o(),u("div",Xt,[d.value?(o(),u("div",Qt,[_(C,{class:"edit-link-button",href:i(s).url,"no-icon":!0},{default:v(()=>[N[0]||(N[0]=f("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),U(" "+L(i(s).text),1)]),_:1},8,["href"])])):h("",!0),l.value?(o(),u("div",Zt,[_(Jt)])):h("",!0)])):h("",!0),i(r).prev?.link||i(r).next?.link?(o(),u("nav",en,[N[1]||(N[1]=f("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),f("div",tn,[i(r).prev?.link?(o(),k(C,{key:0,class:"pager-link prev",href:i(r).prev.link},{default:v(()=>[f("span",{class:"desc",innerHTML:i(t).docFooter?.prev||"Previous page"},null,8,nn),f("span",{class:"title",innerHTML:i(r).prev.text},null,8,an)]),_:1},8,["href"])):h("",!0)]),f("div",sn,[i(r).next?.link?(o(),k(C,{key:0,class:"pager-link next",href:i(r).next.link},{default:v(()=>[f("span",{class:"desc",innerHTML:i(t).docFooter?.next||"Next page"},null,8,on),f("span",{class:"title",innerHTML:i(r).next.text},null,8,rn)]),_:1},8,["href"])):h("",!0)])])):h("",!0)])):h("",!0)}}),cn=b(ln,[["__scopeId","data-v-e257564d"]]),un={class:"container"},dn={class:"aside-container"},vn={class:"aside-content"},fn={class:"content"},hn={class:"content-container"},mn={class:"main"},pn=m({__name:"VPDoc",setup(e){const{theme:t}=$(),n=ne(),{hasSidebar:a,hasAside:s,leftAside:r}=G(),d=p(()=>n.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(l,g)=>{const y=j("Content");return o(),u("div",{class:P(["VPDoc",{"has-sidebar":i(a),"has-aside":i(s)}])},[c(l.$slots,"doc-top",{},void 0,!0),f("div",un,[i(s)?(o(),u("div",{key:0,class:P(["aside",{"left-aside":i(r)}])},[g[0]||(g[0]=f("div",{class:"aside-curtain"},null,-1)),f("div",dn,[f("div",vn,[_(Kt,null,{"aside-top":v(()=>[c(l.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[c(l.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[c(l.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[c(l.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[c(l.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[c(l.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):h("",!0),f("div",fn,[f("div",hn,[c(l.$slots,"doc-before",{},void 0,!0),f("main",mn,[_(y,{class:P(["vp-doc",[d.value,i(t).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),_(cn,null,{"doc-footer-before":v(()=>[c(l.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),c(l.$slots,"doc-after",{},void 0,!0)])])]),c(l.$slots,"doc-bottom",{},void 0,!0)],2)}}}),kn=b(pn,[["__scopeId","data-v-7011f0d8"]]),gn=m({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(e){const t=e,n=p(()=>t.href&&Ne.test(t.href)),a=p(()=>t.tag||(t.href?"a":"button"));return(s,r)=>(o(),k(B(a.value),{class:P(["VPButton",[e.size,e.theme]]),href:e.href?i(me)(e.href):void 0,target:t.target??(n.value?"_blank":void 0),rel:t.rel??(n.value?"noreferrer":void 0)},{default:v(()=>[c(s.$slots,"default",{},()=>[U(L(e.text),1)],!0)]),_:3},8,["class","href","target","rel"]))}}),_n=b(gn,[["__scopeId","data-v-01bff58b"]]),bn=["src","alt"],$n=m({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(e){return(t,n)=>{const a=j("VPImage",!0);return e.image?(o(),u(S,{key:0},[typeof e.image=="string"||"src"in e.image?(o(),u("img",O({key:0,class:"VPImage"},typeof e.image=="string"?t.$attrs:{...e.image,...t.$attrs},{src:i(fe)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,bn)):(o(),u(S,{key:1},[_(a,O({class:"dark",image:e.image.dark,alt:e.image.alt},t.$attrs),null,16,["image","alt"]),_(a,O({class:"light",image:e.image.light,alt:e.image.alt},t.$attrs),null,16,["image","alt"])],64))],64)):h("",!0)}}}),ee=b($n,[["__scopeId","data-v-8426fc1a"]]),yn={class:"container"},Pn={class:"main"},Ln={class:"heading"},Vn=["innerHTML"],Sn=["innerHTML"],Nn=["innerHTML"],Tn={key:0,class:"actions"},An={key:0,class:"image"},Mn={class:"image-container"},wn=m({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(e){const{heroImageSlotExists:t}=se(Be,{heroImageSlotExists:p(()=>!1)});return(n,a)=>(o(),u("div",{class:P(["VPHero",{"has-image":e.image||i(t)}])},[f("div",yn,[f("div",Pn,[c(n.$slots,"home-hero-info-before",{},void 0,!0),c(n.$slots,"home-hero-info",{},()=>[f("h1",Ln,[e.name?(o(),u("span",{key:0,innerHTML:e.name,class:"name clip"},null,8,Vn)):h("",!0),e.text?(o(),u("span",{key:1,innerHTML:e.text,class:"text"},null,8,Sn)):h("",!0)]),e.tagline?(o(),u("p",{key:0,innerHTML:e.tagline,class:"tagline"},null,8,Nn)):h("",!0)],!0),c(n.$slots,"home-hero-info-after",{},void 0,!0),e.actions?(o(),u("div",Tn,[c(n.$slots,"home-hero-actions-before-actions",{},void 0,!0),(o(!0),u(S,null,w(e.actions,s=>(o(),u("div",{key:s.link,class:"action"},[_(_n,{tag:"a",size:"medium",theme:s.theme,text:s.text,href:s.link,target:s.target,rel:s.rel},null,8,["theme","text","href","target","rel"])]))),128))])):h("",!0),c(n.$slots,"home-hero-actions-after",{},void 0,!0)]),e.image||i(t)?(o(),u("div",An,[f("div",Mn,[a[0]||(a[0]=f("div",{class:"image-bg"},null,-1)),c(n.$slots,"home-hero-image",{},()=>[e.image?(o(),k(ee,{key:0,class:"image-src",image:e.image},null,8,["image"])):h("",!0)],!0)])])):h("",!0)])],2))}}),In=b(wn,[["__scopeId","data-v-e62e4946"]]),xn=m({__name:"VPHomeHero",setup(e){const{frontmatter:t}=$();return(n,a)=>i(t).hero?(o(),k(In,{key:0,class:"VPHomeHero",name:i(t).hero.name,text:i(t).hero.text,tagline:i(t).hero.tagline,image:i(t).hero.image,actions:i(t).hero.actions},{"home-hero-info-before":v(()=>[c(n.$slots,"home-hero-info-before")]),"home-hero-info":v(()=>[c(n.$slots,"home-hero-info")]),"home-hero-info-after":v(()=>[c(n.$slots,"home-hero-info-after")]),"home-hero-actions-after":v(()=>[c(n.$slots,"home-hero-actions-after")]),"home-hero-actions-before-actions":v(()=>[c(n.$slots,"home-hero-actions-before-actions")]),"home-hero-image":v(()=>[c(n.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):h("",!0)}}),Hn={class:"box"},Bn={key:0,class:"icon"},Cn=["innerHTML"],En=["innerHTML"],Fn={key:3,class:"details"},On=["innerHTML"],Dn=["innerHTML"],Gn={key:5,class:"link-text"},Kn={class:"link-text-value"},Rn=m({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(e){return(t,n)=>(o(),k(C,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:v(()=>[f("article",Hn,[typeof e.icon=="object"&&e.icon.wrap?(o(),u("div",Bn,[_(ee,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(o(),k(ee,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(o(),u("div",{key:2,class:"icon",innerHTML:e.icon},null,8,Cn)):h("",!0),f("h2",{class:"title",innerHTML:e.title},null,8,En),Array.isArray(e.details)?(o(),u("ul",Fn,[(o(!0),u(S,null,w(e.details,a=>(o(),u("li",{key:a,innerHTML:a},null,8,On))),128))])):e.details?(o(),u("p",{key:4,class:"details",innerHTML:e.details},null,8,Dn)):h("",!0),e.linkText?(o(),u("div",Gn,[f("p",Kn,[U(L(e.linkText)+" ",1),n[0]||(n[0]=f("span",{class:"vpi-arrow-right link-text-icon"},null,-1))])])):h("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),Un=b(Rn,[["__scopeId","data-v-e5511d04"]]),jn={key:0,class:"VPFeatures"},Wn={class:"container"},zn={class:"items"},qn=m({__name:"VPFeatures",props:{features:{}},setup(e){const t=e,n=p(()=>{const a=t.features.length;if(a){if(a===2)return"grid-2";if(a===3)return"grid-3";if(a%3===0)return"grid-6";if(a>3)return"grid-4"}else return});return(a,s)=>e.features?(o(),u("div",jn,[f("div",Wn,[f("div",zn,[(o(!0),u(S,null,w(e.features,r=>(o(),u("div",{key:r.title,class:P(["item",[n.value]])},[_(Un,{icon:r.icon,title:r.title,details:r.details,link:r.link,"link-text":r.linkText,rel:r.rel,target:r.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):h("",!0)}}),Jn=b(qn,[["__scopeId","data-v-a6181336"]]),Yn=m({__name:"VPHomeFeatures",setup(e){const{frontmatter:t}=$();return(n,a)=>i(t).features?(o(),k(Jn,{key:0,class:"VPHomeFeatures",features:i(t).features},null,8,["features"])):h("",!0)}}),Xn=m({__name:"VPHomeContent",setup(e){const{width:t}=Je({initialWidth:0,includeScrollbar:!1});return(n,a)=>(o(),u("div",{class:"vp-doc container",style:Te(i(t)?{"--vp-offset":`calc(50% - ${i(t)/2}px)`}:{})},[c(n.$slots,"default",{},void 0,!0)],4))}}),Qn=b(Xn,[["__scopeId","data-v-8e2d4988"]]),Zn=m({__name:"VPHome",setup(e){const{frontmatter:t,theme:n}=$();return(a,s)=>{const r=j("Content");return o(),u("div",{class:P(["VPHome",{"external-link-icon-enabled":i(n).externalLinkIcon}])},[c(a.$slots,"home-hero-before",{},void 0,!0),_(xn,null,{"home-hero-info-before":v(()=>[c(a.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[c(a.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[c(a.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[c(a.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-actions-before-actions":v(()=>[c(a.$slots,"home-hero-actions-before-actions",{},void 0,!0)]),"home-hero-image":v(()=>[c(a.$slots,"home-hero-image",{},void 0,!0)]),_:3}),c(a.$slots,"home-hero-after",{},void 0,!0),c(a.$slots,"home-features-before",{},void 0,!0),_(Yn),c(a.$slots,"home-features-after",{},void 0,!0),i(t).markdownStyles!==!1?(o(),k(Qn,{key:0},{default:v(()=>[_(r)]),_:1})):(o(),k(r,{key:1}))],2)}}}),ea=b(Zn,[["__scopeId","data-v-bb6342a6"]]),ta={},na={class:"VPPage"};function aa(e,t){const n=j("Content");return o(),u("div",na,[c(e.$slots,"page-top"),_(n),c(e.$slots,"page-bottom")])}const sa=b(ta,[["render",aa]]),oa=m({__name:"VPContent",setup(e){const{page:t,frontmatter:n}=$(),{isHome:a,hasSidebar:s}=G();return(r,d)=>(o(),u("div",{class:P(["VPContent",{"has-sidebar":i(s),"is-home":i(a)}]),id:"VPContent"},[i(t).isNotFound?c(r.$slots,"not-found",{key:0},()=>[_(kt)],!0):i(n).layout==="page"?(o(),k(sa,{key:1},{"page-top":v(()=>[c(r.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[c(r.$slots,"page-bottom",{},void 0,!0)]),_:3})):i(n).layout==="home"?(o(),k(ea,{key:2},{"home-hero-before":v(()=>[c(r.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[c(r.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[c(r.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[c(r.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[c(r.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-actions-before-actions":v(()=>[c(r.$slots,"home-hero-actions-before-actions",{},void 0,!0)]),"home-hero-image":v(()=>[c(r.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[c(r.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[c(r.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[c(r.$slots,"home-features-after",{},void 0,!0)]),_:3})):i(n).layout&&i(n).layout!=="doc"?(o(),k(B(i(n).layout),{key:3})):(o(),k(kn,{key:4},{"doc-top":v(()=>[c(r.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[c(r.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":v(()=>[c(r.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[c(r.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[c(r.$slots,"doc-after",{},void 0,!0)]),"aside-top":v(()=>[c(r.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":v(()=>[c(r.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[c(r.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[c(r.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[c(r.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":v(()=>[c(r.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),ra=b(oa,[["__scopeId","data-v-9dc86fcc"]]),ia={class:"container"},la=["innerHTML"],ca=["innerHTML"],ua=m({__name:"VPFooter",setup(e){const{theme:t,frontmatter:n}=$(),{hasSidebar:a}=G();return(s,r)=>i(t).footer&&i(n).footer!==!1?(o(),u("footer",{key:0,class:P(["VPFooter",{"has-sidebar":i(a)}])},[f("div",ia,[i(t).footer.message?(o(),u("p",{key:0,class:"message",innerHTML:i(t).footer.message},null,8,la)):h("",!0),i(t).footer.copyright?(o(),u("p",{key:1,class:"copyright",innerHTML:i(t).footer.copyright},null,8,ca)):h("",!0)])],2)):h("",!0)}}),da=b(ua,[["__scopeId","data-v-c3855bb3"]]),va={class:"menu-text"},fa={class:"header"},ha={class:"outline"},ma=m({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(e){const t=e,{theme:n}=$(),a=V(!1),s=V(0),r=V(),d=V();function l(A){r.value?.contains(A.target)||(a.value=!1)}H(a,A=>{if(A){document.addEventListener("click",l);return}document.removeEventListener("click",l)}),ie("Escape",()=>{a.value=!1}),Se(()=>{a.value=!1});function g(){a.value=!a.value,s.value=window.innerHeight+Math.min(window.scrollY-t.navHeight,0)}function y(A){A.target.classList.contains("outline-link")&&(d.value&&(d.value.style.transition="none"),Ae(()=>{a.value=!1}))}function N(){a.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(A,E)=>(o(),u("div",{class:"VPLocalNavOutlineDropdown",style:Te({"--vp-vh":s.value+"px"}),ref_key:"main",ref:r},[e.headers.length>0?(o(),u("button",{key:0,onClick:g,class:P({open:a.value})},[f("span",va,L(i(He)(i(n))),1),E[0]||(E[0]=f("span",{class:"vpi-chevron-right icon"},null,-1))],2)):(o(),u("button",{key:1,onClick:N},L(i(n).returnToTopLabel||"Return to top"),1)),_(ve,{name:"flyout"},{default:v(()=>[a.value?(o(),u("div",{key:0,ref_key:"items",ref:d,class:"items",onClick:y},[f("div",fa,[f("a",{class:"top-link",href:"#",onClick:N},L(i(n).returnToTopLabel||"Return to top"),1)]),f("div",ha,[_(Ce,{headers:e.headers},null,8,["headers"])])],512)):h("",!0)]),_:1})],4))}}),pa=b(ma,[["__scopeId","data-v-0bf0e06f"]]),ka={class:"container"},ga=["aria-expanded"],_a={class:"menu-text"},ba=m({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(e){const{theme:t}=$(),{isHome:n,hasSidebar:a,headers:s,hasLocalNav:r}=G(),{y:d}=Me(),l=V(0);R(()=>{l.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))});const g=p(()=>({VPLocalNav:!0,"has-sidebar":a.value,empty:!r.value,fixed:!r.value&&!a.value}));return(y,N)=>!i(n)&&(i(r)||i(a)||i(d)>=l.value)?(o(),u("div",{key:0,class:P(g.value)},[f("div",ka,[i(a)?(o(),u("button",{key:0,class:"menu","aria-expanded":e.open,"aria-controls":"VPSidebarNav",onClick:N[0]||(N[0]=A=>y.$emit("open-menu"))},[N[1]||(N[1]=f("span",{class:"vpi-align-left menu-icon"},null,-1)),f("span",_a,L(i(t).sidebarMenuLabel||"Menu"),1)],8,ga)):h("",!0),_(pa,{headers:i(s),navHeight:l.value},null,8,["headers","navHeight"])])],2)):h("",!0)}}),$a=b(ba,[["__scopeId","data-v-db738f89"]]);function ya(){const e=V(!1);function t(){e.value=!0,window.addEventListener("resize",s)}function n(){e.value=!1,window.removeEventListener("resize",s)}function a(){e.value?n():t()}function s(){window.outerWidth>=768&&n()}const r=ne();return H(()=>r.path,n),{isScreenOpen:e,openScreen:t,closeScreen:n,toggleScreen:a}}const pe=Symbol("nav"),Pa={},La={class:"VPSwitch",type:"button",role:"switch"},Va={class:"check"},Sa={key:0,class:"icon"};function Na(e,t){return o(),u("button",La,[f("span",Va,[e.$slots.default?(o(),u("span",Sa,[c(e.$slots,"default",{},void 0,!0)])):h("",!0)])])}const Ta=b(Pa,[["render",Na],["__scopeId","data-v-1d5665e3"]]),Aa=m({__name:"VPSwitchAppearance",setup(e){const{isDark:t,theme:n}=$(),a=se("toggle-appearance",()=>{t.value=!t.value}),s=V("");return Ve(()=>{s.value=t.value?n.value.lightModeSwitchTitle||"Switch to light theme":n.value.darkModeSwitchTitle||"Switch to dark theme"}),(r,d)=>(o(),k(Ta,{title:s.value,class:"VPSwitchAppearance","aria-checked":i(t),onClick:i(a)},{default:v(()=>[...d[0]||(d[0]=[f("span",{class:"vpi-sun sun"},null,-1),f("span",{class:"vpi-moon moon"},null,-1)])]),_:1},8,["title","aria-checked","onClick"]))}}),ke=b(Aa,[["__scopeId","data-v-5337faa4"]]),Ma={key:0,class:"VPNavBarAppearance"},wa=m({__name:"VPNavBarAppearance",setup(e){const{site:t}=$();return(n,a)=>i(t).appearance&&i(t).appearance!=="force-dark"&&i(t).appearance!=="force-auto"?(o(),u("div",Ma,[_(ke)])):h("",!0)}}),Ia=b(wa,[["__scopeId","data-v-6c893767"]]),ge=V();let Ee=!1,re=0;function xa(e){const t=V(!1);if(Y){!Ee&&Ha(),re++;const n=H(ge,a=>{a===e.el.value||e.el.value?.contains(a)?(t.value=!0,e.onFocus?.()):(t.value=!1,e.onBlur?.())});he(()=>{n(),re--,re||Ba()})}return Ye(t)}function Ha(){document.addEventListener("focusin",Fe),Ee=!0,ge.value=document.activeElement}function Ba(){document.removeEventListener("focusin",Fe)}function Fe(){ge.value=document.activeElement}const Ca={class:"VPMenuLink"},Ea=["innerHTML"],Fa=m({inheritAttrs:!1,__name:"VPMenuLink",props:{item:{},rel:{}},setup(e){const t=e,{page:n}=$(),a=p(()=>typeof t.item.link=="function"?t.item.link(n.value):t.item.link),s=p(()=>D(n.value.relativePath,t.item.activeMatch||a.value,!!t.item.activeMatch));return(r,d)=>(o(),u("div",Ca,[_(C,O(r.$attrs,{class:{active:s.value},href:a.value,target:e.item.target,rel:t.rel??e.item.rel,"no-icon":e.item.noIcon}),{default:v(()=>[f("span",{innerHTML:e.item.text},null,8,Ea)]),_:1},16,["class","href","target","rel","no-icon"])]))}}),oe=b(Fa,[["__scopeId","data-v-867c295f"]]),Oa={class:"VPMenuGroup"},Da={key:0,class:"title"},Ga=m({__name:"VPMenuGroup",props:{text:{},items:{}},setup(e){return(t,n)=>(o(),u("div",Oa,[e.text?(o(),u("p",Da,L(e.text),1)):h("",!0),(o(!0),u(S,null,w(e.items,a=>(o(),u(S,{key:JSON.stringify(a)},["link"in a?(o(),k(oe,{key:0,item:a},null,8,["item"])):h("",!0)],64))),128))]))}}),Ka=b(Ga,[["__scopeId","data-v-1963e1bb"]]),Ra={class:"VPMenu"},Ua={key:0,class:"items"},ja=m({__name:"VPMenu",props:{items:{}},setup(e){return(t,n)=>(o(),u("div",Ra,[e.items?(o(),u("div",Ua,[(o(!0),u(S,null,w(e.items,a=>(o(),u(S,{key:JSON.stringify(a)},["link"in a?(o(),k(oe,{key:0,item:a},null,8,["item"])):"component"in a?(o(),k(B(a.component),O({key:1,ref_for:!0},a.props),null,16)):(o(),k(Ka,{key:2,text:a.text,items:a.items},null,8,["text","items"]))],64))),128))])):h("",!0),c(t.$slots,"default",{},void 0,!0)]))}}),Wa=b(ja,[["__scopeId","data-v-25a6cce8"]]),za=["aria-expanded","aria-label"],qa={key:0,class:"text"},Ja=["innerHTML"],Ya={key:1,class:"vpi-more-horizontal icon"},Xa={class:"menu"},Qa=m({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(e){const t=V(!1),n=V();xa({el:n,onBlur:a});function a(){t.value=!1}return(s,r)=>(o(),u("div",{class:"VPFlyout",ref_key:"el",ref:n,onMouseenter:r[1]||(r[1]=d=>t.value=!0),onMouseleave:r[2]||(r[2]=d=>t.value=!1)},[f("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":t.value,"aria-label":e.label,onClick:r[0]||(r[0]=d=>t.value=!t.value)},[e.button||e.icon?(o(),u("span",qa,[e.icon?(o(),u("span",{key:0,class:P([e.icon,"option-icon"])},null,2)):h("",!0),e.button?(o(),u("span",{key:1,innerHTML:e.button},null,8,Ja)):h("",!0),r[3]||(r[3]=f("span",{class:"vpi-chevron-down text-icon"},null,-1))])):(o(),u("span",Ya))],8,za),f("div",Xa,[_(Wa,{items:e.items},{default:v(()=>[c(s.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),_e=b(Qa,[["__scopeId","data-v-42cb505d"]]),Za=["href","aria-label","rel","innerHTML"],es=m({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{},me:{type:Boolean}},setup(e){const t=e,n=V();R(async()=>{await Ae();const s=n.value?.children[0];s instanceof HTMLElement&&s.className.startsWith("vpi-social-")&&(getComputedStyle(s).maskImage||getComputedStyle(s).webkitMaskImage)==="none"&&s.style.setProperty("--icon",`url('https://api.iconify.design/simple-icons/${t.icon}.svg')`)});const a=p(()=>typeof t.icon=="object"?t.icon.svg:``);return(s,r)=>(o(),u("a",{ref_key:"el",ref:n,class:"VPSocialLink no-icon",href:e.link,"aria-label":e.ariaLabel??(typeof e.icon=="string"?e.icon:""),target:"_blank",rel:e.me?"me noopener":"noopener",innerHTML:a.value},null,8,Za))}}),ts=b(es,[["__scopeId","data-v-591a6b30"]]),ns={class:"VPSocialLinks"},as=m({__name:"VPSocialLinks",props:{links:{},me:{type:Boolean,default:!0}},setup(e){return(t,n)=>(o(),u("div",ns,[(o(!0),u(S,null,w(e.links,({link:a,icon:s,ariaLabel:r})=>(o(),k(ts,{key:a,icon:s,link:a,ariaLabel:r,me:e.me},null,8,["icon","link","ariaLabel","me"]))),128))]))}}),be=b(as,[["__scopeId","data-v-d07f11e6"]]),ss={key:0,class:"group translations"},os={class:"trans-title"},rs={key:1,class:"group"},is={class:"item appearance"},ls={class:"label"},cs={class:"appearance-action"},us={key:2,class:"group"},ds={class:"item social-links"},vs=m({__name:"VPNavBarExtra",setup(e){const{site:t,theme:n}=$(),{localeLinks:a,currentLang:s}=X({correspondingLink:!0}),r=p(()=>a.value.length&&s.value.label||t.value.appearance||n.value.socialLinks);return(d,l)=>r.value?(o(),k(_e,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:v(()=>[i(a).length&&i(s).label?(o(),u("div",ss,[f("p",os,L(i(s).label),1),(o(!0),u(S,null,w(i(a),g=>(o(),k(oe,{key:g.link,item:g,lang:g.lang,hreflang:g.lang,rel:"alternate",dir:g.dir},null,8,["item","lang","hreflang","dir"]))),128))])):h("",!0),i(t).appearance&&i(t).appearance!=="force-dark"&&i(t).appearance!=="force-auto"?(o(),u("div",rs,[f("div",is,[f("p",ls,L(i(n).darkModeSwitchLabel||"Appearance"),1),f("div",cs,[_(ke)])])])):h("",!0),i(n).socialLinks?(o(),u("div",us,[f("div",ds,[_(be,{class:"social-links-list",links:i(n).socialLinks},null,8,["links"])])])):h("",!0)]),_:1})):h("",!0)}}),fs=b(vs,[["__scopeId","data-v-562c832a"]]),hs=["aria-expanded"],ms=m({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(e){return(t,n)=>(o(),u("button",{type:"button",class:P(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:n[0]||(n[0]=a=>t.$emit("click"))},[...n[1]||(n[1]=[f("span",{class:"container"},[f("span",{class:"top"}),f("span",{class:"middle"}),f("span",{class:"bottom"})],-1)])],10,hs))}}),ps=b(ms,[["__scopeId","data-v-e5dd9c1c"]]),ks=["innerHTML"],gs=m({__name:"VPNavBarMenuLink",props:{item:{}},setup(e){const t=e,{page:n}=$(),a=p(()=>typeof t.item.link=="function"?t.item.link(n.value):t.item.link),s=p(()=>D(n.value.relativePath,t.item.activeMatch||a.value,!!t.item.activeMatch));return(r,d)=>(o(),k(C,{class:P({VPNavBarMenuLink:!0,active:s.value}),href:a.value,target:e.item.target,rel:e.item.rel,"no-icon":e.item.noIcon,tabindex:"0"},{default:v(()=>[f("span",{innerHTML:e.item.text},null,8,ks)]),_:1},8,["class","href","target","rel","no-icon"]))}}),_s=b(gs,[["__scopeId","data-v-6dd25bb8"]]),bs=m({__name:"VPNavBarMenuGroup",props:{item:{}},setup(e){const t=e,{page:n}=$(),a=p(()=>t.item.activeMatch?D(n.value.relativePath,t.item.activeMatch,!0):s(t.item));function s(r){if("component"in r)return!1;if("link"in r){const d=typeof r.link=="function"?r.link(n.value):r.link;return D(n.value.relativePath,r.activeMatch||d,!!r.activeMatch)}return r.items.some(s)}return(r,d)=>(o(),k(_e,{class:P({VPNavBarMenuGroup:!0,active:a.value}),button:e.item.text,items:e.item.items},null,8,["class","button","items"]))}}),$s={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},ys=m({__name:"VPNavBarMenu",setup(e){const{theme:t}=$();return(n,a)=>i(t).nav?(o(),u("nav",$s,[a[0]||(a[0]=f("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),(o(!0),u(S,null,w(i(t).nav,s=>(o(),u(S,{key:JSON.stringify(s)},["link"in s?(o(),k(_s,{key:0,item:s},null,8,["item"])):"component"in s?(o(),k(B(s.component),O({key:1,ref_for:!0},s.props),null,16)):(o(),k(bs,{key:2,item:s},null,8,["item"]))],64))),128))])):h("",!0)}}),Ps=b(ys,[["__scopeId","data-v-39714824"]]);function Ls(e){const t=e.mode??"auto",n=Vs(e),a=e.askAi,s=!!(a&&typeof a=="object"&&a.sidePanel);switch(t){case"sidePanel":return{mode:t,showKeywordSearch:!1,useSidePanel:!0};case"hybrid":return n||console.error('[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).'),{mode:t,showKeywordSearch:n,useSidePanel:!0};case"modal":return{mode:t,showKeywordSearch:n,useSidePanel:!1};case"auto":default:return{mode:"auto",showKeywordSearch:n,useSidePanel:s}}}function Vs(e){return!!(e.appId&&e.apiKey&&e.indexName)}function Oe(e,t){return[...(Array.isArray(e)?e:e?[e]:[]).map(s=>Array.isArray(s)?s.filter(r=>typeof r=="string"&&!r.startsWith("lang:")):s).filter(s=>typeof s=="string"?!s.startsWith("lang:"):Array.isArray(s)&&s.length>0),`lang:${t}`]}function Ss(e,t,n){const a=typeof e=="string",s=!a&&e.searchParameters?{...e.searchParameters}:void 0,r=s?.facetFilters??t.searchParameters?.facetFilters,d=Oe(r,n),l={...s,facetFilters:d.length?d:void 0},g={...a?{}:e,indexName:a?t.indexName:e.indexName,apiKey:a?t.apiKey:e.apiKey,appId:a?t.appId:e.appId,assistantId:a?e:e.assistantId};return Object.values(l).some(y=>y!=null)&&(g.searchParameters=l),g}function Ns(e,t,n){e=De(e,e.locales?.[t]||{});const a=Oe(e.searchParameters?.facetFilters,n),s=e.askAi?Ss(e.askAi,e,n):void 0;return{...e,searchParameters:{...e.searchParameters,facetFilters:a},askAi:s}}function De(e,t){const n={...e};for(const a in t){const s=t[a];if(s!==void 0){if(a==="searchParameters"){n[a]=s;continue}Pe(s)&&Pe(n[a])?n[a]=De(n[a],s):n[a]=s}}return delete n.locales,n}function Ts(e,t=(n,a)=>JSON.stringify(n)===JSON.stringify(a)){return p(n=>{const a=e();return n===void 0||!t(n,a)?a:n})}const As={},Ms={type:"button",class:"VPNavBarAskAiButton"};function ws(e,t){return o(),u("button",Ms,[...t[0]||(t[0]=[f("span",{class:"vpi-sparkles","aria-hidden":"true"},null,-1)])])}const Is=b(As,[["render",ws],["__scopeId","data-v-4eb17e89"]]),xs={type:"button",class:"VPNavBarSearchButton"},Hs={class:"text"},Bs=m({__name:"VPNavBarSearchButton",props:{text:{}},setup(e){return(t,n)=>(o(),u("button",xs,[n[0]||(n[0]=f("span",{class:"vpi-search","aria-hidden":"true"},null,-1)),f("span",Hs,L(e.text),1),n[1]||(n[1]=f("span",{class:"keys","aria-hidden":"true"},[f("kbd",{class:"key-cmd"},"⌘"),f("kbd",{class:"key-ctrl"},"Ctrl"),f("kbd",null,"K")],-1))]))}}),Le=b(Bs,[["__scopeId","data-v-baa3be99"]]),Cs={class:"VPNavBarSearch"},Es=m({__name:"VPNavBarSearch",setup(e){const t=Xe(()=>Qe(()=>import("./VPLocalSearchBox.DIqEGPXK.js"),__vite__mapDeps([0,1]))),n=()=>null,{theme:a,localeIndex:s,lang:r}=$(),d="local",l=Ts(()=>Ns(a.value.search?.options||{},s.value,r.value)),g=p(()=>Ls(l.value)),y=p(()=>{if(!g.value.useSidePanel)return null;const T=l.value.askAi;return!T||typeof T=="string"||!T.sidePanel?null:T.sidePanel===!0?{}:T.sidePanel}),N=p(()=>y.value?.keyboardShortcuts?.["Ctrl/Cmd+I"]!==!1),A=V(null);let E=0;const I=V(!1),x=V(!1);R(()=>{});function W(T){I.value||(I.value=!0),A.value={target:T,nonce:++E}}const F=V(!1);ie("k",T=>{(T.ctrlKey||T.metaKey)&&(T.preventDefault(),F.value=!0)}),ie("/",T=>{z(T)||(T.preventDefault(),F.value=!0)});function z(T){const M=T.target,K=M.tagName;return M.isContentEditable||K==="INPUT"||K==="SELECT"||K==="TEXTAREA"}return(T,M)=>(o(),u("div",Cs,[i(d)==="algolia"?(o(),u(S,{key:0},[g.value.showKeywordSearch?(o(),k(Le,{key:0,text:i(l).translations?.button?.buttonText||"Search","aria-label":i(l).translations?.button?.buttonAriaLabel||"Search","aria-keyshortcuts":"/ control+k meta+k",onClick:M[0]||(M[0]=K=>W("search"))},null,8,["text","aria-label"])):h("",!0),y.value?(o(),k(Is,{key:1,"aria-label":y.value.button?.translations?.buttonAriaLabel||"Ask AI","aria-keyshortcuts":N.value?"control+i meta+i":void 0,onClick:M[1]||(M[1]=K=>x.value?W("toggleAskAi"):W("askAi"))},null,8,["aria-label","aria-keyshortcuts"])):h("",!0),I.value?(o(),k(i(n),{key:2,"algolia-options":i(l),"open-request":A.value,onVnodeBeforeMount:M[2]||(M[2]=K=>x.value=!0)},null,8,["algolia-options","open-request"])):h("",!0)],64)):i(d)==="local"?(o(),u(S,{key:1},[_(Le,{text:i(l).translations?.button?.buttonText||"Search","aria-label":i(l).translations?.button?.buttonAriaLabel||"Search","aria-keyshortcuts":"/ control+k meta+k",onClick:M[3]||(M[3]=K=>F.value=!0)},null,8,["text","aria-label"]),F.value?(o(),k(i(t),{key:0,onClose:M[4]||(M[4]=K=>F.value=!1)})):h("",!0)],64)):h("",!0)]))}}),Fs=b(Es,[["__scopeId","data-v-2fc7f2c6"]]),Os=m({__name:"VPNavBarSocialLinks",setup(e){const{theme:t}=$();return(n,a)=>i(t).socialLinks?(o(),k(be,{key:0,class:"VPNavBarSocialLinks",links:i(t).socialLinks},null,8,["links"])):h("",!0)}}),Ds=b(Os,[["__scopeId","data-v-0394ad82"]]),Gs=["href","rel","target"],Ks=["innerHTML"],Rs={key:2},Us=m({__name:"VPNavBarTitle",setup(e){const{site:t,theme:n}=$(),{hasSidebar:a}=G(),{currentLang:s}=X(),r=p(()=>typeof n.value.logoLink=="string"?n.value.logoLink:n.value.logoLink?.link),d=p(()=>typeof n.value.logoLink=="string"?void 0:n.value.logoLink?.rel),l=p(()=>typeof n.value.logoLink=="string"?void 0:n.value.logoLink?.target);return(g,y)=>(o(),u("div",{class:P(["VPNavBarTitle",{"has-sidebar":i(a)}])},[f("a",{class:"title",href:r.value??i(me)(i(s).link),rel:d.value,target:l.value},[c(g.$slots,"nav-bar-title-before",{},void 0,!0),i(n).logo?(o(),k(ee,{key:0,class:"logo",image:i(n).logo},null,8,["image"])):h("",!0),i(n).siteTitle?(o(),u("span",{key:1,innerHTML:i(n).siteTitle},null,8,Ks)):i(n).siteTitle===void 0?(o(),u("span",Rs,L(i(t).title),1)):h("",!0),c(g.$slots,"nav-bar-title-after",{},void 0,!0)],8,Gs)],2))}}),js=b(Us,[["__scopeId","data-v-1e38c6bc"]]),Ws={class:"items"},zs={class:"title"},qs=m({__name:"VPNavBarTranslations",setup(e){const{theme:t}=$(),{localeLinks:n,currentLang:a}=X({correspondingLink:!0});return(s,r)=>i(n).length&&i(a).label?(o(),k(_e,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:i(t).langMenuLabel||"Change language"},{default:v(()=>[f("div",Ws,[f("p",zs,L(i(a).label),1),(o(!0),u(S,null,w(i(n),d=>(o(),k(oe,{key:d.link,item:d,lang:d.lang,hreflang:d.lang,rel:"alternate",dir:d.dir},null,8,["item","lang","hreflang","dir"]))),128))])]),_:1},8,["label"])):h("",!0)}}),Js=b(qs,[["__scopeId","data-v-fafa8c23"]]),Ys={class:"wrapper"},Xs={class:"container"},Qs={class:"title"},Zs={class:"content"},eo={class:"content-body"},to=m({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(e){const{y:t}=Me(),{isHome:n,hasSidebar:a}=G();return(s,r)=>(o(),u("div",{class:P(["VPNavBar",{"has-sidebar":i(a),home:i(n),top:i(t)===0,"screen-open":e.isScreenOpen}])},[f("div",Ys,[f("div",Xs,[f("div",Qs,[_(js,null,{"nav-bar-title-before":v(()=>[c(s.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[c(s.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),f("div",Zs,[f("div",eo,[c(s.$slots,"nav-bar-content-before",{},void 0,!0),_(Fs,{class:"search"}),_(Ps,{class:"menu"}),_(Js,{class:"translations"}),_(Ia,{class:"appearance"}),_(Ds,{class:"social-links"}),_(fs,{class:"extra"}),c(s.$slots,"nav-bar-content-after",{},void 0,!0),_(ps,{class:"hamburger",active:e.isScreenOpen,onClick:r[0]||(r[0]=d=>s.$emit("toggle-screen"))},null,8,["active"])])])])]),r[1]||(r[1]=f("div",{class:"divider"},[f("div",{class:"divider-line"})],-1))],2))}}),no=b(to,[["__scopeId","data-v-9ca1369d"]]),ao={key:0,class:"VPNavScreenAppearance"},so={class:"text"},oo=m({__name:"VPNavScreenAppearance",setup(e){const{site:t,theme:n}=$();return(a,s)=>i(t).appearance&&i(t).appearance!=="force-dark"&&i(t).appearance!=="force-auto"?(o(),u("div",ao,[f("p",so,L(i(n).darkModeSwitchLabel||"Appearance"),1),_(ke)])):h("",!0)}}),ro=b(oo,[["__scopeId","data-v-b44890b2"]]),io=["innerHTML"],lo=m({__name:"VPNavScreenMenuLink",props:{item:{}},setup(e){const t=e,{page:n}=$(),a=p(()=>typeof t.item.link=="function"?t.item.link(n.value):t.item.link),s=p(()=>D(n.value.relativePath,t.item.activeMatch||a.value,!!t.item.activeMatch)),{closeScreen:r}=se(pe);return(d,l)=>(o(),k(C,{class:P({VPNavScreenMenuLink:!0,active:s.value}),href:a.value,target:e.item.target,rel:e.item.rel,"no-icon":e.item.noIcon,onClick:i(r)},{default:v(()=>[f("span",{innerHTML:e.item.text},null,8,io)]),_:1},8,["class","href","target","rel","no-icon","onClick"]))}}),co=b(lo,[["__scopeId","data-v-b924ab8a"]]),uo=["innerHTML"],vo=m({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(e){const t=e,{page:n}=$(),a=p(()=>typeof t.item.link=="function"?t.item.link(n.value):t.item.link),s=p(()=>D(n.value.relativePath,t.item.activeMatch||a.value,!!t.item.activeMatch)),{closeScreen:r}=se(pe);return(d,l)=>(o(),k(C,{class:P({VPNavScreenMenuGroupLink:!0,active:s.value}),href:a.value,target:e.item.target,rel:e.item.rel,"no-icon":e.item.noIcon,onClick:i(r)},{default:v(()=>[f("span",{innerHTML:e.item.text},null,8,uo)]),_:1},8,["class","href","target","rel","no-icon","onClick"]))}}),Ge=b(vo,[["__scopeId","data-v-ecf4b472"]]),fo={class:"VPNavScreenMenuGroupSection"},ho={key:0,class:"title"},mo=m({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(e){return(t,n)=>(o(),u("div",fo,[e.text?(o(),u("p",ho,L(e.text),1)):h("",!0),(o(!0),u(S,null,w(e.items,a=>(o(),k(Ge,{key:a.text,item:a},null,8,["item"]))),128))]))}}),po=b(mo,[["__scopeId","data-v-4b7a798b"]]),ko=["aria-controls","aria-expanded"],go=["innerHTML"],_o=["id"],bo={key:0,class:"item"},$o={key:1,class:"item"},yo={key:2,class:"group"},Po=m({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(e){const t=e,n=V(!1),a=p(()=>`NavScreenGroup-${t.text.replace(" ","-").toLowerCase()}`);function s(){n.value=!n.value}return(r,d)=>(o(),u("div",{class:P(["VPNavScreenMenuGroup",{open:n.value}])},[f("button",{class:"button","aria-controls":a.value,"aria-expanded":n.value,onClick:s},[f("span",{class:"button-text",innerHTML:e.text},null,8,go),d[0]||(d[0]=f("span",{class:"vpi-plus button-icon"},null,-1))],8,ko),f("div",{id:a.value,class:"items"},[(o(!0),u(S,null,w(e.items,l=>(o(),u(S,{key:JSON.stringify(l)},["link"in l?(o(),u("div",bo,[_(Ge,{item:l},null,8,["item"])])):"component"in l?(o(),u("div",$o,[(o(),k(B(l.component),O({ref_for:!0},l.props,{"screen-menu":""}),null,16))])):(o(),u("div",yo,[_(po,{text:l.text,items:l.items},null,8,["text","items"])]))],64))),128))],8,_o)],2))}}),Lo=b(Po,[["__scopeId","data-v-956364f9"]]),Vo={key:0,class:"VPNavScreenMenu"},So=m({__name:"VPNavScreenMenu",setup(e){const{theme:t}=$();return(n,a)=>i(t).nav?(o(),u("nav",Vo,[(o(!0),u(S,null,w(i(t).nav,s=>(o(),u(S,{key:JSON.stringify(s)},["link"in s?(o(),k(co,{key:0,item:s},null,8,["item"])):"component"in s?(o(),k(B(s.component),O({key:1,ref_for:!0},s.props,{"screen-menu":""}),null,16)):(o(),k(Lo,{key:2,text:s.text||"",items:s.items},null,8,["text","items"]))],64))),128))])):h("",!0)}}),No=m({__name:"VPNavScreenSocialLinks",setup(e){const{theme:t}=$();return(n,a)=>i(t).socialLinks?(o(),k(be,{key:0,class:"VPNavScreenSocialLinks",links:i(t).socialLinks},null,8,["links"])):h("",!0)}}),To={class:"list"},Ao=m({__name:"VPNavScreenTranslations",setup(e){const{localeLinks:t,currentLang:n}=X({correspondingLink:!0}),a=V(!1);function s(){a.value=!a.value}return(r,d)=>i(t).length&&i(n).label?(o(),u("div",{key:0,class:P(["VPNavScreenTranslations",{open:a.value}])},[f("button",{class:"title",onClick:s},[d[0]||(d[0]=f("span",{class:"vpi-languages icon lang"},null,-1)),U(" "+L(i(n).label)+" ",1),d[1]||(d[1]=f("span",{class:"vpi-chevron-down icon chevron"},null,-1))]),f("ul",To,[(o(!0),u(S,null,w(i(t),l=>(o(),u("li",{key:l.link,class:"item"},[_(C,{class:"link",href:l.link,lang:l.lang,dir:l.dir},{default:v(()=>[U(L(l.text),1)]),_:2},1032,["href","lang","dir"])]))),128))])],2)):h("",!0)}}),Mo=b(Ao,[["__scopeId","data-v-a4d9b172"]]),wo={key:0,class:"VPNavScreen",id:"VPNavScreen"},Io={class:"container"},xo=m({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(e){const t=we(Y?document.body:null);return(n,a)=>(o(),k(ve,{name:"fade",onEnter:a[0]||(a[0]=s=>t.value=!0),onAfterLeave:a[1]||(a[1]=s=>t.value=!1)},{default:v(()=>[e.open?(o(),u("div",wo,[f("div",Io,[c(n.$slots,"nav-screen-content-before",{},void 0,!0),_(So,{class:"menu"}),_(Mo,{class:"translations"}),_(ro,{class:"appearance"}),_(No,{class:"social-links"}),c(n.$slots,"nav-screen-content-after",{},void 0,!0)])])):h("",!0)]),_:3}))}}),Ho=b(xo,[["__scopeId","data-v-05f3d7bc"]]),Bo={key:0,class:"VPNav"},Co=m({__name:"VPNav",setup(e){const{isScreenOpen:t,closeScreen:n,toggleScreen:a}=ya(),{frontmatter:s}=$(),r=p(()=>s.value.navbar!==!1);return Ie(pe,{closeScreen:n}),te(()=>{Y&&document.documentElement.classList.toggle("hide-nav",!r.value)}),(d,l)=>r.value?(o(),u("header",Bo,[_(no,{"is-screen-open":i(t),onToggleScreen:i(a)},{"nav-bar-title-before":v(()=>[c(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[c(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[c(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[c(d.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),_(Ho,{open:i(t)},{"nav-screen-content-before":v(()=>[c(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[c(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):h("",!0)}}),Eo=b(Co,[["__scopeId","data-v-9f75dce3"]]),Fo=["role","tabindex"],Oo={key:1,class:"items"},Do=m({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(e){const t=e,{collapsed:n,collapsible:a,isLink:s,isActiveLink:r,hasActiveLink:d,hasChildren:l,toggle:g}=Mt(p(()=>t.item)),y=p(()=>l.value?"section":"div"),N=p(()=>s.value?"a":"div"),A=p(()=>l.value?t.depth+2===7?"p":`h${t.depth+2}`:"p"),E=p(()=>s.value?void 0:"button"),I=p(()=>[[`level-${t.depth}`],{collapsible:a.value},{collapsed:n.value},{"is-link":s.value},{"is-active":r.value},{"has-active":d.value}]);function x(F){"key"in F&&F.key!=="Enter"||!t.item.link&&g()}function W(){t.item.link&&g()}return(F,z)=>{const T=j("VPSidebarItem",!0);return o(),k(B(y.value),{class:P(["VPSidebarItem",I.value])},{default:v(()=>[e.item.text?(o(),u("div",O({key:0,class:"item",role:E.value},Ze(e.item.items?{click:x,keydown:x}:{},!0),{tabindex:e.item.items&&0}),[z[1]||(z[1]=f("div",{class:"indicator"},null,-1)),e.item.link?(o(),k(C,{key:0,tag:N.value,class:"link",href:e.item.link,rel:e.item.rel,target:e.item.target},{default:v(()=>[(o(),k(B(A.value),{class:"text",innerHTML:e.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(o(),k(B(A.value),{key:1,class:"text",innerHTML:e.item.text},null,8,["innerHTML"])),e.item.collapsed!=null&&e.item.items&&e.item.items.length?(o(),u("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:W,onKeydown:et(W,["enter"]),tabindex:"0"},[...z[0]||(z[0]=[f("span",{class:"vpi-chevron-right caret-icon"},null,-1)])],32)):h("",!0)],16,Fo)):h("",!0),e.item.items&&e.item.items.length?(o(),u("div",Oo,[e.depth<5?(o(!0),u(S,{key:0},w(e.item.items,M=>(o(),k(T,{key:M.text,item:M,depth:e.depth+1},null,8,["item","depth"]))),128)):h("",!0)])):h("",!0)]),_:1},8,["class"])}}}),Go=b(Do,[["__scopeId","data-v-d81de50c"]]),Ko=m({__name:"VPSidebarGroup",props:{items:{}},setup(e){const t=V(!0);let n=null;return R(()=>{n=setTimeout(()=>{n=null,t.value=!1},300)}),tt(()=>{n!=null&&(clearTimeout(n),n=null)}),(a,s)=>(o(!0),u(S,null,w(e.items,r=>(o(),u("div",{key:r.text,class:P(["group",{"no-transition":t.value}])},[_(Go,{item:r,depth:0},null,8,["item"])],2))),128))}}),Ro=b(Ko,[["__scopeId","data-v-8d50c081"]]),Uo={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},jo=m({__name:"VPSidebar",props:{open:{type:Boolean}},setup(e){const{sidebarGroups:t,hasSidebar:n}=G(),a=e,s=V(null),r=we(Y?document.body:null);H([a,s],()=>{a.open?(r.value=!0,s.value?.focus()):r.value=!1},{immediate:!0,flush:"post"});const d=V(0);return H(t,()=>{d.value+=1},{deep:!0}),(l,g)=>i(n)?(o(),u("aside",{key:0,class:P(["VPSidebar",{open:e.open}]),ref_key:"navEl",ref:s,onClick:g[0]||(g[0]=nt(()=>{},["stop"]))},[g[2]||(g[2]=f("div",{class:"curtain"},null,-1)),f("nav",Uo,[g[1]||(g[1]=f("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),c(l.$slots,"sidebar-nav-before",{},void 0,!0),(o(),k(Ro,{items:i(t),key:d.value},null,8,["items"])),c(l.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):h("",!0)}}),Wo=b(jo,[["__scopeId","data-v-af661f50"]]),zo={href:"#VPContent",class:"VPSkipLink visually-hidden"},qo=m({__name:"VPSkipLink",setup(e){const{theme:t}=$(),n=ne(),a=V();return H(()=>n.path,()=>a.value.focus()),(s,r)=>(o(),u(S,null,[f("span",{ref_key:"backToTop",ref:a,tabindex:"-1"},null,512),f("a",zo,L(i(t).skipToContentLabel||"Skip to content"),1)],64))}}),Jo=b(qo,[["__scopeId","data-v-331ec75c"]]),Yo=m({__name:"Layout",setup(e){const{isOpen:t,open:n,close:a}=At();wt({closeSidebar:a});const{frontmatter:s}=$(),r=at(),d=p(()=>!!r["home-hero-image"]);return Ie(Be,{heroImageSlotExists:d}),(l,g)=>{const y=j("Content");return i(s).layout!==!1?(o(),u("div",{key:0,class:P(["Layout",i(s).pageClass])},[c(l.$slots,"layout-top",{},void 0,!0),_(Jo),_(it,{class:"backdrop",show:i(t),onClick:i(a)},null,8,["show","onClick"]),_(Eo,null,{"nav-bar-title-before":v(()=>[c(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[c(l.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[c(l.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[c(l.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":v(()=>[c(l.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[c(l.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),_($a,{open:i(t),onOpenMenu:i(n)},null,8,["open","onOpenMenu"]),_(Wo,{open:i(t)},{"sidebar-nav-before":v(()=>[c(l.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":v(()=>[c(l.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),_(ra,null,{"page-top":v(()=>[c(l.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[c(l.$slots,"page-bottom",{},void 0,!0)]),"not-found":v(()=>[c(l.$slots,"not-found",{},void 0,!0)]),"home-hero-before":v(()=>[c(l.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[c(l.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[c(l.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[c(l.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[c(l.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-actions-before-actions":v(()=>[c(l.$slots,"home-hero-actions-before-actions",{},void 0,!0)]),"home-hero-image":v(()=>[c(l.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[c(l.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[c(l.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[c(l.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":v(()=>[c(l.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[c(l.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[c(l.$slots,"doc-after",{},void 0,!0)]),"doc-top":v(()=>[c(l.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[c(l.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":v(()=>[c(l.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[c(l.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[c(l.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[c(l.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[c(l.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[c(l.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),_(da),c(l.$slots,"layout-bottom",{},void 0,!0)],2)):(o(),k(y,{key:1}))}}}),Xo=b(Yo,[["__scopeId","data-v-0cf61682"]]),Zo={Layout:Xo,enhanceApp:({app:e})=>{e.component("Badge",st)}};export{Zo as t,$ as u}; diff --git a/docs/assets/guide_auth.md.DNA7JhY7.js b/docs/assets/guide_auth.md.DNA7JhY7.js deleted file mode 100644 index 283088fbfe..0000000000 --- a/docs/assets/guide_auth.md.DNA7JhY7.js +++ /dev/null @@ -1,3 +0,0 @@ -import{c as i,S as t,j as a,m as e}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Auth","description":"","frontmatter":{},"headers":[],"relativePath":"guide/auth.md","filePath":"guide/auth.md","lastUpdated":1779780644000}'),h={name:"guide/auth.md"};function n(o,s,l,r,p,k){return t(),a("div",null,[...s[0]||(s[0]=[e(`

Auth

Gittensory has two auth surfaces:

  • GitHub OAuth for MCP users.
  • GitHub App installation auth for repositories.

MCP User Auth

Run:

sh
gittensory-mcp login

The CLI starts GitHub Device Flow, asks you to approve the code on GitHub, then exchanges the GitHub token server-side for a short-lived Gittensory session token.

Gittensory stores:

  • a hashed server-side session token
  • the GitHub login and user id
  • scopes and session expiry metadata

Gittensory does not store user PATs.

Non-Interactive Bootstrap

For local automation, pass a GitHub token only long enough to mint a Gittensory session:

sh
gittensory-mcp login --github-token "$(gh auth token)"

The token is exchanged immediately. The MCP wrapper stores the Gittensory session token, not the GitHub token.

Check Auth State

sh
gittensory-mcp whoami
-gittensory-mcp status
-gittensory-mcp doctor

Logout

sh
gittensory-mcp logout

Logout revokes the remote session when possible and removes the local config file.

Static Tokens

Static bearer tokens remain only for private backend operations, internal jobs, and temporary beta bootstrap. Normal MCP users should use GitHub OAuth.

`,21)])])}const u=i(h,[["render",n]]);export{c as __pageData,u as default}; diff --git a/docs/assets/guide_auth.md.DNA7JhY7.lean.js b/docs/assets/guide_auth.md.DNA7JhY7.lean.js deleted file mode 100644 index c1c621e628..0000000000 --- a/docs/assets/guide_auth.md.DNA7JhY7.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as i,S as t,j as a,m as e}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Auth","description":"","frontmatter":{},"headers":[],"relativePath":"guide/auth.md","filePath":"guide/auth.md","lastUpdated":1779780644000}'),h={name:"guide/auth.md"};function n(o,s,l,r,p,k){return t(),a("div",null,[...s[0]||(s[0]=[e("",21)])])}const u=i(h,[["render",n]]);export{c as __pageData,u as default}; diff --git a/docs/assets/guide_github-app-setup.md.ZPvLDsvt.js b/docs/assets/guide_github-app-setup.md.ZPvLDsvt.js deleted file mode 100644 index 83f8a94fd1..0000000000 --- a/docs/assets/guide_github-app-setup.md.ZPvLDsvt.js +++ /dev/null @@ -1,3 +0,0 @@ -import{c as t,S as i,j as s,m as a}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"GitHub App Setup","description":"","frontmatter":{},"headers":[],"relativePath":"guide/github-app-setup.md","filePath":"guide/github-app-setup.md","lastUpdated":1779781690000}'),l={name:"guide/github-app-setup.md"};function r(d,e,n,h,o,p){return i(),s("div",null,[...e[0]||(e[0]=[a(`

GitHub App Setup

The GitHub App is the maintainer/install surface. GitHub OAuth is the MCP user-auth surface.

Basic Fields

Use these values:

FieldValue
Homepage URLhttps://gittensory.aethereal.dev
Webhook URL\${GITTENSORY_API_URL}/v1/github/webhook
Webhook activeenabled
SSL verificationenabled
Device Flowenabled

GITTENSORY_API_URL is the private API origin for the deployed backend. Do not use the GitHub Pages docs domain for webhooks; Pages only serves static docs.

Use a generated webhook secret and set the same value in Cloudflare as GITHUB_WEBHOOK_SECRET.

Required Repository Permissions

PermissionAccessWhy
MetadataReadRequired for repository identity and repository events.
ChecksWriteRequired to create and update the Gittensory check run.
Pull requestsReadRequired for PR metadata, reviewability, and webhook events.
IssuesReadRequired for issue linkage, issue-discovery context, and duplicate signals.

Optional:

PermissionAccessWhy
IssuesWriteOnly needed if public-safe sticky PR comments are enabled.
ContentsReadOnly needed if a future feature reads repository files directly through the App.

Required Events

Subscribe to:

  • Pull request
  • Issues
  • Repository

If GitHub shows Installation target, select it. Some installation-related events are not always shown as normal selectable event rows; Gittensory should not block health on event names that are hidden in the app UI.

Install Or Repair

  1. Update the GitHub App permissions and events.
  2. Reinstall the app or approve the changed permissions.
  3. Select the repos Gittensory should inspect.
  4. Trigger installation-health refresh:
sh
curl -X POST "$GITTENSORY_API_URL/v1/internal/jobs/refresh-installation-health/run" \\
-  -H "Authorization: Bearer $INTERNAL_JOB_TOKEN"
  1. Check health:
sh
curl "$GITTENSORY_API_URL/v1/readiness" \\
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Healthy app installation state should remove the readiness warning about GitHub App installations needing attention.

Marketplace Readiness

Before Marketplace submission, add:

  • public docs URL
  • support contact
  • privacy policy
  • terms page if needed
  • clear setup flow
  • valid webhook and install diagnostics

Do not submit until the privacy, support, terms, install diagnostics, and public setup flow are complete.

`,25)])])}const k=t(l,[["render",r]]);export{c as __pageData,k as default}; diff --git a/docs/assets/guide_github-app-setup.md.ZPvLDsvt.lean.js b/docs/assets/guide_github-app-setup.md.ZPvLDsvt.lean.js deleted file mode 100644 index b93aaf36b6..0000000000 --- a/docs/assets/guide_github-app-setup.md.ZPvLDsvt.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as t,S as i,j as s,m as a}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"GitHub App Setup","description":"","frontmatter":{},"headers":[],"relativePath":"guide/github-app-setup.md","filePath":"guide/github-app-setup.md","lastUpdated":1779781690000}'),l={name:"guide/github-app-setup.md"};function r(d,e,n,h,o,p){return i(),s("div",null,[...e[0]||(e[0]=[a("",25)])])}const k=t(l,[["render",r]]);export{c as __pageData,k as default}; diff --git a/docs/assets/guide_install.md.ByQAA8QU.js b/docs/assets/guide_install.md.ByQAA8QU.js deleted file mode 100644 index 8509126a98..0000000000 --- a/docs/assets/guide_install.md.ByQAA8QU.js +++ /dev/null @@ -1,11 +0,0 @@ -import{c as i,S as a,j as t,m as e}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Install","description":"","frontmatter":{},"headers":[],"relativePath":"guide/install.md","filePath":"guide/install.md","lastUpdated":1779781690000}'),l={name:"guide/install.md"};function n(h,s,p,r,k,o){return a(),t("div",null,[...s[0]||(s[0]=[e(`

Install

Gittensory has two install paths: public npm for normal use, and local checkout for development.

Public npm

Use this for Codex, Claude Desktop, Cursor, or any other stdio MCP client:

sh
npm install -g @jsonbored/gittensory-mcp
-gittensory-mcp login
-gittensory-mcp status
-gittensory-mcp --stdio

The login command uses GitHub Device Flow and stores a short-lived Gittensory session token in your local config directory.

Local checkout

Use this when developing Gittensory itself:

sh
git clone https://github.com/JSONbored/gittensory.git
-cd gittensory
-npm install
-npm link --workspace @jsonbored/gittensory-mcp
-gittensory-mcp login
-gittensory-mcp --stdio

Verify The Install

Run:

sh
gittensory-mcp doctor
-gittensory-mcp whoami
-gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN --json

doctor checks API health, auth state, source-upload defaults, local git metadata, and whether the binary is likely visible to MCP clients.

Privacy Defaults

Gittensory MCP v1 sends structured metadata only:

  • repository full name
  • branch and base refs
  • changed file paths and counts
  • linked issue references
  • commit messages
  • validation command summaries

It does not upload source contents. GITTENSORY_UPLOAD_SOURCE=true is rejected.

`,17)])])}const g=i(l,[["render",n]]);export{c as __pageData,g as default}; diff --git a/docs/assets/guide_install.md.ByQAA8QU.lean.js b/docs/assets/guide_install.md.ByQAA8QU.lean.js deleted file mode 100644 index 53f3940dad..0000000000 --- a/docs/assets/guide_install.md.ByQAA8QU.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as i,S as a,j as t,m as e}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Install","description":"","frontmatter":{},"headers":[],"relativePath":"guide/install.md","filePath":"guide/install.md","lastUpdated":1779781690000}'),l={name:"guide/install.md"};function n(h,s,p,r,k,o){return a(),t("div",null,[...s[0]||(s[0]=[e("",17)])])}const g=i(l,[["render",n]]);export{c as __pageData,g as default}; diff --git a/docs/assets/guide_maintainers.md.CGSokUpu.js b/docs/assets/guide_maintainers.md.CGSokUpu.js deleted file mode 100644 index c773eabd63..0000000000 --- a/docs/assets/guide_maintainers.md.CGSokUpu.js +++ /dev/null @@ -1 +0,0 @@ -import{c as i,S as a,j as t,m as n}from"./chunks/framework.BXgR76oC.js";const m=JSON.parse('{"title":"For Maintainers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/maintainers.md","filePath":"guide/maintainers.md","lastUpdated":1779780644000}'),l={name:"guide/maintainers.md"};function o(r,e,s,c,d,p){return a(),t("div",null,[...e[0]||(e[0]=[n('

For Maintainers

Gittensory is meant to make Gittensor-driven contribution flow less noisy.

GitHub App Surface

The GitHub App creates the detailed maintainer surface:

  • Gittensory check runs on PRs
  • reviewability context
  • linked issue and duplicate signals
  • contributor role context
  • queue and repo-lane context
  • optional public-safe sticky PR comments

The check run is the canonical detailed report. Public comments are opt-in and sanitized.

Reviewability Actions

Gittensory maps PRs to maintainer-friendly actions:

  • review_now
  • needs_author
  • likely_duplicate
  • close_or_redirect
  • watch
  • maintainer_lane

The point is not to shame contributors. The point is to identify the lowest-friction next step.

Public Comments

Public comments stay off by default.

When enabled, comments can include:

  • contribution context
  • PR hygiene
  • duplicate or WIP risk
  • maintainer review notes
  • contributor next steps

Comments must not include raw trust scores, wallet data, hotkeys, public reward estimates, or public score optimization language.

Repo Owner Signals

Repo owners can use Gittensory to inspect:

  • repo lane clarity
  • label configuration
  • maintainer cut readiness
  • queue health
  • contributor intake health
  • GitHub App installation health
  • stale or degraded backfill state
',18)])])}const h=i(l,[["render",o]]);export{m as __pageData,h as default}; diff --git a/docs/assets/guide_maintainers.md.CGSokUpu.lean.js b/docs/assets/guide_maintainers.md.CGSokUpu.lean.js deleted file mode 100644 index 0dc7eccbcd..0000000000 --- a/docs/assets/guide_maintainers.md.CGSokUpu.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as i,S as a,j as t,m as n}from"./chunks/framework.BXgR76oC.js";const m=JSON.parse('{"title":"For Maintainers","description":"","frontmatter":{},"headers":[],"relativePath":"guide/maintainers.md","filePath":"guide/maintainers.md","lastUpdated":1779780644000}'),l={name:"guide/maintainers.md"};function o(r,e,s,c,d,p){return a(),t("div",null,[...e[0]||(e[0]=[n("",18)])])}const h=i(l,[["render",o]]);export{m as __pageData,h as default}; diff --git a/docs/assets/guide_mcp.md.DK8zvHnS.js b/docs/assets/guide_mcp.md.DK8zvHnS.js deleted file mode 100644 index 9856977d43..0000000000 --- a/docs/assets/guide_mcp.md.DK8zvHnS.js +++ /dev/null @@ -1,19 +0,0 @@ -import{c as i,S as a,j as e,m as t}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"MCP","description":"","frontmatter":{},"headers":[],"relativePath":"guide/mcp.md","filePath":"guide/mcp.md","lastUpdated":1779780644000}'),n={name:"guide/mcp.md"};function l(h,s,p,r,o,k){return a(),e("div",null,[...s[0]||(s[0]=[t(`

MCP

The MCP package is the contributor-facing surface for coding agents. It runs locally over stdio and calls the private Gittensory API with your Gittensory session token.

Generate Client Config

Print a config snippet:

sh
gittensory-mcp init-client --print codex
-gittensory-mcp init-client --print claude
-gittensory-mcp init-client --print cursor

These commands do not edit your files. Use an absolute command path if your client does not inherit your shell PATH.

Codex

toml
[mcp_servers.gittensory]
-command = "gittensory-mcp"
-args = ["--stdio"]

Claude Desktop

json
{
-  "mcpServers": {
-    "gittensory": {
-      "command": "gittensory-mcp",
-      "args": ["--stdio"]
-    }
-  }
-}

Cursor

json
{
-  "mcpServers": {
-    "gittensory": {
-      "command": "gittensory-mcp",
-      "args": ["--stdio"]
-    }
-  }
-}

Tools

The local wrapper exposes repo context, contributor decision packs, local branch preflight, score blockers, PR packets, variant comparison, and registry change tools.

Useful tools:

  • gittensory_local_status
  • gittensory_get_decision_pack
  • gittensory_explain_repo_decision
  • gittensory_preflight_current_branch
  • gittensory_preview_current_branch_score
  • gittensory_rank_local_next_actions
  • gittensory_explain_local_blockers
  • gittensory_prepare_pr_packet

Runtime Rules

  • Stdio only for the local wrapper.
  • No source upload in v1.
  • Private score and reward/risk reasoning stay in MCP/API output.
  • Public PR packets are sanitized and do not include wallet, hotkey, raw trust score, or public score estimates.
`,18)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/guide_mcp.md.DK8zvHnS.lean.js b/docs/assets/guide_mcp.md.DK8zvHnS.lean.js deleted file mode 100644 index 523cd30cf4..0000000000 --- a/docs/assets/guide_mcp.md.DK8zvHnS.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as i,S as a,j as e,m as t}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"MCP","description":"","frontmatter":{},"headers":[],"relativePath":"guide/mcp.md","filePath":"guide/mcp.md","lastUpdated":1779780644000}'),n={name:"guide/mcp.md"};function l(h,s,p,r,o,k){return a(),e("div",null,[...s[0]||(s[0]=[t("",18)])])}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/guide_miners.md.BeaGERAQ.js b/docs/assets/guide_miners.md.BeaGERAQ.js deleted file mode 100644 index 1c4e116314..0000000000 --- a/docs/assets/guide_miners.md.BeaGERAQ.js +++ /dev/null @@ -1,6 +0,0 @@ -import{c as s,S as e,j as a,m as l}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"For Miners","description":"","frontmatter":{},"headers":[],"relativePath":"guide/miners.md","filePath":"guide/miners.md","lastUpdated":1779785713000}'),n={name:"guide/miners.md"};function t(r,i,h,p,o,k){return e(),a("div",null,[...i[0]||(i[0]=[l(`

For Miners

Gittensory helps miners decide what to do next with evidence instead of guesswork.

What It Answers

  • Is this repo a direct-PR lane, issue-discovery lane, split lane, inactive lane, or unknown lane?
  • Am I in a normal contributor lane or a maintainer lane for this repo?
  • Does my current branch look reviewable?
  • What blocks scoreability right now?
  • Should I clean up open PRs before opening more work?
  • Is there duplicate or WIP collision risk?
  • What public-safe PR packet should I give a maintainer?

Branch Analysis

Run from a Git repo:

sh
gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN --json

The response includes:

  • lane context
  • role context
  • preflight findings
  • private score blockers
  • current vs projected scoreability scenarios
  • reward/risk reasoning
  • base freshness warnings when the local diff may be inflated
  • maintainer-fit notes
  • public-safe PR packet
  • ranked next actions

When the current score is blocked by temporary account/queue state, pass the assumptions explicitly:

sh
gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN \\
-  --pending-merged-prs 3 \\
-  --expected-open-prs 0 \\
-  --projected-credibility 0.8 \\
-  --scenario-note "approved PRs expected to merge" \\
-  --json

Gittensory labels that as a user-supplied scenario. It shows the current effective score, the underlying potential score, and what changes if the open-PR and credibility gates clear.

Preflight

sh
gittensory-mcp preflight --login YOUR_GITHUB_LOGIN --json

Use this before opening a PR. It is especially useful when you need to know whether a branch is missing tests, missing a linked issue, colliding with active work, or likely to increase maintainer burden.

How This Helps

Gittensory does not promise payouts. It explains scoreability and risk:

  • open PR pressure
  • credibility assumptions
  • lane eligibility
  • issue-discovery vs direct PR fit
  • duplicate clusters
  • stale work
  • review friction

That makes recommendations actionable: land or withdraw blocked work, avoid direct PRs in issue-discovery-only repos, improve validation evidence, or pick a repo where your history and the lane actually fit.

`,19)])])}const g=s(n,[["render",t]]);export{c as __pageData,g as default}; diff --git a/docs/assets/guide_miners.md.BeaGERAQ.lean.js b/docs/assets/guide_miners.md.BeaGERAQ.lean.js deleted file mode 100644 index 745cd93753..0000000000 --- a/docs/assets/guide_miners.md.BeaGERAQ.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as s,S as e,j as a,m as l}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"For Miners","description":"","frontmatter":{},"headers":[],"relativePath":"guide/miners.md","filePath":"guide/miners.md","lastUpdated":1779785713000}'),n={name:"guide/miners.md"};function t(r,i,h,p,o,k){return e(),a("div",null,[...i[0]||(i[0]=[l("",19)])])}const g=s(n,[["render",t]]);export{c as __pageData,g as default}; diff --git a/docs/assets/index.md.BKalGx3J.js b/docs/assets/index.md.BKalGx3J.js deleted file mode 100644 index 8341947c92..0000000000 --- a/docs/assets/index.md.BKalGx3J.js +++ /dev/null @@ -1 +0,0 @@ -import{c as t,S as i,j as a,m as s}from"./chunks/framework.BXgR76oC.js";const g=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Gittensory","text":"Private repo intelligence for Gittensor work.","tagline":"For Gittensor miners, maintainers, and agents: MCP preflight, scoreability projections, and GitHub App review packets. Still not a Gittensor frontend.","image":{"src":"/logo.svg","alt":"Gittensory signal mark"},"actions":[{"theme":"brand","text":"Install MCP","link":"/guide/install"},{"theme":"alt","text":"GitHub App Setup","link":"/guide/github-app-setup"}]},"features":[{"title":"Scoreability","details":"Current estimate, ungated potential, and near-term what-if scenarios."},{"title":"Queue Pressure","details":"Open PR gates, stale branches, duplicate risk, and cleanup-first actions."},{"title":"Role Context","details":"Contributor lane, maintainer lane, owner history, and repo-specific evidence."},{"title":"Reviewability","details":"Private maintainer packets plus public-safe PR guidance."},{"title":"Registry Signals","details":"Repo lanes, label multipliers, issue discovery, bounties, and config drift."},{"title":"MCP Native","details":"Codex, Claude, Cursor, and agents get structured JSON without code upload."}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1779793775000}'),n={name:"index.md"};function r(o,e,l,d,c,p){return i(),a("div",null,[...e[0]||(e[0]=[s('
01 / Miners
Pick work with less guesswork.Score blockers, lane fit, stale base checks, and cleanup-first guidance.
02 / Maintainers
Review signal, not noise.Role-aware context, duplicate risk, validation evidence, and next action.
03 / Agents
Local branch context.Metadata-only MCP tools for Codex, Claude, Cursor, and automation.
04 / Privacy
Private by default.No PAT storage, no source upload, no public score or wallet leakage.

Not another Gittensor frontend.

Gittensory is the private signal layer behind better contributions: official miner context, registry lanes, queue pressure, scoreability projections, maintainer friction, and public-safe PR packets.

lanedirect_pr / issue_discovery / maintainer
scorecurrent 0, potential visible, scenario gated
riskopen PR pressure, credibility, stale base
outputprivate MCP JSON + sanitized PR packet
',1)])])}const v=t(n,[["render",r]]);export{g as __pageData,v as default}; diff --git a/docs/assets/index.md.BKalGx3J.lean.js b/docs/assets/index.md.BKalGx3J.lean.js deleted file mode 100644 index 45e610d265..0000000000 --- a/docs/assets/index.md.BKalGx3J.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as t,S as i,j as a,m as s}from"./chunks/framework.BXgR76oC.js";const g=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Gittensory","text":"Private repo intelligence for Gittensor work.","tagline":"For Gittensor miners, maintainers, and agents: MCP preflight, scoreability projections, and GitHub App review packets. Still not a Gittensor frontend.","image":{"src":"/logo.svg","alt":"Gittensory signal mark"},"actions":[{"theme":"brand","text":"Install MCP","link":"/guide/install"},{"theme":"alt","text":"GitHub App Setup","link":"/guide/github-app-setup"}]},"features":[{"title":"Scoreability","details":"Current estimate, ungated potential, and near-term what-if scenarios."},{"title":"Queue Pressure","details":"Open PR gates, stale branches, duplicate risk, and cleanup-first actions."},{"title":"Role Context","details":"Contributor lane, maintainer lane, owner history, and repo-specific evidence."},{"title":"Reviewability","details":"Private maintainer packets plus public-safe PR guidance."},{"title":"Registry Signals","details":"Repo lanes, label multipliers, issue discovery, bounties, and config drift."},{"title":"MCP Native","details":"Codex, Claude, Cursor, and agents get structured JSON without code upload."}]},"headers":[],"relativePath":"index.md","filePath":"index.md","lastUpdated":1779793775000}'),n={name:"index.md"};function r(o,e,l,d,c,p){return i(),a("div",null,[...e[0]||(e[0]=[s("",1)])])}const v=t(n,[["render",r]]);export{g as __pageData,v as default}; diff --git a/docs/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2 b/docs/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2 deleted file mode 100644 index 27a82ed339..0000000000 Binary files a/docs/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-cyrillic.D7dRslh9.woff2 b/docs/assets/inter-italic-cyrillic.D7dRslh9.woff2 deleted file mode 100644 index 116e3acd78..0000000000 Binary files a/docs/assets/inter-italic-cyrillic.D7dRslh9.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2 b/docs/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2 deleted file mode 100644 index 9117dabfc2..0000000000 Binary files a/docs/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-greek.DNcpQ8QC.woff2 b/docs/assets/inter-italic-greek.DNcpQ8QC.woff2 deleted file mode 100644 index f098cf7405..0000000000 Binary files a/docs/assets/inter-italic-greek.DNcpQ8QC.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-latin-ext.DytegdRQ.woff2 b/docs/assets/inter-italic-latin-ext.DytegdRQ.woff2 deleted file mode 100644 index 42f338f1c7..0000000000 Binary files a/docs/assets/inter-italic-latin-ext.DytegdRQ.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-latin.COaG5lWR.woff2 b/docs/assets/inter-italic-latin.COaG5lWR.woff2 deleted file mode 100644 index fdc2aa2290..0000000000 Binary files a/docs/assets/inter-italic-latin.COaG5lWR.woff2 and /dev/null differ diff --git a/docs/assets/inter-italic-vietnamese.BI5UxJD-.woff2 b/docs/assets/inter-italic-vietnamese.BI5UxJD-.woff2 deleted file mode 100644 index cfe9183421..0000000000 Binary files a/docs/assets/inter-italic-vietnamese.BI5UxJD-.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2 b/docs/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2 deleted file mode 100644 index 465b22289b..0000000000 Binary files a/docs/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-cyrillic.CD0kT8R4.woff2 b/docs/assets/inter-roman-cyrillic.CD0kT8R4.woff2 deleted file mode 100644 index 6bbced8e79..0000000000 Binary files a/docs/assets/inter-roman-cyrillic.CD0kT8R4.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2 b/docs/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2 deleted file mode 100644 index 7a4633ffbc..0000000000 Binary files a/docs/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-greek.Dsf7YjP7.woff2 b/docs/assets/inter-roman-greek.Dsf7YjP7.woff2 deleted file mode 100644 index e30e308a65..0000000000 Binary files a/docs/assets/inter-roman-greek.Dsf7YjP7.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-latin-ext.Dl_ayf4-.woff2 b/docs/assets/inter-roman-latin-ext.Dl_ayf4-.woff2 deleted file mode 100644 index 6f19b69a4b..0000000000 Binary files a/docs/assets/inter-roman-latin-ext.Dl_ayf4-.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-latin.Cy4MYw_J.woff2 b/docs/assets/inter-roman-latin.Cy4MYw_J.woff2 deleted file mode 100644 index 18a8987f25..0000000000 Binary files a/docs/assets/inter-roman-latin.Cy4MYw_J.woff2 and /dev/null differ diff --git a/docs/assets/inter-roman-vietnamese.CpqCnS2H.woff2 b/docs/assets/inter-roman-vietnamese.CpqCnS2H.woff2 deleted file mode 100644 index 2df3edeb22..0000000000 Binary files a/docs/assets/inter-roman-vietnamese.CpqCnS2H.woff2 and /dev/null differ diff --git a/docs/assets/reference_api.md.CvVunwm0.js b/docs/assets/reference_api.md.CvVunwm0.js deleted file mode 100644 index da252fae87..0000000000 --- a/docs/assets/reference_api.md.CvVunwm0.js +++ /dev/null @@ -1,3 +0,0 @@ -import{c as e,S as a,j as i,m as r}from"./chunks/framework.BXgR76oC.js";const b=JSON.parse('{"title":"API Reference","description":"","frontmatter":{},"headers":[],"relativePath":"reference/api.md","filePath":"reference/api.md","lastUpdated":1779781690000}'),s={name:"reference/api.md"};function d(n,t,o,l,p,h){return a(),i("div",null,[...t[0]||(t[0]=[r(`

API Reference

The REST API is bearer-token protected except for /health, GitHub webhook delivery, and public auth start/poll endpoints.

Use the live OpenAPI document when authenticated:

sh
export GITTENSORY_API_URL="https://your-gittensory-api-origin.example"
-curl "$GITTENSORY_API_URL/openapi.json" \\
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Contributor APIs

EndpointPurpose
GET /v1/contributors/:login/profileContributor evidence profile using official Gittensor stats first when available.
GET /v1/contributors/:login/decision-packCanonical private miner decision payload for MCP and internal clients.
GET /v1/contributors/:login/repos/:owner/:repo/decisionRepo-specific decision extracted from the decision pack.

Repo APIs

EndpointPurpose
GET /v1/reposKnown registered and installed repositories.
GET /v1/repos/:owner/:repoRepository metadata.
GET /v1/repos/:owner/:repo/intelligenceCanonical repository intelligence bundle.
GET /v1/repos/:owner/:repo/pulls/:number/maintainer-packetPR-specific maintainer review packet.
GET /v1/repos/:owner/:repo/pulls/:number/reviewabilityPrivate PR reviewability score and maintainer action.

Local And Preflight APIs

EndpointPurpose
POST /v1/local/branch-analysisMCP-oriented local branch analysis from structured metadata.
POST /v1/preflight/prPlanned PR metadata preflight.
POST /v1/preflight/local-diffLocal-diff metadata preflight.

Ops APIs

EndpointPurpose
GET /v1/readinessService health, signal fidelity, secrets presence, and installation health summary.
GET /v1/sync/statusRepo sync segments, GitHub totals, rate-limit state, and signal fidelity.
GET /v1/installationsGitHub App installations and health records.
GET /v1/installations/:id/healthExact installation permission/event remediation context.

MCP Endpoint

POST /mcp exposes remote MCP over Streamable HTTP style requests. The local npm wrapper is still the preferred MCP user surface.

`,14)])])}const k=e(s,[["render",d]]);export{b as __pageData,k as default}; diff --git a/docs/assets/reference_api.md.CvVunwm0.lean.js b/docs/assets/reference_api.md.CvVunwm0.lean.js deleted file mode 100644 index 3f2b9e0d26..0000000000 --- a/docs/assets/reference_api.md.CvVunwm0.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as e,S as a,j as i,m as r}from"./chunks/framework.BXgR76oC.js";const b=JSON.parse('{"title":"API Reference","description":"","frontmatter":{},"headers":[],"relativePath":"reference/api.md","filePath":"reference/api.md","lastUpdated":1779781690000}'),s={name:"reference/api.md"};function d(n,t,o,l,p,h){return a(),i("div",null,[...t[0]||(t[0]=[r("",14)])])}const k=e(s,[["render",d]]);export{b as __pageData,k as default}; diff --git a/docs/assets/security_privacy.md.CHSODDHk.js b/docs/assets/security_privacy.md.CHSODDHk.js deleted file mode 100644 index 47d8cfe2c9..0000000000 --- a/docs/assets/security_privacy.md.CHSODDHk.js +++ /dev/null @@ -1 +0,0 @@ -import{c as a,S as i,j as t,m as r}from"./chunks/framework.BXgR76oC.js";const h=JSON.parse('{"title":"Privacy And Security","description":"","frontmatter":{},"headers":[],"relativePath":"security/privacy.md","filePath":"security/privacy.md","lastUpdated":1779780644000}'),s={name:"security/privacy.md"};function l(n,e,o,c,d,u){return i(),t("div",null,[...e[0]||(e[0]=[r('

Privacy And Security

Gittensory handles contribution intelligence, not wallets or private source code.

No PAT Storage

MCP login uses GitHub OAuth Device Flow. The backend exchanges the GitHub token for a Gittensory session token and stores only the hashed Gittensory token server-side.

No Source Upload

Local MCP branch analysis sends metadata only:

  • repo full name
  • branch name
  • base and head refs
  • changed file paths
  • additions and deletions
  • linked issue references
  • commit messages
  • validation summaries

Source contents are not uploaded in v1. GITTENSORY_UPLOAD_SOURCE=true fails closed.

Public Output Boundaries

Public comments and public-safe PR packets must not include:

  • wallets
  • hotkeys
  • raw trust scores
  • public score estimates
  • public reward estimates
  • farming language
  • public shaming

Private API and MCP responses can include scoreability and reward/risk reasoning because they are authenticated private outputs.

Rate Limiting

Gittensory uses route classes:

  • strict: auth routes
  • normal: read APIs and MCP tools
  • expensive: branch analysis, scoring preview, decision-pack refresh, signal refresh

Rate-limited responses return 429 with retry metadata.

GitHub App Boundaries

The GitHub App is advisory-only. It does not auto-label, close, merge, or rewrite contributor work.

',18)])])}const b=a(s,[["render",l]]);export{h as __pageData,b as default}; diff --git a/docs/assets/security_privacy.md.CHSODDHk.lean.js b/docs/assets/security_privacy.md.CHSODDHk.lean.js deleted file mode 100644 index 5edec4fbb5..0000000000 --- a/docs/assets/security_privacy.md.CHSODDHk.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as a,S as i,j as t,m as r}from"./chunks/framework.BXgR76oC.js";const h=JSON.parse('{"title":"Privacy And Security","description":"","frontmatter":{},"headers":[],"relativePath":"security/privacy.md","filePath":"security/privacy.md","lastUpdated":1779780644000}'),s={name:"security/privacy.md"};function l(n,e,o,c,d,u){return i(),t("div",null,[...e[0]||(e[0]=[r("",18)])])}const b=a(s,[["render",l]]);export{h as __pageData,b as default}; diff --git a/docs/assets/style.DbhuNsMp.css b/docs/assets/style.DbhuNsMp.css deleted file mode 100644 index 8e7f44c3bc..0000000000 --- a/docs/assets/style.DbhuNsMp.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic.CD0kT8R4.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek.Dsf7YjP7.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-vietnamese.CpqCnS2H.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin-ext.Dl_ayf4-.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin.Cy4MYw_J.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic.D7dRslh9.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek.DNcpQ8QC.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-vietnamese.BI5UxJD-.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin-ext.DytegdRQ.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin.COaG5lWR.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic.CD0kT8R4.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek.Dsf7YjP7.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-vietnamese.CpqCnS2H.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin-ext.Dl_ayf4-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter4CJK;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin.Cy4MYw_J.woff2) format("woff2");unicode-range:U+0000-007D,U+007F-00B6,U+00B8-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-2012,U+2016-2017,U+201A-201B,U+201E-2025,U+2027-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic.D7dRslh9.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek.DNcpQ8QC.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-vietnamese.BI5UxJD-.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin-ext.DytegdRQ.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter4CJK;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin.COaG5lWR.woff2) format("woff2");unicode-range:U+0000-007D,U+007F-00B6,U+00B8-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-2012,U+2016-2017,U+201A-201B,U+201E-2025,U+2027-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: #3c3c43;--vp-c-text-2: #67676c;--vp-c-text-3: #929295}.dark{--vp-c-text-1: #dfdfd6;--vp-c-text-2: #98989f;--vp-c-text-3: #6a6a71}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", -apple-system, BlinkMacSystemFont, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}[lang]:where(:lang(zh,ja)){--vp-font-family-base: "Inter4CJK", -apple-system, BlinkMacSystemFont, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}[lang]:where(:lang(zh,ja,ko)) h1,[lang]:where(:lang(zh,ja,ko)) h2,[lang]:where(:lang(zh,ja,ko)) h3,[lang]:where(:lang(zh,ja,ko)) h4,[lang]:where(:lang(zh,ja,ko)) h5,[lang]:where(:lang(zh,ja,ko)) h6,[lang]:where(:lang(zh,ja,ko)) li,[lang]:where(:lang(zh,ja,ko)) p{line-break:strict}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media(min-width:960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-2);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-2);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:lang(es),:lang(pt){--vp-code-copy-copied-text-content: "Copiado"}:lang(fa){--vp-code-copy-copied-text-content: "کپی شد"}:lang(ko){--vp-code-copy-copied-text-content: "복사됨"}:lang(ru){--vp-code-copy-copied-text-content: "Скопировано"}:lang(zh){--vp-code-copy-copied-text-content: "已复制"}:lang(ja){--vp-code-copy-copied-text-content: "コピー完了"}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@layer __vitepress_base{@media(prefers-reduced-motion:reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-autospace:normal;text-spacing-trim:normal}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono);text-autospace:no-autospace}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 12H3m14 6H3M21 6H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 12h14m-7-7l7 7l-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m9 18l6-6l-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z'/%3E%3C/g%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 12h14m-7-7v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/g%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 3a6 6 0 0 0 9 9a9 9 0 1 1-9-9'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/g%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 8l6 6m-7 0l6-6l2-3M2 5h12M7 2h1m14 20l-5-10l-5 10m2-4h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2c-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/g%3E%3C/svg%3E")}.vpi-sparkles{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.8'%3E%3Cpath d='M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594zM20 2v4m2-2h-4'/%3E%3Ccircle cx='4' cy='20' r='2'/%3E%3C/g%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7m-7 5h7m-7 6h7m-7 5h7'/%3E%3C/g%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm2 4l6 6m0-6l-6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3Cpath d='m9 10l-5 5l5 5'/%3E%3C/g%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/g%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14l2 2l4-4'/%3E%3C/g%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .shiki span{color:var(--shiki-dark, inherit)}html:not(.dark) .shiki span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media(min-width:640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc h4{margin:24px 0 0;letter-spacing:-.01em;line-height:24px;font-size:18px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media(min-width:768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc img,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s;color:var(--vp-c-text-2)}.vp-doc blockquote>p{margin:0;font-size:16px;transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0!important;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group,.vp-doc .custom-block [class*=vp-code-block]{margin-top:8px}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc .custom-block .vp-code-group div[class*=language-],.vp-doc .custom-block [class*=vp-code-block] div[class*=language-]{margin-top:0!important}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code,.vp-doc h4>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media(min-width:640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media(max-width:639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto;text-align:left}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;-webkit-user-select:none;user-select:none;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;white-space:nowrap;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge,.vp-doc h2>.VPBadge{margin:0 0 0 2px;vertical-align:middle}.vp-doc h2>.VPBadge{padding:0 8px}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-c79a1216]{position:fixed;inset:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-c79a1216],.VPBackdrop.fade-leave-to[data-v-c79a1216]{opacity:0}.VPBackdrop.fade-leave-active[data-v-c79a1216]{transition-duration:.25s}@media(min-width:1280px){.VPBackdrop[data-v-c79a1216]{display:none}}.NotFound[data-v-829df670]{padding:64px 24px 96px;text-align:center}@media(min-width:768px){.NotFound[data-v-829df670]{padding:96px 32px 168px}}.code[data-v-829df670]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-829df670]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-829df670]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-829df670]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-829df670]{padding-top:20px}.link[data-v-829df670]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-829df670]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-1ce71065]{position:relative;z-index:1}.nested[data-v-1ce71065]{padding-right:16px;padding-left:16px}.outline-link[data-v-1ce71065]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-1ce71065]:hover,.outline-link.active[data-v-1ce71065]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-1ce71065]{padding-left:13px}.VPDocAsideOutline[data-v-60d5052e]{display:none}.VPDocAsideOutline.has-outline[data-v-60d5052e]{display:block}.content[data-v-60d5052e]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-60d5052e]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-60d5052e]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-3f215769]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-3f215769]{flex-grow:1}.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-3f215769] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-3c637f39]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media(min-width:640px){.VPLastUpdated[data-v-3c637f39]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-e257564d]{margin-top:64px}.edit-info[data-v-e257564d]{padding-bottom:18px}@media(min-width:640px){.edit-info[data-v-e257564d]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-e257564d]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-e257564d]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-e257564d]{margin-right:8px}.prev-next[data-v-e257564d]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media(min-width:640px){.prev-next[data-v-e257564d]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-e257564d]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-e257564d]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-e257564d]{margin-left:auto;text-align:right}.desc[data-v-e257564d]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-e257564d]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-7011f0d8]{padding:32px 24px 96px;width:100%}@media(min-width:768px){.VPDoc[data-v-7011f0d8]{padding:48px 32px 128px}}@media(min-width:960px){.VPDoc[data-v-7011f0d8]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-7011f0d8]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-7011f0d8]{max-width:752px}}@media(min-width:1280px){.VPDoc .container[data-v-7011f0d8]{display:flex;justify-content:center}.VPDoc .aside[data-v-7011f0d8]{display:block}}@media(min-width:1440px){.VPDoc:not(.has-sidebar) .content[data-v-7011f0d8]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-7011f0d8]{max-width:1104px}}.container[data-v-7011f0d8]{margin:0 auto;width:100%}.aside[data-v-7011f0d8]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-7011f0d8]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-7011f0d8]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-7011f0d8]::-webkit-scrollbar{display:none}.aside-curtain[data-v-7011f0d8]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%);pointer-events:none}.aside-content[data-v-7011f0d8]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-7011f0d8]{position:relative;margin:0 auto;width:100%}@media(min-width:960px){.content[data-v-7011f0d8]{padding:0 32px 128px}}@media(min-width:1280px){.content[data-v-7011f0d8]{order:1;margin:0;min-width:640px}}.content-container[data-v-7011f0d8]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-7011f0d8]{max-width:688px}.VPButton[data-v-01bff58b]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-01bff58b]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-01bff58b]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-01bff58b]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-01bff58b]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-01bff58b]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-01bff58b]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-01bff58b]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-01bff58b]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-01bff58b]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-01bff58b]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-01bff58b]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-01bff58b]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-8426fc1a]{display:none}.dark .VPImage.light[data-v-8426fc1a]{display:none}.VPHero[data-v-e62e4946]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media(min-width:640px){.VPHero[data-v-e62e4946]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media(min-width:960px){.VPHero[data-v-e62e4946]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-e62e4946]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media(min-width:960px){.container[data-v-e62e4946]{flex-direction:row}}.main[data-v-e62e4946]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-e62e4946]{text-align:center}@media(min-width:960px){.VPHero.has-image .container[data-v-e62e4946]{text-align:left}}@media(min-width:960px){.main[data-v-e62e4946]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-e62e4946]{max-width:592px}}.heading[data-v-e62e4946]{display:flex;flex-direction:column}:is(.name,.text)[data-v-e62e4946]{width:fit-content;max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}:is(.name,.text)[data-v-e62e4946]:lang(ja){font-feature-settings:"palt";word-break:auto-phrase}.VPHero.has-image .name[data-v-e62e4946],.VPHero.has-image .text[data-v-e62e4946]{margin:0 auto}.name[data-v-e62e4946]{color:var(--vp-home-hero-name-color)}.clip[data-v-e62e4946]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media(min-width:640px){.name[data-v-e62e4946],.text[data-v-e62e4946]{max-width:576px;line-height:56px;font-size:48px}}@media(min-width:960px){.name[data-v-e62e4946],.text[data-v-e62e4946]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-e62e4946],.VPHero.has-image .text[data-v-e62e4946]{margin:0}}.tagline[data-v-e62e4946]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-e62e4946]{margin:0 auto}@media(min-width:640px){.tagline[data-v-e62e4946]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media(min-width:960px){.tagline[data-v-e62e4946]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-e62e4946]{margin:0}}.actions[data-v-e62e4946]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-e62e4946]{justify-content:center}@media(min-width:640px){.actions[data-v-e62e4946]{padding-top:32px}}@media(min-width:960px){.VPHero.has-image .actions[data-v-e62e4946]{justify-content:flex-start}}.action[data-v-e62e4946]{flex-shrink:0;padding:6px}.image[data-v-e62e4946]{order:1;margin:-76px -24px -48px}@media(min-width:640px){.image[data-v-e62e4946]{margin:-108px -24px -48px}}@media(min-width:960px){.image[data-v-e62e4946]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-e62e4946]{position:relative;margin:0 auto;width:320px;height:320px}@media(min-width:640px){.image-container[data-v-e62e4946]{width:392px;height:392px}}@media(min-width:960px){.image-container[data-v-e62e4946]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-e62e4946]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media(min-width:640px){.image-bg[data-v-e62e4946]{width:256px;height:256px}}@media(min-width:960px){.image-bg[data-v-e62e4946]{width:320px;height:320px}}[data-v-e62e4946] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;width:100%;height:100%;object-fit:contain;transform:translate(-50%,-50%)}@media(min-width:640px){[data-v-e62e4946] .image-src{max-width:256px;max-height:256px}}@media(min-width:960px){[data-v-e62e4946] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-e5511d04]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-e5511d04]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-e5511d04]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-e5511d04]>.VPImage{margin-bottom:20px}.icon[data-v-e5511d04]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-e5511d04]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-e5511d04]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}ul.details[data-v-e5511d04]{list-style-type:disc;padding-left:14px}.link-text[data-v-e5511d04]{padding-top:8px}.link-text-value[data-v-e5511d04]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-e5511d04]{margin-left:6px}.VPFeatures[data-v-a6181336]{position:relative;padding:0 24px}@media(min-width:640px){.VPFeatures[data-v-a6181336]{padding:0 48px}}@media(min-width:960px){.VPFeatures[data-v-a6181336]{padding:0 64px}}.container[data-v-a6181336]{margin:0 auto;max-width:1152px}.items[data-v-a6181336]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-a6181336]{padding:8px;width:100%}@media(min-width:640px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:50%}}@media(min-width:768px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336]{width:50%}.item.grid-3[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:calc(100% / 3)}}@media(min-width:960px){.item.grid-4[data-v-a6181336]{width:25%}}.container[data-v-8e2d4988]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media(min-width:640px){.container[data-v-8e2d4988]{padding:0 48px}}@media(min-width:960px){.container[data-v-8e2d4988]{width:100%;padding:0 64px}}.vp-doc[data-v-8e2d4988] .VPHomeSponsors,.vp-doc[data-v-8e2d4988] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-8e2d4988] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-8e2d4988] .VPHomeSponsors a,.vp-doc[data-v-8e2d4988] .VPTeamPage a{text-decoration:none}.VPHome[data-v-bb6342a6]{margin-bottom:96px}@media(min-width:768px){.VPHome[data-v-bb6342a6]{margin-bottom:128px}}.VPContent[data-v-9dc86fcc]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-9dc86fcc]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-9dc86fcc]{margin:0}@media(min-width:960px){.VPContent[data-v-9dc86fcc]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-9dc86fcc]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media(min-width:1440px){.VPContent.has-sidebar[data-v-9dc86fcc]{padding-right:calc((100% - var(--vp-layout-max-width)) / 2);padding-left:calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-c3855bb3]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-c3855bb3]{display:none}.VPFooter[data-v-c3855bb3] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-c3855bb3] a:hover{color:var(--vp-c-text-1)}@media(min-width:768px){.VPFooter[data-v-c3855bb3]{padding:32px}}.container[data-v-c3855bb3]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-c3855bb3],.copyright[data-v-c3855bb3]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown button[data-v-0bf0e06f]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-0bf0e06f]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-0bf0e06f]{color:var(--vp-c-text-1)}.icon[data-v-0bf0e06f]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media(min-width:960px){.VPLocalNavOutlineDropdown button[data-v-0bf0e06f]{font-size:14px}.icon[data-v-0bf0e06f]{font-size:16px}}.open>.icon[data-v-0bf0e06f]{transform:rotate(90deg)}.items[data-v-0bf0e06f]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media(min-width:960px){.items[data-v-0bf0e06f]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-0bf0e06f]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-0bf0e06f]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-0bf0e06f]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-0bf0e06f]{transition:all .2s ease-out}.flyout-leave-active[data-v-0bf0e06f]{transition:all .15s ease-in}.flyout-enter-from[data-v-0bf0e06f],.flyout-leave-to[data-v-0bf0e06f]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-db738f89]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-db738f89]{position:fixed}@media(min-width:960px){.VPLocalNav[data-v-db738f89]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-db738f89]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-db738f89]{display:none}}@media(min-width:1280px){.VPLocalNav[data-v-db738f89]{display:none}}.container[data-v-db738f89]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-db738f89]{display:flex;align-items:center;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-db738f89]:hover{color:var(--vp-c-text-1);transition:color .25s}@media(min-width:960px){.menu[data-v-db738f89]{display:none}}.menu-icon[data-v-db738f89]{margin-right:8px;font-size:14px}.menu[data-v-db738f89],[data-v-db738f89] .VPLocalNavOutlineDropdown>button{padding:12px 24px 11px}@media(min-width:768px){.menu[data-v-db738f89],[data-v-db738f89] .VPLocalNavOutlineDropdown>button{padding:12px 32px 11px}}.VPSwitch[data-v-1d5665e3]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-1d5665e3]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-1d5665e3]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-1d5665e3]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-1d5665e3] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-1d5665e3] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-5337faa4]{opacity:1}.moon[data-v-5337faa4],.dark .sun[data-v-5337faa4]{opacity:0}.dark .moon[data-v-5337faa4]{opacity:1}.dark .VPSwitchAppearance[data-v-5337faa4] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-6c893767]{display:none}@media(min-width:1280px){.VPNavBarAppearance[data-v-6c893767]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-867c295f]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-867c295f]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);text-align:left;white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-867c295f]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-867c295f]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-1963e1bb]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-1963e1bb]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-1963e1bb]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-1963e1bb]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-25a6cce8]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-25a6cce8] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-25a6cce8] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-25a6cce8] .group:last-child{padding-bottom:0}.VPMenu[data-v-25a6cce8] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-25a6cce8] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-25a6cce8] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-25a6cce8] .action{padding-left:24px}.VPFlyout[data-v-42cb505d]{position:relative}.VPFlyout[data-v-42cb505d]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-42cb505d]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-42cb505d]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-42cb505d]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-42cb505d]{color:var(--vp-c-brand-2)}.button[aria-expanded=false]+.menu[data-v-42cb505d]{opacity:0;visibility:hidden;transform:translateY(0)}.VPFlyout:hover .menu[data-v-42cb505d],.button[aria-expanded=true]+.menu[data-v-42cb505d]{opacity:1;visibility:visible;transform:translateY(0)}.button[data-v-42cb505d]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-42cb505d]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-42cb505d]{margin-right:0;font-size:16px}.text-icon[data-v-42cb505d]{margin-left:4px;font-size:14px}.icon[data-v-42cb505d]{font-size:20px;transition:fill .25s}.menu[data-v-42cb505d]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-591a6b30]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-591a6b30]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-591a6b30]>svg,.VPSocialLink[data-v-591a6b30]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-d07f11e6]{display:flex;justify-content:center}.VPNavBarExtra[data-v-562c832a]{display:none;margin-right:-12px}@media(min-width:768px){.VPNavBarExtra[data-v-562c832a]{display:block}}@media(min-width:1280px){.VPNavBarExtra[data-v-562c832a]{display:none}}.trans-title[data-v-562c832a]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-562c832a],.item.social-links[data-v-562c832a]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-562c832a]{min-width:176px}.appearance-action[data-v-562c832a]{margin-right:-2px}.social-links-list[data-v-562c832a]{margin:-4px -8px}.VPNavBarHamburger[data-v-e5dd9c1c]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media(min-width:768px){.VPNavBarHamburger[data-v-e5dd9c1c]{display:none}}.container[data-v-e5dd9c1c]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-e5dd9c1c]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .middle[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .bottom[data-v-e5dd9c1c]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-e5dd9c1c],.middle[data-v-e5dd9c1c],.bottom[data-v-e5dd9c1c]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(0)}.middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-6dd25bb8]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-6dd25bb8],.VPNavBarMenuLink[data-v-6dd25bb8]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-39714824]{display:none}@media(min-width:768px){.VPNavBarMenu[data-v-39714824]{display:flex}}.VPNavBarAskAiButton[data-v-4eb17e89]{display:flex;align-items:center;height:var(--vp-nav-height);padding:8px 14px;font-size:20px}@media(min-width:768px){.VPNavBarAskAiButton[data-v-4eb17e89]{height:auto;padding:11.5px;transition:color .3s ease;background-color:var(--vp-c-bg-alt);border-radius:8px;font-size:15px;color:var(--vp-c-text-2)}.VPNavBarAskAiButton[data-v-4eb17e89]:hover{color:var(--vp-c-brand-1)}}.VPNavBarSearchButton[data-v-baa3be99]{display:flex;align-items:center;gap:8px;height:var(--vp-nav-height);padding:8px 14px;font-size:20px}.text[data-v-baa3be99],.keys[data-v-baa3be99],:root.mac .key-ctrl[data-v-baa3be99],:root:not(.mac) .key-cmd[data-v-baa3be99]{display:none}kbd[data-v-baa3be99]{font-family:inherit;font-weight:500}@media(min-width:768px){.VPNavBarSearchButton[data-v-baa3be99]{height:auto;padding:8px 12px;background-color:var(--vp-c-bg-alt);border-radius:8px;font-size:14px;line-height:1;color:var(--vp-c-text-2)}.text[data-v-baa3be99]{display:inline;font-size:13px}.keys[data-v-baa3be99]{display:flex;align-items:center;gap:4px;padding:4px 6px;border:1px solid var(--vp-c-divider);border-radius:4px;font-size:12px}}.VPNavBarSearch[data-v-2fc7f2c6]{display:flex;align-items:center}@media(min-width:768px){.VPNavBarSearch[data-v-2fc7f2c6]{gap:8px;flex-grow:1;padding-left:24px}}@media(min-width:960px){.VPNavBarSearch[data-v-2fc7f2c6]{padding-left:32px}}.VPNavBarSocialLinks[data-v-0394ad82]{display:none}@media(min-width:1280px){.VPNavBarSocialLinks[data-v-0394ad82]{display:flex;align-items:center}}.title[data-v-1e38c6bc]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media(min-width:960px){.title[data-v-1e38c6bc]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-1e38c6bc]{border-bottom-color:var(--vp-c-divider)}}[data-v-1e38c6bc] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-fafa8c23]{display:none}@media(min-width:1280px){.VPNavBarTranslations[data-v-fafa8c23]{display:flex;align-items:center}}.title[data-v-fafa8c23]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-9ca1369d]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .25s}.VPNavBar.screen-open[data-v-9ca1369d]{transition:none;background-color:var(--vp-nav-bg-color);border-bottom:1px solid var(--vp-c-divider)}.VPNavBar[data-v-9ca1369d]:not(.home){background-color:var(--vp-nav-bg-color)}@media(min-width:960px){.VPNavBar[data-v-9ca1369d]:not(.home){background-color:transparent}.VPNavBar[data-v-9ca1369d]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-9ca1369d]{padding:0 8px 0 24px}@media(min-width:768px){.wrapper[data-v-9ca1369d]{padding:0 32px}}@media(min-width:960px){.VPNavBar.has-sidebar .wrapper[data-v-9ca1369d]{padding:0}}.container[data-v-9ca1369d]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-9ca1369d],.container>.content[data-v-9ca1369d]{pointer-events:none}.container[data-v-9ca1369d] *{pointer-events:auto}@media(min-width:960px){.VPNavBar.has-sidebar .container[data-v-9ca1369d]{max-width:100%}}.title[data-v-9ca1369d]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media(min-width:960px){.VPNavBar.has-sidebar .title[data-v-9ca1369d]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media(min-width:1440px){.VPNavBar.has-sidebar .title[data-v-9ca1369d]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-9ca1369d]{flex-grow:1}@media(min-width:960px){.VPNavBar.has-sidebar .content[data-v-9ca1369d]{position:relative;z-index:1;padding-left:var(--vp-sidebar-width);padding-right:32px}}@media(min-width:1440px){.VPNavBar.has-sidebar .content[data-v-9ca1369d]{padding-left:calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));padding-right:calc((100% - var(--vp-layout-max-width)) / 2 + 32px)}}.content-body[data-v-9ca1369d]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media(min-width:960px){.VPNavBar:not(.home.top) .content-body[data-v-9ca1369d]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-9ca1369d]{background-color:transparent}.content-body[data-v-9ca1369d]{margin-right:-100vw;padding-right:100vw}}.menu+.translations[data-v-9ca1369d]:before,.menu+.appearance[data-v-9ca1369d]:before,.menu+.social-links[data-v-9ca1369d]:before,.translations+.appearance[data-v-9ca1369d]:before,.appearance+.social-links[data-v-9ca1369d]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-9ca1369d]:before,.translations+.appearance[data-v-9ca1369d]:before{margin-right:16px}.appearance+.social-links[data-v-9ca1369d]:before{margin-left:16px}.social-links[data-v-9ca1369d]{margin-right:-8px}.divider[data-v-9ca1369d]{width:100%;height:1px}@media(min-width:960px){.VPNavBar.has-sidebar .divider[data-v-9ca1369d]{padding-left:var(--vp-sidebar-width)}}@media(min-width:1440px){.VPNavBar.has-sidebar .divider[data-v-9ca1369d]{padding-left:calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-9ca1369d]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-9ca1369d]{background-color:var(--vp-c-gutter)}@media(min-width:960px){.VPNavBar:not(.home.top) .divider-line[data-v-9ca1369d]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-9ca1369d]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-b44890b2]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-b44890b2]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-b924ab8a]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-b924ab8a]:hover,.VPNavScreenMenuLink.active[data-v-b924ab8a]{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-ecf4b472]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-ecf4b472]:hover,.VPNavScreenMenuGroupLink.active[data-v-ecf4b472]{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-4b7a798b]{display:block}.title[data-v-4b7a798b]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-956364f9]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-956364f9]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-956364f9]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-956364f9]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-956364f9]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-956364f9]{transform:rotate(45deg)}.button[data-v-956364f9]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-956364f9]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-956364f9]{transition:transform .25s}.group[data-v-956364f9]:first-child{padding-top:0}.group+.group[data-v-956364f9],.group+.item[data-v-956364f9]{padding-top:4px}.VPNavScreenTranslations[data-v-a4d9b172]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-a4d9b172]{height:auto}.title[data-v-a4d9b172]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-a4d9b172]{font-size:16px}.icon.lang[data-v-a4d9b172]{margin-right:8px}.icon.chevron[data-v-a4d9b172]{margin-left:4px}.list[data-v-a4d9b172]{padding:4px 0 0 24px}.link[data-v-a4d9b172]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-05f3d7bc]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px));right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .25s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-05f3d7bc],.VPNavScreen.fade-leave-active[data-v-05f3d7bc]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-05f3d7bc],.VPNavScreen.fade-leave-active .container[data-v-05f3d7bc]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-05f3d7bc],.VPNavScreen.fade-leave-to[data-v-05f3d7bc]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-05f3d7bc],.VPNavScreen.fade-leave-to .container[data-v-05f3d7bc]{transform:translateY(-8px)}@media(min-width:768px){.VPNavScreen[data-v-05f3d7bc]{display:none}}.container[data-v-05f3d7bc]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-05f3d7bc],.menu+.appearance[data-v-05f3d7bc],.translations+.appearance[data-v-05f3d7bc]{margin-top:24px}.menu+.social-links[data-v-05f3d7bc]{margin-top:16px}.appearance+.social-links[data-v-05f3d7bc]{margin-top:16px}.VPNav[data-v-9f75dce3]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media(min-width:960px){.VPNav[data-v-9f75dce3]{position:fixed}}.VPSidebarItem.level-0[data-v-d81de50c]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-d81de50c]{padding-bottom:10px}.item[data-v-d81de50c]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-d81de50c]{cursor:pointer}.indicator[data-v-d81de50c]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-d81de50c],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-d81de50c],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-d81de50c],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-d81de50c]{background-color:var(--vp-c-brand-1)}.link[data-v-d81de50c]{display:flex;align-items:center;flex-grow:1}.text[data-v-d81de50c]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-d81de50c]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-d81de50c],.VPSidebarItem.level-2 .text[data-v-d81de50c],.VPSidebarItem.level-3 .text[data-v-d81de50c],.VPSidebarItem.level-4 .text[data-v-d81de50c],.VPSidebarItem.level-5 .text[data-v-d81de50c]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-d81de50c],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-d81de50c],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-d81de50c],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-d81de50c],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-d81de50c],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-d81de50c]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-1.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-2.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-3.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-4.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-5.has-active>.item>.text[data-v-d81de50c],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-d81de50c],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-d81de50c],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-d81de50c],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-d81de50c],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-d81de50c],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-d81de50c]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-d81de50c],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-d81de50c],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-d81de50c],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-d81de50c],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-d81de50c],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-d81de50c]{color:var(--vp-c-brand-1)}.caret[data-v-d81de50c]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-d81de50c]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-d81de50c]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-d81de50c]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-d81de50c]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-d81de50c],.VPSidebarItem.level-2 .items[data-v-d81de50c],.VPSidebarItem.level-3 .items[data-v-d81de50c],.VPSidebarItem.level-4 .items[data-v-d81de50c],.VPSidebarItem.level-5 .items[data-v-d81de50c]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-d81de50c]{display:none}.no-transition[data-v-8d50c081] .caret-icon{transition:none}.group+.group[data-v-8d50c081]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media(min-width:960px){.group[data-v-8d50c081]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSidebar[data-v-af661f50]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-af661f50]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-af661f50]{box-shadow:var(--vp-shadow-1)}@media(min-width:960px){.VPSidebar[data-v-af661f50]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media(min-width:1440px){.VPSidebar[data-v-af661f50]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media(min-width:960px){.curtain[data-v-af661f50]{position:sticky;top:calc(var(--vp-nav-height) * -1);left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-af661f50]{outline:0}.VPSkipLink[data-v-331ec75c]{position:fixed;top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-331ec75c]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media(min-width:1280px){.VPSkipLink[data-v-331ec75c]{top:14px;left:16px}}.Layout[data-v-0cf61682]{display:flex;flex-direction:column;min-height:100vh}:root{color-scheme:dark;--gt-black: #050608;--gt-ink: #0d1117;--gt-panel: #11161f;--gt-panel-soft: #151b23;--gt-line: #30363d;--gt-line-strong: #3d444d;--gt-text: #f0f6fc;--gt-muted: #9198a1;--gt-dim: #6e7681;--gt-blue: #031cdc;--gt-blue-soft: #4493f8;--gt-green: #7ee787;--gt-green-soft: #3fb950;--gt-amber: #f2cc60;--gt-red: #f85149;--vp-c-bg: var(--gt-black);--vp-c-bg-alt: var(--gt-ink);--vp-c-bg-elv: var(--gt-panel);--vp-c-bg-soft: var(--gt-panel-soft);--vp-c-divider: rgba(61, 68, 77, .82);--vp-c-divider-light: rgba(61, 68, 77, .48);--vp-c-text-1: var(--gt-text);--vp-c-text-2: #c9d1d9;--vp-c-text-3: var(--gt-muted);--vp-c-brand-1: var(--gt-green);--vp-c-brand-2: var(--gt-green-soft);--vp-c-brand-3: #214f32;--vp-c-brand-soft: rgba(126, 231, 135, .12);--vp-c-gutter: rgba(61, 68, 77, .8);--vp-button-brand-bg: var(--gt-text);--vp-button-brand-border: var(--gt-text);--vp-button-brand-text: var(--gt-black);--vp-button-brand-hover-bg: var(--gt-green);--vp-button-brand-hover-border: var(--gt-green);--vp-button-brand-hover-text: var(--gt-black);--vp-button-alt-bg: transparent;--vp-button-alt-border: var(--gt-line-strong);--vp-button-alt-text: var(--gt-text);--vp-button-alt-hover-bg: var(--gt-panel-soft);--vp-button-alt-hover-border: var(--gt-green);--vp-button-alt-hover-text: var(--gt-green);--vp-code-color: var(--gt-green);--vp-code-bg: rgba(101, 108, 118, .2);--vp-font-family-base: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--vp-font-family-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--vp-home-hero-name-color: var(--gt-text);--vp-home-hero-name-background: none}*{box-sizing:border-box}html,body{background:var(--gt-black)}body{font-family:var(--vp-font-family-base);letter-spacing:0;overflow-x:hidden}::selection{background:var(--gt-blue);color:#fff}.VPNav{border-bottom:1px solid rgba(61,68,77,.72);background:#050608eb;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}.VPNavBar.has-sidebar .content{background:transparent}.VPNavBarTitle .title{font-weight:700;letter-spacing:0}.VPNavBarTitle .logo{border-radius:0}.VPNavBarMenuLink{color:var(--gt-muted);font-family:var(--vp-font-family-mono);font-size:12px;text-transform:uppercase}.VPNavBarMenuLink:hover,.VPNavBarMenuLink.active{color:var(--gt-text)}.VPHome{background:linear-gradient(180deg,rgba(3,28,220,.14),transparent 260px),radial-gradient(circle at 72% 20%,rgba(126,231,135,.1),transparent 330px),var(--gt-black)}.VPHome .main{max-width:1180px}.VPHomeHero{padding-top:92px!important;padding-bottom:44px!important}.VPHomeHero .container{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,430px);gap:48px;max-width:1180px}.VPHomeHero .main{display:flex;flex-direction:column;align-items:flex-start;text-align:left}.VPHomeHero .name,.VPHomeHero .text{max-width:780px;letter-spacing:0}.VPHomeHero .name{font-size:clamp(56px,8vw,112px);line-height:.9;font-weight:800}.VPHomeHero .text{margin-top:20px;color:#d7dee8;font-size:clamp(24px,3.4vw,44px);line-height:1;font-weight:600}.VPHomeHero .tagline{max-width:660px;color:var(--gt-muted);font-size:17px;line-height:1.7}.VPHomeHero .actions{justify-content:flex-start}.VPButton{border-radius:4px!important;font-family:var(--vp-font-family-mono);font-size:12px!important;font-weight:700!important;letter-spacing:0;text-transform:uppercase}.VPHomeHero .image{display:block}.VPHomeHero .image-container{display:flex;align-items:center;justify-content:center;width:420px;height:420px;transform:none!important}.VPHomeHero .image-bg{display:none}.VPHomeHero .image-src{position:static!important;width:min(100%,420px);max-width:420px!important;height:auto;border:1px solid var(--gt-line-strong);border-radius:0;background:linear-gradient(180deg,var(--gt-panel),var(--gt-black));box-shadow:0 0 0 1px #7ee78714,0 24px 80px #00000061;transform:none!important}.VPFeatures{padding:28px 24px 72px!important}.VPFeatures .container{max-width:1180px}.VPFeatures .items{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px!important;border:1px solid var(--gt-line);background:var(--gt-line)}.VPFeatures .item{width:auto!important;padding:0!important}.VPFeature{height:100%;min-height:168px;border:0!important;border-radius:0!important;background:var(--gt-ink)!important;padding:22px!important}.VPFeature .title{color:var(--gt-text);font-family:var(--vp-font-family-mono);font-size:13px;line-height:1.35;text-transform:uppercase}.VPFeature .details{color:var(--gt-muted);font-size:14px;line-height:1.65}.vp-doc{color:#d7dee8}.VPDoc,.VPDoc .container,.VPDoc .content,.VPDoc .content-container,.VPDoc .main{min-width:0;max-width:100%}.vp-doc h1,.vp-doc h2,.vp-doc h3{color:var(--gt-text);letter-spacing:0}.vp-doc h1{font-size:40px;line-height:1.05}.vp-doc h2{margin-top:44px;border-top:1px solid var(--vp-c-divider);border-bottom:0;padding-top:22px;font-family:var(--vp-font-family-mono);font-size:18px;text-transform:uppercase}.vp-doc h3{font-size:18px}.vp-doc p,.vp-doc li{color:#c9d1d9;line-height:1.72}.vp-doc a{color:var(--gt-green);text-decoration:underline;text-underline-offset:3px}.vp-doc blockquote{border-left-color:var(--gt-green);background:#7ee7870f}.vp-doc div[class*=language-],.vp-doc pre{max-width:100%;overflow-x:auto;border:1px solid var(--gt-line);border-radius:0;background:#0d1117!important}.vp-doc code{border-radius:4px}.vp-doc table{display:table;width:100%;border:1px solid var(--gt-line)}.vp-doc th,.vp-doc td{border-color:var(--gt-line)!important}.vp-doc tr{background:var(--gt-ink)}.vp-doc tr:nth-child(2n){background:var(--gt-panel-soft)}.VPSidebar{background:var(--gt-ink)!important;border-right:1px solid var(--gt-line)}.VPSidebarItem .text{color:var(--gt-muted)}.VPSidebarItem.is-active>.item .text{color:var(--gt-green)}.VPDocAsideOutline .content{border-left-color:var(--gt-line)}.VPFooter{border-top:1px solid var(--gt-line);background:var(--gt-black)}.gtn-home{max-width:1180px;margin:0 auto 72px;padding:0 24px}.gtn-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border:1px solid var(--gt-line);background:var(--gt-line)}.gtn-strip__item{min-height:112px;padding:20px;background:var(--gt-ink)}.gtn-kicker{color:var(--gt-green);font-family:var(--vp-font-family-mono);font-size:12px;font-weight:700;text-transform:uppercase}.gtn-strip__item strong{display:block;margin-top:12px;color:var(--gt-text);font-size:22px;line-height:1.1}.gtn-strip__item span{display:block;margin-top:8px;color:var(--gt-muted);font-size:13px;line-height:1.55}.gtn-panel{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,420px);gap:1px;margin-top:28px;border:1px solid var(--gt-line);background:var(--gt-line)}.gtn-panel>section{background:var(--gt-ink);padding:28px}.gtn-panel h2{margin:0;color:var(--gt-text);font-size:clamp(30px,5vw,56px);line-height:.98}.gtn-panel p{max-width:640px;color:var(--gt-muted);font-size:16px;line-height:1.72}.gtn-terminal{font-family:var(--vp-font-family-mono);font-size:12px}.gtn-terminal div{display:flex;gap:12px;padding:10px 0;border-bottom:1px solid rgba(61,68,77,.58)}.gtn-terminal div:last-child{border-bottom:0}.gtn-terminal b{min-width:78px;color:var(--gt-green);font-weight:600}.gtn-terminal span{color:#c9d1d9}@media(max-width:960px){.VPHomeHero .container,.gtn-panel{grid-template-columns:1fr}.VPHomeHero .image{order:-1}.VPHomeHero .image-src{width:220px}.gtn-strip,.VPFeatures .items{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:640px){.VPDoc{padding:0 24px!important}.VPDoc .container,.VPDoc .content,.VPDoc .content-container,.VPDoc .main,.vp-doc{display:block!important;width:100%!important;max-width:100%!important;padding-left:0!important;padding-right:0!important}.VPDocAside{display:none!important}.vp-doc p,.vp-doc li{overflow-wrap:anywhere}.VPHomeHero{padding-top:64px!important}.VPHomeHero .name{font-size:54px}.VPHomeHero .text{font-size:26px}.gtn-strip,.VPFeatures .items{grid-template-columns:1fr}.gtn-panel>section{padding:22px}}.VPLocalSearchBox[data-v-8c9d8b44]{position:fixed;z-index:100;inset:0;display:flex}.backdrop[data-v-8c9d8b44]{position:absolute;inset:0;background:var(--vp-backdrop-bg-color);transition:opacity .5s}.shell[data-v-8c9d8b44]{position:relative;padding:12px;margin:64px auto;display:flex;flex-direction:column;gap:16px;background:var(--vp-local-search-bg);width:min(100vw - 60px,900px);height:min-content;max-height:min(100vh - 128px,900px);border-radius:6px}@media(max-width:767px){.shell[data-v-8c9d8b44]{margin:0;width:100vw;height:100vh;max-height:none;border-radius:0}}.search-bar[data-v-8c9d8b44]{border:1px solid var(--vp-c-divider);border-radius:4px;display:flex;align-items:center;padding:0 12px;cursor:text}@media(max-width:767px){.search-bar[data-v-8c9d8b44]{padding:0 8px}}.search-bar[data-v-8c9d8b44]:focus-within{border-color:var(--vp-c-brand-1)}.local-search-icon[data-v-8c9d8b44]{display:block;font-size:18px}.navigate-icon[data-v-8c9d8b44]{display:block;font-size:14px}.search-icon[data-v-8c9d8b44]{margin:8px}@media(max-width:767px){.search-icon[data-v-8c9d8b44]{display:none}}.search-input[data-v-8c9d8b44]{padding:6px 12px;font-size:inherit;width:100%}.search-input[data-v-8c9d8b44]::-webkit-search-cancel-button{display:none}@media(max-width:767px){.search-input[data-v-8c9d8b44]{padding:6px 4px}}.search-actions[data-v-8c9d8b44]{display:flex;gap:4px}@media(any-pointer:coarse){.search-actions[data-v-8c9d8b44]{gap:8px}}@media(min-width:769px){.search-actions.before[data-v-8c9d8b44]{display:none}}.search-actions button[data-v-8c9d8b44]{padding:8px}.search-actions button[data-v-8c9d8b44]:not([disabled]):hover,.toggle-layout-button.detailed-list[data-v-8c9d8b44]{color:var(--vp-c-brand-1)}.search-actions button.clear-button[data-v-8c9d8b44]:disabled{opacity:.37}.search-keyboard-shortcuts[data-v-8c9d8b44]{font-size:.8rem;opacity:75%;display:flex;flex-wrap:wrap;gap:16px;line-height:14px}.search-keyboard-shortcuts span[data-v-8c9d8b44]{display:flex;align-items:center;gap:4px}@media(max-width:767px){.search-keyboard-shortcuts[data-v-8c9d8b44]{display:none}}.search-keyboard-shortcuts kbd[data-v-8c9d8b44]{background:#8080801a;border-radius:4px;padding:3px 6px;min-width:24px;display:inline-block;text-align:center;vertical-align:middle;border:1px solid rgba(128,128,128,.15);box-shadow:0 2px 2px #0000001a}.results[data-v-8c9d8b44]{display:flex;flex-direction:column;gap:6px;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.result[data-v-8c9d8b44]{display:flex;align-items:center;gap:8px;border-radius:4px;transition:none;line-height:1rem;border:solid 2px var(--vp-local-search-result-border);outline:none}.result>div[data-v-8c9d8b44]{margin:12px;width:100%;overflow:hidden}@media(max-width:767px){.result>div[data-v-8c9d8b44]{margin:8px}}.titles[data-v-8c9d8b44]{display:flex;flex-wrap:wrap;gap:4px;position:relative;z-index:1001;padding:2px 0}.title[data-v-8c9d8b44]{display:flex;align-items:center;gap:4px}.title.main[data-v-8c9d8b44]{font-weight:500}.title-icon[data-v-8c9d8b44]{opacity:.5;font-weight:500;color:var(--vp-c-brand-1)}.title svg[data-v-8c9d8b44]{opacity:.5}.result.selected[data-v-8c9d8b44]{--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);border-color:var(--vp-local-search-result-selected-border)}.excerpt-wrapper[data-v-8c9d8b44]{position:relative}.excerpt[data-v-8c9d8b44]{opacity:50%;pointer-events:none;max-height:140px;overflow:hidden;position:relative;margin-top:4px}.result.selected .excerpt[data-v-8c9d8b44]{opacity:1}.excerpt[data-v-8c9d8b44] *{font-size:.8rem!important;line-height:130%!important}.titles[data-v-8c9d8b44] mark,.excerpt[data-v-8c9d8b44] mark{background-color:var(--vp-local-search-highlight-bg);color:var(--vp-local-search-highlight-text);border-radius:2px;padding:0 2px}.excerpt[data-v-8c9d8b44] .vp-code-group .tabs{display:none}.excerpt[data-v-8c9d8b44] .vp-code-group div[class*=language-]{border-radius:8px!important}.excerpt-gradient-bottom[data-v-8c9d8b44]{position:absolute;bottom:-1px;left:0;width:100%;height:8px;background:linear-gradient(transparent,var(--vp-local-search-result-bg));z-index:1000}.excerpt-gradient-top[data-v-8c9d8b44]{position:absolute;top:-1px;left:0;width:100%;height:8px;background:linear-gradient(var(--vp-local-search-result-bg),transparent);z-index:1000}.result.selected .titles[data-v-8c9d8b44],.result.selected .title-icon[data-v-8c9d8b44]{color:var(--vp-c-brand-1)!important}.no-results[data-v-8c9d8b44]{font-size:.9rem;text-align:center;padding:12px}svg[data-v-8c9d8b44]{flex:none} diff --git a/docs/assets/troubleshooting.md.BAr9iojM.js b/docs/assets/troubleshooting.md.BAr9iojM.js deleted file mode 100644 index a5bdbd8e65..0000000000 --- a/docs/assets/troubleshooting.md.BAr9iojM.js +++ /dev/null @@ -1,4 +0,0 @@ -import{c as s,S as e,j as a,m as t}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Troubleshooting","description":"","frontmatter":{},"headers":[],"relativePath":"troubleshooting.md","filePath":"troubleshooting.md","lastUpdated":1779781690000}'),n={name:"troubleshooting.md"};function l(h,i,r,o,p,d){return e(),a("div",null,[...i[0]||(i[0]=[t(`

Troubleshooting

gittensory-mcp: command not found

If the command is not installed globally:

sh
npm link --workspace @jsonbored/gittensory-mcp

Then retry:

sh
gittensory-mcp doctor

If your MCP client does not inherit your shell PATH, use an absolute command path in that client config.

Login Fails

Check:

sh
gittensory-mcp doctor
-gittensory-mcp status

GitHub Device Flow must be enabled on the GitHub App or OAuth app configured for Gittensory.

Session Expired

Run:

sh
gittensory-mcp login

Sessions are intentionally short-lived.

Source Upload Error

If you see a source-upload error, remove this env var:

sh
unset GITTENSORY_UPLOAD_SOURCE

Gittensory rejects source upload mode in v1.

GitHub App Installation Needs Attention

Check the installation health endpoint:

sh
export GITTENSORY_API_URL="https://your-gittensory-api-origin.example"
-curl "$GITTENSORY_API_URL/v1/installations/INSTALLATION_ID/health" \\
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Fix the reported missing permissions and events, approve the app permission update in GitHub, then refresh installation health.

Rate Limited

If a command returns 429, retry after the reported retry-after value. Expensive analysis routes have stricter limits than normal read routes.

Stale Decision Pack

If decision-pack returns needs_snapshot_refresh, Gittensory has enqueued a rebuild. Retry after the queue drains.

`,27)])])}const g=s(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/assets/troubleshooting.md.BAr9iojM.lean.js b/docs/assets/troubleshooting.md.BAr9iojM.lean.js deleted file mode 100644 index 2b16fc20e1..0000000000 --- a/docs/assets/troubleshooting.md.BAr9iojM.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{c as s,S as e,j as a,m as t}from"./chunks/framework.BXgR76oC.js";const c=JSON.parse('{"title":"Troubleshooting","description":"","frontmatter":{},"headers":[],"relativePath":"troubleshooting.md","filePath":"troubleshooting.md","lastUpdated":1779781690000}'),n={name:"troubleshooting.md"};function l(h,i,r,o,p,d){return e(),a("div",null,[...i[0]||(i[0]=[t("",27)])])}const g=s(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/docs/guide/auth.html b/docs/guide/auth.html deleted file mode 100644 index e068d56260..0000000000 --- a/docs/guide/auth.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - Auth | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

Auth

Gittensory has two auth surfaces:

  • GitHub OAuth for MCP users.
  • GitHub App installation auth for repositories.

MCP User Auth

Run:

sh
gittensory-mcp login

The CLI starts GitHub Device Flow, asks you to approve the code on GitHub, then exchanges the GitHub token server-side for a short-lived Gittensory session token.

Gittensory stores:

  • a hashed server-side session token
  • the GitHub login and user id
  • scopes and session expiry metadata

Gittensory does not store user PATs.

Non-Interactive Bootstrap

For local automation, pass a GitHub token only long enough to mint a Gittensory session:

sh
gittensory-mcp login --github-token "$(gh auth token)"

The token is exchanged immediately. The MCP wrapper stores the Gittensory session token, not the GitHub token.

Check Auth State

sh
gittensory-mcp whoami
-gittensory-mcp status
-gittensory-mcp doctor

Logout

sh
gittensory-mcp logout

Logout revokes the remote session when possible and removes the local config file.

Static Tokens

Static bearer tokens remain only for private backend operations, internal jobs, and temporary beta bootstrap. Normal MCP users should use GitHub OAuth.

- - - - \ No newline at end of file diff --git a/docs/guide/github-app-setup.html b/docs/guide/github-app-setup.html deleted file mode 100644 index 5f040fc028..0000000000 --- a/docs/guide/github-app-setup.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - GitHub App Setup | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

GitHub App Setup

The GitHub App is the maintainer/install surface. GitHub OAuth is the MCP user-auth surface.

Basic Fields

Use these values:

FieldValue
Homepage URLhttps://gittensory.aethereal.dev
Webhook URL${GITTENSORY_API_URL}/v1/github/webhook
Webhook activeenabled
SSL verificationenabled
Device Flowenabled

GITTENSORY_API_URL is the private API origin for the deployed backend. Do not use the GitHub Pages docs domain for webhooks; Pages only serves static docs.

Use a generated webhook secret and set the same value in Cloudflare as GITHUB_WEBHOOK_SECRET.

Required Repository Permissions

PermissionAccessWhy
MetadataReadRequired for repository identity and repository events.
ChecksWriteRequired to create and update the Gittensory check run.
Pull requestsReadRequired for PR metadata, reviewability, and webhook events.
IssuesReadRequired for issue linkage, issue-discovery context, and duplicate signals.

Optional:

PermissionAccessWhy
IssuesWriteOnly needed if public-safe sticky PR comments are enabled.
ContentsReadOnly needed if a future feature reads repository files directly through the App.

Required Events

Subscribe to:

  • Pull request
  • Issues
  • Repository

If GitHub shows Installation target, select it. Some installation-related events are not always shown as normal selectable event rows; Gittensory should not block health on event names that are hidden in the app UI.

Install Or Repair

  1. Update the GitHub App permissions and events.
  2. Reinstall the app or approve the changed permissions.
  3. Select the repos Gittensory should inspect.
  4. Trigger installation-health refresh:
sh
curl -X POST "$GITTENSORY_API_URL/v1/internal/jobs/refresh-installation-health/run" \
-  -H "Authorization: Bearer $INTERNAL_JOB_TOKEN"
  1. Check health:
sh
curl "$GITTENSORY_API_URL/v1/readiness" \
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Healthy app installation state should remove the readiness warning about GitHub App installations needing attention.

Marketplace Readiness

Before Marketplace submission, add:

  • public docs URL
  • support contact
  • privacy policy
  • terms page if needed
  • clear setup flow
  • valid webhook and install diagnostics

Do not submit until the privacy, support, terms, install diagnostics, and public setup flow are complete.

- - - - \ No newline at end of file diff --git a/docs/guide/install.html b/docs/guide/install.html deleted file mode 100644 index d4226d213c..0000000000 --- a/docs/guide/install.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - Install | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

Install

Gittensory has two install paths: public npm for normal use, and local checkout for development.

Public npm

Use this for Codex, Claude Desktop, Cursor, or any other stdio MCP client:

sh
npm install -g @jsonbored/gittensory-mcp
-gittensory-mcp login
-gittensory-mcp status
-gittensory-mcp --stdio

The login command uses GitHub Device Flow and stores a short-lived Gittensory session token in your local config directory.

Local checkout

Use this when developing Gittensory itself:

sh
git clone https://github.com/JSONbored/gittensory.git
-cd gittensory
-npm install
-npm link --workspace @jsonbored/gittensory-mcp
-gittensory-mcp login
-gittensory-mcp --stdio

Verify The Install

Run:

sh
gittensory-mcp doctor
-gittensory-mcp whoami
-gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN --json

doctor checks API health, auth state, source-upload defaults, local git metadata, and whether the binary is likely visible to MCP clients.

Privacy Defaults

Gittensory MCP v1 sends structured metadata only:

  • repository full name
  • branch and base refs
  • changed file paths and counts
  • linked issue references
  • commit messages
  • validation command summaries

It does not upload source contents. GITTENSORY_UPLOAD_SOURCE=true is rejected.

- - - - \ No newline at end of file diff --git a/docs/guide/maintainers.html b/docs/guide/maintainers.html deleted file mode 100644 index 4746e21ba0..0000000000 --- a/docs/guide/maintainers.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - For Maintainers | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

For Maintainers

Gittensory is meant to make Gittensor-driven contribution flow less noisy.

GitHub App Surface

The GitHub App creates the detailed maintainer surface:

  • Gittensory check runs on PRs
  • reviewability context
  • linked issue and duplicate signals
  • contributor role context
  • queue and repo-lane context
  • optional public-safe sticky PR comments

The check run is the canonical detailed report. Public comments are opt-in and sanitized.

Reviewability Actions

Gittensory maps PRs to maintainer-friendly actions:

  • review_now
  • needs_author
  • likely_duplicate
  • close_or_redirect
  • watch
  • maintainer_lane

The point is not to shame contributors. The point is to identify the lowest-friction next step.

Public Comments

Public comments stay off by default.

When enabled, comments can include:

  • contribution context
  • PR hygiene
  • duplicate or WIP risk
  • maintainer review notes
  • contributor next steps

Comments must not include raw trust scores, wallet data, hotkeys, public reward estimates, or public score optimization language.

Repo Owner Signals

Repo owners can use Gittensory to inspect:

  • repo lane clarity
  • label configuration
  • maintainer cut readiness
  • queue health
  • contributor intake health
  • GitHub App installation health
  • stale or degraded backfill state
- - - - \ No newline at end of file diff --git a/docs/guide/mcp.html b/docs/guide/mcp.html deleted file mode 100644 index 0e21a3f634..0000000000 --- a/docs/guide/mcp.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - MCP | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

MCP

The MCP package is the contributor-facing surface for coding agents. It runs locally over stdio and calls the private Gittensory API with your Gittensory session token.

Generate Client Config

Print a config snippet:

sh
gittensory-mcp init-client --print codex
-gittensory-mcp init-client --print claude
-gittensory-mcp init-client --print cursor

These commands do not edit your files. Use an absolute command path if your client does not inherit your shell PATH.

Codex

toml
[mcp_servers.gittensory]
-command = "gittensory-mcp"
-args = ["--stdio"]

Claude Desktop

json
{
-  "mcpServers": {
-    "gittensory": {
-      "command": "gittensory-mcp",
-      "args": ["--stdio"]
-    }
-  }
-}

Cursor

json
{
-  "mcpServers": {
-    "gittensory": {
-      "command": "gittensory-mcp",
-      "args": ["--stdio"]
-    }
-  }
-}

Tools

The local wrapper exposes repo context, contributor decision packs, local branch preflight, score blockers, PR packets, variant comparison, and registry change tools.

Useful tools:

  • gittensory_local_status
  • gittensory_get_decision_pack
  • gittensory_explain_repo_decision
  • gittensory_preflight_current_branch
  • gittensory_preview_current_branch_score
  • gittensory_rank_local_next_actions
  • gittensory_explain_local_blockers
  • gittensory_prepare_pr_packet

Runtime Rules

  • Stdio only for the local wrapper.
  • No source upload in v1.
  • Private score and reward/risk reasoning stay in MCP/API output.
  • Public PR packets are sanitized and do not include wallet, hotkey, raw trust score, or public score estimates.
- - - - \ No newline at end of file diff --git a/docs/guide/miners.html b/docs/guide/miners.html deleted file mode 100644 index 0ce65f7432..0000000000 --- a/docs/guide/miners.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - For Miners | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

For Miners

Gittensory helps miners decide what to do next with evidence instead of guesswork.

What It Answers

  • Is this repo a direct-PR lane, issue-discovery lane, split lane, inactive lane, or unknown lane?
  • Am I in a normal contributor lane or a maintainer lane for this repo?
  • Does my current branch look reviewable?
  • What blocks scoreability right now?
  • Should I clean up open PRs before opening more work?
  • Is there duplicate or WIP collision risk?
  • What public-safe PR packet should I give a maintainer?

Branch Analysis

Run from a Git repo:

sh
gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN --json

The response includes:

  • lane context
  • role context
  • preflight findings
  • private score blockers
  • current vs projected scoreability scenarios
  • reward/risk reasoning
  • base freshness warnings when the local diff may be inflated
  • maintainer-fit notes
  • public-safe PR packet
  • ranked next actions

When the current score is blocked by temporary account/queue state, pass the assumptions explicitly:

sh
gittensory-mcp analyze-branch --login YOUR_GITHUB_LOGIN \
-  --pending-merged-prs 3 \
-  --expected-open-prs 0 \
-  --projected-credibility 0.8 \
-  --scenario-note "approved PRs expected to merge" \
-  --json

Gittensory labels that as a user-supplied scenario. It shows the current effective score, the underlying potential score, and what changes if the open-PR and credibility gates clear.

Preflight

sh
gittensory-mcp preflight --login YOUR_GITHUB_LOGIN --json

Use this before opening a PR. It is especially useful when you need to know whether a branch is missing tests, missing a linked issue, colliding with active work, or likely to increase maintainer burden.

How This Helps

Gittensory does not promise payouts. It explains scoreability and risk:

  • open PR pressure
  • credibility assumptions
  • lane eligibility
  • issue-discovery vs direct PR fit
  • duplicate clusters
  • stale work
  • review friction

That makes recommendations actionable: land or withdraw blocked work, avoid direct PRs in issue-discovery-only repos, improve validation evidence, or pick a repo where your history and the lane actually fit.

- - - - \ No newline at end of file diff --git a/docs/hashmap.json b/docs/hashmap.json deleted file mode 100644 index c3c4392a23..0000000000 --- a/docs/hashmap.json +++ /dev/null @@ -1 +0,0 @@ -{"guide_auth.md":"DNA7JhY7","guide_github-app-setup.md":"ZPvLDsvt","guide_install.md":"ByQAA8QU","guide_maintainers.md":"CGSokUpu","guide_mcp.md":"DK8zvHnS","guide_miners.md":"BeaGERAQ","index.md":"BKalGx3J","reference_api.md":"CvVunwm0","security_privacy.md":"CHSODDHk","troubleshooting.md":"BAr9iojM"} diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 03aabffbe6..0000000000 --- a/docs/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

GittensoryPrivate repo intelligence for Gittensor work.

For Gittensor miners, maintainers, and agents: MCP preflight, scoreability projections, and GitHub App review packets. Still not a Gittensor frontend.

Gittensory signal mark
01 / Miners
Pick work with less guesswork.Score blockers, lane fit, stale base checks, and cleanup-first guidance.
02 / Maintainers
Review signal, not noise.Role-aware context, duplicate risk, validation evidence, and next action.
03 / Agents
Local branch context.Metadata-only MCP tools for Codex, Claude, Cursor, and automation.
04 / Privacy
Private by default.No PAT storage, no source upload, no public score or wallet leakage.

Not another Gittensor frontend.

Gittensory is the private signal layer behind better contributions: official miner context, registry lanes, queue pressure, scoreability projections, maintainer friction, and public-safe PR packets.

lanedirect_pr / issue_discovery / maintainer
scorecurrent 0, potential visible, scenario gated
riskopen PR pressure, credibility, stale base
outputprivate MCP JSON + sanitized PR packet
- - - - \ No newline at end of file diff --git a/docs/logo.svg b/docs/logo.svg deleted file mode 100644 index af97c38454..0000000000 --- a/docs/logo.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/reference/api.html b/docs/reference/api.html deleted file mode 100644 index 279a206b82..0000000000 --- a/docs/reference/api.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - API Reference | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

API Reference

The REST API is bearer-token protected except for /health, GitHub webhook delivery, and public auth start/poll endpoints.

Use the live OpenAPI document when authenticated:

sh
export GITTENSORY_API_URL="https://your-gittensory-api-origin.example"
-curl "$GITTENSORY_API_URL/openapi.json" \
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Contributor APIs

EndpointPurpose
GET /v1/contributors/:login/profileContributor evidence profile using official Gittensor stats first when available.
GET /v1/contributors/:login/decision-packCanonical private miner decision payload for MCP and internal clients.
GET /v1/contributors/:login/repos/:owner/:repo/decisionRepo-specific decision extracted from the decision pack.

Repo APIs

EndpointPurpose
GET /v1/reposKnown registered and installed repositories.
GET /v1/repos/:owner/:repoRepository metadata.
GET /v1/repos/:owner/:repo/intelligenceCanonical repository intelligence bundle.
GET /v1/repos/:owner/:repo/pulls/:number/maintainer-packetPR-specific maintainer review packet.
GET /v1/repos/:owner/:repo/pulls/:number/reviewabilityPrivate PR reviewability score and maintainer action.

Local And Preflight APIs

EndpointPurpose
POST /v1/local/branch-analysisMCP-oriented local branch analysis from structured metadata.
POST /v1/preflight/prPlanned PR metadata preflight.
POST /v1/preflight/local-diffLocal-diff metadata preflight.

Ops APIs

EndpointPurpose
GET /v1/readinessService health, signal fidelity, secrets presence, and installation health summary.
GET /v1/sync/statusRepo sync segments, GitHub totals, rate-limit state, and signal fidelity.
GET /v1/installationsGitHub App installations and health records.
GET /v1/installations/:id/healthExact installation permission/event remediation context.

MCP Endpoint

POST /mcp exposes remote MCP over Streamable HTTP style requests. The local npm wrapper is still the preferred MCP user surface.

- - - - \ No newline at end of file diff --git a/docs/security/privacy.html b/docs/security/privacy.html deleted file mode 100644 index 375b86ff98..0000000000 --- a/docs/security/privacy.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Privacy And Security | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

Privacy And Security

Gittensory handles contribution intelligence, not wallets or private source code.

No PAT Storage

MCP login uses GitHub OAuth Device Flow. The backend exchanges the GitHub token for a Gittensory session token and stores only the hashed Gittensory token server-side.

No Source Upload

Local MCP branch analysis sends metadata only:

  • repo full name
  • branch name
  • base and head refs
  • changed file paths
  • additions and deletions
  • linked issue references
  • commit messages
  • validation summaries

Source contents are not uploaded in v1. GITTENSORY_UPLOAD_SOURCE=true fails closed.

Public Output Boundaries

Public comments and public-safe PR packets must not include:

  • wallets
  • hotkeys
  • raw trust scores
  • public score estimates
  • public reward estimates
  • farming language
  • public shaming

Private API and MCP responses can include scoreability and reward/risk reasoning because they are authenticated private outputs.

Rate Limiting

Gittensory uses route classes:

  • strict: auth routes
  • normal: read APIs and MCP tools
  • expensive: branch analysis, scoring preview, decision-pack refresh, signal refresh

Rate-limited responses return 429 with retry metadata.

GitHub App Boundaries

The GitHub App is advisory-only. It does not auto-label, close, merge, or rewrite contributor work.

- - - - \ No newline at end of file diff --git a/docs/troubleshooting.html b/docs/troubleshooting.html deleted file mode 100644 index 1f6cc5b5f2..0000000000 --- a/docs/troubleshooting.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - Troubleshooting | Gittensory - - - - - - - - - - - - - - - - - - - - -
Skip to content

Troubleshooting

gittensory-mcp: command not found

If the command is not installed globally:

sh
npm link --workspace @jsonbored/gittensory-mcp

Then retry:

sh
gittensory-mcp doctor

If your MCP client does not inherit your shell PATH, use an absolute command path in that client config.

Login Fails

Check:

sh
gittensory-mcp doctor
-gittensory-mcp status

GitHub Device Flow must be enabled on the GitHub App or OAuth app configured for Gittensory.

Session Expired

Run:

sh
gittensory-mcp login

Sessions are intentionally short-lived.

Source Upload Error

If you see a source-upload error, remove this env var:

sh
unset GITTENSORY_UPLOAD_SOURCE

Gittensory rejects source upload mode in v1.

GitHub App Installation Needs Attention

Check the installation health endpoint:

sh
export GITTENSORY_API_URL="https://your-gittensory-api-origin.example"
-curl "$GITTENSORY_API_URL/v1/installations/INSTALLATION_ID/health" \
-  -H "Authorization: Bearer $GITTENSORY_API_TOKEN"

Fix the reported missing permissions and events, approve the app permission update in GitHub, then refresh installation health.

Rate Limited

If a command returns 429, retry after the reported retry-after value. Expensive analysis routes have stricter limits than normal read routes.

Stale Decision Pack

If decision-pack returns needs_snapshot_refresh, Gittensory has enqueued a rebuild. Retry after the queue drains.

- - - - \ No newline at end of file diff --git a/docs/vp-icons.css b/docs/vp-icons.css deleted file mode 100644 index ddc5bd8edb..0000000000 --- a/docs/vp-icons.css +++ /dev/null @@ -1 +0,0 @@ -.vpi-social-github{--icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")} \ No newline at end of file diff --git a/migrations/0009_github_app_visibility.sql b/migrations/0009_github_app_visibility.sql new file mode 100644 index 0000000000..14802aacf1 --- /dev/null +++ b/migrations/0009_github_app_visibility.sql @@ -0,0 +1,18 @@ +ALTER TABLE repository_settings ADD COLUMN auto_label_enabled INTEGER NOT NULL DEFAULT 1; +ALTER TABLE repository_settings ADD COLUMN gittensor_label TEXT NOT NULL DEFAULT 'gittensor'; +ALTER TABLE repository_settings ADD COLUMN create_missing_label INTEGER NOT NULL DEFAULT 1; +ALTER TABLE repository_settings ADD COLUMN public_surface TEXT NOT NULL DEFAULT 'comment_and_label'; +ALTER TABLE repository_settings ADD COLUMN include_maintainer_authors INTEGER NOT NULL DEFAULT 0; +ALTER TABLE repository_settings ADD COLUMN require_linked_issue INTEGER NOT NULL DEFAULT 0; + +UPDATE repository_settings +SET + comment_mode = 'detected_contributors_only', + check_run_mode = 'off', + check_run_detail_level = 'minimal', + auto_label_enabled = 1, + gittensor_label = 'gittensor', + create_missing_label = 1, + public_surface = 'comment_and_label', + include_maintainer_authors = 0, + require_linked_issue = 0; diff --git a/package-lock.json b/package-lock.json index a64c3cc133..60a526d934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "drizzle-kit": "^0.31.7", "fast-check": "^4.8.0", "git-cliff": "^2.13.1", + "github-actionlint": "^1.7.12", "node-addon-api": "^8.5.0", "node-gyp": "^12.1.0", "typescript": "^5.9.3", @@ -3164,6 +3165,16 @@ "node": ">= 0.6" } }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/agents": { "version": "0.7.9", "resolved": "https://registry.npmjs.org/agents/-/agents-0.7.9.tgz", @@ -4570,6 +4581,23 @@ "win32" ] }, + "node_modules/github-actionlint": { + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/github-actionlint/-/github-actionlint-1.7.12.tgz", + "integrity": "sha512-kSVz5clt9voatp2Tdh1BETKNooT358/nPChYseaDC4M6f9Hc/sBRMbhagx2FTu2F/WHzDoxlNPh2EqwBngTYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "adm-zip": "^0.5.16", + "tar": "^7.4.0" + }, + "bin": { + "github-actionlint": "dist/bin/actionlint.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7566,7 +7594,7 @@ }, "packages/gittensory-mcp": { "name": "@jsonbored/gittensory-mcp", - "version": "0.1.3", + "version": "0.1.4", "license": "AGPL-3.0-only", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", diff --git a/package.json b/package.json index 5cf47e7510..06a10d51df 100644 --- a/package.json +++ b/package.json @@ -17,18 +17,22 @@ "drizzle:generate": "drizzle-kit generate", "build:mcp": "npm --workspace @jsonbored/gittensory-mcp run build", "test:mcp-pack": "node scripts/check-mcp-package.mjs", + "actionlint": "github-actionlint .github/workflows/*.yml", "docs:dev": "vitepress dev site --host 127.0.0.1", "docs:build": "vitepress build site", "docs:preview": "vitepress preview site --host 127.0.0.1", "docs:check": "node scripts/check-docs.mjs", - "changelog": "git-cliff --config cliff.toml --output CHANGELOG.md", + "changelog": "npm run changelog:root && npm run changelog:mcp", + "changelog:root": "git-cliff --config cliff.toml --output CHANGELOG.md", + "changelog:mcp": "git-cliff --config cliff.mcp.toml --include-path 'packages/gittensory-mcp/**' --include-path '.github/workflows/npm-publish.yml' --output packages/gittensory-mcp/CHANGELOG.md", + "changelog:check": "node scripts/check-changelog.mjs", "typecheck": "tsc --noEmit", "test": "vitest run", "test:unit": "vitest run test/unit", "test:integration": "vitest run test/integration", "test:workers": "vitest run --config vitest.workers.config.ts", "test:coverage": "vitest run --coverage", - "test:ci": "npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run docs:check && npm run docs:build && npm audit --audit-level=moderate", + "test:ci": "git diff --check && npm run actionlint && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run docs:check && npm run docs:build && npm run changelog:check && npm audit --audit-level=moderate", "test:watch": "vitest", "validate": "npm run typecheck && npm run test:coverage" }, @@ -49,6 +53,7 @@ "drizzle-kit": "^0.31.7", "fast-check": "^4.8.0", "git-cliff": "^2.13.1", + "github-actionlint": "^1.7.12", "node-addon-api": "^8.5.0", "node-gyp": "^12.1.0", "typescript": "^5.9.3", diff --git a/packages/gittensory-mcp/CHANGELOG.md b/packages/gittensory-mcp/CHANGELOG.md new file mode 100644 index 0000000000..37727493d3 --- /dev/null +++ b/packages/gittensory-mcp/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## Unreleased + + +### Features + +- Add public registration polish gates + + +## mcp-v0.1.3 - 2026-05-26 + + +### Chores + +- Prepare public gittensory launch + + + +### Features + +- Add install site and mcp diagnostics + +- Add situational score projections + + + diff --git a/packages/gittensory-mcp/LICENSE b/packages/gittensory-mcp/LICENSE index bf5e0ae1b1..be3f7b28e5 100644 --- a/packages/gittensory-mcp/LICENSE +++ b/packages/gittensory-mcp/LICENSE @@ -1,3 +1,661 @@ -AGPL-3.0-only + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 -The full license text is available in the root Gittensory repository. + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/gittensory-mcp/README.md b/packages/gittensory-mcp/README.md index 4078f9b48a..89071c5e23 100644 --- a/packages/gittensory-mcp/README.md +++ b/packages/gittensory-mcp/README.md @@ -6,7 +6,7 @@ It inspects local git metadata and calls the Gittensory API for branch preflight ## Status -The package is public beta. Gittensory still keeps sensitive score, trust, and maintainer context out of public PR comments. +The package is public. Gittensory keeps sensitive score, trust, wallet, and maintainer context out of public PR comments. ## Install @@ -31,6 +31,7 @@ gittensory-mcp login gittensory-mcp logout gittensory-mcp whoami gittensory-mcp status +gittensory-mcp changelog gittensory-mcp doctor gittensory-mcp init-client --print codex gittensory-mcp init-client --print claude @@ -70,5 +71,16 @@ The wrapper stores a Gittensory session token, not a GitHub token. - `GITTENSOR_SCORE_PREVIEW_CMD` - `GITTENSOR_ROOT` - `GITTENSORY_UPLOAD_SOURCE=false` +- `GITTENSORY_SKIP_NPM_VERSION_CHECK=true` `GITTENSORY_UPLOAD_SOURCE=true` is not supported and fails closed. + +## Release Notes + +The package ships with `CHANGELOG.md`. Run: + +```sh +gittensory-mcp changelog +``` + +`gittensory-mcp status` also reports the local package version, latest npm version when reachable, API health, auth state, and source-upload posture. diff --git a/packages/gittensory-mcp/bin/gittensory-mcp.js b/packages/gittensory-mcp/bin/gittensory-mcp.js index 97c0833ff0..819d3a0af8 100755 --- a/packages/gittensory-mcp/bin/gittensory-mcp.js +++ b/packages/gittensory-mcp/bin/gittensory-mcp.js @@ -8,6 +8,9 @@ import { z } from "zod"; import { buildBranchAnalysisPayload, collectLocalDiff, collectLocalBranchMetadata, setupGuidanceForLocalScorer } from "../lib/local-branch.js"; const defaultApiUrl = "https://gittensory-api.zeronode.workers.dev"; +const packageName = "@jsonbored/gittensory-mcp"; +const packageVersion = "0.1.4"; +const changelogPath = new URL("../CHANGELOG.md", import.meta.url); const configPath = process.env.GITTENSORY_CONFIG_PATH ?? (process.env.GITTENSORY_CONFIG_DIR @@ -121,7 +124,7 @@ if (cliArgs[0] && cliArgs[0] !== "--stdio") { const server = new McpServer({ name: "gittensory-local", - version: "0.1.3", + version: packageVersion, }); server.registerTool( @@ -364,6 +367,7 @@ async function runCli(args) { if (command === "logout") return logout(options); if (command === "whoami") return whoami(options); if (command === "status") return status(options); + if (command === "changelog") return changelog(options); if (command === "doctor") return doctor(options); if (command === "init-client") return initClient(options); if (command !== "analyze-branch" && command !== "preflight") throw new Error(`Unknown command: ${command}`); @@ -413,6 +417,7 @@ function printHelp() { gittensory-mcp logout [--json] gittensory-mcp whoami [--json] gittensory-mcp status [--json] + gittensory-mcp changelog [--json] gittensory-mcp doctor [--cwd path] [--json] gittensory-mcp init-client --print codex|claude|cursor [--json] gittensory-mcp analyze-branch --login [--repo owner/repo] [--base origin/main] [--pending-merged-prs 3] [--expected-open-prs 0] [--projected-credibility 0.8] [--scenario-note "..."] [--validation "passed|npm test|summary"] [--json] @@ -509,6 +514,8 @@ async function whoami(options) { async function status(options) { let auth = { status: getApiToken() ? "token_configured" : "unauthenticated" }; + let health = null; + let latest = null; if (getApiToken()) { try { auth = await apiGet("/v1/auth/session"); @@ -516,8 +523,26 @@ async function status(options) { auth = { status: "token_configured", session: "unverified", error: error instanceof Error ? error.message : "status_failed" }; } } + try { + health = await apiFetch("/health", { method: "GET" }, { auth: false, timeoutMs: 5000 }); + } catch (error) { + health = { status: "unreachable", error: error instanceof Error ? error.message : "health_check_failed" }; + } + try { + latest = await fetchLatestPackageVersion(); + } catch (error) { + latest = { status: "unavailable", error: error instanceof Error ? error.message : "npm_version_check_failed" }; + } const payload = { apiUrl, + package: { + name: packageName, + version: packageVersion, + latestVersion: latest?.version ?? null, + updateAvailable: typeof latest?.version === "string" && latest.version !== packageVersion, + latestStatus: latest?.status ?? "ok", + }, + api: health, auth, configPath, sourceUploadDefault: false, @@ -525,12 +550,27 @@ async function status(options) { }; if (options.json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`); else { + process.stdout.write(`${packageName}: ${packageVersion}${payload.package.latestVersion ? ` (latest ${payload.package.latestVersion})` : ""}\n`); process.stdout.write(`API: ${apiUrl}\n`); + process.stdout.write(`API health: ${health?.status ?? "unknown"}\n`); process.stdout.write(`Auth: ${auth.status}${auth.login ? ` (${auth.login})` : ""}\n`); process.stdout.write("Source upload: disabled\n"); } } +async function changelog(options) { + const text = existsSync(changelogPath) ? readFileSync(changelogPath, "utf8") : "# Changelog\n\nNo packaged changelog was found.\n"; + const payload = { + package: { + name: packageName, + version: packageVersion, + }, + changelog: text, + }; + if (options.json) process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`); + else process.stdout.write(text.endsWith("\n") ? text : `${text}\n`); +} + async function doctor(options) { const checks = []; const add = (name, statusValue, detail, remediation) => checks.push(stripUndefined({ name, status: statusValue, detail, remediation })); @@ -734,6 +774,19 @@ async function apiFetch(path, init, options = {}) { return payload; } +async function fetchLatestPackageVersion() { + if (/^(1|true|yes)$/i.test(process.env.GITTENSORY_SKIP_NPM_VERSION_CHECK ?? "false")) return { status: "skipped" }; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + const response = await fetch("https://registry.npmjs.org/@jsonbored%2fgittensory-mcp/latest", { + signal: controller.signal, + headers: { accept: "application/json" }, + }).finally(() => clearTimeout(timeout)); + const payload = await response.json().catch(() => ({})); + if (!response.ok || typeof payload.version !== "string") throw new Error("npm_latest_version_unavailable"); + return { status: "ok", version: payload.version }; +} + async function analyzeCurrentBranch(input) { const payload = buildBranchAnalysisPayload(input); const { localScorerStatus, ...body } = payload; diff --git a/packages/gittensory-mcp/package.json b/packages/gittensory-mcp/package.json index 3616180b31..07ad914ec0 100644 --- a/packages/gittensory-mcp/package.json +++ b/packages/gittensory-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@jsonbored/gittensory-mcp", - "version": "0.1.3", + "version": "0.1.4", "license": "AGPL-3.0-only", "type": "module", "description": "Local stdio MCP wrapper for Gittensory contributor intelligence.", @@ -29,7 +29,8 @@ }, "files": [ "bin", - "lib" + "lib", + "CHANGELOG.md" ], "scripts": { "build": "node --check bin/gittensory-mcp.js && node --check lib/local-branch.js" diff --git a/scripts/check-changelog.mjs b/scripts/check-changelog.mjs new file mode 100644 index 0000000000..4924aaed21 --- /dev/null +++ b/scripts/check-changelog.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; + +const tempDir = mkdtempSync(join(tmpdir(), "gittensory-changelog-")); + +try { + const checks = [ + { + label: "root changelog", + output: "CHANGELOG.md", + args: ["--config", "cliff.toml", "--output", join(tempDir, "CHANGELOG.md")], + }, + { + label: "MCP package changelog", + output: "packages/gittensory-mcp/CHANGELOG.md", + args: [ + "--config", + "cliff.mcp.toml", + "--include-path", + "packages/gittensory-mcp/**", + "--include-path", + ".github/workflows/npm-publish.yml", + "--output", + join(tempDir, "MCP_CHANGELOG.md"), + ], + }, + ]; + + const failures = []; + for (const check of checks) { + const generatedPath = check.args.at(-1); + run(["git-cliff", ...check.args], check.label); + const expected = readFileSync(generatedPath, "utf8"); + const actual = readFileSync(check.output, "utf8"); + if (normalize(actual) !== normalize(expected)) failures.push(`${check.output} is stale; run npm run changelog.`); + } + + if (failures.length > 0) { + console.error(failures.join("\n")); + process.exit(1); + } + + console.log("changelogs are current"); +} finally { + rmSync(tempDir, { recursive: true, force: true }); +} + +function run(command, label) { + const result = spawnSync(command[0], command.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }); + if (result.status !== 0) { + process.stderr.write(result.stderr || result.stdout || `${label} failed`); + process.exit(result.status ?? 1); + } +} + +function normalize(value) { + return value.replace(/\r\n/g, "\n").trimEnd(); +} diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 8d6de73a5f..e1c1ab841f 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -3,7 +3,7 @@ import { readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; const root = process.cwd(); -const targets = ["README.md", "packages/gittensory-mcp/README.md", "site"]; +const targets = ["README.md", "CONTRIBUTING.md", "SECURITY.md", "SUPPORT.md", "packages/gittensory-mcp/README.md", "site"]; const staleRoutes = [ "/v1/contributors/:login/opportunities", "/v1/contributors/:login/fit", @@ -21,6 +21,8 @@ const forbidden = [ { name: "dev vars assignment", pattern: /^GITHUB_(?:WEBHOOK_SECRET|APP_PRIVATE_KEY|PUBLIC_TOKEN)=.+$/m }, { name: "gittensory token assignment", pattern: /^GITTENSORY_(?:API_TOKEN|MCP_TOKEN|TOKEN)=.+$/m }, { name: "internal token assignment", pattern: /^INTERNAL_JOB_TOKEN=.+$/m }, + { name: "stale private-beta wording", pattern: /\bprivate beta\b/i }, + { name: "preview worker domain", pattern: /zeronode\.workers\.dev/i }, ]; const files = targets.flatMap((target) => collect(join(root, target))).filter((file) => /\.(md|mts|ts|js|json|yml)$/.test(file)); @@ -42,6 +44,14 @@ for (const phrase of ["Gittensor miners", "GitHub App", "MCP", "not a Gittensor if (!siteIndex.includes(phrase)) failures.push(`site/index.md: missing required positioning phrase ${JSON.stringify(phrase)}`); } +for (const required of ["SUPPORT.md", "site/security/privacy.md", "site/security/terms.md", "site/support.md"]) { + try { + readFileSync(join(root, required), "utf8"); + } catch { + failures.push(`${required}: missing public-registration support/privacy/terms doc`); + } +} + if (failures.length > 0) { console.error(failures.join("\n")); process.exit(1); diff --git a/scripts/check-mcp-package.mjs b/scripts/check-mcp-package.mjs index 7ec54c84dc..61f7fdd051 100644 --- a/scripts/check-mcp-package.mjs +++ b/scripts/check-mcp-package.mjs @@ -14,9 +14,10 @@ if (result.status !== 0) { const [pack] = JSON.parse(result.stdout); const files = pack.files.map((file) => file.path).sort(); -const allowed = [/^bin\/gittensory-mcp\.js$/, /^lib\/local-branch\.js$/, /^package\.json$/, /^README\.md$/, /^LICENSE$/]; +const allowed = [/^bin\/gittensory-mcp\.js$/, /^lib\/local-branch\.js$/, /^package\.json$/, /^README\.md$/, /^CHANGELOG\.md$/, /^LICENSE$/]; const forbiddenPath = /(^|\/)(\.dev\.vars|\.env|\.npmrc|.*\.pem|.*private.*key.*|.*secret.*)$/i; const forbiddenContent = /(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9_]+|gts_[0-9a-f]{64}|[A-Z0-9_]*(TOKEN|SECRET|PRIVATE_KEY)=)/; +const stalePackageText = /(private beta|zeronode\.workers\.dev|preview URL)/i; for (const file of files) { if (forbiddenPath.test(file)) throw new Error(`Forbidden file in MCP package: ${file}`); @@ -24,6 +25,7 @@ for (const file of files) { const fullPath = join("packages/gittensory-mcp", file); const content = readFileSync(fullPath, "utf8"); if (forbiddenContent.test(content)) throw new Error(`Secret-like content found in MCP package file: ${file}`); + if (file === "README.md" && stalePackageText.test(content)) throw new Error(`Stale public-package wording found in MCP package file: ${file}`); } process.stdout.write(`MCP package dry-run ok: ${files.join(", ")}\n`); diff --git a/site/.vitepress/config.mts b/site/.vitepress/config.mts index 3976f6f36b..198f3682e5 100644 --- a/site/.vitepress/config.mts +++ b/site/.vitepress/config.mts @@ -44,6 +44,8 @@ export default defineConfig({ items: [ { text: "API", link: "/reference/api" }, { text: "Privacy", link: "/security/privacy" }, + { text: "Terms", link: "/security/terms" }, + { text: "Support", link: "/support" }, { text: "Troubleshooting", link: "/troubleshooting" }, ], }, diff --git a/site/guide/github-app-setup.md b/site/guide/github-app-setup.md index 06ade4aaf2..1fea17f786 100644 --- a/site/guide/github-app-setup.md +++ b/site/guide/github-app-setup.md @@ -23,15 +23,14 @@ Use a generated webhook secret and set the same value in Cloudflare as `GITHUB_W | Permission | Access | Why | | --- | --- | --- | | Metadata | Read | Required for repository identity and repository events. | -| Checks | Write | Required to create and update the Gittensory check run. | -| Pull requests | Read | Required for PR metadata, reviewability, and webhook events. | -| Issues | Read | Required for issue linkage, issue-discovery context, and duplicate signals. | +| Pull requests | Read | Required for PR metadata and webhook events. | +| Issues | Write | Required to post the sticky PR comment and apply the maintainer-configured label. | Optional: | Permission | Access | Why | | --- | --- | --- | -| Issues | Write | Only needed if public-safe sticky PR comments are enabled. | +| Checks | Write | Only needed if minimal check runs are explicitly enabled. | | Contents | Read | Only needed if a future feature reads repository files directly through the App. | ## Required Events @@ -44,6 +43,19 @@ Subscribe to: If GitHub shows `Installation target`, select it. Some installation-related events are not always shown as normal selectable event rows; Gittensory should not block health on event names that are hidden in the app UI. +## Default Visibility + +Gittensory inspects PR webhooks quietly first. It publishes a public surface only when the PR author is confirmed through the official Gittensor API. + +Default visible behavior: + +- non-miner authors: no comment, no label, no check +- bot authors: no comment, no label, no check +- maintainer-associated authors: no public output unless `includeMaintainerAuthors=true` +- confirmed miners: one sticky public-safe comment plus the configured label, defaulting to `gittensor` + +Check runs default to off. If enabled later, they stay minimal and do not include private reviewability, scoring, wallet, hotkey, or reward/risk context. + ## Install Or Repair 1. Update the GitHub App permissions and events. diff --git a/site/guide/maintainers.md b/site/guide/maintainers.md index 7f850bcb4a..0c134b0ee1 100644 --- a/site/guide/maintainers.md +++ b/site/guide/maintainers.md @@ -4,16 +4,15 @@ Gittensory is meant to make Gittensor-driven contribution flow less noisy. ## GitHub App Surface -The GitHub App creates the detailed maintainer surface: +The GitHub App is designed to stay quiet unless a PR author is an officially confirmed Gittensor miner. -- Gittensory check runs on PRs -- reviewability context -- linked issue and duplicate signals -- contributor role context -- queue and repo-lane context -- optional public-safe sticky PR comments +Default visible output for confirmed miner PRs: -The check run is the canonical detailed report. Public comments are opt-in and sanitized. +- one sticky public-safe PR comment +- one maintainer-configured label, defaulting to `gittensor` +- no public output for bots, non-miners, or maintainer-associated authors unless explicitly enabled + +Private reviewability context, queue risk, score blockers, and reward/risk reasoning stay in the API/MCP. GitHub checks default to off; if a maintainer enables them later, they remain minimal and do not carry detailed findings. ## Reviewability Actions @@ -30,9 +29,7 @@ The point is not to shame contributors. The point is to identify the lowest-fric ## Public Comments -Public comments stay off by default. - -When enabled, comments can include: +Confirmed miner comments can include: - contribution context - PR hygiene diff --git a/site/reference/api.md b/site/reference/api.md index cfa2cd4fcf..8a4d5abe23 100644 --- a/site/reference/api.md +++ b/site/reference/api.md @@ -25,6 +25,8 @@ curl "$GITTENSORY_API_URL/openapi.json" \ | `GET /v1/repos` | Known registered and installed repositories. | | `GET /v1/repos/:owner/:repo` | Repository metadata. | | `GET /v1/repos/:owner/:repo/intelligence` | Canonical repository intelligence bundle. | +| `GET /v1/repos/:owner/:repo/registration-readiness` | Repo-owner readiness signal for Gittensor registration. | +| `GET /v1/repos/:owner/:repo/gittensor-config-recommendation` | Private Gittensor config recommendation for repo owners. | | `GET /v1/repos/:owner/:repo/pulls/:number/maintainer-packet` | PR-specific maintainer review packet. | | `GET /v1/repos/:owner/:repo/pulls/:number/reviewability` | Private PR reviewability score and maintainer action. | diff --git a/site/security/privacy.md b/site/security/privacy.md index 8861733c34..7593023363 100644 --- a/site/security/privacy.md +++ b/site/security/privacy.md @@ -47,4 +47,4 @@ Rate-limited responses return `429` with retry metadata. ## GitHub App Boundaries -The GitHub App is advisory-only. It does not auto-label, close, merge, or rewrite contributor work. +The GitHub App is advisory-only. It can apply a maintainer-configured label to confirmed miner PRs, but it does not close, merge, rewrite, or publicly score contributor work. diff --git a/site/security/terms.md b/site/security/terms.md new file mode 100644 index 0000000000..d73ccd1255 --- /dev/null +++ b/site/security/terms.md @@ -0,0 +1,34 @@ +# Terms + +Gittensory provides advisory developer tooling for Gittensor repository participation. It is not a +wallet, exchange, custodian, payout service, or official Gittensor frontend. + +## Advisory Output + +Gittensory output is evidence-based guidance from public repository data, official Gittensor stats +where available, and local metadata supplied by the MCP wrapper. It can be incomplete, stale, or +wrong when upstream data is unavailable or delayed. + +Do not treat scoreability projections, queue signals, or contributor guidance as guaranteed rewards, +guaranteed merge outcomes, or financial advice. + +## User Responsibilities + +Users are responsible for: + +- reviewing generated PR packets before posting them +- keeping tokens and local credentials private +- avoiding source upload unless a future version explicitly documents and supports it +- complying with each upstream repository's contribution policy +- verifying any score or eligibility assumptions against current Gittensor rules + +## Public GitHub App Behavior + +The GitHub App is intentionally low-noise. It can post sanitized comments and configured labels for +officially confirmed Gittensor miners when an installed repository enables that surface. It does not +close, merge, rewrite, publicly rank, or publicly score contributor work. + +## Availability + +The hosted API and MCP integrations may change as Gittensor rules, GitHub APIs, and repository +registration requirements evolve. diff --git a/site/support.md b/site/support.md new file mode 100644 index 0000000000..7db150d929 --- /dev/null +++ b/site/support.md @@ -0,0 +1,22 @@ +# Support + +Use GitHub issues for public support when the report does not contain sensitive material. + +Good public issues include: + +- MCP install, login, `doctor`, or local branch-analysis problems +- GitHub App install-health, permission, comment, or label behavior +- API response shape, data freshness, or signal-quality bugs +- documentation corrections + +Do not post tokens, private keys, webhook secrets, wallet details, hotkeys, coldkeys, raw private +score output, or maintainer-only evidence in public issues. + +For security reports, follow `SECURITY.md` in the repository. + +## Helpful Details + +- `gittensory-mcp status` output with local paths and tokens removed +- command and sanitized error text +- affected repository owner/name when relevant +- whether the issue affects MCP, API, GitHub App, docs, or data freshness diff --git a/src/api/routes.ts b/src/api/routes.ts index 64ef141931..bd04e57995 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -94,7 +94,7 @@ import { attachDataQuality, buildCoreSignalFidelity, buildRepoDataQuality, build import { buildPullRequestReviewability } from "../signals/reward-risk"; import { buildLocalBranchAnalysis } from "../signals/local-branch"; import type { ContributorEvidenceRecord, JobMessage, JsonValue, RepoSyncSegmentRecord } from "../types"; -import { nowIso } from "../utils/json"; +import { errorMessage, nowIso } from "../utils/json"; type AppBindings = { Bindings: Env }; @@ -203,9 +203,16 @@ const scorePreviewSchema = z.object({ }); const repositorySettingsSchema = z.object({ - commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]), + commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]).default("detected_contributors_only"), publicSignalLevel: z.enum(["minimal", "standard"]).default("standard"), + checkRunMode: z.enum(["off", "enabled"]).default("off"), checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]).default("standard"), + autoLabelEnabled: z.boolean().default(true), + gittensorLabel: z.string().trim().min(1).max(50).default("gittensor"), + createMissingLabel: z.boolean().default(true), + publicSurface: z.enum(["off", "comment_and_label", "comment_only", "label_only"]).default("comment_and_label"), + includeMaintainerAuthors: z.boolean().default(false), + requireLinkedIssue: z.boolean().default(false), backfillEnabled: z.boolean().default(true), privateTrustEnabled: z.boolean().default(true), }); @@ -265,7 +272,8 @@ export function createApp() { 201, ); } catch (error) { - return c.json({ error: error instanceof Error ? error.message : "github_device_flow_start_failed" }, error instanceof Error && error.message === "github_oauth_not_configured" ? 503 : 502); + const message = errorMessage(error, "github_device_flow_start_failed"); + return c.json({ error: message }, message === "github_oauth_not_configured" ? 503 : 502); } }); @@ -276,7 +284,8 @@ export function createApp() { try { return c.json(await pollGitHubDeviceFlow(c.env, deviceCode)); } catch (error) { - return c.json({ error: error instanceof Error ? error.message : "github_device_flow_poll_failed" }, error instanceof Error && error.message === "github_oauth_not_configured" ? 503 : 502); + const message = errorMessage(error, "github_device_flow_poll_failed"); + return c.json({ error: message }, message === "github_oauth_not_configured" ? 503 : 502); } }); @@ -287,7 +296,7 @@ export function createApp() { try { return c.json(await createSessionFromGitHubToken(c.env, githubToken, { source: "github_token_exchange" }), 201); } catch (error) { - return c.json({ error: error instanceof Error ? error.message : "github_session_create_failed" }, 401); + return c.json({ error: errorMessage(error, "github_session_create_failed") }, 401); } }); @@ -497,6 +506,16 @@ export function createApp() { return c.json(await buildRepoIntelligenceResponse(c.env, fullName)); }); + app.get("/v1/repos/:owner/:repo/registration-readiness", async (c) => { + const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`; + return c.json(await buildRegistrationReadinessResponse(c.env, fullName)); + }); + + app.get("/v1/repos/:owner/:repo/gittensor-config-recommendation", async (c) => { + const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`; + return c.json(await buildGittensorConfigRecommendationResponse(c.env, fullName)); + }); + app.get("/v1/repos/:owner/:repo/settings", async (c) => { const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`; return c.json(await getRepositorySettings(c.env, fullName)); @@ -864,8 +883,14 @@ export function createApp() { repoFullName: fullName, commentMode: parsed.data.commentMode, publicSignalLevel: parsed.data.publicSignalLevel, - checkRunMode: "enabled", + checkRunMode: parsed.data.checkRunMode, checkRunDetailLevel: parsed.data.checkRunDetailLevel, + autoLabelEnabled: parsed.data.autoLabelEnabled, + gittensorLabel: parsed.data.gittensorLabel, + createMissingLabel: parsed.data.createMissingLabel, + publicSurface: parsed.data.publicSurface, + includeMaintainerAuthors: parsed.data.includeMaintainerAuthors, + requireLinkedIssue: parsed.data.requireLinkedIssue, backfillEnabled: parsed.data.backfillEnabled, privateTrustEnabled: parsed.data.privateTrustEnabled, }), @@ -936,6 +961,101 @@ async function buildRepoIntelligenceResponse(env: Env, fullName: string) { }; } +async function buildRegistrationReadinessResponse(env: Env, fullName: string) { + const intelligence = await buildRepoIntelligenceResponse(env, fullName); + const settings = await getRepositorySettings(env, fullName); + const repo = intelligence.repo; + const configQuality = intelligence.configQuality as ReturnType; + const maintainerCutReadiness = intelligence.maintainerCutReadiness as ReturnType; + const contributorIntakeHealth = intelligence.contributorIntakeHealth as ReturnType; + const lane = buildLaneAdvice(repo, fullName); + const blockers = [ + ...(!repo?.isRegistered ? ["Repository is not registered in the latest Gittensory registry snapshot."] : []), + ...(configQuality.level === "fragile" ? ["Repository config quality is fragile."] : []), + ...(contributorIntakeHealth.level === "blocked" ? ["Contributor intake health is blocked."] : []), + ]; + const warnings = [ + ...(configQuality.level === "needs_attention" ? ["Repository config quality needs attention before registration promotion."] : []), + ...(contributorIntakeHealth.level === "strained" ? ["Contributor intake is strained; expect more maintainer triage."] : []), + ...(settings.publicSurface === "off" ? ["GitHub App public surface is disabled; maintainers will not get comment/label assistance."] : []), + ]; + const issuePolicy = + lane.lane === "issue_discovery" + ? "issue_discovery_enabled" + : lane.lane === "split" + ? "split_pr_and_issue_discovery_enabled" + : settings.requireLinkedIssue + ? "direct_pr_requires_linked_issue" + : "direct_pr_no_issue_required"; + const ready = blockers.length === 0 && !["fragile", "needs_attention"].includes(configQuality.level); + return { + repoFullName: fullName, + generatedAt: nowIso(), + ready, + recommendedRegistrationMode: lane.lane === "issue_discovery" ? "issue_discovery" : lane.lane === "split" ? "split" : "direct_pr", + issuePolicy, + labelPolicy: { + autoLabelEnabled: settings.autoLabelEnabled, + label: settings.gittensorLabel, + createMissingLabel: settings.createMissingLabel, + configuredRegistryLabels: configQuality.configuredLabels, + missingOrUnusedRegistryLabels: configQuality.notObservedConfiguredLabels, + }, + maintainerCutReadiness, + contributorIntakeHealth, + docsCompleteness: { + status: "repo_docs_not_crawled", + requiredDocs: ["README", "CONTRIBUTING", "SECURITY", "SUPPORT"], + note: "Gittensory validates public repo docs from the local project during CI; remote repo-doc crawling is not enabled in this signal yet.", + }, + blockers, + warnings, + dataQuality: intelligence.dataQuality, + }; +} + +async function buildGittensorConfigRecommendationResponse(env: Env, fullName: string) { + const intelligence = await buildRepoIntelligenceResponse(env, fullName); + const settings = await getRepositorySettings(env, fullName); + const repo = intelligence.repo; + const lane = buildLaneAdvice(repo, fullName); + const configQuality = intelligence.configQuality as ReturnType; + const contributorIntakeHealth = intelligence.contributorIntakeHealth as ReturnType; + const maintainerCutReadiness = intelligence.maintainerCutReadiness as ReturnType; + const current = repo?.registryConfig ?? null; + const shouldEnableIssueDiscovery = contributorIntakeHealth.level === "healthy" && configQuality.level === "excellent"; + const recommendedIssueDiscoveryShare = shouldEnableIssueDiscovery ? 0.1 : 0; + const currentAllocation = current?.emissionShare ?? 0; + const directPrShare = Math.max(0, currentAllocation - recommendedIssueDiscoveryShare); + const recommendedMaintainerCut = maintainerCutReadiness.ready ? Math.max(current?.maintainerCut ?? 0, 0.02) : current?.maintainerCut ?? 0; + return { + repoFullName: fullName, + generatedAt: nowIso(), + privateOnly: true, + current, + recommended: { + participationMode: recommendedIssueDiscoveryShare > 0 ? "split" : "direct_pr", + issueDiscoveryShare: recommendedIssueDiscoveryShare, + directPrShare, + maintainerCut: recommendedMaintainerCut, + requireLinkedIssue: settings.requireLinkedIssue, + labelMultipliers: configQuality.configuredLabels.length > 0 ? "keep_current_and_prune_unused" : "start_without_trusted_label_multipliers", + publicSurface: settings.publicSurface, + confirmedMinerLabel: settings.gittensorLabel, + }, + reasons: [ + lane.lane === "issue_discovery" ? "The current registry lane already routes meaningful work through issue discovery." : "Direct-PR mode is the safest default until issue-discovery intake is intentionally staffed.", + shouldEnableIssueDiscovery ? "Config and intake signals are strong enough to consider a small issue-discovery slice." : "Issue discovery should stay disabled until config quality and intake health are excellent.", + maintainerCutReadiness.ready ? "Maintainer cut can be considered because config and queue signals are clean." : "Maintainer cut should stay unchanged until readiness blockers are cleared.", + ], + warnings: [ + ...(configQuality.notObservedConfiguredLabels.length > 0 ? [`${configQuality.notObservedConfiguredLabels.length} configured label(s) have not been observed in cached repo activity.`] : []), + ...(contributorIntakeHealth.level === "strained" || contributorIntakeHealth.level === "blocked" ? [`Contributor intake is ${contributorIntakeHealth.level}; avoid increasing noisy lanes yet.`] : []), + ], + dataQuality: intelligence.dataQuality, + }; +} + async function loadOpenQueueCounts(env: Env, fullName: string): Promise<{ openIssues: number; openPullRequests: number }> { const [totals, openIssues, openPullRequests] = await Promise.all([getLatestRepoGithubTotalsSnapshot(env, fullName), countOpenIssues(env, fullName), countOpenPullRequests(env, fullName)]); return { diff --git a/src/db/repositories.ts b/src/db/repositories.ts index f86d9692cb..14a9529199 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -274,10 +274,16 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise if (!row) { return { repoFullName: fullName, - commentMode: "off", + commentMode: "detected_contributors_only", publicSignalLevel: "standard", - checkRunMode: "enabled", - checkRunDetailLevel: "standard", + checkRunMode: "off", + checkRunDetailLevel: "minimal", + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, backfillEnabled: true, privateTrustEnabled: true, }; @@ -286,8 +292,14 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise repoFullName: row.repoFullName, commentMode: parseCommentMode(row.commentMode), publicSignalLevel: row.publicSignalLevel === "minimal" ? "minimal" : "standard", - checkRunMode: "enabled", + checkRunMode: parseCheckRunMode(row.checkRunMode), checkRunDetailLevel: parseCheckRunDetailLevel(row.checkRunDetailLevel), + autoLabelEnabled: row.autoLabelEnabled, + gittensorLabel: row.gittensorLabel, + createMissingLabel: row.createMissingLabel, + publicSurface: parsePublicSurface(row.publicSurface), + includeMaintainerAuthors: row.includeMaintainerAuthors, + requireLinkedIssue: row.requireLinkedIssue, backfillEnabled: row.backfillEnabled, privateTrustEnabled: row.privateTrustEnabled, createdAt: row.createdAt, @@ -295,33 +307,60 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise }; } -export async function upsertRepositorySettings(env: Env, settings: RepositorySettings): Promise { +export async function upsertRepositorySettings(env: Env, settings: Partial & { repoFullName: string }): Promise { + const resolved: RepositorySettings = { + repoFullName: settings.repoFullName, + commentMode: settings.commentMode ?? "detected_contributors_only", + publicSignalLevel: settings.publicSignalLevel ?? "standard", + checkRunMode: settings.checkRunMode ?? "off", + checkRunDetailLevel: settings.checkRunDetailLevel ?? "minimal", + autoLabelEnabled: settings.autoLabelEnabled ?? true, + gittensorLabel: settings.gittensorLabel ?? "gittensor", + createMissingLabel: settings.createMissingLabel ?? true, + publicSurface: settings.publicSurface ?? "comment_and_label", + includeMaintainerAuthors: settings.includeMaintainerAuthors ?? false, + requireLinkedIssue: settings.requireLinkedIssue ?? false, + backfillEnabled: settings.backfillEnabled ?? true, + privateTrustEnabled: settings.privateTrustEnabled ?? true, + }; const db = getDb(env.DB); await db .insert(repositorySettings) .values({ - repoFullName: settings.repoFullName, - commentMode: settings.commentMode, - publicSignalLevel: settings.publicSignalLevel, - checkRunMode: "enabled", - checkRunDetailLevel: settings.checkRunDetailLevel, - backfillEnabled: settings.backfillEnabled, - privateTrustEnabled: settings.privateTrustEnabled, + repoFullName: resolved.repoFullName, + commentMode: resolved.commentMode, + publicSignalLevel: resolved.publicSignalLevel, + checkRunMode: resolved.checkRunMode, + checkRunDetailLevel: resolved.checkRunDetailLevel, + autoLabelEnabled: resolved.autoLabelEnabled, + gittensorLabel: resolved.gittensorLabel, + createMissingLabel: resolved.createMissingLabel, + publicSurface: resolved.publicSurface, + includeMaintainerAuthors: resolved.includeMaintainerAuthors, + requireLinkedIssue: resolved.requireLinkedIssue, + backfillEnabled: resolved.backfillEnabled, + privateTrustEnabled: resolved.privateTrustEnabled, updatedAt: nowIso(), }) .onConflictDoUpdate({ target: repositorySettings.repoFullName, set: { - commentMode: settings.commentMode, - publicSignalLevel: settings.publicSignalLevel, - checkRunMode: "enabled", - checkRunDetailLevel: settings.checkRunDetailLevel, - backfillEnabled: settings.backfillEnabled, - privateTrustEnabled: settings.privateTrustEnabled, + commentMode: resolved.commentMode, + publicSignalLevel: resolved.publicSignalLevel, + checkRunMode: resolved.checkRunMode, + checkRunDetailLevel: resolved.checkRunDetailLevel, + autoLabelEnabled: resolved.autoLabelEnabled, + gittensorLabel: resolved.gittensorLabel, + createMissingLabel: resolved.createMissingLabel, + publicSurface: resolved.publicSurface, + includeMaintainerAuthors: resolved.includeMaintainerAuthors, + requireLinkedIssue: resolved.requireLinkedIssue, + backfillEnabled: resolved.backfillEnabled, + privateTrustEnabled: resolved.privateTrustEnabled, updatedAt: nowIso(), }, }); - return getRepositorySettings(env, settings.repoFullName); + return getRepositorySettings(env, resolved.repoFullName); } export async function upsertRepoSyncState(env: Env, state: RepoSyncStateRecord): Promise { @@ -1859,11 +1898,20 @@ function parseCommentMode(value: string): RepositorySettings["commentMode"] { return "off"; } +function parseCheckRunMode(value: string): RepositorySettings["checkRunMode"] { + return value === "enabled" ? "enabled" : "off"; +} + function parseCheckRunDetailLevel(value: string): RepositorySettings["checkRunDetailLevel"] { if (value === "minimal" || value === "deep") return value; return "standard"; } +function parsePublicSurface(value: string): RepositorySettings["publicSurface"] { + if (value === "comment_only" || value === "label_only" || value === "off") return value; + return "comment_and_label"; +} + function parseSyncStatus(value: string): RepoSyncStateRecord["status"] { if ( value === "running" || diff --git a/src/db/schema.ts b/src/db/schema.ts index 6a7d614f8e..bc07b84a49 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -35,10 +35,16 @@ export const repositories = sqliteTable("repositories", { export const repositorySettings = sqliteTable("repository_settings", { repoFullName: text("repo_full_name").primaryKey(), - commentMode: text("comment_mode").notNull().default("off"), + commentMode: text("comment_mode").notNull().default("detected_contributors_only"), publicSignalLevel: text("public_signal_level").notNull().default("standard"), - checkRunMode: text("check_run_mode").notNull().default("enabled"), - checkRunDetailLevel: text("check_run_detail_level").notNull().default("standard"), + checkRunMode: text("check_run_mode").notNull().default("off"), + checkRunDetailLevel: text("check_run_detail_level").notNull().default("minimal"), + autoLabelEnabled: integer("auto_label_enabled", { mode: "boolean" }).notNull().default(true), + gittensorLabel: text("gittensor_label").notNull().default("gittensor"), + createMissingLabel: integer("create_missing_label", { mode: "boolean" }).notNull().default(true), + publicSurface: text("public_surface").notNull().default("comment_and_label"), + includeMaintainerAuthors: integer("include_maintainer_authors", { mode: "boolean" }).notNull().default(false), + requireLinkedIssue: integer("require_linked_issue", { mode: "boolean" }).notNull().default(false), backfillEnabled: integer("backfill_enabled", { mode: "boolean" }).notNull().default(true), privateTrustEnabled: integer("private_trust_enabled", { mode: "boolean" }).notNull().default(true), createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"), diff --git a/src/github/backfill.ts b/src/github/backfill.ts index 650782e85d..0e4f809192 100644 --- a/src/github/backfill.ts +++ b/src/github/backfill.ts @@ -51,7 +51,7 @@ import type { RepoSyncStateRecord, RepositoryRecord, } from "../types"; -import { nowIso, repoParts } from "../utils/json"; +import { errorMessage, nowIso, repoParts, strippedErrorMessage } from "../utils/json"; import { createInstallationToken, getAppInstallation } from "./app"; type GitHubLabelPayload = { @@ -519,7 +519,7 @@ export async function refreshContributorActivity( try { payload = await githubGraphQl(env, query, token); } catch (error) { - warnings.push(`Contributor activity refresh failed for ${chunk.map((repo) => repo.fullName).join(", ")}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`Contributor activity refresh failed for ${chunk.map((repo) => repo.fullName).join(", ")}: ${errorMessage(error)}`); continue; } if (payload.errors?.length) { @@ -577,10 +577,12 @@ export async function refreshContributorActivity( } export const REQUIRED_INSTALLATION_PERMISSIONS: Record = { - checks: "write", metadata: "read", pull_requests: "read", - issues: "read", + issues: "write", +}; +export const OPTIONAL_CHECK_RUN_PERMISSION: Record = { + checks: "write", }; export const REQUIRED_INSTALLATION_EVENTS = ["issues", "pull_request", "repository"] as const; @@ -589,12 +591,17 @@ export const OPTIONAL_VISIBLE_INSTALLATION_EVENTS = ["installation_target"] as c export function enrichInstallationHealth(health: InstallationHealthRecord) { const missingPermissions = new Set(health.missingPermissions); const missingEvents = new Set(health.missingEvents); + const requiredPermissions = { + ...REQUIRED_INSTALLATION_PERMISSIONS, + ...(missingPermissions.has("checks") ? OPTIONAL_CHECK_RUN_PERMISSION : {}), + }; return { ...health, - requiredPermissions: REQUIRED_INSTALLATION_PERMISSIONS, + requiredPermissions, + optionalPermissions: OPTIONAL_CHECK_RUN_PERMISSION, requiredEvents: [...REQUIRED_INSTALLATION_EVENTS], optionalVisibleEvents: [...OPTIONAL_VISIBLE_INSTALLATION_EVENTS], - permissionRemediation: Object.entries(REQUIRED_INSTALLATION_PERMISSIONS).map(([permission, access]) => ({ + permissionRemediation: Object.entries(requiredPermissions).map(([permission, access]) => ({ permission, requiredAccess: access, currentAccess: health.permissions[permission] ?? "missing", @@ -625,7 +632,13 @@ export async function refreshInstallationHealth(env: Env) { const { installation: currentInstallation, errorSummary } = await refreshStoredInstallation(env, installation); const installedRepos = repositories.filter((repo) => repo.installationId === currentInstallation.id && repo.isInstalled); const registeredInstalled = installedRepos.filter((repo) => repo.isRegistered); - const missingPermissions = Object.entries(REQUIRED_INSTALLATION_PERMISSIONS) + const installedSettings = await Promise.all(installedRepos.map((repo) => getRepositorySettings(env, repo.fullName))); + const requiresChecks = installedSettings.some((settings) => settings.checkRunMode === "enabled"); + const requiredPermissions = { + ...REQUIRED_INSTALLATION_PERMISSIONS, + ...(requiresChecks ? OPTIONAL_CHECK_RUN_PERMISSION : {}), + }; + const missingPermissions = Object.entries(requiredPermissions) .filter(([permission, expected]) => !permissionSatisfies(currentInstallation.permissions[permission], expected)) .map(([permission]) => permission); const missingEvents = REQUIRED_INSTALLATION_EVENTS.filter((event) => !currentInstallation.events.includes(event)); @@ -670,7 +683,7 @@ async function refreshStoredInstallation(env: Env, installation: InstallationRec } catch (error) { return { installation, - errorSummary: String(error).replace(/^Error: /, "") || "Failed to refresh GitHub App installation metadata.", + errorSummary: strippedErrorMessage(error, "Failed to refresh GitHub App installation metadata."), }; } } @@ -917,7 +930,7 @@ async function fetchPagedSegment( warnings.push(`GitHub sync is waiting for rate-limit recovery for ${path}: ${error.message}`); } else { status = fetchedThisRun > 0 ? "partial" : "error"; - warnings.push(`GitHub sync failed for ${path}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`GitHub sync failed for ${path}: ${errorMessage(error)}`); } } let fetchedCount = options.countPersisted ? await options.countPersisted() : priorFetched + fetchedThisRun; @@ -975,7 +988,7 @@ async function supplementUnderCountIfNeeded( if (supplemented > 0) warnings.push(`Supplemented ${supplemented} open issue row(s) from GitHub GraphQL because REST open issue pagination undercounted the authoritative total.`); return options.countPersisted ? await options.countPersisted() : fetchedCount + supplemented; } catch (error) { - warnings.push(`GitHub GraphQL supplement failed after REST undercount: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`GitHub GraphQL supplement failed after REST undercount: ${errorMessage(error)}`); return fetchedCount; } } @@ -1275,7 +1288,7 @@ async function backfillRepository(env: Env, repo: RepositoryRecord, limits: Back dataQuality, }; } catch (error) { - const errorSummary = error instanceof Error ? error.message : "unknown error"; + const errorSummary = errorMessage(error); const rateLimitResetAt = error instanceof GitHubApiError ? error.rateLimitResetAt : undefined; const status = error instanceof GitHubApiError && error.rateLimited ? "rate_limited" : "error"; await completeSegment(env, repo, "metadata", repo.installationId ? "installation" : "github", mode, startedAt, { @@ -1324,12 +1337,12 @@ async function fetchAndStorePullRequestDetails( const [files, reviews, checks] = await Promise.all([ fetchPullRequestFiles(env, repoFullName, pr.number, token, warnings), githubJson(env, repoFullName, `/pulls/${pr.number}/reviews?per_page=100`, token).catch((error) => { - warnings.push(`Review sync failed for #${pr.number}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`Review sync failed for #${pr.number}: ${errorMessage(error)}`); return []; }), pr.headSha ? githubJson<{ check_runs?: GitHubCheckRunPayload[] }>(env, repoFullName, `/commits/${pr.headSha}/check-runs?per_page=100`, token).catch((error) => { - warnings.push(`Check sync failed for #${pr.number}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`Check sync failed for #${pr.number}: ${errorMessage(error)}`); return { check_runs: [] }; }) : Promise.resolve({ check_runs: [] }), @@ -1385,7 +1398,7 @@ async function fetchPullRequestFiles( warnings: string[], ): Promise { return githubJson(env, repoFullName, `/pulls/${pullNumber}/files?per_page=100`, token).catch((error) => { - warnings.push(`File sync failed for #${pullNumber}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`File sync failed for #${pullNumber}: ${errorMessage(error)}`); return []; }); } @@ -1469,7 +1482,7 @@ async function syncLabels( }); return { items, warnings: [], segment }; } catch (error) { - const warning = `Label sync failed: ${error instanceof Error ? error.message : "unknown error"}`; + const warning = `Label sync failed: ${errorMessage(error)}`; const segment = await completeSegment(env, repo, "labels", sourceKind, mode, startedAt, { status: error instanceof GitHubApiError && error.rateLimited ? "rate_limited" : "partial", fetchedCount: 0, @@ -1528,7 +1541,7 @@ async function githubPaged( } catch (error) { status = error instanceof GitHubApiError && error.rateLimited ? "rate_limited" : items.length > 0 ? "partial" : "error"; rateLimitResetAt = error instanceof GitHubApiError ? error.rateLimitResetAt : undefined; - warnings.push(`GitHub sync failed for ${path}: ${error instanceof Error ? error.message : "unknown error"}`); + warnings.push(`GitHub sync failed for ${path}: ${errorMessage(error)}`); } if (status === "complete" && items.length >= limit && limit > 0) { diff --git a/src/github/labels.ts b/src/github/labels.ts new file mode 100644 index 0000000000..c6a75e9fbe --- /dev/null +++ b/src/github/labels.ts @@ -0,0 +1,59 @@ +import { Octokit } from "@octokit/core"; +import { createInstallationToken } from "./app"; + +type GitHubLabel = { + name?: string | null; +}; + +export async function ensurePullRequestLabel( + env: Env, + installationId: number, + repoFullName: string, + pullNumber: number, + labelName: string, + options: { createMissingLabel: boolean }, +): Promise<{ applied: boolean; created: boolean }> { + const [owner, repo] = repoFullName.split("/"); + if (!owner || !repo) throw new Error(`Invalid repository full name: ${repoFullName}`); + + const token = await createInstallationToken(env, installationId); + const octokit = new Octokit({ auth: token }); + const existing = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}/labels", { + owner, + repo, + issue_number: pullNumber, + per_page: 100, + }); + const labels = existing.data as GitHubLabel[]; + if (labels.some((label) => label.name?.toLowerCase() === labelName.toLowerCase())) { + return { applied: false, created: false }; + } + + let created = false; + if (options.createMissingLabel) { + const repoLabels = await octokit.request("GET /repos/{owner}/{repo}/labels", { + owner, + repo, + per_page: 100, + }); + const labelExists = (repoLabels.data as GitHubLabel[]).some((label) => label.name?.toLowerCase() === labelName.toLowerCase()); + if (!labelExists) { + await octokit.request("POST /repos/{owner}/{repo}/labels", { + owner, + repo, + name: labelName, + color: "7ee787", + description: "Gittensor contributor context", + }); + created = true; + } + } + + await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/labels", { + owner, + repo, + issue_number: pullNumber, + labels: [labelName], + }); + return { applied: true, created }; +} diff --git a/src/gittensor/api.ts b/src/gittensor/api.ts index 041c5f3af2..4abb0aae92 100644 --- a/src/gittensor/api.ts +++ b/src/gittensor/api.ts @@ -1,4 +1,5 @@ import type { ContributorRepoStatRecord } from "../types"; +import { errorMessage } from "../utils/json"; const GITTENSOR_API_BASE = "https://api.gittensor.io"; const GITTENSOR_MIRROR_API_BASE = "https://mirror.gittensor.io/api/v1"; @@ -33,6 +34,11 @@ type GittensorMinerSummaryResponse = { usdPerDay?: number; }; +type ConfirmedGittensorMinerSummaryResponse = GittensorMinerSummaryResponse & { + githubId: string; + githubUsername: string; +}; + type GittensorMinerDetailResponse = GittensorMinerSummaryResponse & { repositories?: GittensorRepositoryEvaluationResponse[]; }; @@ -146,58 +152,28 @@ export type GittensorContributorSnapshot = { }; export async function fetchGittensorContributorSnapshot(login: string): Promise { + try { + const detection = await fetchOfficialGittensorMiner(login); + return detection.status === "confirmed" ? detection.snapshot : null; + } catch { + return null; + } +} + +export type OfficialGittensorMinerDetection = + | { status: "confirmed"; snapshot: GittensorContributorSnapshot } + | { status: "not_found" } + | { status: "unavailable"; error: string }; + +export async function fetchOfficialGittensorMiner(login: string): Promise { try { const miners = await fetchJson(`${GITTENSOR_API_BASE}/miners`); const normalizedLogin = login.toLowerCase(); const miner = miners.find((candidate) => candidate.githubUsername?.toLowerCase() === normalizedLogin || candidate.githubId === login); - if (!miner?.githubId || !miner.githubUsername) return null; - - const [detailResult, pullRequestsResult, issuesResult] = await Promise.allSettled([ - fetchJson(`${GITTENSOR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}`), - fetchJson(`${GITTENSOR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}/prs`), - fetchJson(`${GITTENSOR_MIRROR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}/issues`), - ]); - const detail = detailResult.status === "fulfilled" ? detailResult.value : {}; - const pullRequests = pullRequestsResult.status === "fulfilled" ? pullRequestsResult.value : []; - const issues = issuesResult.status === "fulfilled" ? issuesResult.value.issues ?? [] : []; - const source = { ...miner, ...detail }; - - return { - source: "gittensor_api", - githubId: miner.githubId, - githubUsername: miner.githubUsername, - uid: source.uid, - hotkey: source.hotkey, - failedReason: source.failedReason, - evaluatedAt: source.evaluatedAt, - updatedAt: source.updatedAt, - isEligible: Boolean(source.isEligible), - credibility: asNumber(source.credibility), - eligibleRepoCount: asNumber(source.eligibleRepoCount), - issueDiscoveryScore: asNumber(source.issueDiscoveryScore), - issueTokenScore: asNumber(source.issueTokenScore), - issueCredibility: asNumber(source.issueCredibility, 1), - isIssueEligible: Boolean(source.isIssueEligible), - issueEligibleRepoCount: asNumber(source.issueEligibleRepoCount), - alphaPerDay: asNumber(source.alphaPerDay), - taoPerDay: asNumber(source.taoPerDay), - usdPerDay: asNumber(source.usdPerDay), - totals: { - pullRequests: asNumber(source.totalPrs), - mergedPullRequests: asNumber(source.totalMergedPrs), - openPullRequests: asNumber(source.totalOpenPrs), - closedPullRequests: asNumber(source.totalClosedPrs), - openIssues: asNumber(source.totalOpenIssues), - closedIssues: asNumber(source.totalClosedIssues), - solvedIssues: asNumber(source.totalSolvedIssues), - validSolvedIssues: asNumber(source.totalValidSolvedIssues), - }, - repositories: (detail.repositories ?? []).map(toRepositoryEvaluation).filter((repo) => repo.pullRequests + repo.openIssues + repo.closedIssues > 0), - pullRequests: pullRequests.map(toPullRequest).filter((pr) => pr.repoFullName && pr.number > 0), - issueLabels: issues.flatMap((issue) => (issue.labels ?? []).flatMap((label) => (label.name ? [label.name] : []))), - }; - } catch { - return null; + if (!miner?.githubId || !miner.githubUsername) return { status: "not_found" }; + return { status: "confirmed", snapshot: await buildGittensorContributorSnapshot({ ...miner, githubId: miner.githubId, githubUsername: miner.githubUsername }) }; + } catch (error) { + return { status: "unavailable", error: errorMessage(error, "unknown Gittensor API error") }; } } @@ -217,6 +193,53 @@ export function contributorRepoStatsFromGittensor(snapshot: GittensorContributor })); } +async function buildGittensorContributorSnapshot(miner: ConfirmedGittensorMinerSummaryResponse): Promise { + const [detailResult, pullRequestsResult, issuesResult] = await Promise.allSettled([ + fetchJson(`${GITTENSOR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}`), + fetchJson(`${GITTENSOR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}/prs`), + fetchJson(`${GITTENSOR_MIRROR_API_BASE}/miners/${encodeURIComponent(miner.githubId)}/issues`), + ]); + const detail = detailResult.status === "fulfilled" ? detailResult.value : {}; + const pullRequests = pullRequestsResult.status === "fulfilled" ? pullRequestsResult.value : []; + const issues = issuesResult.status === "fulfilled" ? issuesResult.value.issues ?? [] : []; + const source = { ...miner, ...detail }; + + return { + source: "gittensor_api", + githubId: miner.githubId, + githubUsername: miner.githubUsername, + uid: source.uid, + hotkey: source.hotkey, + failedReason: source.failedReason, + evaluatedAt: source.evaluatedAt, + updatedAt: source.updatedAt, + isEligible: Boolean(source.isEligible), + credibility: asNumber(source.credibility), + eligibleRepoCount: asNumber(source.eligibleRepoCount), + issueDiscoveryScore: asNumber(source.issueDiscoveryScore), + issueTokenScore: asNumber(source.issueTokenScore), + issueCredibility: asNumber(source.issueCredibility, 1), + isIssueEligible: Boolean(source.isIssueEligible), + issueEligibleRepoCount: asNumber(source.issueEligibleRepoCount), + alphaPerDay: asNumber(source.alphaPerDay), + taoPerDay: asNumber(source.taoPerDay), + usdPerDay: asNumber(source.usdPerDay), + totals: { + pullRequests: asNumber(source.totalPrs), + mergedPullRequests: asNumber(source.totalMergedPrs), + openPullRequests: asNumber(source.totalOpenPrs), + closedPullRequests: asNumber(source.totalClosedPrs), + openIssues: asNumber(source.totalOpenIssues), + closedIssues: asNumber(source.totalClosedIssues), + solvedIssues: asNumber(source.totalSolvedIssues), + validSolvedIssues: asNumber(source.totalValidSolvedIssues), + }, + repositories: (detail.repositories ?? []).map(toRepositoryEvaluation).filter((repo) => repo.pullRequests + repo.openIssues + repo.closedIssues > 0), + pullRequests: pullRequests.map(toPullRequest).filter((pr) => pr.repoFullName && pr.number > 0), + issueLabels: issues.flatMap((issue) => (issue.labels ?? []).flatMap((label) => (label.name ? [label.name] : []))), + }; +} + async function fetchJson(url: string): Promise { const response = await fetch(url, { headers: { diff --git a/src/openapi/schemas.ts b/src/openapi/schemas.ts index c069cdd8dc..96277692c6 100644 --- a/src/openapi/schemas.ts +++ b/src/openapi/schemas.ts @@ -434,8 +434,14 @@ export const RepositorySettingsSchema = z repoFullName: z.string(), commentMode: z.enum(["off", "detected_contributors_only", "all_prs"]), publicSignalLevel: z.enum(["minimal", "standard"]), - checkRunMode: z.enum(["enabled"]), + checkRunMode: z.enum(["off", "enabled"]), checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]), + autoLabelEnabled: z.boolean(), + gittensorLabel: z.string(), + createMissingLabel: z.boolean(), + publicSurface: z.enum(["off", "comment_and_label", "comment_only", "label_only"]), + includeMaintainerAuthors: z.boolean(), + requireLinkedIssue: z.boolean(), backfillEnabled: z.boolean(), privateTrustEnabled: z.boolean(), createdAt: z.string().nullable().optional(), @@ -1020,6 +1026,36 @@ export const RepoIntelligenceSchema = z }) .openapi("RepoIntelligence"); +export const RegistrationReadinessSchema = z + .object({ + repoFullName: z.string(), + generatedAt: z.string(), + ready: z.boolean(), + recommendedRegistrationMode: z.enum(["direct_pr", "issue_discovery", "split"]), + issuePolicy: z.enum(["issue_discovery_enabled", "split_pr_and_issue_discovery_enabled", "direct_pr_requires_linked_issue", "direct_pr_no_issue_required"]), + labelPolicy: z.record(z.unknown()), + maintainerCutReadiness: z.record(z.unknown()), + contributorIntakeHealth: z.record(z.unknown()), + docsCompleteness: z.record(z.unknown()), + blockers: z.array(z.string()), + warnings: z.array(z.string()), + dataQuality: z.record(z.unknown()), + }) + .openapi("RegistrationReadiness"); + +export const GittensorConfigRecommendationSchema = z + .object({ + repoFullName: z.string(), + generatedAt: z.string(), + privateOnly: z.boolean(), + current: z.record(z.unknown()).nullable(), + recommended: z.record(z.unknown()), + reasons: z.array(z.string()), + warnings: z.array(z.string()), + dataQuality: z.record(z.unknown()), + }) + .openapi("GittensorConfigRecommendation"); + export const RewardRiskActionSchema = z .object({ actionKind: z.enum([ diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts index 37a30d64d4..9711d1ec8d 100644 --- a/src/openapi/spec.ts +++ b/src/openapi/spec.ts @@ -39,6 +39,8 @@ import { DecisionPackRefreshNeededSchema, RepoFitRecommendationSchema, RepoDecisionResponseSchema, + GittensorConfigRecommendationSchema, + RegistrationReadinessSchema, RepoIntelligenceSchema, RepoRewardRiskSchema, RegistrySnapshotSchema, @@ -78,6 +80,8 @@ export function buildOpenApiSpec() { registry.register("DecisionPackRefreshNeeded", DecisionPackRefreshNeededSchema); registry.register("RepoDecisionResponse", RepoDecisionResponseSchema); registry.register("RepoIntelligence", RepoIntelligenceSchema); + registry.register("RegistrationReadiness", RegistrationReadinessSchema); + registry.register("GittensorConfigRecommendation", GittensorConfigRecommendationSchema); registry.register("RepoFitRecommendation", RepoFitRecommendationSchema); registry.register("PreflightResult", PreflightResultSchema); registry.register("LocalDiffPreflightResult", LocalDiffPreflightResultSchema); @@ -159,7 +163,7 @@ export function buildOpenApiSpec() { method: "get", path: "/v1/readiness", responses: { - 200: { description: "Operational readiness summary for private beta and public-review preparation", content: { "application/json": { schema: ReadinessSchema } } }, + 200: { description: "Operational readiness summary for hosted API, signal fidelity, and public-review preparation", content: { "application/json": { schema: ReadinessSchema } } }, }, }); registry.registerPath({ @@ -209,6 +213,20 @@ export function buildOpenApiSpec() { 200: { description: "Canonical repository intelligence bundle", content: { "application/json": { schema: RepoIntelligenceSchema } } }, }, }); + registry.registerPath({ + method: "get", + path: "/v1/repos/{owner}/{repo}/registration-readiness", + responses: { + 200: { description: "Gittensor registration readiness signal for repo owners", content: { "application/json": { schema: RegistrationReadinessSchema } } }, + }, + }); + registry.registerPath({ + method: "get", + path: "/v1/repos/{owner}/{repo}/gittensor-config-recommendation", + responses: { + 200: { description: "Private Gittensor config recommendation for repo owners", content: { "application/json": { schema: GittensorConfigRecommendationSchema } } }, + }, + }); registry.registerPath({ method: "get", path: "/v1/repos/{owner}/{repo}/settings", diff --git a/src/queue/processors.ts b/src/queue/processors.ts index d42f5e78d4..3cdb295d21 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -4,7 +4,6 @@ import { getLatestRepoGithubTotalsSnapshot, getRepository, getRepositorySettings, - listCheckSummaries, listAllIssues, listAllPullRequests, listContributorIssues, @@ -14,8 +13,6 @@ import { listIssueSignalSample, listOtherOpenPullRequests, listOpenPullRequests, - listPullRequestFiles, - listPullRequestReviews, listPullRequests, listRecentMergedPullRequests, listRepoLabels, @@ -44,9 +41,10 @@ import { refreshContributorActivity, refreshInstallationHealth, } from "../github/backfill"; -import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot } from "../gittensor/api"; +import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot, fetchOfficialGittensorMiner, type GittensorContributorSnapshot } from "../gittensor/api"; import { createOrUpdateCheckRun, getInstallationId } from "../github/app"; import { createOrUpdatePrIntelligenceComment } from "../github/comments"; +import { ensurePullRequestLabel } from "../github/labels"; import { fetchPublicContributorProfile } from "../github/public"; import { refreshRegistry } from "../registry/sync"; import { buildIssueAdvisory, buildPullRequestAdvisory } from "../rules/advisory"; @@ -70,10 +68,9 @@ import { buildPublicPrIntelligenceComment, buildQueueHealth, detectGittensorContributor, - shouldPublishPrIntelligenceComment, } from "../signals/engine"; -import { buildPullRequestReviewability } from "../signals/reward-risk"; import type { ContributorEvidenceRecord, GitHubWebhookPayload, JobMessage, JsonValue } from "../types"; +import { errorMessage } from "../utils/json"; export async function processJob(env: Env, message: JobMessage): Promise { switch (message.type) { @@ -431,42 +428,28 @@ async function processGitHubWebhook(env: Env, deliveryId: string, eventName: str if (payload.repository) await upsertRepositoryFromGitHub(env, payload.repository, installationId ?? undefined); if (payload.repository?.full_name && payload.pull_request) { - const pr = await upsertPullRequestFromGitHub(env, payload.repository.full_name, payload.pull_request); - const repo = await getRepository(env, payload.repository.full_name); - const [otherOpenPullRequests, issues, pullRequests, files, reviews, checks, recentMergedPullRequests] = await Promise.all([ - listOtherOpenPullRequests(env, payload.repository.full_name, pr.number), - listIssues(env, payload.repository.full_name), - listPullRequests(env, payload.repository.full_name), - listPullRequestFiles(env, payload.repository.full_name, pr.number), - listPullRequestReviews(env, payload.repository.full_name, pr.number), - listCheckSummaries(env, payload.repository.full_name, pr.number), - listRecentMergedPullRequests(env, payload.repository.full_name), + const repoFullName = payload.repository.full_name; + const pr = await upsertPullRequestFromGitHub(env, repoFullName, payload.pull_request); + const [repo, settings, otherOpenPullRequests] = await Promise.all([ + getRepository(env, repoFullName), + getRepositorySettings(env, repoFullName), + listOtherOpenPullRequests(env, repoFullName, pr.number), ]); - const reviewability = buildPullRequestReviewability({ - repo, - pullRequest: pr, - issues, - pullRequests, - files, - reviews, - checks, - recentMergedPullRequests, - repoFullName: payload.repository.full_name, - pullNumber: pr.number, - }); - const advisory = buildPullRequestAdvisory(repo, pr, { otherOpenPullRequests, reviewabilityText: reviewability.privateSummary }); + const advisory = buildPullRequestAdvisory(repo, pr, { otherOpenPullRequests, requireLinkedIssue: settings.requireLinkedIssue }); await persistAdvisory(env, advisory); - if (installationId && advisory.headSha) await createOrUpdateCheckRun(env, installationId, payload.repository.full_name, advisory); if (installationId) { - await maybePublishPrIntelligenceComment(env, installationId, payload.repository.full_name, pr, repo).catch((error) => { + await maybePublishPrPublicSurface(env, installationId, repoFullName, pr, repo, settings, advisory, { + deliveryId, + authorType: payload.pull_request.user?.type, + }).catch((error) => { console.error( JSON.stringify({ level: "warn", - event: "pr_intelligence_comment_failed", + event: "pr_public_surface_failed", deliveryId, repository: payload.repository?.full_name, pullNumber: pr.number, - error: error instanceof Error ? error.message : "unknown error", + error: errorMessage(error), }), ); }); @@ -498,38 +481,66 @@ async function processGitHubWebhook(env: Env, deliveryId: string, eventName: str repositoryFullName: payload.repository?.full_name, payloadHash: "processed", status: "error", - errorSummary: error instanceof Error ? error.message : "unknown error", + errorSummary: errorMessage(error), }); throw error; } } -async function maybePublishPrIntelligenceComment( +async function maybePublishPrPublicSurface( env: Env, installationId: number, repoFullName: string, pr: Awaited>, repo: Awaited>, + settings: Awaited>, + advisory: Awaited>, + webhook: { deliveryId: string; authorType?: string | undefined }, ): Promise { - const settings = await getRepositorySettings(env, repoFullName); - if (settings.commentMode === "off") return; + if (!hasVisiblePrSurface(settings)) return; const author = pr.authorLogin; - if (!author) return; + if (!author) { + await auditPrVisibilitySkip(env, repoFullName, pr.number, null, "missing_author", webhook.deliveryId); + return; + } + if (webhook.authorType === "Bot" || /\[bot\]$/i.test(author)) { + await auditPrVisibilitySkip(env, repoFullName, pr.number, author, "bot_author", webhook.deliveryId); + return; + } + if (!settings.includeMaintainerAuthors && pr.authorAssociation && ["OWNER", "MEMBER", "COLLABORATOR"].includes(pr.authorAssociation)) { + await auditPrVisibilitySkip(env, repoFullName, pr.number, author, "maintainer_author", webhook.deliveryId); + return; + } + + const official = await fetchOfficialGittensorMiner(author); + if (official.status === "unavailable") { + await recordAuditEvent(env, { + eventType: "github_app.miner_detection_unavailable", + actor: author, + targetKey: `${repoFullName}#${pr.number}`, + outcome: "error", + detail: official.error, + metadata: { deliveryId: webhook.deliveryId }, + }); + return; + } + if (official.status === "not_found") { + await auditPrVisibilitySkip(env, repoFullName, pr.number, author, "not_official_gittensor_miner", webhook.deliveryId); + return; + } - const [contributorPullRequests, contributorIssues, repoIssues, repoPullRequests, github, cachedRepoStats, gittensorSnapshot] = await Promise.all([ + const [contributorPullRequests, contributorIssues, repoIssues, repoPullRequests, github, cachedRepoStats] = await Promise.all([ listContributorPullRequests(env, author), listContributorIssues(env, author), listIssues(env, repoFullName), listPullRequests(env, repoFullName), fetchPublicContributorProfile(author), listContributorRepoStats(env, author), - fetchGittensorContributorSnapshot(author), ]); - const repoStats = authoritativeContributorRepoStats(gittensorSnapshot, cachedRepoStats); - const detection = detectGittensorContributor(author, pr, contributorPullRequests, contributorIssues, repoStats); - if (!shouldPublishPrIntelligenceComment(settings, detection)) return; + const repoStats = authoritativeContributorRepoStats(official.snapshot, cachedRepoStats); + const detection = officialGittensorContributorDetection(official.snapshot, pr, contributorPullRequests, contributorIssues, repoStats); - const profile = buildContributorProfile(author, github, contributorPullRequests, contributorIssues, repoStats, gittensorSnapshot); + const profile = buildContributorProfile(author, github, contributorPullRequests, contributorIssues, repoStats, official.snapshot); const collisions = buildCollisionReport(repoFullName, repoIssues, repoPullRequests); const queueHealth = buildQueueHealth(repo, repoIssues, repoPullRequests, collisions); const preflight = buildPreflightResult( @@ -546,17 +557,96 @@ async function maybePublishPrIntelligenceComment( repoIssues, repoPullRequests, ); - const body = buildPublicPrIntelligenceComment({ - repo, - pr, - profile, - detection, - queueHealth, - collisions, - preflight, - settings, + if (shouldPublishPrComment(settings)) { + const body = buildPublicPrIntelligenceComment({ + repo, + pr, + profile, + detection, + queueHealth, + collisions, + preflight, + settings, + }); + await createOrUpdatePrIntelligenceComment(env, installationId, repoFullName, pr.number, body); + } + if (shouldApplyPrLabel(settings)) { + await ensurePullRequestLabel(env, installationId, repoFullName, pr.number, settings.gittensorLabel, { + createMissingLabel: settings.createMissingLabel, + }); + } + if (settings.checkRunMode === "enabled" && advisory.headSha) { + await createOrUpdateCheckRun(env, installationId, repoFullName, { + ...advisory, + conclusion: "success", + severity: "info", + title: "Gittensory context posted", + summary: "Gittensory posted public-safe contributor context.", + findings: [], + }); + } + await recordAuditEvent(env, { + eventType: "github_app.pr_public_surface_published", + actor: author, + targetKey: `${repoFullName}#${pr.number}`, + outcome: "completed", + metadata: { + deliveryId: webhook.deliveryId, + publicSurface: settings.publicSurface, + label: shouldApplyPrLabel(settings) ? settings.gittensorLabel : null, + checkRunMode: settings.checkRunMode, + }, }); - await createOrUpdatePrIntelligenceComment(env, installationId, repoFullName, pr.number, body); +} + +function hasVisiblePrSurface(settings: Awaited>): boolean { + return settings.publicSurface !== "off" || settings.checkRunMode === "enabled"; +} + +function shouldPublishPrComment(settings: Awaited>): boolean { + if (settings.commentMode === "off") return false; + return settings.publicSurface === "comment_and_label" || settings.publicSurface === "comment_only"; +} + +function shouldApplyPrLabel(settings: Awaited>): boolean { + return settings.autoLabelEnabled && (settings.publicSurface === "comment_and_label" || settings.publicSurface === "label_only"); +} + +async function auditPrVisibilitySkip( + env: Env, + repoFullName: string, + pullNumber: number, + author: string | null, + reason: string, + deliveryId: string, +): Promise { + await recordAuditEvent(env, { + eventType: "github_app.pr_visibility_skipped", + actor: author, + targetKey: `${repoFullName}#${pullNumber}`, + outcome: "completed", + detail: reason, + metadata: { deliveryId }, + }); +} + +function officialGittensorContributorDetection( + snapshot: GittensorContributorSnapshot, + currentPr: Awaited>, + pullRequests: Awaited>, + issues: Awaited>, + repoStats: Awaited>, +) { + const cached = detectGittensorContributor(snapshot.githubUsername, currentPr, pullRequests, issues, repoStats); + return { + ...cached, + detected: true, + source: "official_gittensor_api" as const, + reason: "Official Gittensor API confirms this GitHub user.", + priorPullRequests: Math.max(cached.priorPullRequests, snapshot.totals.pullRequests), + priorMergedPullRequests: Math.max(cached.priorMergedPullRequests, snapshot.totals.mergedPullRequests), + priorIssues: Math.max(cached.priorIssues, snapshot.totals.openIssues + snapshot.totals.closedIssues), + }; } function authoritativeContributorRepoStats( diff --git a/src/registry/sync.ts b/src/registry/sync.ts index 06b07fa5ff..cd645fe2f2 100644 --- a/src/registry/sync.ts +++ b/src/registry/sync.ts @@ -2,7 +2,7 @@ import { and, desc, eq, notInArray } from "drizzle-orm"; import { getDb } from "../db/client"; import { registrySnapshots, repositories, syncRuns } from "../db/schema"; import type { RegistrySnapshot } from "../types"; -import { jsonString, nowIso, repoParts } from "../utils/json"; +import { errorMessage, jsonString, nowIso, repoParts } from "../utils/json"; import { normalizeRegistryPayload } from "./normalize"; const API_CANDIDATES = [ @@ -59,7 +59,7 @@ export async function refreshRegistry(env: Env): Promise { .where(eq(syncRuns.id, syncId)); return snapshot; } catch (error) { - warnings.push(`Registry probe failed: ${url} (${error instanceof Error ? error.message : "unknown error"})`); + warnings.push(`Registry probe failed: ${url} (${errorMessage(error)})`); } } throw new Error("No registry source returned usable data."); @@ -69,7 +69,7 @@ export async function refreshRegistry(env: Env): Promise { .set({ status: "error", warningsJson: jsonString(warnings), - errorSummary: error instanceof Error ? error.message : "unknown error", + errorSummary: errorMessage(error), completedAt: nowIso(), }) .where(eq(syncRuns.id, syncId)); diff --git a/src/rules/advisory.ts b/src/rules/advisory.ts index c7a529610f..c63e8194b3 100644 --- a/src/rules/advisory.ts +++ b/src/rules/advisory.ts @@ -28,7 +28,7 @@ export function buildRepositoryAdvisory(repo: RepositoryRecord | null, fullName: export function buildPullRequestAdvisory( repo: RepositoryRecord | null, pr: PullRequestRecord | null, - context: { otherOpenPullRequests?: PullRequestRecord[]; reviewabilityText?: string } = {}, + context: { otherOpenPullRequests?: PullRequestRecord[]; requireLinkedIssue?: boolean } = {}, ): Advisory { const repoFullName = pr?.repoFullName ?? repo?.fullName ?? "unknown/unknown"; const targetKey = pr ? `${repoFullName}#${pr.number}` : `${repoFullName}#unknown`; @@ -53,15 +53,7 @@ export function buildPullRequestAdvisory( action: "Re-deliver the webhook or wait for the next sync.", }); } else { - addPullRequestFindings(repo, pr, findings, context.otherOpenPullRequests ?? []); - } - if (context.reviewabilityText) { - findings.push({ - code: "private_reviewability_context", - severity: "info", - title: "Private reviewability context", - detail: context.reviewabilityText, - }); + addPullRequestFindings(repo, pr, findings, context.otherOpenPullRequests ?? [], Boolean(context.requireLinkedIssue)); } return advisory("pull_request", targetKey, repoFullName, findings, "Pull request advisory generated.", pr?.number, undefined, pr?.headSha ?? undefined); } @@ -94,15 +86,10 @@ export function buildIssueAdvisory(repo: RepositoryRecord | null, issue: IssueRe } export function formatCheckRunOutput(advisoryResult: Advisory): { title: string; summary: string; text: string } { - const sections = advisoryResult.findings.map((finding) => { - const publicText = finding.publicText ?? finding.detail; - const action = finding.action ? `\nAction: ${finding.action}` : ""; - return `### ${finding.title}\n${publicText}${action}`; - }); return { - title: advisoryResult.title, - summary: advisoryResult.summary, - text: sections.length > 0 ? sections.join("\n\n") : "No advisory findings.", + title: advisoryResult.conclusion === "success" ? "Gittensory context checked" : "Gittensory context posted", + summary: "Gittensory public check output is intentionally minimal. Detailed maintainer context is available only through private API/MCP surfaces.", + text: "No detailed findings are published in check runs.", }; } @@ -159,6 +146,7 @@ function addPullRequestFindings( pr: PullRequestRecord, findings: AdvisoryFinding[], otherOpenPullRequests: PullRequestRecord[], + requireLinkedIssue: boolean, ): void { if (pr.state !== "open") { findings.push({ @@ -168,7 +156,7 @@ function addPullRequestFindings( detail: `The pull request state is ${pr.state}.`, }); } - if (pr.linkedIssues.length === 0) { + if (pr.linkedIssues.length === 0 && requireLinkedIssue) { findings.push({ code: "missing_linked_issue", severity: "warning", @@ -261,7 +249,7 @@ function advisory( ): Advisory { const severity = highestSeverity(findings); const conclusion = conclusionForSeverity(severity, findings); - const title = conclusion === "success" ? "Gittensory advisory passed" : "Gittensory advisory needs review"; + const title = conclusion === "success" ? "Gittensory advisory passed" : "Gittensory advisory available"; return { id: crypto.randomUUID(), targetType, diff --git a/src/scoring/model.ts b/src/scoring/model.ts index 36406d92de..589209c8a1 100644 --- a/src/scoring/model.ts +++ b/src/scoring/model.ts @@ -4,7 +4,7 @@ import { } from "../db/repositories"; import { getLatestRegistrySnapshot } from "../registry/sync"; import type { JsonValue, ScoringModelSnapshotRecord } from "../types"; -import { nowIso } from "../utils/json"; +import { errorMessage, nowIso } from "../utils/json"; export const DEFAULT_SCORING_CONSTANTS: Record = { OSS_EMISSION_SHARE: 0.9, @@ -95,10 +95,10 @@ export function parsePythonNumberConstants(source: string): Record }; } catch (error) { - return { ok: false, error: error instanceof Error ? error.message : "unknown error" }; + return { ok: false, error: errorMessage(error) }; } } diff --git a/src/signals/engine.ts b/src/signals/engine.ts index a64be8c086..b16da8b95e 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -410,6 +410,7 @@ export type BountyAdvisory = { export type ContributorDetection = { detected: boolean; reason: string; + source?: "official_gittensor_api" | "github_cache"; priorPullRequests: number; priorMergedPullRequests: number; priorIssues: number; @@ -1053,8 +1054,8 @@ export function detectGittensorContributor( export function shouldPublishPrIntelligenceComment(settings: RepositorySettings, detection: ContributorDetection): boolean { if (settings.commentMode === "off") return false; - if (settings.commentMode === "all_prs") return true; - return detection.detected; + if (settings.publicSurface !== "comment_and_label" && settings.publicSurface !== "comment_only") return false; + return detection.detected && detection.source === "official_gittensor_api"; } export function buildContributorOpportunities( @@ -2202,9 +2203,16 @@ export function buildPublicPrIntelligenceComment(args: { }): string { const publicFindings = args.preflight.findings .filter((finding) => finding.severity !== "critical") + .filter((finding) => args.settings.requireLinkedIssue || finding.code !== "missing_linked_issue") + .filter((finding) => !containsPrivatePublicTerm([finding.code, finding.title, finding.detail, finding.publicText, finding.action].filter(Boolean).join(" "))) .slice(0, args.settings.publicSignalLevel === "minimal" ? 2 : 5); const collisionCount = args.collisions.clusters.length; - const linkedIssues = args.pr.linkedIssues.length > 0 ? args.pr.linkedIssues.map((issue) => `#${issue}`).join(", ") : "None detected"; + const linkedIssues = + args.pr.linkedIssues.length > 0 + ? args.pr.linkedIssues.map((issue) => `#${issue}`).join(", ") + : args.settings.requireLinkedIssue + ? "None detected" + : "Not required by this repo setting"; const roleContext = buildRoleContext({ login: args.pr.authorLogin ?? args.profile.login, repo: args.repo, @@ -2215,20 +2223,21 @@ export function buildPublicPrIntelligenceComment(args: { }); const nextSteps = [ ...(roleContext.maintainerLane ? ["Treat this as maintainer-lane context rather than normal contributor-lane activity."] : []), - ...(args.pr.linkedIssues.length === 0 ? ["Link the issue being solved, or explain why this is a no-issue PR."] : []), + ...(args.settings.requireLinkedIssue && args.pr.linkedIssues.length === 0 ? ["Link the issue being solved, or explain why this is a no-issue PR."] : []), ...(collisionCount > 0 ? ["Check overlapping issues/PRs before review continues."] : []), ...(publicFindings.length > 0 ? publicFindings.flatMap((finding) => (finding.action ? [finding.action] : [])) : []), - ]; + ].filter((step) => !containsPrivatePublicTerm(step)); return [ "", "## Gittensory contribution context", "", - "_Advisory context generated from public GitHub metadata and Gittensory's registered-repo cache. This is not an endorsement or compensation estimate._", + "_Advisory context generated from public GitHub metadata and Gittensory's registered-repo cache. This is not an endorsement._", "", "### Contributor context", `- Author: \`${args.pr.authorLogin ?? "unknown"}\``, + `- Confirmed Gittensor miner: ${args.detection.source === "official_gittensor_api" ? "yes" : "not confirmed"}`, `- Role context: ${roleContext.role}${roleContext.maintainerLane ? " (maintainer lane)" : ""}`, - `- Registered-repo signal: ${args.detection.detected ? args.detection.reason : "No prior cached registered-repo activity detected."}`, + `- Gittensory signal: ${args.detection.detected ? args.detection.reason : "No confirmed Gittensor miner activity detected."}`, `- Prior cached PRs/issues: ${args.detection.priorPullRequests} PR(s), ${args.detection.priorIssues} issue(s)`, `- Public profile languages: ${args.profile.github.topLanguages.length > 0 ? args.profile.github.topLanguages.join(", ") : "not available"}`, "", @@ -2251,6 +2260,10 @@ export function buildPublicPrIntelligenceComment(args: { ].join("\n"); } +function containsPrivatePublicTerm(value: string): boolean { + return /\b(reward|payout|farming|wallet|hotkey|trust score|raw trust|estimated score|scoreability|likely_duplicate|reviewability\s*\d|\/100)\b/i.test(value); +} + function issueItem(issue: IssueRecord): CollisionItem { return { type: "issue", diff --git a/src/types.ts b/src/types.ts index 39029a4f40..3e223b05bf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -120,6 +120,7 @@ export type GitHubPullRequestPayload = { updated_at?: string | null; user?: { login?: string; + type?: string; }; author_association?: string; head?: { @@ -267,8 +268,14 @@ export type RepositorySettings = { repoFullName: string; commentMode: "off" | "detected_contributors_only" | "all_prs"; publicSignalLevel: "minimal" | "standard"; - checkRunMode: "enabled"; + checkRunMode: "off" | "enabled"; checkRunDetailLevel: "minimal" | "standard" | "deep"; + autoLabelEnabled: boolean; + gittensorLabel: string; + createMissingLabel: boolean; + publicSurface: "off" | "comment_and_label" | "comment_only" | "label_only"; + includeMaintainerAuthors: boolean; + requireLinkedIssue: boolean; backfillEnabled: boolean; privateTrustEnabled: boolean; createdAt?: string | null | undefined; diff --git a/src/utils/json.ts b/src/utils/json.ts index 0e0e1216a2..16d7222743 100644 --- a/src/utils/json.ts +++ b/src/utils/json.ts @@ -15,6 +15,15 @@ export function nowIso(): string { return new Date().toISOString(); } +export function errorMessage(error: unknown, fallback = "unknown error"): string { + return error instanceof Error && error.message ? error.message : fallback; +} + +export function strippedErrorMessage(error: unknown, fallback: string): string { + const message = errorMessage(error, ""); + return message.replace(/^Error: /, "") || fallback; +} + export function normalizeRepoFullName(value: string): string { return value.trim(); } diff --git a/test/helpers/d1.ts b/test/helpers/d1.ts index 46afa2962c..38d4e9aec6 100644 --- a/test/helpers/d1.ts +++ b/test/helpers/d1.ts @@ -34,8 +34,8 @@ export class TestD1Database { return rows.map((row) => columns.map((column) => row[column])) as T[]; }, async run() { - statement.run(...bound); - return { success: true, meta: {}, results: [] }; + const result = statement.run(...bound); + return { success: true, meta: { changes: Number(result.changes ?? 0) }, results: [] }; }, }; return api; diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index 22a084907c..1337709f4d 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -17,6 +17,7 @@ import { upsertPullRequestFromGitHub, persistScoringModelSnapshot, upsertRepositoryFromGitHub, + upsertRepositorySettings, } from "../../src/db/repositories"; import { createApp } from "../../src/api/routes"; import { normalizeRegistryPayload } from "../../src/registry/normalize"; @@ -32,6 +33,9 @@ describe("api routes", () => { const app = createApp(); const env = createTestEnv(); + const preflight = await app.request("/v1/repos", { method: "OPTIONS", headers: { origin: "https://gittensory.aethereal.dev" } }, env); + expect(preflight.status).toBe(204); + const health = await app.request("/health", {}, env); expect(health.status).toBe(200); await expect(health.json()).resolves.toMatchObject({ status: "ok", service: "gittensory-api" }); @@ -232,6 +236,30 @@ describe("api routes", () => { dataQuality: expect.any(Object), }); + const registrationReadiness = await app.request("/v1/repos/entrius/allways-ui/registration-readiness", { headers: apiHeaders(env) }, env); + expect(registrationReadiness.status).toBe(200); + await expect(registrationReadiness.json()).resolves.toMatchObject({ + repoFullName: "entrius/allways-ui", + recommendedRegistrationMode: "direct_pr", + issuePolicy: "direct_pr_no_issue_required", + labelPolicy: { label: "gittensor" }, + docsCompleteness: { status: "repo_docs_not_crawled" }, + dataQuality: expect.any(Object), + }); + + const configRecommendation = await app.request("/v1/repos/entrius/allways-ui/gittensor-config-recommendation", { headers: apiHeaders(env) }, env); + expect(configRecommendation.status).toBe(200); + await expect(configRecommendation.json()).resolves.toMatchObject({ + repoFullName: "entrius/allways-ui", + privateOnly: true, + recommended: { + participationMode: "direct_pr", + issueDiscoveryShare: 0, + confirmedMinerLabel: "gittensor", + }, + reasons: expect.arrayContaining([expect.stringMatching(/Direct-PR|Direct-PR|Direct/i)]), + }); + for (const path of [ "/v1/repos/entrius/allways-ui/queue-health", "/v1/repos/entrius/allways-ui/collisions", @@ -419,6 +447,37 @@ describe("api routes", () => { ); expect(localBranchWithMcpToken.status).toBe(200); + const localBranchWithHeadRefOnly = await app.request( + "/v1/local/branch-analysis", + { + method: "POST", + headers: apiHeaders(env), + body: JSON.stringify({ + login: "oktofeesh1", + repoFullName: "entrius/allways-ui", + headRef: "head-only", + changedFiles: [{ path: "src/cache.ts", additions: 1, deletions: 0 }], + }), + }, + env, + ); + expect(localBranchWithHeadRefOnly.status).toBe(200); + + const localBranchWithLocalTarget = await app.request( + "/v1/local/branch-analysis", + { + method: "POST", + headers: apiHeaders(env), + body: JSON.stringify({ + login: "oktofeesh1", + repoFullName: "entrius/allways-ui", + changedFiles: [{ path: "src/cache.ts", additions: 1, deletions: 0 }], + }), + }, + env, + ); + expect(localBranchWithLocalTarget.status).toBe(200); + const sourceContentRejected = await app.request( "/v1/local/branch-analysis", { @@ -486,8 +545,9 @@ describe("api routes", () => { expect(installationHealth.status).toBe(200); await expect(installationHealth.json()).resolves.toMatchObject({ installationId: 123, - requiredPermissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, - permissionRemediation: expect.arrayContaining([expect.objectContaining({ permission: "checks", ok: true })]), + requiredPermissions: { metadata: "read", pull_requests: "read", issues: "write" }, + optionalPermissions: { checks: "write" }, + permissionRemediation: expect.arrayContaining([expect.objectContaining({ permission: "issues", ok: true })]), repairSteps: ["No repair needed."], }); @@ -680,6 +740,19 @@ describe("api routes", () => { const app = createApp(); const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); await seedSignalData(env); + await upsertInstallationHealth(env, { + installationId: 123, + accountLogin: "entrius", + repositorySelection: "selected", + installedReposCount: 1, + registeredInstalledCount: 1, + status: "needs_attention", + missingPermissions: ["issues"], + missingEvents: [], + permissions: { metadata: "read", pull_requests: "read" }, + events: ["issues", "pull_request", "repository"], + checkedAt: "2026-05-23T00:00:00.000Z", + }); await upsertRepoSyncSegment(env, { repoFullName: "entrius/allways-ui", segment: "open_pull_requests", @@ -704,6 +777,18 @@ describe("api routes", () => { completedAt: "2026-05-23T00:00:00.000Z", warnings: ["secondary rate limit"], }); + await upsertRepoSyncSegment(env, { + repoFullName: "entrius/allways-ui", + segment: "check_summaries", + status: "stale", + sourceKind: "github", + mode: "full", + fetchedCount: 2, + expectedCount: 2, + pageCount: 1, + completedAt: "2026-05-23T00:00:00.000Z", + warnings: ["old check data"], + }); const readiness = await app.request("/v1/readiness", { headers: apiHeaders(env) }, env); expect(readiness.status).toBe(200); @@ -715,15 +800,18 @@ describe("api routes", () => { status: "blocked", cappedRepos: ["entrius/allways-ui"], rateLimitedRepos: ["entrius/allways-ui"], + staleRepos: ["entrius/allways-ui"], nextRecoverableAt: "2026-05-27T00:00:00.000Z", }, cappedRepos: ["entrius/allways-ui"], rateLimitedRepos: ["entrius/allways-ui"], + staleRepos: ["entrius/allways-ui"], nextRecoverableAt: "2026-05-27T00:00:00.000Z", githubBackfill: { cappedSegments: [expect.objectContaining({ repoFullName: "entrius/allways-ui", segment: "open_pull_requests", nextCursor: "2" })], rateLimitedSegments: [expect.objectContaining({ repoFullName: "entrius/allways-ui", segment: "open_issues", rateLimitResetAt: "2026-05-27T00:00:00.000Z" })], }, + warnings: expect.arrayContaining([expect.stringContaining("repo sync(s) are stale"), "One or more GitHub App installations need attention."]), }); const syncStatus = await app.request("/v1/sync/status", { headers: apiHeaders(env) }, env); @@ -735,6 +823,28 @@ describe("api routes", () => { expect.objectContaining({ repoFullName: "entrius/allways-ui", segment: "open_issues", status: "rate_limited" }), ]), }); + + const refreshingEnv = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); + await seedSignalData(refreshingEnv); + await upsertRepoSyncSegment(refreshingEnv, { + repoFullName: "entrius/allways-ui", + segment: "labels", + status: "running", + sourceKind: "github", + mode: "resume", + fetchedCount: 2, + expectedCount: 2, + pageCount: 1, + completedAt: "2026-05-23T00:00:00.000Z", + warnings: [], + }); + const refreshingReadiness = await app.request("/v1/readiness", { headers: apiHeaders(refreshingEnv) }, refreshingEnv); + expect(refreshingReadiness.status).toBe(200); + await expect(refreshingReadiness.json()).resolves.toMatchObject({ + readyForPublicReview: true, + coreSignalFidelity: { status: "complete", refreshingRepos: ["entrius/allways-ui"] }, + warnings: expect.arrayContaining([expect.stringContaining("repo(s) are refreshing")]), + }); }); it("serves private MCP tool listing and tool calls", async () => { @@ -1227,6 +1337,313 @@ describe("api routes", () => { expect(JSON.stringify(missingBountyPayload)).toMatch(/Bounty not found|error|isError/i); }, 15_000); + it("covers registration-readiness policy variants for repo-owner launch planning", async () => { + const app = createApp(); + const env = createTestEnv(); + await seedSignalData(env); + + const unknownReadiness = await app.request("/v1/repos/JSONbored/gittensory/registration-readiness", { headers: apiHeaders(env) }, env); + expect(unknownReadiness.status).toBe(200); + await expect(unknownReadiness.json()).resolves.toMatchObject({ + ready: false, + recommendedRegistrationMode: "direct_pr", + blockers: expect.arrayContaining(["Repository is not registered in the latest Gittensory registry snapshot."]), + }); + + await upsertRepositorySettings(env, { + repoFullName: "entrius/allways-ui", + publicSurface: "off", + requireLinkedIssue: true, + autoLabelEnabled: false, + createMissingLabel: false, + gittensorLabel: "gittensor-miner", + }); + const directReadiness = await app.request("/v1/repos/entrius/allways-ui/registration-readiness", { headers: apiHeaders(env) }, env); + expect(directReadiness.status).toBe(200); + await expect(directReadiness.json()).resolves.toMatchObject({ + issuePolicy: "direct_pr_requires_linked_issue", + labelPolicy: { autoLabelEnabled: false, label: "gittensor-miner", createMissingLabel: false }, + warnings: expect.arrayContaining(["GitHub App public surface is disabled; maintainers will not get comment/label assistance."]), + }); + + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { + "entrius/allways-ui": { + emission_share: 0.01107, + issue_discovery_share: 0.01, + label_multipliers: {}, + trusted_label_pipeline: false, + maintainer_cut: 0.03, + }, + }, + { kind: "raw-github", url: "https://example.test/issue-discovery-registry.json" }, + "2026-05-26T00:00:00.000Z", + ), + ); + const issueDiscoveryReadiness = await app.request("/v1/repos/entrius/allways-ui/registration-readiness", { headers: apiHeaders(env) }, env); + expect(issueDiscoveryReadiness.status).toBe(200); + await expect(issueDiscoveryReadiness.json()).resolves.toMatchObject({ + recommendedRegistrationMode: "split", + issuePolicy: "split_pr_and_issue_discovery_enabled", + }); + + const recommendation = await app.request("/v1/repos/entrius/allways-ui/gittensor-config-recommendation", { headers: apiHeaders(env) }, env); + expect(recommendation.status).toBe(200); + await expect(recommendation.json()).resolves.toMatchObject({ + privateOnly: true, + current: { issueDiscoveryShare: 0.01, maintainerCut: 0.03 }, + recommended: { + requireLinkedIssue: true, + confirmedMinerLabel: "gittensor-miner", + publicSurface: "off", + }, + reasons: expect.arrayContaining([expect.stringMatching(/issue discovery|Direct-PR/i)]), + }); + }); + + it("covers modern route fallback branches, stale snapshots, and launch-readiness edge policies", async () => { + const queued: unknown[] = []; + const app = createApp(); + const env = createTestEnv({ + JOBS: { + async send(message: unknown) { + queued.push(message); + }, + } as unknown as Queue, + }); + + const deviceStart = await app.request("/v1/auth/github/device/start", { method: "POST" }, env); + expect(deviceStart.status).toBe(503); + const deviceMissingCode = await app.request("/v1/auth/github/device/poll", { method: "POST", body: JSON.stringify({}) }, env); + expect(deviceMissingCode.status).toBe(400); + const devicePollUnconfigured = await app.request("/v1/auth/github/device/poll", { method: "POST", body: JSON.stringify({ deviceCode: "abc" }) }, env); + expect(devicePollUnconfigured.status).toBe(503); + const sessionMissingToken = await app.request("/v1/auth/github/session", { method: "POST", body: JSON.stringify({}) }, env); + expect(sessionMissingToken.status).toBe(400); + vi.stubGlobal("fetch", async () => new Response("bad token", { status: 401 })); + const sessionRejected = await app.request("/v1/auth/github/session", { method: "POST", body: JSON.stringify({ githubToken: "bad" }) }, env); + expect(sessionRejected.status).toBe(401); + const unauthenticatedSession = await app.request("/v1/auth/session", {}, env); + expect(unauthenticatedSession.status).toBe(401); + const logout = await app.request("/v1/auth/logout", { method: "POST" }, env); + await expect(logout.json()).resolves.toMatchObject({ ok: true, revoked: false }); + + await persistSignalSnapshot(env, { + id: "stale-pack", + signalType: "contributor-decision-pack", + targetKey: "stale-user", + payload: { + status: "ready", + source: "computed", + login: "stale-user", + generatedAt: "2026-01-01T00:00:00.000Z", + stale: false, + scoringModelSnapshotId: "scoring-1", + profile: {}, + outcomeHistory: {}, + roleContexts: [], + repoDecisions: [], + topActions: [], + cleanupFirst: [], + pursueRepos: [], + avoidRepos: [], + maintainerLaneRepos: [], + scoreBlockers: [], + dataQuality: { signalFidelity: { status: "degraded" } }, + summary: "stale", + nextActions: [], + } as never, + generatedAt: "2026-01-01T00:00:00.000Z", + }); + const staleDecisionPack = await app.request("/v1/contributors/stale-user/decision-pack", { headers: apiHeaders(env) }, env); + expect(staleDecisionPack.status).toBe(202); + await expect(staleDecisionPack.json()).resolves.toMatchObject({ + status: "needs_snapshot_refresh", + reason: "stale_snapshot", + staleSnapshot: { generatedAt: "2026-01-01T00:00:00.000Z" }, + dataQuality: { signalFidelity: { status: "degraded" } }, + }); + + await persistSignalSnapshot(env, { + id: "fresh-empty-pack", + signalType: "contributor-decision-pack", + targetKey: "fresh-user", + payload: { + status: "ready", + source: "computed", + login: "fresh-user", + generatedAt: new Date().toISOString(), + stale: false, + scoringModelSnapshotId: "scoring-1", + profile: {}, + outcomeHistory: {}, + roleContexts: [], + repoDecisions: [], + topActions: [], + cleanupFirst: [], + pursueRepos: [], + avoidRepos: [], + maintainerLaneRepos: [], + scoreBlockers: [], + dataQuality: { signalFidelity: { status: "complete" } }, + summary: "fresh", + nextActions: [], + } as never, + generatedAt: new Date().toISOString(), + }); + const missingRepoDecision = await app.request("/v1/contributors/fresh-user/repos/owner/repo/decision", { headers: apiHeaders(env) }, env); + expect(missingRepoDecision.status).toBe(404); + + const invalidReviewability = await app.request("/v1/repos/owner/repo/pulls/not-a-number/reviewability", { headers: apiHeaders(env) }, env); + expect(invalidReviewability.status).toBe(400); + const noAuthorReviewability = await app.request("/v1/repos/owner/repo/pulls/123/reviewability", { headers: apiHeaders(env) }, env); + expect(noAuthorReviewability.status).toBe(200); + await expect(noAuthorReviewability.json()).resolves.toMatchObject({ action: "review_now" }); + + const backfillDefault = await app.request( + "/v1/internal/jobs/backfill-registered-repos", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ mode: "invalid" }) }, + env, + ); + expect(backfillDefault.status).toBe(202); + const backfillFullRun = await app.request( + "/v1/internal/jobs/backfill-registered-repos/run", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ mode: "full" }) }, + env, + ); + expect(backfillFullRun.status).toBe(200); + const missingSegmentRepo = await app.request("/v1/internal/jobs/backfill-repo-segment", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({}) }, env); + expect(missingSegmentRepo.status).toBe(400); + const invalidSegment = await app.request( + "/v1/internal/jobs/backfill-repo-segment/run", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo", segment: "bad" }) }, + env, + ); + expect(invalidSegment.status).toBe(400); + const queuedSegment = await app.request( + "/v1/internal/jobs/backfill-repo-segment", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo", segment: "labels", mode: "full", cursor: "2", force: true }) }, + env, + ); + expect(queuedSegment.status).toBe(202); + const queuedResumeSegment = await app.request( + "/v1/internal/jobs/backfill-repo-segment", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo", segment: "labels", mode: "resume" }) }, + env, + ); + expect(queuedResumeSegment.status).toBe(202); + const missingDetailsRepo = await app.request("/v1/internal/jobs/backfill-pr-details", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({}) }, env); + expect(missingDetailsRepo.status).toBe(400); + const queuedDetails = await app.request( + "/v1/internal/jobs/backfill-pr-details", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo", mode: "resume", cursor: "5" }) }, + env, + ); + expect(queuedDetails.status).toBe(202); + const queuedFullDetails = await app.request( + "/v1/internal/jobs/backfill-pr-details", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo", mode: "full" }) }, + env, + ); + expect(queuedFullDetails.status).toBe(202); + const evidenceAll = await app.request("/v1/internal/jobs/build-contributor-evidence", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: "{}" }, env); + expect(evidenceAll.status).toBe(202); + const packsAll = await app.request("/v1/internal/jobs/build-contributor-decision-packs", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: "{}" }, env); + expect(packsAll.status).toBe(202); + const missingActivityLogin = await app.request("/v1/internal/jobs/refresh-contributor-activity", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: "{}" }, env); + expect(missingActivityLogin.status).toBe(400); + const activityQueued = await app.request( + "/v1/internal/jobs/refresh-contributor-activity", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ login: "jsonbored", repoFullName: "owner/repo" }) }, + env, + ); + expect(activityQueued.status).toBe(202); + const burdenAll = await app.request("/v1/internal/jobs/build-burden-forecasts", { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: "{}" }, env); + expect(burdenAll.status).toBe(202); + const signalsOne = await app.request( + "/v1/internal/jobs/generate-signal-snapshots", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ repoFullName: "owner/repo" }) }, + env, + ); + expect(signalsOne.status).toBe(202); + const invalidSettings = await app.request( + "/v1/internal/repos/owner/repo/settings", + { method: "POST", headers: { authorization: `Bearer ${env.INTERNAL_JOB_TOKEN}` }, body: JSON.stringify({ commentMode: "loud" }) }, + env, + ); + expect(invalidSettings.status).toBe(400); + expect(queued).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "backfill-registered-repos", mode: "light" }), + expect.objectContaining({ type: "backfill-repo-segment", repoFullName: "owner/repo", segment: "labels", mode: "full", cursor: "2", force: true }), + expect.objectContaining({ type: "backfill-pr-details", repoFullName: "owner/repo", mode: "resume", cursor: 5 }), + expect.objectContaining({ type: "build-contributor-evidence", login: undefined }), + expect.objectContaining({ type: "build-contributor-decision-packs", login: undefined }), + expect.objectContaining({ type: "refresh-contributor-activity", login: "jsonbored", repoFullName: "owner/repo" }), + expect.objectContaining({ type: "build-burden-forecasts", repoFullName: undefined }), + expect.objectContaining({ type: "generate-signal-snapshots", repoFullName: "owner/repo" }), + ]), + ); + + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { + "owner/excellent": { emission_share: 0.02, issue_discovery_share: 0, label_multipliers: {}, trusted_label_pipeline: false, maintainer_cut: 0 }, + "owner/issue-only": { emission_share: 0.02, issue_discovery_share: 1, label_multipliers: {}, trusted_label_pipeline: false, maintainer_cut: 0 }, + "owner/fragile": { emission_share: 0, issue_discovery_share: 0, label_multipliers: {}, trusted_label_pipeline: true, maintainer_cut: 0 }, + }, + { kind: "raw-github", url: "fixture://route-edge-registry" }, + "2026-05-26T00:00:00.000Z", + ), + ); + for (const fullName of ["owner/excellent", "owner/issue-only", "owner/fragile"]) { + const [, name] = fullName.split("/"); + await upsertRepositoryFromGitHub(env, { name: name!, full_name: fullName, private: false, owner: { login: "owner" }, default_branch: "main" }); + } + await persistRepoGithubTotalsSnapshot(env, { + id: "excellent-totals", + repoFullName: "owner/excellent", + openIssuesTotal: 0, + openPullRequestsTotal: 0, + mergedPullRequestsTotal: 0, + closedUnmergedPullRequestsTotal: 0, + labelsTotal: 0, + sourceKind: "github", + fetchedAt: "2026-05-26T00:00:00.000Z", + payload: {}, + }); + await persistRepoGithubTotalsSnapshot(env, { + id: "fragile-totals", + repoFullName: "owner/fragile", + openIssuesTotal: 500, + openPullRequestsTotal: 300, + mergedPullRequestsTotal: 0, + closedUnmergedPullRequestsTotal: 0, + labelsTotal: 0, + sourceKind: "github", + fetchedAt: "2026-05-26T00:00:00.000Z", + payload: {}, + }); + const excellentRecommendation = await app.request("/v1/repos/owner/excellent/gittensor-config-recommendation", { headers: apiHeaders(env) }, env); + expect(excellentRecommendation.status).toBe(200); + await expect(excellentRecommendation.json()).resolves.toMatchObject({ + recommended: { participationMode: "split", issueDiscoveryShare: 0.1, maintainerCut: 0.02 }, + reasons: expect.arrayContaining(["Config and intake signals are strong enough to consider a small issue-discovery slice.", "Maintainer cut can be considered because config and queue signals are clean."]), + }); + const issueOnlyReadiness = await app.request("/v1/repos/owner/issue-only/registration-readiness", { headers: apiHeaders(env) }, env); + expect(issueOnlyReadiness.status).toBe(200); + await expect(issueOnlyReadiness.json()).resolves.toMatchObject({ recommendedRegistrationMode: "issue_discovery", issuePolicy: "issue_discovery_enabled" }); + const fragileReadiness = await app.request("/v1/repos/owner/fragile/registration-readiness", { headers: apiHeaders(env) }, env); + expect(fragileReadiness.status).toBe(200); + await expect(fragileReadiness.json()).resolves.toMatchObject({ + ready: false, + blockers: expect.arrayContaining(["Repository config quality is fragile.", "Contributor intake health is blocked."]), + }); + }); + it("updates repository settings through protected internal API", async () => { const app = createApp(); const env = createTestEnv(); @@ -1357,7 +1774,7 @@ async function seedSignalData(env: Env): Promise { id: 123, account: { login: "entrius", id: 1, type: "Organization" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }, }); @@ -1502,7 +1919,7 @@ async function seedSignalData(env: Env): Promise { status: "healthy", missingPermissions: [], missingEvents: [], - permissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], checkedAt: "2026-05-23T00:00:00.000Z", }); diff --git a/test/integration/routes-errors.test.ts b/test/integration/routes-errors.test.ts index e1a810bb9f..4642bcd50f 100644 --- a/test/integration/routes-errors.test.ts +++ b/test/integration/routes-errors.test.ts @@ -83,6 +83,34 @@ describe("api route guards and error branches", () => { expect(allowedRate.headers.get("x-ratelimit-reset")).toBe("2026-05-25T00:01:00.000Z"); }); + it("keeps auth route failures generic for non-Error provider failures", async () => { + const app = createApp(); + const env = createTestEnv({ GITHUB_OAUTH_CLIENT_ID: "client-id" }); + vi.stubGlobal("fetch", async () => { + throw "provider down"; + }); + + const start = await app.request("/v1/auth/github/device/start", { method: "POST" }, env); + expect(start.status).toBe(502); + await expect(start.json()).resolves.toEqual({ error: "github_device_flow_start_failed" }); + + const poll = await app.request("/v1/auth/github/device/poll", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ deviceCode: "device-code" }), + }, env); + expect(poll.status).toBe(502); + await expect(poll.json()).resolves.toEqual({ error: "github_device_flow_poll_failed" }); + + const session = await app.request("/v1/auth/github/session", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ githubToken: "token" }), + }, env); + expect(session.status).toBe(401); + await expect(session.json()).resolves.toEqual({ error: "github_session_create_failed" }); + }); + it("exposes the GitHub device OAuth route flow without requiring a static token", async () => { const app = createApp(); const env = createTestEnv({ GITHUB_OAUTH_CLIENT_ID: "client-id" }); @@ -233,6 +261,12 @@ describe("api route guards and error branches", () => { body: JSON.stringify({ mode: "full" }), }, env); expect(queuedFullBackfill.status).toBe(202); + const queuedResumeBackfill = await app.request("/v1/internal/jobs/backfill-registered-repos", { + method: "POST", + headers: internalHeaders(env), + body: JSON.stringify({ mode: "resume" }), + }, env); + expect(queuedResumeBackfill.status).toBe(202); const queuedSegment = await app.request("/v1/internal/jobs/backfill-repo-segment", { method: "POST", @@ -266,6 +300,12 @@ describe("api route guards and error branches", () => { }, env); expect(response.status).toBe(202); } + const queuedFullSegment = await app.request("/v1/internal/jobs/backfill-repo-segment", { + method: "POST", + headers: internalHeaders(env), + body: JSON.stringify({ repoFullName: "infiniflow/ragflow", segment: "labels", mode: "full" }), + }, env); + expect(queuedFullSegment.status).toBe(202); const queuedDetails = await app.request("/v1/internal/jobs/backfill-pr-details", { method: "POST", headers: internalHeaders(env), diff --git a/test/unit/auth.test.ts b/test/unit/auth.test.ts index 48bf2a99b7..746fe26bd4 100644 --- a/test/unit/auth.test.ts +++ b/test/unit/auth.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { createSessionFromGitHubToken, pollGitHubDeviceFlow, startGitHubDeviceFlow } from "../../src/auth/github-oauth"; -import { RateLimiter, routeClassForPath } from "../../src/auth/rate-limit"; +import { enforceRateLimit, RateLimiter, routeClassForPath } from "../../src/auth/rate-limit"; import { authenticatePrivateToken, createSessionForGitHubUser, revokeSession } from "../../src/auth/security"; import { createTestEnv } from "../helpers/d1"; @@ -41,14 +41,87 @@ describe("private-beta auth and rate limiting", () => { expect(invalid.status).toBe(400); }); + it("resets Durable Object buckets after the configured window expires", async () => { + const state = memoryDurableObjectState(); + const limiter = new RateLimiter(state as unknown as DurableObjectState, createTestEnv()); + const now = vi.spyOn(Date, "now"); + now.mockReturnValue(1_000); + + const first = await limiter.fetch(new Request("https://rate-limit/check", { method: "POST", body: JSON.stringify({ key: "session:reset", limit: 1, windowSeconds: 1 }) })); + const second = await limiter.fetch(new Request("https://rate-limit/check", { method: "POST", body: JSON.stringify({ key: "session:reset", limit: 1, windowSeconds: 1 }) })); + now.mockReturnValue(2_001); + const reset = await limiter.fetch(new Request("https://rate-limit/check", { method: "POST", body: JSON.stringify({ key: "session:reset", limit: 1, windowSeconds: 1 }) })); + + expect(first.status).toBe(200); + expect(second.status).toBe(429); + expect(reset.status).toBe(200); + await expect(reset.json()).resolves.toMatchObject({ allowed: true, remaining: 0 }); + }); + it("classifies rate-limit route costs", () => { expect(routeClassForPath("/v1/auth/github/device/start")).toBe("strict"); expect(routeClassForPath("/v1/local/branch-analysis")).toBe("expensive"); + expect(routeClassForPath("/v1/scoring/preview")).toBe("expensive"); expect(routeClassForPath("/v1/contributors/jsonbored/decision-pack")).toBe("expensive"); expect(routeClassForPath("/v1/internal/jobs/generate-signal-snapshots")).toBe("expensive"); + expect(routeClassForPath("/v1/internal/jobs/build-contributor-decision-packs")).toBe("expensive"); expect(routeClassForPath("/v1/repos")).toBe("normal"); }); + it("enforces route limits with session and IP keys plus retry headers", async () => { + const env = createTestEnv(); + const noLimiter = fakeContext(env, "/v1/repos/123/pulls/456", { authorization: "Bearer session-token" }); + await expect(enforceRateLimit(noLimiter, "normal")).resolves.toBeNull(); + + const fallbackHeaders = fakeContext( + createTestEnv({ RATE_LIMITER: rateLimiterNamespace({ status: 200, body: {} }) as unknown as DurableObjectNamespace }), + "/v1/repos/JSONbored/gittensory", + { "x-forwarded-for": "198.51.100.2, 198.51.100.3" }, + ); + await expect(enforceRateLimit(fallbackHeaders, "normal")).resolves.toBeNull(); + expect(fallbackHeaders.res.headers.get("x-ratelimit-limit")).toBe("120"); + expect(fallbackHeaders.res.headers.get("x-ratelimit-remaining")).toBe("120"); + expect(fallbackHeaders.res.headers.get("x-ratelimit-reset")).toBeNull(); + + const allowed = fakeContext( + createTestEnv({ RATE_LIMITER: rateLimiterNamespace({ status: 200, body: { limit: 3, remaining: 2, resetAt: "2026-05-25T00:01:00.000Z" } }) as unknown as DurableObjectNamespace }), + "/v1/repos/JSONbored/gittensory/pulls/123/reviewability", + { authorization: "Bearer session-token" }, + ); + await expect(enforceRateLimit(allowed, "normal")).resolves.toBeNull(); + expect(allowed.res.headers.get("x-ratelimit-limit")).toBe("3"); + expect(allowed.res.headers.get("x-ratelimit-remaining")).toBe("2"); + expect(allowed.res.headers.get("x-ratelimit-reset")).toBe("2026-05-25T00:01:00.000Z"); + + const deniedEnv = createTestEnv({ RATE_LIMITER: rateLimiterNamespace({ status: 429, body: { resetAt: "2026-05-25T00:02:00.000Z" } }) as unknown as DurableObjectNamespace }); + const denied = fakeContext( + deniedEnv, + "/v1/local/branch-analysis", + { "cf-connecting-ip": "203.0.113.7" }, + ); + const response = await enforceRateLimit(denied, "expensive"); + expect(response?.status).toBe(429); + expect(response?.headers.get("retry-after")).toBe("60"); + await expect(response?.json()).resolves.toMatchObject({ error: "rate_limited", routeClass: "expensive", retryAfterSeconds: 60 }); + + const audited = await deniedEnv.DB.prepare("select event_type, actor, outcome from audit_events where event_type = ?").bind("rate_limit.denied").all(); + expect(audited.results).toEqual(expect.arrayContaining([expect.objectContaining({ event_type: "rate_limit.denied", actor: "anonymous", outcome: "denied" })])); + + const deniedWithTokenEnv = createTestEnv({ + RATE_LIMITER: rateLimiterNamespace({ status: 429, body: { limit: 20, remaining: 0, retryAfterSeconds: 17, resetAt: "2026-05-25T00:03:00.000Z" } }) as unknown as DurableObjectNamespace, + }); + const deniedWithToken = fakeContext(deniedWithTokenEnv, "/v1/local/branch-analysis", { authorization: "Bearer session-token" }); + const deniedWithTokenResponse = await enforceRateLimit(deniedWithToken, "expensive"); + expect(deniedWithTokenResponse?.headers.get("retry-after")).toBe("17"); + expect(deniedWithTokenResponse?.headers.get("x-ratelimit-reset")).toBe("2026-05-25T00:03:00.000Z"); + const tokenAudit = await deniedWithTokenEnv.DB.prepare("select actor, metadata_json from audit_events where event_type = ?").bind("rate_limit.denied").first<{ + actor: string; + metadata_json: string; + }>(); + expect(tokenAudit?.actor).toMatch(/^token:/); + expect(JSON.parse(tokenAudit?.metadata_json ?? "{}")).toMatchObject({ retryAfterSeconds: 17 }); + }); + it("starts GitHub device flow and rejects malformed provider responses", async () => { const env = createTestEnv({ GITHUB_OAUTH_CLIENT_ID: "client-id" }); vi.stubGlobal("fetch", async () => @@ -66,6 +139,9 @@ describe("private-beta auth and rate limiting", () => { vi.stubGlobal("fetch", async () => Response.json({ error: "bad_verification_code", error_description: "bad" })); await expect(startGitHubDeviceFlow(env)).rejects.toThrow(/bad/); + vi.stubGlobal("fetch", async () => Response.json({}, { status: 502 })); + await expect(startGitHubDeviceFlow(env)).rejects.toThrow(/github_device_flow_start_failed/); + vi.stubGlobal("fetch", async () => Response.json({ device_code: "missing" })); await expect(startGitHubDeviceFlow(env)).rejects.toThrow(/response_invalid/); await expect(startGitHubDeviceFlow(createTestEnv())).rejects.toThrow(/not_configured/); @@ -105,6 +181,22 @@ describe("private-beta auth and rate limiting", () => { }); await expect(pollGitHubDeviceFlow(env, "device-code")).resolves.toMatchObject({ status: "slow_down" }); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.includes("access_token")) return Response.json({ error: "bad_verification_code", error_description: "bad code" }); + return Response.json({}); + }); + await expect(pollGitHubDeviceFlow(env, "device-code")).resolves.toMatchObject({ status: "bad_verification_code", message: "bad code" }); + await expect(env.DB.prepare("select outcome from audit_events where event_type = ? and detail = ?").bind("auth.github_device_poll", "bad_verification_code").first()).resolves.toMatchObject({ outcome: "error" }); + + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.includes("access_token")) return Response.json({ access_token: "gh-token" }); + if (url === "https://api.github.com/user") return Response.json({ login: "scopefree", id: 43 }); + return Response.json({}); + }); + await expect(pollGitHubDeviceFlow(env, "device-code")).resolves.toMatchObject({ login: "scopefree", scopes: [] }); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { const url = input.toString(); if (url.includes("access_token")) return Response.json({}); @@ -137,3 +229,38 @@ function memoryDurableObjectState() { }, }; } + +function rateLimiterNamespace(decision: { status: number; body: Record }) { + return { + idFromName(name: string) { + expect(name).toMatch(/^(normal|expensive):/); + return name; + }, + get() { + return { + async fetch(_url: string, init?: RequestInit) { + expect(init?.method).toBe("POST"); + expect(JSON.parse(String(init?.body ?? "{}"))).toEqual(expect.objectContaining({ key: expect.any(String), limit: expect.any(Number), windowSeconds: expect.any(Number) })); + return Response.json(decision.body, { status: decision.status }); + }, + }; + }, + }; +} + +function fakeContext(env: Env, path: string, headers: Record = {}) { + const responseHeaders = new Headers(); + return { + env, + req: { + path, + header(name: string) { + return headers[name.toLowerCase()] ?? headers[name]; + }, + }, + res: { headers: responseHeaders }, + json(body: unknown, status: number, responseHeadersInit?: HeadersInit) { + return Response.json(body, responseHeadersInit ? { status, headers: responseHeadersInit } : { status }); + }, + } as unknown as import("hono").Context<{ Bindings: Env }> & { res: { headers: Headers } }; +} diff --git a/test/unit/backfill.test.ts b/test/unit/backfill.test.ts index bf49990169..2211104849 100644 --- a/test/unit/backfill.test.ts +++ b/test/unit/backfill.test.ts @@ -208,6 +208,52 @@ describe("GitHub backfill", () => { expect(await listContributorRepoStats(env, "jsonbored")).toEqual([]); }); + it("records unknown contributor activity failures and deterministic dominant label ties", async () => { + const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); + await seedRegisteredRepo(env); + let calls = 0; + vi.stubGlobal("fetch", async () => { + calls += 1; + if (calls === 1) throw "network vanished"; + return Response.json({ + data: { + r_JSONbored_gittensory_all: { + issueCount: 2, + nodes: [ + { __typename: "PullRequest", updatedAt: "bad-date", labels: { nodes: [{ name: "zeta" }] }, body: "" }, + { __typename: "PullRequest", updatedAt: "2026-05-24T00:00:00Z", labels: { nodes: [{ name: "alpha" }] }, body: "Fixes #1" }, + ], + }, + r_JSONbored_gittensory_merged: { issueCount: 0, nodes: [] }, + r_JSONbored_gittensory_open: { + issueCount: 2, + nodes: [ + { __typename: "PullRequest", updatedAt: "bad-date", labels: { nodes: [{ name: "zeta" }] }, body: "" }, + { __typename: "PullRequest", updatedAt: "2026-05-23T00:00:00Z", labels: { nodes: [{ name: "alpha" }] }, body: "Fixes #1" }, + ], + }, + r_JSONbored_gittensory_issues: { + issueCount: 1, + nodes: [{ __typename: "Issue", updatedAt: "2026-05-22T00:00:00Z", labels: { nodes: [null, { name: "alpha" }, { name: "zeta" }] }, body: "" }], + }, + }, + }); + }); + + const failed = await refreshContributorActivity(env, "jsonbored"); + const recovered = await refreshContributorActivity(env, "jsonbored"); + + expect(failed).toMatchObject({ updatedRepoStats: 0, warnings: ["Contributor activity refresh failed for JSONbored/gittensory: unknown error"] }); + expect(recovered).toMatchObject({ updatedRepoStats: 1, warnings: [] }); + expect(await listContributorRepoStats(env, "jsonbored")).toEqual([ + expect.objectContaining({ + dominantLabels: ["alpha", "zeta"], + lastActivityAt: "bad-date", + unlinkedPullRequests: 1, + }), + ]); + }); + it("carries GraphQL warnings and ignores repos with no contributor activity", async () => { const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); await seedRegisteredRepo(env); @@ -285,6 +331,51 @@ describe("GitHub backfill", () => { expect(refreshed.installations).toEqual(expect.arrayContaining([expect.objectContaining({ installationId: 124, status: "healthy" })])); }); + it("requires Checks write only for repos with check runs enabled", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await seedRegisteredRepo(env); + await upsertInstallation(env, { + installation: { + id: 123, + account: { login: "JSONbored", id: 1, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, + events: ["issues", "pull_request", "repository"], + }, + }); + await upsertRepositoryFromGitHub(env, { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, 123); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + checkRunMode: "enabled", + }); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.endsWith("/app/installations/123")) { + return Response.json({ + id: 123, + account: { login: "JSONbored", id: 1, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, + events: ["issues", "pull_request", "repository"], + }); + } + return new Response("not found", { status: 404 }); + }); + + const refreshed = await refreshInstallationHealth(env); + + expect(refreshed.installations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + installationId: 123, + status: "needs_attention", + missingPermissions: ["checks"], + requiredPermissions: expect.objectContaining({ checks: "write" }), + }), + ]), + ); + }); + it("refreshes installation health from live GitHub App metadata", async () => { const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); await seedRegisteredRepo(env); @@ -333,7 +424,7 @@ describe("GitHub backfill", () => { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }); } @@ -346,7 +437,7 @@ describe("GitHub backfill", () => { expect.objectContaining({ installationId: 123, status: "healthy", - errorSummary: undefined, + missingPermissions: [], }), ]), ); @@ -360,7 +451,7 @@ describe("GitHub backfill", () => { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }, }); @@ -1251,6 +1342,133 @@ describe("GitHub backfill", () => { } }); + it("skips missing repositories and preserves segment progress while waiting for rate-limit recovery", async () => { + const sent: Array<{ message: import("../../src/types").JobMessage; options?: QueueSendOptions }> = []; + const env = createTestEnv({ + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(message: import("../../src/types").JobMessage, options?: QueueSendOptions) { + sent.push(options ? { message, options } : { message }); + }, + } as unknown as Queue, + }); + + await expect(backfillRepositorySegment(env, { repoFullName: "missing/repo", segment: "labels" })).resolves.toMatchObject({ + status: "skipped", + fetchedCount: 0, + warnings: ["Repository was not found."], + }); + await expect(backfillOpenPullRequestDetails(env, { repoFullName: "missing/repo" })).resolves.toMatchObject({ + status: "skipped", + processed: 0, + warnings: ["Repository was not found."], + }); + + await seedRegisteredRepo(env); + await upsertRepoSyncSegment(env, { + repoFullName: "JSONbored/gittensory", + segment: "labels", + status: "partial", + sourceKind: "github", + mode: "resume", + fetchedCount: 7, + expectedCount: 20, + pageCount: 2, + nextCursor: "3", + completedAt: "2026-05-24T00:00:00.000Z", + warnings: ["previous partial"], + }); + await recordGitHubRateLimitObservation(env, { + repoFullName: "JSONbored/gittensory", + resource: "rest", + path: "/labels", + statusCode: 200, + remaining: 0, + resetAt: "2999-01-01T00:00:00.000Z", + }); + + const result = await backfillRepositorySegment(env, { repoFullName: "JSONbored/gittensory", segment: "labels", requestedBy: "schedule", mode: "resume", cursor: "4" }); + + expect(result).toMatchObject({ status: "waiting_rate_limit", fetchedCount: 7, expectedCount: 20, nextCursor: "3" }); + expect(sent).toEqual([ + { + message: expect.objectContaining({ type: "backfill-repo-segment", requestedBy: "schedule", repoFullName: "JSONbored/gittensory", segment: "labels", mode: "resume", force: true }), + options: { delaySeconds: 900 }, + }, + ]); + expect(await listRepoSyncSegments(env, "JSONbored/gittensory")).toEqual( + expect.arrayContaining([ + expect.objectContaining({ segment: "labels", status: "waiting_rate_limit", fetchedCount: 7, expectedCount: 20, pageCount: 2, nextCursor: "3" }), + ]), + ); + + const freshWaitEnv = createTestEnv({ + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(message: import("../../src/types").JobMessage, options?: QueueSendOptions) { + sent.push(options ? { message, options } : { message }); + }, + } as unknown as Queue, + }); + await seedRegisteredRepo(freshWaitEnv); + await recordGitHubRateLimitObservation(freshWaitEnv, { + repoFullName: "JSONbored/gittensory", + resource: "rest", + path: "/labels", + statusCode: 200, + remaining: 0, + resetAt: "2999-01-01T00:00:00.000Z", + }); + const freshWait = await backfillRepositorySegment(freshWaitEnv, { repoFullName: "JSONbored/gittensory", segment: "open_pull_requests", mode: "light" }); + expect(freshWait).toMatchObject({ status: "waiting_rate_limit", fetchedCount: 0 }); + expect(await listRepoSyncSegments(freshWaitEnv, "JSONbored/gittensory")).toEqual( + expect.arrayContaining([expect.objectContaining({ segment: "open_pull_requests", status: "waiting_rate_limit", fetchedCount: 0, pageCount: 0 })]), + ); + }); + + it("uses cached totals and unauthenticated segment fallback when no GitHub token is available", async () => { + const env = createTestEnv(); + await seedRegisteredRepo(env); + await env.DB.prepare( + `insert into repo_github_totals_snapshots ( + id, repo_full_name, open_issues_total, open_pull_requests_total, merged_pull_requests_total, + closed_unmerged_pull_requests_total, labels_total, source_kind, fetched_at, payload_json + ) values ('totals-1', 'JSONbored/gittensory', 0, 0, 0, 0, 0, 'github', '2026-05-25T00:00:00.000Z', '{}')`, + ).run(); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + expect(url).toContain("/labels?"); + return Response.json([]); + }); + + const result = await backfillRepositorySegment(env, { repoFullName: "JSONbored/gittensory", segment: "labels", mode: "light" }); + + expect(result).toMatchObject({ status: "complete", fetchedCount: 0, expectedCount: 0 }); + }); + + it("reconciles stale open issue rows after complete open-data crawls", async () => { + const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); + await seedRegisteredRepo(env); + await upsertIssueFromGitHub( + env, + "JSONbored/gittensory", + { number: 99, title: "Previously open", state: "open", user: { login: "reporter" }, labels: [], body: "" }, + { seenOpenAt: "2026-05-20T00:00:00.000Z" }, + ); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url === "https://api.github.com/graphql") return githubTotalsResponse({ openIssues: 0, openPullRequests: 0, mergedPullRequests: 0, closedPullRequests: 0, labels: 0 }); + if (url.includes("/issues?")) return Response.json([]); + return Response.json([]); + }); + + const result = await backfillRepositorySegment(env, { repoFullName: "JSONbored/gittensory", segment: "open_issues", mode: "full", force: true }); + + expect(result).toMatchObject({ status: "complete", fetchedCount: 0 }); + expect(result.warnings.join(" ")).toMatch(/Marked 1 stale open issue row/); + expect(await listIssues(env, "JSONbored/gittensory")).toEqual([expect.objectContaining({ number: 99, state: "closed" })]); + }); + it("backs off PR detail hydration under low REST rate limit", async () => { const sent: import("../../src/types").JobMessage[] = []; const env = createTestEnv({ @@ -1279,6 +1497,159 @@ describe("GitHub backfill", () => { expect.arrayContaining([expect.objectContaining({ segment: "pull_request_files", status: "waiting_rate_limit" })]), ); }); + + it("defaults PR detail retry cursors when rate-limit recovery starts without prior state", async () => { + const sent: Array<{ message: import("../../src/types").JobMessage; options?: QueueSendOptions }> = []; + const env = createTestEnv({ + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(message: import("../../src/types").JobMessage, options?: QueueSendOptions) { + sent.push(options ? { message, options } : { message }); + }, + } as unknown as Queue, + }); + await seedRegisteredRepo(env); + await recordGitHubRateLimitObservation(env, { + repoFullName: "JSONbored/gittensory", + resource: "rest", + path: "/pulls", + statusCode: 200, + remaining: 0, + resetAt: "2999-01-01T00:00:00.000Z", + }); + + const result = await backfillOpenPullRequestDetails(env, { repoFullName: "JSONbored/gittensory", mode: "light" }); + + expect(result).toMatchObject({ status: "waiting_rate_limit", processed: 0 }); + expect(sent).toEqual([{ message: expect.objectContaining({ type: "backfill-pr-details", repoFullName: "JSONbored/gittensory", mode: "light", cursor: 0 }), options: { delaySeconds: 900 } }]); + expect(await listRepoSyncSegments(env, "JSONbored/gittensory")).toEqual( + expect.arrayContaining([expect.objectContaining({ segment: "pull_request_files", status: "waiting_rate_limit", fetchedCount: 0, pageCount: 0 })]), + ); + }); + + it("uses installation source for queued segment jobs and sparse live installation fallback metadata", async () => { + const sent: import("../../src/types").JobMessage[] = []; + const env = createTestEnv({ + GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem(), + GITHUB_PUBLIC_TOKEN: "public-token", + JOBS: { + async send(message: import("../../src/types").JobMessage) { + sent.push(message); + }, + } as unknown as Queue, + }); + await seedRegisteredRepo(env); + await upsertInstallation(env, { + installation: { + id: 123, + account: { login: "JSONbored", id: 1, type: "User" }, + repository_selection: "selected", + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, + events: ["issues", "pull_request", "repository"], + }, + }); + await upsertRepositoryFromGitHub( + env, + { name: "gittensory", full_name: "JSONbored/gittensory", private: true, default_branch: "main", owner: { login: "JSONbored" } }, + 123, + ); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.endsWith("/app/installations/123")) return Response.json({ id: 123 }); + if (url === "https://api.github.com/graphql") { + return Response.json({ + data: { + rateLimit: {}, + repository: { + issues: {}, + openPullRequests: {}, + mergedPullRequests: {}, + closedPullRequests: {}, + labels: {}, + }, + }, + }); + } + if (url.includes("/labels?")) return Response.json([]); + return Response.json([]); + }); + + const queued = await enqueueRepositoryOpenDataBackfill(env, { repoFullName: "JSONbored/gittensory", requestedBy: "api", mode: "resume" }); + const segment = await backfillRepositorySegment(env, { repoFullName: "JSONbored/gittensory", segment: "labels", mode: "resume", force: true }); + const details = await backfillOpenPullRequestDetails(env, { repoFullName: "JSONbored/gittensory", mode: "resume" }); + const health = await refreshInstallationHealth(env); + + expect(queued).toMatchObject({ status: "queued", totals: { sourceKind: "installation", openIssuesTotal: 0, openPullRequestsTotal: 0, labelsTotal: 0 } }); + expect(segment).toMatchObject({ status: "complete", fetchedCount: 0, expectedCount: 0 }); + expect(details).toMatchObject({ status: "complete", processed: 0 }); + expect(health.installations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + installationId: 123, + accountLogin: "JSONbored", + repositorySelection: "selected", + status: "needs_attention", + permissions: {}, + events: [], + missingPermissions: ["metadata", "pull_requests", "issues"], + missingEvents: ["issues", "pull_request", "repository"], + }), + ]), + ); + expect(await listRepoSyncSegments(env, "JSONbored/gittensory")).toEqual( + expect.arrayContaining([expect.objectContaining({ segment: "labels", sourceKind: "installation" })]), + ); + expect(sent).toEqual(expect.arrayContaining([expect.objectContaining({ type: "backfill-repo-segment", segment: "labels" })])); + }); + + it("records label rate limits, in-loop page caps, and expired rate observations", async () => { + const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); + await seedRegisteredRepo(env); + await recordGitHubRateLimitObservation(env, { + repoFullName: "JSONbored/gittensory", + resource: "rest", + path: "/old", + statusCode: 200, + remaining: 1, + resetAt: "2020-01-01T00:00:00.000Z", + }); + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.endsWith("/repos/JSONbored/gittensory")) { + return Response.json({ name: "gittensory", full_name: "JSONbored/gittensory", private: false, default_branch: "main", owner: { login: "JSONbored" } }); + } + if (url.includes("/labels?")) { + return new Response("label secondary limit", { + status: 403, + headers: { + "x-ratelimit-limit": "5000", + "x-ratelimit-remaining": "0", + "x-ratelimit-reset": "1780000000", + }, + }); + } + if (url.includes("/issues?") && new URL(url).searchParams.get("page") === "1") { + return Response.json( + Array.from({ length: 100 }, (_, index) => ({ number: index + 1, title: `Issue ${index + 1}`, state: "open", user: { login: "reporter" }, labels: [], body: "" })), + { headers: { link: '; rel="next"', "x-ratelimit-remaining": "not-a-number" } }, + ); + } + if (url.includes("/pulls?")) return Response.json([]); + return Response.json([]); + }); + + const result = await backfillRegisteredRepositories(env, { force: true, limits: { issues: 100, pullRequests: 0, recentMergedPullRequests: 0, pullRequestDetails: 0 } }); + + expect(result.repos[0]).toMatchObject({ status: "rate_limited", dataQuality: { capped: true, rateLimited: true, partial: true } }); + expect(result.repos[0]?.warnings.join("\n")).toMatch(/Label sync failed|local cap/); + expect(await listRepoSyncSegments(env, "JSONbored/gittensory")).toEqual( + expect.arrayContaining([ + expect.objectContaining({ segment: "labels", status: "rate_limited", rateLimitResetAt: "2026-05-28T20:26:40.000Z" }), + expect.objectContaining({ segment: "open_issues", status: "capped", nextCursor: "2" }), + ]), + ); + }); }); async function seedRegisteredRepo(env: Env) { diff --git a/test/unit/data-spine.test.ts b/test/unit/data-spine.test.ts index 147e99e67a..8ec3758785 100644 --- a/test/unit/data-spine.test.ts +++ b/test/unit/data-spine.test.ts @@ -229,7 +229,12 @@ describe("data spine repositories", () => { expect(await getRepository(env, "owner/repo")).toMatchObject({ owner: "owner", isInstalled: false, isPrivate: false }); expect(await getRepository(env, "OWNER/REPO")).toMatchObject({ fullName: "owner/repo" }); expect(await getRepository(env, "missing/repo")).toBeNull(); - expect(await getRepositorySettings(env, "missing/repo")).toMatchObject({ commentMode: "off", checkRunDetailLevel: "standard" }); + expect(await getRepositorySettings(env, "missing/repo")).toMatchObject({ + commentMode: "detected_contributors_only", + checkRunMode: "off", + checkRunDetailLevel: "minimal", + publicSurface: "comment_and_label", + }); expect(await getRepoSyncState(env, "missing/repo")).toBeNull(); expect(await getPullRequest(env, "owner/repo", 404)).toBeNull(); expect(await getIssue(env, "owner/repo", 404)).toBeNull(); diff --git a/test/unit/db-parsers.test.ts b/test/unit/db-parsers.test.ts new file mode 100644 index 0000000000..97b721a042 --- /dev/null +++ b/test/unit/db-parsers.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from "vitest"; +import { + getLatestScorePreview, + getLatestScoringModelSnapshot, + listPullRequestDetailSyncStates, + listRepoSyncSegments, + listRepoSyncStates, +} from "../../src/db/repositories"; +import { createTestEnv } from "../helpers/d1"; + +describe("database row parser hardening", () => { + it("normalizes enum-like database values from stored sync, scoring, and preview rows", async () => { + const env = createTestEnv(); + + for (const [repo, status, source] of [ + ["owner/skipped", "skipped", "installation"], + ["owner/capped", "capped", "test"], + ["owner/rate", "rate_limited", "unknown-source"], + ["owner/stale", "stale", "github"], + ["owner/bad", "not-a-real-status", "bad-source"], + ]) { + await env.DB.prepare( + `insert into repo_sync_state ( + repo_full_name, status, source_kind, open_issues_count, open_pull_requests_count, + recent_merged_pull_requests_count, warnings_json + ) values (?, ?, ?, 0, 0, 0, '[]')`, + ) + .bind(repo, status, source) + .run(); + } + + for (const [segment, status, mode] of [ + ["recent_merged_pull_requests", "sampled", "full"], + ["pull_request_files", "waiting_rate_limit", "resume"], + ["pull_request_reviews", "error", "bad-mode"], + ["check_summaries", "not_modified", "light"], + ["bad-segment", "bad-status", "bad-mode"], + ]) { + await env.DB.prepare( + `insert into repo_sync_segments ( + id, repo_full_name, segment, status, source_kind, mode, fetched_count, page_count, warnings_json + ) values (?, 'owner/repo', ?, ?, 'github', ?, 0, 0, '[]')`, + ) + .bind(`segment-${segment}-${status}`, segment, status, mode) + .run(); + } + + for (const [pullNumber, status] of [ + [1, "waiting_rate_limit"], + [2, "error"], + [3, "bad-status"], + ] as const) { + await env.DB.prepare( + `insert into pull_request_detail_sync_state ( + id, repo_full_name, pull_number, status + ) values (?, 'owner/repo', ?, ?)`, + ) + .bind(`detail-${pullNumber}`, pullNumber, status) + .run(); + } + + await env.DB.prepare( + `insert into scoring_model_snapshots ( + id, source_kind, source_url, fetched_at, active_model, constants_json, + programming_languages_json, warnings_json, payload_json + ) values ('score-model', 'bad-source', 'fixture://model', '2026-05-25T00:00:00.000Z', 'bad-model', '{}', '{}', '[]', '{}')`, + ).run(); + + for (const [targetType, generatedAt] of [ + ["pull_request", "2026-05-25T00:00:01.000Z"], + ["local_diff", "2026-05-25T00:00:02.000Z"], + ["variant", "2026-05-25T00:00:03.000Z"], + ["bad-target", "2026-05-25T00:00:04.000Z"], + ]) { + await env.DB.prepare( + `insert into score_previews ( + id, scoring_model_snapshot_id, repo_full_name, target_type, target_key, + input_json, result_json, generated_at + ) values (?, 'score-model', 'owner/repo', ?, ?, '{}', '{}', ?)`, + ) + .bind(`preview-${targetType}`, targetType, `target-${targetType}`, generatedAt) + .run(); + } + + expect(await listRepoSyncStates(env)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ repoFullName: "owner/skipped", status: "skipped", sourceKind: "installation" }), + expect.objectContaining({ repoFullName: "owner/capped", status: "capped", sourceKind: "test" }), + expect.objectContaining({ repoFullName: "owner/rate", status: "rate_limited", sourceKind: "github" }), + expect.objectContaining({ repoFullName: "owner/stale", status: "stale" }), + expect.objectContaining({ repoFullName: "owner/bad", status: "never_synced", sourceKind: "github" }), + ]), + ); + expect(await listRepoSyncSegments(env, "owner/repo")).toEqual( + expect.arrayContaining([ + expect.objectContaining({ segment: "recent_merged_pull_requests", status: "sampled", mode: "full" }), + expect.objectContaining({ segment: "pull_request_files", status: "waiting_rate_limit", mode: "resume" }), + expect.objectContaining({ segment: "pull_request_reviews", status: "error", mode: "light" }), + expect.objectContaining({ segment: "check_summaries", status: "not_modified" }), + expect.objectContaining({ segment: "metadata", status: "never_synced", mode: "light" }), + ]), + ); + expect(await listPullRequestDetailSyncStates(env, "owner/repo")).toEqual( + expect.arrayContaining([ + expect.objectContaining({ pullNumber: 1, status: "waiting_rate_limit" }), + expect.objectContaining({ pullNumber: 2, status: "error" }), + expect.objectContaining({ pullNumber: 3, status: "never_synced" }), + ]), + ); + expect(await getLatestScoringModelSnapshot(env)).toMatchObject({ sourceKind: "fallback", activeModel: "unknown" }); + await expect(getLatestScorePreview(env, "owner/repo", "target-pull_request")).resolves.toMatchObject({ targetType: "pull_request" }); + await expect(getLatestScorePreview(env, "owner/repo", "target-local_diff")).resolves.toMatchObject({ targetType: "local_diff" }); + await expect(getLatestScorePreview(env, "owner/repo", "target-variant")).resolves.toMatchObject({ targetType: "variant" }); + await expect(getLatestScorePreview(env, "owner/repo", "target-bad-target")).resolves.toMatchObject({ targetType: "planned_pr" }); + await expect(getLatestScorePreview(env, "owner/repo", "missing")).resolves.toBeNull(); + }); +}); diff --git a/test/unit/github-app.test.ts b/test/unit/github-app.test.ts index 0807dc53b0..82cbd6f3fc 100644 --- a/test/unit/github-app.test.ts +++ b/test/unit/github-app.test.ts @@ -22,10 +22,11 @@ describe("GitHub check runs", () => { return Response.json({ total_count: 0, check_runs: [] }); } if (url.includes("/check-runs")) { - const body = JSON.parse(String(init?.body)) as { name: string; conclusion: string; output: { text: string } }; + const body = JSON.parse(String(init?.body)) as { name: string; conclusion: string; output: { title: string; text: string } }; expect(body.name).toBe("Gittensory"); expect(body.conclusion).toBe("neutral"); - expect(body.output.text).not.toMatch(/reward|farming/i); + expect(body.output.title).toBe("Gittensory context posted"); + expect(body.output.text).not.toMatch(/linked issue|reviewability|reward|farming|wallet|hotkey|trust score/i); return Response.json({ id: 42, html_url: "https://github.com/checks/42" }, { status: 201 }); } return new Response("not found", { status: 404 }); @@ -41,7 +42,7 @@ describe("GitHub check runs", () => { headSha: "abc123", conclusion: "neutral", severity: "warning", - title: "Gittensory advisory needs review", + title: "Gittensory advisory available", summary: "1 advisory finding generated.", findings: [ { @@ -85,9 +86,11 @@ describe("GitHub check runs", () => { return Response.json({ total_count: 1, check_runs: [{ id: 42, name: "Gittensory" }] }); } if (url.includes("/check-runs/42")) { - const body = JSON.parse(String(init?.body)) as { name: string; conclusion: string }; + const body = JSON.parse(String(init?.body)) as { name: string; conclusion: string; output: { title: string; text: string } }; expect(body.name).toBe("Gittensory"); expect(body.conclusion).toBe("success"); + expect(body.output.title).toBe("Gittensory context checked"); + expect(body.output.text).not.toMatch(/reviewability|reward|farming|wallet|hotkey|trust score/i); return Response.json({ id: 42, html_url: "https://github.com/checks/42" }); } return new Response("not found", { status: 404 }); diff --git a/test/unit/github-labels.test.ts b/test/unit/github-labels.test.ts new file mode 100644 index 0000000000..370e22a156 --- /dev/null +++ b/test/unit/github-labels.test.ts @@ -0,0 +1,104 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { generateKeyPairSync } from "node:crypto"; +import { ensurePullRequestLabel } from "../../src/github/labels"; +import { createTestEnv } from "../helpers/d1"; + +describe("GitHub PR labels", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("rejects invalid repository names before making GitHub calls", async () => { + await expect(ensurePullRequestLabel(createTestEnv(), 123, "invalid", 4, "gittensor", { createMissingLabel: true })).rejects.toThrow(/Invalid repository full name/); + }); + + it("does nothing when the label is already applied", async () => { + const calls: string[] = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + calls.push(`${init?.method ?? "GET"} ${url}`); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/4/labels")) return Response.json([{ name: "GitTensor" }]); + return new Response("unexpected", { status: 500 }); + }); + + const result = await ensurePullRequestLabel(createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem() }), 123, "JSONbored/gittensory", 4, "gittensor", { + createMissingLabel: true, + }); + + expect(result).toEqual({ applied: false, created: false }); + expect(calls.some((call) => call.includes("/repos/JSONbored/gittensory/labels"))).toBe(false); + }); + + it("applies an existing repository label without creating it", async () => { + const calls: string[] = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + calls.push(`${method} ${url}`); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/4/labels") && method === "GET") return Response.json([]); + if (url.includes("/repos/JSONbored/gittensory/labels") && method === "GET") return Response.json([{ name: "gittensor" }]); + if (url.includes("/issues/4/labels") && method === "POST") return Response.json([{ name: "gittensor" }]); + return new Response("unexpected", { status: 500 }); + }); + + const result = await ensurePullRequestLabel(createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem() }), 123, "JSONbored/gittensory", 4, "gittensor", { + createMissingLabel: true, + }); + + expect(result).toEqual({ applied: true, created: false }); + expect(calls.some((call) => call.startsWith("POST ") && call.endsWith("/repos/JSONbored/gittensory/labels"))).toBe(false); + }); + + it("creates a missing repository label when configured", async () => { + const calls: string[] = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + calls.push(`${method} ${url}`); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/4/labels") && method === "GET") return Response.json([]); + if (url.includes("/repos/JSONbored/gittensory/labels") && method === "GET") return Response.json([]); + if (url.includes("/repos/JSONbored/gittensory/labels") && method === "POST") { + const body = JSON.parse(String(init?.body ?? "{}")) as { name?: string; color?: string }; + expect(body).toMatchObject({ name: "gittensor", color: "7ee787" }); + return Response.json({ name: "gittensor" }, { status: 201 }); + } + if (url.includes("/issues/4/labels") && method === "POST") return Response.json([{ name: "gittensor" }]); + return new Response("unexpected", { status: 500 }); + }); + + const result = await ensurePullRequestLabel(createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem() }), 123, "JSONbored/gittensory", 4, "gittensor", { + createMissingLabel: true, + }); + + expect(result).toEqual({ applied: true, created: true }); + expect(calls).toEqual(expect.arrayContaining([expect.stringMatching(/^POST .*\/repos\/JSONbored\/gittensory\/labels$/)])); + }); + + it("applies a configured label without creating the repository label when creation is disabled", async () => { + const calls: string[] = []; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + calls.push(`${method} ${url}`); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/4/labels") && method === "GET") return Response.json([]); + if (url.includes("/issues/4/labels") && method === "POST") return Response.json([{ name: "gittensor" }]); + return new Response("unexpected", { status: 500 }); + }); + + const result = await ensurePullRequestLabel(createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem() }), 123, "JSONbored/gittensory", 4, "gittensor", { + createMissingLabel: false, + }); + + expect(result).toEqual({ applied: true, created: false }); + expect(calls.some((call) => call.includes("/repos/JSONbored/gittensory/labels"))).toBe(false); + }); +}); + +function generateRsaPrivateKeyPem(): string { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }); + return privateKey.export({ type: "pkcs1", format: "pem" }).toString(); +} diff --git a/test/unit/gittensor-api.test.ts b/test/unit/gittensor-api.test.ts index 376f5bc8a1..d4cae17d26 100644 --- a/test/unit/gittensor-api.test.ts +++ b/test/unit/gittensor-api.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot } from "../../src/gittensor/api"; +import { contributorRepoStatsFromGittensor, fetchGittensorContributorSnapshot, fetchOfficialGittensorMiner } from "../../src/gittensor/api"; describe("Gittensor API contributor snapshots", () => { afterEach(() => { @@ -182,4 +182,83 @@ describe("Gittensor API contributor snapshots", () => { vi.stubGlobal("fetch", async () => Response.json([{ githubUsername: "someone-else", githubId: "999" }])); await expect(fetchGittensorContributorSnapshot("jsonbored")).resolves.toBeNull(); }); + + it("classifies official miner detection without a complete identity and handles non-Error failures", async () => { + vi.stubGlobal("fetch", async () => Response.json([{ githubUsername: "jsonbored" }, { githubId: "49853598" }])); + await expect(fetchOfficialGittensorMiner("jsonbored")).resolves.toEqual({ status: "not_found" }); + await expect(fetchOfficialGittensorMiner("49853598")).resolves.toEqual({ status: "not_found" }); + + vi.stubGlobal("fetch", async () => { + throw "network down"; + }); + await expect(fetchOfficialGittensorMiner("jsonbored")).resolves.toEqual({ status: "unavailable", error: "unknown Gittensor API error" }); + }); + + it("maps optional Gittensor detail fields when present", async () => { + vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const url = input.toString(); + if (url.endsWith("/miners")) { + return Response.json([ + { + githubUsername: "jsonbored", + githubId: "49853598", + failedReason: null, + issueDiscoveryScore: "3", + issueTokenScore: "4", + isIssueEligible: true, + issueEligibleRepoCount: "2", + alphaPerDay: "1.25", + taoPerDay: "0.5", + usdPerDay: "100", + }, + ]); + } + if (url.endsWith("/miners/49853598")) { + return Response.json({ + uid: 29, + hotkey: "hotkey", + updatedAt: "2026-05-25T00:00:00.000Z", + repositories: [ + { + repositoryFullName: "owner/issues", + totalPrs: 0, + totalOpenIssues: 1, + totalClosedIssues: 1, + totalSolvedIssues: 1, + totalValidSolvedIssues: 1, + isIssueEligible: true, + issueCredibility: "0.9", + baseTotalScore: "12.5", + }, + ], + }); + } + if (url.endsWith("/miners/49853598/prs")) { + return Response.json([{ repository: "owner/repo", pullRequestNumber: 9, pullRequestTitle: undefined, prState: undefined, mergedAt: null, label: "bug", tokenScore: "7" }]); + } + if (url.endsWith("/miners/49853598/issues")) { + return Response.json({ issues: [{ labels: undefined }, { labels: [{ name: "bug" }] }] }); + } + return Response.json({}); + }); + + const snapshot = await fetchGittensorContributorSnapshot("jsonbored"); + + expect(snapshot).toMatchObject({ + uid: 29, + hotkey: "hotkey", + isIssueEligible: true, + issueEligibleRepoCount: 2, + alphaPerDay: 1.25, + taoPerDay: 0.5, + usdPerDay: 100, + updatedAt: "2026-05-25T00:00:00.000Z", + repositories: [expect.objectContaining({ repoFullName: "owner/issues", openIssues: 1, closedIssues: 1, validSolvedIssues: 1, issueCredibility: 0.9 })], + pullRequests: [expect.objectContaining({ repoFullName: "owner/repo", number: 9, title: "", state: "UNKNOWN", label: "bug", tokenScore: 7 })], + issueLabels: ["bug"], + }); + expect(contributorRepoStatsFromGittensor(snapshot)).toEqual( + expect.arrayContaining([expect.objectContaining({ login: "jsonbored", repoFullName: "owner/issues", issues: 2, lastActivityAt: "2026-05-25T00:00:00.000Z" })]), + ); + }); }); diff --git a/test/unit/json-utils.test.ts b/test/unit/json-utils.test.ts new file mode 100644 index 0000000000..0a72ce1981 --- /dev/null +++ b/test/unit/json-utils.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { errorMessage, jsonString, normalizeRepoFullName, parseJson, repoParts, strippedErrorMessage } from "../../src/utils/json"; + +describe("JSON and string utility helpers", () => { + it("keeps JSON parsing and stringification fallbacks explicit", () => { + expect(parseJson<{ ok: boolean }>('{"ok":true}', { ok: false })).toEqual({ ok: true }); + expect(parseJson("{bad-json", { ok: false })).toEqual({ ok: false }); + expect(parseJson(null, { ok: false })).toEqual({ ok: false }); + expect(jsonString(undefined)).toBe("null"); + expect(jsonString({ ok: true })).toBe('{"ok":true}'); + }); + + it("normalizes repo names and error messages without leaking arbitrary thrown values", () => { + expect(normalizeRepoFullName(" JSONbored/gittensory ")).toBe("JSONbored/gittensory"); + expect(repoParts("JSONbored/gittensory")).toEqual({ owner: "JSONbored", name: "gittensory" }); + expect(repoParts("")).toEqual({ owner: "", name: "" }); + expect(repoParts("owner/nested/name")).toEqual({ owner: "owner", name: "nested/name" }); + + expect(errorMessage(new Error("specific failure"))).toBe("specific failure"); + expect(errorMessage("string failure", "fallback failure")).toBe("fallback failure"); + expect(strippedErrorMessage(new Error("Error: wrapped failure"), "fallback failure")).toBe("wrapped failure"); + expect(strippedErrorMessage("string failure", "fallback failure")).toBe("fallback failure"); + }); +}); diff --git a/test/unit/local-branch.test.ts b/test/unit/local-branch.test.ts index 7674b4f3b3..4ba6a7b9bd 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -140,6 +140,69 @@ describe("local branch analysis", () => { expect(analysis.recommendedRerunCondition).toMatch(/git fetch origin/i); }); + it("distinguishes fresh, merge-base-stale, and large unverified base states", () => { + const fresh = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + baseRef: "origin/main", + baseSha: "base", + remoteTrackingSha: "base", + branchName: "docs-polish", + body: "Fixes #7", + changedFiles: [{ path: "README.md", additions: 1, deletions: 0, status: "modified" }], + }, + repo, + issues: [{ repoFullName: repo.fullName, number: 7, title: "Docs polish", state: "open", labels: [], linkedPrs: [] }], + pullRequests: [], + profile, + outcomeHistory, + scoringSnapshot, + scoringProfile, + }); + expect(fresh.baseFreshness.status).toBe("fresh"); + expect(fresh.recommendedRerunCondition).toBe("Rerun after any branch, base, or PR state changes before opening/submitting."); + + const mergeBaseStale = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + baseRef: "origin/main", + baseSha: "base", + mergeBaseSha: "older-base", + remoteTrackingSha: "base", + changedFiles: [{ path: "src/cache.ts", additions: 2, deletions: 1, status: "modified" }], + }, + repo, + issues: [], + pullRequests: [], + profile, + outcomeHistory, + scoringSnapshot, + scoringProfile, + }); + expect(mergeBaseStale.baseFreshness.status).toBe("stale"); + expect(mergeBaseStale.baseFreshness.warnings.join(" ")).toMatch(/Merge-base does not match/i); + + const largeUnverified = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + changedFiles: Array.from({ length: 50 }, (_, index) => ({ path: `src/file-${index}.ts`, additions: Number.NaN, deletions: undefined, status: "modified" as const })), + }, + repo, + issues: [], + pullRequests: [], + profile, + outcomeHistory, + scoringSnapshot, + scoringProfile, + }); + expect(largeUnverified.baseFreshness.status).toBe("possibly_stale"); + expect(largeUnverified.localFindings).toEqual(expect.arrayContaining([expect.objectContaining({ code: "stale_base_ref" })])); + expect(largeUnverified.preflight.localDiff.changedLineCount).toBe(0); + }); + it("keeps unregistered gittensory work in product/maintainer context instead of miner target context", () => { const analysis = buildLocalBranchAnalysis({ input: { @@ -164,6 +227,39 @@ describe("local branch analysis", () => { expect(analysis.rewardRisk.scoreBlockers).toEqual(expect.arrayContaining(["Maintainer-lane work is not normal outside-contributor reward evidence."])); }); + it("separates account maturity blockers from clean branch metadata when no pending scenario is supplied", () => { + const pressuredHistory: ContributorOutcomeHistory = { + ...outcomeHistory, + totals: { ...outcomeHistory.totals, openPullRequests: 4, credibility: 0.2 }, + repoOutcomes: [{ ...outcomeHistory.repoOutcomes[0]!, openPullRequests: 4, credibility: 0.2, closedPullRequestRate: 0 }], + }; + + const analysis = buildLocalBranchAnalysis({ + input: { + login: "oktofeesh1", + repoFullName: repo.fullName, + body: "Fixes #7", + changedFiles: [ + { path: "src/cache.ts", additions: 12, deletions: 1, status: "modified" }, + { path: "src/cache.test.ts", additions: 20, deletions: 0, status: "added" }, + ], + validation: [{ command: "npm test -- cache", status: "passed" }], + }, + repo, + issues: [{ repoFullName: repo.fullName, number: 7, title: "Cache edge", state: "open", labels: ["bug"], linkedPrs: [] }], + pullRequests: [], + profile, + outcomeHistory: pressuredHistory, + scoringSnapshot, + scoringProfile, + }); + + expect(analysis.branchQualityBlockers).toEqual([]); + expect(analysis.accountStateBlockers.join(" ")).toMatch(/Open PR count|Credibility/i); + expect(analysis.recommendedRerunCondition).toBe("Rerun after account/queue maturity blockers clear."); + expect(analysis.nextActions[0]?.actionKind).not.toBe("land_existing_prs"); + }); + it("handles sparse metadata, failed validation, binary changes, and commit-title fallback", () => { const analysis = buildLocalBranchAnalysis({ input: { diff --git a/test/unit/mcp-cli.test.ts b/test/unit/mcp-cli.test.ts index 3c15e2800d..9dcc5436d0 100644 --- a/test/unit/mcp-cli.test.ts +++ b/test/unit/mcp-cli.test.ts @@ -50,6 +50,27 @@ describe("gittensory-mcp CLI", () => { ); }); + it("reports package status and prints the packaged changelog", async () => { + tempDir = mkdtempSync(join(tmpdir(), "gittensory-cli-")); + const url = await startFixtureServer(); + const status = JSON.parse( + await runAsync(["status", "--json"], { + GITTENSORY_API_URL: url, + GITTENSORY_TOKEN: "session-token", + GITTENSORY_CONFIG_DIR: tempDir, + GITTENSORY_SKIP_NPM_VERSION_CHECK: "true", + }), + ) as { package: { name: string; version: string; latestStatus: string }; api: { status: string }; auth: { login: string } }; + + expect(status.package).toMatchObject({ name: "@jsonbored/gittensory-mcp", version: "0.1.4", latestStatus: "skipped" }); + expect(status.api.status).toBe("ok"); + expect(status.auth.login).toBe("JSONbored"); + + const changelog = JSON.parse(run(["changelog", "--json"])) as { package: { version: string }; changelog: string }; + expect(changelog.package.version).toBe("0.1.4"); + expect(changelog.changelog).toContain("# Changelog"); + }); + it("rejects unsupported client snippets", () => { expect(() => run(["init-client", "--print", "other"])).toThrow(/Unsupported client/); }); diff --git a/test/unit/openapi.test.ts b/test/unit/openapi.test.ts index 54cf628622..74deeda1d2 100644 --- a/test/unit/openapi.test.ts +++ b/test/unit/openapi.test.ts @@ -10,6 +10,8 @@ describe("OpenAPI contract", () => { expect(spec.paths["/v1/readiness"]).toBeDefined(); expect(spec.paths["/v1/sync/status"]).toBeDefined(); expect(spec.paths["/v1/repos/{owner}/{repo}/intelligence"]).toBeDefined(); + expect(spec.paths["/v1/repos/{owner}/{repo}/registration-readiness"]).toBeDefined(); + expect(spec.paths["/v1/repos/{owner}/{repo}/gittensor-config-recommendation"]).toBeDefined(); expect(spec.paths["/v1/repos/{owner}/{repo}/pulls/{number}/maintainer-packet"]).toBeDefined(); expect(spec.paths["/v1/repos/{owner}/{repo}/pulls/{number}/reviewability"]).toBeDefined(); expect(spec.paths["/v1/contributors/{login}/profile"]).toBeDefined(); @@ -55,6 +57,8 @@ describe("OpenAPI contract", () => { expect(spec.components?.schemas?.DecisionPackRefreshNeeded).toBeDefined(); expect(spec.components?.schemas?.RepoDecisionResponse).toBeDefined(); expect(spec.components?.schemas?.RepoIntelligence).toBeDefined(); + expect(spec.components?.schemas?.RegistrationReadiness).toBeDefined(); + expect(spec.components?.schemas?.GittensorConfigRecommendation).toBeDefined(); expect(spec.components?.schemas?.PullRequestMaintainerPacket).toBeDefined(); expect(spec.components?.schemas?.PullRequestReviewability).toBeDefined(); expect(spec.components?.schemas?.LocalBranchAnalysis).toBeDefined(); diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts index 3c2a2a1ca1..8c46b42cb6 100644 --- a/test/unit/queue.test.ts +++ b/test/unit/queue.test.ts @@ -206,6 +206,46 @@ describe("queue processors", () => { ); }); + it("marks fidelity repair completed when only signal refreshes are needed", async () => { + const sent: Array<{ message: import("../../src/types").JobMessage; options?: QueueSendOptions }> = []; + const env = createTestEnv({ + JOBS: { + async send(message: import("../../src/types").JobMessage, options?: QueueSendOptions) { + sent.push(options ? { message, options } : { message }); + }, + } as unknown as Queue, + }); + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { + "JSONbored/gittensory": { emission_share: 0.01, issue_discovery_share: 0, label_multipliers: {}, trusted_label_pipeline: false }, + "we-promise/sure": { emission_share: 0.02, issue_discovery_share: 0, label_multipliers: {}, trusted_label_pipeline: false }, + }, + { kind: "raw-github", url: "fixture://registry" }, + "2026-05-25T00:00:00.000Z", + ), + ); + for (const repoFullName of ["JSONbored/gittensory", "we-promise/sure"]) { + await upsertRepoSyncSegment(env, completeSegment(repoFullName, "labels")); + await upsertRepoSyncSegment(env, completeSegment(repoFullName, "open_issues")); + await upsertRepoSyncSegment(env, completeSegment(repoFullName, "open_pull_requests")); + } + + await processJob(env, { type: "repair-data-fidelity", requestedBy: "api" }); + + expect(sent).toEqual([ + { message: expect.objectContaining({ type: "generate-signal-snapshots", repoFullName: "JSONbored/gittensory" }) }, + { message: expect.objectContaining({ type: "generate-signal-snapshots", repoFullName: "we-promise/sure" }), options: { delaySeconds: 70 } }, + ]); + const audit = await env.DB.prepare("select outcome, metadata_json from audit_events where event_type = ?").bind("sync.fidelity_repair").first<{ + outcome: string; + metadata_json: string; + }>(); + expect(audit?.outcome).toBe("completed"); + expect(JSON.parse(audit?.metadata_json ?? "{}")).toMatchObject({ repairCount: 0, signalRefreshCount: 2 }); + }); + it("fans out signal snapshot generation instead of doing all repo work inline", async () => { const sent: import("../../src/types").JobMessage[] = []; const env = createTestEnv({ @@ -344,7 +384,7 @@ describe("queue processors", () => { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }, repositories: [{ name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }], @@ -357,7 +397,7 @@ describe("queue processors", () => { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read", issues: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }); } @@ -369,7 +409,7 @@ describe("queue processors", () => { }); it("processes GitHub webhook jobs for PRs, issues, comments-off, comment-attempt, and deleted installs", async () => { - const env = createTestEnv(); + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); await persistRegistrySnapshot( env, normalizeRegistryPayload( @@ -387,10 +427,80 @@ describe("queue processors", () => { labels: [{ name: "bug" }], body: "Fixes #1", }); - vi.stubGlobal("fetch", async (input: RequestInfo | URL) => { + const visibleCalls = { comments: 0, labelsCreated: 0, labelsApplied: 0, checks: 0 }; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { const url = input.toString(); + const method = init?.method ?? "GET"; + if (url === "https://api.gittensor.io/miners") { + return Response.json([ + { + uid: 7, + githubUsername: "oktofeesh1", + githubId: "123", + totalPrs: 4, + totalMergedPrs: 3, + totalOpenPrs: 1, + totalClosedPrs: 0, + totalOpenIssues: 0, + totalClosedIssues: 0, + totalSolvedIssues: 0, + totalValidSolvedIssues: 0, + isEligible: true, + credibility: 1, + eligibleRepoCount: 1, + hotkey: "must-not-leak", + }, + ]); + } + if (url === "https://api.gittensor.io/miners/123") { + return Response.json({ + repositories: [ + { + repositoryFullName: "JSONbored/gittensory", + totalPrs: "4", + totalMergedPrs: "3", + totalOpenPrs: "1", + totalClosedPrs: "0", + totalOpenIssues: "0", + totalClosedIssues: "0", + isEligible: true, + credibility: "1.000000", + }, + ], + }); + } + if (url === "https://api.gittensor.io/miners/123/prs") return Response.json([]); + if (url === "https://mirror.gittensor.io/api/v1/miners/123/issues") return Response.json({ issues: [] }); if (url.endsWith("/users/oktofeesh1")) return Response.json({ login: "oktofeesh1", public_repos: 2, followers: 1 }); if (url.includes("/users/oktofeesh1/repos")) return Response.json([{ language: "TypeScript" }]); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/3/comments") && method === "GET") return Response.json([]); + if (url.includes("/issues/3/comments") && method === "POST") { + const body = JSON.parse(String(init?.body ?? "{}")) as { body?: string }; + expect(body.body).toContain(""); + expect(body.body).toContain("Confirmed Gittensor miner: yes"); + expect(body.body).not.toMatch(/reviewability|likely_duplicate|reward|scoreability|estimated score|wallet|hotkey|trust score|payout|farming/i); + visibleCalls.comments += 1; + return Response.json({ id: 1, html_url: "https://github.com/comment/1" }, { status: 201 }); + } + if (url.includes("/issues/3/labels") && method === "GET") return Response.json([]); + if (url.includes("/issues/3/labels") && method === "POST") { + const body = JSON.parse(String(init?.body ?? "{}")) as { labels?: string[] }; + expect(body.labels).toEqual(["gittensor"]); + visibleCalls.labelsApplied += 1; + return Response.json([{ name: "gittensor" }]); + } + if (url.includes("/repos/JSONbored/gittensory/labels") && !url.includes("/issues/") && method === "GET") return Response.json([]); + if (url.includes("/repos/JSONbored/gittensory/labels") && !url.includes("/issues/") && method === "POST") { + const body = JSON.parse(String(init?.body ?? "{}")) as { name?: string }; + expect(body.name).toBe("gittensor"); + visibleCalls.labelsCreated += 1; + return Response.json({ name: "gittensor" }, { status: 201 }); + } + if (url.includes("/check-runs")) { + visibleCalls.checks += 1; + return new Response("checks disabled", { status: 500 }); + } return new Response("not found", { status: 404 }); }); @@ -399,12 +509,23 @@ describe("queue processors", () => { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, repository_selection: "selected", - permissions: { checks: "write", metadata: "read", pull_requests: "read" }, + permissions: { metadata: "read", pull_requests: "read", issues: "write" }, events: ["issues", "pull_request", "repository"], }, repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, }; + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + commentMode: "off", + publicSignalLevel: "standard", + publicSurface: "off", + autoLabelEnabled: false, + checkRunMode: "off", + checkRunDetailLevel: "minimal", + backfillEnabled: true, + privateTrustEnabled: true, + }); await processJob(env, { type: "github-webhook", deliveryId: "pr-off", @@ -428,8 +549,10 @@ describe("queue processors", () => { repoFullName: "JSONbored/gittensory", commentMode: "detected_contributors_only", publicSignalLevel: "standard", - checkRunMode: "enabled", - checkRunDetailLevel: "standard", + publicSurface: "comment_and_label", + autoLabelEnabled: true, + checkRunMode: "off", + checkRunDetailLevel: "minimal", backfillEnabled: true, privateTrustEnabled: true, }); @@ -473,8 +596,10 @@ describe("queue processors", () => { repoFullName: "JSONbored/gittensory", commentMode: "all_prs", publicSignalLevel: "minimal", - checkRunMode: "enabled", - checkRunDetailLevel: "standard", + publicSurface: "comment_and_label", + autoLabelEnabled: true, + checkRunMode: "off", + checkRunDetailLevel: "minimal", backfillEnabled: true, privateTrustEnabled: true, }); @@ -519,9 +644,242 @@ describe("queue processors", () => { eventName: "installation", payload: { action: "deleted", installation: { id: 123 } }, }); + + expect(visibleCalls).toEqual({ comments: 1, labelsCreated: 1, labelsApplied: 1, checks: 0 }); + const skipped = await env.DB.prepare("select detail from audit_events where event_type = ? order by created_at").bind("github_app.pr_visibility_skipped").all<{ + detail: string; + }>(); + expect(skipped.results.map((event) => event.detail)).toEqual(expect.arrayContaining(["not_official_gittensor_miner", "missing_author"])); }); - it("records webhook processing errors when GitHub check creation fails", async () => { + it("skips bots and maintainer authors, and keeps explicitly enabled checks minimal", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { "JSONbored/gittensory": { emission_share: 0.01, issue_discovery_share: 0 } }, + { kind: "raw-github", url: "https://example.test" }, + "2026-05-23T00:00:00.000Z", + ), + ); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + commentMode: "off", + publicSurface: "off", + autoLabelEnabled: false, + checkRunMode: "enabled", + }); + const calls = { minerList: 0, checks: 0 }; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + if (url === "https://api.gittensor.io/miners") { + calls.minerList += 1; + return Response.json([{ githubUsername: "oktofeesh1", githubId: "123", totalPrs: 1, totalMergedPrs: 1, isEligible: true, credibility: 1 }]); + } + if (url === "https://api.gittensor.io/miners/123") return Response.json({ repositories: [] }); + if (url === "https://api.gittensor.io/miners/123/prs") return Response.json([]); + if (url === "https://mirror.gittensor.io/api/v1/miners/123/issues") return Response.json({ issues: [] }); + if (url.endsWith("/users/oktofeesh1")) return Response.json({ login: "oktofeesh1" }); + if (url.includes("/users/oktofeesh1/repos")) return Response.json([]); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/commits/abc123/check-runs")) return Response.json({ total_count: 0, check_runs: [] }); + if (url.includes("/check-runs") && method === "POST") { + const body = JSON.parse(String(init?.body ?? "{}")) as { output?: { title?: string; text?: string } }; + expect(body.output).toMatchObject({ title: "Gittensory context checked", text: "No detailed findings are published in check runs." }); + calls.checks += 1; + return Response.json({ id: 99 }, { status: 201 }); + } + return new Response("not found", { status: 404 }); + }); + const basePayload = { + installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } }, + repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, + }; + + await processJob(env, { + type: "github-webhook", + deliveryId: "bot-skip", + eventName: "pull_request", + payload: { + action: "opened", + ...basePayload, + pull_request: { number: 20, title: "Dependency update", state: "open", user: { login: "renovate[bot]", type: "Bot" }, labels: [], body: "" }, + }, + }); + await processJob(env, { + type: "github-webhook", + deliveryId: "maintainer-skip", + eventName: "pull_request", + payload: { + action: "opened", + ...basePayload, + pull_request: { number: 21, title: "Maintainer work", state: "open", user: { login: "jsonbored" }, author_association: "OWNER", labels: [], body: "" }, + }, + }); + await processJob(env, { + type: "github-webhook", + deliveryId: "check-enabled", + eventName: "pull_request", + payload: { + action: "opened", + ...basePayload, + pull_request: { number: 22, title: "Miner work", state: "open", user: { login: "oktofeesh1" }, head: { sha: "abc123" }, labels: [], body: "No issue needed." }, + }, + }); + + expect(calls).toEqual({ minerList: 1, checks: 1 }); + const skipped = await env.DB.prepare("select detail from audit_events where event_type = ? order by created_at").bind("github_app.pr_visibility_skipped").all<{ + detail: string; + }>(); + expect(skipped.results.map((event) => event.detail)).toEqual(expect.arrayContaining(["bot_author", "maintainer_author"])); + }); + + it("records webhook processing when public comment publishing fails after miner confirmation", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { "JSONbored/gittensory": { emission_share: 0.01, issue_discovery_share: 0 } }, + { kind: "raw-github", url: "https://example.test" }, + "2026-05-23T00:00:00.000Z", + ), + ); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + commentMode: "detected_contributors_only", + publicSurface: "comment_only", + autoLabelEnabled: true, + createMissingLabel: true, + checkRunMode: "off", + }); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + if (url === "https://api.gittensor.io/miners") return Response.json([{ githubUsername: "oktofeesh1", githubId: "123", totalPrs: 2, totalMergedPrs: 2, isEligible: true, credibility: 1 }]); + if (url === "https://api.gittensor.io/miners/123") return Response.json({ repositories: [] }); + if (url === "https://api.gittensor.io/miners/123/prs") return Response.json([]); + if (url === "https://mirror.gittensor.io/api/v1/miners/123/issues") return Response.json({ issues: [] }); + if (url.endsWith("/users/oktofeesh1")) return Response.json({ login: "oktofeesh1" }); + if (url.includes("/users/oktofeesh1/repos")) return Response.json([]); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/30/comments") && method === "GET") return Response.json([]); + if (url.includes("/issues/30/comments") && method === "POST") return new Response("comment failed", { status: 503 }); + return new Response("not found", { status: 404 }); + }); + + await expect( + processJob(env, { + type: "github-webhook", + deliveryId: "comment-failure", + eventName: "pull_request", + payload: { + action: "opened", + installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } }, + repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, + pull_request: { number: 30, title: "Miner work", state: "open", user: { login: "oktofeesh1" }, labels: [], body: "Fixes #1" }, + }, + }), + ).resolves.toBeUndefined(); + + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("pr_public_surface_failed")); + const webhook = await env.DB.prepare("select status from webhook_events where delivery_id = ?").bind("comment-failure").first<{ status: string }>(); + expect(webhook?.status).toBe("processed"); + const published = await env.DB.prepare("select event_type from audit_events where event_type = ?").bind("github_app.pr_public_surface_published").all(); + expect(published.results).toEqual([]); + errorSpy.mockRestore(); + }); + + it("keeps repository and PR webhook processing internal when installation context is absent", async () => { + const env = createTestEnv(); + await processJob(env, { + type: "github-webhook", + deliveryId: "repositories-without-installation", + eventName: "repository", + payload: { + action: "created", + repositories: [{ name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }], + }, + }); + await processJob(env, { + type: "github-webhook", + deliveryId: "pr-without-installation", + eventName: "pull_request", + payload: { + action: "opened", + repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, + pull_request: { number: 44, title: "Internal-only PR", state: "open", user: { login: "oktofeesh1" }, labels: [] }, + }, + }); + + expect(await listPullRequests(env, "JSONbored/gittensory")).toEqual(expect.arrayContaining([expect.objectContaining({ number: 44, body: null })])); + const events = await env.DB.prepare("select delivery_id, status from webhook_events where delivery_id in (?, ?) order by delivery_id") + .bind("pr-without-installation", "repositories-without-installation") + .all<{ delivery_id: string; status: string }>(); + expect(events.results).toEqual([ + { delivery_id: "pr-without-installation", status: "processed" }, + { delivery_id: "repositories-without-installation", status: "processed" }, + ]); + }); + + it("supports label-only public surfaces for confirmed miners", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await persistRegistrySnapshot( + env, + normalizeRegistryPayload( + { "JSONbored/gittensory": { emission_share: 0.01, issue_discovery_share: 0 } }, + { kind: "raw-github", url: "https://example.test" }, + "2026-05-23T00:00:00.000Z", + ), + ); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + commentMode: "detected_contributors_only", + publicSurface: "label_only", + autoLabelEnabled: true, + createMissingLabel: false, + checkRunMode: "off", + }); + const calls = { comments: 0, labels: 0 }; + vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { + const url = input.toString(); + const method = init?.method ?? "GET"; + if (url === "https://api.gittensor.io/miners") return Response.json([{ githubUsername: "oktofeesh1", githubId: "123", totalPrs: 1, totalMergedPrs: 1, isEligible: true, credibility: 1 }]); + if (url === "https://api.gittensor.io/miners/123") return Response.json({ repositories: [] }); + if (url === "https://api.gittensor.io/miners/123/prs") return Response.json([]); + if (url === "https://mirror.gittensor.io/api/v1/miners/123/issues") return Response.json({ issues: [] }); + if (url.endsWith("/users/oktofeesh1")) return Response.json({ login: "oktofeesh1" }); + if (url.includes("/users/oktofeesh1/repos")) return Response.json([]); + if (url.includes("/access_tokens")) return Response.json({ token: "installation-token" }); + if (url.includes("/issues/45/comments")) { + calls.comments += 1; + return Response.json([]); + } + if (url.includes("/issues/45/labels") && method === "GET") return Response.json([]); + if (url.includes("/issues/45/labels") && method === "POST") { + calls.labels += 1; + return Response.json([{ name: "gittensor" }]); + } + return new Response("not found", { status: 404 }); + }); + + await processJob(env, { + type: "github-webhook", + deliveryId: "label-only", + eventName: "pull_request", + payload: { + action: "opened", + installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } }, + repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: true, owner: { login: "JSONbored" } }, + pull_request: { number: 45, title: "Miner label-only work", state: "open", user: { login: "oktofeesh1" }, labels: [], body: "Fixes #1" }, + }, + }); + + expect(calls).toEqual({ comments: 0, labels: 1 }); + }); + + it("fails closed when official miner detection is unavailable", async () => { const env = createTestEnv(); const payload = { action: "opened", @@ -538,7 +896,17 @@ describe("queue processors", () => { }, }; - await expect(processJob(env, { type: "github-webhook", deliveryId: "check-fail", eventName: "pull_request", payload })).rejects.toThrow(); + vi.stubGlobal("fetch", async () => new Response("gittensor unavailable", { status: 503 })); + + await expect(processJob(env, { type: "github-webhook", deliveryId: "miner-unavailable", eventName: "pull_request", payload })).resolves.toBeUndefined(); + const audit = await env.DB.prepare("select event_type, outcome, detail from audit_events where target_key = ?") + .bind("JSONbored/gittensory#10") + .all<{ event_type: string; outcome: string; detail: string }>(); + expect(audit.results).toEqual( + expect.arrayContaining([ + expect.objectContaining({ event_type: "github_app.miner_detection_unavailable", outcome: "error", detail: expect.stringContaining("Gittensor API failed") }), + ]), + ); }); }); diff --git a/test/unit/rules.test.ts b/test/unit/rules.test.ts index 296d14f50b..9912059889 100644 --- a/test/unit/rules.test.ts +++ b/test/unit/rules.test.ts @@ -20,7 +20,7 @@ const repo: RepositoryRecord = { }; describe("advisory rules", () => { - it("flags missing linked issues on PR advisories", () => { + it("suppresses missing linked issues on direct-contribution PR advisories by default", () => { const pr: PullRequestRecord = { repoFullName: repo.fullName, number: 12, @@ -35,9 +35,28 @@ describe("advisory rules", () => { const advisory = buildPullRequestAdvisory(repo, pr); + expect(advisory.conclusion).toBe("success"); + expect(advisory.findings.map((finding) => finding.code)).not.toContain("missing_linked_issue"); + expect(formatCheckRunOutput(advisory).text).not.toMatch(/reward|farming/i); + }); + + it("flags missing linked issues only when a repo explicitly requires linkage", () => { + const pr: PullRequestRecord = { + repoFullName: repo.fullName, + number: 12, + title: "Add registry sync", + state: "open", + authorLogin: "oktofeesh1", + authorAssociation: "NONE", + headSha: "abc123", + labels: ["feature"], + linkedIssues: [], + }; + + const advisory = buildPullRequestAdvisory(repo, pr, { requireLinkedIssue: true }); + expect(advisory.conclusion).toBe("neutral"); expect(advisory.findings.map((finding) => finding.code)).toContain("missing_linked_issue"); - expect(formatCheckRunOutput(advisory).text).not.toMatch(/reward|farming/i); }); it("marks unknown repositories as action required", () => { @@ -45,6 +64,11 @@ describe("advisory rules", () => { expect(advisory.conclusion).toBe("action_required"); }); + it("handles uncached PR and issue advisories for unknown repositories", () => { + expect(buildPullRequestAdvisory(null, null).findings.map((finding) => finding.code)).toEqual(["repo_not_registered", "pr_not_cached"]); + expect(buildIssueAdvisory(null, null).findings.map((finding) => finding.code)).toEqual(["repo_not_registered", "issue_not_cached"]); + }); + it("warns when an issue already has linked PRs", () => { const issue: IssueRecord = { repoFullName: repo.fullName, @@ -85,7 +109,7 @@ describe("advisory rules", () => { expect(advisory.findings.map((finding) => finding.code)).toContain("duplicate_pr_risk"); }); - it("adds private reviewability context to check output without reward language", () => { + it("keeps private reviewability context out of check output", () => { const pr: PullRequestRecord = { repoFullName: repo.fullName, number: 12, @@ -98,13 +122,12 @@ describe("advisory rules", () => { linkedIssues: [4], }; - const advisory = buildPullRequestAdvisory(repo, pr, { - reviewabilityText: "Reviewability 72/100; action needs_author; missing tests and duplicate context should be cleared first.", - }); + const advisory = buildPullRequestAdvisory(repo, pr); + const output = formatCheckRunOutput(advisory); - expect(advisory.findings.map((finding) => finding.code)).toContain("private_reviewability_context"); - expect(formatCheckRunOutput(advisory).text).toContain("Reviewability 72/100"); - expect(formatCheckRunOutput(advisory).text).not.toMatch(/reward|farming|wallet|hotkey/i); + expect(advisory.findings.map((finding) => finding.code)).not.toContain("private_reviewability_context"); + expect(output.text).not.toMatch(/reviewability|likely_duplicate|needs_author|reward|farming|wallet|hotkey/i); + expect(output.title).toBe("Gittensory context checked"); }); it("covers repository config lane advisories", () => { @@ -167,6 +190,35 @@ describe("advisory rules", () => { expect(uncachedPr.findings.map((finding) => finding.code)).toContain("pr_not_cached"); expect(issueAdvisory.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["issue_not_open", "issue_discovery_not_configured"])); - expect(formatCheckRunOutput({ ...uncachedPr, findings: [] }).text).toBe("No advisory findings."); + expect(formatCheckRunOutput({ ...uncachedPr, findings: [] }).text).toContain("No detailed findings are published"); + }); + + it("separates issue-discovery-only issues from clean split-lane issue advisories", () => { + const issue: IssueRecord = { + repoFullName: repo.fullName, + number: 33, + title: "Actionable issue", + state: "open", + authorLogin: "reporter", + labels: [], + linkedPrs: [], + }; + const issueDiscoveryRepo: RepositoryRecord = { + ...repo, + registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 1 }, + }; + const splitRepo: RepositoryRecord = { + ...repo, + registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 0.5 }, + }; + + const issueOnly = buildIssueAdvisory(issueDiscoveryRepo, issue); + const cleanSplit = buildIssueAdvisory(splitRepo, issue); + + expect(issueOnly.findings.map((finding) => finding.code)).toContain("direct_pr_pool_disabled"); + expect(issueOnly.findings.map((finding) => finding.code)).not.toContain("issue_discovery_not_configured"); + expect(cleanSplit.findings).toEqual([]); + expect(cleanSplit.summary).toBe("Issue advisory generated."); + expect(cleanSplit.conclusion).toBe("success"); }); }); diff --git a/test/unit/signals-coverage.test.ts b/test/unit/signals-coverage.test.ts new file mode 100644 index 0000000000..88e0706c1b --- /dev/null +++ b/test/unit/signals-coverage.test.ts @@ -0,0 +1,671 @@ +import { describe, expect, it } from "vitest"; +import { + buildBountyAdvisory, + buildCollisionReport, + buildConfigQuality, + buildContributorFit, + buildContributorOpportunities, + buildContributorOutcomeHistory, + buildContributorPatternReport, + buildContributorProfile, + buildContributorScoringProfile, + buildContributorStrategy, + buildBurdenForecast, + buildIssueQualityReport, + buildLabelAudit, + buildLaneAdvice, + buildLocalDiffPreflightResult, + buildMaintainerPacket, + buildPreflightResult, + buildPublicPrIntelligenceComment, + buildQueueHealth, + buildRoleContext, + detectGittensorContributor, + shouldPublishPrIntelligenceComment, +} from "../../src/signals/engine"; +import { + buildContributorRewardRiskStrategy, + buildMaintainerNoiseReport, + buildPullRequestReviewability, + buildRepoRewardRisk, +} from "../../src/signals/reward-risk"; +import type { + ContributorRepoStatRecord, + CheckSummaryRecord, + IssueRecord, + PullRequestFileRecord, + PullRequestRecord, + RepoLabelRecord, + RegistryRepoConfig, + RepositoryRecord, + RepositorySettings, + ScoringModelSnapshotRecord, +} from "../../src/types"; + +describe("signal coverage edge cases", () => { + it("branches lane, label, config, and public comment publishing decisions", () => { + const directRepo = repo("owner/direct", { issueDiscoveryShare: 0, labelMultipliers: { bug: 1.2 }, trustedLabelPipeline: true }); + const issueRepo = repo("owner/issues", { issueDiscoveryShare: 1 }); + const splitRepo = repo("owner/split", { issueDiscoveryShare: 0.4 }); + const inactiveRepo = repo("owner/inactive", { emissionShare: 0 }); + const missingRepo = { ...directRepo, isRegistered: false, registryConfig: null }; + const emptyTrusted = repo("owner/empty-trusted", { labelMultipliers: {}, trustedLabelPipeline: true }); + const settings = repoSettings(directRepo.fullName); + + expect(buildLaneAdvice(null, "missing/repo").lane).toBe("unknown"); + expect(buildLaneAdvice(missingRepo, missingRepo.fullName).lane).toBe("unknown"); + expect(buildLaneAdvice(inactiveRepo, inactiveRepo.fullName).lane).toBe("inactive"); + expect(buildLaneAdvice(directRepo, directRepo.fullName).lane).toBe("direct_pr"); + expect(buildLaneAdvice(issueRepo, issueRepo.fullName).lane).toBe("issue_discovery"); + expect(buildLaneAdvice(splitRepo, splitRepo.fullName).lane).toBe("split"); + + const emptyQuality = buildConfigQuality(emptyTrusted, [], [], emptyTrusted.fullName); + const unknownQuality = buildConfigQuality(null, [], [], "missing/repo"); + const inactiveQuality = buildConfigQuality(inactiveRepo, [], [], inactiveRepo.fullName); + expect(emptyQuality.findings.map((finding) => finding.code)).toContain("trusted_labels_without_multipliers"); + expect(unknownQuality.findings.map((finding) => finding.code)).toContain("registry_unknown"); + expect(inactiveQuality.findings.map((finding) => finding.code)).toContain("inactive_allocation"); + + const audit = buildLabelAudit(directRepo, [], [], [], directRepo.fullName); + expect(audit.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["trusted_labels_missing", "configured_labels_unused"])); + + const officialDetection = { detected: true, source: "official_gittensor_api" as const, reason: "official", priorPullRequests: 3, priorMergedPullRequests: 2, priorIssues: 1 }; + const cachedDetection = { ...officialDetection, source: "github_cache" as const }; + expect(shouldPublishPrIntelligenceComment(settings, officialDetection)).toBe(true); + expect(shouldPublishPrIntelligenceComment({ ...settings, commentMode: "off" }, officialDetection)).toBe(false); + expect(shouldPublishPrIntelligenceComment({ ...settings, publicSurface: "label_only" }, officialDetection)).toBe(false); + expect(shouldPublishPrIntelligenceComment(settings, cachedDetection)).toBe(false); + }); + + it("ranks opportunities and contributor strategy from cached edge evidence", () => { + const directRepo = repo("owner/direct", { issueDiscoveryShare: 0, labelMultipliers: { bug: 1.2 } }); + const issueRepo = repo("owner/issues", { issueDiscoveryShare: 1, labelMultipliers: { security: 1.4 } }); + const inactiveRepo = repo("owner/inactive", { emissionShare: 0 }); + const profile = buildContributorProfile( + "dev", + { login: "dev", topLanguages: ["Python"], source: "github" }, + [], + [], + [ + { login: "dev", repoFullName: directRepo.fullName, pullRequests: 2, mergedPullRequests: 1, openPullRequests: 1, issues: 1, stalePullRequests: 1, unlinkedPullRequests: 1, dominantLabels: ["bug"] }, + ], + ); + const issues: IssueRecord[] = [ + issue(directRepo.fullName, 1, "Fix direct cache bug", { labels: ["bug"] }), + issue(issueRepo.fullName, 2, "Report security issue", { labels: ["security"] }), + issue(inactiveRepo.fullName, 3, "Inactive issue", { labels: [] }), + ]; + const busyPrs = Array.from({ length: 8 }, (_, index) => pr(issueRepo.fullName, index + 10, "Busy PR", { linkedIssues: [], authorLogin: `dev${index}` })); + const opportunities = buildContributorOpportunities(profile, [directRepo, issueRepo, inactiveRepo], issues, busyPrs); + const fit = buildContributorFit( + profile, + [directRepo, issueRepo, inactiveRepo], + issues, + busyPrs, + [ + { repoFullName: directRepo.fullName, status: "success", sourceKind: "github", primaryLanguage: null, openIssuesCount: 1, openPullRequestsCount: 0, recentMergedPullRequestsCount: 0, warnings: [] }, + { repoFullName: issueRepo.fullName, status: "partial", sourceKind: "github", primaryLanguage: "Rust", openIssuesCount: 1, openPullRequestsCount: 8, recentMergedPullRequestsCount: 0, warnings: ["sampled"] }, + ], + [ + { login: "dev", repoFullName: directRepo.fullName, pullRequests: 2, mergedPullRequests: 1, openPullRequests: 1, issues: 1, stalePullRequests: 1, unlinkedPullRequests: 1, dominantLabels: ["bug"] }, + ], + ); + const scoringProfile = buildContributorScoringProfile({ login: "dev", fit, scoringSnapshot: scoringSnapshot() }); + const strategy = buildContributorStrategy({ login: "dev", fit, scoringProfile, scoringSnapshot: scoringSnapshot() }); + + expect(opportunities.map((opportunity) => opportunity.repoFullName)).toEqual(expect.arrayContaining([directRepo.fullName, issueRepo.fullName, inactiveRepo.fullName])); + expect(opportunities.find((opportunity) => opportunity.repoFullName === issueRepo.fullName)?.warnings).toEqual( + expect.arrayContaining(["This repo has a busy open PR queue.", "This repo is not a direct-PR-first lane."]), + ); + expect(opportunities.find((opportunity) => opportunity.repoFullName === inactiveRepo.fullName)?.warnings).toContain("Gittensory cannot recommend this as a strong contribution target right now."); + expect(fit.findings.map((finding) => finding.code)).toContain("no_language_fit"); + expect(strategy.nextActions).toEqual(expect.arrayContaining(["Clean up linked issue/context patterns before adding more open PRs.", "Prefer repos where the changed files match prior language evidence, or keep first submissions small."])); + }); + + it("separates cached outcome history, maintainer role sources, and contributor detections", () => { + const directRepo = repo("owner/direct"); + const ownerRepo = repo("owner/project"); + const profile = buildContributorProfile( + "dev", + { login: "dev", topLanguages: ["Go"], source: "github" }, + [ + pr(directRepo.fullName, 1, "Merged focused fix", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z", authorLogin: "dev", labels: ["bug"] }), + pr(directRepo.fullName, 2, "Closed failed fix", { state: "closed", authorLogin: "dev", labels: ["bug"] }), + pr(directRepo.fullName, 3, "Open pressure", { state: "open", authorLogin: "dev", labels: ["bug"] }), + ], + [issue(directRepo.fullName, 10, "Closed issue", { state: "closed", authorLogin: "dev" })], + ); + const history = buildContributorOutcomeHistory({ + login: "dev", + profile, + repositories: [directRepo, ownerRepo], + pullRequests: [ + pr(directRepo.fullName, 1, "Merged focused fix", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z", authorLogin: "dev", labels: ["bug"] }), + pr(directRepo.fullName, 2, "Closed failed fix", { state: "closed", authorLogin: "dev", labels: ["bug"] }), + pr(directRepo.fullName, 3, "Open pressure", { state: "open", authorLogin: "dev", labels: ["bug"] }), + pr(ownerRepo.fullName, 4, "Owner work", { state: "open", authorLogin: "owner", authorAssociation: "OWNER" }), + ], + issues: [issue(directRepo.fullName, 10, "Closed issue", { state: "closed", authorLogin: "dev" })], + repoStats: [], + }); + + expect(buildRoleContext({ login: "owner", repo: ownerRepo, repoFullName: ownerRepo.fullName, pullRequests: [pr(ownerRepo.fullName, 4, "Owner work", { authorLogin: "owner", authorAssociation: "OWNER" })] })).toMatchObject({ + role: "owner", + source: "repo_owner_match", + maintainerLane: true, + }); + expect(buildRoleContext({ login: "member", repo: directRepo, repoFullName: directRepo.fullName, pullRequests: [pr(directRepo.fullName, 5, "Member work", { authorLogin: "member", authorAssociation: "MEMBER" })] })).toMatchObject({ + role: "org_member", + source: "github_association", + maintainerLane: true, + }); + expect(detectGittensorContributor("dev", pr(directRepo.fullName, 5, "Current", { authorLogin: "dev" }), history.repoOutcomes.map((outcome, index) => pr(outcome.repoFullName, index + 100, "Prior", { authorLogin: "dev", state: index === 0 ? "merged" : "open" })), [])).toMatchObject({ + detected: true, + priorMergedPullRequests: 1, + }); + expect(detectGittensorContributor("issue-dev", pr(directRepo.fullName, 6, "Current", { authorLogin: "issue-dev" }), [], [issue(directRepo.fullName, 99, "Prior issue", { authorLogin: "issue-dev" })])).toMatchObject({ + detected: true, + priorIssues: 1, + }); + expect(buildContributorPatternReport(history, "success").patterns.map((pattern) => pattern.title)).toContain("Emerging repo fit"); + expect(buildContributorPatternReport(history, "failure").patterns.map((pattern) => pattern.title)).toContain("Closed PR credibility pressure"); + }); + + it("covers preflight and maintainer packet branches for clean direct-contribution repos", () => { + const directRepo = repo("owner/direct"); + const cleanPr = pr(directRepo.fullName, 7, "Fix cache invalidation", { + linkedIssues: [1], + labels: [], + body: "Fixes #1", + updatedAt: new Date().toISOString(), + }); + const cleanPacket = buildMaintainerPacket(directRepo, [], [cleanPr], directRepo.fullName); + const local = buildLocalDiffPreflightResult( + { + repoFullName: directRepo.fullName, + title: "Fix cache invalidation", + body: "Fixes #1", + changedFiles: ["internal/entity/model.go"], + testFiles: ["internal/entity/model_test.go"], + changedLineCount: 40, + }, + directRepo, + [issue(directRepo.fullName, 1, "Cache invalidation")], + [], + ); + const directNoIssue = buildPreflightResult( + { repoFullName: directRepo.fullName, title: "Docs maintenance", body: "No issue: typo fix", changedFiles: ["README.md"], tests: ["manual"] }, + directRepo, + [], + [], + ); + + expect(cleanPacket.pullRequestPackets[0]).toMatchObject({ reviewPriority: "review", reasons: ["No obvious queue hygiene issue detected in cached metadata."] }); + expect(cleanPacket.suggestedActions).toEqual(["Queue looks manageable from cached Gittensory signals."]); + expect(local.status).toBe("ready"); + expect(local.localDiff).toMatchObject({ codeFileCount: 1, testFileCount: 1, inferredLinkedIssues: [1] }); + expect(directNoIssue.findings.map((finding) => finding.code)).toContain("missing_linked_issue"); + }); + + it("covers issue quality, burden, bounties, noise, and reviewability edge decisions", () => { + const directRepo = repo("owner/direct"); + const issueRepo = repo("owner/issues", { issueDiscoveryShare: 1 }); + const duplicateIssues = [ + issue(directRepo.fullName, 1, "Cache refresh websocket reconnect failure", { body: "Short." }), + issue(directRepo.fullName, 2, "Cache refresh websocket reconnect failure", { body: "Short." }), + ]; + const highOverlapCollisions = buildCollisionReport(directRepo.fullName, duplicateIssues, []); + const readyIssues = buildIssueQualityReport( + issueRepo, + [ + issue(issueRepo.fullName, 3, "Actionable issue discovery candidate", { + body: "x".repeat(220), + labels: ["bug"], + updatedAt: new Date().toISOString(), + }), + ], + [], + issueRepo.fullName, + ); + const burden = buildBurdenForecast( + directRepo, + [], + Array.from({ length: 40 }, (_, index) => pr(directRepo.fullName, index + 100, `Busy PR ${index}`, { linkedIssues: [], updatedAt: new Date().toISOString() })), + highOverlapCollisions, + 7, + ); + const historicalBounty = buildBountyAdvisory({ id: "b1", repoFullName: "missing/repo", issueNumber: 1, status: "Cancelled", payload: { target_bounty: "1" } }, null, null); + const activeBounty = buildBountyAdvisory({ id: "b2", repoFullName: directRepo.fullName, issueNumber: 9, status: "Open", payload: { bounty_amount: "1.0000" } }, directRepo, issue(directRepo.fullName, 9, "Funded work")); + const directIssueQuality = buildIssueQualityReport( + directRepo, + [ + issue(directRepo.fullName, 4, "Thin stale direct issue", { + body: undefined, + updatedAt: "2025-01-01T00:00:00.000Z", + }), + ], + [], + directRepo.fullName, + ); + + expect(highOverlapCollisions.summary.highRiskCount).toBeGreaterThan(0); + expect(readyIssues.issues[0]).toMatchObject({ status: "ready" }); + expect(directIssueQuality.issues[0]).toMatchObject({ + status: "needs_proof", + warnings: expect.arrayContaining(["Repo is direct-PR first; issue filing is not the primary Gittensor lane."]), + }); + expect(burden.level).toBe("critical"); + expect(burden.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["queue_growth_risk"])); + expect(buildBurdenForecast(directRepo, [], Array.from({ length: 5 }, (_, index) => pr(directRepo.fullName, index + 300, `Medium PR ${index}`, { linkedIssues: [index] })), buildCollisionReport(directRepo.fullName, [], []), 7).level).toBe("medium"); + expect(buildBurdenForecast(directRepo, [], Array.from({ length: 12 }, (_, index) => pr(directRepo.fullName, index + 400, `High PR ${index}`, { linkedIssues: [index] })), buildCollisionReport(directRepo.fullName, [], []), 7).level).toBe("high"); + expect(historicalBounty).toMatchObject({ lifecycle: "historical", fundingStatus: "target_only", consensusRisk: "low" }); + expect(activeBounty).toMatchObject({ lifecycle: "active", fundingStatus: "funded", consensusRisk: "low" }); + expect(historicalBounty.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["historical_bounty", "bounty_repo_unregistered", "bounty_issue_not_cached"])); + + const stalePr = pr(directRepo.fullName, 20, "Misc refactor cleanup various things", { + linkedIssues: [], + authorLogin: "dev", + updatedAt: "2025-01-01T00:00:00.000Z", + }); + const noise = buildMaintainerNoiseReport(directRepo, [], [stalePr], [], directRepo.fullName); + const quietNoise = buildMaintainerNoiseReport(directRepo, [], [], [], directRepo.fullName); + expect(noise.noiseSources.join("\n")).toMatch(/lack linked issue|stale PR|broad/i); + expect(noise.maintainerActions).toEqual(expect.arrayContaining(["needs_author"])); + expect(quietNoise.maintainerActions).toEqual(["watch"]); + + const fragileRepo = repo("owner/fragile", { emissionShare: 0, trustedLabelPipeline: true }); + const noisyPacket = buildMaintainerPacket( + fragileRepo, + duplicateIssues, + Array.from({ length: 10 }, (_, index) => + pr(fragileRepo.fullName, 500 + index, `Misc cleanup ${index}`, { + linkedIssues: [], + labels: index === 0 ? ["bug"] : [], + updatedAt: "2025-01-01T00:00:00.000Z", + }), + ), + fragileRepo.fullName, + ); + expect(noisyPacket.pullRequestPackets[0]).toMatchObject({ reviewPriority: "needs_author", reasons: expect.arrayContaining(["Missing linked issue context."]) }); + expect(noisyPacket.suggestedActions).toEqual( + expect.arrayContaining([ + "Ask authors of unlinked PRs to add issue context or a no-issue rationale.", + "Review repo Gittensor config quality before inviting more contributor flow.", + "Prioritize queue clearing before encouraging new work.", + ]), + ); + + const needsAuthor = buildPullRequestReviewability({ + repo: directRepo, + pullRequest: { ...stalePr, title: "Small unlinked code fix", updatedAt: new Date().toISOString() }, + issues: [], + pullRequests: [{ ...stalePr, title: "Small unlinked code fix", updatedAt: new Date().toISOString() }], + files: [{ repoFullName: directRepo.fullName, pullNumber: 20, path: "src/cache.ts", additions: 10, deletions: 1, changes: 11, payload: {} }], + reviews: [], + checks: [], + recentMergedPullRequests: [], + repoFullName: directRepo.fullName, + pullNumber: 20, + }); + expect(needsAuthor.action).toBe("needs_author"); + expect(needsAuthor.maintainerNextSteps).toEqual(expect.arrayContaining(["Ask the author to address the concrete missing context before deep review."])); + + const makeUnlinked = (count: number) => + Array.from({ length: count }, (_, index) => + pr(directRepo.fullName, 200 + index, `Focused fix ${index}`, { + linkedIssues: [], + updatedAt: new Date().toISOString(), + }), + ); + expect(buildMaintainerNoiseReport(directRepo, [], makeUnlinked(3), [], directRepo.fullName).level).toBe("medium"); + expect(buildMaintainerNoiseReport(directRepo, [], makeUnlinked(5), [], directRepo.fullName).level).toBe("high"); + expect(buildMaintainerNoiseReport(directRepo, [], makeUnlinked(8), [], directRepo.fullName).level).toBe("critical"); + + const noisyClosedPr = pr(directRepo.fullName, 21, "Closed broad PR", { + state: "closed", + authorLogin: "dev", + linkedIssues: [], + updatedAt: new Date().toISOString(), + }); + const closedRateHistory = buildContributorOutcomeHistory({ + login: "dev", + profile: buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []), + repositories: [directRepo], + pullRequests: [ + pr(directRepo.fullName, 30, "Closed one", { state: "closed", authorLogin: "dev" }), + pr(directRepo.fullName, 31, "Closed two", { state: "closed", authorLogin: "dev" }), + pr(directRepo.fullName, 32, "Merged", { state: "merged", mergedAt: "2026-05-20T00:00:00.000Z", authorLogin: "dev" }), + ], + issues: [], + repoStats: [], + }); + const broadFiles: PullRequestFileRecord[] = Array.from({ length: 12 }, (_, index) => ({ + repoFullName: directRepo.fullName, + pullNumber: 21, + path: `src/file-${index}.ts`, + additions: 80, + deletions: 0, + changes: 80, + payload: {}, + })); + const failingChecks: CheckSummaryRecord[] = [ + { + id: "check-1", + repoFullName: directRepo.fullName, + pullNumber: 21, + name: "test", + status: "completed", + conclusion: "failure", + payload: {}, + }, + ]; + const closedReviewability = buildPullRequestReviewability({ + repo: directRepo, + pullRequest: noisyClosedPr, + issues: [], + pullRequests: [noisyClosedPr], + files: broadFiles, + reviews: [], + checks: failingChecks, + recentMergedPullRequests: [], + repoFullName: directRepo.fullName, + pullNumber: 21, + outcomeHistory: closedRateHistory, + }); + expect(closedReviewability.action).toBe("close_or_redirect"); + expect(closedReviewability.noiseSources.join("\n")).toMatch(/failing|broad|closed PR rate|PR is closed/i); + }); + + it("covers private reward/risk edge scoring for unknown, issue-only, and maintainer contexts", () => { + const directRepo = repo("owner/direct", { labelMultipliers: { "status:ready": 2, feature: 1.5 } }); + const issueRepo = repo("owner/issues", { issueDiscoveryShare: 1 }); + const maintainerRepo = repo("jsonbored/awesome-claude"); + const inactiveRepo = repo("owner/inactive", { emissionShare: 0 }); + const profile = buildContributorProfile( + "jsonbored", + { login: "jsonbored", topLanguages: ["TypeScript"], source: "github" }, + [pr(maintainerRepo.fullName, 1, "Owner work", { authorLogin: "jsonbored", authorAssociation: "OWNER" })], + [], + ); + const history = buildContributorOutcomeHistory({ + login: "jsonbored", + profile, + repositories: [directRepo, issueRepo, maintainerRepo, inactiveRepo], + pullRequests: [pr(maintainerRepo.fullName, 1, "Owner work", { authorLogin: "jsonbored", authorAssociation: "OWNER" })], + issues: [], + repoStats: [ + { login: "jsonbored", repoFullName: directRepo.fullName, pullRequests: 4, mergedPullRequests: 2, openPullRequests: 4, issues: 0, stalePullRequests: 0, unlinkedPullRequests: 0, dominantLabels: ["feature"] }, + ], + }); + const fit = buildContributorFit(profile, [directRepo, issueRepo, maintainerRepo, inactiveRepo], [], [], [], [ + { login: "jsonbored", repoFullName: directRepo.fullName, pullRequests: 4, mergedPullRequests: 2, openPullRequests: 4, issues: 0, stalePullRequests: 0, unlinkedPullRequests: 0, dominantLabels: ["feature"] }, + ]); + const scoringProfile = buildContributorScoringProfile({ login: "jsonbored", fit, scoringSnapshot: scoringSnapshot() }); + + const direct = buildRepoRewardRisk({ + login: "jsonbored", + repo: directRepo, + repoFullName: directRepo.fullName, + profile, + outcomeHistory: history, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [], + }); + const issueOnly = buildRepoRewardRisk({ + login: "jsonbored", + repo: issueRepo, + repoFullName: issueRepo.fullName, + profile, + outcomeHistory: history, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [], + }); + const unknown = buildRepoRewardRisk({ + login: "jsonbored", + repo: null, + repoFullName: "missing/repo", + profile, + outcomeHistory: history, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [], + }); + const strategy = buildContributorRewardRiskStrategy({ + login: "jsonbored", + fit, + scoringProfile, + scoringSnapshot: scoringSnapshot(), + outcomeHistory: history, + repositories: [directRepo, issueRepo, maintainerRepo, inactiveRepo], + allIssues: [], + allPullRequests: [], + }); + + expect(direct.rewardUpside.labelMultiplier).toBe(1.5); + expect(direct.scoreBlockers).toContain("Open PR count exceeds the current threshold assumption."); + expect(issueOnly.rewardUpside.relevantLane).toBe("issue_discovery"); + expect(unknown.scoreBlockers).toEqual(expect.arrayContaining(["Repository is not registered in the local snapshot.", "Repository lane is unknown."])); + expect(strategy.reasoning.join("\n")).toContain("maintainer-lane economics are separate from normal contributor rewards"); + expect(strategy.actionImpact.join("\n")).toContain("openPrMultiplier"); + + const emptyStrategy = buildContributorRewardRiskStrategy({ + login: "newbie", + fit: { ...fit, opportunities: [] }, + scoringProfile, + scoringSnapshot: scoringSnapshot(), + outcomeHistory: { ...history, repoOutcomes: [], totals: { ...history.totals, openPullRequests: 0 } }, + repositories: [], + allIssues: [], + allPullRequests: [], + }); + expect(emptyStrategy.nextActions).toEqual(["Refresh official Gittensor and GitHub backfill data, then rerun strategy."]); + + const issueCleanupHistory = { + ...history, + repoOutcomes: [ + ...history.repoOutcomes, + { + repoFullName: issueRepo.fullName, + role: "outside_contributor" as const, + lane: "issue_discovery" as const, + maintainerLane: false, + pullRequests: 1, + mergedPullRequests: 0, + openPullRequests: 1, + closedPullRequests: 0, + closedPullRequestRate: 0, + issues: 0, + openIssues: 0, + closedIssues: 0, + solvedIssues: 0, + validSolvedIssues: 0, + credibility: 1, + issueCredibility: 1, + isEligible: true, + successLevel: "weak" as const, + dominantLabels: [], + successfulPaths: [], + successfulLanguages: [], + strengths: [], + risks: [], + }, + ], + }; + const issueCleanup = buildRepoRewardRisk({ + login: "jsonbored", + repo: issueRepo, + repoFullName: issueRepo.fullName, + profile, + outcomeHistory: issueCleanupHistory, + scoringSnapshot: scoringSnapshot(), + scoringProfile, + issues: [], + pullRequests: [pr(issueRepo.fullName, 91, "Open issue-lane work", { authorLogin: "jsonbored", linkedIssues: [] })], + }); + expect(issueCleanup.actions.map((action) => action.actionKind)).toContain("cleanup_existing_prs"); + expect(issueCleanup.actions.map((action) => action.actionKind)).not.toContain("land_existing_prs"); + }); + + it("sanitizes public PR comments and supports minimal public signal level", () => { + const directRepo = repo("owner/direct"); + const prRecord = pr(directRepo.fullName, 55, "Fix cache", { authorLogin: "miner", linkedIssues: [] }); + const profile = buildContributorProfile("miner", { login: "miner", topLanguages: [], source: "github" }, [], []); + const preflight = buildPreflightResult( + { + repoFullName: directRepo.fullName, + title: "Fix cache", + body: "", + changedFiles: ["src/cache.ts"], + }, + directRepo, + [], + [], + ); + const comment = buildPublicPrIntelligenceComment({ + repo: directRepo, + pr: prRecord, + profile, + detection: { detected: true, source: "official_gittensor_api", reason: "Confirmed by official API.", priorPullRequests: 1, priorMergedPullRequests: 0, priorIssues: 0 }, + queueHealth: buildQueueHealth(directRepo, [], [], buildCollisionReport(directRepo.fullName, [], [])), + collisions: buildCollisionReport(directRepo.fullName, [], []), + preflight: { + ...preflight, + findings: [ + ...preflight.findings, + { code: "score_private", severity: "warning", title: "Estimated score", detail: "Private reward score should not leak.", action: "Do not publish score." }, + { code: "critical_private", severity: "critical", title: "Critical private", detail: "wallet hotkey trust score", action: "secret" }, + ], + }, + settings: { ...repoSettings(directRepo.fullName), publicSignalLevel: "minimal", requireLinkedIssue: true }, + }); + + expect(comment).toContain("Confirmed Gittensor miner: yes"); + expect(comment).toContain("Linked issues: None detected"); + expect(comment).not.toMatch(/reward|score|wallet|hotkey|trust score|farming|critical private/i); + + const maintainerComment = buildPublicPrIntelligenceComment({ + repo: directRepo, + pr: { ...prRecord, authorLogin: "owner", authorAssociation: "OWNER", linkedIssues: [1], body: "Fixes #1" }, + profile: buildContributorProfile("owner", { login: "owner", topLanguages: ["TypeScript"], source: "github" }, [], []), + detection: { detected: true, source: "official_gittensor_api", reason: "Confirmed by official API.", priorPullRequests: 1, priorMergedPullRequests: 1, priorIssues: 0 }, + queueHealth: buildQueueHealth(directRepo, [], [], buildCollisionReport(directRepo.fullName, [], [])), + collisions: buildCollisionReport(directRepo.fullName, [], []), + preflight, + settings: repoSettings(directRepo.fullName), + }); + + expect(maintainerComment).toContain("maintainer lane"); + expect(maintainerComment).not.toMatch(/reward|score|wallet|hotkey|trust score|farming/i); + }); + + it("audits label ordering and suspicious configured labels deterministically", () => { + const directRepo = repo("owner/direct", { labelMultipliers: { bug: 1.2, feature: 1.1, "status:ready": 1.05 }, trustedLabelPipeline: true }); + const labels: RepoLabelRecord[] = [ + { repoFullName: directRepo.fullName, name: "feature", isConfigured: true, observedCount: 1, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" }, + { repoFullName: directRepo.fullName, name: "bug", isConfigured: true, observedCount: 1, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" }, + { repoFullName: directRepo.fullName, name: "status:ready", isConfigured: true, observedCount: 0, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" }, + ]; + const audit = buildLabelAudit( + directRepo, + labels, + [issue(directRepo.fullName, 1, "Feature", { labels: ["feature"] })], + [pr(directRepo.fullName, 2, "Bug", { labels: ["bug"] })], + directRepo.fullName, + ); + + expect(audit.observedLabels.slice(0, 2).map((label) => label.name)).toEqual(["bug", "feature"]); + expect(audit.findings.map((finding) => finding.code)).toContain("suspicious_configured_labels"); + }); +}); + +function repo(fullName: string, overrides: Partial = {}): RepositoryRecord { + const [owner, name] = fullName.split("/") as [string, string]; + return { + fullName, + owner, + name, + isInstalled: true, + isRegistered: true, + isPrivate: false, + defaultBranch: "main", + registryConfig: { + repo: fullName, + emissionShare: 0.02, + issueDiscoveryShare: 0, + labelMultipliers: {}, + trustedLabelPipeline: false, + maintainerCut: 0, + raw: {}, + ...overrides, + }, + }; +} + +function issue(repoFullName: string, number: number, title: string, overrides: Partial = {}): IssueRecord { + return { + repoFullName, + number, + title, + state: "open", + authorLogin: "reporter", + authorAssociation: "NONE", + labels: [], + linkedPrs: [], + body: "Detailed issue body with reproduction steps and expected behavior.", + updatedAt: new Date().toISOString(), + ...overrides, + }; +} + +function pr(repoFullName: string, number: number, title: string, overrides: Partial = {}): PullRequestRecord { + return { + repoFullName, + number, + title, + state: "open", + authorLogin: "dev", + authorAssociation: "NONE", + labels: [], + linkedIssues: [], + body: "", + updatedAt: new Date().toISOString(), + ...overrides, + }; +} + +function repoSettings(repoFullName: string): RepositorySettings { + return { + repoFullName, + commentMode: "detected_contributors_only", + publicSignalLevel: "standard", + checkRunMode: "off", + checkRunDetailLevel: "minimal", + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, + backfillEnabled: true, + privateTrustEnabled: true, + }; +} + +function scoringSnapshot(): ScoringModelSnapshotRecord { + return { + id: "coverage-scoring", + sourceKind: "test", + sourceUrl: "fixture://coverage", + fetchedAt: "2026-05-25T00:00:00.000Z", + activeModel: "current_density_model", + constants: {}, + programmingLanguages: {}, + warnings: [], + payload: {}, + }; +} diff --git a/test/unit/signals-v2.test.ts b/test/unit/signals-v2.test.ts index 1e6cb47209..45a3850b0a 100644 --- a/test/unit/signals-v2.test.ts +++ b/test/unit/signals-v2.test.ts @@ -1226,10 +1226,24 @@ describe("v2 signal builders", () => { queueHealth: buildQueueHealth(repo, [], [], buildCollisionReport(repo.fullName, [], [])), collisions: buildCollisionReport(repo.fullName, [], []), preflight: buildPreflightResult({ repoFullName: repo.fullName, title: "Docs", body: "No linked issue", changedFiles: ["README.md"], tests: ["manual"] }, repo, [], []), - settings: { repoFullName: repo.fullName, commentMode: "all_prs", publicSignalLevel: "standard", checkRunMode: "enabled", checkRunDetailLevel: "standard", backfillEnabled: true, privateTrustEnabled: true }, + settings: { + repoFullName: repo.fullName, + commentMode: "detected_contributors_only", + publicSignalLevel: "standard", + checkRunMode: "off", + checkRunDetailLevel: "minimal", + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, + backfillEnabled: true, + privateTrustEnabled: true, + }, }); expect(comment).toContain("Author: `unknown`"); - expect(comment).toContain("No prior cached registered-repo activity detected."); + expect(comment).toContain("No confirmed Gittensor miner activity detected."); expect(comment).not.toMatch(/wallet|raw trust score|ranking/i); }); }); diff --git a/test/unit/signals.test.ts b/test/unit/signals.test.ts index 5f400fb187..42a330e590 100644 --- a/test/unit/signals.test.ts +++ b/test/unit/signals.test.ts @@ -287,13 +287,19 @@ describe("world-class backend signals", () => { state: "closed", mergedAt: "2026-05-01T00:00:00.000Z", }; - const detection = detectGittensorContributor("oktofeesh1", currentPr, [currentPr, priorPr], []); + const detection = { ...detectGittensorContributor("oktofeesh1", currentPr, [currentPr, priorPr], []), source: "official_gittensor_api" as const }; const settings = { repoFullName: repo.fullName, commentMode: "detected_contributors_only" as const, publicSignalLevel: "standard" as const, - checkRunMode: "enabled" as const, - checkRunDetailLevel: "standard" as const, + checkRunMode: "off" as const, + checkRunDetailLevel: "minimal" as const, + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label" as const, + includeMaintainerAuthors: false, + requireLinkedIssue: false, backfillEnabled: true, privateTrustEnabled: true, }; @@ -345,16 +351,25 @@ describe("world-class backend signals", () => { repoFullName: repo.fullName, commentMode: "off", publicSignalLevel: "minimal", - checkRunMode: "enabled", + checkRunMode: "off", checkRunDetailLevel: "minimal", + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, backfillEnabled: true, privateTrustEnabled: true, }; const undetected = detectGittensorContributor("newbie", currentPr, [currentPr], []); + const cachedDetected = detectGittensorContributor("oktofeesh1", currentPr, [currentPr, { ...currentPr, number: 10, mergedAt: "2026-05-01T00:00:00.000Z" }], []); expect(undetected.detected).toBe(false); expect(shouldPublishPrIntelligenceComment(settings, undetected)).toBe(false); - expect(shouldPublishPrIntelligenceComment({ ...settings, commentMode: "all_prs" }, undetected)).toBe(true); + expect(shouldPublishPrIntelligenceComment({ ...settings, commentMode: "all_prs" }, undetected)).toBe(false); + expect(shouldPublishPrIntelligenceComment({ ...settings, commentMode: "all_prs" }, cachedDetected)).toBe(false); + expect(shouldPublishPrIntelligenceComment({ ...settings, commentMode: "all_prs" }, { ...cachedDetected, source: "official_gittensor_api" })).toBe(true); }); it("returns hold/caution opportunities for inactive and issue-discovery lanes", () => { @@ -380,7 +395,7 @@ describe("world-class backend signals", () => { it("summarizes public comments at minimal signal level", () => { const currentPr: PullRequestRecord = { ...pullRequests[0]!, linkedIssues: [], body: "" }; - const detection = detectGittensorContributor("newbie", currentPr, [], []); + const detection = { ...detectGittensorContributor("newbie", currentPr, [], []), detected: true, source: "official_gittensor_api" as const, reason: "Official Gittensor API confirms this GitHub user." }; const collisions = buildCollisionReport(repo.fullName, issues, [currentPr]); const queueHealth = buildQueueHealth(repo, issues, [currentPr], collisions); const preflight = buildPreflightResult({ repoFullName: repo.fullName, title: currentPr.title, changedFiles: ["README.md"] }, repo, issues, [currentPr]); @@ -389,15 +404,21 @@ describe("world-class backend signals", () => { repoFullName: repo.fullName, commentMode: "all_prs", publicSignalLevel: "minimal", - checkRunMode: "enabled", - checkRunDetailLevel: "standard", + checkRunMode: "off", + checkRunDetailLevel: "minimal", + autoLabelEnabled: true, + gittensorLabel: "gittensor", + createMissingLabel: true, + publicSurface: "comment_and_label", + includeMaintainerAuthors: false, + requireLinkedIssue: false, backfillEnabled: true, privateTrustEnabled: true, }; const comment = buildPublicPrIntelligenceComment({ repo, pr: currentPr, profile, detection, queueHealth, collisions, preflight, settings }); - expect(comment).toContain("Linked issues: None detected"); + expect(comment).toContain("Linked issues: Not required by this repo setting"); expect(comment).toContain("Public profile languages: not available"); expect(comment).not.toMatch(/trust score|wallet|ranking/i); }); diff --git a/vitest.config.ts b/vitest.config.ts index d5302a67a2..691bde3980 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -20,10 +20,10 @@ export default defineConfig({ include: ["src/**/*.ts"], exclude: ["src/env.d.ts"], thresholds: { - lines: 90, - functions: 90, - branches: 90, - statements: 90, + lines: 95, + functions: 95, + branches: 95, + statements: 95, }, }, },