@fodx/codelens 1.0.0
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 +212 -0
- package/adapters/pi/codelens.extension.ts +280 -0
- package/adapters/pi/extension.json +10 -0
- package/build/src/cli.js +265 -0
- package/build/src/cli.js.map +1 -0
- package/build/src/context/schema.sql +23 -0
- package/build/src/context/store.js +91 -0
- package/build/src/context/store.js.map +1 -0
- package/build/src/db/db.js +65 -0
- package/build/src/db/db.js.map +1 -0
- package/build/src/db/migrations.js +88 -0
- package/build/src/db/migrations.js.map +1 -0
- package/build/src/db/schema.js +11 -0
- package/build/src/db/schema.js.map +1 -0
- package/build/src/db/schema.sql +111 -0
- package/build/src/git/scope.js +68 -0
- package/build/src/git/scope.js.map +1 -0
- package/build/src/graph/edges.js +76 -0
- package/build/src/graph/edges.js.map +1 -0
- package/build/src/graph/grammars.js +57 -0
- package/build/src/graph/grammars.js.map +1 -0
- package/build/src/graph/query.js +52 -0
- package/build/src/graph/query.js.map +1 -0
- package/build/src/graph/resolve.js +68 -0
- package/build/src/graph/resolve.js.map +1 -0
- package/build/src/graph/symbols.js +84 -0
- package/build/src/graph/symbols.js.map +1 -0
- package/build/src/graph/tests.js +73 -0
- package/build/src/graph/tests.js.map +1 -0
- package/build/src/index/autoprune.js +29 -0
- package/build/src/index/autoprune.js.map +1 -0
- package/build/src/index/deny.js +40 -0
- package/build/src/index/deny.js.map +1 -0
- package/build/src/index/freshness.js +60 -0
- package/build/src/index/freshness.js.map +1 -0
- package/build/src/index/fts.js +125 -0
- package/build/src/index/fts.js.map +1 -0
- package/build/src/index/identity.js +21 -0
- package/build/src/index/identity.js.map +1 -0
- package/build/src/index/indexer.js +32 -0
- package/build/src/index/indexer.js.map +1 -0
- package/build/src/index/manager.js +48 -0
- package/build/src/index/manager.js.map +1 -0
- package/build/src/index/queue.js +47 -0
- package/build/src/index/queue.js.map +1 -0
- package/build/src/index/recovery.js +51 -0
- package/build/src/index/recovery.js.map +1 -0
- package/build/src/index/reindex.js +70 -0
- package/build/src/index/reindex.js.map +1 -0
- package/build/src/index/scanner.js +147 -0
- package/build/src/index/scanner.js.map +1 -0
- package/build/src/index/ttl.js +87 -0
- package/build/src/index/ttl.js.map +1 -0
- package/build/src/index/watcher.js +74 -0
- package/build/src/index/watcher.js.map +1 -0
- package/build/src/installer/agents.js +440 -0
- package/build/src/installer/agents.js.map +1 -0
- package/build/src/obs/doctor.js +53 -0
- package/build/src/obs/doctor.js.map +1 -0
- package/build/src/obs/stats.js +28 -0
- package/build/src/obs/stats.js.map +1 -0
- package/build/src/obs/usage.js +136 -0
- package/build/src/obs/usage.js.map +1 -0
- package/build/src/search/rank.js +70 -0
- package/build/src/search/rank.js.map +1 -0
- package/build/src/search/snippet.js +66 -0
- package/build/src/search/snippet.js.map +1 -0
- package/build/src/server.js +126 -0
- package/build/src/server.js.map +1 -0
- package/build/src/tools/current.js +32 -0
- package/build/src/tools/current.js.map +1 -0
- package/build/src/tools/expand.js +54 -0
- package/build/src/tools/expand.js.map +1 -0
- package/build/src/tools/prune.js +46 -0
- package/build/src/tools/prune.js.map +1 -0
- package/build/src/tools/refresh.js +14 -0
- package/build/src/tools/refresh.js.map +1 -0
- package/build/src/tools/registry.js +176 -0
- package/build/src/tools/registry.js.map +1 -0
- package/build/src/tools/related.js +28 -0
- package/build/src/tools/related.js.map +1 -0
- package/build/src/tools/save.js +15 -0
- package/build/src/tools/save.js.map +1 -0
- package/build/src/tools/search.js +124 -0
- package/build/src/tools/search.js.map +1 -0
- package/build/src/upgrade.js +74 -0
- package/build/src/upgrade.js.map +1 -0
- package/build/src/util/hash.js +15 -0
- package/build/src/util/hash.js.map +1 -0
- package/build/src/util/paths.js +67 -0
- package/build/src/util/paths.js.map +1 -0
- package/build/src/version.js +27 -0
- package/build/src/version.js.map +1 -0
- package/docs/agent-guide.md +47 -0
- package/docs/codelens-preview.png +0 -0
- package/docs/routing.md +59 -0
- package/docs/tools.md +53 -0
- package/package.json +103 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { statSync, openSync, readSync, closeSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, posix } from "node:path";
|
|
4
|
+
import { resolveReal, toPosix } from "../util/paths.js";
|
|
5
|
+
import { shouldDeny } from "./deny.js";
|
|
6
|
+
import ignore from "ignore";
|
|
7
|
+
/**
|
|
8
|
+
* File scanner (Design Decisions #2, #3, #11).
|
|
9
|
+
*
|
|
10
|
+
* Honors .gitignore via `git ls-files` (includes untracked, excludes ignored).
|
|
11
|
+
* Applies heuristic deny (build/dist/out/coverage + generated patterns) even
|
|
12
|
+
* for untracked files. Skips files >5MB. Skips binary files (NUL in first 8KB).
|
|
13
|
+
* Returns POSIX repo-relative paths.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_FILE_BYTES = 5 * 1024 * 1024; // 5MB
|
|
16
|
+
const EXT_LANG = {
|
|
17
|
+
".ts": "typescript", ".tsx": "typescript", ".js": "javascript", ".jsx": "javascript",
|
|
18
|
+
".mjs": "javascript", ".cjs": "javascript", ".py": "python", ".go": "go", ".rs": "rust",
|
|
19
|
+
".java": "java", ".rb": "ruby", ".php": "php", ".c": "c", ".h": "c", ".cpp": "cpp",
|
|
20
|
+
".cc": "cpp", ".hpp": "cpp", ".cs": "csharp", ".swift": "swift", ".kt": "kotlin",
|
|
21
|
+
".scala": "scala", ".sh": "bash", ".md": "markdown", ".json": "json", ".yaml": "yaml",
|
|
22
|
+
".yml": "yaml", ".toml": "toml", ".sql": "sql",
|
|
23
|
+
};
|
|
24
|
+
function inferLanguage(posixPath) {
|
|
25
|
+
const ext = posix.extname(posixPath).toLowerCase();
|
|
26
|
+
return EXT_LANG[ext] ?? null;
|
|
27
|
+
}
|
|
28
|
+
/** True if file starts with a NUL byte or non-UTF8 bytes in first 8KB (binary heuristic). */
|
|
29
|
+
export function isBinary(absPath) {
|
|
30
|
+
let fd = null;
|
|
31
|
+
try {
|
|
32
|
+
fd = openSync(absPath, "r");
|
|
33
|
+
const buf = Buffer.alloc(8192);
|
|
34
|
+
const n = readSync(fd, buf, 0, 8192, 0);
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
const b = buf[i];
|
|
37
|
+
// NUL byte → binary. Allow common text bytes (incl. tab/cr/lf).
|
|
38
|
+
if (b === 0)
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Heuristic: if >10% of bytes are non-text control chars (excluding \t \n \r), treat as binary.
|
|
42
|
+
let controls = 0;
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
const b = buf[i];
|
|
45
|
+
if (b < 9 || (b > 13 && b < 32))
|
|
46
|
+
controls++;
|
|
47
|
+
}
|
|
48
|
+
return n > 0 && controls / n > 0.1;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return true; // unreadable → treat as binary/skip
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
if (fd !== null)
|
|
55
|
+
try {
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
catch { /* ignore */ }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Scan the repo at `repoRoot`. Uses `git ls-files` when inside a git repo
|
|
63
|
+
* (honors .gitignore: untracked included, ignored excluded). Falls back to a
|
|
64
|
+
* plain directory walk for non-git dirs.
|
|
65
|
+
*/
|
|
66
|
+
export function scanFiles(repoRoot) {
|
|
67
|
+
const root = resolveReal(repoRoot);
|
|
68
|
+
const relPaths = gitLsFiles(root) ?? walkDir(root);
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const rel of relPaths) {
|
|
71
|
+
const p = toPosix(rel);
|
|
72
|
+
if (shouldDeny(p))
|
|
73
|
+
continue;
|
|
74
|
+
const abs = join(root, p);
|
|
75
|
+
let st;
|
|
76
|
+
try {
|
|
77
|
+
const s = statSync(abs);
|
|
78
|
+
if (!s.isFile())
|
|
79
|
+
continue;
|
|
80
|
+
st = { size: s.size, mtimeMs: s.mtimeMs };
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (st.size > MAX_FILE_BYTES)
|
|
86
|
+
continue;
|
|
87
|
+
if (isBinary(abs))
|
|
88
|
+
continue;
|
|
89
|
+
out.push({ path: p, size: st.size, mtimeMs: st.mtimeMs, language: inferLanguage(p) });
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/** `git ls-files --others --exclude-standard --cached -z` → untracked+tracked, ignored excluded. */
|
|
94
|
+
function gitLsFiles(root) {
|
|
95
|
+
const r = spawnSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
96
|
+
cwd: root,
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
});
|
|
99
|
+
if (r.error || r.status !== 0)
|
|
100
|
+
return null;
|
|
101
|
+
return (r.stdout ?? "").split("\0").filter((s) => s.length > 0);
|
|
102
|
+
}
|
|
103
|
+
/** Plain directory walk fallback for non-git dirs. Honors nested .gitignore. */
|
|
104
|
+
function walkDir(root) {
|
|
105
|
+
const out = [];
|
|
106
|
+
// Each stack frame carries the cumulative .gitignore pattern lines from all
|
|
107
|
+
// ancestor dirs (gitignore patterns without a slash match in any subdir, so
|
|
108
|
+
// concatenating lines approximates git's nested semantics for the fallback).
|
|
109
|
+
const stack = [{ dir: root, patterns: [] }];
|
|
110
|
+
while (stack.length) {
|
|
111
|
+
const { dir: cur, patterns } = stack.pop();
|
|
112
|
+
let entries;
|
|
113
|
+
try {
|
|
114
|
+
entries = readdirSync(cur, { withFileTypes: true });
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Inherit ancestor patterns and add this dir's .gitignore lines (if present).
|
|
120
|
+
let dirPatterns = patterns;
|
|
121
|
+
let dirIg = ignore().add(patterns);
|
|
122
|
+
try {
|
|
123
|
+
const gi = readFileSync(posix.join(cur, ".gitignore"), "utf-8");
|
|
124
|
+
dirPatterns = [...patterns, ...gi.split(/\r?\n/).filter((l) => l.trim().length > 0)];
|
|
125
|
+
dirIg = ignore().add(dirPatterns);
|
|
126
|
+
}
|
|
127
|
+
catch { /* no .gitignore here */ }
|
|
128
|
+
for (const e of entries) {
|
|
129
|
+
if (e.name === ".gitignore" || e.name.startsWith(".git"))
|
|
130
|
+
continue;
|
|
131
|
+
const full = posix.join(cur, e.name);
|
|
132
|
+
const rel = toPosix(full.slice(root.length + 1));
|
|
133
|
+
if (e.isDirectory()) {
|
|
134
|
+
if (dirIg.ignores(rel + "/"))
|
|
135
|
+
continue;
|
|
136
|
+
stack.push({ dir: full, patterns: dirPatterns });
|
|
137
|
+
}
|
|
138
|
+
else if (e.isFile()) {
|
|
139
|
+
if (dirIg.ignores(rel))
|
|
140
|
+
continue;
|
|
141
|
+
out.push(rel);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../../src/index/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getActiveIndexId } from "./manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* TTL pruner (Step 22).
|
|
4
|
+
*
|
|
5
|
+
* Deletes inactive indexes per retention policy. NEVER deletes:
|
|
6
|
+
* - the active index
|
|
7
|
+
* - pinned indexes
|
|
8
|
+
* - indexes with an active (non-expired) write lease
|
|
9
|
+
* - indexes accessed within the grace window
|
|
10
|
+
*
|
|
11
|
+
* Default retention:
|
|
12
|
+
* active index: never (expires_at NULL)
|
|
13
|
+
* inactive branch: 14 days since last_access
|
|
14
|
+
* detached: 3 days
|
|
15
|
+
* temp worktree: 48h
|
|
16
|
+
* Grace window: 1h (don't prune if accessed in the last hour even if expired).
|
|
17
|
+
*/
|
|
18
|
+
export const RETENTION = {
|
|
19
|
+
inactiveBranchDays: 14,
|
|
20
|
+
detachedDays: 3,
|
|
21
|
+
worktreeHours: 48,
|
|
22
|
+
graceMs: 3600_000,
|
|
23
|
+
};
|
|
24
|
+
/** Compute an expires_at timestamp for an index row (ms since epoch). */
|
|
25
|
+
export function computeExpiry(row, _now = Date.now()) {
|
|
26
|
+
if (row.pinned)
|
|
27
|
+
return null;
|
|
28
|
+
// Explicit expires_at wins; NULL means "compute from last_accessed_at".
|
|
29
|
+
// The currently-active index is guarded in pruneIndexes, not here: status='active'
|
|
30
|
+
// just means "was active once", not "currently in use", so it must NOT bypass TTL
|
|
31
|
+
// or no index would ever expire (every index is created with status='active').
|
|
32
|
+
if (row.expires_at !== null)
|
|
33
|
+
return row.expires_at;
|
|
34
|
+
if (row.branch_name === "DETACHED")
|
|
35
|
+
return row.last_accessed_at + RETENTION.detachedDays * 86400_000;
|
|
36
|
+
if (row.worktree_path && row.worktree_path !== row.repo_root) {
|
|
37
|
+
return row.last_accessed_at + RETENTION.worktreeHours * 3600_000;
|
|
38
|
+
}
|
|
39
|
+
return row.last_accessed_at + RETENTION.inactiveBranchDays * 86400_000;
|
|
40
|
+
}
|
|
41
|
+
/** Prune expired inactive indexes. Respects never-delete guards. */
|
|
42
|
+
export function pruneIndexes(db, now = Date.now()) {
|
|
43
|
+
const active = getActiveIndexId();
|
|
44
|
+
// Exclude indexes with an active (non-expired) write lease via NOT EXISTS.
|
|
45
|
+
const rows = db.prepare(`SELECT i.id AS id, i.pinned AS pinned, i.status AS status, i.branch_name AS branch,
|
|
46
|
+
i.last_accessed_at AS last, i.expires_at AS expires_at, i.worktree_path AS wpath, i.repo_root AS root
|
|
47
|
+
FROM indexes i
|
|
48
|
+
WHERE NOT EXISTS (
|
|
49
|
+
SELECT 1 FROM index_locks l WHERE l.index_id = i.id AND l.expires_at > ?
|
|
50
|
+
)`).all(now);
|
|
51
|
+
const deleted = [];
|
|
52
|
+
let skipped = 0;
|
|
53
|
+
for (const r of rows) {
|
|
54
|
+
// Never-delete guards.
|
|
55
|
+
if (r.id === active) {
|
|
56
|
+
skipped++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (r.pinned) {
|
|
60
|
+
skipped++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (now - r.last < RETENTION.graceMs) {
|
|
64
|
+
skipped++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const expiry = r.expires_at ?? computeExpiry({ pinned: r.pinned, status: r.status, branch_name: r.branch, head_sha: "", last_accessed_at: r.last, expires_at: r.expires_at, worktree_path: r.wpath }, now);
|
|
68
|
+
if (expiry === null || now < expiry) {
|
|
69
|
+
skipped++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Delete scoped rows across all tables, transactionally.
|
|
73
|
+
const tx = db.transaction(() => {
|
|
74
|
+
db.prepare("DELETE FROM chunks_fts WHERE index_id = ?").run(r.id);
|
|
75
|
+
db.prepare("DELETE FROM chunks WHERE index_id = ?").run(r.id);
|
|
76
|
+
db.prepare("DELETE FROM symbols WHERE index_id = ?").run(r.id);
|
|
77
|
+
db.prepare("DELETE FROM edges WHERE index_id = ?").run(r.id);
|
|
78
|
+
db.prepare("DELETE FROM files WHERE index_id = ?").run(r.id);
|
|
79
|
+
db.prepare("DELETE FROM index_locks WHERE index_id = ?").run(r.id);
|
|
80
|
+
db.prepare("DELETE FROM indexes WHERE id = ?").run(r.id);
|
|
81
|
+
});
|
|
82
|
+
tx();
|
|
83
|
+
deleted.push(r.id);
|
|
84
|
+
}
|
|
85
|
+
return { deletedIndexes: deleted, skipped, bytesFreed: 0 };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=ttl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ttl.js","sourceRoot":"","sources":["../../../src/index/ttl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
import { resolveReal, toPosix } from "../util/paths.js";
|
|
3
|
+
import { relative, posix } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* File watcher (Gap #6).
|
|
6
|
+
*
|
|
7
|
+
* Maintains a set of repo-relative POSIX paths that changed since the last
|
|
8
|
+
* consume(), via recursive fs.watch where supported. Used by ensureFreshIndex
|
|
9
|
+
* to skip re-scanning during quiet periods (the main per-query cost) while
|
|
10
|
+
* still catching edits promptly.
|
|
11
|
+
*
|
|
12
|
+
* Platform note: recursive fs.watch is supported on macOS and Windows. On
|
|
13
|
+
* Linux it falls back to a non-recursive watch of the repo root only — callers
|
|
14
|
+
* must not rely on completeness; ensureFreshIndex periodically does a full
|
|
15
|
+
* scan as a safety net (FULL_SCAN_INTERVAL_MS) so missed changes are caught.
|
|
16
|
+
*
|
|
17
|
+
* Always non-fatal: if watching fails (unsupported FS, permissions), the
|
|
18
|
+
* watcher stays empty and the tool falls back to full-scan-per-query (the
|
|
19
|
+
* original behavior).
|
|
20
|
+
*/
|
|
21
|
+
export class FileWatcher {
|
|
22
|
+
repoRoot;
|
|
23
|
+
watcher = null;
|
|
24
|
+
dirty = new Set();
|
|
25
|
+
started = false;
|
|
26
|
+
constructor(repoRoot) {
|
|
27
|
+
this.repoRoot = resolveReal(repoRoot);
|
|
28
|
+
}
|
|
29
|
+
start() {
|
|
30
|
+
if (this.started)
|
|
31
|
+
return;
|
|
32
|
+
this.started = true;
|
|
33
|
+
try {
|
|
34
|
+
this.watcher = watch(this.repoRoot, { recursive: true }, (_event, filename) => {
|
|
35
|
+
if (!filename)
|
|
36
|
+
return;
|
|
37
|
+
const abs = posix.join(this.repoRoot, toPosix(filename));
|
|
38
|
+
try {
|
|
39
|
+
const rel = toPosix(relative(this.repoRoot, abs));
|
|
40
|
+
if (rel && !rel.startsWith(".."))
|
|
41
|
+
this.dirty.add(rel);
|
|
42
|
+
}
|
|
43
|
+
catch { /* ignore */ }
|
|
44
|
+
});
|
|
45
|
+
this.watcher.on("error", () => { });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
this.watcher = null; // unsupported → degrade to full-scan
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
stop() {
|
|
52
|
+
try {
|
|
53
|
+
this.watcher?.close();
|
|
54
|
+
}
|
|
55
|
+
catch { /* ignore */ }
|
|
56
|
+
this.watcher = null;
|
|
57
|
+
this.started = false;
|
|
58
|
+
}
|
|
59
|
+
/** True if the watcher is active (may still be partial on Linux). */
|
|
60
|
+
get active() {
|
|
61
|
+
return this.watcher !== null;
|
|
62
|
+
}
|
|
63
|
+
/** Snapshot + clear the dirty set. Returns repo-relative POSIX paths. */
|
|
64
|
+
consume() {
|
|
65
|
+
const out = [...this.dirty];
|
|
66
|
+
this.dirty.clear();
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
/** Mark a path dirty manually (e.g. after an in-process index write). */
|
|
70
|
+
markDirty(rel) {
|
|
71
|
+
this.dirty.add(rel);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/index/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,OAAO,WAAW;IACd,QAAQ,CAAS;IACjB,OAAO,GAAqB,IAAI,CAAC;IAChC,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3B,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;gBAC5E,IAAI,CAAC,QAAQ;oBAAE,OAAO;gBACtB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACzD,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;oBAClD,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;wBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACxD,CAAC;gBAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAmD,CAAC,CAAC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,qCAAqC;QAC5D,CAAC;IACH,CAAC;IAED,IAAI;QACF,IAAI,CAAC;YAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,qEAAqE;IACrE,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;IAED,yEAAyE;IACzE,OAAO;QACL,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,yEAAyE;IACzE,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;CACF"}
|