@vibe-agent-toolkit/utils 0.2.0-rc.1 → 0.2.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -23
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/entrypoint.d.ts +54 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +71 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/env-flag.d.ts +51 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +62 -0
- package/dist/env-flag.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.d.ts +60 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +133 -29
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +422 -191
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +534 -204
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +9 -7
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +199 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +250 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +166 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +250 -22
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +84 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +208 -82
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +13 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +15 -6
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/index.d.ts +32 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -50
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +112 -0
- package/dist/listing-refusal.d.ts.map +1 -0
- package/dist/listing-refusal.js +105 -0
- package/dist/listing-refusal.js.map +1 -0
- package/dist/path-containment.d.ts +64 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +111 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +113 -18
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +139 -23
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/process.d.ts +7 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +15 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +40 -11
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +111 -25
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +30 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +71 -5
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +55 -10
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/spawn-hardened.js +0 -1
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +135 -100
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +224 -146
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +94 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +119 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +197 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +354 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +45 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +51 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +229 -0
- package/dist/timing-dump.js.map +1 -0
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +223 -49
- package/eslint/index.cjs +111 -97
- package/eslint/index.d.cts +16 -6
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +1 -1
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +5 -2
- package/eslint/rules/exempt-path-matcher.cjs +2 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +217 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
- package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +136 -107
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +305 -0
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +168 -0
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +16 -16
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -496
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A **dirty-corrected git tree snapshot**, rebased onto absolute paths.
|
|
3
|
+
*
|
|
4
|
+
* `@vibe-validate/git`'s `getGitTreeSnapshot` answers the question git is
|
|
5
|
+
* uniquely good at: every path git can see under a directory, each with a blob
|
|
6
|
+
* OID naming *the bytes that are actually on disk right now* — dirty working-tree
|
|
7
|
+
* edits included, not the stale committed-index SHA — plus one deterministic
|
|
8
|
+
* `write-tree` hash over the whole set. Membership is `git add --all` without
|
|
9
|
+
* `--force`, i.e. `tracked ∪ (untracked ∧ ¬ignored)`.
|
|
10
|
+
*
|
|
11
|
+
* This module is the thin layer VAT needs on top, and it exists for one reason
|
|
12
|
+
* that is easy to get wrong: **git spells those paths relative to the repository
|
|
13
|
+
* root, not to the directory you asked about.** A caller scanning
|
|
14
|
+
* `<repo>/packages/foo` gets back `packages/foo/x.md`, and a caller that joined
|
|
15
|
+
* those onto its own root would build `<repo>/packages/foo/packages/foo/x.md` —
|
|
16
|
+
* a path that does not exist, so every consumer reads it as "absent" rather than
|
|
17
|
+
* as a bug. Resolving against the repository root here makes that unrepresentable
|
|
18
|
+
* at the call site.
|
|
19
|
+
*
|
|
20
|
+
* ## ⚠️ This is not a pure read
|
|
21
|
+
*
|
|
22
|
+
* Taking a snapshot runs `git add --all` against a throwaway index, which
|
|
23
|
+
* **writes loose blob objects into the target repository's `.git/objects`** for
|
|
24
|
+
* any content git has not already stored. The real index and working tree are
|
|
25
|
+
* never touched, and the objects are ordinary unreferenced blobs that `git gc`
|
|
26
|
+
* reclaims — but a command that advertises itself as read-only is, at the byte
|
|
27
|
+
* level, not. `vibe-validate` accepts this cost on every commit it gates; a VAT
|
|
28
|
+
* lane that adopts it inherits the same trade rather than a different one.
|
|
29
|
+
*
|
|
30
|
+
* ## ⚠️ A symlink's OID names its TARGET STRING, not a file's bytes
|
|
31
|
+
*
|
|
32
|
+
* Git stores a symlink as a blob whose content is the link target, under mode
|
|
33
|
+
* `120000`. Two links with the same relative target but different resolutions
|
|
34
|
+
* therefore share an OID while a consumer that follows them reads two different
|
|
35
|
+
* documents. {@link GitSnapshotEntry.isSymlink} is computed here precisely so
|
|
36
|
+
* that a consumer keying work off `oid` can exclude them — see
|
|
37
|
+
* `packages/resources/src/content-key.ts`, whose standing rule is that a git SHA
|
|
38
|
+
* may be a *lookup hint whose miss is free* and must never be the key itself.
|
|
39
|
+
*
|
|
40
|
+
* ## ⚠️ Two consumers want the SAME snapshot — see {@link withGitSnapshotCache}
|
|
41
|
+
*
|
|
42
|
+
* One command takes this snapshot twice, of one repository, sequentially, and
|
|
43
|
+
* keeps a different half of each: the projection store keeps `hash` as its
|
|
44
|
+
* cache key, and the git crawl source keeps `entries`. Bracketing the command
|
|
45
|
+
* makes that one snapshot. Everything outside the bracket still pays per call,
|
|
46
|
+
* deliberately.
|
|
47
|
+
*/
|
|
48
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
49
|
+
import { getGitTreeSnapshot, GIT_MODE_GITLINK, GIT_MODE_SYMLINK } from '@vibe-validate/git';
|
|
50
|
+
import { gitFindRoot } from './git-utils.js';
|
|
51
|
+
import { safePath } from './path-utils.js';
|
|
52
|
+
/**
|
|
53
|
+
* The snapshots taken inside the bracket now open, keyed by repository root.
|
|
54
|
+
*
|
|
55
|
+
* `AsyncLocalStorage` rather than a module-level `Map`, and the distinction is
|
|
56
|
+
* the whole design. A blanket memo would dedupe the same two calls — and would
|
|
57
|
+
* then hand a stale snapshot to anything that mutates a repository and
|
|
58
|
+
* re-snapshots it in the same process, which is what a vitest worker running
|
|
59
|
+
* many suites does all day. Scoping it to a bracket makes the memo's lifetime
|
|
60
|
+
* something a caller states rather than something the module assumes; outside
|
|
61
|
+
* one, {@link gitTreeSnapshot} behaves exactly as it did before this existed.
|
|
62
|
+
*
|
|
63
|
+
* Async-scoped rather than a plain variable because the bracket spans a whole
|
|
64
|
+
* command's `await`s, and two commands may be open at once in one process —
|
|
65
|
+
* `crawl-timing.ts` reaches for the same primitive for the same reason.
|
|
66
|
+
*
|
|
67
|
+
* `null` is a stored value, not an absence: "git could not answer" costs a full
|
|
68
|
+
* `add`/`write-tree` attempt to discover, so repeating it is pure cost. Absence
|
|
69
|
+
* is `undefined`, which `Map.get` returns and this module never stores.
|
|
70
|
+
*/
|
|
71
|
+
const snapshotsInBracket = new AsyncLocalStorage();
|
|
72
|
+
/**
|
|
73
|
+
* Run work that may snapshot the same repository more than once, and pay for
|
|
74
|
+
* each repository ONCE.
|
|
75
|
+
*
|
|
76
|
+
* ## What this buys, in both currencies
|
|
77
|
+
*
|
|
78
|
+
* A snapshot is not a read — it copies the index, runs `git add --all` into it
|
|
79
|
+
* and then `git write-tree`. One `vat` command took two of them, of the same
|
|
80
|
+
* repository, back to back: `openPopulationCache` needs `hash` to key the
|
|
81
|
+
* projection store, and `GitCrawlSource` needs `entries` to enumerate. Measured
|
|
82
|
+
* on a large monorepo, the pair cost 195.22 ms and 158.55 ms.
|
|
83
|
+
*
|
|
84
|
+
* The cost is the smaller half. Two snapshots taken 195 ms apart are two
|
|
85
|
+
* *different answers* whenever the working tree changes in between, and the
|
|
86
|
+
* command then files the second one's extent under the first one's key — a
|
|
87
|
+
* cache entry whose key does not describe its contents, produced silently and
|
|
88
|
+
* discoverable only as a wrong answer much later. One snapshot removes the race
|
|
89
|
+
* rather than merely the duplicate.
|
|
90
|
+
*
|
|
91
|
+
* ## Where to open it
|
|
92
|
+
*
|
|
93
|
+
* At the level that already brackets the whole command, so that it encloses
|
|
94
|
+
* *every* snapshotting consumer. Opened deeper than one of them, the dedupe
|
|
95
|
+
* silently does nothing and looks exactly like a dedupe that works.
|
|
96
|
+
*
|
|
97
|
+
* ## What it deliberately does NOT do
|
|
98
|
+
*
|
|
99
|
+
* Inside the bracket, a working-tree edit made between two calls is not
|
|
100
|
+
* observed by the second — the first snapshot is the answer for the whole
|
|
101
|
+
* bracket. That is the race being closed, not a limitation to work around. Work
|
|
102
|
+
* that must see edits as they land does not belong inside one bracket.
|
|
103
|
+
*
|
|
104
|
+
* Nesting is safe and inner-most wins: an inner bracket starts an empty memo,
|
|
105
|
+
* and the outer one's entries are restored when it returns.
|
|
106
|
+
*
|
|
107
|
+
* @param run - The work to run with the memo open. Sync or async — the return
|
|
108
|
+
* value is passed straight through, so an async `run` keeps the memo for the
|
|
109
|
+
* whole promise it returns
|
|
110
|
+
* @returns Whatever `run` returned
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* return withGitSnapshotCache(async () => {
|
|
115
|
+
* const opened = await openPopulationCache(options); // snapshots
|
|
116
|
+
* return work(opened?.cache); // crawls, snapshots
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function withGitSnapshotCache(run) {
|
|
121
|
+
return snapshotsInBracket.run(new Map(), run);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Snapshot everything git can see under a directory.
|
|
125
|
+
*
|
|
126
|
+
* Inside a {@link withGitSnapshotCache} bracket the answer for a given
|
|
127
|
+
* repository is taken once and reused — including a `null`. Outside one, every
|
|
128
|
+
* call spawns git, which is what it has always done.
|
|
129
|
+
*
|
|
130
|
+
* @param options - Where to look
|
|
131
|
+
* @param options.cwd - Any directory inside the repository of interest. Git
|
|
132
|
+
* resolves upward to the worktree root, and the snapshot covers that whole
|
|
133
|
+
* root — NOT only the subtree named here. Narrowing is the caller's job, and
|
|
134
|
+
* is why {@link GitSnapshotEntry.absolutePath} is absolute
|
|
135
|
+
* @returns The snapshot, or `null` when git could not answer — no `git` on
|
|
136
|
+
* `PATH`, not a repository, a bare or unreadable one. An empty `entries` is a
|
|
137
|
+
* real answer (an initialized repository with no files) and stays
|
|
138
|
+
* distinguishable from it
|
|
139
|
+
*/
|
|
140
|
+
export function gitTreeSnapshot(options) {
|
|
141
|
+
// Resolved from the filesystem BEFORE spawning anything, and deliberately not
|
|
142
|
+
// from a `rev-parse` of our own: `getGitTreeSnapshot` has already paid for that
|
|
143
|
+
// question internally, and asking it twice invites the two answers to disagree
|
|
144
|
+
// on a worktree, where the git directory and the worktree root are different
|
|
145
|
+
// places. `gitFindRoot` is memoized, so this is free after the first call.
|
|
146
|
+
const repositoryRoot = gitFindRoot(options.cwd);
|
|
147
|
+
if (repositoryRoot === null) {
|
|
148
|
+
// Not memoized, and it does not need to be: this branch spawns nothing, and
|
|
149
|
+
// `gitFindRoot` has its own memo. There is also no key to file it under —
|
|
150
|
+
// the memo is keyed by repository root, and this is the case with none.
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// Keyed by the RESOLVED root rather than by `options.cwd`, because a snapshot
|
|
154
|
+
// covers the whole repository however deep the directory it was asked from.
|
|
155
|
+
// The two call sites this bracket exists for pass different directories — the
|
|
156
|
+
// corpus root and the project root — so keying on `cwd` would produce two
|
|
157
|
+
// entries holding the identical answer, i.e. a dedupe that does nothing in
|
|
158
|
+
// exactly the case it was written for.
|
|
159
|
+
const memo = snapshotsInBracket.getStore();
|
|
160
|
+
const memoized = memo?.get(repositoryRoot);
|
|
161
|
+
if (memoized !== undefined)
|
|
162
|
+
return memoized;
|
|
163
|
+
const snapshot = takeSnapshot(options.cwd, repositoryRoot);
|
|
164
|
+
memo?.set(repositoryRoot, snapshot);
|
|
165
|
+
return snapshot;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A snapshot taken NOW, whatever bracket is open — never read from or written to
|
|
169
|
+
* its memo.
|
|
170
|
+
*
|
|
171
|
+
* ⛔ The bracket makes two consumers agree with EACH OTHER; it cannot make them
|
|
172
|
+
* agree with files read later. A population keyed inside a bracket reads the
|
|
173
|
+
* working tree afterwards, so an edit landing in between is read and then filed
|
|
174
|
+
* under the pre-edit key. This is how a caller checks, before filing, that the
|
|
175
|
+
* bracket's answer still describes the tree. It costs a full snapshot, so it
|
|
176
|
+
* belongs on the write path, never on a hit.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Where to look
|
|
179
|
+
* @param options.cwd - Any directory inside the repository of interest
|
|
180
|
+
* @returns The snapshot, or `null` when git could not answer
|
|
181
|
+
*/
|
|
182
|
+
export function freshGitTreeSnapshot(options) {
|
|
183
|
+
return snapshotsInBracket.exit(() => gitTreeSnapshot(options));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The snapshot this bracket ALREADY holds for a repository — never a new one.
|
|
187
|
+
*
|
|
188
|
+
* The difference from {@link gitTreeSnapshot} is the whole reason this exists:
|
|
189
|
+
* that function TAKES a snapshot when the memo misses, and a snapshot is three
|
|
190
|
+
* spawns and a `git add --all`. This one only ever reports what a previous
|
|
191
|
+
* caller already paid for, so a consumer can prefer it and fall back to its own
|
|
192
|
+
* cheaper question without any risk of turning a miss into the most expensive
|
|
193
|
+
* git operation in the package.
|
|
194
|
+
*
|
|
195
|
+
* That asymmetry is what makes it safe to reach for from shared code. The
|
|
196
|
+
* {@link GitTracker} does exactly this: inside the projection's git lane a
|
|
197
|
+
* snapshot is already in hand and describes precisely the set its own
|
|
198
|
+
* `git ls-files --cached --others --exclude-standard` would spawn to rebuild,
|
|
199
|
+
* while on the incumbent walk no snapshot is ever taken and the tracker must
|
|
200
|
+
* keep spawning. One consumer, two lanes, and neither pays for the other's
|
|
201
|
+
* strategy.
|
|
202
|
+
*
|
|
203
|
+
* @param cwd - Any directory inside the repository of interest
|
|
204
|
+
* @returns The memoized snapshot, or `undefined` when this bracket has none —
|
|
205
|
+
* which includes there being no bracket open at all, and includes a memoized
|
|
206
|
+
* `null` (git could not answer). Callers treat all three the same way: ask
|
|
207
|
+
* your own question instead
|
|
208
|
+
*/
|
|
209
|
+
export function peekGitTreeSnapshot(cwd) {
|
|
210
|
+
const memo = snapshotsInBracket.getStore();
|
|
211
|
+
if (memo === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
// Resolved the same way `gitTreeSnapshot` resolves it, because the memo is
|
|
214
|
+
// keyed by repository root and not by the directory anyone asked from. A peek
|
|
215
|
+
// keyed on `cwd` would miss every time the snapshot was taken from a different
|
|
216
|
+
// depth — which is the normal case, and a miss here is silent.
|
|
217
|
+
const repositoryRoot = gitFindRoot(cwd);
|
|
218
|
+
if (repositoryRoot === null)
|
|
219
|
+
return undefined;
|
|
220
|
+
return memo.get(repositoryRoot) ?? undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Ask git, and rebase the answer onto absolute paths.
|
|
224
|
+
*
|
|
225
|
+
* Split out so {@link gitTreeSnapshot} reads as "key, look up, or take one" —
|
|
226
|
+
* the memo has to sit above the spawn, and a memo interleaved with the mapping
|
|
227
|
+
* is how a later edit ends up caching the wrong half.
|
|
228
|
+
*
|
|
229
|
+
* @param cwd - The directory to ask git from
|
|
230
|
+
* @param repositoryRoot - The already-resolved root every entry is rebased onto
|
|
231
|
+
* @returns The snapshot, or `null` when git could not answer
|
|
232
|
+
*/
|
|
233
|
+
function takeSnapshot(cwd, repositoryRoot) {
|
|
234
|
+
const snapshot = getGitTreeSnapshot({ cwd });
|
|
235
|
+
if (snapshot === null) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
hash: snapshot.hash,
|
|
240
|
+
repositoryRoot,
|
|
241
|
+
entries: snapshot.entries.map((entry) => ({
|
|
242
|
+
absolutePath: safePath.resolve(repositoryRoot, entry.path),
|
|
243
|
+
oid: entry.oid,
|
|
244
|
+
mode: entry.mode,
|
|
245
|
+
isSymlink: entry.mode === GIT_MODE_SYMLINK,
|
|
246
|
+
isSubmodule: entry.mode === GIT_MODE_GITLINK,
|
|
247
|
+
})),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=git-snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-snapshot.js","sourceRoot":"","sources":["../src/git-snapshot.ts"],"names":[],"mappings":"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"}
|
package/dist/git-tracker.d.ts
CHANGED
|
@@ -56,6 +56,33 @@ export declare class GitTracker {
|
|
|
56
56
|
private readonly activeSet;
|
|
57
57
|
/** Absolute paths of every directory that contains at least one active-set file. */
|
|
58
58
|
private readonly activeAncestors;
|
|
59
|
+
/**
|
|
60
|
+
* Lowercased absolute path → the root-relative path spelled the way git
|
|
61
|
+
* spelled it. Built from the very same `git ls-files` output `activeSet`
|
|
62
|
+
* comes from, so it costs one extra `Map` and no extra git invocation.
|
|
63
|
+
*/
|
|
64
|
+
private readonly indexPaths;
|
|
65
|
+
/**
|
|
66
|
+
* Absolute, forward-slashed directories the active-set listing could not
|
|
67
|
+
* OPEN, so nothing beneath them is in the set — read off `git ls-files
|
|
68
|
+
* --others`' stderr through the listing's `unreadable` policy (`degrade`).
|
|
69
|
+
*
|
|
70
|
+
* 🚨 Beneath one of these the active set has no opinion. Its rule is "absent
|
|
71
|
+
* from the set and present on disk ⇒ ignored", and that rule is sound only
|
|
72
|
+
* where the listing LOOKED: an untracked, non-ignored file under a `--x`
|
|
73
|
+
* directory is absent because git could not read the directory, not because
|
|
74
|
+
* a pattern excludes it, and `git check-ignore` — which answers from the
|
|
75
|
+
* patterns and needs no listing — says so. Before this was recorded the
|
|
76
|
+
* tracker called such a file gitignored, silently, and every consumer of
|
|
77
|
+
* that answer (the leak judge, the link-graph walker, audit's distributed
|
|
78
|
+
* lane) was one refused `opendir` from a wrong verdict.
|
|
79
|
+
*
|
|
80
|
+
* The directory ITSELF is non-ignored by construction — git opened it
|
|
81
|
+
* precisely because no pattern excluded it — so it and its ancestors are
|
|
82
|
+
* entered into {@link activeAncestors}: a walker must not prune what the
|
|
83
|
+
* listing could not see into.
|
|
84
|
+
*/
|
|
85
|
+
private readonly unlistableDirectories;
|
|
59
86
|
private initialized;
|
|
60
87
|
private activeSetPopulated;
|
|
61
88
|
/** Whether `git ls-files` actually answered during {@link initialize}. */
|
|
@@ -72,8 +99,60 @@ export declare class GitTracker {
|
|
|
72
99
|
* With `includeUntracked: false`, only tracked files are pre-populated.
|
|
73
100
|
* Untracked non-ignored files will miss the cache and fall through to
|
|
74
101
|
* `git check-ignore` via {@link isIgnored}.
|
|
102
|
+
*
|
|
103
|
+
* ## This is the one bracket in the crawl-timing seam's `shared` stratum
|
|
104
|
+
*
|
|
105
|
+
* The `git ls-files` spawn below is preparation BOTH crawlers consume and
|
|
106
|
+
* NEITHER owns — the incumbent link walk and the projection's contributors are
|
|
107
|
+
* each handed a tracker by their caller — so it is charged to
|
|
108
|
+
* {@link CRAWL_SHARED_GIT_TRACKER_ID}, in a stratum belonging to no arm.
|
|
109
|
+
*
|
|
110
|
+
* The bracket is here, inside the class, and not at the six sites that build a
|
|
111
|
+
* tracker, for the reason `crawl-timing.ts` gives about `ResourceRegistry`: six
|
|
112
|
+
* copies are six chances to disagree, and a seventh site added later would
|
|
113
|
+
* silently go uncharged. Here, every caller is covered by construction — which
|
|
114
|
+
* includes `@vibe-agent-toolkit/discovery`, a package that could not have filed
|
|
115
|
+
* a row from its own call site at all, since it depends on `utils` alone.
|
|
116
|
+
*
|
|
117
|
+
* The early return above is deliberately OUTSIDE it: a re-entrant call does no
|
|
118
|
+
* work, and charging it would inflate `calls` with questions rather than
|
|
119
|
+
* spawns.
|
|
75
120
|
*/
|
|
76
121
|
initialize(options?: GitTrackerInitOptions): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* The active set read off a snapshot this process has ALREADY taken — or
|
|
124
|
+
* `null` to say "ask git yourself".
|
|
125
|
+
*
|
|
126
|
+
* ## Why this is the same question, not a similar one
|
|
127
|
+
*
|
|
128
|
+
* `git ls-files --cached --others --exclude-standard` returns
|
|
129
|
+
* `tracked ∪ (untracked ∧ ¬ignored)`. A snapshot is `git add --all` **without**
|
|
130
|
+
* `--force` staged into a throwaway index, whose membership is that same set
|
|
131
|
+
* by construction — the exclusion of ignored paths is `--force`'s absence in
|
|
132
|
+
* both cases. So where a snapshot is already in hand, the spawn this method
|
|
133
|
+
* replaces would re-derive a set the process is already holding.
|
|
134
|
+
*
|
|
135
|
+
* It is deliberately a PEEK ({@link peekGitTreeSnapshot}) and never a take: a
|
|
136
|
+
* snapshot costs a `git add --all`, so causing one to avoid an `ls-files`
|
|
137
|
+
* would be a large loss dressed as a saving. On the incumbent walk no snapshot
|
|
138
|
+
* is ever taken, the peek misses, and this tracker spawns exactly as it always
|
|
139
|
+
* has.
|
|
140
|
+
*
|
|
141
|
+
* ## The one divergence, and why it is an improvement
|
|
142
|
+
*
|
|
143
|
+
* `--cached` reads the REAL index, so a tracked file deleted from the working
|
|
144
|
+
* tree is still listed. `git add --all` stages that deletion, so the snapshot
|
|
145
|
+
* omits it. The set is therefore not identical — it is the same set minus
|
|
146
|
+
* paths that do not exist, which is what {@link isIgnoredByActiveSet} already
|
|
147
|
+
* documents the active set to be ("it can only ever contain paths that
|
|
148
|
+
* EXIST"). The snapshot-sourced set honours that sentence more exactly than
|
|
149
|
+
* the spawn does.
|
|
150
|
+
*
|
|
151
|
+
* @param includeUntracked - The caller's requested membership
|
|
152
|
+
* @returns Root-relative paths in `git ls-files` shape, or `null` when no
|
|
153
|
+
* snapshot is available or the request is one a snapshot cannot answer
|
|
154
|
+
*/
|
|
155
|
+
private activePathsFromOpenSnapshot;
|
|
77
156
|
/**
|
|
78
157
|
* Did git actually answer, or is this tracker an empty shell?
|
|
79
158
|
*
|
|
@@ -98,6 +177,22 @@ export declare class GitTracker {
|
|
|
98
177
|
* `hasActiveDescendant` / `isIgnoredByActiveSet` ancestor lookup misses on Windows.
|
|
99
178
|
*/
|
|
100
179
|
private populateAncestorSet;
|
|
180
|
+
/**
|
|
181
|
+
* Record `start` and every directory above it up to (not including) the
|
|
182
|
+
* project root as active ancestors, stopping at the first one already known
|
|
183
|
+
* — everything above it necessarily is too.
|
|
184
|
+
*
|
|
185
|
+
* @param start - Forward-slashed absolute directory to begin at
|
|
186
|
+
*/
|
|
187
|
+
private recordAncestorsFrom;
|
|
188
|
+
/**
|
|
189
|
+
* Whether the active set can say anything about a path: it cannot beneath a
|
|
190
|
+
* directory the listing was refused — see {@link unlistableDirectories}.
|
|
191
|
+
*
|
|
192
|
+
* @param normalizedAbsolutePath - Forward-slashed absolute path
|
|
193
|
+
* @returns True when the path lies strictly beneath a refused directory
|
|
194
|
+
*/
|
|
195
|
+
private beneathUnlistable;
|
|
101
196
|
/**
|
|
102
197
|
* Returns true if the given absolute path IS an active-set file OR is an
|
|
103
198
|
* ancestor directory of at least one active-set file.
|
|
@@ -117,7 +212,9 @@ export declare class GitTracker {
|
|
|
117
212
|
* For paths INSIDE the project root **that exist on disk**, membership in the
|
|
118
213
|
* active set is authoritative: such a path is ignored iff it is not in the
|
|
119
214
|
* active set AND not an ancestor of any active-set path. No `git
|
|
120
|
-
* check-ignore` spawn.
|
|
215
|
+
* check-ignore` spawn. The one exception is a path beneath a directory the
|
|
216
|
+
* listing could not OPEN — see {@link unlistableDirectories} — where absence
|
|
217
|
+
* says nothing and the question goes to `git check-ignore`.
|
|
121
218
|
*
|
|
122
219
|
* The existence qualifier is load-bearing, not a caveat. The active set is
|
|
123
220
|
* built from `git ls-files`, so it can only ever contain paths that EXIST — a
|
|
@@ -131,7 +228,38 @@ export declare class GitTracker {
|
|
|
131
228
|
* so a repeated miss on the same path stays O(1).
|
|
132
229
|
*
|
|
133
230
|
* For paths OUTSIDE the project root, falls back to {@link isIgnored} so
|
|
134
|
-
* legacy behavior is preserved.
|
|
231
|
+
* legacy behavior is preserved. That fallback is the expensive one, and its
|
|
232
|
+
* cost does not resemble the in-root cost at all.
|
|
233
|
+
*
|
|
234
|
+
* ## ⚠️ The out-of-root fallback costs an order of magnitude more per path
|
|
235
|
+
*
|
|
236
|
+
* Measured in the D9 parity fixture: an out-of-root path costs
|
|
237
|
+
* **185–427 ms**, against **12–28 ms** for every in-repo path — ≈7–36× on
|
|
238
|
+
* those ranges.
|
|
239
|
+
*
|
|
240
|
+
* The mechanism is the delegation above plus what git does with it.
|
|
241
|
+
* {@link isIgnored} runs `git check-ignore` with the PROJECT ROOT as cwd; git
|
|
242
|
+
* exits 128 for a path outside that repository; and {@link isGitIgnored}'s
|
|
243
|
+
* exit-128 recovery walk then spawns `check-ignore` ONCE PER ANCESTOR up to
|
|
244
|
+
* `/`. No ancestor of an out-of-root path is ever the project root that would
|
|
245
|
+
* stop the walk, and every one of those ancestors is outside the repository
|
|
246
|
+
* too — so each also exits 128 and the walk never breaks early either. The
|
|
247
|
+
* verdict is cached, so the price is paid once per DISTINCT out-of-root path
|
|
248
|
+
* rather than once per call.
|
|
249
|
+
*
|
|
250
|
+
* The consequence for a caller choosing between the two oracles: switching
|
|
251
|
+
* from {@link isIgnored} to this method removes the spawn population **only
|
|
252
|
+
* for in-root paths**. A lane feeding out-of-root paths in bulk keeps the
|
|
253
|
+
* per-path spawn in full. So a comment asserting that the out-of-project case
|
|
254
|
+
* is rare, safe or cheap is asserting something this measurement contradicts:
|
|
255
|
+
* it is rare in some lanes, and it is never cheap.
|
|
256
|
+
*
|
|
257
|
+
* The exit-128 walk itself is documented in {@link isGitIgnored} — but only
|
|
258
|
+
* for the two cases already answered there: the SYMLINK case it exists to
|
|
259
|
+
* serve, and the NO-REPOSITORY case, which an early return now short-circuits
|
|
260
|
+
* before any spawn. The IN-REPO OUT-OF-ROOT path is the third case, it is
|
|
261
|
+
* documented nowhere there, and it is the one that still pays the walk in
|
|
262
|
+
* full. That is why the number lives here.
|
|
135
263
|
*
|
|
136
264
|
* Requires {@link initialize} with `includeUntracked: true` (the default).
|
|
137
265
|
* When initialized without untracked files, this method delegates to
|
|
@@ -139,8 +267,21 @@ export declare class GitTracker {
|
|
|
139
267
|
* possible per-path spawn.
|
|
140
268
|
*
|
|
141
269
|
* @param absolutePath - Absolute path to check
|
|
270
|
+
* @param knownToExist - The caller's own answer to the existence question, when
|
|
271
|
+
* it has already asked. Supplying it skips this method's `existsSync`, which
|
|
272
|
+
* is otherwise paid once per path that is absent from the active set — i.e.
|
|
273
|
+
* once per ignored path, and the projection's `filesystem` extent enumerates
|
|
274
|
+
* all of them (11,108 calls on an 8,496-path adopter tree).
|
|
275
|
+
*
|
|
276
|
+
* **It must mean what `existsSync` means: `stat` succeeds, following
|
|
277
|
+
* symlinks.** A caller holding only an `lstat` result has a DIFFERENT fact —
|
|
278
|
+
* `lstat` succeeds on a dangling symlink where `existsSync` returns false —
|
|
279
|
+
* and must narrow it to `exists && symlinkResolves !== false` rather than
|
|
280
|
+
* pass the `lstat` boolean through, or dangling symlinks silently stop
|
|
281
|
+
* falling back to `git check-ignore` and start reporting as ignored.
|
|
282
|
+
* Omit it and nothing changes.
|
|
142
283
|
*/
|
|
143
|
-
isIgnoredByActiveSet(absolutePath: string): boolean;
|
|
284
|
+
isIgnoredByActiveSet(absolutePath: string, knownToExist?: boolean): boolean;
|
|
144
285
|
private isWithinProjectRoot;
|
|
145
286
|
/**
|
|
146
287
|
* Check if a file is ignored by git.
|
|
@@ -151,6 +292,28 @@ export declare class GitTracker {
|
|
|
151
292
|
* @returns true if file is gitignored, false otherwise
|
|
152
293
|
*/
|
|
153
294
|
isIgnored(filePath: string): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* The spelling git records for a path, or `null` if git has no record of it.
|
|
297
|
+
*
|
|
298
|
+
* This is the casing oracle, not another ignore check. On a case-insensitive
|
|
299
|
+
* filesystem `docs/Readme.md` and `docs/README.md` are one inode with two
|
|
300
|
+
* spellings, and Node's two `realpath` implementations disagree about which
|
|
301
|
+
* one they hand back — so anything that derives an identity from a path needs
|
|
302
|
+
* a single authoritative spelling, and git's is it wherever git has one.
|
|
303
|
+
*
|
|
304
|
+
* The lookup key is lowercased, which is the point: the caller asks with
|
|
305
|
+
* whatever casing it observed and gets back the casing git holds.
|
|
306
|
+
*
|
|
307
|
+
* Answers only from the pre-populated set — never spawns. A path git does not
|
|
308
|
+
* know (untracked-and-ignored, non-existent, outside the project root, or any
|
|
309
|
+
* path at all when `git ls-files` did not answer) returns `null`, and the
|
|
310
|
+
* caller falls back to the on-disk casing.
|
|
311
|
+
*
|
|
312
|
+
* @param absolutePath - Absolute path to look up
|
|
313
|
+
* @returns Root-relative, forward-slashed path as git spells it — relative to
|
|
314
|
+
* THIS tracker's project root — or `null` when git has no record of it
|
|
315
|
+
*/
|
|
316
|
+
indexPathFor(absolutePath: string): string | null;
|
|
154
317
|
/**
|
|
155
318
|
* Get cache statistics.
|
|
156
319
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-tracker.d.ts","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"git-tracker.d.ts","sourceRoot":"","sources":["../src/git-tracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAgB;IACtD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAS;IACnC,0EAA0E;IAC1E,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAK/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0ChE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,OAAO,CAAC,2BAA2B;IA6BnC;;;;;;;;;;;;;OAaG;IACH,QAAQ,IAAI,OAAO;IAInB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAe3B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAUlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0EG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO;IAiC3E,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAqBpC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIjD;;OAEG;IACH,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;IAQrF;;OAEG;IACH,KAAK,IAAI,IAAI;CAUd"}
|