recce-nightly 1.3.0.20250507__py3-none-any.whl → 1.4.0.20250515__py3-none-any.whl
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.
Potentially problematic release.
This version of recce-nightly might be problematic. Click here for more details.
- recce/VERSION +1 -1
- recce/__init__.py +22 -22
- recce/adapter/base.py +11 -14
- recce/adapter/dbt_adapter/__init__.py +355 -316
- recce/adapter/dbt_adapter/dbt_version.py +3 -0
- recce/adapter/sqlmesh_adapter.py +24 -35
- recce/apis/check_api.py +39 -28
- recce/apis/check_func.py +33 -27
- recce/apis/run_api.py +25 -19
- recce/apis/run_func.py +29 -23
- recce/artifact.py +44 -49
- recce/cli.py +484 -285
- recce/config.py +42 -33
- recce/core.py +52 -44
- recce/data/404.html +1 -1
- recce/data/_next/static/chunks/{368-7587b306577df275.js → 778-aef312bffb4c0312.js} +15 -15
- recce/data/_next/static/chunks/8d700b6a.ed11a130057c7a47.js +1 -0
- recce/data/_next/static/chunks/app/layout-c713a2829d3279e4.js +1 -0
- recce/data/_next/static/chunks/app/page-7086764277331fcb.js +1 -0
- recce/data/_next/static/chunks/{cd9f8d63-cf0d5a7b0f7a92e8.js → cd9f8d63-e020f408095ed77c.js} +3 -3
- recce/data/_next/static/chunks/webpack-b787cb1a4f2293de.js +1 -0
- recce/data/_next/static/css/88b8abc134cfd59a.css +3 -0
- recce/data/index.html +2 -2
- recce/data/index.txt +2 -2
- recce/diff.py +6 -12
- recce/event/__init__.py +74 -72
- recce/event/collector.py +27 -20
- recce/event/track.py +39 -27
- recce/exceptions.py +1 -1
- recce/git.py +7 -7
- recce/github.py +57 -53
- recce/models/__init__.py +1 -1
- recce/models/check.py +6 -7
- recce/models/run.py +1 -0
- recce/models/types.py +27 -27
- recce/pull_request.py +26 -24
- recce/run.py +148 -111
- recce/server.py +103 -89
- recce/state.py +209 -177
- recce/summary.py +168 -143
- recce/tasks/__init__.py +3 -3
- recce/tasks/core.py +11 -13
- recce/tasks/dataframe.py +19 -17
- recce/tasks/histogram.py +69 -34
- recce/tasks/lineage.py +2 -2
- recce/tasks/profile.py +147 -86
- recce/tasks/query.py +139 -87
- recce/tasks/rowcount.py +33 -30
- recce/tasks/schema.py +14 -14
- recce/tasks/top_k.py +35 -35
- recce/tasks/valuediff.py +216 -152
- recce/util/breaking.py +77 -84
- recce/util/cll.py +55 -51
- recce/util/io.py +19 -17
- recce/util/logger.py +1 -1
- recce/util/recce_cloud.py +70 -72
- recce/util/singleton.py +4 -4
- recce/yaml/__init__.py +7 -10
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250515.dist-info}/METADATA +5 -2
- recce_nightly-1.4.0.20250515.dist-info/RECORD +143 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250515.dist-info}/WHEEL +1 -1
- tests/adapter/dbt_adapter/conftest.py +1 -0
- tests/adapter/dbt_adapter/dbt_test_helper.py +28 -18
- tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
- tests/adapter/dbt_adapter/test_dbt_cll.py +39 -32
- tests/adapter/dbt_adapter/test_selector.py +22 -21
- tests/tasks/test_histogram.py +58 -66
- tests/tasks/test_lineage.py +36 -23
- tests/tasks/test_preset_checks.py +45 -31
- tests/tasks/test_profile.py +340 -15
- tests/tasks/test_query.py +40 -40
- tests/tasks/test_row_count.py +65 -46
- tests/tasks/test_schema.py +65 -42
- tests/tasks/test_top_k.py +22 -18
- tests/tasks/test_valuediff.py +43 -32
- tests/test_cli.py +71 -58
- tests/test_config.py +7 -9
- tests/test_core.py +5 -3
- tests/test_dbt.py +7 -7
- tests/test_pull_request.py +1 -1
- tests/test_server.py +19 -13
- tests/test_state.py +40 -27
- tests/test_summary.py +18 -14
- recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
- recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
- recce/data/_next/static/chunks/app/page-92f13c8fad9fae3d.js +0 -1
- recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
- recce_nightly-1.3.0.20250507.dist-info/RECORD +0 -142
- /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → q0Xsc9Sd6PDuo1lshYpLu}/_buildManifest.js +0 -0
- /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → q0Xsc9Sd6PDuo1lshYpLu}/_ssgManifest.js +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250515.dist-info}/entry_points.txt +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250515.dist-info}/licenses/LICENSE +0 -0
- {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250515.dist-info}/top_level.txt +0 -0
recce/config.py
CHANGED
|
@@ -5,11 +5,11 @@ from recce import yaml
|
|
|
5
5
|
from recce.exceptions import RecceConfigException
|
|
6
6
|
from recce.util import SingletonMeta
|
|
7
7
|
|
|
8
|
-
RECCE_CONFIG_FILE =
|
|
9
|
-
RECCE_PRESET_CHECK_COMMENT =
|
|
8
|
+
RECCE_CONFIG_FILE = "recce.yml"
|
|
9
|
+
RECCE_PRESET_CHECK_COMMENT = """Preset Checks
|
|
10
10
|
Please see https://docs.datarecce.io/features/preset-checks/
|
|
11
|
-
|
|
12
|
-
RECCE_ERROR_LOG_FILE =
|
|
11
|
+
"""
|
|
12
|
+
RECCE_ERROR_LOG_FILE = "recce_error.log"
|
|
13
13
|
console = Console()
|
|
14
14
|
|
|
15
15
|
|
|
@@ -21,83 +21,92 @@ class RecceConfig(metaclass=SingletonMeta):
|
|
|
21
21
|
|
|
22
22
|
def load(self):
|
|
23
23
|
try:
|
|
24
|
-
with open(self.config_file,
|
|
24
|
+
with open(self.config_file, "r") as f:
|
|
25
25
|
config = yaml.safe_load(f)
|
|
26
26
|
self.config = config if config else {}
|
|
27
27
|
self._verify_preset_checks()
|
|
28
28
|
except FileNotFoundError:
|
|
29
|
-
console.print(f
|
|
29
|
+
console.print(f"Recce config file not found. Generating default config file at '{self.config_file}'")
|
|
30
30
|
self.config = self.generate_template()
|
|
31
31
|
self.save()
|
|
32
32
|
|
|
33
33
|
def _verify_preset_checks(self):
|
|
34
34
|
from recce.tasks.core import CheckValidator
|
|
35
35
|
|
|
36
|
-
if not self.config.get(
|
|
36
|
+
if not self.config.get("checks"):
|
|
37
37
|
return
|
|
38
38
|
|
|
39
|
-
for check in self.config[
|
|
39
|
+
for check in self.config["checks"]:
|
|
40
40
|
try:
|
|
41
|
-
check_type = check.get(
|
|
41
|
+
check_type = check.get("type")
|
|
42
42
|
if check_type is None:
|
|
43
43
|
raise ValueError(f'Check type is required for check "{check}"')
|
|
44
|
-
if check_type ==
|
|
44
|
+
if check_type == "lineage_diff":
|
|
45
45
|
from recce.tasks.lineage import LineageDiffCheckValidator
|
|
46
|
+
|
|
46
47
|
validator = LineageDiffCheckValidator()
|
|
47
|
-
elif check_type ==
|
|
48
|
+
elif check_type == "schema_diff":
|
|
48
49
|
from recce.tasks.schema import SchemaDiffCheckValidator
|
|
50
|
+
|
|
49
51
|
validator = SchemaDiffCheckValidator()
|
|
50
|
-
elif check_type ==
|
|
52
|
+
elif check_type == "row_count_diff":
|
|
51
53
|
from recce.tasks.rowcount import RowCountDiffCheckValidator
|
|
54
|
+
|
|
52
55
|
validator = RowCountDiffCheckValidator()
|
|
53
|
-
elif check_type ==
|
|
56
|
+
elif check_type == "query":
|
|
54
57
|
from recce.tasks.query import QueryCheckValidator
|
|
58
|
+
|
|
55
59
|
validator = QueryCheckValidator()
|
|
56
|
-
elif check_type ==
|
|
60
|
+
elif check_type == "query_diff":
|
|
57
61
|
from recce.tasks.query import QueryDiffCheckValidator
|
|
62
|
+
|
|
58
63
|
validator = QueryDiffCheckValidator()
|
|
59
|
-
elif check_type ==
|
|
64
|
+
elif check_type == "value_diff" or check_type == "value_diff_detail":
|
|
60
65
|
from recce.tasks.valuediff import ValueDiffCheckValidator
|
|
66
|
+
|
|
61
67
|
validator = ValueDiffCheckValidator()
|
|
62
|
-
elif check_type ==
|
|
68
|
+
elif check_type == "profile_diff":
|
|
63
69
|
from recce.tasks.profile import ProfileCheckValidator
|
|
70
|
+
|
|
64
71
|
validator = ProfileCheckValidator()
|
|
65
|
-
elif check_type ==
|
|
72
|
+
elif check_type == "top_k_diff":
|
|
66
73
|
from recce.tasks.top_k import TopKDiffCheckValidator
|
|
74
|
+
|
|
67
75
|
validator = TopKDiffCheckValidator()
|
|
68
|
-
elif check_type ==
|
|
76
|
+
elif check_type == "histogram_diff":
|
|
69
77
|
from recce.tasks.histogram import HistogramDiffCheckValidator
|
|
78
|
+
|
|
70
79
|
validator = HistogramDiffCheckValidator()
|
|
71
80
|
else:
|
|
72
81
|
validator = CheckValidator()
|
|
73
82
|
validator.validate(check)
|
|
74
83
|
except Exception as e:
|
|
75
84
|
import json
|
|
85
|
+
|
|
76
86
|
raise RecceConfigException(
|
|
77
|
-
f"Load preset checks failed from '{self.config_file}'\n{json.dumps(check, indent=2)}",
|
|
78
|
-
|
|
87
|
+
f"Load preset checks failed from '{self.config_file}'\n{json.dumps(check, indent=2)}", cause=e
|
|
88
|
+
)
|
|
79
89
|
|
|
80
90
|
def generate_template(self):
|
|
81
|
-
data = yaml.CommentedMap(
|
|
82
|
-
|
|
83
|
-
data.yaml_set_comment_before_after_key('checks', before=RECCE_PRESET_CHECK_COMMENT)
|
|
91
|
+
data = yaml.CommentedMap(checks=yaml.CommentedSeq())
|
|
92
|
+
data.yaml_set_comment_before_after_key("checks", before=RECCE_PRESET_CHECK_COMMENT)
|
|
84
93
|
# Define default preset checks
|
|
85
94
|
default_checks = [
|
|
86
95
|
yaml.CommentedMap(
|
|
87
|
-
name=
|
|
88
|
-
description=
|
|
89
|
-
type=
|
|
90
|
-
params={
|
|
96
|
+
name="Row count diff",
|
|
97
|
+
description="Check the row count diff for all table models.",
|
|
98
|
+
type="row_count_diff",
|
|
99
|
+
params={"select": "state:modified,config.materialized:table"},
|
|
91
100
|
),
|
|
92
101
|
yaml.CommentedMap(
|
|
93
|
-
name=
|
|
94
|
-
description=
|
|
95
|
-
type=
|
|
96
|
-
)
|
|
102
|
+
name="Schema diff",
|
|
103
|
+
description="Check the schema diff for all nodes.",
|
|
104
|
+
type="schema_diff",
|
|
105
|
+
),
|
|
97
106
|
]
|
|
98
107
|
|
|
99
108
|
for check in default_checks:
|
|
100
|
-
data[
|
|
109
|
+
data["checks"].append(check)
|
|
101
110
|
|
|
102
111
|
return data
|
|
103
112
|
|
|
@@ -108,7 +117,7 @@ class RecceConfig(metaclass=SingletonMeta):
|
|
|
108
117
|
self.config[key] = value
|
|
109
118
|
|
|
110
119
|
def save(self):
|
|
111
|
-
with open(RECCE_CONFIG_FILE,
|
|
120
|
+
with open(RECCE_CONFIG_FILE, "w") as f:
|
|
112
121
|
yaml.dump(self.config, f)
|
|
113
122
|
|
|
114
123
|
def __str__(self):
|
recce/core.py
CHANGED
|
@@ -3,15 +3,21 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Callable, Dict, Optional,
|
|
6
|
+
from typing import Callable, Dict, List, Optional, Set, Tuple
|
|
7
7
|
|
|
8
8
|
from recce.adapter.base import BaseAdapter
|
|
9
9
|
from recce.models import Check, Run
|
|
10
10
|
from recce.models.types import LineageDiff
|
|
11
|
-
from recce.state import
|
|
11
|
+
from recce.state import (
|
|
12
|
+
GitRepoInfo,
|
|
13
|
+
PullRequestInfo,
|
|
14
|
+
RecceState,
|
|
15
|
+
RecceStateLoader,
|
|
16
|
+
RecceStateMetadata,
|
|
17
|
+
)
|
|
12
18
|
from recce.util.recce_cloud import set_recce_cloud_onboarding_state
|
|
13
19
|
|
|
14
|
-
logger = logging.getLogger(
|
|
20
|
+
logger = logging.getLogger("uvicorn")
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
@dataclass
|
|
@@ -25,8 +31,8 @@ class RecceContext:
|
|
|
25
31
|
|
|
26
32
|
@classmethod
|
|
27
33
|
def load(cls, **kwargs):
|
|
28
|
-
state_loader: RecceStateLoader = kwargs.get(
|
|
29
|
-
is_review_mode = kwargs.get(
|
|
34
|
+
state_loader: RecceStateLoader = kwargs.get("state_loader")
|
|
35
|
+
is_review_mode = kwargs.get("review", False)
|
|
30
36
|
|
|
31
37
|
context = cls(
|
|
32
38
|
review_mode=is_review_mode,
|
|
@@ -34,14 +40,16 @@ class RecceContext:
|
|
|
34
40
|
)
|
|
35
41
|
|
|
36
42
|
# Initiate the adapter
|
|
37
|
-
if kwargs.get(
|
|
38
|
-
logger.warning(
|
|
43
|
+
if kwargs.get("sqlmesh", False):
|
|
44
|
+
logger.warning("SQLMesh adapter is still in EXPERIMENTAL mode.")
|
|
39
45
|
from recce.adapter.sqlmesh_adapter import SqlmeshAdapter
|
|
40
|
-
|
|
46
|
+
|
|
47
|
+
context.adapter_type = "sqlmesh"
|
|
41
48
|
context.adapter = SqlmeshAdapter.load(**kwargs)
|
|
42
49
|
else:
|
|
43
50
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
44
|
-
|
|
51
|
+
|
|
52
|
+
context.adapter_type = "dbt"
|
|
45
53
|
context.adapter = DbtAdapter.load(**kwargs)
|
|
46
54
|
|
|
47
55
|
# Import state
|
|
@@ -52,7 +60,7 @@ class RecceContext:
|
|
|
52
60
|
|
|
53
61
|
if is_review_mode:
|
|
54
62
|
if not state:
|
|
55
|
-
raise Exception(
|
|
63
|
+
raise Exception("The state file is required for review mode")
|
|
56
64
|
|
|
57
65
|
return context
|
|
58
66
|
|
|
@@ -76,14 +84,14 @@ class RecceContext:
|
|
|
76
84
|
curr = self.get_lineage(base=False)
|
|
77
85
|
base = self.get_lineage(base=True)
|
|
78
86
|
|
|
79
|
-
for unique_id, node in curr[
|
|
80
|
-
if excluded_types and node.get(
|
|
87
|
+
for unique_id, node in curr["nodes"].items():
|
|
88
|
+
if excluded_types and node.get("resource_type") in excluded_types:
|
|
81
89
|
continue
|
|
82
|
-
name_to_unique_id[node[
|
|
83
|
-
for unique_id, node in base[
|
|
84
|
-
if excluded_types and node.get(
|
|
90
|
+
name_to_unique_id[node["name"]] = unique_id
|
|
91
|
+
for unique_id, node in base["nodes"].items():
|
|
92
|
+
if excluded_types and node.get("resource_type") in excluded_types:
|
|
85
93
|
continue
|
|
86
|
-
name_to_unique_id[node[
|
|
94
|
+
name_to_unique_id[node["name"]] = unique_id
|
|
87
95
|
return name_to_unique_id
|
|
88
96
|
|
|
89
97
|
def start_monitor_artifacts(self, callback: Callable = None):
|
|
@@ -140,7 +148,7 @@ class RecceContext:
|
|
|
140
148
|
git = GitRepoInfo.from_current_repositroy()
|
|
141
149
|
if git:
|
|
142
150
|
state.git = git
|
|
143
|
-
pr = PullRequestInfo(url=os.getenv(
|
|
151
|
+
pr = PullRequestInfo(url=os.getenv("RECCE_PR_URL"))
|
|
144
152
|
state.pull_request = pr
|
|
145
153
|
|
|
146
154
|
return state
|
|
@@ -152,38 +160,37 @@ class RecceContext:
|
|
|
152
160
|
:param method: merge, revert, overwrite
|
|
153
161
|
|
|
154
162
|
"""
|
|
155
|
-
if method ==
|
|
163
|
+
if method == "merge":
|
|
156
164
|
self.state_loader.refresh()
|
|
157
165
|
self.import_state(self.state_loader.state, merge=True)
|
|
158
166
|
state = self.export_state()
|
|
159
167
|
self.state_loader.export(state)
|
|
160
|
-
elif method ==
|
|
168
|
+
elif method == "revert":
|
|
161
169
|
self.state_loader.refresh()
|
|
162
170
|
self.import_state(self.state_loader.state, merge=False)
|
|
163
|
-
elif method ==
|
|
171
|
+
elif method == "overwrite":
|
|
164
172
|
state = self.export_state()
|
|
165
173
|
self.state_loader.export(state)
|
|
166
174
|
else:
|
|
167
|
-
raise Exception(f
|
|
175
|
+
raise Exception(f"Unsupported method: {method}")
|
|
168
176
|
|
|
169
177
|
def _merge_checks(self, import_checks: list[Check]):
|
|
170
178
|
checks = list(self.checks)
|
|
171
179
|
imports = 0
|
|
172
180
|
|
|
173
181
|
def _calculate_checksum(c: Check):
|
|
174
|
-
payload = json.dumps(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
payload = json.dumps(
|
|
183
|
+
{
|
|
184
|
+
"type": str(c.type),
|
|
185
|
+
"params": c.params,
|
|
186
|
+
"view_options": c.view_options,
|
|
187
|
+
},
|
|
188
|
+
sort_keys=True,
|
|
189
|
+
)
|
|
179
190
|
return hashlib.sha256(payload.encode()).hexdigest()
|
|
180
191
|
|
|
181
|
-
checksum_map = {
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
check_map = {
|
|
185
|
-
c.check_id: c for c in self.checks
|
|
186
|
-
}
|
|
192
|
+
checksum_map = {_calculate_checksum(c): c for c in self.checks if c.is_preset}
|
|
193
|
+
check_map = {c.check_id: c for c in self.checks}
|
|
187
194
|
|
|
188
195
|
# merge checks
|
|
189
196
|
for imported in import_checks:
|
|
@@ -219,12 +226,12 @@ class RecceContext:
|
|
|
219
226
|
return imports
|
|
220
227
|
|
|
221
228
|
def import_state(self, import_state: RecceState, merge: bool = True):
|
|
222
|
-
|
|
229
|
+
"""
|
|
223
230
|
Import the state from another RecceState object.
|
|
224
231
|
|
|
225
232
|
:param import_state: the state to import
|
|
226
233
|
:param merge: whether to merge the state or replace the current state
|
|
227
|
-
|
|
234
|
+
"""
|
|
228
235
|
import_runs = 0
|
|
229
236
|
import_checks = 0
|
|
230
237
|
if merge:
|
|
@@ -261,20 +268,21 @@ class RecceContext:
|
|
|
261
268
|
def mark_onboarding_completed(self):
|
|
262
269
|
if self.state_loader.cloud_mode:
|
|
263
270
|
try:
|
|
264
|
-
token = self.state_loader.cloud_options.get(
|
|
265
|
-
set_recce_cloud_onboarding_state(token,
|
|
271
|
+
token = self.state_loader.cloud_options.get("token")
|
|
272
|
+
set_recce_cloud_onboarding_state(token, "completed")
|
|
266
273
|
except Exception as e:
|
|
267
|
-
logger.debug(f
|
|
274
|
+
logger.debug(f"Failed to mark onboarding completed in Recce Cloud. Reason: {str(e)}")
|
|
268
275
|
else:
|
|
269
276
|
# Skip the onboarding state for non-cloud mode
|
|
270
277
|
pass
|
|
271
278
|
|
|
272
279
|
@staticmethod
|
|
273
280
|
def verify_required_artifacts(**kwargs) -> Tuple[bool, Optional[str]]:
|
|
274
|
-
if kwargs.get(
|
|
281
|
+
if kwargs.get("sqlmesh", False):
|
|
275
282
|
pass
|
|
276
283
|
else:
|
|
277
284
|
from recce.adapter.dbt_adapter import DbtAdapter
|
|
285
|
+
|
|
278
286
|
try:
|
|
279
287
|
DbtAdapter.load(**kwargs)
|
|
280
288
|
except FileNotFoundError as e:
|
|
@@ -289,18 +297,18 @@ class RecceContext:
|
|
|
289
297
|
"""
|
|
290
298
|
The state loader mode is used for telemetry purpose.
|
|
291
299
|
"""
|
|
292
|
-
if os.environ.get(
|
|
293
|
-
return
|
|
300
|
+
if os.environ.get("DEMO", False):
|
|
301
|
+
return "demo"
|
|
294
302
|
|
|
295
303
|
if not self.state_loader:
|
|
296
|
-
return
|
|
304
|
+
return "none"
|
|
297
305
|
|
|
298
306
|
if self.state_loader.cloud_mode:
|
|
299
|
-
return
|
|
307
|
+
return "cloud"
|
|
300
308
|
elif self.state_loader.state_file:
|
|
301
|
-
return
|
|
309
|
+
return "file"
|
|
302
310
|
else:
|
|
303
|
-
return
|
|
311
|
+
return "none"
|
|
304
312
|
|
|
305
313
|
|
|
306
314
|
recce_context: Optional[RecceContext] = None
|
recce/data/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/88b8abc134cfd59a.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-b787cb1a4f2293de.js"/><script src="/_next/static/chunks/1f229bf6-d9fe92e56db8d93b.js" async=""></script><script src="/_next/static/chunks/700-3b65fc3666820d00.js" async=""></script><script src="/_next/static/chunks/main-app-0225a2255968e566.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>recce</title><meta name="description" content="Recce: Data validation toolkit for comprehensive PR review"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><script src="/_next/static/chunks/webpack-b787cb1a4f2293de.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/88b8abc134cfd59a.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[37194,[],\"\"]\n4:I[79137,[],\"\"]\n5:I[63846,[],\"\"]\nb:I[67160,[],\"\"]\n6:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n7:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n8:{\"display\":\"inline-block\"}\n9:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nc:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"q0Xsc9Sd6PDuo1lshYpLu\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/88b8abc134cfd59a.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"suppressHydrationWarning\":true,\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$6\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$7\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$8\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$9\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"recce\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"Recce: Data validation toolkit for comprehensive PR review\"}]]\n3:null\n"])</script></body></html>
|